diff --git a/app/src/main/java/fr/free/nrw/commons/CommonsApplication.kt b/app/src/main/java/fr/free/nrw/commons/CommonsApplication.kt index 90ab0393a..89fdaa055 100644 --- a/app/src/main/java/fr/free/nrw/commons/CommonsApplication.kt +++ b/app/src/main/java/fr/free/nrw/commons/CommonsApplication.kt @@ -15,9 +15,8 @@ import com.facebook.drawee.backends.pipeline.Fresco import com.facebook.imagepipeline.core.ImagePipelineConfig import fr.free.nrw.commons.auth.LoginActivity import fr.free.nrw.commons.auth.SessionManager -import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao -import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao -import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao +import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable +import fr.free.nrw.commons.bookmarks.pictures.BookmarksTable import fr.free.nrw.commons.category.CategoryDao import fr.free.nrw.commons.concurrency.BackgroundPoolExceptionHandler import fr.free.nrw.commons.concurrency.ThreadPoolService @@ -257,8 +256,8 @@ class CommonsApplication : MultiDexApplication() { } catch (e: SQLiteException) { Timber.e(e) } - BookmarkPicturesDao.Table.onDelete(db) - BookmarkItemsDao.Table.onDelete(db) + BookmarksTable.onDelete(db) + BookmarkItemsTable.onDelete(db) } diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarkFragment.java b/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarkFragment.java deleted file mode 100644 index 9100fb63c..000000000 --- a/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarkFragment.java +++ /dev/null @@ -1,105 +0,0 @@ -package fr.free.nrw.commons.bookmarks; - -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.FragmentManager; -import fr.free.nrw.commons.contributions.MainActivity; -import fr.free.nrw.commons.databinding.FragmentBookmarksBinding; -import fr.free.nrw.commons.di.CommonsDaggerSupportFragment; -import fr.free.nrw.commons.kvstore.JsonKvStore; -import fr.free.nrw.commons.theme.BaseActivity; -import javax.inject.Inject; -import fr.free.nrw.commons.contributions.ContributionController; -import javax.inject.Named; - -public class BookmarkFragment extends CommonsDaggerSupportFragment { - - private FragmentManager supportFragmentManager; - private BookmarksPagerAdapter adapter; - FragmentBookmarksBinding binding; - - @Inject - ContributionController controller; - /** - * To check if the user is loggedIn or not. - */ - @Inject - @Named("default_preferences") - public - JsonKvStore applicationKvStore; - - @NonNull - public static BookmarkFragment newInstance() { - BookmarkFragment fragment = new BookmarkFragment(); - fragment.setRetainInstance(true); - return fragment; - } - - public void setScroll(boolean canScroll) { - if (binding!=null) { - binding.viewPagerBookmarks.setCanScroll(canScroll); - } - } - - @Override - public void onCreate(@Nullable final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - } - - @Nullable - @Override - public View onCreateView(@NonNull final LayoutInflater inflater, - @Nullable final ViewGroup container, - @Nullable final Bundle savedInstanceState) { - super.onCreateView(inflater, container, savedInstanceState); - binding = FragmentBookmarksBinding.inflate(inflater, container, false); - - // Activity can call methods in the fragment by acquiring a - // reference to the Fragment from FragmentManager, using findFragmentById() - supportFragmentManager = getChildFragmentManager(); - - adapter = new BookmarksPagerAdapter(supportFragmentManager, getContext(), - applicationKvStore.getBoolean("login_skipped")); - binding.viewPagerBookmarks.setAdapter(adapter); - binding.tabLayout.setupWithViewPager(binding.viewPagerBookmarks); - - ((MainActivity) getActivity()).showTabs(); - ((BaseActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false); - - setupTabLayout(); - return binding.getRoot(); - } - - /** - * This method sets up the tab layout. If the adapter has only one element it sets the - * visibility of tabLayout to gone. - */ - public void setupTabLayout() { - binding.tabLayout.setVisibility(View.VISIBLE); - if (adapter.getCount() == 1) { - binding.tabLayout.setVisibility(View.GONE); - } - } - - - public void onBackPressed() { - if (((BookmarkListRootFragment) (adapter.getItem(binding.tabLayout.getSelectedTabPosition()))) - .backPressed()) { - // The event is handled internally by the adapter , no further action required. - return; - } - // Event is not handled by the adapter ( performed back action ) change action bar. - ((BaseActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false); - } - - @Override - public void onDestroy() { - super.onDestroy(); - binding = null; - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarkFragment.kt b/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarkFragment.kt new file mode 100644 index 000000000..48db40ad0 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarkFragment.kt @@ -0,0 +1,98 @@ +package fr.free.nrw.commons.bookmarks + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import fr.free.nrw.commons.contributions.ContributionController +import fr.free.nrw.commons.contributions.MainActivity +import fr.free.nrw.commons.databinding.FragmentBookmarksBinding +import fr.free.nrw.commons.di.CommonsDaggerSupportFragment +import fr.free.nrw.commons.kvstore.JsonKvStore +import fr.free.nrw.commons.theme.BaseActivity +import javax.inject.Inject +import javax.inject.Named + +class BookmarkFragment : CommonsDaggerSupportFragment() { + private var adapter: BookmarksPagerAdapter? = null + + @JvmField + var binding: FragmentBookmarksBinding? = null + + @JvmField + @Inject + var controller: ContributionController? = null + + /** + * To check if the user is loggedIn or not. + */ + @JvmField + @Inject + @Named("default_preferences") + var applicationKvStore: JsonKvStore? = null + + fun setScroll(canScroll: Boolean) { + binding?.let { + it.viewPagerBookmarks.isCanScroll = canScroll + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + super.onCreateView(inflater, container, savedInstanceState) + binding = FragmentBookmarksBinding.inflate(inflater, container, false) + + // Activity can call methods in the fragment by acquiring a + // reference to the Fragment from FragmentManager, using findFragmentById() + val supportFragmentManager = childFragmentManager + + adapter = BookmarksPagerAdapter( + supportFragmentManager, requireContext(), + applicationKvStore!!.getBoolean("login_skipped") + ) + binding!!.viewPagerBookmarks.adapter = adapter + binding!!.tabLayout.setupWithViewPager(binding!!.viewPagerBookmarks) + + (requireActivity() as MainActivity).showTabs() + (requireActivity() as BaseActivity).supportActionBar!!.setDisplayHomeAsUpEnabled(false) + + setupTabLayout() + return binding!!.root + } + + /** + * This method sets up the tab layout. If the adapter has only one element it sets the + * visibility of tabLayout to gone. + */ + fun setupTabLayout() { + binding!!.tabLayout.visibility = View.VISIBLE + if (adapter!!.count == 1) { + binding!!.tabLayout.visibility = View.GONE + } + } + + + fun onBackPressed() { + if (((adapter!!.getItem(binding!!.tabLayout.selectedTabPosition)) as BookmarkListRootFragment).backPressed()) { + // The event is handled internally by the adapter , no further action required. + return + } + + // Event is not handled by the adapter ( performed back action ) change action bar. + (requireActivity() as BaseActivity).supportActionBar!!.setDisplayHomeAsUpEnabled(false) + } + + override fun onDestroy() { + super.onDestroy() + binding = null + } + + companion object { + fun newInstance(): BookmarkFragment = BookmarkFragment().apply { + retainInstance = true + } + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarkListRootFragment.java b/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarkListRootFragment.java deleted file mode 100644 index e14cbbb6f..000000000 --- a/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarkListRootFragment.java +++ /dev/null @@ -1,267 +0,0 @@ -package fr.free.nrw.commons.bookmarks; - -import android.content.Context; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import fr.free.nrw.commons.Media; -import fr.free.nrw.commons.R; -import fr.free.nrw.commons.bookmarks.category.BookmarkCategoriesFragment; -import fr.free.nrw.commons.bookmarks.items.BookmarkItemsFragment; -import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsFragment; -import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesFragment; -import fr.free.nrw.commons.category.CategoryImagesCallback; -import fr.free.nrw.commons.category.GridViewAdapter; -import fr.free.nrw.commons.contributions.MainActivity; -import fr.free.nrw.commons.databinding.FragmentFeaturedRootBinding; -import fr.free.nrw.commons.di.CommonsDaggerSupportFragment; -import fr.free.nrw.commons.media.MediaDetailPagerFragment; -import fr.free.nrw.commons.media.MediaDetailProvider; -import fr.free.nrw.commons.navtab.NavTab; -import java.util.ArrayList; -import java.util.Iterator; -import timber.log.Timber; - -public class BookmarkListRootFragment extends CommonsDaggerSupportFragment implements - FragmentManager.OnBackStackChangedListener, - MediaDetailProvider, - AdapterView.OnItemClickListener, CategoryImagesCallback { - - private MediaDetailPagerFragment mediaDetails; - //private BookmarkPicturesFragment bookmarkPicturesFragment; - private BookmarkLocationsFragment bookmarkLocationsFragment; - public Fragment listFragment; - private BookmarksPagerAdapter bookmarksPagerAdapter; - - FragmentFeaturedRootBinding binding; - - public BookmarkListRootFragment() { - //empty constructor necessary otherwise crashes on recreate - } - - public BookmarkListRootFragment(Bundle bundle, BookmarksPagerAdapter bookmarksPagerAdapter) { - String title = bundle.getString("categoryName"); - int order = bundle.getInt("order"); - final int orderItem = bundle.getInt("orderItem"); - - switch (order){ - case 0: listFragment = new BookmarkPicturesFragment(); - break; - - case 1: listFragment = new BookmarkLocationsFragment(); - break; - - case 3: listFragment = new BookmarkCategoriesFragment(); - break; - } - if(orderItem == 2) { - listFragment = new BookmarkItemsFragment(); - } - - Bundle featuredArguments = new Bundle(); - featuredArguments.putString("categoryName", title); - listFragment.setArguments(featuredArguments); - this.bookmarksPagerAdapter = bookmarksPagerAdapter; - } - - @Nullable - @Override - public View onCreateView(@NonNull final LayoutInflater inflater, - @Nullable final ViewGroup container, - @Nullable final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - binding = FragmentFeaturedRootBinding.inflate(inflater, container, false); - return binding.getRoot(); - } - - @Override - public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - if (savedInstanceState == null) { - setFragment(listFragment, mediaDetails); - } - } - - public void setFragment(Fragment fragment, Fragment otherFragment) { - if (fragment.isAdded() && otherFragment != null) { - getChildFragmentManager() - .beginTransaction() - .hide(otherFragment) - .show(fragment) - .addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG") - .commit(); - getChildFragmentManager().executePendingTransactions(); - } else if (fragment.isAdded() && otherFragment == null) { - getChildFragmentManager() - .beginTransaction() - .show(fragment) - .addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG") - .commit(); - getChildFragmentManager().executePendingTransactions(); - } else if (!fragment.isAdded() && otherFragment != null) { - getChildFragmentManager() - .beginTransaction() - .hide(otherFragment) - .add(R.id.explore_container, fragment) - .addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG") - .commit(); - getChildFragmentManager().executePendingTransactions(); - } else if (!fragment.isAdded()) { - getChildFragmentManager() - .beginTransaction() - .replace(R.id.explore_container, fragment) - .addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG") - .commit(); - getChildFragmentManager().executePendingTransactions(); - } - } - - public void removeFragment(Fragment fragment) { - getChildFragmentManager() - .beginTransaction() - .remove(fragment) - .commit(); - getChildFragmentManager().executePendingTransactions(); - } - - @Override - public void onAttach(final Context context) { - super.onAttach(context); - } - - @Override - public void onMediaClicked(int position) { - Timber.d("on media clicked"); - /*container.setVisibility(View.VISIBLE); - ((BookmarkFragment)getParentFragment()).tabLayout.setVisibility(View.GONE); - mediaDetails = new MediaDetailPagerFragment(false, true, position); - setFragment(mediaDetails, bookmarkPicturesFragment);*/ - } - - /** - * This method is called mediaDetailPagerFragment. It returns the Media Object at that Index - * - * @param i It is the index of which media object is to be returned which is same as current - * index of viewPager. - * @return Media Object - */ - @Override - public Media getMediaAtPosition(int i) { - if (bookmarksPagerAdapter.getMediaAdapter() == null) { - // not yet ready to return data - return null; - } else { - return (Media) bookmarksPagerAdapter.getMediaAdapter().getItem(i); - } - } - - /** - * This method is called on from getCount of MediaDetailPagerFragment The viewpager will contain - * same number of media items as that of media elements in adapter. - * - * @return Total Media count in the adapter - */ - @Override - public int getTotalMediaCount() { - if (bookmarksPagerAdapter.getMediaAdapter() == null) { - return 0; - } - return bookmarksPagerAdapter.getMediaAdapter().getCount(); - } - - @Override - public Integer getContributionStateAt(int position) { - return null; - } - - /** - * Reload media detail fragment once media is nominated - * - * @param index item position that has been nominated - */ - @Override - public void refreshNominatedMedia(int index) { - if (mediaDetails != null && !listFragment.isVisible()) { - removeFragment(mediaDetails); - mediaDetails = MediaDetailPagerFragment.newInstance(false, true); - ((BookmarkFragment) getParentFragment()).setScroll(false); - setFragment(mediaDetails, listFragment); - mediaDetails.showImage(index); - } - } - - /** - * This method is called on success of API call for featured images or mobile uploads. The - * viewpager will notified that number of items have changed. - */ - @Override - public void viewPagerNotifyDataSetChanged() { - if (mediaDetails != null) { - mediaDetails.notifyDataSetChanged(); - } - } - - public boolean backPressed() { - //check mediaDetailPage fragment is not null then we check mediaDetail.is Visible or not to avoid NullPointerException - if (mediaDetails != null) { - if (mediaDetails.isVisible()) { - // todo add get list fragment - ((BookmarkFragment) getParentFragment()).setupTabLayout(); - ArrayList removed = mediaDetails.getRemovedItems(); - removeFragment(mediaDetails); - ((BookmarkFragment) getParentFragment()).setScroll(true); - setFragment(listFragment, mediaDetails); - ((MainActivity) getActivity()).showTabs(); - if (listFragment instanceof BookmarkPicturesFragment) { - GridViewAdapter adapter = ((GridViewAdapter) ((BookmarkPicturesFragment) listFragment) - .getAdapter()); - Iterator i = removed.iterator(); - while (i.hasNext()) { - adapter.remove(adapter.getItem((int) i.next())); - } - mediaDetails.clearRemoved(); - - } - } else { - moveToContributionsFragment(); - } - } else { - moveToContributionsFragment(); - } - // notify mediaDetails did not handled the backPressed further actions required. - return false; - } - - void moveToContributionsFragment() { - ((MainActivity) getActivity()).setSelectedItemId(NavTab.CONTRIBUTIONS.code()); - ((MainActivity) getActivity()).showTabs(); - } - - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - Timber.d("on media clicked"); - binding.exploreContainer.setVisibility(View.VISIBLE); - ((BookmarkFragment) getParentFragment()).binding.tabLayout.setVisibility(View.GONE); - mediaDetails = MediaDetailPagerFragment.newInstance(false, true); - ((BookmarkFragment) getParentFragment()).setScroll(false); - setFragment(mediaDetails, listFragment); - mediaDetails.showImage(position); - } - - @Override - public void onBackStackChanged() { - - } - - @Override - public void onDestroy() { - super.onDestroy(); - binding = null; - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarkListRootFragment.kt b/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarkListRootFragment.kt new file mode 100644 index 000000000..a9ed33abc --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarkListRootFragment.kt @@ -0,0 +1,226 @@ +package fr.free.nrw.commons.bookmarks + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.AdapterView +import android.widget.AdapterView.OnItemClickListener +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import fr.free.nrw.commons.Media +import fr.free.nrw.commons.R +import fr.free.nrw.commons.bookmarks.category.BookmarkCategoriesFragment +import fr.free.nrw.commons.bookmarks.items.BookmarkItemsFragment +import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsFragment +import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesFragment +import fr.free.nrw.commons.category.CategoryImagesCallback +import fr.free.nrw.commons.category.GridViewAdapter +import fr.free.nrw.commons.contributions.MainActivity +import fr.free.nrw.commons.databinding.FragmentFeaturedRootBinding +import fr.free.nrw.commons.di.CommonsDaggerSupportFragment +import fr.free.nrw.commons.media.MediaDetailPagerFragment +import fr.free.nrw.commons.media.MediaDetailPagerFragment.Companion.newInstance +import fr.free.nrw.commons.media.MediaDetailProvider +import fr.free.nrw.commons.navtab.NavTab +import timber.log.Timber + +class BookmarkListRootFragment : CommonsDaggerSupportFragment, + FragmentManager.OnBackStackChangedListener, MediaDetailProvider, OnItemClickListener, + CategoryImagesCallback { + private var mediaDetails: MediaDetailPagerFragment? = null + private val bookmarkLocationsFragment: BookmarkLocationsFragment? = null + var listFragment: Fragment? = null + private var bookmarksPagerAdapter: BookmarksPagerAdapter? = null + + var binding: FragmentFeaturedRootBinding? = null + + constructor() + + constructor(bundle: Bundle, bookmarksPagerAdapter: BookmarksPagerAdapter) { + val title = bundle.getString("categoryName") + val order = bundle.getInt("order") + val orderItem = bundle.getInt("orderItem") + + when (order) { + 0 -> listFragment = BookmarkPicturesFragment() + 1 -> listFragment = BookmarkLocationsFragment() + 3 -> listFragment = BookmarkCategoriesFragment() + } + if (orderItem == 2) { + listFragment = BookmarkItemsFragment() + } + + val featuredArguments = Bundle() + featuredArguments.putString("categoryName", title) + listFragment!!.setArguments(featuredArguments) + this.bookmarksPagerAdapter = bookmarksPagerAdapter + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + super.onCreate(savedInstanceState) + binding = FragmentFeaturedRootBinding.inflate(inflater, container, false) + return binding!!.getRoot() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + if (savedInstanceState == null) { + setFragment(listFragment!!, mediaDetails) + } + } + + fun setFragment(fragment: Fragment, otherFragment: Fragment?) { + if (fragment.isAdded() && otherFragment != null) { + getChildFragmentManager() + .beginTransaction() + .hide(otherFragment) + .show(fragment) + .addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG") + .commit() + getChildFragmentManager().executePendingTransactions() + } else if (fragment.isAdded() && otherFragment == null) { + getChildFragmentManager() + .beginTransaction() + .show(fragment) + .addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG") + .commit() + getChildFragmentManager().executePendingTransactions() + } else if (!fragment.isAdded() && otherFragment != null) { + getChildFragmentManager() + .beginTransaction() + .hide(otherFragment) + .add(R.id.explore_container, fragment) + .addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG") + .commit() + getChildFragmentManager().executePendingTransactions() + } else if (!fragment.isAdded()) { + getChildFragmentManager() + .beginTransaction() + .replace(R.id.explore_container, fragment) + .addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG") + .commit() + getChildFragmentManager().executePendingTransactions() + } + } + + fun removeFragment(fragment: Fragment) { + getChildFragmentManager() + .beginTransaction() + .remove(fragment) + .commit() + getChildFragmentManager().executePendingTransactions() + } + + override fun onMediaClicked(position: Int) { + Timber.d("on media clicked") + /*container.setVisibility(View.VISIBLE); + ((BookmarkFragment)getParentFragment()).tabLayout.setVisibility(View.GONE); + mediaDetails = new MediaDetailPagerFragment(false, true, position); + setFragment(mediaDetails, bookmarkPicturesFragment);*/ + } + + /** + * This method is called mediaDetailPagerFragment. It returns the Media Object at that Index + * + * @param i It is the index of which media object is to be returned which is same as current + * index of viewPager. + * @return Media Object + */ + override fun getMediaAtPosition(i: Int): Media? = + bookmarksPagerAdapter!!.mediaAdapter?.getItem(i) as Media? + + /** + * This method is called on from getCount of MediaDetailPagerFragment The viewpager will contain + * same number of media items as that of media elements in adapter. + * + * @return Total Media count in the adapter + */ + override fun getTotalMediaCount(): Int = + bookmarksPagerAdapter!!.mediaAdapter?.count ?: 0 + + override fun getContributionStateAt(position: Int): Int? { + return null + } + + /** + * Reload media detail fragment once media is nominated + * + * @param index item position that has been nominated + */ + override fun refreshNominatedMedia(index: Int) { + if (mediaDetails != null && !listFragment!!.isVisible()) { + removeFragment(mediaDetails!!) + mediaDetails = newInstance(false, true) + (parentFragment as BookmarkFragment).setScroll(false) + setFragment(mediaDetails!!, listFragment) + mediaDetails!!.showImage(index) + } + } + + /** + * This method is called on success of API call for featured images or mobile uploads. The + * viewpager will notified that number of items have changed. + */ + override fun viewPagerNotifyDataSetChanged() { + if (mediaDetails != null) { + mediaDetails!!.notifyDataSetChanged() + } + } + + fun backPressed(): Boolean { + //check mediaDetailPage fragment is not null then we check mediaDetail.is Visible or not to avoid NullPointerException + if (mediaDetails != null) { + if (mediaDetails!!.isVisible()) { + // todo add get list fragment + (parentFragment as BookmarkFragment).setupTabLayout() + val removed: ArrayList = mediaDetails!!.removedItems + removeFragment(mediaDetails!!) + (parentFragment as BookmarkFragment).setScroll(true) + setFragment(listFragment!!, mediaDetails) + (requireActivity() as MainActivity).showTabs() + if (listFragment is BookmarkPicturesFragment) { + val adapter = ((listFragment as BookmarkPicturesFragment) + .getAdapter() as GridViewAdapter?) + val i: MutableIterator<*> = removed.iterator() + while (i.hasNext()) { + adapter!!.remove(adapter.getItem(i.next() as Int)) + } + mediaDetails!!.clearRemoved() + } + } else { + moveToContributionsFragment() + } + } else { + moveToContributionsFragment() + } + // notify mediaDetails did not handled the backPressed further actions required. + return false + } + + fun moveToContributionsFragment() { + (requireActivity() as MainActivity).setSelectedItemId(NavTab.CONTRIBUTIONS.code()) + (requireActivity() as MainActivity).showTabs() + } + + override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + Timber.d("on media clicked") + binding!!.exploreContainer.visibility = View.VISIBLE + (parentFragment as BookmarkFragment).binding!!.tabLayout.setVisibility(View.GONE) + mediaDetails = newInstance(false, true) + (parentFragment as BookmarkFragment).setScroll(false) + setFragment(mediaDetails!!, listFragment) + mediaDetails!!.showImage(position) + } + + override fun onBackStackChanged() = Unit + + override fun onDestroy() { + super.onDestroy() + binding = null + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarksPagerAdapter.java b/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarksPagerAdapter.java deleted file mode 100644 index f0620032a..000000000 --- a/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarksPagerAdapter.java +++ /dev/null @@ -1,94 +0,0 @@ -package fr.free.nrw.commons.bookmarks; - -import android.content.Context; -import android.os.Bundle; -import android.widget.ListAdapter; - -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentPagerAdapter; - -import java.util.ArrayList; - -import fr.free.nrw.commons.R; -import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesFragment; - -public class BookmarksPagerAdapter extends FragmentPagerAdapter { - - private ArrayList pages; - - /** - * Default Constructor - * @param fm - * @param context - * @param onlyPictures is true if the fragment requires only BookmarkPictureFragment - * (i.e. when no user is logged in). - */ - BookmarksPagerAdapter(FragmentManager fm, Context context,boolean onlyPictures) { - super(fm); - pages = new ArrayList<>(); - Bundle picturesBundle = new Bundle(); - picturesBundle.putString("categoryName", context.getString(R.string.title_page_bookmarks_pictures)); - picturesBundle.putInt("order", 0); - pages.add(new BookmarkPages( - new BookmarkListRootFragment(picturesBundle, this), - context.getString(R.string.title_page_bookmarks_pictures))); - if (!onlyPictures) { - // if onlyPictures is false we also add the location fragment. - Bundle locationBundle = new Bundle(); - locationBundle.putString("categoryName", - context.getString(R.string.title_page_bookmarks_locations)); - locationBundle.putInt("order", 1); - pages.add(new BookmarkPages( - new BookmarkListRootFragment(locationBundle, this), - context.getString(R.string.title_page_bookmarks_locations))); - - locationBundle.putInt("orderItem", 2); - pages.add(new BookmarkPages( - new BookmarkListRootFragment(locationBundle, this), - context.getString(R.string.title_page_bookmarks_items))); - } - final Bundle categoriesBundle = new Bundle(); - categoriesBundle.putString("categoryName", - context.getString(R.string.title_page_bookmarks_categories)); - categoriesBundle.putInt("order", 3); - pages.add(new BookmarkPages( - new BookmarkListRootFragment(categoriesBundle, this), - context.getString(R.string.title_page_bookmarks_categories))); - notifyDataSetChanged(); - } - - @Override - public Fragment getItem(int position) { - return pages.get(position).getPage(); - } - - @Override - public int getCount() { - return pages.size(); - } - - @Nullable - @Override - public CharSequence getPageTitle(int position) { - return pages.get(position).getTitle(); - } - - /** - * Return the Adapter used to display the picture gridview - * @return adapter - */ - public ListAdapter getMediaAdapter() { - BookmarkPicturesFragment fragment = (BookmarkPicturesFragment)(((BookmarkListRootFragment)pages.get(0).getPage()).listFragment); - return fragment.getAdapter(); - } - - /** - * Update the pictures list for the bookmark fragment - */ - public void requestPictureListUpdate() { - BookmarkPicturesFragment fragment = (BookmarkPicturesFragment)(((BookmarkListRootFragment)pages.get(0).getPage()).listFragment); - fragment.onResume(); - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarksPagerAdapter.kt b/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarksPagerAdapter.kt new file mode 100644 index 000000000..a7cbf0e68 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarksPagerAdapter.kt @@ -0,0 +1,82 @@ +package fr.free.nrw.commons.bookmarks + +import android.content.Context +import android.widget.ListAdapter +import androidx.core.os.bundleOf +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.FragmentPagerAdapter +import fr.free.nrw.commons.R +import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesFragment + +class BookmarksPagerAdapter internal constructor( + fm: FragmentManager, context: Context, onlyPictures: Boolean +) : FragmentPagerAdapter(fm) { + private val pages = mutableListOf() + + /** + * Default Constructor + * @param fm + * @param context + * @param onlyPictures is true if the fragment requires only BookmarkPictureFragment + * (i.e. when no user is logged in). + */ + init { + pages.add( + BookmarkPages( + BookmarkListRootFragment( + bundleOf( + "categoryName" to context.getString(R.string.title_page_bookmarks_pictures), + "order" to 0 + ), this + ), context.getString(R.string.title_page_bookmarks_pictures) + ) + ) + if (!onlyPictures) { + // if onlyPictures is false we also add the location fragment. + val locationBundle = bundleOf( + "categoryName" to context.getString(R.string.title_page_bookmarks_locations), + "order" to 1 + ) + + pages.add( + BookmarkPages( + BookmarkListRootFragment(locationBundle, this), + context.getString(R.string.title_page_bookmarks_locations) + ) + ) + + locationBundle.putInt("orderItem", 2) + pages.add( + BookmarkPages( + BookmarkListRootFragment(locationBundle, this), + context.getString(R.string.title_page_bookmarks_items) + ) + ) + } + pages.add( + BookmarkPages( + BookmarkListRootFragment( + bundleOf( + "categoryName" to context.getString(R.string.title_page_bookmarks_categories), + "order" to 3 + ), this), + context.getString(R.string.title_page_bookmarks_categories) + ) + ) + notifyDataSetChanged() + } + + override fun getItem(position: Int): Fragment = pages[position].page!! + + override fun getCount(): Int = pages.size + + override fun getPageTitle(position: Int): CharSequence? = pages[position].title + + /** + * Return the Adapter used to display the picture gridview + * @return adapter + */ + val mediaAdapter: ListAdapter? + get() = (((pages[0].page as BookmarkListRootFragment).listFragment) as BookmarkPicturesFragment).getAdapter() +} diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsContentProvider.java b/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsContentProvider.java deleted file mode 100644 index 3a85ec159..000000000 --- a/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsContentProvider.java +++ /dev/null @@ -1,129 +0,0 @@ -package fr.free.nrw.commons.bookmarks.items; - -import static fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao.Table.COLUMN_ID; -import static fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao.Table.TABLE_NAME; - -import android.content.ContentValues; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteQueryBuilder; -import android.net.Uri; -import android.text.TextUtils; -import androidx.annotation.NonNull; -import fr.free.nrw.commons.BuildConfig; -import fr.free.nrw.commons.data.DBOpenHelper; -import fr.free.nrw.commons.di.CommonsDaggerContentProvider; -import javax.inject.Inject; -import timber.log.Timber; - -/** - * Handles private storage for bookmarked items - */ -public class BookmarkItemsContentProvider extends CommonsDaggerContentProvider { - - private static final String BASE_PATH = "bookmarksItems"; - public static final Uri BASE_URI = - Uri.parse("content://" + BuildConfig.BOOKMARK_ITEMS_AUTHORITY + "/" + BASE_PATH); - - - /** - * Append bookmark items ID to the base uri - */ - public static Uri uriForName(final String id) { - return Uri.parse(BASE_URI + "/" + id); - } - - @Inject - DBOpenHelper dbOpenHelper; - - @Override - public String getType(@NonNull final Uri uri) { - return null; - } - - /** - * Queries the SQLite database for the bookmark items - * @param uri : contains the uri for bookmark items - * @param projection : contains the all fields of the table - * @param selection : handles Where - * @param selectionArgs : the condition of Where clause - * @param sortOrder : ascending or descending - */ - @SuppressWarnings("ConstantConditions") - @Override - public Cursor query(@NonNull final Uri uri, final String[] projection, final String selection, - final String[] selectionArgs, final String sortOrder) { - final SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); - queryBuilder.setTables(TABLE_NAME); - final SQLiteDatabase db = dbOpenHelper.getReadableDatabase(); - final Cursor cursor = queryBuilder.query(db, projection, selection, - selectionArgs, null, null, sortOrder); - cursor.setNotificationUri(getContext().getContentResolver(), uri); - return cursor; - } - - /** - * Handles the update query of local SQLite Database - * @param uri : contains the uri for bookmark items - * @param contentValues : new values to be entered to db - * @param selection : handles Where - * @param selectionArgs : the condition of Where clause - */ - @SuppressWarnings("ConstantConditions") - @Override - public int update(@NonNull final Uri uri, final ContentValues contentValues, - final String selection, final String[] selectionArgs) { - final SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase(); - final int rowsUpdated; - if (TextUtils.isEmpty(selection)) { - final int id = Integer.parseInt(uri.getLastPathSegment()); - rowsUpdated = sqlDB.update(TABLE_NAME, - contentValues, - COLUMN_ID + " = ?", - new String[]{String.valueOf(id)}); - } else { - throw new IllegalArgumentException( - "Parameter `selection` should be empty when updating an ID"); - } - - getContext().getContentResolver().notifyChange(uri, null); - return rowsUpdated; - } - - /** - * Handles the insertion of new bookmark items record to local SQLite Database - * @param uri - * @param contentValues - * @return - */ - @SuppressWarnings("ConstantConditions") - @Override - public Uri insert(@NonNull final Uri uri, final ContentValues contentValues) { - final SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase(); - final long id = sqlDB.insert(TABLE_NAME, null, contentValues); - getContext().getContentResolver().notifyChange(uri, null); - return Uri.parse(BASE_URI + "/" + id); - } - - /** - * Handles the deletion of new bookmark items record to local SQLite Database - * @param uri - * @param s - * @param strings - * @return - */ - @SuppressWarnings("ConstantConditions") - @Override - public int delete(@NonNull final Uri uri, final String s, final String[] strings) { - final int rows; - final SQLiteDatabase db = dbOpenHelper.getReadableDatabase(); - Timber.d("Deleting bookmark name %s", uri.getLastPathSegment()); - rows = db.delete( - TABLE_NAME, - "item_id = ?", - new String[]{uri.getLastPathSegment()} - ); - getContext().getContentResolver().notifyChange(uri, null); - return rows; - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsContentProvider.kt b/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsContentProvider.kt new file mode 100644 index 000000000..8007ba208 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsContentProvider.kt @@ -0,0 +1,101 @@ +package fr.free.nrw.commons.bookmarks.items + +import android.content.ContentValues +import android.database.Cursor +import android.database.sqlite.SQLiteQueryBuilder +import android.net.Uri +import fr.free.nrw.commons.BuildConfig +import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.TABLE_NAME +import fr.free.nrw.commons.di.CommonsDaggerContentProvider +import androidx.core.net.toUri +import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_ID + +/** + * Handles private storage for bookmarked items + */ +class BookmarkItemsContentProvider : CommonsDaggerContentProvider() { + override fun getType(uri: Uri): String? = null + + /** + * Queries the SQLite database for the bookmark items + * @param uri : contains the uri for bookmark items + * @param projection : contains the all fields of the table + * @param selection : handles Where + * @param selectionArgs : the condition of Where clause + * @param sortOrder : ascending or descending + */ + override fun query( + uri: Uri, projection: Array?, selection: String?, + selectionArgs: Array?, sortOrder: String? + ): Cursor { + val queryBuilder = SQLiteQueryBuilder().apply { + tables = TABLE_NAME + } + + return queryBuilder.query( + requireDb(), projection, selection, + selectionArgs, null, null, sortOrder + ).apply { + setNotificationUri(requireContext().contentResolver, uri) + } + } + + /** + * Handles the update query of local SQLite Database + * @param uri : contains the uri for bookmark items + * @param contentValues : new values to be entered to db + * @param selection : handles Where + * @param selectionArgs : the condition of Where clause + */ + override fun update( + uri: Uri, contentValues: ContentValues?, + selection: String?, selectionArgs: Array? + ): Int { + val rowsUpdated: Int + if (selection.isNullOrEmpty()) { + val id = uri.lastPathSegment!!.toInt() + rowsUpdated = requireDb().update( + TABLE_NAME, + contentValues, + "$COLUMN_ID = ?", + arrayOf(id.toString()) + ) + } else { + throw IllegalArgumentException( + "Parameter `selection` should be empty when updating an ID" + ) + } + + requireContext().contentResolver.notifyChange(uri, null) + return rowsUpdated + } + + /** + * Handles the insertion of new bookmark items record to local SQLite Database + */ + override fun insert(uri: Uri, contentValues: ContentValues?): Uri? { + val id = requireDb().insert(TABLE_NAME, null, contentValues) + requireContext().contentResolver.notifyChange(uri, null) + return "$BASE_URI/$id".toUri() + } + + + /** + * Handles the deletion of new bookmark items record to local SQLite Database + */ + override fun delete(uri: Uri, s: String?, strings: Array?): Int { + val rows: Int = requireDb().delete( + TABLE_NAME, + "$COLUMN_ID = ?", + arrayOf(uri.lastPathSegment) + ) + requireContext().contentResolver.notifyChange(uri, null) + return rows + } + + companion object { + private const val BASE_PATH = "bookmarksItems" + val BASE_URI: Uri = "content://${BuildConfig.BOOKMARK_ITEMS_AUTHORITY}/$BASE_PATH".toUri() + fun uriForName(id: String) = "$BASE_URI/$id".toUri() + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsController.java b/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsController.java deleted file mode 100644 index d059e4cc4..000000000 --- a/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsController.java +++ /dev/null @@ -1,27 +0,0 @@ -package fr.free.nrw.commons.bookmarks.items; - -import fr.free.nrw.commons.upload.structure.depictions.DepictedItem; -import java.util.List; -import javax.inject.Inject; -import javax.inject.Singleton; - -/** - * Handles loading bookmarked items from Database - */ -@Singleton -public class BookmarkItemsController { - - @Inject - BookmarkItemsDao bookmarkItemsDao; - - @Inject - public BookmarkItemsController() {} - - /** - * Load from DB the bookmarked items - * @return a list of DepictedItem objects. - */ - public List loadFavoritesItems() { - return bookmarkItemsDao.getAllBookmarksItems(); - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsController.kt b/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsController.kt new file mode 100644 index 000000000..d1a9ef785 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsController.kt @@ -0,0 +1,23 @@ +package fr.free.nrw.commons.bookmarks.items + +import fr.free.nrw.commons.upload.structure.depictions.DepictedItem +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Handles loading bookmarked items from Database + */ +@Singleton +class BookmarkItemsController @Inject constructor() { + @JvmField + @Inject + var bookmarkItemsDao: BookmarkItemsDao? = null + + /** + * Load from DB the bookmarked items + * @return a list of DepictedItem objects. + */ + fun loadFavoritesItems(): List { + return bookmarkItemsDao?.getAllBookmarksItems() ?: emptyList() + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDao.java b/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDao.java deleted file mode 100644 index 6788a8290..000000000 --- a/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDao.java +++ /dev/null @@ -1,329 +0,0 @@ -package fr.free.nrw.commons.bookmarks.items; - -import android.annotation.SuppressLint; -import android.content.ContentProviderClient; -import android.content.ContentValues; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.os.RemoteException; -import fr.free.nrw.commons.category.CategoryItem; -import fr.free.nrw.commons.upload.structure.depictions.DepictedItem; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Provider; -import javax.inject.Singleton; -import org.apache.commons.lang3.StringUtils; - -/** - * Handles database operations for bookmarked items - */ -@Singleton -public class BookmarkItemsDao { - - private final Provider clientProvider; - - @Inject - public BookmarkItemsDao( - @Named("bookmarksItem") final Provider clientProvider) { - this.clientProvider = clientProvider; - } - - - /** - * Find all persisted items bookmarks on database - * @return list of bookmarks - */ - public List getAllBookmarksItems() { - final List items = new ArrayList<>(); - final ContentProviderClient db = clientProvider.get(); - try (final Cursor cursor = db.query( - BookmarkItemsContentProvider.BASE_URI, - Table.ALL_FIELDS, - null, - new String[]{}, - null)) { - while (cursor != null && cursor.moveToNext()) { - items.add(fromCursor(cursor)); - } - } catch (final RemoteException e) { - throw new RuntimeException(e); - } finally { - db.release(); - } - return items; - } - - - /** - * Look for a bookmark in database and in order to insert or delete it - * @param depictedItem : Bookmark object - * @return boolean : is bookmark now favorite ? - */ - public boolean updateBookmarkItem(final DepictedItem depictedItem) { - final boolean bookmarkExists = findBookmarkItem(depictedItem.getId()); - if (bookmarkExists) { - deleteBookmarkItem(depictedItem); - } else { - addBookmarkItem(depictedItem); - } - return !bookmarkExists; - } - - /** - * Add a Bookmark to database - * @param depictedItem : Bookmark to add - */ - private void addBookmarkItem(final DepictedItem depictedItem) { - final ContentProviderClient db = clientProvider.get(); - try { - db.insert(BookmarkItemsContentProvider.BASE_URI, toContentValues(depictedItem)); - } catch (final RemoteException e) { - throw new RuntimeException(e); - } finally { - db.release(); - } - } - - /** - * Delete a bookmark from database - * @param depictedItem : Bookmark to delete - */ - private void deleteBookmarkItem(final DepictedItem depictedItem) { - final ContentProviderClient db = clientProvider.get(); - try { - db.delete(BookmarkItemsContentProvider.uriForName(depictedItem.getId()), null, null); - } catch (final RemoteException e) { - throw new RuntimeException(e); - } finally { - db.release(); - } - } - - /** - * Find a bookmark from database based on its name - * @param depictedItemID : Bookmark to find - * @return boolean : is bookmark in database ? - */ - public boolean findBookmarkItem(final String depictedItemID) { - if (depictedItemID == null) { //Avoiding NPE's - return false; - } - final ContentProviderClient db = clientProvider.get(); - try (final Cursor cursor = db.query( - BookmarkItemsContentProvider.BASE_URI, - Table.ALL_FIELDS, - Table.COLUMN_ID + "=?", - new String[]{depictedItemID}, - null - )) { - if (cursor != null && cursor.moveToFirst()) { - return true; - } - } catch (final RemoteException e) { - throw new RuntimeException(e); - } finally { - db.release(); - } - return false; - } - - /** - * Recives real data from cursor - * @param cursor : Object for storing database data - * @return DepictedItem - */ - @SuppressLint("Range") - DepictedItem fromCursor(final Cursor cursor) { - final String fileName = cursor.getString(cursor.getColumnIndex(Table.COLUMN_NAME)); - final String description - = cursor.getString(cursor.getColumnIndex(Table.COLUMN_DESCRIPTION)); - final String imageUrl = cursor.getString(cursor.getColumnIndex(Table.COLUMN_IMAGE)); - final String instanceListString - = cursor.getString(cursor.getColumnIndex(Table.COLUMN_INSTANCE_LIST)); - final List instanceList = StringToArray(instanceListString); - final String categoryNameListString = cursor.getString(cursor - .getColumnIndex(Table.COLUMN_CATEGORIES_NAME_LIST)); - final List categoryNameList = StringToArray(categoryNameListString); - final String categoryDescriptionListString = cursor.getString(cursor - .getColumnIndex(Table.COLUMN_CATEGORIES_DESCRIPTION_LIST)); - final List categoryDescriptionList = StringToArray(categoryDescriptionListString); - final String categoryThumbnailListString = cursor.getString(cursor - .getColumnIndex(Table.COLUMN_CATEGORIES_THUMBNAIL_LIST)); - final List categoryThumbnailList = StringToArray(categoryThumbnailListString); - final List categoryList = convertToCategoryItems(categoryNameList, - categoryDescriptionList, categoryThumbnailList); - final boolean isSelected - = Boolean.parseBoolean(cursor.getString(cursor - .getColumnIndex(Table.COLUMN_IS_SELECTED))); - final String id = cursor.getString(cursor.getColumnIndex(Table.COLUMN_ID)); - - return new DepictedItem( - fileName, - description, - imageUrl, - instanceList, - categoryList, - isSelected, - id - ); - } - - private List convertToCategoryItems(List categoryNameList, - List categoryDescriptionList, List categoryThumbnailList) { - List categoryItems = new ArrayList<>(); - for(int i=0; i StringToArray(final String listString) { - final String[] elements = listString.split(","); - return Arrays.asList(elements); - } - - /** - * Converts string to List - * @param list list of items - * @return string comma separated single string of items - */ - private String ArrayToString(final List list) { - if (list != null) { - return StringUtils.join(list, ','); - } - return null; - } - - /** - * Takes data from DepictedItem and create a content value object - * @param depictedItem depicted item - * @return ContentValues - */ - private ContentValues toContentValues(final DepictedItem depictedItem) { - - final List namesOfCommonsCategories = new ArrayList<>(); - for (final CategoryItem category : - depictedItem.getCommonsCategories()) { - namesOfCommonsCategories.add(category.getName()); - } - - final List descriptionsOfCommonsCategories = new ArrayList<>(); - for (final CategoryItem category : - depictedItem.getCommonsCategories()) { - descriptionsOfCommonsCategories.add(category.getDescription()); - } - - final List thumbnailsOfCommonsCategories = new ArrayList<>(); - for (final CategoryItem category : - depictedItem.getCommonsCategories()) { - thumbnailsOfCommonsCategories.add(category.getThumbnail()); - } - - final ContentValues cv = new ContentValues(); - cv.put(Table.COLUMN_NAME, depictedItem.getName()); - cv.put(Table.COLUMN_DESCRIPTION, depictedItem.getDescription()); - cv.put(Table.COLUMN_IMAGE, depictedItem.getImageUrl()); - cv.put(Table.COLUMN_INSTANCE_LIST, ArrayToString(depictedItem.getInstanceOfs())); - cv.put(Table.COLUMN_CATEGORIES_NAME_LIST, ArrayToString(namesOfCommonsCategories)); - cv.put(Table.COLUMN_CATEGORIES_DESCRIPTION_LIST, - ArrayToString(descriptionsOfCommonsCategories)); - cv.put(Table.COLUMN_CATEGORIES_THUMBNAIL_LIST, - ArrayToString(thumbnailsOfCommonsCategories)); - cv.put(Table.COLUMN_IS_SELECTED, depictedItem.isSelected()); - cv.put(Table.COLUMN_ID, depictedItem.getId()); - return cv; - } - - /** - * Table of bookmarksItems data - */ - public static final class Table { - public static final String TABLE_NAME = "bookmarksItems"; - public static final String COLUMN_NAME = "item_name"; - public static final String COLUMN_DESCRIPTION = "item_description"; - public static final String COLUMN_IMAGE = "item_image_url"; - public static final String COLUMN_INSTANCE_LIST = "item_instance_of"; - public static final String COLUMN_CATEGORIES_NAME_LIST = "item_name_categories"; - public static final String COLUMN_CATEGORIES_DESCRIPTION_LIST = "item_description_categories"; - public static final String COLUMN_CATEGORIES_THUMBNAIL_LIST = "item_thumbnail_categories"; - public static final String COLUMN_IS_SELECTED = "item_is_selected"; - public static final String COLUMN_ID = "item_id"; - - public static final String[] ALL_FIELDS = { - COLUMN_NAME, - COLUMN_DESCRIPTION, - COLUMN_IMAGE, - COLUMN_INSTANCE_LIST, - COLUMN_CATEGORIES_NAME_LIST, - COLUMN_CATEGORIES_DESCRIPTION_LIST, - COLUMN_CATEGORIES_THUMBNAIL_LIST, - COLUMN_IS_SELECTED, - COLUMN_ID - }; - - static final String DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS " + TABLE_NAME; - static final String CREATE_TABLE_STATEMENT = "CREATE TABLE " + TABLE_NAME + " (" - + COLUMN_NAME + " STRING," - + COLUMN_DESCRIPTION + " STRING," - + COLUMN_IMAGE + " STRING," - + COLUMN_INSTANCE_LIST + " STRING," - + COLUMN_CATEGORIES_NAME_LIST + " STRING," - + COLUMN_CATEGORIES_DESCRIPTION_LIST + " STRING," - + COLUMN_CATEGORIES_THUMBNAIL_LIST + " STRING," - + COLUMN_IS_SELECTED + " STRING," - + COLUMN_ID + " STRING PRIMARY KEY" - + ");"; - - /** - * Creates table - * @param db SQLiteDatabase - */ - public static void onCreate(final SQLiteDatabase db) { - db.execSQL(CREATE_TABLE_STATEMENT); - } - - /** - * Deletes database - * @param db SQLiteDatabase - */ - public static void onDelete(final SQLiteDatabase db) { - db.execSQL(DROP_TABLE_STATEMENT); - onCreate(db); - } - - /** - * Updates database - * @param db SQLiteDatabase - * @param from starting - * @param to end - */ - public static void onUpdate(final SQLiteDatabase db, int from, final int to) { - if (from == to) { - return; - } - if (from < 18) { - // doesn't exist yet - from++; - onUpdate(db, from, to); - return; - } - - if (from == 18) { - // table added in version 19 - onCreate(db); - from++; - onUpdate(db, from, to); - } - } - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDao.kt b/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDao.kt new file mode 100644 index 000000000..d64ab16b3 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDao.kt @@ -0,0 +1,199 @@ +package fr.free.nrw.commons.bookmarks.items + +import android.annotation.SuppressLint +import android.content.ContentProviderClient +import android.content.ContentValues +import android.database.Cursor +import android.os.RemoteException +import androidx.core.content.contentValuesOf +import fr.free.nrw.commons.bookmarks.items.BookmarkItemsContentProvider.Companion.BASE_URI +import fr.free.nrw.commons.bookmarks.items.BookmarkItemsContentProvider.Companion.uriForName +import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_CATEGORIES_DESCRIPTION_LIST +import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_CATEGORIES_NAME_LIST +import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_CATEGORIES_THUMBNAIL_LIST +import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_DESCRIPTION +import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_ID +import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_IMAGE +import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_INSTANCE_LIST +import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_IS_SELECTED +import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_NAME +import fr.free.nrw.commons.category.CategoryItem +import fr.free.nrw.commons.upload.structure.depictions.DepictedItem +import fr.free.nrw.commons.utils.arrayToString +import fr.free.nrw.commons.utils.getString +import fr.free.nrw.commons.utils.getStringArray +import javax.inject.Inject +import javax.inject.Named +import javax.inject.Provider +import javax.inject.Singleton + +/** + * Handles database operations for bookmarked items + */ +@Singleton +class BookmarkItemsDao @Inject constructor( + @param:Named("bookmarksItem") private val clientProvider: Provider +) { + /** + * Find all persisted items bookmarks on database + * @return list of bookmarks + */ + fun getAllBookmarksItems(): List { + val items: MutableList = mutableListOf() + val db = clientProvider.get() + try { + db.query( + BASE_URI, + BookmarkItemsTable.ALL_FIELDS, + null, + arrayOf(), + null + ).use { cursor -> + while (cursor != null && cursor.moveToNext()) { + items.add(fromCursor(cursor)) + } + } + } catch (e: RemoteException) { + throw RuntimeException(e) + } finally { + db.release() + } + return items + } + + + /** + * Look for a bookmark in database and in order to insert or delete it + * @param depictedItem : Bookmark object + * @return boolean : is bookmark now favorite ? + */ + fun updateBookmarkItem(depictedItem: DepictedItem): Boolean { + val bookmarkExists = findBookmarkItem(depictedItem.id) + if (bookmarkExists) { + deleteBookmarkItem(depictedItem) + } else { + addBookmarkItem(depictedItem) + } + return !bookmarkExists + } + + /** + * Add a Bookmark to database + * @param depictedItem : Bookmark to add + */ + private fun addBookmarkItem(depictedItem: DepictedItem) { + val db = clientProvider.get() + try { + db.insert(BASE_URI, toContentValues(depictedItem)) + } catch (e: RemoteException) { + throw RuntimeException(e) + } finally { + db.release() + } + } + + /** + * Delete a bookmark from database + * @param depictedItem : Bookmark to delete + */ + private fun deleteBookmarkItem(depictedItem: DepictedItem) { + val db = clientProvider.get() + try { + db.delete(uriForName(depictedItem.id), null, null) + } catch (e: RemoteException) { + throw RuntimeException(e) + } finally { + db.release() + } + } + + /** + * Find a bookmark from database based on its name + * @param depictedItemID : Bookmark to find + * @return boolean : is bookmark in database ? + */ + fun findBookmarkItem(depictedItemID: String?): Boolean { + if (depictedItemID == null) { //Avoiding NPE's + return false + } + val db = clientProvider.get() + try { + db.query( + BASE_URI, + BookmarkItemsTable.ALL_FIELDS, + COLUMN_ID + "=?", + arrayOf(depictedItemID), + null + ).use { cursor -> + if (cursor != null && cursor.moveToFirst()) { + return true + } + } + } catch (e: RemoteException) { + throw RuntimeException(e) + } finally { + db.release() + } + return false + } + + /** + * Recives real data from cursor + * @param cursor : Object for storing database data + * @return DepictedItem + */ + @SuppressLint("Range") + fun fromCursor(cursor: Cursor) = with(cursor) { + DepictedItem( + getString(COLUMN_NAME), + getString(COLUMN_DESCRIPTION), + getString(COLUMN_IMAGE), + getStringArray(COLUMN_INSTANCE_LIST), + convertToCategoryItems( + getStringArray(COLUMN_CATEGORIES_NAME_LIST), + getStringArray(COLUMN_CATEGORIES_DESCRIPTION_LIST), + getStringArray(COLUMN_CATEGORIES_THUMBNAIL_LIST) + ), + getString(COLUMN_IS_SELECTED).toBoolean(), + getString(COLUMN_ID) + ) + } + + private fun convertToCategoryItems( + categoryNameList: List, + categoryDescriptionList: List, + categoryThumbnailList: List + ): List { + return buildList { + for (i in categoryNameList.indices) { + add( + CategoryItem( + categoryNameList[i], + categoryDescriptionList[i], + categoryThumbnailList[i], + false + ) + ) + } + } + } + + /** + * Takes data from DepictedItem and create a content value object + * @param depictedItem depicted item + * @return ContentValues + */ + private fun toContentValues(depictedItem: DepictedItem): ContentValues { + return contentValuesOf( + COLUMN_NAME to depictedItem.name, + COLUMN_DESCRIPTION to depictedItem.description, + COLUMN_IMAGE to depictedItem.imageUrl, + COLUMN_INSTANCE_LIST to arrayToString(depictedItem.instanceOfs), + COLUMN_CATEGORIES_NAME_LIST to arrayToString(depictedItem.commonsCategories.map { it.name }), + COLUMN_CATEGORIES_DESCRIPTION_LIST to arrayToString(depictedItem.commonsCategories.map { it.description }), + COLUMN_CATEGORIES_THUMBNAIL_LIST to arrayToString(depictedItem.commonsCategories.map { it.thumbnail }), + COLUMN_IS_SELECTED to depictedItem.isSelected, + COLUMN_ID to depictedItem.id, + ) + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsFragment.java b/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsFragment.java deleted file mode 100644 index 75a0fa7a4..000000000 --- a/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsFragment.java +++ /dev/null @@ -1,81 +0,0 @@ -package fr.free.nrw.commons.bookmarks.items; - -import android.content.Context; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ProgressBar; -import android.widget.RelativeLayout; -import android.widget.TextView; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import dagger.android.support.DaggerFragment; -import fr.free.nrw.commons.R; -import fr.free.nrw.commons.databinding.FragmentBookmarksItemsBinding; -import fr.free.nrw.commons.upload.structure.depictions.DepictedItem; -import java.util.List; -import javax.inject.Inject; -import org.jetbrains.annotations.NotNull; - -/** - * Tab fragment to show list of bookmarked Wikidata Items - */ -public class BookmarkItemsFragment extends DaggerFragment { - - private FragmentBookmarksItemsBinding binding; - - @Inject - BookmarkItemsController controller; - - public static BookmarkItemsFragment newInstance() { - return new BookmarkItemsFragment(); - } - - @Override - public View onCreateView( - @NonNull final LayoutInflater inflater, - final ViewGroup container, - final Bundle savedInstanceState - ) { - binding = FragmentBookmarksItemsBinding.inflate(inflater, container, false); - return binding.getRoot(); - } - - @Override - public void onViewCreated(final @NotNull View view, @Nullable final Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - initList(requireContext()); - } - - @Override - public void onResume() { - super.onResume(); - initList(requireContext()); - } - - /** - * Get list of DepictedItem and sets to the adapter - * @param context context - */ - private void initList(final Context context) { - final List depictItems = controller.loadFavoritesItems(); - final BookmarkItemsAdapter adapter = new BookmarkItemsAdapter(depictItems, context); - binding.listView.setAdapter(adapter); - binding.loadingImagesProgressBar.setVisibility(View.GONE); - if (depictItems.isEmpty()) { - binding.statusMessage.setText(R.string.bookmark_empty); - binding.statusMessage.setVisibility(View.VISIBLE); - } else { - binding.statusMessage.setVisibility(View.GONE); - } - } - - @Override - public void onDestroy() { - super.onDestroy(); - binding = null; - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsFragment.kt b/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsFragment.kt new file mode 100644 index 000000000..aa9dcccc0 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsFragment.kt @@ -0,0 +1,62 @@ +package fr.free.nrw.commons.bookmarks.items + +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import dagger.android.support.DaggerFragment +import fr.free.nrw.commons.R +import fr.free.nrw.commons.databinding.FragmentBookmarksItemsBinding +import javax.inject.Inject + +/** + * Tab fragment to show list of bookmarked Wikidata Items + */ +class BookmarkItemsFragment : DaggerFragment() { + private var binding: FragmentBookmarksItemsBinding? = null + + @JvmField + @Inject + var controller: BookmarkItemsController? = null + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = FragmentBookmarksItemsBinding.inflate(inflater, container, false) + return binding!!.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + initList(requireContext()) + } + + override fun onResume() { + super.onResume() + initList(requireContext()) + } + + /** + * Get list of DepictedItem and sets to the adapter + * @param context context + */ + private fun initList(context: Context) { + val depictItems = controller!!.loadFavoritesItems() + binding!!.listView.adapter = BookmarkItemsAdapter(depictItems, context) + binding!!.loadingImagesProgressBar.visibility = View.GONE + if (depictItems.isEmpty()) { + binding!!.statusMessage.setText(R.string.bookmark_empty) + binding!!.statusMessage.visibility = View.VISIBLE + } else { + binding!!.statusMessage.visibility = View.GONE + } + } + + override fun onDestroy() { + super.onDestroy() + binding = null + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsTable.kt b/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsTable.kt new file mode 100644 index 000000000..b1b03c71b --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsTable.kt @@ -0,0 +1,90 @@ +package fr.free.nrw.commons.bookmarks.items + +import android.database.sqlite.SQLiteDatabase + +/** + * Table of bookmarksItems data + */ +object BookmarkItemsTable { + const val TABLE_NAME = "bookmarksItems" + const val COLUMN_NAME = "item_name" + const val COLUMN_DESCRIPTION = "item_description" + const val COLUMN_IMAGE = "item_image_url" + const val COLUMN_INSTANCE_LIST = "item_instance_of" + const val COLUMN_CATEGORIES_NAME_LIST = "item_name_categories" + const val COLUMN_CATEGORIES_DESCRIPTION_LIST = "item_description_categories" + const val COLUMN_CATEGORIES_THUMBNAIL_LIST = "item_thumbnail_categories" + const val COLUMN_IS_SELECTED = "item_is_selected" + const val COLUMN_ID = "item_id" + + val ALL_FIELDS = arrayOf( + COLUMN_NAME, + COLUMN_DESCRIPTION, + COLUMN_IMAGE, + COLUMN_INSTANCE_LIST, + COLUMN_CATEGORIES_NAME_LIST, + COLUMN_CATEGORIES_DESCRIPTION_LIST, + COLUMN_CATEGORIES_THUMBNAIL_LIST, + COLUMN_IS_SELECTED, + COLUMN_ID + ) + + const val DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS $TABLE_NAME" + + val CREATE_TABLE_STATEMENT = + """CREATE TABLE $TABLE_NAME ( + $COLUMN_NAME STRING, + $COLUMN_DESCRIPTION STRING, + $COLUMN_IMAGE STRING, + $COLUMN_INSTANCE_LIST STRING, + $COLUMN_CATEGORIES_NAME_LIST STRING, + $COLUMN_CATEGORIES_DESCRIPTION_LIST STRING, + $COLUMN_CATEGORIES_THUMBNAIL_LIST STRING, + $COLUMN_IS_SELECTED STRING, + $COLUMN_ID STRING PRIMARY KEY + );""".trimIndent() + + /** + * Creates table + * + * @param db SQLiteDatabase + */ + fun onCreate(db: SQLiteDatabase) { + db.execSQL(CREATE_TABLE_STATEMENT) + } + + /** + * Deletes database + * + * @param db SQLiteDatabase + */ + fun onDelete(db: SQLiteDatabase) { + db.execSQL(DROP_TABLE_STATEMENT) + onCreate(db) + } + + /** + * Updates database + * + * @param db SQLiteDatabase + * @param from starting + * @param to end + */ + fun onUpdate(db: SQLiteDatabase, from: Int, to: Int) { + if (from == to) { + return + } + + if (from < 18) { + // doesn't exist yet + onUpdate(db, from + 1, to) + return + } + + if (from == 18) { + // table added in version 19 + onCreate(db) + onUpdate(db, from + 1, to) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesContentProvider.java b/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesContentProvider.java deleted file mode 100644 index 2aac07902..000000000 --- a/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesContentProvider.java +++ /dev/null @@ -1,120 +0,0 @@ -package fr.free.nrw.commons.bookmarks.pictures; - -import android.content.ContentValues; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteQueryBuilder; -// We can get uri using java.Net.Uri, but andoid implimentation is faster (but it's forgiving with handling exceptions though) -import android.net.Uri; -import android.text.TextUtils; - -import androidx.annotation.NonNull; - -import javax.inject.Inject; - -import fr.free.nrw.commons.BuildConfig; -import fr.free.nrw.commons.data.DBOpenHelper; -import fr.free.nrw.commons.di.CommonsDaggerContentProvider; -import timber.log.Timber; - -import static fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao.Table.COLUMN_MEDIA_NAME; -import static fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao.Table.TABLE_NAME; - -/** - * Handles private storage for Bookmark pictures - */ -public class BookmarkPicturesContentProvider extends CommonsDaggerContentProvider { - - private static final String BASE_PATH = "bookmarks"; - public static final Uri BASE_URI = Uri.parse("content://" + BuildConfig.BOOKMARK_AUTHORITY + "/" + BASE_PATH); - - /** - * Append bookmark pictures name to the base uri - */ - public static Uri uriForName(String name) { - return Uri.parse(BASE_URI.toString() + "/" + name); - } - - @Inject - DBOpenHelper dbOpenHelper; - - @Override - public String getType(@NonNull Uri uri) { - return null; - } - - /** - * Queries the SQLite database for the bookmark pictures - * @param uri : contains the uri for bookmark pictures - * @param projection - * @param selection : handles Where - * @param selectionArgs : the condition of Where clause - * @param sortOrder : ascending or descending - */ - @SuppressWarnings("ConstantConditions") - @Override - public Cursor query(@NonNull Uri uri, String[] projection, String selection, - String[] selectionArgs, String sortOrder) { - SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); - queryBuilder.setTables(TABLE_NAME); - - SQLiteDatabase db = dbOpenHelper.getReadableDatabase(); - Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder); - cursor.setNotificationUri(getContext().getContentResolver(), uri); - - return cursor; - } - - /** - * Handles the update query of local SQLite Database - * @param uri : contains the uri for bookmark pictures - * @param contentValues : new values to be entered to db - * @param selection : handles Where - * @param selectionArgs : the condition of Where clause - */ - @SuppressWarnings("ConstantConditions") - @Override - public int update(@NonNull Uri uri, ContentValues contentValues, String selection, - String[] selectionArgs) { - SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase(); - int rowsUpdated; - if (TextUtils.isEmpty(selection)) { - int id = Integer.valueOf(uri.getLastPathSegment()); - rowsUpdated = sqlDB.update(TABLE_NAME, - contentValues, - COLUMN_MEDIA_NAME + " = ?", - new String[]{String.valueOf(id)}); - } else { - throw new IllegalArgumentException( - "Parameter `selection` should be empty when updating an ID"); - } - getContext().getContentResolver().notifyChange(uri, null); - return rowsUpdated; - } - - /** - * Handles the insertion of new bookmark pictures record to local SQLite Database - */ - @SuppressWarnings("ConstantConditions") - @Override - public Uri insert(@NonNull Uri uri, ContentValues contentValues) { - SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase(); - long id = sqlDB.insert(BookmarkPicturesDao.Table.TABLE_NAME, null, contentValues); - getContext().getContentResolver().notifyChange(uri, null); - return Uri.parse(BASE_URI + "/" + id); - } - - @SuppressWarnings("ConstantConditions") - @Override - public int delete(@NonNull Uri uri, String s, String[] strings) { - int rows; - SQLiteDatabase db = dbOpenHelper.getReadableDatabase(); - Timber.d("Deleting bookmark name %s", uri.getLastPathSegment()); - rows = db.delete(TABLE_NAME, - "media_name = ?", - new String[]{uri.getLastPathSegment()} - ); - getContext().getContentResolver().notifyChange(uri, null); - return rows; - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesContentProvider.kt b/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesContentProvider.kt new file mode 100644 index 000000000..bf6f6039b --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesContentProvider.kt @@ -0,0 +1,100 @@ +package fr.free.nrw.commons.bookmarks.pictures + +import android.content.ContentValues +import android.database.Cursor +import android.database.sqlite.SQLiteQueryBuilder +import android.net.Uri +import fr.free.nrw.commons.BuildConfig +import fr.free.nrw.commons.di.CommonsDaggerContentProvider +import androidx.core.net.toUri +import fr.free.nrw.commons.bookmarks.pictures.BookmarksTable.COLUMN_MEDIA_NAME +import fr.free.nrw.commons.bookmarks.pictures.BookmarksTable.TABLE_NAME + +/** + * Handles private storage for Bookmark pictures + */ +class BookmarkPicturesContentProvider : CommonsDaggerContentProvider() { + override fun getType(uri: Uri): String? = null + + /** + * Queries the SQLite database for the bookmark pictures + * @param uri : contains the uri for bookmark pictures + * @param projection + * @param selection : handles Where + * @param selectionArgs : the condition of Where clause + * @param sortOrder : ascending or descending + */ + override fun query( + uri: Uri, projection: Array?, selection: String?, + selectionArgs: Array?, sortOrder: String? + ): Cursor { + val queryBuilder = SQLiteQueryBuilder().apply { + tables = TABLE_NAME + } + + val cursor = queryBuilder.query( + requireDb(), projection, selection, + selectionArgs, null, null, sortOrder + ) + cursor.setNotificationUri(requireContext().contentResolver, uri) + + return cursor + } + + /** + * Handles the update query of local SQLite Database + * @param uri : contains the uri for bookmark pictures + * @param contentValues : new values to be entered to db + * @param selection : handles Where + * @param selectionArgs : the condition of Where clause + */ + override fun update( + uri: Uri, contentValues: ContentValues?, selection: String?, + selectionArgs: Array? + ): Int { + val rowsUpdated: Int + if (selection.isNullOrEmpty()) { + val id = uri.lastPathSegment!!.toInt() + rowsUpdated = requireDb().update( + TABLE_NAME, + contentValues, + "$COLUMN_MEDIA_NAME = ?", + arrayOf(id.toString()) + ) + } else { + throw IllegalArgumentException( + "Parameter `selection` should be empty when updating an ID" + ) + } + requireContext().contentResolver.notifyChange(uri, null) + return rowsUpdated + } + + /** + * Handles the insertion of new bookmark pictures record to local SQLite Database + */ + override fun insert(uri: Uri, contentValues: ContentValues?): Uri { + val id = requireDb().insert(TABLE_NAME, null, contentValues) + requireContext().contentResolver.notifyChange(uri, null) + return "$BASE_URI/$id".toUri() + } + + override fun delete(uri: Uri, s: String?, strings: Array?): Int { + val rows: Int = requireDb().delete( + TABLE_NAME, + "media_name = ?", + arrayOf(uri.lastPathSegment) + ) + requireContext().contentResolver.notifyChange(uri, null) + return rows + } + + companion object { + private const val BASE_PATH = "bookmarks" + @JvmField + val BASE_URI: Uri = "content://${BuildConfig.BOOKMARK_AUTHORITY}/$BASE_PATH".toUri() + + @JvmStatic + fun uriForName(name: String): Uri = "$BASE_URI/$name".toUri() + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesController.java b/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesController.java deleted file mode 100644 index 7b644586c..000000000 --- a/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesController.java +++ /dev/null @@ -1,63 +0,0 @@ -package fr.free.nrw.commons.bookmarks.pictures; - -import fr.free.nrw.commons.Media; -import fr.free.nrw.commons.bookmarks.models.Bookmark; -import fr.free.nrw.commons.media.MediaClient; -import io.reactivex.Observable; -import io.reactivex.ObservableSource; -import io.reactivex.Single; -import io.reactivex.functions.Function; -import java.util.ArrayList; -import java.util.List; -import javax.inject.Inject; -import javax.inject.Singleton; - -@Singleton -public class BookmarkPicturesController { - - private final MediaClient mediaClient; - private final BookmarkPicturesDao bookmarkDao; - - private List currentBookmarks; - - @Inject - public BookmarkPicturesController(MediaClient mediaClient, BookmarkPicturesDao bookmarkDao) { - this.mediaClient = mediaClient; - this.bookmarkDao = bookmarkDao; - currentBookmarks = new ArrayList<>(); - } - - /** - * Loads the Media objects from the raw data stored in DB and the API. - * @return a list of bookmarked Media object - */ - Single> loadBookmarkedPictures() { - List bookmarks = bookmarkDao.getAllBookmarks(); - currentBookmarks = bookmarks; - return Observable.fromIterable(bookmarks) - .flatMap((Function>) this::getMediaFromBookmark) - .toList(); - } - - private Observable getMediaFromBookmark(Bookmark bookmark) { - return mediaClient.getMedia(bookmark.getMediaName()) - .toObservable() - .onErrorResumeNext(Observable.empty()); - } - - /** - * Loads the Media objects from the raw data stored in DB and the API. - * @return a list of bookmarked Media object - */ - boolean needRefreshBookmarkedPictures() { - List bookmarks = bookmarkDao.getAllBookmarks(); - return bookmarks.size() != currentBookmarks.size(); - } - - /** - * Cancels the requests to the API and the DB - */ - void stop() { - //noop - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesController.kt b/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesController.kt new file mode 100644 index 000000000..5ee88d973 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesController.kt @@ -0,0 +1,38 @@ +package fr.free.nrw.commons.bookmarks.pictures + +import fr.free.nrw.commons.Media +import fr.free.nrw.commons.bookmarks.models.Bookmark +import fr.free.nrw.commons.media.MediaClient +import io.reactivex.Observable +import io.reactivex.Single +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class BookmarkPicturesController @Inject constructor( + private val mediaClient: MediaClient, + private val bookmarkDao: BookmarkPicturesDao +) { + private var currentBookmarks: List = listOf() + + /** + * Loads the Media objects from the raw data stored in DB and the API. + * @return a list of bookmarked Media object + */ + fun loadBookmarkedPictures(): Single> { + val bookmarks = bookmarkDao.getAllBookmarks() + currentBookmarks = bookmarks + return Observable.fromIterable(bookmarks).flatMap { + mediaClient.getMedia(it.mediaName) + .toObservable() + .onErrorResumeNext(Observable.empty()) + }.toList() + } + + fun needRefreshBookmarkedPictures(): Boolean { + val bookmarks = bookmarkDao.getAllBookmarks() + return bookmarks.size != currentBookmarks.size + } + + fun stop() = Unit +} diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesDao.java b/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesDao.java deleted file mode 100644 index c214ae996..000000000 --- a/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesDao.java +++ /dev/null @@ -1,227 +0,0 @@ -package fr.free.nrw.commons.bookmarks.pictures; - -import android.annotation.SuppressLint; -import android.content.ContentProviderClient; -import android.content.ContentValues; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.os.RemoteException; - -import androidx.annotation.NonNull; - -import java.util.ArrayList; -import java.util.List; - -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Provider; -import javax.inject.Singleton; - -import fr.free.nrw.commons.bookmarks.models.Bookmark; - -import static fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesContentProvider.BASE_URI; - -@Singleton -public class BookmarkPicturesDao { - - private final Provider clientProvider; - - @Inject - public BookmarkPicturesDao(@Named("bookmarks") Provider clientProvider) { - this.clientProvider = clientProvider; - } - - - /** - * Find all persisted pictures bookmarks on database - * - * @return list of bookmarks - */ - @NonNull - public List getAllBookmarks() { - List items = new ArrayList<>(); - Cursor cursor = null; - ContentProviderClient db = clientProvider.get(); - try { - cursor = db.query( - BookmarkPicturesContentProvider.BASE_URI, - Table.ALL_FIELDS, - null, - new String[]{}, - null); - while (cursor != null && cursor.moveToNext()) { - items.add(fromCursor(cursor)); - } - } catch (RemoteException e) { - throw new RuntimeException(e); - } finally { - if (cursor != null) { - cursor.close(); - } - db.release(); - } - return items; - } - - - /** - * Look for a bookmark in database and in order to insert or delete it - * - * @param bookmark : Bookmark object - * @return boolean : is bookmark now fav ? - */ - public boolean updateBookmark(Bookmark bookmark) { - boolean bookmarkExists = findBookmark(bookmark); - if (bookmarkExists) { - deleteBookmark(bookmark); - } else { - addBookmark(bookmark); - } - return !bookmarkExists; - } - - /** - * Add a Bookmark to database - * - * @param bookmark : Bookmark to add - */ - private void addBookmark(Bookmark bookmark) { - ContentProviderClient db = clientProvider.get(); - try { - db.insert(BASE_URI, toContentValues(bookmark)); - } catch (RemoteException e) { - throw new RuntimeException(e); - } finally { - db.release(); - } - } - - /** - * Delete a bookmark from database - * - * @param bookmark : Bookmark to delete - */ - private void deleteBookmark(Bookmark bookmark) { - ContentProviderClient db = clientProvider.get(); - try { - if (bookmark.getContentUri() == null) { - throw new RuntimeException("tried to delete item with no content URI"); - } else { - db.delete(bookmark.getContentUri(), null, null); - } - } catch (RemoteException e) { - throw new RuntimeException(e); - } finally { - db.release(); - } - } - - /** - * Find a bookmark from database based on its name - * - * @param bookmark : Bookmark to find - * @return boolean : is bookmark in database ? - */ - public boolean findBookmark(Bookmark bookmark) { - if (bookmark == null) {//Avoiding NPE's - return false; - } - - Cursor cursor = null; - ContentProviderClient db = clientProvider.get(); - try { - cursor = db.query( - BookmarkPicturesContentProvider.BASE_URI, - Table.ALL_FIELDS, - Table.COLUMN_MEDIA_NAME + "=?", - new String[]{bookmark.getMediaName()}, - null); - if (cursor != null && cursor.moveToFirst()) { - return true; - } - } catch (RemoteException e) { - // This feels lazy, but to hell with checked exceptions. :) - throw new RuntimeException(e); - } finally { - if (cursor != null) { - cursor.close(); - } - db.release(); - } - return false; - } - - @SuppressLint("Range") - @NonNull - Bookmark fromCursor(Cursor cursor) { - String fileName = cursor.getString(cursor.getColumnIndex(Table.COLUMN_MEDIA_NAME)); - return new Bookmark( - fileName, - cursor.getString(cursor.getColumnIndex(Table.COLUMN_CREATOR)), - BookmarkPicturesContentProvider.uriForName(fileName) - ); - } - - private ContentValues toContentValues(Bookmark bookmark) { - ContentValues cv = new ContentValues(); - cv.put(BookmarkPicturesDao.Table.COLUMN_MEDIA_NAME, bookmark.getMediaName()); - cv.put(BookmarkPicturesDao.Table.COLUMN_CREATOR, bookmark.getMediaCreator()); - return cv; - } - - - public static class Table { - public static final String TABLE_NAME = "bookmarks"; - - public static final String COLUMN_MEDIA_NAME = "media_name"; - public static final String COLUMN_CREATOR = "media_creator"; - - // NOTE! KEEP IN SAME ORDER AS THEY ARE DEFINED UP THERE. HELPS HARD CODE COLUMN INDICES. - public static final String[] ALL_FIELDS = { - COLUMN_MEDIA_NAME, - COLUMN_CREATOR - }; - - public static final String DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS " + TABLE_NAME; - - public static final String CREATE_TABLE_STATEMENT = "CREATE TABLE " + TABLE_NAME + " (" - + COLUMN_MEDIA_NAME + " STRING PRIMARY KEY," - + COLUMN_CREATOR + " STRING" - + ");"; - - public static void onCreate(SQLiteDatabase db) { - db.execSQL(CREATE_TABLE_STATEMENT); - } - - public static void onDelete(SQLiteDatabase db) { - db.execSQL(DROP_TABLE_STATEMENT); - onCreate(db); - } - - public static void onUpdate(SQLiteDatabase db, int from, int to) { - if (from == to) { - return; - } - if (from < 7) { - // doesn't exist yet - from++; - onUpdate(db, from, to); - return; - } - - if (from == 7) { - // table added in version 8 - onCreate(db); - from++; - onUpdate(db, from, to); - return; - } - - if (from == 8) { - from++; - onUpdate(db, from, to); - return; - } - } - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesDao.kt b/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesDao.kt new file mode 100644 index 000000000..e30b3160d --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesDao.kt @@ -0,0 +1,141 @@ +package fr.free.nrw.commons.bookmarks.pictures + +import android.content.ContentProviderClient +import android.content.ContentValues +import android.database.Cursor +import android.os.RemoteException +import androidx.core.content.contentValuesOf +import fr.free.nrw.commons.bookmarks.models.Bookmark +import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesContentProvider.Companion.BASE_URI +import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesContentProvider.Companion.uriForName +import fr.free.nrw.commons.bookmarks.pictures.BookmarksTable.ALL_FIELDS +import fr.free.nrw.commons.bookmarks.pictures.BookmarksTable.COLUMN_CREATOR +import fr.free.nrw.commons.bookmarks.pictures.BookmarksTable.COLUMN_MEDIA_NAME +import fr.free.nrw.commons.utils.getString +import javax.inject.Inject +import javax.inject.Named +import javax.inject.Provider +import javax.inject.Singleton + +@Singleton +class BookmarkPicturesDao @Inject constructor( + @param:Named("bookmarks") private val clientProvider: Provider +) { + /** + * Find all persisted pictures bookmarks on database + * + * @return list of bookmarks + */ + fun getAllBookmarks(): List { + val items: MutableList = mutableListOf() + var cursor: Cursor? = null + val db = clientProvider.get() + try { + cursor = db.query( + BASE_URI, ALL_FIELDS, null, arrayOf(), null + ) + while (cursor != null && cursor.moveToNext()) { + items.add(fromCursor(cursor)) + } + } catch (e: RemoteException) { + throw RuntimeException(e) + } finally { + cursor?.close() + db.release() + } + return items + } + + /** + * Look for a bookmark in database and in order to insert or delete it + * + * @param bookmark : Bookmark object + * @return boolean : is bookmark now fav ? + */ + fun updateBookmark(bookmark: Bookmark): Boolean { + val bookmarkExists = findBookmark(bookmark) + if (bookmarkExists) { + deleteBookmark(bookmark) + } else { + addBookmark(bookmark) + } + return !bookmarkExists + } + + /** + * Add a Bookmark to database + * + * @param bookmark : Bookmark to add + */ + private fun addBookmark(bookmark: Bookmark) { + val db = clientProvider.get() + try { + db.insert(BASE_URI, toContentValues(bookmark)) + } catch (e: RemoteException) { + throw RuntimeException(e) + } finally { + db.release() + } + } + + /** + * Delete a bookmark from database + * + * @param bookmark : Bookmark to delete + */ + private fun deleteBookmark(bookmark: Bookmark) { + val db = clientProvider.get() + try { + if (bookmark.contentUri == null) { + throw RuntimeException("tried to delete item with no content URI") + } else { + db.delete(bookmark.contentUri!!, null, null) + } + } catch (e: RemoteException) { + throw RuntimeException(e) + } finally { + db.release() + } + } + + /** + * Find a bookmark from database based on its name + * + * @param bookmark : Bookmark to find + * @return boolean : is bookmark in database ? + */ + fun findBookmark(bookmark: Bookmark?): Boolean { + if (bookmark == null) { + return false + } + + var cursor: Cursor? = null + val db = clientProvider.get() + try { + cursor = db.query( + BASE_URI, ALL_FIELDS, "$COLUMN_MEDIA_NAME=?", arrayOf(bookmark.mediaName), null + ) + if (cursor != null && cursor.moveToFirst()) { + return true + } + } catch (e: RemoteException) { + throw RuntimeException(e) + } finally { + cursor?.close() + db.release() + } + return false + } + + fun fromCursor(cursor: Cursor): Bookmark { + val fileName = cursor.getString(COLUMN_MEDIA_NAME) + return Bookmark( + fileName, cursor.getString(COLUMN_CREATOR), uriForName(fileName) + ) + } + + private fun toContentValues(bookmark: Bookmark): ContentValues = contentValuesOf( + COLUMN_MEDIA_NAME to bookmark.mediaName, + COLUMN_CREATOR to bookmark.mediaCreator + ) +} 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/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarksTable.kt b/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarksTable.kt new file mode 100644 index 000000000..6a8f4d541 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarksTable.kt @@ -0,0 +1,54 @@ +package fr.free.nrw.commons.bookmarks.pictures + +import android.database.sqlite.SQLiteDatabase + +object BookmarksTable { + const val TABLE_NAME: String = "bookmarks" + const val COLUMN_MEDIA_NAME: String = "media_name" + const val COLUMN_CREATOR: String = "media_creator" + + // NOTE! KEEP IN SAME ORDER AS THEY ARE DEFINED UP THERE. HELPS HARD CODE COLUMN INDICES. + val ALL_FIELDS = arrayOf( + COLUMN_MEDIA_NAME, + COLUMN_CREATOR + ) + + const val DROP_TABLE_STATEMENT: String = "DROP TABLE IF EXISTS $TABLE_NAME" + + const val CREATE_TABLE_STATEMENT: String = ("CREATE TABLE $TABLE_NAME (" + + "$COLUMN_MEDIA_NAME STRING PRIMARY KEY, " + + "$COLUMN_CREATOR STRING" + + ");") + + fun onCreate(db: SQLiteDatabase) = + db.execSQL(CREATE_TABLE_STATEMENT) + + fun onDelete(db: SQLiteDatabase) { + db.execSQL(DROP_TABLE_STATEMENT) + onCreate(db) + } + + fun onUpdate(db: SQLiteDatabase, from: Int, to: Int) { + if (from == to) { + return + } + + if (from < 7) { + // doesn't exist yet + onUpdate(db, from+1, to) + return + } + + if (from == 7) { + // table added in version 8 + onCreate(db) + onUpdate(db, from+1, to) + return + } + + if (from == 8) { + onUpdate(db, from+1, to) + return + } + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryContentProvider.kt b/app/src/main/java/fr/free/nrw/commons/category/CategoryContentProvider.kt index ddd7f5ae4..f5cec0fce 100644 --- a/app/src/main/java/fr/free/nrw/commons/category/CategoryContentProvider.kt +++ b/app/src/main/java/fr/free/nrw/commons/category/CategoryContentProvider.kt @@ -9,12 +9,9 @@ import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteQueryBuilder import android.net.Uri import android.text.TextUtils -import androidx.annotation.NonNull import fr.free.nrw.commons.BuildConfig -import fr.free.nrw.commons.data.DBOpenHelper import fr.free.nrw.commons.di.CommonsDaggerContentProvider -import timber.log.Timber -import javax.inject.Inject +import androidx.core.net.toUri class CategoryContentProvider : CommonsDaggerContentProvider() { @@ -23,9 +20,6 @@ class CategoryContentProvider : CommonsDaggerContentProvider() { addURI(BuildConfig.CATEGORY_AUTHORITY, "${BASE_PATH}/#", CATEGORIES_ID) } - @Inject - lateinit var dbOpenHelper: DBOpenHelper - @SuppressWarnings("ConstantConditions") override fun query(uri: Uri, projection: Array?, selection: String?, selectionArgs: Array?, sortOrder: String?): Cursor? { @@ -34,7 +28,7 @@ class CategoryContentProvider : CommonsDaggerContentProvider() { } val uriType = uriMatcher.match(uri) - val db = dbOpenHelper.readableDatabase + val db = requireDb() val cursor: Cursor? = when (uriType) { CATEGORIES -> queryBuilder.query( @@ -58,45 +52,37 @@ class CategoryContentProvider : CommonsDaggerContentProvider() { else -> throw IllegalArgumentException("Unknown URI $uri") } - cursor?.setNotificationUri(context?.contentResolver, uri) + cursor?.setNotificationUri(requireContext().contentResolver, uri) return cursor } - override fun getType(uri: Uri): String? { - return null - } + override fun getType(uri: Uri): String? = null @SuppressWarnings("ConstantConditions") - override fun insert(uri: Uri, contentValues: ContentValues?): Uri? { + override fun insert(uri: Uri, contentValues: ContentValues?): Uri { val uriType = uriMatcher.match(uri) - val sqlDB = dbOpenHelper.writableDatabase val id: Long when (uriType) { CATEGORIES -> { - id = sqlDB.insert(TABLE_NAME, null, contentValues) + id = requireDb().insert(TABLE_NAME, null, contentValues) } else -> throw IllegalArgumentException("Unknown URI: $uri") } - context?.contentResolver?.notifyChange(uri, null) - return Uri.parse("${Companion.BASE_URI}/$id") + requireContext().contentResolver?.notifyChange(uri, null) + return "${BASE_URI}/$id".toUri() } @SuppressWarnings("ConstantConditions") - override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int { - // Not implemented - return 0 - } + override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int = 0 @SuppressWarnings("ConstantConditions") override fun bulkInsert(uri: Uri, values: Array): Int { - Timber.d("Hello, bulk insert! (CategoryContentProvider)") val uriType = uriMatcher.match(uri) - val sqlDB = dbOpenHelper.writableDatabase + val sqlDB = requireDb() sqlDB.beginTransaction() when (uriType) { CATEGORIES -> { for (value in values) { - Timber.d("Inserting! %s", value) sqlDB.insert(TABLE_NAME, null, value) } sqlDB.setTransactionSuccessful() @@ -104,7 +90,7 @@ class CategoryContentProvider : CommonsDaggerContentProvider() { else -> throw IllegalArgumentException("Unknown URI: $uri") } sqlDB.endTransaction() - context?.contentResolver?.notifyChange(uri, null) + requireContext().contentResolver?.notifyChange(uri, null) return values.size } @@ -112,17 +98,18 @@ class CategoryContentProvider : CommonsDaggerContentProvider() { override fun update(uri: Uri, contentValues: ContentValues?, selection: String?, selectionArgs: Array?): Int { val uriType = uriMatcher.match(uri) - val sqlDB = dbOpenHelper.writableDatabase val rowsUpdated: Int when (uriType) { CATEGORIES_ID -> { if (TextUtils.isEmpty(selection)) { val id = uri.lastPathSegment?.toInt() ?: throw IllegalArgumentException("Invalid ID") - rowsUpdated = sqlDB.update(TABLE_NAME, + rowsUpdated = requireDb().update( + TABLE_NAME, contentValues, "$COLUMN_ID = ?", - arrayOf(id.toString())) + arrayOf(id.toString()) + ) } else { throw IllegalArgumentException( "Parameter `selection` should be empty when updating an ID") @@ -130,7 +117,7 @@ class CategoryContentProvider : CommonsDaggerContentProvider() { } else -> throw IllegalArgumentException("Unknown URI: $uri with type $uriType") } - context?.contentResolver?.notifyChange(uri, null) + requireContext().contentResolver?.notifyChange(uri, null) return rowsUpdated } @@ -165,13 +152,9 @@ class CategoryContentProvider : CommonsDaggerContentProvider() { "$COLUMN_TIMES_USED INTEGER" + ");" - fun uriForId(id: Int): Uri { - return Uri.parse("${BASE_URI}/$id") - } + fun uriForId(id: Int): Uri = Uri.parse("${BASE_URI}/$id") - fun onCreate(db: SQLiteDatabase) { - db.execSQL(CREATE_TABLE_STATEMENT) - } + fun onCreate(db: SQLiteDatabase) = db.execSQL(CREATE_TABLE_STATEMENT) fun onDelete(db: SQLiteDatabase) { db.execSQL(DROP_TABLE_STATEMENT) @@ -200,6 +183,6 @@ class CategoryContentProvider : CommonsDaggerContentProvider() { private const val CATEGORIES = 1 private const val CATEGORIES_ID = 2 private const val BASE_PATH = "categories" - val BASE_URI: Uri = Uri.parse("content://${BuildConfig.CATEGORY_AUTHORITY}/${Companion.BASE_PATH}") + val BASE_URI: Uri = "content://${BuildConfig.CATEGORY_AUTHORITY}/${BASE_PATH}".toUri() } } diff --git a/app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.kt b/app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.kt index 7cb7f60f7..7bbe5de06 100644 --- a/app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.kt +++ b/app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.kt @@ -4,9 +4,8 @@ import android.content.Context import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteException import android.database.sqlite.SQLiteOpenHelper -import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao -import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao -import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao +import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable +import fr.free.nrw.commons.bookmarks.pictures.BookmarksTable import fr.free.nrw.commons.category.CategoryDao import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao @@ -30,16 +29,16 @@ class DBOpenHelper( */ override fun onCreate(db: SQLiteDatabase) { CategoryDao.Table.onCreate(db) - BookmarkPicturesDao.Table.onCreate(db) - BookmarkItemsDao.Table.onCreate(db) + BookmarksTable.onCreate(db) + BookmarkItemsTable.onCreate(db) RecentSearchesDao.Table.onCreate(db) RecentLanguagesDao.Table.onCreate(db) } override fun onUpgrade(db: SQLiteDatabase, from: Int, to: Int) { CategoryDao.Table.onUpdate(db, from, to) - BookmarkPicturesDao.Table.onUpdate(db, from, to) - BookmarkItemsDao.Table.onUpdate(db, from, to) + BookmarksTable.onUpdate(db, from, to) + BookmarkItemsTable.onUpdate(db, from, to) RecentSearchesDao.Table.onUpdate(db, from, to) RecentLanguagesDao.Table.onUpdate(db, from, to) deleteTable(db, CONTRIBUTIONS_TABLE) diff --git a/app/src/main/java/fr/free/nrw/commons/di/CommonsDaggerContentProvider.kt b/app/src/main/java/fr/free/nrw/commons/di/CommonsDaggerContentProvider.kt index c1bda689c..4c77d1aad 100644 --- a/app/src/main/java/fr/free/nrw/commons/di/CommonsDaggerContentProvider.kt +++ b/app/src/main/java/fr/free/nrw/commons/di/CommonsDaggerContentProvider.kt @@ -1,14 +1,25 @@ package fr.free.nrw.commons.di import android.content.ContentProvider +import android.database.sqlite.SQLiteDatabase +import fr.free.nrw.commons.data.DBOpenHelper import fr.free.nrw.commons.di.ApplicationlessInjection.Companion.getInstance +import javax.inject.Inject abstract class CommonsDaggerContentProvider : ContentProvider() { + @JvmField + @Inject + var dbOpenHelper: DBOpenHelper? = null + override fun onCreate(): Boolean { inject() return true } + fun requireDbOpenHelper(): DBOpenHelper = dbOpenHelper!! + + fun requireDb(): SQLiteDatabase = requireDbOpenHelper().writableDatabase!! + private fun inject() { val injection = getInstance(context!!) diff --git a/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesContentProvider.java b/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesContentProvider.java deleted file mode 100644 index ab6dd7b05..000000000 --- a/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesContentProvider.java +++ /dev/null @@ -1,202 +0,0 @@ -package fr.free.nrw.commons.explore.recentsearches; - -import android.content.ContentValues; -import android.content.UriMatcher; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteQueryBuilder; -import android.net.Uri; -import android.text.TextUtils; - -import androidx.annotation.NonNull; - -import javax.inject.Inject; - -import fr.free.nrw.commons.BuildConfig; -import fr.free.nrw.commons.data.DBOpenHelper; -import fr.free.nrw.commons.di.CommonsDaggerContentProvider; -import timber.log.Timber; - -import static android.content.UriMatcher.NO_MATCH; -import static fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao.Table.ALL_FIELDS; -import static fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao.Table.COLUMN_ID; -import static fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao.Table.TABLE_NAME; - - -/** - * This class contains functions for executing queries for - * inserting, searching, deleting, editing recent searches in SqLite DB - **/ -public class RecentSearchesContentProvider extends CommonsDaggerContentProvider { - - // For URI matcher - private static final int RECENT_SEARCHES = 1; - private static final int RECENT_SEARCHES_ID = 2; - private static final String BASE_PATH = "recent_searches"; - public static final Uri BASE_URI = Uri.parse("content://" + BuildConfig.RECENT_SEARCH_AUTHORITY + "/" + BASE_PATH); - private static final UriMatcher uriMatcher = new UriMatcher(NO_MATCH); - - static { - uriMatcher.addURI(BuildConfig.RECENT_SEARCH_AUTHORITY, BASE_PATH, RECENT_SEARCHES); - uriMatcher.addURI(BuildConfig.RECENT_SEARCH_AUTHORITY, BASE_PATH + "/#", RECENT_SEARCHES_ID); - } - - public static Uri uriForId(int id) { - return Uri.parse(BASE_URI.toString() + "/" + id); - } - - @Inject DBOpenHelper dbOpenHelper; - - /** - * This functions executes query for searching recent searches in SqLite DB - **/ - @SuppressWarnings("ConstantConditions") - @Override - public Cursor query(@NonNull Uri uri, String[] projection, String selection, - String[] selectionArgs, String sortOrder) { - SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); - queryBuilder.setTables(TABLE_NAME); - - int uriType = uriMatcher.match(uri); - - SQLiteDatabase db = dbOpenHelper.getReadableDatabase(); - Cursor cursor; - - switch (uriType) { - case RECENT_SEARCHES: - cursor = queryBuilder.query(db, projection, selection, selectionArgs, - null, null, sortOrder); - break; - case RECENT_SEARCHES_ID: - cursor = queryBuilder.query(db, - ALL_FIELDS, - "_id = ?", - new String[]{uri.getLastPathSegment()}, - null, - null, - sortOrder - ); - break; - default: - throw new IllegalArgumentException("Unknown URI" + uri); - } - - cursor.setNotificationUri(getContext().getContentResolver(), uri); - - return cursor; - } - - @Override - public String getType(@NonNull Uri uri) { - return null; - } - - /** - * This functions executes query for inserting a recentSearch object in SqLite DB - **/ - @SuppressWarnings("ConstantConditions") - @Override - public Uri insert(@NonNull Uri uri, ContentValues contentValues) { - int uriType = uriMatcher.match(uri); - SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase(); - long id; - switch (uriType) { - case RECENT_SEARCHES: - id = sqlDB.insert(TABLE_NAME, null, contentValues); - break; - default: - throw new IllegalArgumentException("Unknown URI: " + uri); - } - getContext().getContentResolver().notifyChange(uri, null); - return Uri.parse(BASE_URI + "/" + id); - } - - /** - * This functions executes query for deleting a recentSearch object in SqLite DB - **/ - @Override - public int delete(@NonNull Uri uri, String s, String[] strings) { - int rows; - int uriType = uriMatcher.match(uri); - SQLiteDatabase db = dbOpenHelper.getReadableDatabase(); - switch (uriType) { - case RECENT_SEARCHES_ID: - Timber.d("Deleting recent searches id %s", uri.getLastPathSegment()); - rows = db.delete(RecentSearchesDao.Table.TABLE_NAME, - "_id = ?", - new String[]{uri.getLastPathSegment()} - ); - break; - default: - throw new IllegalArgumentException("Unknown URI" + uri); - } - getContext().getContentResolver().notifyChange(uri, null); - return rows; - } - - /** - * This functions executes query for inserting multiple recentSearch objects in SqLite DB - **/ - @SuppressWarnings("ConstantConditions") - @Override - public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) { - Timber.d("Hello, bulk insert! (RecentSearchesContentProvider)"); - int uriType = uriMatcher.match(uri); - SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase(); - sqlDB.beginTransaction(); - switch (uriType) { - case RECENT_SEARCHES: - for (ContentValues value : values) { - Timber.d("Inserting! %s", value); - sqlDB.insert(TABLE_NAME, null, value); - } - break; - default: - throw new IllegalArgumentException("Unknown URI: " + uri); - } - sqlDB.setTransactionSuccessful(); - sqlDB.endTransaction(); - getContext().getContentResolver().notifyChange(uri, null); - return values.length; - } - - /** - * This functions executes query for updating a particular recentSearch object in SqLite DB - **/ - @SuppressWarnings("ConstantConditions") - @Override - public int update(@NonNull Uri uri, ContentValues contentValues, String selection, - String[] selectionArgs) { - /* - SQL Injection warnings: First, note that we're not exposing this to the - outside world (exported="false"). Even then, we should make sure to sanitize - all user input appropriately. Input that passes through ContentValues - should be fine. So only issues are those that pass in via concating. - - In here, the only concat created argument is for id. It is cast to an int, - and will error out otherwise. - */ - int uriType = uriMatcher.match(uri); - SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase(); - int rowsUpdated; - switch (uriType) { - case RECENT_SEARCHES_ID: - if (TextUtils.isEmpty(selection)) { - int id = Integer.valueOf(uri.getLastPathSegment()); - rowsUpdated = sqlDB.update(TABLE_NAME, - contentValues, - COLUMN_ID + " = ?", - new String[]{String.valueOf(id)}); - } else { - throw new IllegalArgumentException( - "Parameter `selection` should be empty when updating an ID"); - } - break; - default: - throw new IllegalArgumentException("Unknown URI: " + uri + " with type " + uriType); - } - getContext().getContentResolver().notifyChange(uri, null); - return rowsUpdated; - } -} - diff --git a/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesContentProvider.kt b/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesContentProvider.kt new file mode 100644 index 000000000..f30636db7 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesContentProvider.kt @@ -0,0 +1,174 @@ +package fr.free.nrw.commons.explore.recentsearches + +import android.content.ContentValues +import android.content.UriMatcher +import android.database.Cursor +import android.database.sqlite.SQLiteQueryBuilder +import android.net.Uri +import androidx.core.net.toUri +import fr.free.nrw.commons.BuildConfig +import fr.free.nrw.commons.di.CommonsDaggerContentProvider +import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao.Table.ALL_FIELDS +import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao.Table.COLUMN_ID +import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao.Table.TABLE_NAME + +/** + * This class contains functions for executing queries for + * inserting, searching, deleting, editing recent searches in SqLite DB + */ +class RecentSearchesContentProvider : CommonsDaggerContentProvider() { + + /** + * This functions executes query for searching recent searches in SqLite DB + */ + override fun query( + uri: Uri, projection: Array?, selection: String?, + selectionArgs: Array?, sortOrder: String? + ): Cursor { + val queryBuilder = SQLiteQueryBuilder().apply { + tables = TABLE_NAME + } + + val uriType = uriMatcher.match(uri) + + val cursor = when (uriType) { + RECENT_SEARCHES -> queryBuilder.query( + requireDb(), projection, selection, selectionArgs, + null, null, sortOrder + ) + + RECENT_SEARCHES_ID -> queryBuilder.query( + requireDb(), + ALL_FIELDS, + "$COLUMN_ID = ?", + arrayOf(uri.lastPathSegment), + null, + null, + sortOrder + ) + + else -> throw IllegalArgumentException("Unknown URI$uri") + } + + cursor.setNotificationUri(requireContext().contentResolver, uri) + + return cursor + } + + override fun getType(uri: Uri): String? = null + + /** + * This functions executes query for inserting a recentSearch object in SqLite DB + */ + override fun insert(uri: Uri, contentValues: ContentValues?): Uri? { + val uriType = uriMatcher.match(uri) + val id: Long = when (uriType) { + RECENT_SEARCHES -> requireDb().insert(TABLE_NAME, null, contentValues) + + else -> throw IllegalArgumentException("Unknown URI: $uri") + } + requireContext().contentResolver.notifyChange(uri, null) + return "$BASE_URI/$id".toUri() + } + + /** + * This functions executes query for deleting a recentSearch object in SqLite DB + */ + override fun delete(uri: Uri, s: String?, strings: Array?): Int { + val rows: Int + val uriType = uriMatcher.match(uri) + when (uriType) { + RECENT_SEARCHES_ID -> { + rows = requireDb().delete( + TABLE_NAME, + "_id = ?", + arrayOf(uri.lastPathSegment) + ) + } + + else -> throw IllegalArgumentException("Unknown URI - $uri") + } + requireContext().contentResolver.notifyChange(uri, null) + return rows + } + + /** + * This functions executes query for inserting multiple recentSearch objects in SqLite DB + */ + override fun bulkInsert(uri: Uri, values: Array): Int { + val uriType = uriMatcher.match(uri) + val sqlDB = requireDb() + sqlDB.beginTransaction() + when (uriType) { + RECENT_SEARCHES -> for (value in values) { + sqlDB.insert(TABLE_NAME, null, value) + } + + else -> throw IllegalArgumentException("Unknown URI: $uri") + } + sqlDB.setTransactionSuccessful() + sqlDB.endTransaction() + requireContext().contentResolver.notifyChange(uri, null) + return values.size + } + + /** + * This functions executes query for updating a particular recentSearch object in SqLite DB + */ + override fun update( + uri: Uri, contentValues: ContentValues?, selection: String?, + selectionArgs: Array? + ): Int { + /* + SQL Injection warnings: First, note that we're not exposing this to the + outside world (exported="false"). Even then, we should make sure to sanitize + all user input appropriately. Input that passes through ContentValues + should be fine. So only issues are those that pass in via concating. + + In here, the only concat created argument is for id. It is cast to an int, + and will error out otherwise. + */ + val uriType = uriMatcher.match(uri) + val rowsUpdated: Int + when (uriType) { + RECENT_SEARCHES_ID -> if (selection.isNullOrEmpty()) { + val id = uri.lastPathSegment!!.toInt() + rowsUpdated = requireDb().update( + TABLE_NAME, + contentValues, + "$COLUMN_ID = ?", + arrayOf(id.toString()) + ) + } else { + throw IllegalArgumentException( + "Parameter `selection` should be empty when updating an ID" + ) + } + + else -> throw IllegalArgumentException("Unknown URI: $uri with type $uriType") + } + requireContext().contentResolver.notifyChange(uri, null) + return rowsUpdated + } + + companion object { + // For URI matcher + private const val RECENT_SEARCHES = 1 + private const val RECENT_SEARCHES_ID = 2 + private const val BASE_PATH = "recent_searches" + + @JvmField + val BASE_URI: Uri = "content://${BuildConfig.RECENT_SEARCH_AUTHORITY}/$BASE_PATH".toUri() + + private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH) + + init { + uriMatcher.addURI(BuildConfig.RECENT_SEARCH_AUTHORITY, BASE_PATH, RECENT_SEARCHES) + uriMatcher.addURI(BuildConfig.RECENT_SEARCH_AUTHORITY, "$BASE_PATH/#", RECENT_SEARCHES_ID) + } + + @JvmStatic + fun uriForId(id: Int): Uri = "$BASE_URI/$id".toUri() + } +} + diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.kt b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.kt index b66c888aa..92cca611e 100644 --- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.kt @@ -166,7 +166,7 @@ class MediaDetailPagerFragment : CommonsDaggerSupportFragment(), OnPageChangeLis val mediaDetailFragment = adapter!!.currentMediaDetailFragment when (item.itemId) { R.id.menu_bookmark_current_image -> { - val bookmarkExists = bookmarkDao!!.updateBookmark(bookmark) + val bookmarkExists = bookmarkDao!!.updateBookmark(bookmark!!) val snackbar = if (bookmarkExists) Snackbar.make( requireView(), R.string.add_bookmark, @@ -436,7 +436,7 @@ ${m.pageTitle.canonicalUri}""" bookmark = Bookmark( m.filename, m.getAuthorOrUser(), - BookmarkPicturesContentProvider.uriForName(m.filename) + BookmarkPicturesContentProvider.uriForName(m.filename!!) ) updateBookmarkState(menu.findItem(R.id.menu_bookmark_current_image)) val contributionState = provider.getContributionStateAt(position) diff --git a/app/src/main/java/fr/free/nrw/commons/recentlanguages/RecentLanguagesContentProvider.kt b/app/src/main/java/fr/free/nrw/commons/recentlanguages/RecentLanguagesContentProvider.kt index facc4384f..04c18fbb7 100644 --- a/app/src/main/java/fr/free/nrw/commons/recentlanguages/RecentLanguagesContentProvider.kt +++ b/app/src/main/java/fr/free/nrw/commons/recentlanguages/RecentLanguagesContentProvider.kt @@ -3,17 +3,13 @@ package fr.free.nrw.commons.recentlanguages import android.content.ContentValues import android.database.Cursor -import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteQueryBuilder import android.net.Uri -import android.text.TextUtils import fr.free.nrw.commons.BuildConfig -import fr.free.nrw.commons.data.DBOpenHelper import fr.free.nrw.commons.di.CommonsDaggerContentProvider import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao.Table.COLUMN_NAME import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao.Table.TABLE_NAME -import javax.inject.Inject -import timber.log.Timber +import androidx.core.net.toUri /** @@ -23,27 +19,17 @@ class RecentLanguagesContentProvider : CommonsDaggerContentProvider() { companion object { private const val BASE_PATH = "recent_languages" - val BASE_URI: Uri = - Uri.parse( - "content://${BuildConfig.RECENT_LANGUAGE_AUTHORITY}/$BASE_PATH" - ) + val BASE_URI: Uri = "content://${BuildConfig.RECENT_LANGUAGE_AUTHORITY}/$BASE_PATH".toUri() /** * Append language code to the base URI * @param languageCode Code of a language */ @JvmStatic - fun uriForCode(languageCode: String): Uri { - return Uri.parse("$BASE_URI/$languageCode") - } + fun uriForCode(languageCode: String): Uri = "$BASE_URI/$languageCode".toUri() } - @Inject - lateinit var dbOpenHelper: DBOpenHelper - - override fun getType(uri: Uri): String? { - return null - } + override fun getType(uri: Uri): String? = null /** * Queries the SQLite database for the recently used languages @@ -60,11 +46,12 @@ class RecentLanguagesContentProvider : CommonsDaggerContentProvider() { selectionArgs: Array?, sortOrder: String? ): Cursor? { - val queryBuilder = SQLiteQueryBuilder() - queryBuilder.tables = TABLE_NAME - val db = dbOpenHelper.readableDatabase + val queryBuilder = SQLiteQueryBuilder().apply { + tables = TABLE_NAME + } + val cursor = queryBuilder.query( - db, + requireDb(), projection, selection, selectionArgs, @@ -72,7 +59,7 @@ class RecentLanguagesContentProvider : CommonsDaggerContentProvider() { null, sortOrder ) - cursor.setNotificationUri(context?.contentResolver, uri) + cursor.setNotificationUri(requireContext().contentResolver, uri) return cursor } @@ -89,12 +76,11 @@ class RecentLanguagesContentProvider : CommonsDaggerContentProvider() { selection: String?, selectionArgs: Array? ): Int { - val sqlDB = dbOpenHelper.writableDatabase val rowsUpdated: Int if (selection.isNullOrEmpty()) { val id = uri.lastPathSegment?.toInt() ?: throw IllegalArgumentException("Invalid URI: $uri") - rowsUpdated = sqlDB.update( + rowsUpdated = requireDb().update( TABLE_NAME, contentValues, "$COLUMN_NAME = ?", @@ -104,7 +90,7 @@ class RecentLanguagesContentProvider : CommonsDaggerContentProvider() { throw IllegalArgumentException("Parameter `selection` should be empty when updating an ID") } - context?.contentResolver?.notifyChange(uri, null) + requireContext().contentResolver?.notifyChange(uri, null) return rowsUpdated } @@ -114,14 +100,13 @@ class RecentLanguagesContentProvider : CommonsDaggerContentProvider() { * @param contentValues : new values to be entered to the database */ override fun insert(uri: Uri, contentValues: ContentValues?): Uri? { - val sqlDB = dbOpenHelper.writableDatabase - val id = sqlDB.insert( + val id = requireDb().insert( TABLE_NAME, null, contentValues ) - context?.contentResolver?.notifyChange(uri, null) - return Uri.parse("$BASE_URI/$id") + requireContext().contentResolver?.notifyChange(uri, null) + return "$BASE_URI/$id".toUri() } /** @@ -129,14 +114,12 @@ class RecentLanguagesContentProvider : CommonsDaggerContentProvider() { * @param uri : contains the URI for recently used languages */ override fun delete(uri: Uri, s: String?, strings: Array?): Int { - val db = dbOpenHelper.readableDatabase - Timber.d("Deleting recently used language %s", uri.lastPathSegment) - val rows = db.delete( + val rows = requireDb().delete( TABLE_NAME, "language_code = ?", arrayOf(uri.lastPathSegment) ) - context?.contentResolver?.notifyChange(uri, null) + requireContext().contentResolver?.notifyChange(uri, null) return rows } } diff --git a/app/src/main/java/fr/free/nrw/commons/utils/DatabaseUtils.kt b/app/src/main/java/fr/free/nrw/commons/utils/DatabaseUtils.kt new file mode 100644 index 000000000..69560279b --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/utils/DatabaseUtils.kt @@ -0,0 +1,32 @@ +package fr.free.nrw.commons.utils + +import android.annotation.SuppressLint +import android.database.Cursor + +fun Cursor.getStringArray(name: String): List = + stringToArray(getString(name)) + +@SuppressLint("Range") +fun Cursor.getString(name: String): String = + getString(getColumnIndex(name)) + +/** + * Converts string to List + * @param listString comma separated single string from of list items + * @return List of string + */ +fun stringToArray(listString: String?): List { + if (listString.isNullOrEmpty()) return emptyList(); + val elements = listString.split(",".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + return listOf(*elements) +} + +/** + * Converts string to List + * @param list list of items + * @return string comma separated single string of items + */ +fun arrayToString(list: List?): String? { + return list?.joinToString(",") +} + diff --git a/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/BookmarkListRootFragmentUnitTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/BookmarkListRootFragmentUnitTest.kt index d168fe6e4..c2c33667b 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/BookmarkListRootFragmentUnitTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/BookmarkListRootFragmentUnitTest.kt @@ -288,7 +288,7 @@ class BookmarkListRootFragmentUnitTest { @Test @Throws(Exception::class) fun testOnItemClick() { - fragment.onItemClick(null, null, 0, 0) + fragment.onItemClick(null, view, 0, 0) verify(childFragmentManager).beginTransaction() verify(childFragmentTransaction).commit() verify(childFragmentManager).executePendingTransactions() diff --git a/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/BookmarksPagerAdapterTests.kt b/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/BookmarksPagerAdapterTests.kt index bf143ecc0..3123bc1d0 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/BookmarksPagerAdapterTests.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/BookmarksPagerAdapterTests.kt @@ -2,12 +2,20 @@ package fr.free.nrw.commons.bookmarks import android.content.Context import androidx.fragment.app.FragmentManager +import fr.free.nrw.commons.TestCommonsApplication import org.junit.Assert import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.MockitoAnnotations +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +import org.robolectric.annotation.LooperMode +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [21], application = TestCommonsApplication::class) +@LooperMode(LooperMode.Mode.PAUSED) class BookmarksPagerAdapterTests { @Mock private lateinit var bookmarksPagerAdapter: BookmarksPagerAdapter diff --git a/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/LoggedOutBookmarksPagerAdapterTests.kt b/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/LoggedOutBookmarksPagerAdapterTests.kt index 7ba7d559e..19d612aba 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/LoggedOutBookmarksPagerAdapterTests.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/LoggedOutBookmarksPagerAdapterTests.kt @@ -2,15 +2,23 @@ package fr.free.nrw.commons.bookmarks import android.content.Context import androidx.fragment.app.FragmentManager +import fr.free.nrw.commons.TestCommonsApplication import org.junit.Assert import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.MockitoAnnotations +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +import org.robolectric.annotation.LooperMode /** * BookmarksPagerAdapter when user is not loggedIn. */ +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [21], application = TestCommonsApplication::class) +@LooperMode(LooperMode.Mode.PAUSED) class LoggedOutBookmarksPagerAdapterTests { @Mock private lateinit var bookmarksPagerAdapter: BookmarksPagerAdapter diff --git a/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/items/BookmarkItemsControllerTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/items/BookmarkItemsControllerTest.kt index 98279520d..38bf6b267 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/items/BookmarkItemsControllerTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/items/BookmarkItemsControllerTest.kt @@ -21,7 +21,7 @@ class BookmarkItemsControllerTest { @Before fun setup() { MockitoAnnotations.openMocks(this) - whenever(bookmarkDao!!.allBookmarksItems) + whenever(bookmarkDao!!.getAllBookmarksItems()) .thenReturn(mockBookmarkList) } diff --git a/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDaoTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDaoTest.kt index 60ae7869d..e7ecbe074 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDaoTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDaoTest.kt @@ -18,20 +18,20 @@ import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.verify import com.nhaarman.mockitokotlin2.whenever import fr.free.nrw.commons.TestCommonsApplication -import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao.Table.COLUMN_CATEGORIES_DESCRIPTION_LIST -import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao.Table.COLUMN_CATEGORIES_NAME_LIST -import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao.Table.COLUMN_CATEGORIES_THUMBNAIL_LIST -import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao.Table.COLUMN_DESCRIPTION -import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao.Table.COLUMN_ID -import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao.Table.COLUMN_IMAGE -import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao.Table.COLUMN_INSTANCE_LIST -import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao.Table.COLUMN_IS_SELECTED -import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao.Table.COLUMN_NAME -import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao.Table.CREATE_TABLE_STATEMENT -import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao.Table.DROP_TABLE_STATEMENT -import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao.Table.onCreate -import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao.Table.onDelete -import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao.Table.onUpdate +import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_CATEGORIES_DESCRIPTION_LIST +import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_CATEGORIES_NAME_LIST +import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_CATEGORIES_THUMBNAIL_LIST +import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_DESCRIPTION +import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_ID +import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_IMAGE +import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_INSTANCE_LIST +import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_IS_SELECTED +import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_NAME +import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.CREATE_TABLE_STATEMENT +import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.DROP_TABLE_STATEMENT +import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.onCreate +import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.onDelete +import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.onUpdate import fr.free.nrw.commons.category.CategoryItem import fr.free.nrw.commons.upload.structure.depictions.DepictedItem import org.junit.Assert @@ -135,7 +135,7 @@ class BookmarkItemsDaoTest { whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())) .thenReturn(createCursor(14)) - val result = testObject.allBookmarksItems + val result = testObject.getAllBookmarksItems() Assert.assertEquals(14, (result.size)) } @@ -145,20 +145,20 @@ class BookmarkItemsDaoTest { whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenThrow( RemoteException(""), ) - testObject.allBookmarksItems + testObject.getAllBookmarksItems() } @Test fun getAllItemsBookmarksReturnsEmptyList_emptyCursor() { whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())) .thenReturn(createCursor(0)) - Assert.assertTrue(testObject.allBookmarksItems.isEmpty()) + Assert.assertTrue(testObject.getAllBookmarksItems().isEmpty()) } @Test fun getAllItemsBookmarksReturnsEmptyList_nullCursor() { whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenReturn(null) - Assert.assertTrue(testObject.allBookmarksItems.isEmpty()) + Assert.assertTrue(testObject.getAllBookmarksItems().isEmpty()) } @Test @@ -167,7 +167,7 @@ class BookmarkItemsDaoTest { whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenReturn(mockCursor) whenever(mockCursor.moveToFirst()).thenReturn(false) - testObject.allBookmarksItems + testObject.getAllBookmarksItems() verify(mockCursor).close() } diff --git a/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/items/BookmarkItemsFragmentUnitTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/items/BookmarkItemsFragmentUnitTest.kt index 12292af91..086fd703b 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/items/BookmarkItemsFragmentUnitTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/items/BookmarkItemsFragmentUnitTest.kt @@ -88,7 +88,7 @@ class BookmarkItemsFragmentUnitTest { context = ApplicationProvider.getApplicationContext() OkHttpConnectionFactory.CLIENT = createTestClient() val activity = Robolectric.buildActivity(ProfileActivity::class.java).create().get() - fragment = BookmarkItemsFragment.newInstance() + fragment = BookmarkItemsFragment() val fragmentManager: FragmentManager = activity.supportFragmentManager val fragmentTransaction: FragmentTransaction = fragmentManager.beginTransaction() fragmentTransaction.add(fragment, null) diff --git a/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/pictures/BookmarkPictureDaoTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/pictures/BookmarkPictureDaoTest.kt index 02668ff1c..5932f24bc 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/pictures/BookmarkPictureDaoTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/pictures/BookmarkPictureDaoTest.kt @@ -19,14 +19,14 @@ import com.nhaarman.mockitokotlin2.verify import com.nhaarman.mockitokotlin2.whenever import fr.free.nrw.commons.TestCommonsApplication import fr.free.nrw.commons.bookmarks.models.Bookmark -import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesContentProvider.BASE_URI -import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao.Table.COLUMN_CREATOR -import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao.Table.COLUMN_MEDIA_NAME -import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao.Table.CREATE_TABLE_STATEMENT -import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao.Table.DROP_TABLE_STATEMENT -import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao.Table.onCreate -import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao.Table.onDelete -import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao.Table.onUpdate +import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesContentProvider.Companion.BASE_URI +import fr.free.nrw.commons.bookmarks.pictures.BookmarksTable.COLUMN_CREATOR +import fr.free.nrw.commons.bookmarks.pictures.BookmarksTable.COLUMN_MEDIA_NAME +import fr.free.nrw.commons.bookmarks.pictures.BookmarksTable.CREATE_TABLE_STATEMENT +import fr.free.nrw.commons.bookmarks.pictures.BookmarksTable.DROP_TABLE_STATEMENT +import fr.free.nrw.commons.bookmarks.pictures.BookmarksTable.onCreate +import fr.free.nrw.commons.bookmarks.pictures.BookmarksTable.onDelete +import fr.free.nrw.commons.bookmarks.pictures.BookmarksTable.onUpdate import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue @@ -84,7 +84,7 @@ class BookmarkPictureDaoTest { fun getAllBookmarks() { whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenReturn(createCursor(14)) - var result = testObject.allBookmarks + var result = testObject.getAllBookmarks() assertEquals(14, (result.size)) } @@ -92,19 +92,19 @@ class BookmarkPictureDaoTest { @Test(expected = RuntimeException::class) fun getAllBookmarksTranslatesExceptions() { whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenThrow(RemoteException("")) - testObject.allBookmarks + testObject.getAllBookmarks() } @Test fun getAllBookmarksReturnsEmptyList_emptyCursor() { whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenReturn(createCursor(0)) - assertTrue(testObject.allBookmarks.isEmpty()) + assertTrue(testObject.getAllBookmarks().isEmpty()) } @Test fun getAllBookmarksReturnsEmptyList_nullCursor() { whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenReturn(null) - assertTrue(testObject.allBookmarks.isEmpty()) + assertTrue(testObject.getAllBookmarks().isEmpty()) } @Test @@ -113,7 +113,7 @@ class BookmarkPictureDaoTest { whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenReturn(mockCursor) whenever(mockCursor.moveToFirst()).thenReturn(false) - testObject.allBookmarks + testObject.getAllBookmarks() verify(mockCursor).close() } diff --git a/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesControllerTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesControllerTest.kt index bb0718c27..154a5a9b3 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesControllerTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesControllerTest.kt @@ -35,7 +35,7 @@ class BookmarkPicturesControllerTest { fun setup() { MockitoAnnotations.initMocks(this) val mockMedia = mockMedia - whenever(bookmarkDao!!.allBookmarks) + whenever(bookmarkDao!!.getAllBookmarks()) .thenReturn(mockBookmarkList) whenever( mediaClient!!.getMedia( 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 diff --git a/app/src/test/kotlin/fr/free/nrw/commons/category/CategoryDaoTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/category/CategoryDaoTest.kt index e93f48c55..3499ffaee 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/category/CategoryDaoTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/category/CategoryDaoTest.kt @@ -29,7 +29,7 @@ import fr.free.nrw.commons.category.CategoryDao.Table.DROP_TABLE_STATEMENT import fr.free.nrw.commons.category.CategoryDao.Table.onCreate import fr.free.nrw.commons.category.CategoryDao.Table.onDelete import fr.free.nrw.commons.category.CategoryDao.Table.onUpdate -import fr.free.nrw.commons.explore.recentsearches.RecentSearchesContentProvider.uriForId +import fr.free.nrw.commons.explore.recentsearches.RecentSearchesContentProvider.Companion.uriForId import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull diff --git a/app/src/test/kotlin/fr/free/nrw/commons/explore/recentsearches/RecentSearchesDaoTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/explore/recentsearches/RecentSearchesDaoTest.kt index 3e550b670..c772f796e 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/explore/recentsearches/RecentSearchesDaoTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/explore/recentsearches/RecentSearchesDaoTest.kt @@ -18,8 +18,8 @@ import com.nhaarman.mockitokotlin2.verify import com.nhaarman.mockitokotlin2.whenever import fr.free.nrw.commons.TestCommonsApplication import fr.free.nrw.commons.explore.models.RecentSearch -import fr.free.nrw.commons.explore.recentsearches.RecentSearchesContentProvider.BASE_URI -import fr.free.nrw.commons.explore.recentsearches.RecentSearchesContentProvider.uriForId +import fr.free.nrw.commons.explore.recentsearches.RecentSearchesContentProvider.Companion.BASE_URI +import fr.free.nrw.commons.explore.recentsearches.RecentSearchesContentProvider.Companion.uriForId import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao.Table.ALL_FIELDS import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao.Table.COLUMN_ID import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao.Table.COLUMN_LAST_USED