diff --git a/app/src/main/java/com/battlelancer/seriesguide/movies/MovieClickListenerImpl.kt b/app/src/main/java/com/battlelancer/seriesguide/movies/MovieClickListenerImpl.kt index 983e018391..b5a5e03bdf 100644 --- a/app/src/main/java/com/battlelancer/seriesguide/movies/MovieClickListenerImpl.kt +++ b/app/src/main/java/com/battlelancer/seriesguide/movies/MovieClickListenerImpl.kt @@ -1,5 +1,5 @@ -// Copyright 2023 Uwe Trottmann // SPDX-License-Identifier: Apache-2.0 +// Copyright 2020, 2022-2024 Uwe Trottmann package com.battlelancer.seriesguide.movies @@ -14,7 +14,7 @@ import com.battlelancer.seriesguide.movies.tools.MovieTools import com.battlelancer.seriesguide.provider.SgRoomDatabase import com.battlelancer.seriesguide.util.Utils -internal open class MovieClickListenerImpl(val context: Context) : MovieClickListener { +open class MovieClickListenerImpl(val context: Context) : MovieClickListener { override fun onClickMovie(movieTmdbId: Int, posterView: ImageView) { if (movieTmdbId == -1) return diff --git a/app/src/main/java/com/battlelancer/seriesguide/movies/MovieViewHolder.kt b/app/src/main/java/com/battlelancer/seriesguide/movies/MovieViewHolder.kt index a99518c1d6..3f3b51a49c 100644 --- a/app/src/main/java/com/battlelancer/seriesguide/movies/MovieViewHolder.kt +++ b/app/src/main/java/com/battlelancer/seriesguide/movies/MovieViewHolder.kt @@ -1,5 +1,5 @@ -// Copyright 2023 Uwe Trottmann // SPDX-License-Identifier: Apache-2.0 +// Copyright 2019-2024 Uwe Trottmann package com.battlelancer.seriesguide.movies @@ -9,7 +9,7 @@ import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import com.battlelancer.seriesguide.R -import com.battlelancer.seriesguide.databinding.ItemDiscoverMovieBinding +import com.battlelancer.seriesguide.databinding.ItemMovieBinding import com.battlelancer.seriesguide.movies.database.SgMovie import com.battlelancer.seriesguide.util.ImageTools import com.squareup.picasso.Picasso @@ -17,7 +17,7 @@ import com.uwetrottmann.tmdb2.entities.BaseMovie import java.text.DateFormat class MovieViewHolder( - val binding: ItemDiscoverMovieBinding, + val binding: ItemMovieBinding, itemClickListener: MovieClickListener? ) : RecyclerView.ViewHolder(binding.root) { @@ -107,7 +107,7 @@ class MovieViewHolder( @JvmStatic fun inflate(parent: ViewGroup, itemClickListener: MovieClickListener?): MovieViewHolder { return MovieViewHolder( - ItemDiscoverMovieBinding.inflate( + ItemMovieBinding.inflate( LayoutInflater.from(parent.context), parent, false ), itemClickListener diff --git a/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesActivityImpl.kt b/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesActivityImpl.kt index f1a3af257d..3ce05115cc 100644 --- a/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesActivityImpl.kt +++ b/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesActivityImpl.kt @@ -1,6 +1,6 @@ -// Copyright 2013-2023 Uwe Trottmann -// Copyright 2017 Christophe Beyls // SPDX-License-Identifier: Apache-2.0 +// Copyright 2013-2024 Uwe Trottmann +// Copyright 2017 Christophe Beyls package com.battlelancer.seriesguide.movies @@ -172,11 +172,8 @@ open class MoviesActivityImpl : BaseTopActivity() { } companion object { - // const val SEARCH_LOADER_ID = 100 const val NOW_TRAKT_USER_LOADER_ID = 101 const val NOW_TRAKT_FRIENDS_LOADER_ID = 102 - const val WATCHLIST_LOADER_ID = 103 - const val COLLECTION_LOADER_ID = 104 const val TAB_POSITION_DISCOVER = 0 const val TAB_POSITION_WATCHLIST_DEFAULT = 1 diff --git a/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesWatchedAdapter.kt b/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesAdapter.kt similarity index 94% rename from app/src/main/java/com/battlelancer/seriesguide/movies/MoviesWatchedAdapter.kt rename to app/src/main/java/com/battlelancer/seriesguide/movies/MoviesAdapter.kt index 57931aee2d..68922c7f33 100644 --- a/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesWatchedAdapter.kt +++ b/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesAdapter.kt @@ -1,5 +1,5 @@ -// Copyright 2023 Uwe Trottmann // SPDX-License-Identifier: Apache-2.0 +// Copyright 2019-2024 Uwe Trottmann package com.battlelancer.seriesguide.movies @@ -12,7 +12,10 @@ import com.battlelancer.seriesguide.movies.tools.MovieTools import com.battlelancer.seriesguide.settings.TmdbSettings import java.text.DateFormat -internal class MoviesWatchedAdapter( +/** + * Binds pages of [SgMovie] to [MovieViewHolder]. + */ +class MoviesAdapter( context: Context, val itemClickListener: MovieClickListener ) : PagingDataAdapter(DIFF_CALLBACK) { diff --git a/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesBaseFragment.kt b/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesBaseFragment.kt index e4775b5e35..2703daa2d4 100644 --- a/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesBaseFragment.kt +++ b/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesBaseFragment.kt @@ -1,94 +1,105 @@ -// Copyright 2023 Uwe Trottmann // SPDX-License-Identifier: Apache-2.0 +// Copyright 2019-2024 Uwe Trottmann package com.battlelancer.seriesguide.movies -import android.database.Cursor import android.os.Bundle import android.view.View -import android.widget.GridView import android.widget.TextView import androidx.annotation.StringRes -import androidx.core.view.ViewCompat +import androidx.core.view.isGone import androidx.fragment.app.Fragment import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider -import androidx.loader.app.LoaderManager -import androidx.loader.content.Loader -import com.battlelancer.seriesguide.movies.MoviesDistillationSettings.MoviesSortOrderChangedEvent +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.RecyclerView +import com.battlelancer.seriesguide.R +import com.battlelancer.seriesguide.ui.AutoGridLayoutManager import com.battlelancer.seriesguide.ui.MoviesActivity +import com.battlelancer.seriesguide.ui.widgets.SgFastScroller +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.launch import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode /** - * A shell for a fragment displaying a number of movies. + * Base class for a fragment displaying movies. */ -abstract class MoviesBaseFragment : Fragment(), LoaderManager.LoaderCallbacks { +abstract class MoviesBaseFragment : Fragment() { - private lateinit var adapter: MoviesCursorAdapter + private lateinit var adapter: MoviesAdapter - /** - * Return a loader id different from any other used within [com.battlelancer.seriesguide.ui.MoviesActivity]. - */ - abstract val loaderId: Int + abstract val model: MoviesWatchedViewModel @get:StringRes abstract val emptyViewTextResId: Int - abstract val gridView: GridView + abstract val recyclerView: RecyclerView abstract val emptyView: TextView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - + // note: fragment is in static view pager tab so will never be destroyed if swiped away EventBus.getDefault().register(this) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - // enable app bar scrolling out of view - ViewCompat.setNestedScrollingEnabled(gridView, true) - emptyView.setText(emptyViewTextResId) - gridView.emptyView = emptyView - ViewModelProvider(requireActivity()).get(MoviesActivityViewModel::class.java) + adapter = MoviesAdapter(requireContext(), MovieClickListenerImpl(requireContext())) + + recyclerView.also { + it.setHasFixedSize(true) + it.layoutManager = + AutoGridLayoutManager( + context, R.dimen.movie_grid_columnWidth, 1, 3 + ) + it.adapter = adapter + SgFastScroller(requireContext(), it) + } + + requireActivity().addMenuProvider( + MoviesOptionsMenu(requireActivity()), + viewLifecycleOwner, + Lifecycle.State.RESUMED + ) + + ViewModelProvider(requireActivity())[MoviesActivityViewModel::class.java] .scrollTabToTopLiveData .observe(viewLifecycleOwner) { if (it != null) { if (it.tabPosition == getTabPosition(it.isShowingNowTab)) { - gridView.smoothScrollToPosition(0) + recyclerView.scrollToPosition(0) } } } - adapter = MoviesCursorAdapter( - context, MovieClickListenerImpl(requireContext()), - loaderId - ) - gridView.adapter = adapter - - requireActivity().addMenuProvider( - MoviesOptionsMenu(requireActivity()), - viewLifecycleOwner, - Lifecycle.State.RESUMED - ) + viewLifecycleOwner.lifecycleScope.launch { + adapter.onPagesUpdatedFlow.conflate().collectLatest { + emptyView.isGone = adapter.itemCount > 0 + } + } - LoaderManager.getInstance(this).initLoader(loaderId, null, this) + viewLifecycleOwner.lifecycleScope.launch { + model.items.collectLatest { + adapter.submitData(it) + } + } } override fun onDestroy() { super.onDestroy() - EventBus.getDefault().unregister(this) } @Subscribe(threadMode = ThreadMode.MAIN) - fun onEventMainThread(@Suppress("UNUSED_PARAMETER") event: MoviesSortOrderChangedEvent) { - LoaderManager.getInstance(this).restartLoader(loaderId, null, this) + fun onEventMainThread(@Suppress("UNUSED_PARAMETER") event: MoviesDistillationSettings.MoviesSortOrderChangedEvent) { + model.updateQueryString() } /** @@ -97,12 +108,4 @@ abstract class MoviesBaseFragment : Fragment(), LoaderManager.LoaderCallbacks, data: Cursor) { - adapter.swapCursor(data) - } - - override fun onLoaderReset(loader: Loader) { - adapter.swapCursor(null) - } - } diff --git a/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesCollectionFragment.kt b/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesCollectionFragment.kt index 01dc72c38e..83ace8b970 100644 --- a/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesCollectionFragment.kt +++ b/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesCollectionFragment.kt @@ -1,32 +1,32 @@ -// Copyright 2023 Uwe Trottmann // SPDX-License-Identifier: Apache-2.0 +// Copyright 2019-2020, 2022-2024 Uwe Trottmann package com.battlelancer.seriesguide.movies -import android.database.Cursor import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.GridView import android.widget.TextView -import androidx.loader.content.CursorLoader -import androidx.loader.content.Loader +import androidx.fragment.app.viewModels +import androidx.recyclerview.widget.RecyclerView import com.battlelancer.seriesguide.R import com.battlelancer.seriesguide.databinding.FragmentMoviesCollectionBinding -import com.battlelancer.seriesguide.provider.SeriesGuideContract.Movies /** * Displays the users collection of movies. */ class MoviesCollectionFragment : MoviesBaseFragment() { - override val loaderId: Int = MoviesActivityImpl.COLLECTION_LOADER_ID - override val emptyViewTextResId = R.string.movies_collection_empty - override val gridView: GridView - get() = binding!!.gridViewMoviesCollection + private val _model by viewModels() + + override val model: MoviesWatchedViewModel + get() = _model + + override val recyclerView: RecyclerView + get() = binding!!.recyclerViewMoviesCollection override val emptyView: TextView get() = binding!!.textViewMoviesCollectionEmpty @@ -42,14 +42,6 @@ class MoviesCollectionFragment : MoviesBaseFragment() { .root } - override fun onCreateLoader(id: Int, args: Bundle?): Loader { - return CursorLoader( - requireContext(), Movies.CONTENT_URI, - MoviesCursorAdapter.MoviesQuery.PROJECTION, Movies.SELECTION_COLLECTION, null, - MoviesDistillationSettings.getSortQuery(context) - ) - } - override fun getTabPosition(showingNowTab: Boolean): Int { return if (showingNowTab) { MoviesActivityImpl.TAB_POSITION_COLLECTION_WITH_NOW @@ -58,7 +50,12 @@ class MoviesCollectionFragment : MoviesBaseFragment() { } } + override fun onDestroyView() { + super.onDestroyView() + binding = null + } + companion object { - val liftOnScrollTargetViewId = R.id.gridViewMoviesCollection + val liftOnScrollTargetViewId = R.id.recyclerViewMoviesCollection } } diff --git a/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesCollectionViewModel.kt b/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesCollectionViewModel.kt new file mode 100644 index 0000000000..7d629c334e --- /dev/null +++ b/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesCollectionViewModel.kt @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2024 Uwe Trottmann + +package com.battlelancer.seriesguide.movies + +import android.app.Application +import com.battlelancer.seriesguide.provider.SeriesGuideContract + +class MoviesCollectionViewModel(application: Application) : MoviesWatchedViewModel(application) { + + override val selection: String + get() = SeriesGuideContract.Movies.SELECTION_COLLECTION + +} \ No newline at end of file diff --git a/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesCursorAdapter.java b/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesCursorAdapter.java deleted file mode 100644 index 1103b06dab..0000000000 --- a/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesCursorAdapter.java +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright 2023 Uwe Trottmann -// SPDX-License-Identifier: Apache-2.0 - -package com.battlelancer.seriesguide.movies; - -import static com.battlelancer.seriesguide.provider.SeriesGuideContract.Movies; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.database.Cursor; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; -import androidx.cursoradapter.widget.CursorAdapter; -import com.battlelancer.seriesguide.R; -import com.battlelancer.seriesguide.movies.tools.MovieTools; -import com.battlelancer.seriesguide.settings.DisplaySettings; -import com.battlelancer.seriesguide.settings.TmdbSettings; -import com.battlelancer.seriesguide.util.ImageTools; -import java.text.DateFormat; -import java.util.Date; - -class MoviesCursorAdapter extends CursorAdapter { - - private final int uniqueId; - private final String tmdbImageBaseUrl; - - private final DateFormat dateFormatMovieReleaseDate = MovieTools.getMovieShortDateFormat(); - - private final MovieClickListenerImpl movieClickListener; - - MoviesCursorAdapter(Context context, MovieClickListenerImpl movieClickListener, int uniqueId) { - super(context, null, 0); - this.movieClickListener = movieClickListener; - this.uniqueId = uniqueId; - - // figure out which size of posters to load based on screen density - if (DisplaySettings.isVeryHighDensityScreen(context)) { - tmdbImageBaseUrl = TmdbSettings.getImageBaseUrl(context) - + TmdbSettings.POSTER_SIZE_SPEC_W342; - } else { - tmdbImageBaseUrl = TmdbSettings.getImageBaseUrl(context) - + TmdbSettings.POSTER_SIZE_SPEC_W154; - } - } - - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - // do not use parent layout params to avoid padding issues - @SuppressLint("InflateParams") View v = - LayoutInflater.from(parent.getContext()).inflate(R.layout.item_movie, null); - new ViewHolder(v, movieClickListener); - return v; - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - ViewHolder holder = (ViewHolder) view.getTag(); - holder.bind(context, cursor, dateFormatMovieReleaseDate, tmdbImageBaseUrl, uniqueId); - } - - public static class ViewHolder { - public TextView title; - public TextView releaseDate; - public ImageView poster; - public View contextMenu; - - private int movieTmdbId; - - public ViewHolder(View itemView, MovieClickListenerImpl clickListener) { - itemView.setTag(this); - - this.title = itemView.findViewById(R.id.textViewMovieTitle); - this.releaseDate = itemView.findViewById(R.id.textViewMovieDate); - this.poster = itemView.findViewById(R.id.imageViewMoviePoster); - this.contextMenu = itemView.findViewById(R.id.imageViewMovieItemContextMenu); - - itemView.setOnClickListener(v -> clickListener.onClickMovie(movieTmdbId, poster)); - // context menu - contextMenu - .setOnClickListener(v -> clickListener.onClickMovieMoreOptions(movieTmdbId, v)); - } - - public void bind(Context context, Cursor cursor, DateFormat dateFormat, - String tmdbImageBaseUrl, int uniqueId) { - this.movieTmdbId = cursor.getInt(MoviesQuery.TMDB_ID); - - // title - title.setText(cursor.getString(MoviesQuery.TITLE)); - - // release date - long released = cursor.getLong(MoviesQuery.RELEASED_UTC_MS); - if (released != Long.MAX_VALUE) { - releaseDate.setText(dateFormat.format(new Date(released))); - } else { - releaseDate.setText(""); - } - - // load poster, cache on external storage - String posterPath = cursor.getString(MoviesQuery.POSTER); - // use fixed size so bitmaps can be re-used on config change - ImageTools.loadWithPicasso(context, TextUtils.isEmpty(posterPath) - ? null : tmdbImageBaseUrl + posterPath) - .resizeDimen(R.dimen.movie_poster_width, R.dimen.movie_poster_height) - .centerCrop() - .into(poster); - - // set unique transition names - poster.setTransitionName( - "moviesCursorAdapterPoster_" + uniqueId + "_" + movieTmdbId); - } - } - - public interface MoviesQuery { - - String[] PROJECTION = { Movies._ID, Movies.TMDB_ID, Movies.TITLE, Movies.POSTER, - Movies.RELEASED_UTC_MS }; - - int ID = 0; - int TMDB_ID = 1; - int TITLE = 2; - int POSTER = 3; - int RELEASED_UTC_MS = 4; - } -} diff --git a/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesDiscoverAdapter.java b/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesDiscoverAdapter.java index d57c6817c6..8ab563786f 100644 --- a/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesDiscoverAdapter.java +++ b/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesDiscoverAdapter.java @@ -1,5 +1,5 @@ -// Copyright 2023 Uwe Trottmann // SPDX-License-Identifier: Apache-2.0 +// Copyright 2017-2024 Uwe Trottmann package com.battlelancer.seriesguide.movies; @@ -24,7 +24,7 @@ public class MoviesDiscoverAdapter extends RecyclerView.Adapter() + + override val model: MoviesWatchedViewModel + get() = _model + + override val recyclerView: RecyclerView + get() = binding!!.recyclerViewMoviesWatchlist override val emptyView: TextView get() = binding!!.textViewMoviesWatchlistEmpty @@ -42,14 +42,6 @@ class MoviesWatchListFragment : MoviesBaseFragment() { .root } - override fun onCreateLoader(loaderId: Int, args: Bundle?): Loader { - return CursorLoader( - requireContext(), Movies.CONTENT_URI, - MoviesCursorAdapter.MoviesQuery.PROJECTION, Movies.SELECTION_WATCHLIST, null, - MoviesDistillationSettings.getSortQuery(context) - ) - } - override fun getTabPosition(showingNowTab: Boolean): Int { return if (showingNowTab) { MoviesActivityImpl.TAB_POSITION_WATCHLIST_WITH_NOW @@ -64,6 +56,6 @@ class MoviesWatchListFragment : MoviesBaseFragment() { } companion object { - val liftOnScrollTargetViewId = R.id.gridViewMoviesWatchlist + val liftOnScrollTargetViewId = R.id.recyclerViewMoviesWatchlist } } diff --git a/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesWatchListViewModel.kt b/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesWatchListViewModel.kt new file mode 100644 index 0000000000..38e5094c07 --- /dev/null +++ b/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesWatchListViewModel.kt @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2024 Uwe Trottmann + +package com.battlelancer.seriesguide.movies + +import android.app.Application +import com.battlelancer.seriesguide.provider.SeriesGuideContract + +class MoviesWatchListViewModel(application: Application) : MoviesWatchedViewModel(application) { + + override val selection: String + get() = SeriesGuideContract.Movies.SELECTION_WATCHLIST + +} \ No newline at end of file diff --git a/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesWatchedFragment.kt b/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesWatchedFragment.kt index 48a01e973b..2d97e2ebfe 100644 --- a/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesWatchedFragment.kt +++ b/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesWatchedFragment.kt @@ -1,5 +1,5 @@ -// Copyright 2023 Uwe Trottmann // SPDX-License-Identifier: Apache-2.0 +// Copyright 2019-2024 Uwe Trottmann package com.battlelancer.seriesguide.movies @@ -7,35 +7,31 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.core.view.isGone -import androidx.fragment.app.Fragment +import android.widget.TextView import androidx.fragment.app.viewModels -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.RecyclerView import com.battlelancer.seriesguide.R import com.battlelancer.seriesguide.databinding.FragmentMoviesWatchedBinding -import com.battlelancer.seriesguide.ui.AutoGridLayoutManager -import com.battlelancer.seriesguide.ui.widgets.SgFastScroller -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.conflate -import kotlinx.coroutines.launch -import org.greenrobot.eventbus.EventBus -import org.greenrobot.eventbus.Subscribe -import org.greenrobot.eventbus.ThreadMode -class MoviesWatchedFragment : Fragment() { +/** + * Displays watched movies. + */ +class MoviesWatchedFragment : MoviesBaseFragment() { - private var binding: FragmentMoviesWatchedBinding? = null + override val emptyViewTextResId = R.string.now_movies_empty - private val model by viewModels() - private lateinit var adapter: MoviesWatchedAdapter + private val _model by viewModels() - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - // note: fragment is in static view pager tab so will never be destroyed if swiped away - EventBus.getDefault().register(this) - } + override val model: MoviesWatchedViewModel + get() = _model + + override val recyclerView: RecyclerView + get() = binding!!.recyclerViewMoviesWatched + + override val emptyView: TextView + get() = binding!!.textViewEmptyMoviesWatched + + private var binding: FragmentMoviesWatchedBinding? = null override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -46,52 +42,11 @@ class MoviesWatchedFragment : Fragment() { return binding.root } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - adapter = MoviesWatchedAdapter(requireContext(), MovieClickListenerImpl(requireContext())) - - binding!!.recyclerViewMoviesWatched.also { - it.setHasFixedSize(true) - it.layoutManager = - AutoGridLayoutManager( - context, R.dimen.movie_grid_columnWidth, 1, 3 - ) - it.adapter = adapter - SgFastScroller(requireContext(), it) - } - - requireActivity().addMenuProvider( - MoviesOptionsMenu(requireActivity()), - viewLifecycleOwner, - Lifecycle.State.RESUMED - ) - - ViewModelProvider(requireActivity()).get(MoviesActivityViewModel::class.java) - .scrollTabToTopLiveData - .observe(viewLifecycleOwner) { - if (it != null) { - val positionOfThisTab = if (it.isShowingNowTab) { - MoviesActivityImpl.TAB_POSITION_WATCHED_WITH_NOW - } else { - MoviesActivityImpl.TAB_POSITION_WATCHED_DEFAULT - } - if (it.tabPosition == positionOfThisTab) { - binding?.recyclerViewMoviesWatched?.scrollToPosition(0) - } - } - } - - viewLifecycleOwner.lifecycleScope.launch { - adapter.onPagesUpdatedFlow.conflate().collectLatest { - binding?.textViewEmptyMoviesWatched?.isGone = adapter.itemCount > 0 - } - } - - viewLifecycleOwner.lifecycleScope.launch { - model.items.collectLatest { - adapter.submitData(it) - } + override fun getTabPosition(showingNowTab: Boolean): Int { + return if (showingNowTab) { + MoviesActivityImpl.TAB_POSITION_WATCHED_WITH_NOW + } else { + MoviesActivityImpl.TAB_POSITION_WATCHED_DEFAULT } } @@ -100,16 +55,6 @@ class MoviesWatchedFragment : Fragment() { binding = null } - override fun onDestroy() { - super.onDestroy() - EventBus.getDefault().unregister(this) - } - - @Subscribe(threadMode = ThreadMode.MAIN) - fun onEventMainThread(@Suppress("UNUSED_PARAMETER") event: MoviesDistillationSettings.MoviesSortOrderChangedEvent) { - model.updateQueryString() - } - companion object { val liftOnScrollTargetViewId = R.id.recyclerViewMoviesWatched diff --git a/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesWatchedViewModel.kt b/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesWatchedViewModel.kt index 6a4eba08b7..40b532f89a 100644 --- a/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesWatchedViewModel.kt +++ b/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesWatchedViewModel.kt @@ -1,5 +1,5 @@ -// Copyright 2023 Uwe Trottmann // SPDX-License-Identifier: Apache-2.0 +// Copyright 2019, 2021-2024 Uwe Trottmann package com.battlelancer.seriesguide.movies @@ -20,7 +20,7 @@ import com.battlelancer.seriesguide.provider.SgRoomDatabase import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flatMapLatest -class MoviesWatchedViewModel(application: Application) : +open class MoviesWatchedViewModel(application: Application) : AndroidViewModel(application) { private val queryString = MutableLiveData() @@ -30,7 +30,7 @@ class MoviesWatchedViewModel(application: Application) : ) { SgRoomDatabase.getInstance(getApplication()) .movieHelper() - .getWatchedMovies(SimpleSQLiteQuery(it)) + .getMovies(SimpleSQLiteQuery(it)) }.flow }.cachedIn(viewModelScope) @@ -38,8 +38,10 @@ class MoviesWatchedViewModel(application: Application) : updateQueryString() } + open val selection: String + get() = SeriesGuideContract.Movies.SELECTION_WATCHED + fun updateQueryString() { - val selection = SeriesGuideContract.Movies.SELECTION_WATCHED val order = MoviesDistillationSettings.getSortQuery(getApplication()) queryString.value = "SELECT * FROM ${SeriesGuideDatabase.Tables.MOVIES} WHERE $selection ORDER BY $order" diff --git a/app/src/main/java/com/battlelancer/seriesguide/movies/database/MovieHelper.kt b/app/src/main/java/com/battlelancer/seriesguide/movies/database/MovieHelper.kt index b90a7bcbf9..b192648bc3 100644 --- a/app/src/main/java/com/battlelancer/seriesguide/movies/database/MovieHelper.kt +++ b/app/src/main/java/com/battlelancer/seriesguide/movies/database/MovieHelper.kt @@ -1,5 +1,5 @@ -// Copyright 2023 Uwe Trottmann // SPDX-License-Identifier: Apache-2.0 +// Copyright 2020-2024 Uwe Trottmann package com.battlelancer.seriesguide.movies.database @@ -41,7 +41,7 @@ interface MovieHelper { ): List @RawQuery(observedEntities = [SgMovie::class]) - fun getWatchedMovies(query: SupportSQLiteQuery): PagingSource + fun getMovies(query: SupportSQLiteQuery): PagingSource @Query( """SELECT movies_tmdbid, movies_incollection, movies_inwatchlist, movies_watched, movies_plays diff --git a/app/src/main/res/layout/fragment_movies_collection.xml b/app/src/main/res/layout/fragment_movies_collection.xml index 70b5ecfc69..4c40c3a903 100644 --- a/app/src/main/res/layout/fragment_movies_collection.xml +++ b/app/src/main/res/layout/fragment_movies_collection.xml @@ -1,5 +1,5 @@ - + android:textAppearance="@style/TextAppearance.SeriesGuide.EmptyState" /> - + - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_movies_discover.xml b/app/src/main/res/layout/fragment_movies_discover.xml index 3a40fb4b51..6fc8e27a60 100644 --- a/app/src/main/res/layout/fragment_movies_discover.xml +++ b/app/src/main/res/layout/fragment_movies_discover.xml @@ -15,6 +15,6 @@ android:paddingLeft="@dimen/grid_item_margin_horizontal" android:paddingRight="@dimen/grid_item_margin_horizontal" android:paddingTop="@dimen/grid_item_margin_vertical" - tools:listitem="@layout/item_discover_movie" /> + tools:listitem="@layout/item_movie" /> diff --git a/app/src/main/res/layout/fragment_movies_search.xml b/app/src/main/res/layout/fragment_movies_search.xml index 05d1cea2df..e0e0916cc2 100644 --- a/app/src/main/res/layout/fragment_movies_search.xml +++ b/app/src/main/res/layout/fragment_movies_search.xml @@ -37,7 +37,7 @@ android:paddingTop="@dimen/grid_item_margin_vertical" android:paddingRight="@dimen/grid_item_margin_horizontal" android:paddingBottom="40dp" - tools:listitem="@layout/item_discover_movie" /> + tools:listitem="@layout/item_movie" /> + android:layout_height="match_parent"> @@ -25,6 +24,6 @@ android:paddingTop="@dimen/grid_item_margin_vertical" android:paddingRight="@dimen/grid_item_margin_horizontal" android:paddingBottom="@dimen/grid_item_margin_vertical" - tools:listitem="@layout/item_discover_movie" /> + tools:listitem="@layout/item_movie" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_movies_watchlist.xml b/app/src/main/res/layout/fragment_movies_watchlist.xml index d9892ae066..a61e30a0c5 100644 --- a/app/src/main/res/layout/fragment_movies_watchlist.xml +++ b/app/src/main/res/layout/fragment_movies_watchlist.xml @@ -1,5 +1,5 @@ - + android:textAppearance="@style/TextAppearance.SeriesGuide.EmptyState" /> - + - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/item_discover_movie.xml b/app/src/main/res/layout/item_discover_movie.xml deleted file mode 100644 index 06aa38618d..0000000000 --- a/app/src/main/res/layout/item_discover_movie.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - diff --git a/app/src/main/res/layout/item_movie.xml b/app/src/main/res/layout/item_movie.xml index 725eb56375..06aa38618d 100644 --- a/app/src/main/res/layout/item_movie.xml +++ b/app/src/main/res/layout/item_movie.xml @@ -4,10 +4,16 @@ style="?attr/materialCardViewElevatedStyle" android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_marginLeft="@dimen/grid_item_margin_horizontal" + android:layout_marginTop="@dimen/grid_item_margin_vertical" + android:layout_marginRight="@dimen/grid_item_margin_horizontal" + android:layout_marginBottom="@dimen/grid_item_margin_vertical" android:focusable="true" android:foreground="?attr/selectableItemBackground" tools:layout_width="100dp"> - +