Fix space problem of categoriesPresenter.kt

This commit is contained in:
Zihan Pan 2024-10-20 20:11:51 +11:00
parent 1e31497f10
commit 75ec41bdb5

View file

@ -28,302 +28,302 @@ import javax.inject.Singleton
*/ */
@Singleton @Singleton
class CategoriesPresenter class CategoriesPresenter
@Inject
constructor(
private val repository: UploadRepository,
@param:Named(CommonsApplicationModule.IO_THREAD) private val ioScheduler: Scheduler,
@param:Named(CommonsApplicationModule.MAIN_THREAD) private val mainThreadScheduler: Scheduler,
) : CategoriesContract.UserActionListener {
companion object {
private val DUMMY: CategoriesContract.View = proxy()
}
var view = DUMMY
private val compositeDisposable = CompositeDisposable()
private val searchTerms = PublishSubject.create<String>()
private var categoryList: MutableLiveData<List<CategoryItem>> = MutableLiveData()
/**
* Current media
*/
private var media: Media? = null
/**
* helper class for editing categories
*/
@Inject @Inject
lateinit var categoryEditHelper: CategoryEditHelper constructor(
private val repository: UploadRepository,
@param:Named(CommonsApplicationModule.IO_THREAD) private val ioScheduler: Scheduler,
@param:Named(CommonsApplicationModule.MAIN_THREAD) private val mainThreadScheduler: Scheduler,
) : CategoriesContract.UserActionListener {
companion object {
private val DUMMY: CategoriesContract.View = proxy()
}
@SuppressLint("TimberArgCount") var view = DUMMY
override fun onAttachView(view: CategoriesContract.View) { private val compositeDisposable = CompositeDisposable()
this.view = view private val searchTerms = PublishSubject.create<String>()
compositeDisposable.add( private var categoryList: MutableLiveData<List<CategoryItem>> = MutableLiveData()
searchTerms
.observeOn(mainThreadScheduler)
.doOnNext {
view.showProgress(true)
}.switchMap(::searchResults)
.map { repository.selectedCategories + it }
.map { it.distinctBy { categoryItem -> categoryItem.name } }
.observeOn(mainThreadScheduler)
.subscribe(
{
setCategoryListValue(it)
view.showProgress(false)
if (it.isEmpty() && !isInitialLoad) {
view.showError(R.string.no_categories_found)
}
},
{ t: Throwable? ->
view.showProgress(false)
view.showError(R.string.no_categories_found)
Timber.e(t)
},
),
)
//isInitialLoad = false /**
} * Current media
*/
private var media: Media? = null
private var isInitialLoad = true //avoid initial empty content of edittext lead to showError /**
* helper class for editing categories
*/
@Inject
lateinit var categoryEditHelper: CategoryEditHelper
@SuppressLint("TimberArgCount")
/** override fun onAttachView(view: CategoriesContract.View) {
* If media is null : Fetches categories from server according to the term this.view = view
* Else : Fetches existing categories by their name, fetches categories from server according compositeDisposable.add(
* to the term and combines both in a list searchTerms
*/ .observeOn(mainThreadScheduler)
private fun searchResults(term: String): Observable<List<CategoryItem>>? { .doOnNext {
if (media == null) { view.showProgress(true)
return repository }.switchMap(::searchResults)
.searchAll(term, getImageTitleList(), repository.selectedDepictions) .map { repository.selectedCategories + it }
.subscribeOn(ioScheduler) .map { it.distinctBy { categoryItem -> categoryItem.name } }
.map { .observeOn(mainThreadScheduler)
it.filter { categoryItem -> .subscribe(
!repository.isSpammyCategory(categoryItem.name) || {
categoryItem.name == term setCategoryListValue(it)
} view.showProgress(false)
} if (it.isEmpty() && !isInitialLoad) {
} else { view.showError(R.string.no_categories_found)
return Observable
.zip(
repository
.getCategories(repository.selectedExistingCategories)
.map { list ->
list.map {
CategoryItem(it.name, it.description, it.thumbnail, true)
} }
}, },
repository.searchAll(term, getImageTitleList(), repository.selectedDepictions), { t: Throwable? ->
) { it1, it2 -> view.showProgress(false)
it1 + it2
}.subscribeOn(ioScheduler)
.map {
it.filter { categoryItem ->
!repository.isSpammyCategory(categoryItem.name) ||
categoryItem.name == term
}
}.map { it.filterNot { categoryItem -> categoryItem.thumbnail == "hidden" } }
}
}
override fun onDetachView() {
view = DUMMY
compositeDisposable.clear()
isInitialLoad = true
}
/**
* asks the repository to fetch categories for the query
* @param query
*/
override fun searchForCategories(query: String) {
if (query.isBlank()) {
if (!isInitialLoad) {
view.showError(R.string.no_categories_found)
}
return
}
isInitialLoad = false
searchTerms.onNext(query)
}
/**
* Returns image title list from UploadItem
* @return
*/
private fun getImageTitleList(): List<String> =
repository.uploads
.map { it.uploadMediaDetails[0].captionText }
.filterNot { TextUtils.isEmpty(it) }
/**
* Verifies the number of categories selected, prompts the user if none selected
*/
override fun verifyCategories() {
val selectedCategories = repository.selectedCategories
if (selectedCategories.isNotEmpty()) {
repository.setSelectedCategories(selectedCategories.map { it.name })
view.goToNextScreen()
} else {
view.showNoCategorySelected()
}
}
/**
* ask repository to handle category clicked
*
* @param categoryItem
*/
override fun onCategoryItemClicked(categoryItem: CategoryItem) {
repository.onCategoryClicked(categoryItem, media)
}
/**
* Attaches view and media
*/
override fun onAttachViewWithMedia(
view: CategoriesContract.View,
media: Media,
) {
this.view = view
this.media = media
repository.selectedExistingCategories = view.existingCategories
compositeDisposable.add(
searchTerms
.observeOn(mainThreadScheduler)
.doOnNext {
view.showProgress(true)
}.switchMap(::searchResults)
.map { repository.selectedCategories + it }
.map { it.distinctBy { categoryItem -> categoryItem.name } }
.observeOn(mainThreadScheduler)
.subscribe(
{
setCategoryListValue(it)
view.showProgress(false)
if (it.isEmpty() && !isInitialLoad) {
view.showError(R.string.no_categories_found) view.showError(R.string.no_categories_found)
Timber.e(t)
},
),
)
//isInitialLoad = false
}
private var isInitialLoad = true //avoid initial empty content of edittext lead to showError
/**
* If media is null : Fetches categories from server according to the term
* Else : Fetches existing categories by their name, fetches categories from server according
* to the term and combines both in a list
*/
private fun searchResults(term: String): Observable<List<CategoryItem>>? {
if (media == null) {
return repository
.searchAll(term, getImageTitleList(), repository.selectedDepictions)
.subscribeOn(ioScheduler)
.map {
it.filter { categoryItem ->
!repository.isSpammyCategory(categoryItem.name) ||
categoryItem.name == term
} }
}, }
{ t: Throwable? -> } else {
view.showProgress(false) return Observable
view.showError(R.string.no_categories_found) .zip(
Timber.e(t) repository
}, .getCategories(repository.selectedExistingCategories)
), .map { list ->
) list.map {
} CategoryItem(it.name, it.description, it.thumbnail, true)
}
/** },
* Clears previous selections repository.searchAll(term, getImageTitleList(), repository.selectedDepictions),
*/ ) { it1, it2 ->
override fun clearPreviousSelection() { it1 + it2
repository.cleanup() }.subscribeOn(ioScheduler)
} .map {
it.filter { categoryItem ->
/** !repository.isSpammyCategory(categoryItem.name) ||
* Gets the selected categories and send them for posting to the server categoryItem.name == term
* }
* @param media media }.map { it.filterNot { categoryItem -> categoryItem.thumbnail == "hidden" } }
* @param wikiText current WikiText from server
*/
override fun updateCategories(
media: Media,
wikiText: String,
) {
// check if view.existingCategories is null
if (repository.selectedCategories.isNotEmpty() ||
(view.existingCategories != null && repository.selectedExistingCategories.size != view.existingCategories.size)
) {
val selectedCategories: MutableList<String> =
(
repository.selectedCategories.map { it.name }.toMutableList() +
repository.selectedExistingCategories
).toMutableList()
if (selectedCategories.isNotEmpty()) {
view.showProgressDialog()
try {
compositeDisposable.add(
categoryEditHelper
.makeCategoryEdit(
view.fragmentContext,
media,
selectedCategories,
wikiText,
).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
Timber.d("Categories are added.")
media.addedCategories = selectedCategories
repository.cleanup()
view.dismissProgressDialog()
view.refreshCategories()
view.goBackToPreviousScreen()
}, {
Timber.e(
"Failed to update categories",
)
}),
)
} catch (e: InvalidLoginTokenException) {
view.navigateToLoginScreen()
}
} }
} else { }
override fun onDetachView() {
view = DUMMY
compositeDisposable.clear()
isInitialLoad = true
}
/**
* asks the repository to fetch categories for the query
* @param query
*/
override fun searchForCategories(query: String) {
if (query.isBlank()) {
if (!isInitialLoad) {
view.showError(R.string.no_categories_found)
}
return
}
isInitialLoad = false
searchTerms.onNext(query)
}
/**
* Returns image title list from UploadItem
* @return
*/
private fun getImageTitleList(): List<String> =
repository.uploads
.map { it.uploadMediaDetails[0].captionText }
.filterNot { TextUtils.isEmpty(it) }
/**
* Verifies the number of categories selected, prompts the user if none selected
*/
override fun verifyCategories() {
val selectedCategories = repository.selectedCategories
if (selectedCategories.isNotEmpty()) {
repository.setSelectedCategories(selectedCategories.map { it.name })
view.goToNextScreen()
} else {
view.showNoCategorySelected()
}
}
/**
* ask repository to handle category clicked
*
* @param categoryItem
*/
override fun onCategoryItemClicked(categoryItem: CategoryItem) {
repository.onCategoryClicked(categoryItem, media)
}
/**
* Attaches view and media
*/
override fun onAttachViewWithMedia(
view: CategoriesContract.View,
media: Media,
) {
this.view = view
this.media = media
repository.selectedExistingCategories = view.existingCategories
compositeDisposable.add(
searchTerms
.observeOn(mainThreadScheduler)
.doOnNext {
view.showProgress(true)
}.switchMap(::searchResults)
.map { repository.selectedCategories + it }
.map { it.distinctBy { categoryItem -> categoryItem.name } }
.observeOn(mainThreadScheduler)
.subscribe(
{
setCategoryListValue(it)
view.showProgress(false)
if (it.isEmpty() && !isInitialLoad) {
view.showError(R.string.no_categories_found)
}
},
{ t: Throwable? ->
view.showProgress(false)
view.showError(R.string.no_categories_found)
Timber.e(t)
},
),
)
}
/**
* Clears previous selections
*/
override fun clearPreviousSelection() {
repository.cleanup() repository.cleanup()
view.showNoCategorySelected()
}
}
/**
* Selects each [CategoryItem] in a given list as if they were clicked by the user by calling
* [onCategoryItemClicked] for each category and adding the category to [categoryList]
*/
private fun selectNewCategories(toSelect: List<CategoryItem>) {
toSelect.forEach {
it.isSelected = true
repository.onCategoryClicked(it, media)
} }
// Add the new selections to the list of category items so that the selections appear /**
// immediately (i.e. without any search term queries) * Gets the selected categories and send them for posting to the server
categoryList.value *
?.toMutableList() * @param media media
?.let { toSelect + it } * @param wikiText current WikiText from server
?.distinctBy(CategoryItem::name) */
?.let { setCategoryListValue(it) } override fun updateCategories(
} media: Media,
wikiText: String,
) {
// check if view.existingCategories is null
if (repository.selectedCategories.isNotEmpty() ||
(view.existingCategories != null && repository.selectedExistingCategories.size != view.existingCategories.size)
) {
val selectedCategories: MutableList<String> =
(
repository.selectedCategories.map { it.name }.toMutableList() +
repository.selectedExistingCategories
).toMutableList()
/** if (selectedCategories.isNotEmpty()) {
* Livedata being used to observe category list inside view.showProgressDialog()
* @see UploadCategoriesFragment
* Any changes to category list reflect immediately to the adapter list
*/
override fun getCategories(): LiveData<List<CategoryItem>> = categoryList
/** try {
* needed for tests compositeDisposable.add(
*/ categoryEditHelper
fun setCategoryList(categoryList: MutableLiveData<List<CategoryItem>>) { .makeCategoryEdit(
this.categoryList = categoryList view.fragmentContext,
} media,
selectedCategories,
wikiText,
).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
Timber.d("Categories are added.")
media.addedCategories = selectedCategories
repository.cleanup()
view.dismissProgressDialog()
view.refreshCategories()
view.goBackToPreviousScreen()
}, {
Timber.e(
"Failed to update categories",
)
}),
)
} catch (e: InvalidLoginTokenException) {
view.navigateToLoginScreen()
}
}
} else {
repository.cleanup()
view.showNoCategorySelected()
}
}
/** /**
* needed for tests * Selects each [CategoryItem] in a given list as if they were clicked by the user by calling
*/ * [onCategoryItemClicked] for each category and adding the category to [categoryList]
fun setCategoryListValue(categoryItems: List<CategoryItem>) { */
categoryList.postValue(categoryItems) private fun selectNewCategories(toSelect: List<CategoryItem>) {
} toSelect.forEach {
it.isSelected = true
repository.onCategoryClicked(it, media)
}
override fun selectCategories() { // Add the new selections to the list of category items so that the selections appear
compositeDisposable.add( // immediately (i.e. without any search term queries)
repository.placeCategories categoryList.value
.subscribeOn(ioScheduler) ?.toMutableList()
.observeOn(mainThreadScheduler) ?.let { toSelect + it }
.subscribe(::selectNewCategories), ?.distinctBy(CategoryItem::name)
) ?.let { setCategoryListValue(it) }
}
/**
* Livedata being used to observe category list inside
* @see UploadCategoriesFragment
* Any changes to category list reflect immediately to the adapter list
*/
override fun getCategories(): LiveData<List<CategoryItem>> = categoryList
/**
* needed for tests
*/
fun setCategoryList(categoryList: MutableLiveData<List<CategoryItem>>) {
this.categoryList = categoryList
}
/**
* needed for tests
*/
fun setCategoryListValue(categoryItems: List<CategoryItem>) {
categoryList.postValue(categoryItems)
}
override fun selectCategories() {
compositeDisposable.add(
repository.placeCategories
.subscribeOn(ioScheduler)
.observeOn(mainThreadScheduler)
.subscribe(::selectNewCategories),
)
}
} }
}