Added movie ratings to movie listview items (#613)

Redesigning rate text to a five star rating bar.
This commit is contained in:
Martijn Brekhof 2019-02-26 21:08:46 +01:00 committed by Synced Synapse
parent d6b6e81b94
commit 00fe2d49a5
12 changed files with 281 additions and 60 deletions

View File

@ -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'

View File

@ -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;

View File

@ -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<String> 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);
}
}
}

View File

@ -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<ClipDrawable> 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;
}
}

View File

@ -15,14 +15,17 @@
limitations under the License.
-->
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Widget.CardView">
style="@style/Widget.CardView"
tools:layout_width="match_parent"
tools:layout_height="100dp">
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/relativeLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
@ -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" />
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
style="@style/TextAppearance.Medialist.Title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/art"
android:layout_toEndOf="@id/art"
android:layout_alignTop="@id/art"
style="@style/TextAppearance.Medialist.Title"/>
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" />
<TextView
android:id="@+id/details"
android:layout_width="match_parent"
style="@style/TextAppearance.Medialist.Details"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_alignLeft="@id/title"
android:layout_alignStart="@id/title"
android:layout_below="@id/title"
style="@style/TextAppearance.Medialist.Details"/>
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" />
<TextView
android:id="@+id/duration"
android:layout_width="wrap_content"
android:id="@+id/meta_info"
style="@style/TextAppearance.Medialist.OtherInfo"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_alignLeft="@id/title"
android:layout_alignStart="@id/title"
android:layout_below="@id/details"
android:layout_alignParentBottom="true"
style="@style/TextAppearance.Medialist.OtherInfo"/>
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@id/title"
app:layout_constraintEnd_toStartOf="@id/rating_bar"/>
<org.xbmc.kore.ui.views.RatingBar
android:id="@+id/rating_bar"
style="@style/RatingBar"
android:layout_width="wrap_content"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="@id/meta_info"
app:layout_constraintRight_toLeftOf="@id/checkmark"
app:layout_constraintEnd_toStartOf="@id/checkmark"
app:layout_constraintTop_toTopOf="@id/meta_info"
app:backgroundColor="?attr/ratingBarStarBackgroundColor"
app:foregroundColor="?attr/ratingBarStarForegroundColor"
app:icon="?attr/iconRating"/>
<ImageView
android:id="@+id/checkmark"
style="@style/Widget.Button.Borderless"
android:layout_width="@dimen/default_icon_size"
android:layout_height="@dimen/default_icon_size"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
style="@style/Widget.Button.Borderless"
android:contentDescription="@string/seen"
android:padding="@dimen/default_icon_padding"
android:src="?attr/iconSeen"
android:contentDescription="@string/seen"/>
</RelativeLayout>
</android.support.v7.widget.CardView>
<!--<?xml version="1.0" encoding="utf-8"?>-->
<!--<FrameLayout-->
<!--xmlns:android="http://schemas.android.com/apk/res/android"-->
<!--android:layout_width="match_parent"-->
<!--android:layout_height="wrap_content"-->
<!--android:background="?attr/myCardBackgroundDrawable"-->
<!--android:foreground="?attr/selectableItemBackground">-->
<!--<include layout="@layout/grid_item_movie_content"/>-->
<!--</FrameLayout>-->
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintRight_toRightOf="parent" />
</android.support.constraint.ConstraintLayout>
</android.support.v7.widget.CardView>

View File

@ -35,7 +35,7 @@
android:contentDescription="@string/poster"
android:scaleType="centerCrop"/>
<TextView
android:id="@+id/duration"
android:id="@+id/meta_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
@ -73,7 +73,7 @@
android:layout_below="@id/title"
android:layout_toRightOf="@id/art"
android:layout_toEndOf="@id/art"
android:layout_toLeftOf="@id/duration"
android:layout_toLeftOf="@id/meta_info"
android:layout_toStartOf="@id/size_duration"
style="@style/TextAppearance.Playlist.Details"/>
</RelativeLayout>

View File

@ -73,4 +73,12 @@
android:title="@string/show_watched_status"
app:showAsAction="never"/>
</group>
<group
android:checkableBehavior="all">
<item
android:id="@+id/action_show_rating"
android:title="@string/show_rating"
app:showAsAction="never"/>
</group>
</menu>

View File

@ -52,6 +52,9 @@
<attr name="defaultButtonColorFilter" format="reference|color" />
<!--<attr name="remoteBackgroundColorFilter" format="reference|color" />-->
<attr name="ratingBarStarBackgroundColor" format="reference|color"/>
<attr name="ratingBarStarForegroundColor" format="reference|color"/>
<!-- Icons -->
<attr name="iconHosts" format="reference" />
<attr name="iconRemote" format="reference" />
@ -112,6 +115,7 @@
<attr name="iconInfoWWW" format="reference" />
<attr name="iconSeen" format="reference" />
<attr name="iconDisabled" format="reference" />
<attr name="iconRating" format="reference" />
<attr name="iconCollapse" format="reference" />
<attr name="iconExpand" format="reference" />
@ -133,5 +137,12 @@
<declare-styleable name="SquareGridLayout">
<attr name="columnCount" format="integer"/>
</declare-styleable>
<declare-styleable name="RatingBar">
<attr name="backgroundColor" format="reference|color"/>
<attr name="foregroundColor" format="reference|color"/>
<attr name="iconCount" format="integer"/>
<attr name="icon" format="reference"/>
</declare-styleable>
</resources>

View File

@ -28,11 +28,13 @@
<color name="accent_default_light">@color/orange_A100</color>
<color name="white">#ffffffff</color>
<color name="white_dim_26pct">#43ffffff</color>
<color name="white_dim_50pct">#88ffffff</color>
<color name="white_dim_70pct">#b3ffffff</color>
<color name="black_dim_87pct">#de000000</color>
<color name="black_dim_54pct">#8a000000</color>
<color name="black_dim_26pct">#43000000</color>
<color name="estuary_default">#0f85a5</color>
<color name="estuary_default_light">#17cdff</color>

View File

@ -332,6 +332,7 @@
<string name="sort_by_last_played">By last played</string>
<string name="ignore_prefixes">Ignore prefixes</string>
<string name="show_watched_status">Show watched status</string>
<string name="show_rating">Show rating</string>
<!-- Preferences strings -->
<string name="application">Application</string>

View File

@ -367,5 +367,9 @@
<item name="android:layout_height">match_parent</item>
<item name="android:layout_gravity">center_horizontal</item>
</style>
<style name="RatingBar">
<item name="android:layout_marginBottom">@dimen/small_padding</item>
</style>
</resources>

View File

@ -92,6 +92,9 @@
<item name="defaultButtonColorFilter">@color/white</item>
<!--<item name="remoteBackgroundColorFilter">@color/dark_content_background</item>-->
<item name="ratingBarStarBackgroundColor">@color/white_dim_26pct</item>
<item name="ratingBarStarForegroundColor">?attr/colorAccent</item>
<!-- Icons -->
<item name="iconFABDialsOpenClose">@drawable/ic_plus_white_24dp</item>
<item name="iconFABDefault">@drawable/ic_play_arrow_white_24dp</item>
@ -153,6 +156,7 @@
<item name="iconInfoWWW">@drawable/ic_open_in_new_white_24dp</item>
<item name="iconSeen">@drawable/ic_done_white_24dp</item>
<item name="iconDisabled">@drawable/ic_cancel_white_24dp</item>
<item name="iconRating">@drawable/ic_star_white_24dp</item>
<item name="iconCollapse">@drawable/ic_expand_less_white_24dp</item>
<item name="iconExpand">@drawable/ic_expand_more_white_24dp</item>
@ -232,6 +236,10 @@
<item name="defaultButtonColorFilter">@color/black_dim_54pct</item>
<!--<item name="remoteBackgroundColorFilter">@color/light_content_background</item>-->
<!-- Rating bar -->
<item name="ratingBarStarBackgroundColor">@color/black_dim_26pct</item>
<item name="ratingBarStarForegroundColor">?attr/colorAccent</item>
<!-- Icons, same for all themes, will be colored dynamically -->
<item name="iconFABDialsOpenClose">@drawable/ic_plus_white_24dp</item>
<item name="iconFABDefault">@drawable/ic_play_arrow_white_24dp</item>
@ -303,6 +311,7 @@
<item name="iconInfoWWW">@drawable/ic_open_in_new_white_24dp</item>
<item name="iconSeen">@drawable/ic_done_white_24dp</item>
<item name="iconDisabled">@drawable/ic_cancel_white_24dp</item>
<item name="iconRating">@drawable/ic_star_white_24dp</item>
<item name="iconCollapse">@drawable/ic_expand_less_white_24dp</item>
<item name="iconExpand">@drawable/ic_expand_more_white_24dp</item>