Merge branch 'nitehu-feature/fivestarrating' into develop

This commit is contained in:
Óscar García Amor 2020-03-23 09:50:53 +01:00
commit 8a798ac456
32 changed files with 765 additions and 128 deletions

View File

@ -63,7 +63,9 @@ class MusicDirectory {
var type: String? = null, var type: String? = null,
var created: Date? = null, var created: Date? = null,
var closeness: Int = 0, var closeness: Int = 0,
var bookmarkPosition: Int = 0 var bookmarkPosition: Int = 0,
var userRating: Int? = null,
var averageRating: Float? = null
) : Serializable { ) : Serializable {
fun setDuration(duration: Long) { fun setDuration(duration: Long) {
this.duration = duration.toInt() this.duration = duration.toInt()

View File

@ -0,0 +1,55 @@
package org.moire.ultrasonic.api.subsonic
import org.amshove.kluent.`should be`
import org.junit.Test
import org.moire.ultrasonic.api.subsonic.response.SubsonicResponse
/**
* Integration test for [SubsonicAPIClient] for setRating request.
*/
class SubsonicApiSetRatingTest : SubsonicAPIClientTest() {
@Test
fun `Should parse setRating ok response`() {
val id = "110"
val rating = 3
mockWebServerRule.enqueueResponse("ping_ok.json")
val response = client.api.setRating(id, rating).execute()
assertResponseSuccessful(response)
response.body()?.status `should be` SubsonicResponse.Status.OK
}
@Test
fun `Should parse setRating error response`() {
val id = "110223"
val rating = 5
checkErrorCallParsed(mockWebServerRule) {
client.api.setRating(id, rating).execute()
}
}
@Test
fun `Should pass id parameter`() {
val id = "110"
val rating = 5
mockWebServerRule.assertRequestParam(responseResourceName = "ping_ok.json",
expectedParam = "id=$id") {
client.api.setRating(id, rating).execute()
}
}
@Test
fun `Should pass rating parameter`() {
val id = "110"
val rating = 5
mockWebServerRule.assertRequestParam(responseResourceName = "ping_ok.json",
expectedParam = "rating=$rating") {
client.api.setRating(id, rating).execute()
}
}
}

View File

@ -79,6 +79,12 @@ interface SubsonicAPIDefinition {
@Query("artistId") artistId: String? = null @Query("artistId") artistId: String? = null
): Call<SubsonicResponse> ): Call<SubsonicResponse>
@GET("setRating.view")
fun setRating(
@Query("id") id: String,
@Query("rating") rating: Int
): Call<SubsonicResponse>
@GET("getArtist.view") @GET("getArtist.view")
fun getArtist(@Query("id") id: String): Call<GetArtistResponse> fun getArtist(@Query("id") id: String): Call<GetArtistResponse>

View File

@ -50,11 +50,15 @@ import android.widget.TextView;
import android.widget.ViewFlipper; import android.widget.ViewFlipper;
import com.mobeta.android.dslv.DragSortListView; import com.mobeta.android.dslv.DragSortListView;
import org.koin.java.standalone.KoinJavaComponent;
import org.moire.ultrasonic.R; import org.moire.ultrasonic.R;
import org.moire.ultrasonic.domain.MusicDirectory; import org.moire.ultrasonic.domain.MusicDirectory;
import org.moire.ultrasonic.domain.MusicDirectory.Entry; import org.moire.ultrasonic.domain.MusicDirectory.Entry;
import org.moire.ultrasonic.domain.PlayerState; import org.moire.ultrasonic.domain.PlayerState;
import org.moire.ultrasonic.domain.RepeatMode; import org.moire.ultrasonic.domain.RepeatMode;
import org.moire.ultrasonic.featureflags.Feature;
import org.moire.ultrasonic.featureflags.FeatureStorage;
import org.moire.ultrasonic.service.DownloadFile; import org.moire.ultrasonic.service.DownloadFile;
import org.moire.ultrasonic.service.DownloadService; import org.moire.ultrasonic.service.DownloadService;
import org.moire.ultrasonic.service.MusicService; import org.moire.ultrasonic.service.MusicService;
@ -119,6 +123,15 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
private SilentBackgroundTask<Void> onProgressChangedTask; private SilentBackgroundTask<Void> onProgressChangedTask;
LinearLayout visualizerViewLayout; LinearLayout visualizerViewLayout;
private MenuItem starMenuItem; private MenuItem starMenuItem;
private LinearLayout ratingLinearLayout;
private ImageView fiveStar1ImageView;
private ImageView fiveStar2ImageView;
private ImageView fiveStar3ImageView;
private ImageView fiveStar4ImageView;
private ImageView fiveStar5ImageView;
private boolean useFiveStarRating;
private Drawable hollowStar;
private Drawable fullStar;
/** /**
* Called when the activity is first created. * Called when the activity is first created.
@ -136,6 +149,8 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
int width = size.x; int width = size.x;
int height = size.y; int height = size.y;
useFiveStarRating = KoinJavaComponent.get(FeatureStorage.class).isFeatureEnabled(Feature.FIVE_STAR_RATING);
swipeDistance = (width + height) * PERCENTAGE_OF_SCREEN_FOR_SWIPE / 100; swipeDistance = (width + height) * PERCENTAGE_OF_SCREEN_FOR_SWIPE / 100;
swipeVelocity = swipeDistance; swipeVelocity = swipeDistance;
gestureScanner = new GestureDetector(this, this); gestureScanner = new GestureDetector(this, this);
@ -162,6 +177,54 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
visualizerViewLayout = (LinearLayout) findViewById(R.id.download_visualizer_view_layout); visualizerViewLayout = (LinearLayout) findViewById(R.id.download_visualizer_view_layout);
ratingLinearLayout = findViewById(R.id.song_rating);
fiveStar1ImageView = findViewById(R.id.song_five_star_1);
fiveStar2ImageView = findViewById(R.id.song_five_star_2);
fiveStar3ImageView = findViewById(R.id.song_five_star_3);
fiveStar4ImageView = findViewById(R.id.song_five_star_4);
fiveStar5ImageView = findViewById(R.id.song_five_star_5);
if (!useFiveStarRating) ratingLinearLayout.setVisibility(View.GONE);
hollowStar = Util.getDrawableFromAttribute(SubsonicTabActivity.getInstance(), R.attr.star_hollow);
fullStar = Util.getDrawableFromAttribute(SubsonicTabActivity.getInstance(), R.attr.star_full);
fiveStar1ImageView.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(final View view) {
setSongRating(1);
}
});
fiveStar2ImageView.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(final View view) {
setSongRating(2);
}
});
fiveStar3ImageView.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(final View view) {
setSongRating(3);
}
});
fiveStar4ImageView.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(final View view) {
setSongRating(4);
}
});
fiveStar5ImageView.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(final View view) {
setSongRating(5);
}
});
View.OnTouchListener touchListener = new View.OnTouchListener() View.OnTouchListener touchListener = new View.OnTouchListener()
{ {
@Override @Override
@ -726,22 +789,20 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
currentSong = downloadFile.getSong(); currentSong = downloadFile.getSong();
} }
if (useFiveStarRating) starMenuItem.setVisible(false);
if (currentSong != null) if (currentSong != null)
{ {
final Drawable starDrawable = currentSong.getStarred() ? Util.getDrawableFromAttribute(SubsonicTabActivity.getInstance(), R.attr.star_full) : Util.getDrawableFromAttribute(SubsonicTabActivity.getInstance(), R.attr.star_hollow);
if (starMenuItem != null) if (starMenuItem != null)
{ {
starMenuItem.setIcon(starDrawable); starMenuItem.setIcon(currentSong.getStarred() ? fullStar : hollowStar);
} }
} }
else else
{ {
final Drawable starDrawable = Util.getDrawableFromAttribute(SubsonicTabActivity.getInstance(), R.attr.star_hollow);
if (starMenuItem != null) if (starMenuItem != null)
{ {
starMenuItem.setIcon(starDrawable); starMenuItem.setIcon(hollowStar);
} }
} }
@ -974,12 +1035,12 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
if (isStarred) if (isStarred)
{ {
starMenuItem.setIcon(Util.getDrawableFromAttribute(SubsonicTabActivity.getInstance(), R.attr.star_hollow)); starMenuItem.setIcon(hollowStar);
currentSong.setStarred(false); currentSong.setStarred(false);
} }
else else
{ {
starMenuItem.setIcon(Util.getDrawableFromAttribute(SubsonicTabActivity.getInstance(), R.attr.star_full)); starMenuItem.setIcon(fullStar);
currentSong.setStarred(true); currentSong.setStarred(true);
} }
@ -1320,6 +1381,8 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
downloadTrackTextView.setText(trackFormat); downloadTrackTextView.setText(trackFormat);
downloadTotalDurationTextView.setText(duration); downloadTotalDurationTextView.setText(duration);
getImageLoader().loadImage(albumArtImageView, currentSong, true, 0, false, true); getImageLoader().loadImage(albumArtImageView, currentSong, true, 0, false, true);
displaySongRating();
} }
else else
{ {
@ -1440,6 +1503,9 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
break; break;
} }
// TODO: It would be a lot nicer if DownloadService would send an event when this is necessary instead of updating every time
displaySongRating();
onProgressChangedTask = null; onProgressChangedTask = null;
} }
}; };
@ -1580,4 +1646,23 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
{ {
return progressBar; return progressBar;
} }
private void displaySongRating()
{
int rating = currentSong.getUserRating() == null ? 0 : currentSong.getUserRating();
fiveStar1ImageView.setImageDrawable(rating > 0 ? fullStar : hollowStar);
fiveStar2ImageView.setImageDrawable(rating > 1 ? fullStar : hollowStar);
fiveStar3ImageView.setImageDrawable(rating > 2 ? fullStar : hollowStar);
fiveStar4ImageView.setImageDrawable(rating > 3 ? fullStar : hollowStar);
fiveStar5ImageView.setImageDrawable(rating > 4 ? fullStar : hollowStar);
}
private void setSongRating(final int rating)
{
if (currentSong == null)
return;
displaySongRating();
getDownloadService().setSongRating(rating);
}
} }

View File

@ -172,10 +172,11 @@ public class SettingsFragment extends PreferenceFragment
} }
private void setupFeatureFlagsPreferences() { private void setupFeatureFlagsPreferences() {
final FeatureStorage featureStorage = KoinJavaComponent.get(FeatureStorage.class);
CheckBoxPreference ffImageLoader = (CheckBoxPreference) findPreference( CheckBoxPreference ffImageLoader = (CheckBoxPreference) findPreference(
Constants.PREFERENCES_KEY_FF_IMAGE_LOADER); Constants.PREFERENCES_KEY_FF_IMAGE_LOADER);
final FeatureStorage featureStorage = KoinJavaComponent.get(FeatureStorage.class);
if (ffImageLoader != null) { if (ffImageLoader != null) {
ffImageLoader.setChecked(featureStorage.isFeatureEnabled(Feature.NEW_IMAGE_DOWNLOADER)); ffImageLoader.setChecked(featureStorage.isFeatureEnabled(Feature.NEW_IMAGE_DOWNLOADER));
ffImageLoader.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { ffImageLoader.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@ -187,6 +188,21 @@ public class SettingsFragment extends PreferenceFragment
} }
}); });
} }
CheckBoxPreference useFiveStarRating = (CheckBoxPreference) findPreference(
Constants.PREFERENCES_KEY_USE_FIVE_STAR_RATING);
if (useFiveStarRating != null) {
useFiveStarRating.setChecked(featureStorage.isFeatureEnabled(Feature.FIVE_STAR_RATING));
useFiveStarRating.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object o) {
featureStorage.changeFeatureFlag(Feature.FIVE_STAR_RATING, (Boolean) o);
return true;
}
});
}
} }
private void setupGaplessControlSettingsV14() { private void setupGaplessControlSettingsV14() {

View File

@ -385,7 +385,6 @@ public class CachedMusicService implements MusicService
public void star(String id, String albumId, String artistId, Context context, ProgressListener progressListener) throws Exception public void star(String id, String albumId, String artistId, Context context, ProgressListener progressListener) throws Exception
{ {
musicService.star(id, albumId, artistId, context, progressListener); musicService.star(id, albumId, artistId, context, progressListener);
} }
@Override @Override
@ -394,6 +393,12 @@ public class CachedMusicService implements MusicService
musicService.unstar(id, albumId, artistId, context, progressListener); musicService.unstar(id, albumId, artistId, context, progressListener);
} }
@Override
public void setRating(String id, int rating, Context context, ProgressListener progressListener) throws Exception
{
musicService.setRating(id, rating, context, progressListener);
}
@Override @Override
public List<Genre> getGenres(Context context, ProgressListener progressListener) throws Exception public List<Genre> getGenres(Context context, ProgressListener progressListener) throws Exception
{ {

View File

@ -144,4 +144,8 @@ public interface DownloadService
void stopJukeboxService(); void stopJukeboxService();
void startJukeboxService(); void startJukeboxService();
void updateNotification();
void setSongRating(final int rating);
} }

View File

@ -39,9 +39,11 @@ import android.os.PowerManager;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat; import android.support.v4.app.NotificationManagerCompat;
import android.util.Log; import android.util.Log;
import android.view.View;
import android.widget.RemoteViews; import android.widget.RemoteViews;
import android.widget.SeekBar; import android.widget.SeekBar;
import org.koin.java.standalone.KoinJavaComponent;
import org.moire.ultrasonic.R; import org.moire.ultrasonic.R;
import org.moire.ultrasonic.activity.DownloadActivity; import org.moire.ultrasonic.activity.DownloadActivity;
import org.moire.ultrasonic.activity.SubsonicTabActivity; import org.moire.ultrasonic.activity.SubsonicTabActivity;
@ -52,6 +54,8 @@ import org.moire.ultrasonic.domain.MusicDirectory.Entry;
import org.moire.ultrasonic.domain.PlayerState; import org.moire.ultrasonic.domain.PlayerState;
import org.moire.ultrasonic.domain.RepeatMode; import org.moire.ultrasonic.domain.RepeatMode;
import org.moire.ultrasonic.domain.UserInfo; import org.moire.ultrasonic.domain.UserInfo;
import org.moire.ultrasonic.featureflags.Feature;
import org.moire.ultrasonic.featureflags.FeatureStorage;
import org.moire.ultrasonic.provider.UltraSonicAppWidgetProvider4x1; import org.moire.ultrasonic.provider.UltraSonicAppWidgetProvider4x1;
import org.moire.ultrasonic.provider.UltraSonicAppWidgetProvider4x2; import org.moire.ultrasonic.provider.UltraSonicAppWidgetProvider4x2;
import org.moire.ultrasonic.provider.UltraSonicAppWidgetProvider4x3; import org.moire.ultrasonic.provider.UltraSonicAppWidgetProvider4x3;
@ -1238,11 +1242,7 @@ public class DownloadServiceImpl extends Service implements DownloadService
// Only update notification is player state is one that will change the icon // Only update notification is player state is one that will change the icon
if (this.playerState == PlayerState.STARTED || this.playerState == PlayerState.PAUSED) if (this.playerState == PlayerState.STARTED || this.playerState == PlayerState.PAUSED)
{ {
if (Util.isNotificationEnabled(this)) { updateNotification();
final NotificationManagerCompat notificationManager =
NotificationManagerCompat.from(this);
notificationManager.notify(NOTIFICATION_ID, buildForegroundNotification());
}
tabInstance.showNowPlaying(); tabInstance.showNowPlaying();
} }
} }
@ -2071,6 +2071,16 @@ public class DownloadServiceImpl extends Service implements DownloadService
} }
} }
@Override
public void updateNotification()
{
if (Util.isNotificationEnabled(this)) {
final NotificationManagerCompat notificationManager =
NotificationManagerCompat.from(this);
notificationManager.notify(NOTIFICATION_ID, buildForegroundNotification());
}
}
@SuppressWarnings("IconColors") @SuppressWarnings("IconColors")
private Notification buildForegroundNotification() { private Notification buildForegroundNotification() {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this); NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
@ -2103,6 +2113,7 @@ public class DownloadServiceImpl extends Service implements DownloadService
final String title = song.getTitle(); final String title = song.getTitle();
final String text = song.getArtist(); final String text = song.getArtist();
final String album = song.getAlbum(); final String album = song.getAlbum();
final int rating = song.getUserRating() == null ? 0 : song.getUserRating();
final int imageSize = Util.getNotificationImageSize(this); final int imageSize = Util.getNotificationImageSize(this);
try { try {
@ -2127,6 +2138,17 @@ public class DownloadServiceImpl extends Service implements DownloadService
contentView.setTextViewText(R.id.album, album); contentView.setTextViewText(R.id.album, album);
bigView.setTextViewText(R.id.album, album); bigView.setTextViewText(R.id.album, album);
boolean useFiveStarRating = KoinJavaComponent.get(FeatureStorage.class).isFeatureEnabled(Feature.FIVE_STAR_RATING);
if (!useFiveStarRating) bigView.setViewVisibility(R.id.notification_rating, View.INVISIBLE);
else
{
bigView.setImageViewResource(R.id.notification_five_star_1, rating > 0 ? R.drawable.ic_star_full_dark : R.drawable.ic_star_hollow_dark);
bigView.setImageViewResource(R.id.notification_five_star_2, rating > 1 ? R.drawable.ic_star_full_dark : R.drawable.ic_star_hollow_dark);
bigView.setImageViewResource(R.id.notification_five_star_3, rating > 2 ? R.drawable.ic_star_full_dark : R.drawable.ic_star_hollow_dark);
bigView.setImageViewResource(R.id.notification_five_star_4, rating > 3 ? R.drawable.ic_star_full_dark : R.drawable.ic_star_hollow_dark);
bigView.setImageViewResource(R.id.notification_five_star_5, rating > 4 ? R.drawable.ic_star_full_dark : R.drawable.ic_star_hollow_dark);
}
Notification notification = builder.build(); Notification notification = builder.build();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
notification.bigContentView = bigView; notification.bigContentView = bigView;
@ -2135,6 +2157,38 @@ public class DownloadServiceImpl extends Service implements DownloadService
return notification; return notification;
} }
public void setSongRating(final int rating)
{
if (!KoinJavaComponent.get(FeatureStorage.class).isFeatureEnabled(Feature.FIVE_STAR_RATING))
return;
if (currentPlaying == null)
return;
final Entry song = currentPlaying.getSong();
song.setUserRating(rating);
new Thread(new Runnable()
{
@Override
public void run()
{
final MusicService musicService = MusicServiceFactory.getMusicService(DownloadServiceImpl.this);
try
{
musicService.setRating(song.getId(), rating, DownloadServiceImpl.this, null);
}
catch (Exception e)
{
Log.e(TAG, e.getMessage(), e);
}
}
}).start();
updateNotification();
}
private class BufferTask extends CancellableTask private class BufferTask extends CancellableTask
{ {
private final DownloadFile downloadFile; private final DownloadFile downloadFile;

View File

@ -329,6 +329,21 @@ public class DownloadServiceLifecycleSupport
case KeyEvent.KEYCODE_MEDIA_PAUSE: case KeyEvent.KEYCODE_MEDIA_PAUSE:
downloadService.pause(); downloadService.pause();
break; break;
case KeyEvent.KEYCODE_1:
downloadService.setSongRating(1);
break;
case KeyEvent.KEYCODE_2:
downloadService.setSongRating(2);
break;
case KeyEvent.KEYCODE_3:
downloadService.setSongRating(3);
break;
case KeyEvent.KEYCODE_4:
downloadService.setSongRating(4);
break;
case KeyEvent.KEYCODE_5:
downloadService.setSongRating(5);
break;
default: default:
break; break;
} }

View File

@ -59,6 +59,8 @@ public interface MusicService
void unstar(String id, String albumId, String artistId, Context context, ProgressListener progressListener) throws Exception; void unstar(String id, String albumId, String artistId, Context context, ProgressListener progressListener) throws Exception;
void setRating(String id, int rating, Context context, ProgressListener progressListener) throws Exception;
List<MusicFolder> getMusicFolders(boolean refresh, Context context, ProgressListener progressListener) throws Exception; List<MusicFolder> getMusicFolders(boolean refresh, Context context, ProgressListener progressListener) throws Exception;
Indexes getIndexes(String musicFolderId, boolean refresh, Context context, ProgressListener progressListener) throws Exception; Indexes getIndexes(String musicFolderId, boolean refresh, Context context, ProgressListener progressListener) throws Exception;

View File

@ -238,6 +238,17 @@ public class RESTMusicService implements MusicService {
checkResponseSuccessful(response); checkResponseSuccessful(response);
} }
@Override
public void setRating(String id,
int rating,
Context context,
ProgressListener progressListener) throws Exception {
updateProgressListener(progressListener, R.string.parser_reading);
Response<SubsonicResponse> response = subsonicAPIClient.getApi()
.setRating(id, rating).execute();
checkResponseSuccessful(response);
}
@Override @Override
public MusicDirectory getMusicDirectory(String id, public MusicDirectory getMusicDirectory(String id,
String name, String name,

View File

@ -131,6 +131,7 @@ public final class Constants
public static final String PREFERENCES_KEY_SCAN_MEDIA = "scanMedia"; public static final String PREFERENCES_KEY_SCAN_MEDIA = "scanMedia";
public static final String PREFERENCES_KEY_IMAGE_LOADER_CONCURRENCY = "imageLoaderConcurrency"; public static final String PREFERENCES_KEY_IMAGE_LOADER_CONCURRENCY = "imageLoaderConcurrency";
public static final String PREFERENCES_KEY_FF_IMAGE_LOADER = "ff_new_image_loader"; public static final String PREFERENCES_KEY_FF_IMAGE_LOADER = "ff_new_image_loader";
public static final String PREFERENCES_KEY_USE_FIVE_STAR_RATING = "use_five_star_rating";
// Number of free trial days for non-licensed servers. // Number of free trial days for non-licensed servers.
public static final int FREE_TRIAL_DAYS = 30; public static final int FREE_TRIAL_DAYS = 30;

View File

@ -1306,6 +1306,36 @@ public class Util extends DownloadActivity
intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_STOP)); intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_STOP));
pendingIntent = PendingIntent.getService(context, 0, intent, 0); pendingIntent = PendingIntent.getService(context, 0, intent, 0);
views.setOnClickPendingIntent(R.id.control_stop, pendingIntent); views.setOnClickPendingIntent(R.id.control_stop, pendingIntent);
intent = new Intent("RATE_1");
intent.setComponent(new ComponentName(context, DownloadServiceImpl.class));
intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_1));
pendingIntent = PendingIntent.getService(context, 0, intent, 0);
views.setOnClickPendingIntent(R.id.notification_five_star_1, pendingIntent);
intent = new Intent("RATE_2");
intent.setComponent(new ComponentName(context, DownloadServiceImpl.class));
intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_2));
pendingIntent = PendingIntent.getService(context, 0, intent, 0);
views.setOnClickPendingIntent(R.id.notification_five_star_2, pendingIntent);
intent = new Intent("RATE_3");
intent.setComponent(new ComponentName(context, DownloadServiceImpl.class));
intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_3));
pendingIntent = PendingIntent.getService(context, 0, intent, 0);
views.setOnClickPendingIntent(R.id.notification_five_star_3, pendingIntent);
intent = new Intent("RATE_4");
intent.setComponent(new ComponentName(context, DownloadServiceImpl.class));
intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_4));
pendingIntent = PendingIntent.getService(context, 0, intent, 0);
views.setOnClickPendingIntent(R.id.notification_five_star_4, pendingIntent);
intent = new Intent("RATE_5");
intent.setComponent(new ComponentName(context, DownloadServiceImpl.class));
intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_5));
pendingIntent = PendingIntent.getService(context, 0, intent, 0);
views.setOnClickPendingIntent(R.id.notification_five_star_5, pendingIntent);
} }
public static int getNetworkTimeout(Context context) public static int getNetworkTimeout(Context context)

View File

@ -23,6 +23,7 @@ import android.view.ViewGroup;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.CheckedTextView; import android.widget.CheckedTextView;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import org.moire.ultrasonic.activity.SubsonicTabActivity; import org.moire.ultrasonic.activity.SubsonicTabActivity;
import org.moire.ultrasonic.domain.MusicDirectory.Entry; import org.moire.ultrasonic.domain.MusicDirectory.Entry;
@ -120,6 +121,12 @@ public class EntryAdapter extends ArrayAdapter<Entry>
TextView status; TextView status;
TextView artist; TextView artist;
TextView duration; TextView duration;
LinearLayout rating;
ImageView fiveStar1;
ImageView fiveStar2;
ImageView fiveStar3;
ImageView fiveStar4;
ImageView fiveStar5;
ImageView star; ImageView star;
ImageView drag; ImageView drag;
} }

View File

@ -28,10 +28,14 @@ import android.view.View;
import android.widget.Checkable; import android.widget.Checkable;
import android.widget.CheckedTextView; import android.widget.CheckedTextView;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import org.koin.java.standalone.KoinJavaComponent;
import org.moire.ultrasonic.R; import org.moire.ultrasonic.R;
import org.moire.ultrasonic.domain.MusicDirectory.Entry; import org.moire.ultrasonic.domain.MusicDirectory.Entry;
import org.moire.ultrasonic.featureflags.Feature;
import org.moire.ultrasonic.featureflags.FeatureStorage;
import org.moire.ultrasonic.service.DownloadFile; import org.moire.ultrasonic.service.DownloadFile;
import org.moire.ultrasonic.service.DownloadService; import org.moire.ultrasonic.service.DownloadService;
import org.moire.ultrasonic.service.DownloadServiceImpl; import org.moire.ultrasonic.service.DownloadServiceImpl;
@ -73,12 +77,15 @@ public class SongView extends UpdateView implements Checkable
private boolean playing; private boolean playing;
private EntryAdapter.SongViewHolder viewHolder; private EntryAdapter.SongViewHolder viewHolder;
private boolean maximized = false; private boolean maximized = false;
private boolean useFiveStarRating;
public SongView(Context context) public SongView(Context context)
{ {
super(context); super(context);
this.context = context; this.context = context;
useFiveStarRating = KoinJavaComponent.get(FeatureStorage.class).isFeatureEnabled(Feature.FIVE_STAR_RATING);
String theme = Util.getTheme(context); String theme = Util.getTheme(context);
boolean themesMatch = theme.equals(SongView.theme); boolean themesMatch = theme.equals(SongView.theme);
inflater = LayoutInflater.from(this.context); inflater = LayoutInflater.from(this.context);
@ -124,6 +131,12 @@ public class SongView extends UpdateView implements Checkable
inflater.inflate(song.isVideo() ? R.layout.video_list_item : R.layout.song_list_item, this, true); inflater.inflate(song.isVideo() ? R.layout.video_list_item : R.layout.song_list_item, this, true);
viewHolder = new EntryAdapter.SongViewHolder(); viewHolder = new EntryAdapter.SongViewHolder();
viewHolder.check = (CheckedTextView) findViewById(R.id.song_check); viewHolder.check = (CheckedTextView) findViewById(R.id.song_check);
viewHolder.rating = (LinearLayout) findViewById(R.id.song_rating);
viewHolder.fiveStar1 = (ImageView) findViewById(R.id.song_five_star_1);
viewHolder.fiveStar2 = (ImageView) findViewById(R.id.song_five_star_2);
viewHolder.fiveStar3 = (ImageView) findViewById(R.id.song_five_star_3);
viewHolder.fiveStar4 = (ImageView) findViewById(R.id.song_five_star_4);
viewHolder.fiveStar5 = (ImageView) findViewById(R.id.song_five_star_5);
viewHolder.star = (ImageView) findViewById(R.id.song_star); viewHolder.star = (ImageView) findViewById(R.id.song_star);
viewHolder.drag = (ImageView) findViewById(R.id.song_drag); viewHolder.drag = (ImageView) findViewById(R.id.song_drag);
viewHolder.track = (TextView) findViewById(R.id.song_track); viewHolder.track = (TextView) findViewById(R.id.song_track);
@ -237,56 +250,59 @@ public class SongView extends UpdateView implements Checkable
if (Util.isOffline(this.context)) if (Util.isOffline(this.context))
{ {
viewHolder.star.setVisibility(View.GONE); viewHolder.star.setVisibility(View.GONE);
viewHolder.rating.setVisibility(View.GONE);
} }
else else
{ {
viewHolder.star.setImageDrawable(song.getStarred() ? starDrawable : starHollowDrawable); if (useFiveStarRating)
viewHolder.star.setOnClickListener(new View.OnClickListener()
{ {
@Override viewHolder.star.setVisibility(View.GONE);
public void onClick(View view)
{
final boolean isStarred = song.getStarred();
final String id = song.getId();
if (!isStarred) int rating = song.getUserRating() == null ? 0 : song.getUserRating();
{ viewHolder.fiveStar1.setImageDrawable(rating > 0 ? starDrawable : starHollowDrawable);
viewHolder.star.setImageDrawable(starDrawable); viewHolder.fiveStar2.setImageDrawable(rating > 1 ? starDrawable : starHollowDrawable);
song.setStarred(true); viewHolder.fiveStar3.setImageDrawable(rating > 2 ? starDrawable : starHollowDrawable);
} viewHolder.fiveStar4.setImageDrawable(rating > 3 ? starDrawable : starHollowDrawable);
else viewHolder.fiveStar5.setImageDrawable(rating > 4 ? starDrawable : starHollowDrawable);
{
viewHolder.star.setImageDrawable(starHollowDrawable);
song.setStarred(false);
}
new Thread(new Runnable() }
{ else {
@Override viewHolder.rating.setVisibility(View.GONE);
public void run() viewHolder.star.setImageDrawable(song.getStarred() ? starDrawable : starHollowDrawable);
{
MusicService musicService = MusicServiceFactory.getMusicService(SongView.this.context);
try viewHolder.star.setOnClickListener(new View.OnClickListener() {
{ @Override
if (!isStarred) public void onClick(View view) {
{ final boolean isStarred = song.getStarred();
musicService.star(id, null, null, SongView.this.context, null); final String id = song.getId();
}
else if (!isStarred) {
{ viewHolder.star.setImageDrawable(starDrawable);
musicService.unstar(id, null, null, SongView.this.context, null); song.setStarred(true);
} } else {
} viewHolder.star.setImageDrawable(starHollowDrawable);
catch (Exception e) song.setStarred(false);
{
Log.e(TAG, e.getMessage(), e);
}
} }
}).start();
} new Thread(new Runnable() {
}); @Override
public void run() {
MusicService musicService = MusicServiceFactory.getMusicService(SongView.this.context);
try {
if (!isStarred) {
musicService.star(id, null, null, SongView.this.context, null);
} else {
musicService.unstar(id, null, null, SongView.this.context, null);
}
} catch (Exception e) {
Log.e(TAG, e.getMessage(), e);
}
}
}).start();
}
});
}
} }
update(); update();
@ -394,6 +410,13 @@ public class SongView extends UpdateView implements Checkable
} }
} }
int rating = song.getUserRating() == null ? 0 : song.getUserRating();
viewHolder.fiveStar1.setImageDrawable(rating > 0 ? starDrawable : starHollowDrawable);
viewHolder.fiveStar2.setImageDrawable(rating > 1 ? starDrawable : starHollowDrawable);
viewHolder.fiveStar3.setImageDrawable(rating > 2 ? starDrawable : starHollowDrawable);
viewHolder.fiveStar4.setImageDrawable(rating > 3 ? starDrawable : starHollowDrawable);
viewHolder.fiveStar5.setImageDrawable(rating > 4 ? starDrawable : starHollowDrawable);
boolean playing = downloadService.getCurrentPlaying() == downloadFile; boolean playing = downloadService.getCurrentPlaying() == downloadFile;
if (playing) if (playing)

View File

@ -45,6 +45,8 @@ fun MusicDirectoryChild.toDomainEntity(): MusicDirectory.Entry = MusicDirectory.
if (this@toDomainEntity.publishDate != null) { if (this@toDomainEntity.publishDate != null) {
artist = dateFormat.format(this@toDomainEntity.publishDate!!.time) artist = dateFormat.format(this@toDomainEntity.publishDate!!.time)
} }
userRating = this@toDomainEntity.userRating
averageRating = this@toDomainEntity.averageRating
} }
fun List<MusicDirectoryChild>.toDomainEntityList() = this.map { it.toDomainEntity() } fun List<MusicDirectoryChild>.toDomainEntityList() = this.map { it.toDomainEntity() }

View File

@ -10,5 +10,9 @@ enum class Feature(
/** /**
* Enables new image downloader implementation. * Enables new image downloader implementation.
*/ */
NEW_IMAGE_DOWNLOADER(false) NEW_IMAGE_DOWNLOADER(false),
/**
* Enables five star rating system.
*/
FIVE_STAR_RATING(false)
} }

View File

@ -26,15 +26,93 @@
a:contentDescription="@string/albumArt"/> a:contentDescription="@string/albumArt"/>
<LinearLayout <LinearLayout
a:id="@+id/download_visualizer_view_layout" a:id="@+id/album_art_inside"
a:layout_width="fill_parent" a:layout_width="fill_parent"
a:layout_height="60dip" a:layout_height="fill_parent"
a:layout_gravity="bottom|center_horizontal" a:gravity="bottom"
a:layout_marginLeft="60dip" a:orientation="vertical" >
a:layout_marginRight="60dip"
a:background="@color/translucent"
a:orientation="vertical"/>
<LinearLayout
a:id="@+id/song_rating"
a:layout_width="match_parent"
a:layout_height="60dip"
a:layout_gravity="center"
a:layout_margin="40dip"
a:orientation="horizontal">
<ImageView
a:id="@+id/song_five_star_1"
a:layout_width="0dip"
a:layout_height="fill_parent"
a:layout_weight="1"
a:padding="10dip"
a:background="@android:color/transparent"
a:focusable="false"
a:gravity="center_vertical"
a:scaleType="fitCenter"
a:src="?attr/star_hollow" />
<ImageView
a:id="@+id/song_five_star_2"
a:layout_width="0dip"
a:layout_height="fill_parent"
a:layout_weight="1"
a:padding="10dip"
a:background="@android:color/transparent"
a:focusable="false"
a:gravity="center_vertical"
a:scaleType="fitCenter"
a:src="?attr/star_hollow" />
<ImageView
a:id="@+id/song_five_star_3"
a:layout_width="0dip"
a:layout_height="fill_parent"
a:layout_weight="1"
a:padding="10dip"
a:background="@android:color/transparent"
a:focusable="false"
a:gravity="center_vertical"
a:scaleType="fitCenter"
a:src="?attr/star_hollow" />
<ImageView
a:id="@+id/song_five_star_4"
a:layout_width="0dip"
a:layout_height="fill_parent"
a:layout_weight="1"
a:padding="10dip"
a:background="@android:color/transparent"
a:focusable="false"
a:gravity="center_vertical"
a:scaleType="fitCenter"
a:src="?attr/star_hollow" />
<ImageView
a:id="@+id/song_five_star_5"
a:layout_width="0dip"
a:layout_height="fill_parent"
a:layout_weight="1"
a:padding="10dip"
a:background="@android:color/transparent"
a:focusable="false"
a:gravity="center_vertical"
a:scaleType="fitCenter"
a:src="?attr/star_hollow" />
</LinearLayout>
<LinearLayout
a:id="@+id/download_visualizer_view_layout"
a:layout_width="fill_parent"
a:layout_height="60dip"
a:layout_gravity="bottom|center_horizontal"
a:layout_marginLeft="60dip"
a:layout_marginRight="60dip"
a:background="@color/translucent"
a:orientation="vertical"/>
</LinearLayout>
</FrameLayout> </FrameLayout>
<include layout="@layout/download_playlist"/> <include layout="@layout/download_playlist"/>

View File

@ -28,16 +28,94 @@
a:contentDescription="@string/albumArt"/> a:contentDescription="@string/albumArt"/>
<LinearLayout <LinearLayout
a:id="@+id/download_visualizer_view_layout" a:id="@+id/album_art_inside"
a:layout_width="fill_parent" a:layout_width="fill_parent"
a:layout_height="60dip" a:layout_height="fill_parent"
a:layout_alignParentBottom="true" a:gravity="bottom"
a:layout_gravity="center_horizontal" a:orientation="vertical" >
a:background="@color/translucent"
a:layout_marginLeft="80dip" <LinearLayout
a:layout_marginRight="80dip" a:id="@+id/song_rating"
a:orientation="vertical" a:layout_width="match_parent"
/> a:layout_height="60dip"
a:layout_gravity="center"
a:layout_margin="40dip"
a:orientation="horizontal">
<ImageView
a:id="@+id/song_five_star_1"
a:layout_width="0dip"
a:layout_height="fill_parent"
a:layout_weight="1"
a:padding="5dip"
a:background="@android:color/transparent"
a:focusable="false"
a:gravity="center_vertical"
a:scaleType="fitCenter"
a:src="?attr/star_hollow" />
<ImageView
a:id="@+id/song_five_star_2"
a:layout_width="0dip"
a:layout_height="fill_parent"
a:layout_weight="1"
a:padding="5dip"
a:background="@android:color/transparent"
a:focusable="false"
a:gravity="center_vertical"
a:scaleType="fitCenter"
a:src="?attr/star_hollow" />
<ImageView
a:id="@+id/song_five_star_3"
a:layout_width="0dip"
a:layout_height="fill_parent"
a:layout_weight="1"
a:padding="5dip"
a:background="@android:color/transparent"
a:focusable="false"
a:gravity="center_vertical"
a:scaleType="fitCenter"
a:src="?attr/star_hollow" />
<ImageView
a:id="@+id/song_five_star_4"
a:layout_width="0dip"
a:layout_height="fill_parent"
a:layout_weight="1"
a:padding="5dip"
a:background="@android:color/transparent"
a:focusable="false"
a:gravity="center_vertical"
a:scaleType="fitCenter"
a:src="?attr/star_hollow" />
<ImageView
a:id="@+id/song_five_star_5"
a:layout_width="0dip"
a:layout_height="fill_parent"
a:layout_weight="1"
a:padding="5dip"
a:background="@android:color/transparent"
a:focusable="false"
a:gravity="center_vertical"
a:scaleType="fitCenter"
a:src="?attr/star_hollow" />
</LinearLayout>
<LinearLayout
a:id="@+id/download_visualizer_view_layout"
a:layout_width="fill_parent"
a:layout_height="60dip"
a:layout_gravity="center"
a:background="@color/translucent"
a:layout_marginLeft="80dip"
a:layout_marginRight="80dip"
a:orientation="vertical"
/>
</LinearLayout>
</RelativeLayout> </RelativeLayout>
<include layout="@layout/download_playlist" /> <include layout="@layout/download_playlist" />

View File

@ -4,14 +4,14 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/statusbar" android:id="@+id/statusbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="128dp" android:layout_height="150dp"
android:orientation="horizontal" android:orientation="horizontal"
android:background="@color/background_color_dark" > android:background="@color/background_color_dark" >
<ImageView <ImageView
android:id="@+id/notification_image" android:id="@+id/notification_image"
android:layout_width="128dp" android:layout_width="150dp"
android:layout_height="128dp" android:layout_height="150dp"
android:gravity="center" android:gravity="center"
tools:background="#ff00ff" tools:background="#ff00ff"
/> />
@ -41,7 +41,7 @@
android:layout_weight="1" android:layout_weight="1"
android:ellipsize="marquee" android:ellipsize="marquee"
android:focusable="true" android:focusable="true"
android:maxLines="1" android:singleLine="true"
tools:text="Track name" tools:text="Track name"
/> />
@ -80,15 +80,75 @@
android:maxLines="1" android:maxLines="1"
tools:text="Album" tools:text="Album"
/> />
<LinearLayout
android:id="@+id/notification_rating"
android:layout_width="match_parent"
android:layout_height="30dip"
android:layout_gravity="center"
android:orientation="horizontal"
android:visibility="visible">
<ImageView
android:id="@+id/notification_five_star_1"
android:layout_width="0dip"
android:layout_height="fill_parent"
android:layout_weight="1"
android:background="@android:color/transparent"
android:focusable="false"
android:gravity="center_vertical"
android:scaleType="fitCenter" />
<ImageView
android:id="@+id/notification_five_star_2"
android:layout_width="0dip"
android:layout_height="fill_parent"
android:layout_weight="1"
android:background="@android:color/transparent"
android:focusable="false"
android:gravity="center_vertical"
android:scaleType="fitCenter" />
<ImageView
android:id="@+id/notification_five_star_3"
android:layout_width="0dip"
android:layout_height="fill_parent"
android:layout_weight="1"
android:background="@android:color/transparent"
android:focusable="false"
android:gravity="center_vertical"
android:scaleType="fitCenter" />
<ImageView
android:id="@+id/notification_five_star_4"
android:layout_width="0dip"
android:layout_height="fill_parent"
android:layout_weight="1"
android:background="@android:color/transparent"
android:focusable="false"
android:gravity="center_vertical"
android:scaleType="fitCenter" />
<ImageView
android:id="@+id/notification_five_star_5"
android:layout_width="0dip"
android:layout_height="fill_parent"
android:layout_weight="1"
android:background="@android:color/transparent"
android:focusable="false"
android:gravity="center_vertical"
android:scaleType="fitCenter" />
</LinearLayout>
<FrameLayout <FrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="1dp" android:layout_height="1dp"
android:layout_marginTop="10dip" android:layout_marginTop="5dip"
android:layout_marginBottom="10dip" android:layout_marginBottom="10dip"
android:background="#DD696969" android:background="#DD696969"
/> />
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@ -10,60 +10,59 @@
android:layout_height="4dip" android:layout_height="4dip"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:background="@drawable/drop_shadow" /> android:background="@drawable/drop_shadow" />
<LinearLayout <LinearLayout
android:id="@+id/now_playing_view" android:id="@+id/now_playing_view"
android:orientation="horizontal"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" > android:layout_height="wrap_content">
<ImageView <ImageView
android:id="@+id/now_playing_image" android:id="@+id/now_playing_image"
android:layout_width="64.0dip" android:layout_width="64.0dip"
android:layout_height="64.0dip" android:layout_height="64.0dip"
android:focusable="true" android:focusable="true"
android:gravity="center" /> android:gravity="center" />
<LinearLayout <LinearLayout
android:layout_width="0.0dp" android:layout_width="0.0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:layout_weight="1.0" android:layout_weight="1.0"
android:orientation="vertical" android:orientation="vertical"
android:paddingLeft="11.0dip" > android:paddingLeft="11.0dip">
<TextView <TextView
android:id="@+id/now_playing_trackname" android:id="@+id/now_playing_trackname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:ellipsize="marquee"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textStyle="bold" />
<TextView
android:id="@+id/now_playing_artist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:ellipsize="end"
android:scrollHorizontally="true"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceSmall" />
</LinearLayout>
<ImageView
android:id="@+id/now_playing_control_play"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="left" android:layout_gravity="center|right"
android:ellipsize="marquee" android:layout_marginTop="2dip"
android:singleLine="true" android:layout_marginRight="5dip"
android:textAppearance="?android:attr/textAppearanceMedium" android:layout_weight="0.0"
android:textStyle="bold" /> android:focusable="false"
android:src="?attr/media_pause" />
<TextView
android:id="@+id/now_playing_artist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:ellipsize="end"
android:scrollHorizontally="true"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceSmall" />
</LinearLayout>
<ImageView
android:id="@+id/now_playing_control_play"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center|right"
android:layout_marginRight="5dip"
android:layout_marginTop="2dip"
android:layout_weight="0.0"
android:focusable="false"
android:src="?attr/media_pause" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View File

@ -15,6 +15,66 @@
<include layout="@layout/song_details" /> <include layout="@layout/song_details" />
<LinearLayout
a:id="@+id/song_rating"
a:layout_width="wrap_content"
a:layout_height="wrap_content"
a:layout_gravity="center_vertical"
a:orientation="horizontal">
<ImageView
a:id="@+id/song_five_star_1"
a:layout_width="10dip"
a:layout_height="fill_parent"
a:background="@android:color/transparent"
a:focusable="false"
a:gravity="center_vertical"
a:scaleType="centerInside"
a:src="?attr/star_hollow" />
<ImageView
a:id="@+id/song_five_star_2"
a:layout_width="10dip"
a:layout_height="fill_parent"
a:background="@android:color/transparent"
a:focusable="false"
a:gravity="center_vertical"
a:scaleType="centerInside"
a:src="?attr/star_hollow" />
<ImageView
a:id="@+id/song_five_star_3"
a:layout_width="10dip"
a:layout_height="fill_parent"
a:background="@android:color/transparent"
a:focusable="false"
a:gravity="center_vertical"
a:scaleType="centerInside"
a:src="?attr/star_hollow" />
<ImageView
a:id="@+id/song_five_star_4"
a:layout_width="10dip"
a:layout_height="fill_parent"
a:background="@android:color/transparent"
a:focusable="false"
a:gravity="center_vertical"
a:scaleType="centerInside"
a:src="?attr/star_hollow" />
<ImageView
a:id="@+id/song_five_star_5"
a:layout_width="10dip"
a:layout_height="fill_parent"
a:background="@android:color/transparent"
a:focusable="false"
a:gravity="center_vertical"
a:scaleType="centerInside"
a:layout_marginRight="3dip"
a:src="?attr/star_hollow" />
</LinearLayout>
<ImageView <ImageView
a:id="@+id/song_star" a:id="@+id/song_star"
a:layout_width="wrap_content" a:layout_width="wrap_content"

View File

@ -441,5 +441,9 @@
Actualmente no guarda la imagen en el almacenamiento del dispositivo y sólo utiliza caché en la memoria. Actualmente no guarda la imagen en el almacenamiento del dispositivo y sólo utiliza caché en la memoria.
</string> </string>
<string name="feature_flags_image_loader_title">Habilitar nuevo cargador de imágenes</string> <string name="feature_flags_image_loader_title">Habilitar nuevo cargador de imágenes</string>
<string name="feature_flags_five_star_rating_title">Use cinco estrellas para las canciones.</string>
<string name="feature_flags_five_star_rating_description">Utilice el sistema de calificación de cinco estrellas para canciones
en lugar de simplemente destacar / desestimar elementos.
</string>
</resources> </resources>

View File

@ -442,6 +442,10 @@
Actuellement, il n\'enregistre pas l\'image dans le stockage de l\'appareil et n\'utilise que le cache en Actuellement, il n\'enregistre pas l\'image dans le stockage de l\'appareil et n\'utilise que le cache en
mémoire. mémoire.
</string> </string>
<string name="feature_flags_five_star_rating_title">Utiliser une note de cinq étoiles pour les chansons</string>
<string name="feature_flags_five_star_rating_description">Utiliser un système de notation à cinq étoiles pour les chansons
au lieu de simplement mettre en vedette / désactiver les éléments.
</string>
<string name="feature_flags_category_title">Drapeaux des fonctionnalités</string> <string name="feature_flags_category_title">Drapeaux des fonctionnalités</string>
</resources> </resources>

View File

@ -441,5 +441,9 @@
</string> </string>
<string name="feature_flags_category_title">Jellemzők Zászlók</string> <string name="feature_flags_category_title">Jellemzők Zászlók</string>
<string name="feature_flags_image_loader_title">Engedélyezzen új képbetöltőt</string> <string name="feature_flags_image_loader_title">Engedélyezzen új képbetöltőt</string>
<string name="feature_flags_five_star_rating_title">Öt csillagos értékelés használata a dalokhoz</string>
<string name="feature_flags_five_star_rating_description">Öt csillag használata az értékeléshez az egyszerű
csillaggal jelölés helyett.
</string>
</resources> </resources>

View File

@ -442,5 +442,9 @@
Momenteel slaat het geen afbeeldingen op op de apparaatopslag en wordt alleen geheugencache gebruikt. Momenteel slaat het geen afbeeldingen op op de apparaatopslag en wordt alleen geheugencache gebruikt.
</string> </string>
<string name="feature_flags_category_title">Experimentele functies</string> <string name="feature_flags_category_title">Experimentele functies</string>
<string name="feature_flags_five_star_rating_title">Gebruik vijf sterren voor nummers</string>
<string name="feature_flags_five_star_rating_description">Gebruik vijf sterren ratingsysteem voor liedjes
in plaats van items simpelweg in de hoofdrol te zetten / niet te verwijderen.
</string>
</resources> </resources>

View File

@ -455,5 +455,9 @@ ponieważ api Subsonic nie wspiera nowego sposobu autoryzacji dla użytkowników
<string name="feature_flags_image_loader_description">Włącza implementację modułu ładującego nowe obrazy. <string name="feature_flags_image_loader_description">Włącza implementację modułu ładującego nowe obrazy.
Obecnie nie zapisuje obrazów w pamięci urządzenia, tylko wykorzystuje tylko pamięć podręczną.</string> Obecnie nie zapisuje obrazów w pamięci urządzenia, tylko wykorzystuje tylko pamięć podręczną.</string>
<string name="feature_flags_category_title">Flagi funkcji</string> <string name="feature_flags_category_title">Flagi funkcji</string>
<string name="feature_flags_five_star_rating_title">Użyj pięciu gwiazdek dla utworów</string>
<string name="feature_flags_five_star_rating_description">W przypadku utworów użyj systemu pięciu gwiazdek
zamiast po prostu grać gwiazdkami / bez gwiazd.
</string>
</resources> </resources>

View File

@ -441,5 +441,9 @@
</string> </string>
<string name="feature_flags_category_title">Bandeiras de recursos</string> <string name="feature_flags_category_title">Bandeiras de recursos</string>
<string name="feature_flags_image_loader_title">Ativar novo carregador de imagens</string> <string name="feature_flags_image_loader_title">Ativar novo carregador de imagens</string>
<string name="feature_flags_five_star_rating_title">Use classificação de cinco estrelas para músicas</string>
<string name="feature_flags_five_star_rating_description">Use o sistema de classificação de cinco estrelas para músicas
em vez de simplesmente estrelar / não estrelar itens.
</string>
</resources> </resources>

View File

@ -441,5 +441,9 @@
</string> </string>
<string name="feature_flags_category_title">Bandeiras de recursos</string> <string name="feature_flags_category_title">Bandeiras de recursos</string>
<string name="feature_flags_image_loader_title">Ativar novo carregador de imagens</string> <string name="feature_flags_image_loader_title">Ativar novo carregador de imagens</string>
<string name="feature_flags_five_star_rating_title">Use classificação de cinco estrelas para músicas</string>
<string name="feature_flags_five_star_rating_description">Use o sistema de classificação de cinco estrelas para músicas
em vez de simplesmente estrelar / não estrelar itens.
</string>
</resources> </resources>

View File

@ -445,6 +445,10 @@
<string name="feature_flags_image_loader_description">Enables new image loader implementation. <string name="feature_flags_image_loader_description">Enables new image loader implementation.
Currently it doesn\'t save image in device storage and uses only cache in memory. Currently it doesn\'t save image in device storage and uses only cache in memory.
</string> </string>
<string name="feature_flags_five_star_rating_title">Use five star rating for songs</string>
<string name="feature_flags_five_star_rating_description">Use five star rating system for songs
instead of simply starring/unstarring items.
</string>
<string name="feature_flags_category_title">Feature Flags</string> <string name="feature_flags_category_title">Feature Flags</string>
</resources> </resources>

View File

@ -291,6 +291,12 @@
a:title="@string/feature_flags_image_loader_title" a:title="@string/feature_flags_image_loader_title"
a:summary="@string/feature_flags_image_loader_description" a:summary="@string/feature_flags_image_loader_description"
/> />
<CheckBoxPreference
a:key="use_five_star_rating"
a:persistent="false"
a:title="@string/feature_flags_five_star_rating_title"
a:summary="@string/feature_flags_five_star_rating_description"
/>
</PreferenceCategory> </PreferenceCategory>

View File

@ -39,7 +39,7 @@ class APIMusicDirectoryConverterTest {
transcodedSuffix = "some-transcoded-suffix", duration = 11, bitRate = 256, transcodedSuffix = "some-transcoded-suffix", duration = 11, bitRate = 256,
path = "some-path", isDir = true, isVideo = true, playCount = 323, discNumber = 2, path = "some-path", isDir = true, isVideo = true, playCount = 323, discNumber = 2,
created = Calendar.getInstance(), type = "some-type", created = Calendar.getInstance(), type = "some-type",
starred = Calendar.getInstance()) starred = Calendar.getInstance(), userRating = 3, averageRating = 2.99F)
val convertedEntity = entity.toDomainEntity() val convertedEntity = entity.toDomainEntity()
@ -69,6 +69,8 @@ class APIMusicDirectoryConverterTest {
starred `should be equal to` (entity.starred != null) starred `should be equal to` (entity.starred != null)
discNumber `should equal` entity.discNumber discNumber `should equal` entity.discNumber
type `should equal` entity.type type `should equal` entity.type
userRating `should equal` entity.userRating
averageRating `should equal` entity.averageRating
} }
} }