From e529320e98326538c21b74ec93aa149a6b6965fb Mon Sep 17 00:00:00 2001 From: Paul Hawke Date: Sat, 12 Jul 2025 09:34:09 -0500 Subject: [PATCH] Convert BookmarkPicturesFragment to kotlin --- .../pictures/BookmarkPicturesFragment.java | 218 ------------------ .../pictures/BookmarkPicturesFragment.kt | 201 ++++++++++++++++ .../BookmarkPicturesFragmentUnitTests.kt | 6 +- 3 files changed, 204 insertions(+), 221 deletions(-) delete mode 100644 app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesFragment.java create mode 100644 app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesFragment.kt diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesFragment.java b/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesFragment.java deleted file mode 100644 index 9f02e4631..000000000 --- a/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesFragment.java +++ /dev/null @@ -1,218 +0,0 @@ -package fr.free.nrw.commons.bookmarks.pictures; - -import static android.view.View.GONE; -import static android.view.View.VISIBLE; - -import android.annotation.SuppressLint; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.ListAdapter; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import dagger.android.support.DaggerFragment; -import fr.free.nrw.commons.Media; -import fr.free.nrw.commons.R; -import fr.free.nrw.commons.bookmarks.BookmarkListRootFragment; -import fr.free.nrw.commons.category.GridViewAdapter; -import fr.free.nrw.commons.databinding.FragmentBookmarksPicturesBinding; -import fr.free.nrw.commons.utils.NetworkUtils; -import fr.free.nrw.commons.utils.ViewUtil; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.CompositeDisposable; -import io.reactivex.schedulers.Schedulers; -import java.util.List; -import javax.inject.Inject; -import timber.log.Timber; - -public class BookmarkPicturesFragment extends DaggerFragment { - - private GridViewAdapter gridAdapter; - private CompositeDisposable compositeDisposable = new CompositeDisposable(); - - private FragmentBookmarksPicturesBinding binding; - @Inject - BookmarkPicturesController controller; - - /** - * Create an instance of the fragment with the right bundle parameters - * @return an instance of the fragment - */ - public static BookmarkPicturesFragment newInstance() { - return new BookmarkPicturesFragment(); - } - - @Override - public View onCreateView( - @NonNull LayoutInflater inflater, - ViewGroup container, - Bundle savedInstanceState - ) { - binding = FragmentBookmarksPicturesBinding.inflate(inflater, container, false); - return binding.getRoot(); - } - - @Override - public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - binding.bookmarkedPicturesList.setOnItemClickListener((AdapterView.OnItemClickListener) getParentFragment()); - initList(); - } - - @Override - public void onStop() { - super.onStop(); - controller.stop(); - } - - @Override - public void onDestroy() { - super.onDestroy(); - compositeDisposable.clear(); - binding = null; - } - - @Override - public void onResume() { - super.onResume(); - if (controller.needRefreshBookmarkedPictures()) { - binding.bookmarkedPicturesList.setVisibility(GONE); - if (gridAdapter != null) { - gridAdapter.clear(); - ((BookmarkListRootFragment)getParentFragment()).viewPagerNotifyDataSetChanged(); - } - initList(); - } - } - - /** - * Checks for internet connection and then initializes - * the recycler view with bookmarked pictures - */ - @SuppressLint("CheckResult") - private void initList() { - if (!NetworkUtils.isInternetConnectionEstablished(getContext())) { - handleNoInternet(); - return; - } - - binding.loadingImagesProgressBar.setVisibility(VISIBLE); - binding.statusMessage.setVisibility(GONE); - - compositeDisposable.add(controller.loadBookmarkedPictures() - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(this::handleSuccess, this::handleError)); - } - - /** - * Handles the UI updates for no internet scenario - */ - private void handleNoInternet() { - binding.loadingImagesProgressBar.setVisibility(GONE); - if (gridAdapter == null || gridAdapter.isEmpty()) { - binding.statusMessage.setVisibility(VISIBLE); - binding.statusMessage.setText(getString(R.string.no_internet)); - } else { - ViewUtil.showShortSnackbar(binding.parentLayout, R.string.no_internet); - } - } - - /** - * Logs and handles API error scenario - * @param throwable - */ - private void handleError(Throwable throwable) { - Timber.e(throwable, "Error occurred while loading images inside a category"); - try{ - ViewUtil.showShortSnackbar(binding.getRoot(), R.string.error_loading_images); - initErrorView(); - }catch (Exception e){ - e.printStackTrace(); - } - } - - /** - * Handles the UI updates for a error scenario - */ - private void initErrorView() { - binding.loadingImagesProgressBar.setVisibility(GONE); - if (gridAdapter == null || gridAdapter.isEmpty()) { - binding.statusMessage.setVisibility(VISIBLE); - binding.statusMessage.setText(getString(R.string.no_images_found)); - } else { - binding.statusMessage.setVisibility(GONE); - } - } - - /** - * Handles the UI updates when there is no bookmarks - */ - private void initEmptyBookmarkListView() { - binding.loadingImagesProgressBar.setVisibility(GONE); - if (gridAdapter == null || gridAdapter.isEmpty()) { - binding.statusMessage.setVisibility(VISIBLE); - binding.statusMessage.setText(getString(R.string.bookmark_empty)); - } else { - binding.statusMessage.setVisibility(GONE); - } - } - - /** - * Handles the success scenario - * On first load, it initializes the grid view. On subsequent loads, it adds items to the adapter - * @param collection List of new Media to be displayed - */ - private void handleSuccess(List collection) { - if (collection == null) { - initErrorView(); - return; - } - if (collection.isEmpty()) { - initEmptyBookmarkListView(); - return; - } - - if (gridAdapter == null) { - setAdapter(collection); - } else { - if (gridAdapter.containsAll(collection)) { - binding.loadingImagesProgressBar.setVisibility(GONE); - binding.statusMessage.setVisibility(GONE); - binding.bookmarkedPicturesList.setVisibility(VISIBLE); - binding.bookmarkedPicturesList.setAdapter(gridAdapter); - return; - } - gridAdapter.addItems(collection); - ((BookmarkListRootFragment) getParentFragment()).viewPagerNotifyDataSetChanged(); - } - binding.loadingImagesProgressBar.setVisibility(GONE); - binding.statusMessage.setVisibility(GONE); - binding.bookmarkedPicturesList.setVisibility(VISIBLE); - } - - /** - * Initializes the adapter with a list of Media objects - * @param mediaList List of new Media to be displayed - */ - private void setAdapter(List mediaList) { - gridAdapter = new GridViewAdapter( - this.getContext(), - R.layout.layout_category_images, - mediaList - ); - binding.bookmarkedPicturesList.setAdapter(gridAdapter); - } - - /** - * It return an instance of gridView adapter which helps in extracting media details - * used by the gridView - * @return GridView Adapter - */ - public ListAdapter getAdapter() { - return binding.bookmarkedPicturesList.getAdapter(); - } - -} diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesFragment.kt b/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesFragment.kt new file mode 100644 index 000000000..e8c61371a --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesFragment.kt @@ -0,0 +1,201 @@ +package fr.free.nrw.commons.bookmarks.pictures + +import android.annotation.SuppressLint +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.AdapterView.OnItemClickListener +import android.widget.ListAdapter +import dagger.android.support.DaggerFragment +import fr.free.nrw.commons.Media +import fr.free.nrw.commons.R +import fr.free.nrw.commons.bookmarks.BookmarkListRootFragment +import fr.free.nrw.commons.category.GridViewAdapter +import fr.free.nrw.commons.databinding.FragmentBookmarksPicturesBinding +import fr.free.nrw.commons.utils.NetworkUtils.isInternetConnectionEstablished +import fr.free.nrw.commons.utils.ViewUtil.showShortSnackbar +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.functions.Consumer +import io.reactivex.schedulers.Schedulers +import timber.log.Timber +import javax.inject.Inject + +class BookmarkPicturesFragment : DaggerFragment() { + private var gridAdapter: GridViewAdapter? = null + private val compositeDisposable = CompositeDisposable() + + private var binding: FragmentBookmarksPicturesBinding? = null + + @JvmField + @Inject + var controller: BookmarkPicturesController? = null + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = FragmentBookmarksPicturesBinding.inflate(inflater, container, false) + return binding!!.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding!!.bookmarkedPicturesList.onItemClickListener = + parentFragment as OnItemClickListener? + initList() + } + + override fun onStop() { + super.onStop() + controller!!.stop() + } + + override fun onDestroy() { + super.onDestroy() + compositeDisposable.clear() + binding = null + } + + override fun onResume() { + super.onResume() + if (controller!!.needRefreshBookmarkedPictures()) { + binding!!.bookmarkedPicturesList.visibility = View.GONE + gridAdapter?.let { + it.clear() + (parentFragment as BookmarkListRootFragment).viewPagerNotifyDataSetChanged() + } + initList() + } + } + + /** + * Checks for internet connection and then initializes + * the recycler view with bookmarked pictures + */ + @SuppressLint("CheckResult") + private fun initList() { + if (!isInternetConnectionEstablished(context)) { + handleNoInternet() + return + } + + binding!!.loadingImagesProgressBar.visibility = View.VISIBLE + binding!!.statusMessage.visibility = View.GONE + + compositeDisposable.add( + controller!!.loadBookmarkedPictures() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(::handleSuccess, ::handleError) + ) + } + + /** + * Handles the UI updates for no internet scenario + */ + private fun handleNoInternet() { + binding!!.loadingImagesProgressBar.visibility = View.GONE + if (gridAdapter == null || gridAdapter!!.isEmpty) { + binding!!.statusMessage.visibility = View.VISIBLE + binding!!.statusMessage.text = getString(R.string.no_internet) + } else { + showShortSnackbar(binding!!.parentLayout, R.string.no_internet) + } + } + + /** + * Logs and handles API error scenario + * @param throwable + */ + private fun handleError(throwable: Throwable) { + Timber.e(throwable, "Error occurred while loading images inside a category") + try { + showShortSnackbar(binding!!.root, R.string.error_loading_images) + initErrorView() + } catch (e: Exception) { + Timber.e(e) + } + } + + /** + * Handles the UI updates for a error scenario + */ + private fun initErrorView() { + binding!!.loadingImagesProgressBar.visibility = View.GONE + if (gridAdapter == null || gridAdapter!!.isEmpty) { + binding!!.statusMessage.visibility = View.VISIBLE + binding!!.statusMessage.text = getString(R.string.no_images_found) + } else { + binding!!.statusMessage.visibility = View.GONE + } + } + + /** + * Handles the UI updates when there is no bookmarks + */ + private fun initEmptyBookmarkListView() { + binding!!.loadingImagesProgressBar.visibility = View.GONE + if (gridAdapter == null || gridAdapter!!.isEmpty) { + binding!!.statusMessage.visibility = View.VISIBLE + binding!!.statusMessage.text = getString(R.string.bookmark_empty) + } else { + binding!!.statusMessage.visibility = View.GONE + } + } + + /** + * Handles the success scenario + * On first load, it initializes the grid view. On subsequent loads, it adds items to the adapter + * @param collection List of new Media to be displayed + */ + private fun handleSuccess(collection: List?) { + if (collection == null) { + initErrorView() + return + } + if (collection.isEmpty()) { + initEmptyBookmarkListView() + return + } + + if (gridAdapter == null) { + setAdapter(collection) + } else { + if (gridAdapter!!.containsAll(collection)) { + binding!!.loadingImagesProgressBar.visibility = View.GONE + binding!!.statusMessage.visibility = View.GONE + binding!!.bookmarkedPicturesList.visibility = View.VISIBLE + binding!!.bookmarkedPicturesList.adapter = gridAdapter + return + } + gridAdapter!!.addItems(collection) + (parentFragment as BookmarkListRootFragment).viewPagerNotifyDataSetChanged() + } + binding!!.loadingImagesProgressBar.visibility = View.GONE + binding!!.statusMessage.visibility = View.GONE + binding!!.bookmarkedPicturesList.visibility = View.VISIBLE + } + + /** + * Initializes the adapter with a list of Media objects + * @param mediaList List of new Media to be displayed + */ + private fun setAdapter(mediaList: List) { + gridAdapter = GridViewAdapter( + requireContext(), + R.layout.layout_category_images, + mediaList.toMutableList() + ) + binding?.let { it.bookmarkedPicturesList.adapter = gridAdapter } + } + + /** + * It return an instance of gridView adapter which helps in extracting media details + * used by the gridView + * @return GridView Adapter + */ + fun getAdapter(): ListAdapter? = binding?.bookmarkedPicturesList?.adapter +} diff --git a/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesFragmentUnitTests.kt b/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesFragmentUnitTests.kt index 03de2638d..b1dae0fab 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesFragmentUnitTests.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesFragmentUnitTests.kt @@ -88,7 +88,7 @@ class BookmarkPicturesFragmentUnitTests { context = ApplicationProvider.getApplicationContext() OkHttpConnectionFactory.CLIENT = createTestClient() val activity = Robolectric.buildActivity(ProfileActivity::class.java).create().get() - fragment = BookmarkPicturesFragment.newInstance() + fragment = BookmarkPicturesFragment() val fragmentManager: FragmentManager = activity.supportFragmentManager val fragmentTransaction: FragmentTransaction = fragmentManager.beginTransaction() fragmentTransaction.add(fragment, null) @@ -156,13 +156,13 @@ class BookmarkPicturesFragmentUnitTests { val method: Method = BookmarkPicturesFragment::class.java.getDeclaredMethod("setAdapter", List::class.java) method.isAccessible = true - method.invoke(fragment, mediaList) + method.invoke(fragment, emptyList()) } @Test @Throws(Exception::class) fun testGetAdapter() { - fragment.adapter + fragment.getAdapter() } @Test