From 00fe2d49a5129e44343962c4ced339875e98af27 Mon Sep 17 00:00:00 2001 From: Martijn Brekhof Date: Tue, 26 Feb 2019 21:08:46 +0100 Subject: [PATCH] Added movie ratings to movie listview items (#613) Redesigning rate text to a five star rating bar. --- app/build.gradle | 1 + app/src/main/java/org/xbmc/kore/Settings.java | 4 + .../ui/sections/video/MovieListFragment.java | 57 +++++-- .../org/xbmc/kore/ui/views/RatingBar.java | 143 ++++++++++++++++++ app/src/main/res/layout/grid_item_movie.xml | 97 ++++++------ .../main/res/layout/grid_item_playlist.xml | 4 +- app/src/main/res/menu/movie_list.xml | 8 + app/src/main/res/values/attr.xml | 11 ++ app/src/main/res/values/colors.xml | 2 + app/src/main/res/values/strings.xml | 1 + app/src/main/res/values/styles.xml | 4 + app/src/main/res/values/themes.xml | 9 ++ 12 files changed, 281 insertions(+), 60 deletions(-) create mode 100644 app/src/main/java/org/xbmc/kore/ui/views/RatingBar.java diff --git a/app/build.gradle b/app/build.gradle index aad9c69..bec4602 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -103,6 +103,7 @@ dependencies { implementation "com.android.support:support-v13:${supportLibVersion}" implementation "com.android.support:design:${supportLibVersion}" implementation "com.android.support:recyclerview-v7:${supportLibVersion}" + implementation "com.android.support.constraint:constraint-layout:1.1.3" implementation 'com.fasterxml.jackson.core:jackson-databind:2.5.2' implementation 'com.jakewharton:butterknife:8.8.1' diff --git a/app/src/main/java/org/xbmc/kore/Settings.java b/app/src/main/java/org/xbmc/kore/Settings.java index b17af21..3f8ad50 100644 --- a/app/src/main/java/org/xbmc/kore/Settings.java +++ b/app/src/main/java/org/xbmc/kore/Settings.java @@ -104,6 +104,10 @@ public class Settings { public static final String KEY_PREF_MOVIES_SHOW_WATCHED_STATUS = "movies_show_watched_status"; public static final boolean DEFAULT_PREF_MOVIES_SHOW_WATCHED_STATUS = true; + // Show watched status on movie list + public static final String KEY_PREF_MOVIES_SHOW_RATING = "movies_show_rating"; + public static final boolean DEFAULT_PREF_MOVIES_SHOW_RATING = true; + // Sort order on albums public static final String KEY_PREF_ALBUMS_SORT_ORDER = "albums_sort_order"; public static final int DEFAULT_PREF_ALBUMS_SORT_ORDER = SORT_BY_ALBUM; diff --git a/app/src/main/java/org/xbmc/kore/ui/sections/video/MovieListFragment.java b/app/src/main/java/org/xbmc/kore/ui/sections/video/MovieListFragment.java index fa27903..d96ef17 100644 --- a/app/src/main/java/org/xbmc/kore/ui/sections/video/MovieListFragment.java +++ b/app/src/main/java/org/xbmc/kore/ui/sections/video/MovieListFragment.java @@ -15,7 +15,6 @@ */ package org.xbmc.kore.ui.sections.video; -import android.app.Activity; import android.content.Context; import android.content.SharedPreferences; import android.content.res.Resources; @@ -45,11 +44,13 @@ import org.xbmc.kore.service.library.LibrarySyncService; import org.xbmc.kore.ui.AbstractCursorListFragment; import org.xbmc.kore.ui.AbstractFragment; import org.xbmc.kore.ui.RecyclerViewCursorAdapter; -import org.xbmc.kore.ui.sections.audio.SongsListFragment; +import org.xbmc.kore.ui.views.RatingBar; import org.xbmc.kore.utils.LogUtils; import org.xbmc.kore.utils.UIUtils; import org.xbmc.kore.utils.Utils; +import java.util.ArrayList; + /** * Fragment that presents the movie list */ @@ -64,6 +65,7 @@ public class MovieListFragment extends AbstractCursorListFragment { private OnMovieSelectedListener listenerActivity; private static boolean showWatchedStatus; + private static boolean showRating; @Override protected String getListSyncType() { return LibrarySyncService.SYNC_ALL_MOVIES; } @@ -103,6 +105,7 @@ public class MovieListFragment extends AbstractCursorListFragment { } showWatchedStatus = preferences.getBoolean(Settings.KEY_PREF_MOVIES_SHOW_WATCHED_STATUS, Settings.DEFAULT_PREF_MOVIES_SHOW_WATCHED_STATUS); + showRating = preferences.getBoolean(Settings.KEY_PREF_MOVIES_SHOW_RATING, Settings.DEFAULT_PREF_MOVIES_SHOW_RATING); String sortOrderStr; int sortOrder = preferences.getInt(Settings.KEY_PREF_MOVIES_SORT_ORDER, Settings.DEFAULT_PREF_MOVIES_SORT_ORDER); @@ -165,12 +168,14 @@ public class MovieListFragment extends AbstractCursorListFragment { sortByDateAdded = menu.findItem(R.id.action_sort_by_date_added), sortByLastPlayed = menu.findItem(R.id.action_sort_by_last_played), sortByLength = menu.findItem(R.id.action_sort_by_length), - showWatchedStatusMenuItem = menu.findItem(R.id.action_show_watched_status); + showWatchedStatusMenuItem = menu.findItem(R.id.action_show_watched_status), + showRatingMenuItem = menu.findItem(R.id.action_show_rating); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity()); hideWatched.setChecked(preferences.getBoolean(Settings.KEY_PREF_MOVIES_FILTER_HIDE_WATCHED, Settings.DEFAULT_PREF_MOVIES_FILTER_HIDE_WATCHED)); showWatchedStatusMenuItem.setChecked(preferences.getBoolean(Settings.KEY_PREF_MOVIES_SHOW_WATCHED_STATUS, Settings.DEFAULT_PREF_MOVIES_SHOW_WATCHED_STATUS)); ignoreArticles.setChecked(preferences.getBoolean(Settings.KEY_PREF_MOVIES_IGNORE_PREFIXES, Settings.DEFAULT_PREF_MOVIES_IGNORE_PREFIXES)); + showRatingMenuItem.setChecked(preferences.getBoolean(Settings.KEY_PREF_MOVIES_SHOW_RATING, Settings.DEFAULT_PREF_MOVIES_SHOW_RATING)); int sortOrder = preferences.getInt(Settings.KEY_PREF_MOVIES_SORT_ORDER, Settings.DEFAULT_PREF_MOVIES_SORT_ORDER); switch (sortOrder) { @@ -216,6 +221,14 @@ public class MovieListFragment extends AbstractCursorListFragment { showWatchedStatus = item.isChecked(); refreshList(); break; + case R.id.action_show_rating: + item.setChecked(!item.isChecked()); + preferences.edit() + .putBoolean(Settings.KEY_PREF_MOVIES_SHOW_RATING, item.isChecked()) + .apply(); + showRating = item.isChecked(); + refreshList(); + break; case R.id.action_ignore_prefixes: item.setChecked(!item.isChecked()); preferences.edit() @@ -355,7 +368,8 @@ public class MovieListFragment extends AbstractCursorListFragment { public static class ViewHolder extends RecyclerViewCursorAdapter.CursorViewHolder { TextView titleView; TextView detailsView; - TextView durationView; + TextView metaInfoView; + RatingBar ratingBar; ImageView checkmarkView; ImageView artView; HostManager hostManager; @@ -377,9 +391,10 @@ public class MovieListFragment extends AbstractCursorListFragment { this.artHeight = artHeight; titleView = itemView.findViewById(R.id.title); detailsView = itemView.findViewById(R.id.details); - durationView = itemView.findViewById(R.id.duration); + metaInfoView = itemView.findViewById(R.id.meta_info); checkmarkView = itemView.findViewById(R.id.checkmark); artView = itemView.findViewById(R.id.art); + ratingBar = itemView.findViewById(R.id.rating_bar); } @Override @@ -389,7 +404,6 @@ public class MovieListFragment extends AbstractCursorListFragment { dataHolder.setTitle(cursor.getString(MovieListQuery.TITLE)); dataHolder.setUndertitle(cursor.getString(MovieListQuery.TAGLINE)); - int movieYear = cursor.getInt(MovieListQuery.YEAR); dataHolder.setRating(cursor.getDouble(MovieListQuery.RATING)); dataHolder.setMaxRating(10); @@ -400,13 +414,17 @@ public class MovieListFragment extends AbstractCursorListFragment { genres : dataHolder.getUnderTitle(); detailsView.setText(details); - int runtime = cursor.getInt(MovieListQuery.RUNTIME) / 60; - String duration = runtime > 0 ? - String.format(context.getString(R.string.minutes_abbrev), String.valueOf(runtime)) + - " | " + movieYear : - String.valueOf(movieYear); - durationView.setText(duration); - dataHolder.setDetails(duration + "\n" + details); + String metaInfo = getMetaInfo(cursor); + metaInfoView.setText(metaInfo); + dataHolder.setDetails(metaInfo + "\n" + details); + + if (showRating && dataHolder.getRating() > 0) { + ratingBar.setMaxRating(dataHolder.getMaxRating()); + ratingBar.setRating(dataHolder.getRating()); + ratingBar.setVisibility(View.VISIBLE); + } else { + ratingBar.setVisibility(View.GONE); + } dataHolder.setPosterUrl(cursor.getString(MovieListQuery.THUMBNAIL)); UIUtils.loadImageWithCharacterAvatar(context, hostManager, @@ -425,5 +443,18 @@ public class MovieListFragment extends AbstractCursorListFragment { artView.setTransitionName("a" + dataHolder.getId()); } } + + private String getMetaInfo(Cursor cursor) { + int runtime = cursor.getInt(MovieListQuery.RUNTIME) / 60; + int movieYear = cursor.getInt(MovieListQuery.YEAR); + + ArrayList duration = new ArrayList<>(); + if (runtime > 0) + duration.add(String.format(context.getString(R.string.minutes_abbrev), + String.valueOf(runtime))); + duration.add(String.valueOf(movieYear)); + + return TextUtils.join(" | ", duration); + } } } diff --git a/app/src/main/java/org/xbmc/kore/ui/views/RatingBar.java b/app/src/main/java/org/xbmc/kore/ui/views/RatingBar.java new file mode 100644 index 0000000..4494e74 --- /dev/null +++ b/app/src/main/java/org/xbmc/kore/ui/views/RatingBar.java @@ -0,0 +1,143 @@ +/* + * Copyright 2019 Martijn Brekhof. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.xbmc.kore.ui.views; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.TypedArray; +import android.graphics.drawable.ClipDrawable; +import android.support.annotation.DrawableRes; +import android.support.v4.content.ContextCompat; +import android.support.v4.widget.ImageViewCompat; +import android.support.v7.widget.AppCompatImageView; +import android.support.v7.widget.LinearLayoutCompat; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.View; +import android.widget.FrameLayout; + +import org.xbmc.kore.R; + +import java.util.ArrayList; + +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; + +public class RatingBar extends LinearLayoutCompat { + + private @DrawableRes int iconResourceId; + private int iconCount; + private double maxRating = 5; + private ArrayList clipDrawables = new ArrayList<>(iconCount); + private int backgroundColor; + private int foregroundColor; + + private int maxTotalClipLevel; + private final int maxClipLevel = 10000; + private double ratingScaleFactor; + + public RatingBar(Context context) { + this(context, null); + } + + public RatingBar(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public RatingBar(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + initializeView(context, attrs, defStyle); + } + + private void initializeView(Context context, AttributeSet attrs, int defStyle) { + TypedArray a = context.getTheme().obtainStyledAttributes( + attrs, + R.styleable.RatingBar, + 0, 0); + + try { + backgroundColor = a.getColor(R.styleable.RatingBar_backgroundColor, 0x43000000); + foregroundColor = a.getColor(R.styleable.RatingBar_foregroundColor, 0xffffffff); + iconCount = a.getInteger(R.styleable.RatingBar_iconCount, 5); + iconResourceId = a.getResourceId(R.styleable.RatingBar_icon, -1); + } finally { + a.recycle(); + } + + maxTotalClipLevel = iconCount * maxClipLevel; + ratingScaleFactor = maxTotalClipLevel / maxRating; + + for(int i = 0; i < iconCount; i++) { + View star = createStar(context, attrs, defStyle); + addView(star); + } + } + + public void setRating(double rating) { + if (rating > this.maxRating) + rating = this.maxRating; + + int scaledRating = (int) (rating * ratingScaleFactor); + + int fullyFilledIconsCount = scaledRating / maxClipLevel; + for(int i = 0; i < fullyFilledIconsCount; i++) { + clipDrawables.get(i).setLevel(maxClipLevel); + } + + if (fullyFilledIconsCount < clipDrawables.size()) { + clipDrawables.get(fullyFilledIconsCount).setLevel(scaledRating - (fullyFilledIconsCount * maxClipLevel)); + + for (int i = fullyFilledIconsCount + 1 ; i < clipDrawables.size(); i++) { + clipDrawables.get(i).setLevel(0); + } + } + + invalidate(); + } + + public void setMaxRating(double maxRating) { + this.maxRating = maxRating; + ratingScaleFactor = maxTotalClipLevel / maxRating; + } + + private FrameLayout createStar(Context context, AttributeSet attrs, int defStyle) { + FrameLayout frameLayout = new FrameLayout(getContext()); + frameLayout.setLayoutParams(new LayoutParams(WRAP_CONTENT, MATCH_PARENT)); + + AppCompatImageView ivStarBackground = new AppCompatImageView(context, attrs, defStyle); + ivStarBackground.setLayoutParams(new LayoutParams(WRAP_CONTENT, MATCH_PARENT)); + ivStarBackground.setImageResource(iconResourceId); + ivStarBackground.setAdjustViewBounds(true); + ImageViewCompat.setImageTintList(ivStarBackground, ColorStateList.valueOf(backgroundColor)); + frameLayout.addView(ivStarBackground); + + ClipDrawable clipDrawable = new ClipDrawable( + ContextCompat.getDrawable(context, iconResourceId), + Gravity.START, + ClipDrawable.HORIZONTAL); + + AppCompatImageView ivStarForeground = new AppCompatImageView(context, attrs, defStyle); + ivStarForeground.setLayoutParams(new LayoutParams(WRAP_CONTENT, MATCH_PARENT)); + ivStarForeground.setImageDrawable(clipDrawable); + ivStarForeground.setAdjustViewBounds(true); + ImageViewCompat.setImageTintList(ivStarForeground, ColorStateList.valueOf(foregroundColor)); + frameLayout.addView(ivStarForeground); + + clipDrawables.add((ClipDrawable) ivStarForeground.getDrawable()); + + return frameLayout; + } +} diff --git a/app/src/main/res/layout/grid_item_movie.xml b/app/src/main/res/layout/grid_item_movie.xml index ce76ced..5ed2cc8 100644 --- a/app/src/main/res/layout/grid_item_movie.xml +++ b/app/src/main/res/layout/grid_item_movie.xml @@ -15,14 +15,17 @@ limitations under the License. --> - + style="@style/Widget.CardView" + tools:layout_width="match_parent" + tools:layout_height="100dp"> - @@ -30,62 +33,66 @@ android:id="@+id/art" android:layout_width="@dimen/movielist_art_width" android:layout_height="@dimen/movielist_art_heigth" - android:layout_alignParentStart="true" - android:layout_alignParentLeft="true" android:contentDescription="@string/poster" - android:scaleType="centerCrop"/> + android:scaleType="centerCrop" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintLeft_toRightOf="@id/art" + app:layout_constraintStart_toEndOf="@id/art" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="@id/art" /> + app:layout_constraintRight_toRightOf="@id/title" + app:layout_constraintLeft_toLeftOf="@id/title" + app:layout_constraintStart_toStartOf="@id/title" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@id/title" /> + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="@id/title" + app:layout_constraintEnd_toStartOf="@id/rating_bar"/> + + - - - - - - - - - - - - - - - \ No newline at end of file + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintRight_toRightOf="parent" /> + + \ No newline at end of file diff --git a/app/src/main/res/layout/grid_item_playlist.xml b/app/src/main/res/layout/grid_item_playlist.xml index af8c83f..d5f4ec2 100644 --- a/app/src/main/res/layout/grid_item_playlist.xml +++ b/app/src/main/res/layout/grid_item_playlist.xml @@ -35,7 +35,7 @@ android:contentDescription="@string/poster" android:scaleType="centerCrop"/> diff --git a/app/src/main/res/menu/movie_list.xml b/app/src/main/res/menu/movie_list.xml index 261aedf..c31ab36 100644 --- a/app/src/main/res/menu/movie_list.xml +++ b/app/src/main/res/menu/movie_list.xml @@ -73,4 +73,12 @@ android:title="@string/show_watched_status" app:showAsAction="never"/> + + + + diff --git a/app/src/main/res/values/attr.xml b/app/src/main/res/values/attr.xml index 96bb230..a147752 100644 --- a/app/src/main/res/values/attr.xml +++ b/app/src/main/res/values/attr.xml @@ -52,6 +52,9 @@ + + + @@ -112,6 +115,7 @@ + @@ -133,5 +137,12 @@ + + + + + + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 707f1be..f178c5c 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -28,11 +28,13 @@ @color/orange_A100 #ffffffff + #43ffffff #88ffffff #b3ffffff #de000000 #8a000000 + #43000000 #0f85a5 #17cdff diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 50c3e7e..a3aea06 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -332,6 +332,7 @@ By last played Ignore prefixes Show watched status + Show rating Application diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 97d9cd8..e9c470e 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -367,5 +367,9 @@ match_parent center_horizontal + + diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 94e46e0..3e45561 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -92,6 +92,9 @@ @color/white + @color/white_dim_26pct + ?attr/colorAccent + @drawable/ic_plus_white_24dp @drawable/ic_play_arrow_white_24dp @@ -153,6 +156,7 @@ @drawable/ic_open_in_new_white_24dp @drawable/ic_done_white_24dp @drawable/ic_cancel_white_24dp + @drawable/ic_star_white_24dp @drawable/ic_expand_less_white_24dp @drawable/ic_expand_more_white_24dp @@ -232,6 +236,10 @@ @color/black_dim_54pct + + @color/black_dim_26pct + ?attr/colorAccent + @drawable/ic_plus_white_24dp @drawable/ic_play_arrow_white_24dp @@ -303,6 +311,7 @@ @drawable/ic_open_in_new_white_24dp @drawable/ic_done_white_24dp @drawable/ic_cancel_white_24dp + @drawable/ic_star_white_24dp @drawable/ic_expand_less_white_24dp @drawable/ic_expand_more_white_24dp