project upload

This commit is contained in:
Antonio Cappiello
2020-11-20 15:38:08 +01:00
parent e1749a3123
commit 6eff64e7e1
105 changed files with 5907 additions and 0 deletions

14
.gitignore vendored Normal file
View File

@@ -0,0 +1,14 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx

3
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

6
.idea/compiler.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="1.8" />
</component>
</project>

23
.idea/gradle.xml generated Normal file
View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="PLATFORM" />
<option name="disableWrapperSourceDistributionNotification" value="true" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="1.8" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
<option name="useQualifiedModuleNames" value="true" />
</GradleProjectSettings>
</option>
</component>
</project>

30
.idea/jarRepositories.xml generated Normal file
View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="BintrayJCenter" />
<option name="name" value="BintrayJCenter" />
<option name="url" value="https://jcenter.bintray.com/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven" />
<option name="name" value="maven" />
<option name="url" value="https://jitpack.io" />
</remote-repository>
<remote-repository>
<option name="id" value="Google" />
<option name="name" value="Google" />
<option name="url" value="https://dl.google.com/dl/android/maven2/" />
</remote-repository>
</component>
</project>

9
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

1
app/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

63
app/build.gradle Normal file
View File

@@ -0,0 +1,63 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 30
buildToolsVersion "29.0.3"
defaultConfig {
applicationId "com.cappielloantonio.play"
minSdkVersion 26
targetSdkVersion 30
versionCode 1
versionName "1.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}
buildFeatures {
viewBinding = true
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.4.10'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"
implementation 'pub.devrel:easypermissions:3.0.0'
implementation 'androidx.preference:preference-ktx:1.1.1'
implementation 'com.android.volley:volley:1.1.1'
implementation "com.paulrybitskyi.persistentsearchview:persistentsearchview:1.1.3"
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.1'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.1'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'com.github.bumptech.glide:glide:4.11.0'
implementation "androidx.room:room-runtime:2.2.5"
implementation 'com.github.jellyfin.jellyfin-apiclient-java:android:0.7.7'
implementation "androidx.cardview:cardview:1.0.0"
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
annotationProcessor "androidx.room:room-compiler:2.2.5"
testImplementation 'junit:junit:4.13.1'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
debugImplementation 'com.amitshekhar.android:debug-db:1.0.6'
}

21
app/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -0,0 +1,26 @@
package com.cappielloantonio.play;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.cappielloantonio.libr", appContext.getPackageName());
}
}

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.cappielloantonio.play">
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:usesCleartextTraffic="true">
<activity
android:name=".ui.activities.MainActivity"
android:windowSoftInputMode="adjustPan|adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,65 @@
package com.cappielloantonio.play;
import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.preference.PreferenceManager;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.Volley;
import com.cappielloantonio.play.helper.ThemeHelper;
import com.cappielloantonio.play.util.PreferenceUtil;
import org.jellyfin.apiclient.AppInfo;
import org.jellyfin.apiclient.Jellyfin;
import org.jellyfin.apiclient.JellyfinAndroidKt;
import org.jellyfin.apiclient.JellyfinOptions;
import org.jellyfin.apiclient.interaction.AndroidDevice;
import org.jellyfin.apiclient.interaction.ApiClient;
import org.jellyfin.apiclient.interaction.ApiEventListener;
import org.jellyfin.apiclient.logging.NullLogger;
public class App extends Application {
private static final String TAG = "App";
private static App instance;
private static ApiClient apiClient;
@Override
public void onCreate() {
super.onCreate();
ThemeHelper.applyTheme(PreferenceUtil.getInstance(getApplicationContext()).getTheme());
}
public static App getInstance() {
if (instance == null) {
instance = new App();
}
return instance;
}
public static ApiClient getApiClientInstance(Context context) {
if (apiClient == null) {
apiClient = getApiClient(context);
}
return apiClient;
}
public RequestQueue getRequestQueue(Context context) {
return Volley.newRequestQueue(context);
}
private static ApiClient getApiClient(Context context) {
String server = PreferenceUtil.getInstance(context).getServer();
JellyfinOptions.Builder options = new JellyfinOptions.Builder();
options.setLogger(new NullLogger());
options.setAppInfo(new AppInfo(context.getString(R.string.app_name), BuildConfig.VERSION_NAME));
JellyfinAndroidKt.android(options, context);
Jellyfin jellyfin = new Jellyfin(options.build());
return jellyfin.createApi(server, null, AndroidDevice.fromContext(context), new ApiEventListener());
}
}

View File

@@ -0,0 +1,80 @@
package com.cappielloantonio.play.adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.play.R;
import com.cappielloantonio.play.model.Album;
import com.cappielloantonio.play.model.Genre;
import java.util.List;
public class AlbumAdapter extends RecyclerView.Adapter<AlbumAdapter.ViewHolder> {
private static final String TAG = "RecentMusicAdapter";
private List<Album> albums;
private LayoutInflater mInflater;
private Context context;
private ItemClickListener itemClickListener;
public AlbumAdapter(Context context, List<Album> albums) {
this.context = context;
this.mInflater = LayoutInflater.from(context);
this.albums = albums;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.item_library_album, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Album album = albums.get(position);
holder.textAlbumName.setText(album.getTitle());
holder.textArtistName.setText(album.getArtistName());
}
@Override
public int getItemCount() {
return albums.size();
}
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView textAlbumName;
TextView textArtistName;
ViewHolder(View itemView) {
super(itemView);
textAlbumName = itemView.findViewById(R.id.album_name_label);
textArtistName = itemView.findViewById(R.id.artist_name_label);
itemView.setOnClickListener(this);
}
@Override
public void onClick(View view) {
if (itemClickListener != null) itemClickListener.onItemClick(view, getAdapterPosition());
}
}
public void setItems(List<Album> albums) {
this.albums = albums;
notifyDataSetChanged();
}
public void setClickListener(ItemClickListener itemClickListener) {
this.itemClickListener = itemClickListener;
}
public interface ItemClickListener {
void onItemClick(View view, int position);
}
}

View File

@@ -0,0 +1,77 @@
package com.cappielloantonio.play.adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.play.R;
import com.cappielloantonio.play.model.Album;
import com.cappielloantonio.play.model.Artist;
import java.util.List;
public class ArtistAdapter extends RecyclerView.Adapter<ArtistAdapter.ViewHolder> {
private static final String TAG = "ArtistAdapter";
private List<Artist> artists;
private LayoutInflater mInflater;
private Context context;
private ItemClickListener itemClickListener;
public ArtistAdapter(Context context, List<Artist> artists) {
this.context = context;
this.mInflater = LayoutInflater.from(context);
this.artists = artists;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.item_library_artist, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Artist artist = artists.get(position);
holder.textArtistName.setText(artist.getName());
}
@Override
public int getItemCount() {
return artists.size();
}
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView textArtistName;
ViewHolder(View itemView) {
super(itemView);
textArtistName = itemView.findViewById(R.id.artist_name_label);
itemView.setOnClickListener(this);
}
@Override
public void onClick(View view) {
if (itemClickListener != null) itemClickListener.onItemClick(view, getAdapterPosition());
}
}
public void setItems(List<Artist> artists) {
this.artists = artists;
notifyDataSetChanged();
}
public void setClickListener(ItemClickListener itemClickListener) {
this.itemClickListener = itemClickListener;
}
public interface ItemClickListener {
void onItemClick(View view, int position);
}
}

View File

@@ -0,0 +1,64 @@
package com.cappielloantonio.play.adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.viewpager.widget.PagerAdapter;
import com.cappielloantonio.play.R;
import com.cappielloantonio.play.model.Song;
import java.util.List;
public class DiscoverSongAdapter extends PagerAdapter {
private List<Song> songs;
private LayoutInflater layoutInflater;
private Context context;
public DiscoverSongAdapter(Context context, List<Song> models) {
this.context = context;
this.songs = models;
}
@Override
public int getCount() {
return songs.size();
}
@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
return view.equals(object);
}
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, final int position) {
layoutInflater = LayoutInflater.from(context);
View view = layoutInflater.inflate(R.layout.item_discover_song, container, false);
TextView title;
TextView desc;
title = view.findViewById(R.id.title_discover_song_label);
desc = view.findViewById(R.id.artist_discover_song_label);
title.setText(songs.get(position).getTitle());
desc.setText(songs.get(position).getAlbumName());
view.setOnClickListener(v -> Toast.makeText(context, songs.get(position).getTitle(), Toast.LENGTH_SHORT).show());
container.addView(view, 0);
return view;
}
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
container.removeView((View)object);
}
}

View File

@@ -0,0 +1,77 @@
package com.cappielloantonio.play.adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.play.R;
import com.cappielloantonio.play.model.Genre;
import java.util.List;
public class GenreAdapter extends RecyclerView.Adapter<GenreAdapter.ViewHolder> {
private static final String TAG = "GenreAdapter";
private List<Genre> genres;
private LayoutInflater mInflater;
private Context context;
private ItemClickListener itemClickListener;
public GenreAdapter(Context context, List<Genre> genres) {
this.context = context;
this.mInflater = LayoutInflater.from(context);
this.genres = genres;
}
// inflates the row layout from xml when needed
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.item_library_genre, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Genre genre = genres.get(position);
holder.textGenre.setText(genre.getName());
}
@Override
public int getItemCount() {
return genres.size();
}
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView textGenre;
ViewHolder(View itemView) {
super(itemView);
textGenre = itemView.findViewById(R.id.genre_label);
itemView.setOnClickListener(this);
}
@Override
public void onClick(View view) {
if (itemClickListener != null) itemClickListener.onItemClick(view, getAdapterPosition());
}
}
public void setItems(List<Genre> genres) {
this.genres = genres;
notifyDataSetChanged();
}
public void setClickListener(ItemClickListener itemClickListener) {
this.itemClickListener = itemClickListener;
}
public interface ItemClickListener {
void onItemClick(View view, int position);
}
}

View File

@@ -0,0 +1,77 @@
package com.cappielloantonio.play.adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.play.R;
import com.cappielloantonio.play.model.Artist;
import com.cappielloantonio.play.model.Playlist;
import java.util.List;
public class PlaylistAdapter extends RecyclerView.Adapter<PlaylistAdapter.ViewHolder> {
private static final String TAG = "PlaylistAdapter";
private List<Playlist> playlists;
private LayoutInflater mInflater;
private Context context;
private ItemClickListener itemClickListener;
public PlaylistAdapter(Context context, List<Playlist> playlists) {
this.context = context;
this.mInflater = LayoutInflater.from(context);
this.playlists = playlists;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.item_library_playlist, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Playlist playlist = playlists.get(position);
holder.textPlaylistName.setText(playlist.getName());
}
@Override
public int getItemCount() {
return playlists.size();
}
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView textPlaylistName;
ViewHolder(View itemView) {
super(itemView);
textPlaylistName = itemView.findViewById(R.id.playlist_name_text);
itemView.setOnClickListener(this);
}
@Override
public void onClick(View view) {
if (itemClickListener != null) itemClickListener.onItemClick(view, getAdapterPosition());
}
}
public void setItems(List<Playlist> playlists) {
this.playlists = playlists;
notifyDataSetChanged();
}
public void setClickListener(ItemClickListener itemClickListener) {
this.itemClickListener = itemClickListener;
}
public interface ItemClickListener {
void onItemClick(View view, int position);
}
}

View File

@@ -0,0 +1,76 @@
package com.cappielloantonio.play.adapter;
import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.play.R;
import com.cappielloantonio.play.model.Song;
import java.util.List;
public class RecentMusicAdapter extends RecyclerView.Adapter<RecentMusicAdapter.ViewHolder> {
private static final String TAG = "RecentMusicAdapter";
private List<Song> songs;
private LayoutInflater mInflater;
private Context context;
private ItemClickListener itemClickListener;
public RecentMusicAdapter(Context context, List<Song> songs) {
this.context = context;
this.mInflater = LayoutInflater.from(context);
this.songs = songs;
}
// inflates the row layout from xml when needed
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.item_recent_track, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Song song = songs.get(position);
holder.textTitle.setText(song.getTitle());
holder.textArtist.setText(song.getAlbumName());
}
@Override
public int getItemCount() {
return songs.size();
}
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView textTitle;
TextView textArtist;
ViewHolder(View itemView) {
super(itemView);
textTitle = itemView.findViewById(R.id.title_track_label);
textArtist = itemView.findViewById(R.id.artist_track_label);
itemView.setOnClickListener(this);
}
@Override
public void onClick(View view) {
if (itemClickListener != null) itemClickListener.onItemClick(view, getAdapterPosition());
}
}
public void setClickListener(ItemClickListener itemClickListener) {
this.itemClickListener = itemClickListener;
}
public interface ItemClickListener {
void onItemClick(View view, int position);
}
}

View File

@@ -0,0 +1,46 @@
package com.cappielloantonio.play.database;
import android.content.Context;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import com.cappielloantonio.play.database.dao.AlbumDao;
import com.cappielloantonio.play.database.dao.ArtistDao;
import com.cappielloantonio.play.database.dao.GenreDao;
import com.cappielloantonio.play.database.dao.PlaylistDao;
import com.cappielloantonio.play.database.dao.SongDao;
import com.cappielloantonio.play.model.Album;
import com.cappielloantonio.play.model.Artist;
import com.cappielloantonio.play.model.Genre;
import com.cappielloantonio.play.model.Playlist;
import com.cappielloantonio.play.model.Song;
@Database(entities = {Album.class, Artist.class, Genre.class, Playlist.class, Song.class}, version = 2, exportSchema = false)
public abstract class AppDatabase extends RoomDatabase {
private static final String TAG = "AppDatabase";
private static AppDatabase instance;
private final static String DB_NAME = "play_db";
public static synchronized AppDatabase getInstance(Context context) {
if (instance == null) {
instance = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, DB_NAME)
.fallbackToDestructiveMigration()
.build();
}
return instance;
}
public abstract AlbumDao albumDao();
public abstract ArtistDao artistDao();
public abstract GenreDao genreDao();
public abstract PlaylistDao playlistDao();
public abstract SongDao songDao();
}

View File

@@ -0,0 +1,31 @@
package com.cappielloantonio.play.database.dao;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import com.cappielloantonio.play.model.Album;
import com.cappielloantonio.play.model.Song;
import java.util.List;
@Dao
public interface AlbumDao {
@Query("SELECT * FROM album")
LiveData<List<Album>> getAll();
@Query("SELECT EXISTS(SELECT * FROM album WHERE id = :id)")
boolean exist(String id);
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(Album album);
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insertAll(List<Album> albums);
@Delete
void delete(Album album);
}

View File

@@ -0,0 +1,31 @@
package com.cappielloantonio.play.database.dao;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import com.cappielloantonio.play.model.Album;
import com.cappielloantonio.play.model.Artist;
import java.util.List;
@Dao
public interface ArtistDao {
@Query("SELECT * FROM artist")
LiveData<List<Artist>> getAll();
@Query("SELECT EXISTS(SELECT * FROM artist WHERE id = :id)")
boolean exist(String id);
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(Artist artist);
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insertAll(List<Artist> artists);
@Delete
void delete(Artist artist);
}

View File

@@ -0,0 +1,31 @@
package com.cappielloantonio.play.database.dao;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import com.cappielloantonio.play.model.Artist;
import com.cappielloantonio.play.model.Genre;
import java.util.List;
@Dao
public interface GenreDao {
@Query("SELECT * FROM genre")
LiveData<List<Genre>> getAll();
@Query("SELECT EXISTS(SELECT * FROM genre WHERE id = :id)")
boolean exist(String id);
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(Genre genre);
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insertAll(List<Genre> genres);
@Delete
void delete(Genre genre);
}

View File

@@ -0,0 +1,31 @@
package com.cappielloantonio.play.database.dao;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import com.cappielloantonio.play.model.Artist;
import com.cappielloantonio.play.model.Playlist;
import java.util.List;
@Dao
public interface PlaylistDao {
@Query("SELECT * FROM playlist")
LiveData<List<Playlist>> getAll();
@Query("SELECT EXISTS(SELECT * FROM playlist WHERE id = :id)")
boolean exist(String id);
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(Playlist playlist);
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insertAll(List<Playlist> playlists);
@Delete
void delete(Playlist playlist);
}

View File

@@ -0,0 +1,30 @@
package com.cappielloantonio.play.database.dao;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import com.cappielloantonio.play.model.Song;
import java.util.List;
@Dao
public interface SongDao {
@Query("SELECT * FROM song")
LiveData<List<Song>> getAll();
@Query("SELECT EXISTS(SELECT * FROM song WHERE id = :id)")
boolean exist(String id);
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(Song song);
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insertAll(List<Song> songs);
@Delete
void delete(Song song);
}

View File

@@ -0,0 +1,27 @@
package com.cappielloantonio.play.glide;
import android.content.Context;
import androidx.annotation.NonNull;
import com.bumptech.glide.GlideBuilder;
import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.load.DecodeFormat;
import com.bumptech.glide.load.engine.cache.DiskLruCacheFactory;
import com.bumptech.glide.module.AppGlideModule;
import com.bumptech.glide.request.RequestOptions;
import com.cappielloantonio.play.util.PreferenceUtil;
import java.io.File;
@GlideModule
public class CustomGlideModule extends AppGlideModule {
@Override
public void applyOptions(@NonNull Context context, GlideBuilder builder) {
File file = new File(context.getCacheDir() + "glide");
int size = PreferenceUtil.getInstance(context).getImageCacheSize();
builder.setDiskCache(new DiskLruCacheFactory(() -> file, size));
builder.setDefaultRequestOptions(new RequestOptions().format(DecodeFormat.PREFER_RGB_565));
}
}

View File

@@ -0,0 +1,111 @@
package com.cappielloantonio.play.helper;
import android.util.Log;
import org.jellyfin.apiclient.interaction.ApiClient;
import org.jellyfin.apiclient.interaction.ApiEventListener;
import org.jellyfin.apiclient.model.apiclient.RemoteLogoutReason;
import org.jellyfin.apiclient.model.apiclient.SessionUpdatesEventArgs;
import org.jellyfin.apiclient.model.dto.UserDto;
import org.jellyfin.apiclient.model.entities.LibraryUpdateInfo;
import org.jellyfin.apiclient.model.session.BrowseRequest;
import org.jellyfin.apiclient.model.session.GeneralCommand;
import org.jellyfin.apiclient.model.session.MessageCommand;
import org.jellyfin.apiclient.model.session.PlayRequest;
import org.jellyfin.apiclient.model.session.PlaystateRequest;
import org.jellyfin.apiclient.model.session.SessionInfoDto;
import org.jellyfin.apiclient.model.session.UserDataChangeInfo;
public class EventListenerHelper extends ApiEventListener {
private static final String TAG = "EventListenerHelper";
@Override
public void onRemoteLoggedOut(ApiClient client, RemoteLogoutReason reason) {
Log.i(TAG, "onRemoteLoggedOut: " + reason);
}
@Override
public void onUserUpdated(ApiClient client, UserDto userDto) {
Log.i(TAG, "onUserUpdated: " + userDto.getName());
}
@Override
public void onLibraryChanged(ApiClient client, LibraryUpdateInfo info) {
Log.i(TAG, "onLibraryChanged");
}
@Override
public void onUserConfigurationUpdated(ApiClient client, UserDto userDto) {
Log.i(TAG, "onUserConfigurationUpdated");
}
@Override
public void onBrowseCommand(ApiClient client, BrowseRequest command) {
Log.i(TAG, "onBrowseCommand: " + command.getItemName());
}
@Override
public void onPlayCommand(ApiClient client, PlayRequest command) {
Log.i(TAG, "onPlayCommand: " + command.getPlayCommand());
}
@Override
public void onPlaystateCommand(ApiClient client, PlaystateRequest command) {
Log.i(TAG, "onPlayStateCommand");
}
@Override
public void onMessageCommand(ApiClient client, MessageCommand command) {
Log.i(TAG, "onMessageCommand");
}
@Override
public void onGeneralCommand(ApiClient client, GeneralCommand command) {
Log.i(TAG, "onGeneralCommand: " + command.getName());
}
@Override
public void onSendStringCommand(ApiClient client, String value) {
Log.i(TAG, "onSendStringCommand");
}
@Override
public void onSetVolumeCommand(ApiClient client, int value) {
Log.i(TAG, "onSetVolumeCommand");
}
@Override
public void onSetAudioStreamIndexCommand(ApiClient client, int value) {
Log.i(TAG, "onSetAudioStreamIndexCommand");
}
@Override
public void onSetSubtitleStreamIndexCommand(ApiClient client, int value) {
Log.i(TAG, "onSetSubtitleStreamIndexCommand");
}
@Override
public void onUserDataChanged(ApiClient client, UserDataChangeInfo info) {
Log.i(TAG, "onUserDataChanged");
}
@Override
public void onSessionsUpdated(ApiClient client, SessionUpdatesEventArgs args) {
Log.i(TAG, "onSessionsUpdated");
}
@Override
public void onPlaybackStart(ApiClient client, SessionInfoDto info) {
Log.i(TAG, "onPlaybackStart");
}
@Override
public void onPlaybackStopped(ApiClient client, SessionInfoDto info) {
Log.i(TAG, "onPlaybackStopped");
}
@Override
public void onSessionEnded(ApiClient client, SessionInfoDto info) {
Log.i(TAG, "onSessionEnded");
}
}

View File

@@ -0,0 +1,33 @@
package com.cappielloantonio.play.helper;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatDelegate;
public class ThemeHelper {
public static final String LIGHT_MODE = "light";
public static final String DARK_MODE = "dark";
public static final String DEFAULT_MODE = "default";
public static void applyTheme(@NonNull String themePref) {
switch (themePref) {
case LIGHT_MODE: {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
break;
}
case DARK_MODE: {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
break;
}
default: {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
} else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY);
}
break;
}
}
}
}

View File

@@ -0,0 +1,10 @@
package com.cappielloantonio.play.interfaces;
import com.android.volley.VolleyError;
import java.util.List;
public interface MediaCallback {
void onError(Exception exception);
void onLoadMedia(List<?> media);
}

View File

@@ -0,0 +1,193 @@
package com.cappielloantonio.play.model;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Ignore;
import androidx.room.PrimaryKey;
import org.jellyfin.apiclient.model.dto.BaseItemDto;
import org.jellyfin.apiclient.model.entities.ImageType;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@Entity(tableName = "album")
public class Album implements Parcelable {
@Ignore
public List<Song> songs;
@NonNull
@PrimaryKey
@ColumnInfo(name = "id")
public String id;
@ColumnInfo(name = "title")
public String title;
@ColumnInfo(name = "year")
public int year;
@ColumnInfo(name = "artistId")
public String artistId;
@ColumnInfo(name = "artistName")
public String artistName;
@ColumnInfo(name = "primary")
public String primary;
@ColumnInfo(name = "blurHash")
public String blurHash;
public Album(@NonNull String id, String title, int year, String artistId, String artistName, String primary, String blurHash) {
this.id = id;
this.title = title;
this.year = year;
this.artistId = artistId;
this.artistName = artistName;
this.primary = primary;
this.blurHash = blurHash;
}
@Ignore
public Album(BaseItemDto itemDto) {
this.id = itemDto.getId();
this.title = itemDto.getName();
this.year = itemDto.getProductionYear() != null ? itemDto.getProductionYear() : 0;
if (itemDto.getAlbumArtists().size() != 0) {
this.artistId = itemDto.getAlbumArtists().get(0).getId();
this.artistName = itemDto.getAlbumArtists().get(0).getName();
} else if (itemDto.getArtistItems().size() != 0) {
this.artistId = itemDto.getArtistItems().get(0).getId();
this.artistName = itemDto.getArtistItems().get(0).getName();
}
this.primary = itemDto.getImageTags().containsKey(ImageType.Primary) ? id : null;
if (itemDto.getImageBlurHashes() != null && itemDto.getImageBlurHashes().get(ImageType.Primary) != null) {
this.blurHash = (String) itemDto.getImageBlurHashes().get(ImageType.Primary).values().toArray()[0];
}
this.songs = new ArrayList<>();
}
@NonNull
public String getId() {
return id;
}
public void setId(@NonNull String id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public String getArtistId() {
return artistId;
}
public void setArtistId(String artistId) {
this.artistId = artistId;
}
public String getArtistName() {
return artistName;
}
public void setArtistName(String artistName) {
this.artistName = artistName;
}
public String getPrimary() {
return primary;
}
public void setPrimary(String primary) {
this.primary = primary;
}
public String getBlurHash() {
return blurHash;
}
public void setBlurHash(String blurHash) {
this.blurHash = blurHash;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Album album = (Album) o;
return id.equals(album.id);
}
@Override
public int hashCode() {
return id.hashCode();
}
@NonNull
@Override
public String toString() {
return id;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(id);
dest.writeString(title);
dest.writeInt(year);
dest.writeString(artistId);
dest.writeString(artistName);
dest.writeString(primary);
dest.writeString(blurHash);
}
protected Album(Parcel in) {
this.songs = new ArrayList<>();
this.id = in.readString();
this.title = in.readString();
this.year = in.readInt();
this.artistId = in.readString();
this.artistName = in.readString();
this.primary = in.readString();
this.blurHash = in.readString();
}
public static final Creator<Album> CREATOR = new Creator<Album>() {
public Album createFromParcel(Parcel source) {
return new Album(source);
}
public Album[] newArray(int size) {
return new Album[size];
}
};
}

View File

@@ -0,0 +1,157 @@
package com.cappielloantonio.play.model;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Ignore;
import androidx.room.PrimaryKey;
import org.jellyfin.apiclient.model.dto.BaseItemDto;
import org.jellyfin.apiclient.model.dto.GenreDto;
import org.jellyfin.apiclient.model.entities.ImageType;
import java.util.ArrayList;
import java.util.List;
@Entity(tableName = "artist")
public class Artist implements Parcelable {
@Ignore
public List<Genre> genres;
@Ignore
public List<Album> albums;
@Ignore
public List<Song> songs;
@NonNull
@PrimaryKey
@ColumnInfo(name = "id")
public String id;
@ColumnInfo(name = "name")
public String name;
@ColumnInfo(name = "primary")
public String primary;
@ColumnInfo(name = "blurHash")
public String blurHash;
public Artist(@NonNull String id, String name, String primary, String blurHash) {
this.id = id;
this.name = name;
this.primary = primary;
this.blurHash = blurHash;
}
@Ignore
public Artist(BaseItemDto itemDto) {
this.id = itemDto.getId();
this.name = itemDto.getName();
this.primary = itemDto.getImageTags().containsKey(ImageType.Primary) ? id : null;
if (itemDto.getImageBlurHashes() != null && itemDto.getImageBlurHashes().get(ImageType.Primary) != null) {
this.blurHash = (String) itemDto.getImageBlurHashes().get(ImageType.Primary).values().toArray()[0];
}
this.genres = new ArrayList<>();
this.albums = new ArrayList<>();
this.songs = new ArrayList<>();
if (itemDto.getGenreItems() != null) {
for (GenreDto genre : itemDto.getGenreItems()) {
genres.add(new Genre(genre));
}
}
}
@NonNull
public String getId() {
return id;
}
public void setId(@NonNull String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPrimary() {
return primary;
}
public void setPrimary(String primary) {
this.primary = primary;
}
public String getBlurHash() {
return blurHash;
}
public void setBlurHash(String blurHash) {
this.blurHash = blurHash;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Artist artist = (Artist) o;
return id.equals(artist.id);
}
@Override
public int hashCode() {
return id.hashCode();
}
@NonNull
@Override
public String toString() {
return id;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(id);
dest.writeString(name);
dest.writeString(primary);
dest.writeString(blurHash);
}
protected Artist(Parcel in) {
this.genres = new ArrayList<>();
this.albums = new ArrayList<>();
this.songs = new ArrayList<>();
this.id = in.readString();
this.name = in.readString();
this.primary = in.readString();
this.blurHash = in.readString();
}
public static final Parcelable.Creator<Artist> CREATOR = new Parcelable.Creator<Artist>() {
@Override
public Artist createFromParcel(Parcel source) {
return new Artist(source);
}
@Override
public Artist[] newArray(int size) {
return new Artist[size];
}
};
}

View File

@@ -0,0 +1,140 @@
package com.cappielloantonio.play.model;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Ignore;
import androidx.room.PrimaryKey;
import org.jellyfin.apiclient.model.dto.BaseItemDto;
import org.jellyfin.apiclient.model.dto.GenreDto;
import org.jellyfin.apiclient.model.entities.ImageType;
import java.util.UUID;
@Entity(tableName = "genre")
public class Genre implements Parcelable {
@NonNull
@PrimaryKey
@ColumnInfo(name = "id")
public String id;
@ColumnInfo(name = "name")
public String name;
@ColumnInfo(name = "songCount")
public int songCount;
@ColumnInfo(name = "primary")
public String primary;
@ColumnInfo(name = "blurHash")
public String blurHash;
public Genre(@NonNull String id, String name, int songCount, String primary, String blurHash) {
this.id = id;
this.name = name;
this.songCount = songCount;
this.primary = primary;
this.blurHash = blurHash;
}
@Ignore
public Genre(GenreDto genreDto) {
this.id = genreDto.getId();
this.name = genreDto.getName();
this.songCount = 0;
}
@Ignore
public Genre(BaseItemDto itemDto) {
this.id = itemDto.getId();
this.name = itemDto.getName();
this.songCount = itemDto.getSongCount() != null ? itemDto.getSongCount() : 0;
this.primary = itemDto.getImageTags().containsKey(ImageType.Primary) ? id : null;
if (itemDto.getImageBlurHashes() != null && itemDto.getImageBlurHashes().get(ImageType.Primary) != null) {
this.blurHash = (String) itemDto.getImageBlurHashes().get(ImageType.Primary).values().toArray()[0];
}
}
@NonNull
public String getId() {
return id;
}
public String getName() {
return name;
}
public int getSongCount() {
return songCount;
}
public String getPrimary() {
return primary;
}
public void setPrimary(String primary) {
this.primary = primary;
}
public String getBlurHash() {
return blurHash;
}
public void setBlurHash(String blurHash) {
this.blurHash = blurHash;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Genre genre = (Genre) o;
return id.equals(genre.id);
}
@Override
public int hashCode() {
return id.hashCode();
}
@NonNull
@Override
public String toString() {
return id;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.id);
dest.writeString(this.name);
dest.writeInt(this.songCount);
}
protected Genre(Parcel in) {
this.id = in.readString();
this.name = in.readString();
this.songCount = in.readInt();
}
public static final Creator<Genre> CREATOR = new Creator<Genre>() {
public Genre createFromParcel(Parcel source) {
return new Genre(source);
}
public Genre[] newArray(int size) {
return new Genre[size];
}
};
}

View File

@@ -0,0 +1,119 @@
package com.cappielloantonio.play.model;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Ignore;
import androidx.room.PrimaryKey;
import org.jellyfin.apiclient.model.dto.BaseItemDto;
import org.jellyfin.apiclient.model.entities.ImageType;
@Entity(tableName = "playlist")
public class Playlist implements Parcelable {
@NonNull
@PrimaryKey
@ColumnInfo(name = "id")
public String id;
@ColumnInfo(name = "name")
public String name;
@ColumnInfo(name = "primary")
public String primary;
@ColumnInfo(name = "blurHash")
public String blurHash;
public Playlist(@NonNull String id, String name, String primary, String blurHash) {
this.id = id;
this.name = name;
this.primary = primary;
this.blurHash = blurHash;
}
@Ignore
public Playlist(BaseItemDto itemDto) {
this.id = itemDto.getId();
this.name = itemDto.getName();
this.primary = itemDto.getImageTags().containsKey(ImageType.Primary) ? id : null;
if (itemDto.getImageBlurHashes() != null && itemDto.getImageBlurHashes().get(ImageType.Primary) != null) {
this.blurHash = (String) itemDto.getImageBlurHashes().get(ImageType.Primary).values().toArray()[0];
}
}
@NonNull
public String getId() {
return id;
}
public String getName() {
return name;
}
public String getPrimary() {
return primary;
}
public void setPrimary(String primary) {
this.primary = primary;
}
public String getBlurHash() {
return blurHash;
}
public void setBlurHash(String blurHash) {
this.blurHash = blurHash;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Playlist playlist = (Playlist) o;
return id.equals(playlist.id);
}
@Override
public int hashCode() {
return id.hashCode();
}
@NonNull
@Override
public String toString() {
return id;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.id);
dest.writeString(this.name);
}
protected Playlist(Parcel in) {
this.id = in.readString();
this.name = in.readString();
}
public static final Creator<Playlist> CREATOR = new Creator<Playlist>() {
public Playlist createFromParcel(Parcel source) {
return new Playlist(source);
}
public Playlist[] newArray(int size) {
return new Playlist[size];
}
};
}

View File

@@ -0,0 +1,415 @@
package com.cappielloantonio.play.model;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Ignore;
import androidx.room.PrimaryKey;
import org.jellyfin.apiclient.model.dto.BaseItemDto;
import org.jellyfin.apiclient.model.dto.MediaSourceInfo;
import org.jellyfin.apiclient.model.entities.ImageType;
import org.jellyfin.apiclient.model.entities.MediaStream;
import java.util.UUID;
@Entity(tableName = "song")
public class Song implements Parcelable {
@NonNull
@PrimaryKey
@ColumnInfo(name = "id")
private String id;
@ColumnInfo(name = "title")
private String title;
@ColumnInfo(name = "trackNumber")
private int trackNumber;
@ColumnInfo(name = "discNumber")
private int discNumber;
@ColumnInfo(name = "year")
private int year;
@ColumnInfo(name = "duration")
private long duration;
@ColumnInfo(name = "albumId")
private String albumId;
@ColumnInfo(name = "albumName")
private String albumName;
@ColumnInfo(name = "artistId")
private String artistId;
@ColumnInfo(name = "artistName")
private String artistName;
@ColumnInfo(name = "primary")
private String primary;
@ColumnInfo(name = "blurHash")
private String blurHash;
@ColumnInfo(name = "favorite")
private boolean favorite;
@ColumnInfo(name = "path")
private String path;
@ColumnInfo(name = "size")
private long size;
@ColumnInfo(name = "container")
private String container;
@ColumnInfo(name = "codec")
private String codec;
@ColumnInfo(name = "sampleRate")
private int sampleRate;
@ColumnInfo(name = "bitRate")
private int bitRate;
@ColumnInfo(name = "bitDepth")
private int bitDepth;
@ColumnInfo(name = "channels")
private int channels;
public Song(@NonNull String id, String title, int trackNumber, int discNumber, int year, long duration, String albumId, String albumName, String artistId, String artistName, String primary, String blurHash, boolean favorite, String path, long size, String container, String codec, int sampleRate, int bitRate, int bitDepth, int channels) {
this.id = id;
this.title = title;
this.trackNumber = trackNumber;
this.discNumber = discNumber;
this.year = year;
this.duration = duration;
this.albumId = albumId;
this.albumName = albumName;
this.artistId = artistId;
this.artistName = artistName;
this.primary = primary;
this.blurHash = blurHash;
this.favorite = favorite;
this.path = path;
this.size = size;
this.container = container;
this.codec = codec;
this.sampleRate = sampleRate;
this.bitRate = bitRate;
this.bitDepth = bitDepth;
this.channels = channels;
}
@Ignore
public Song(BaseItemDto itemDto) {
this.id = itemDto.getId();
this.title = itemDto.getName();
this.trackNumber = itemDto.getIndexNumber() != null ? itemDto.getIndexNumber() : 0;
this.discNumber = itemDto.getParentIndexNumber() != null ? itemDto.getParentIndexNumber() : 0;
this.year = itemDto.getProductionYear() != null ? itemDto.getProductionYear() : 0;
this.duration = itemDto.getRunTimeTicks() != null ? itemDto.getRunTimeTicks() / 10000 : 0;
this.albumId = itemDto.getAlbumId();
this.albumName = itemDto.getAlbum();
if (itemDto.getAlbumArtists().size() != 0) {
this.artistId = itemDto.getAlbumArtists().get(0).getId();
this.artistName = itemDto.getAlbumArtists().get(0).getName();
} else if (itemDto.getArtistItems().size() != 0) {
this.artistId = itemDto.getArtistItems().get(0).getId();
this.artistName = itemDto.getArtistItems().get(0).getName();
}
this.primary = itemDto.getAlbumPrimaryImageTag() != null ? albumId : null;
if (itemDto.getImageBlurHashes() != null && itemDto.getImageBlurHashes().get(ImageType.Primary) != null) {
this.blurHash = (String) itemDto.getImageBlurHashes().get(ImageType.Primary).values().toArray()[0];
}
this.favorite = itemDto.getUserData() != null && itemDto.getUserData().getIsFavorite();
if (itemDto.getMediaSources() != null && itemDto.getMediaSources().get(0) != null) {
MediaSourceInfo source = itemDto.getMediaSources().get(0);
this.path = source.getPath();
this.size = source.getSize() != null ? source.getSize() : 0;
this.container = source.getContainer();
this.bitRate = source.getBitrate() != null ? source.getBitrate() : 0;
if (source.getMediaStreams() != null && source.getMediaStreams().size() != 0) {
MediaStream stream = source.getMediaStreams().get(0);
this.codec = stream.getCodec();
this.sampleRate = stream.getSampleRate() != null ? stream.getSampleRate() : 0;
this.bitDepth = stream.getBitDepth() != null ? stream.getBitDepth() : 0;
this.channels = stream.getChannels() != null ? stream.getChannels() : 0;
}
}
}
@Ignore
public Song(String title, String albumName) {
this.id = UUID.randomUUID().toString();
this.title = title;
this.albumName = albumName;
}
@NonNull
public String getId() {
return id;
}
public String getTitle() {
return title;
}
public int getTrackNumber() {
return trackNumber;
}
public int getDiscNumber() {
return discNumber;
}
public int getYear() {
return year;
}
public long getDuration() {
return duration;
}
public String getAlbumId() {
return albumId;
}
public String getAlbumName() {
return albumName;
}
public String getArtistId() {
return artistId;
}
public String getArtistName() {
return artistName;
}
public String getPrimary() {
return primary;
}
public String getBlurHash() {
return blurHash;
}
public boolean isFavorite() {
return favorite;
}
public String getPath() {
return path;
}
public long getSize() {
return size;
}
public String getContainer() {
return container;
}
public String getCodec() {
return codec;
}
public int getSampleRate() {
return sampleRate;
}
public int getBitRate() {
return bitRate;
}
public int getBitDepth() {
return bitDepth;
}
public int getChannels() {
return channels;
}
public void setId(@NonNull String id) {
this.id = id;
}
public void setTitle(String title) {
this.title = title;
}
public void setTrackNumber(int trackNumber) {
this.trackNumber = trackNumber;
}
public void setDiscNumber(int discNumber) {
this.discNumber = discNumber;
}
public void setYear(int year) {
this.year = year;
}
public void setDuration(long duration) {
this.duration = duration;
}
public void setAlbumId(String albumId) {
this.albumId = albumId;
}
public void setAlbumName(String albumName) {
this.albumName = albumName;
}
public void setArtistId(String artistId) {
this.artistId = artistId;
}
public void setArtistName(String artistName) {
this.artistName = artistName;
}
public void setPrimary(String primary) {
this.primary = primary;
}
public void setBlurHash(String blurHash) {
this.blurHash = blurHash;
}
public void setFavorite(boolean favorite) {
this.favorite = favorite;
}
public void setPath(String path) {
this.path = path;
}
public void setSize(long size) {
this.size = size;
}
public void setContainer(String container) {
this.container = container;
}
public void setCodec(String codec) {
this.codec = codec;
}
public void setSampleRate(int sampleRate) {
this.sampleRate = sampleRate;
}
public void setBitRate(int bitRate) {
this.bitRate = bitRate;
}
public void setBitDepth(int bitDepth) {
this.bitDepth = bitDepth;
}
public void setChannels(int channels) {
this.channels = channels;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Song song = (Song) o;
return id.equals(song.id);
}
@Override
public int hashCode() {
return id.hashCode();
}
@Override
public String toString() {
return id;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.id);
dest.writeString(this.title);
dest.writeInt(this.trackNumber);
dest.writeInt(this.discNumber);
dest.writeInt(this.year);
dest.writeLong(this.duration);
dest.writeString(this.albumId);
dest.writeString(this.albumName);
dest.writeString(this.artistId);
dest.writeString(this.artistName);
dest.writeString(this.primary);
dest.writeString(Boolean.toString(favorite));
dest.writeString(this.blurHash);
dest.writeString(this.path);
dest.writeLong(this.size);
dest.writeString(this.container);
dest.writeString(this.codec);
dest.writeInt(this.sampleRate);
dest.writeInt(this.bitRate);
dest.writeInt(this.bitDepth);
dest.writeInt(this.channels);
}
protected Song(Parcel in) {
this.id = in.readString();
this.title = in.readString();
this.trackNumber = in.readInt();
this.discNumber = in.readInt();
this.year = in.readInt();
this.duration = in.readLong();
this.albumId = in.readString();
this.albumName = in.readString();
this.artistId = in.readString();
this.artistName = in.readString();
this.primary = in.readString();
this.favorite = Boolean.parseBoolean(in.readString());
this.blurHash = in.readString();
this.path = in.readString();
this.size = in.readLong();
this.container = in.readString();
this.codec = in.readString();
this.sampleRate = in.readInt();
this.bitRate = in.readInt();
this.bitDepth = in.readInt();
this.channels = in.readInt();
}
public static final Creator<Song> CREATOR = new Creator<Song>() {
public Song createFromParcel(Parcel source) {
return new Song(source);
}
public Song[] newArray(int size) {
return new Song[size];
}
};
}

View File

@@ -0,0 +1,127 @@
package com.cappielloantonio.play.repository;
import android.app.Application;
import androidx.lifecycle.LiveData;
import com.cappielloantonio.play.database.AppDatabase;
import com.cappielloantonio.play.database.dao.AlbumDao;
import com.cappielloantonio.play.model.Album;
import java.util.ArrayList;
import java.util.List;
public class AlbumRepository {
private AlbumDao albumDao;
private LiveData<List<Album>> listLiveAlbums;
public AlbumRepository(Application application) {
AppDatabase database = AppDatabase.getInstance(application);
albumDao = database.albumDao();
listLiveAlbums = albumDao.getAll();
}
public LiveData<List<Album>> getListLiveAlbums() {
return listLiveAlbums;
}
public boolean exist(Album album) {
boolean exist = false;
ExistThreadSafe existThread = new ExistThreadSafe(albumDao, album);
Thread thread = new Thread(existThread);
thread.start();
try {
thread.join();
exist = existThread.exist();
} catch (InterruptedException e) {
e.printStackTrace();
}
return exist;
}
public void insert(Album album) {
InsertThreadSafe insert = new InsertThreadSafe(albumDao, album);
Thread thread = new Thread(insert);
thread.start();
}
public void insertAll(ArrayList<Album> albums) {
InsertAllThreadSafe insertAll = new InsertAllThreadSafe(albumDao, albums);
Thread thread = new Thread(insertAll);
thread.start();
}
public void delete(Album album) {
DeleteThreadSafe delete = new DeleteThreadSafe(albumDao, album);
Thread thread = new Thread(delete);
thread.start();
}
private static class ExistThreadSafe implements Runnable {
private AlbumDao albumDao;
private Album album;
private boolean exist = false;
public ExistThreadSafe(AlbumDao albumDao, Album album) {
this.albumDao = albumDao;
this.album = album;
}
@Override
public void run() {
exist = albumDao.exist(album.getId());
}
public boolean exist() {
return exist;
}
}
private static class InsertThreadSafe implements Runnable {
private AlbumDao albumDao;
private Album album;
public InsertThreadSafe(AlbumDao albumDao, Album album) {
this.albumDao = albumDao;
this.album = album;
}
@Override
public void run() {
albumDao.insert(album);
}
}
private static class InsertAllThreadSafe implements Runnable {
private AlbumDao albumDao;
private ArrayList<Album> albums;
public InsertAllThreadSafe(AlbumDao albumDao, ArrayList<Album> albums) {
this.albumDao = albumDao;
this.albums = albums;
}
@Override
public void run() {
albumDao.insertAll(albums);
}
}
private static class DeleteThreadSafe implements Runnable {
private AlbumDao albumDao;
private Album album;
public DeleteThreadSafe(AlbumDao albumDao, Album album) {
this.albumDao = albumDao;
this.album = album;
}
@Override
public void run() {
albumDao.delete(album);
}
}
}

View File

@@ -0,0 +1,128 @@
package com.cappielloantonio.play.repository;
import android.app.Application;
import androidx.lifecycle.LiveData;
import com.cappielloantonio.play.database.AppDatabase;
import com.cappielloantonio.play.database.dao.ArtistDao;
import com.cappielloantonio.play.model.Artist;
import java.util.ArrayList;
import java.util.List;
public class ArtistRepository {
private ArtistDao artistDao;
private LiveData<List<Artist>> listLiveArtists;
public ArtistRepository(Application application) {
AppDatabase database = AppDatabase.getInstance(application);
artistDao = database.artistDao();
listLiveArtists = artistDao.getAll();
}
public LiveData<List<Artist>> getListLiveArtists() {
return listLiveArtists;
}
public boolean exist(Artist artist) {
boolean exist = false;
ExistThreadSafe existThread = new ExistThreadSafe(artistDao, artist);
Thread thread = new Thread(existThread);
thread.start();
try {
thread.join();
exist = existThread.exist();
}
catch (InterruptedException e) {
e.printStackTrace();
}
return exist;
}
public void insert(Artist artist) {
InsertThreadSafe insert = new InsertThreadSafe(artistDao, artist);
Thread thread = new Thread(insert);
thread.start();
}
public void insertAll(ArrayList<Artist> artists) {
InsertAllThreadSafe insertAll = new InsertAllThreadSafe(artistDao, artists);
Thread thread = new Thread(insertAll);
thread.start();
}
public void delete(Artist artist) {
DeleteThreadSafe delete = new DeleteThreadSafe(artistDao, artist);
Thread thread = new Thread(delete);
thread.start();
}
private static class ExistThreadSafe implements Runnable {
private ArtistDao artistDao;
private Artist artist;
private boolean exist = false;
public ExistThreadSafe(ArtistDao artistDao, Artist artist) {
this.artistDao = artistDao;
this.artist = artist;
}
@Override
public void run() {
exist = artistDao.exist(artist.getId());
}
public boolean exist() {
return exist;
}
}
private static class InsertThreadSafe implements Runnable {
private ArtistDao artistDao;
private Artist artist;
public InsertThreadSafe(ArtistDao artistDao, Artist artist) {
this.artistDao = artistDao;
this.artist = artist;
}
@Override
public void run() {
artistDao.insert(artist);
}
}
private static class InsertAllThreadSafe implements Runnable {
private ArtistDao artistDao;
private ArrayList<Artist> artists;
public InsertAllThreadSafe(ArtistDao artistDao, ArrayList<Artist> artists) {
this.artistDao = artistDao;
this.artists = artists;
}
@Override
public void run() {
artistDao.insertAll(artists);
}
}
private static class DeleteThreadSafe implements Runnable {
private ArtistDao artistDao;
private Artist artist;
public DeleteThreadSafe(ArtistDao artistDao, Artist artist) {
this.artistDao = artistDao;
this.artist = artist;
}
@Override
public void run() {
artistDao.delete(artist);
}
}
}

View File

@@ -0,0 +1,128 @@
package com.cappielloantonio.play.repository;
import android.app.Application;
import androidx.lifecycle.LiveData;
import com.cappielloantonio.play.database.AppDatabase;
import com.cappielloantonio.play.database.dao.GenreDao;
import com.cappielloantonio.play.model.Genre;
import java.util.ArrayList;
import java.util.List;
public class GenreRepository {
private GenreDao genreDao;
private LiveData<List<Genre>> listLiveGenres;
public GenreRepository(Application application) {
AppDatabase database = AppDatabase.getInstance(application);
genreDao = database.genreDao();
listLiveGenres = genreDao.getAll();
}
public LiveData<List<Genre>> getListLiveGenres() {
return listLiveGenres;
}
public boolean exist(Genre genre) {
boolean exist = false;
ExistThreadSafe existThread = new ExistThreadSafe(genreDao, genre);
Thread thread = new Thread(existThread);
thread.start();
try {
thread.join();
exist = existThread.exist();
}
catch (InterruptedException e) {
e.printStackTrace();
}
return exist;
}
public void insert(Genre genre) {
InsertThreadSafe insert = new InsertThreadSafe(genreDao, genre);
Thread thread = new Thread(insert);
thread.start();
}
public void insertAll(ArrayList<Genre> genres) {
InsertAllThreadSafe insertAll = new InsertAllThreadSafe(genreDao, genres);
Thread thread = new Thread(insertAll);
thread.start();
}
public void delete(Genre genre) {
DeleteThreadSafe delete = new DeleteThreadSafe(genreDao, genre);
Thread thread = new Thread(delete);
thread.start();
}
private static class ExistThreadSafe implements Runnable {
private GenreDao genreDao;
private Genre genre;
private boolean exist = false;
public ExistThreadSafe(GenreDao genreDao, Genre genre) {
this.genreDao = genreDao;
this.genre = genre;
}
@Override
public void run() {
exist = genreDao.exist(genre.getId());
}
public boolean exist() {
return exist;
}
}
private static class InsertThreadSafe implements Runnable {
private GenreDao genreDao;
private Genre genre;
public InsertThreadSafe(GenreDao genreDao, Genre genre) {
this.genreDao = genreDao;
this.genre = genre;
}
@Override
public void run() {
genreDao.insert(genre);
}
}
private static class InsertAllThreadSafe implements Runnable {
private GenreDao genreDao;
private ArrayList<Genre> genres;
public InsertAllThreadSafe(GenreDao genreDao, ArrayList<Genre> genres) {
this.genreDao = genreDao;
this.genres = genres;
}
@Override
public void run() {
genreDao.insertAll(genres);
}
}
private static class DeleteThreadSafe implements Runnable {
private GenreDao genreDao;
private Genre genre;
public DeleteThreadSafe(GenreDao genreDao, Genre genre) {
this.genreDao = genreDao;
this.genre = genre;
}
@Override
public void run() {
genreDao.delete(genre);
}
}
}

View File

@@ -0,0 +1,128 @@
package com.cappielloantonio.play.repository;
import android.app.Application;
import androidx.lifecycle.LiveData;
import com.cappielloantonio.play.database.AppDatabase;
import com.cappielloantonio.play.database.dao.PlaylistDao;
import com.cappielloantonio.play.model.Playlist;
import java.util.ArrayList;
import java.util.List;
public class PlaylistRepository {
private PlaylistDao playlistDao;
private LiveData<List<Playlist>> listLivePlaylists;
public PlaylistRepository(Application application) {
AppDatabase database = AppDatabase.getInstance(application);
playlistDao = database.playlistDao();
listLivePlaylists = playlistDao.getAll();
}
public LiveData<List<Playlist>> getListLivePlaylists() {
return listLivePlaylists;
}
public boolean exist(Playlist playlist) {
boolean exist = false;
ExistThreadSafe existThread = new ExistThreadSafe(playlistDao, playlist);
Thread thread = new Thread(existThread);
thread.start();
try {
thread.join();
exist = existThread.exist();
}
catch (InterruptedException e) {
e.printStackTrace();
}
return exist;
}
public void insert(Playlist playlist) {
InsertThreadSafe insert = new InsertThreadSafe(playlistDao, playlist);
Thread thread = new Thread(insert);
thread.start();
}
public void insertAll(ArrayList<Playlist> playlists) {
InsertAllThreadSafe insertAll = new InsertAllThreadSafe(playlistDao, playlists);
Thread thread = new Thread(insertAll);
thread.start();
}
public void delete(Playlist playlist) {
DeleteThreadSafe delete = new DeleteThreadSafe(playlistDao, playlist);
Thread thread = new Thread(delete);
thread.start();
}
private static class ExistThreadSafe implements Runnable {
private PlaylistDao playlistDao;
private Playlist playlist;
private boolean exist = false;
public ExistThreadSafe(PlaylistDao playlistDao, Playlist playlist) {
this.playlistDao = playlistDao;
this.playlist = playlist;
}
@Override
public void run() {
exist = playlistDao.exist(playlist.getId());
}
public boolean exist() {
return exist;
}
}
private static class InsertThreadSafe implements Runnable {
private PlaylistDao playlistDao;
private Playlist playlist;
public InsertThreadSafe(PlaylistDao playlistDao, Playlist playlist) {
this.playlistDao = playlistDao;
this.playlist = playlist;
}
@Override
public void run() {
playlistDao.insert(playlist);
}
}
private static class InsertAllThreadSafe implements Runnable {
private PlaylistDao playlistDao;
private ArrayList<Playlist> playlists;
public InsertAllThreadSafe(PlaylistDao playlistDao, ArrayList<Playlist> playlists) {
this.playlistDao = playlistDao;
this.playlists = playlists;
}
@Override
public void run() {
playlistDao.insertAll(playlists);
}
}
private static class DeleteThreadSafe implements Runnable {
private PlaylistDao playlistDao;
private Playlist playlist;
public DeleteThreadSafe(PlaylistDao playlistDao, Playlist playlist) {
this.playlistDao = playlistDao;
this.playlist = playlist;
}
@Override
public void run() {
playlistDao.delete(playlist);
}
}
}

View File

@@ -0,0 +1,128 @@
package com.cappielloantonio.play.repository;
import android.app.Application;
import androidx.lifecycle.LiveData;
import com.cappielloantonio.play.database.AppDatabase;
import com.cappielloantonio.play.database.dao.SongDao;
import com.cappielloantonio.play.model.Song;
import java.util.ArrayList;
import java.util.List;
public class SongRepository {
private SongDao songDao;
private LiveData<List<Song>> listLiveSongs;
public SongRepository(Application application) {
AppDatabase database = AppDatabase.getInstance(application);
songDao = database.songDao();
listLiveSongs = songDao.getAll();
}
public LiveData<List<Song>> getListLiveSongs() {
return listLiveSongs;
}
public boolean exist(Song song) {
boolean exist = false;
ExistThreadSafe existThread = new ExistThreadSafe(songDao, song);
Thread thread = new Thread(existThread);
thread.start();
try {
thread.join();
exist = existThread.exist();
}
catch (InterruptedException e) {
e.printStackTrace();
}
return exist;
}
public void insert(Song song) {
InsertThreadSafe insert = new InsertThreadSafe(songDao, song);
Thread thread = new Thread(insert);
thread.start();
}
public void insertAll(ArrayList<Song> songs) {
InsertAllThreadSafe insertAll = new InsertAllThreadSafe(songDao, songs);
Thread thread = new Thread(insertAll);
thread.start();
}
public void delete(Song song) {
DeleteThreadSafe delete = new DeleteThreadSafe(songDao, song);
Thread thread = new Thread(delete);
thread.start();
}
private static class ExistThreadSafe implements Runnable {
private SongDao songDao;
private Song song;
private boolean exist = false;
public ExistThreadSafe(SongDao songDao, Song song) {
this.songDao = songDao;
this.song = song;
}
@Override
public void run() {
exist = songDao.exist(song.getId());
}
public boolean exist() {
return exist;
}
}
private static class InsertThreadSafe implements Runnable {
private SongDao songDao;
private Song song;
public InsertThreadSafe(SongDao songDao, Song song) {
this.songDao = songDao;
this.song = song;
}
@Override
public void run() {
songDao.insert(song);
}
}
private static class InsertAllThreadSafe implements Runnable {
private SongDao songDao;
private ArrayList<Song> songs;
public InsertAllThreadSafe(SongDao songDao, ArrayList<Song> songs) {
this.songDao = songDao;
this.songs = songs;
}
@Override
public void run() {
songDao.insertAll(songs);
}
}
private static class DeleteThreadSafe implements Runnable {
private SongDao songDao;
private Song song;
public DeleteThreadSafe(SongDao songDao, Song song) {
this.songDao = songDao;
this.song = song;
}
@Override
public void run() {
songDao.delete(song);
}
}
}

View File

@@ -0,0 +1,117 @@
package com.cappielloantonio.play.ui.activities;
import android.os.Bundle;
import android.view.View;
import androidx.fragment.app.FragmentManager;
import androidx.navigation.NavController;
import androidx.navigation.fragment.NavHostFragment;
import androidx.navigation.ui.NavigationUI;
import com.cappielloantonio.play.App;
import com.cappielloantonio.play.R;
import com.cappielloantonio.play.databinding.ActivityMainBinding;
import com.cappielloantonio.play.ui.activities.base.BaseActivity;
import com.cappielloantonio.play.util.PreferenceUtil;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import org.jellyfin.apiclient.interaction.EmptyResponse;
import org.jellyfin.apiclient.interaction.Response;
import org.jellyfin.apiclient.model.session.ClientCapabilities;
import org.jellyfin.apiclient.model.system.SystemInfo;
import java.util.Objects;
public class MainActivity extends BaseActivity {
private static final String TAG = "MainActivity";
private ActivityMainBinding activityMainBinding;
private FragmentManager fragmentManager;
private NavHostFragment navHostFragment;
private BottomNavigationView bottomNavigationView;
public NavController navController;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
activityMainBinding = ActivityMainBinding.inflate(getLayoutInflater());
View view = activityMainBinding.getRoot();
setContentView(view);
init();
}
public void init() {
fragmentManager = getSupportFragmentManager();
bottomNavigationView = findViewById(R.id.bottom_navigation);
navHostFragment = (NavHostFragment) fragmentManager.findFragmentById(R.id.nav_host_fragment);
navController = navHostFragment.getNavController();
NavigationUI.setupWithNavController(bottomNavigationView, navController);
if (PreferenceUtil.getInstance(this).getToken() != null) {
checkPreviousSession();
} else {
goToLogin();
}
}
private void checkPreviousSession() {
App.getApiClientInstance(getApplicationContext()).ChangeServerLocation(PreferenceUtil.getInstance(this).getServer());
App.getApiClientInstance(getApplicationContext()).SetAuthenticationInfo(PreferenceUtil.getInstance(this).getToken(), PreferenceUtil.getInstance(this).getUser());
App.getApiClientInstance(getApplicationContext()).GetSystemInfoAsync(new Response<SystemInfo>() {
@Override
public void onResponse(SystemInfo result) {
ClientCapabilities clientCapabilities = new ClientCapabilities();
clientCapabilities.setSupportsMediaControl(true);
clientCapabilities.setSupportsPersistentIdentifier(true);
App.getApiClientInstance(getApplicationContext()).ensureWebSocket();
App.getApiClientInstance(getApplicationContext()).ReportCapabilities(clientCapabilities, new EmptyResponse());
goFromLogin();
}
@Override
public void onError(Exception exception) {
goToLogin();
}
});
}
public void goToLogin() {
if (Objects.requireNonNull(navController.getCurrentDestination()).getId() == R.id.landingFragment)
navController.navigate(R.id.action_landingFragment_to_loginFragment);
}
public void goToSync() {
if (Objects.requireNonNull(navController.getCurrentDestination()).getId() == R.id.landingFragment) {
navController.navigate(R.id.action_landingFragment_to_syncFragment);
} else if (Objects.requireNonNull(navController.getCurrentDestination()).getId() == R.id.loginFragment) {
navController.navigate(R.id.action_loginFragment_to_syncFragment);
} else if (Objects.requireNonNull(navController.getCurrentDestination()).getId() == R.id.homeFragment) {
navController.navigate(R.id.action_homeFragment_to_syncFragment);
}
}
public void goToHome() {
bottomNavigationView.setVisibility(View.VISIBLE);
if (Objects.requireNonNull(navController.getCurrentDestination()).getId() == R.id.landingFragment) {
navController.navigate(R.id.action_landingFragment_to_homeFragment);
} else if (Objects.requireNonNull(navController.getCurrentDestination()).getId() == R.id.syncFragment) {
navController.navigate(R.id.action_syncFragment_to_homeFragment);
} else if (Objects.requireNonNull(navController.getCurrentDestination()).getId() == R.id.loginFragment) {
navController.navigate(R.id.action_loginFragment_to_homeFragment);
}
}
public void goFromLogin() {
if (PreferenceUtil.getInstance(getApplicationContext()).getSync()) {
goToHome();
} else {
goToSync();
}
}
}

View File

@@ -0,0 +1,83 @@
package com.cappielloantonio.play.ui.activities.base;
import android.Manifest;
import android.content.Intent;
import android.os.PowerManager;
import android.provider.Settings;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import com.cappielloantonio.play.R;
import java.util.List;
import pub.devrel.easypermissions.AppSettingsDialog;
import pub.devrel.easypermissions.EasyPermissions;
public class BaseActivity extends AppCompatActivity implements EasyPermissions.PermissionCallbacks {
public static final int REQUEST_PERM_ACCESS = 1;
@Override
protected void onResume() {
super.onResume();
checkPermissions();
// checkBatteryOptimization();
}
private void checkBatteryOptimization() {
if (detectBatteryOptimization()) {
showBatteryOptimizationDialog();
}
}
private boolean detectBatteryOptimization() {
String packageName = getPackageName();
PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
return !powerManager.isIgnoringBatteryOptimizations(packageName);
}
private void showBatteryOptimizationDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(R.string.battery_optimizations_message)
.setTitle(R.string.battery_optimizations_title)
.setNegativeButton(R.string.ignore, null)
.setPositiveButton(R.string.disable, (dialog, id) -> openPowerSettings())
.show();
}
private void openPowerSettings() {
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
startActivity(intent);
}
private void checkPermissions() {
String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE};
if (!EasyPermissions.hasPermissions(this, permissions)) {
EasyPermissions.requestPermissions(this, getString(R.string.storage_permission_rationale), REQUEST_PERM_ACCESS, permissions);
}
}
@Override
public void onPermissionsGranted(int requestCode, @NonNull List<String> perms) {
}
@Override
public void onPermissionsDenied(int requestCode, @NonNull List<String> perms) {
if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) {
new AppSettingsDialog.Builder(this).build().show();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
}
}

View File

@@ -0,0 +1,91 @@
package com.cappielloantonio.play.ui.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.play.adapter.DiscoverSongAdapter;
import com.cappielloantonio.play.adapter.RecentMusicAdapter;
import com.cappielloantonio.play.databinding.FragmentHomeBinding;
import com.cappielloantonio.play.ui.activities.MainActivity;
import com.cappielloantonio.play.util.PreferenceUtil;
import com.cappielloantonio.play.viewmodel.HomeViewModel;
public class HomeFragment extends Fragment implements RecentMusicAdapter.ItemClickListener {
private static final String TAG = "CategoriesFragment";
private FragmentHomeBinding bind;
private MainActivity activity;
private HomeViewModel homeViewModel;
private DiscoverSongAdapter discoverSongAdapter;
private RecentMusicAdapter recentMusicAdapter;
private RecentMusicAdapter mostPlayedMusicAdapter;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentHomeBinding.inflate(inflater, container, false);
View view = bind.getRoot();
homeViewModel = new ViewModelProvider(requireActivity()).get(HomeViewModel.class);
init();
initDiscoverSongSlideView();
initRecentPlayedSongView();
initMostPlayedSongView();
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void init() {
bind.resyncButton.setOnClickListener(v -> {
PreferenceUtil.getInstance(requireContext()).setSync(false);
activity.goToSync();
});
}
private void initDiscoverSongSlideView() {
discoverSongAdapter = new DiscoverSongAdapter(requireContext(), homeViewModel.getDiscoverSongList());
bind.discoverSongViewPager.setAdapter(discoverSongAdapter);
bind.discoverSongViewPager.setPageMargin(20);
}
private void initRecentPlayedSongView() {
bind.recentlyPlayedTracksRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.recentlyPlayedTracksRecyclerView.setHasFixedSize(true);
recentMusicAdapter = new RecentMusicAdapter(requireContext(), homeViewModel.getRecentSongList());
recentMusicAdapter.setClickListener(this);
bind.recentlyPlayedTracksRecyclerView.setAdapter(recentMusicAdapter);
}
private void initMostPlayedSongView() {
bind.mostPlayedTracksRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.mostPlayedTracksRecyclerView.setHasFixedSize(true);
mostPlayedMusicAdapter = new RecentMusicAdapter(requireContext(), homeViewModel.getMostPlayedSongList());
mostPlayedMusicAdapter.setClickListener(this);
bind.mostPlayedTracksRecyclerView.setAdapter(mostPlayedMusicAdapter);
}
@Override
public void onItemClick(View view, int position) {
Toast.makeText(requireContext(), "Click: " + position, Toast.LENGTH_SHORT).show();
}
}

View File

@@ -0,0 +1,17 @@
package com.cappielloantonio.play.ui.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.fragment.app.Fragment;
import com.cappielloantonio.play.R;
public class LandingFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_landing, container, false);
}
}

View File

@@ -0,0 +1,101 @@
package com.cappielloantonio.play.ui.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.play.adapter.AlbumAdapter;
import com.cappielloantonio.play.adapter.ArtistAdapter;
import com.cappielloantonio.play.adapter.GenreAdapter;
import com.cappielloantonio.play.adapter.PlaylistAdapter;
import com.cappielloantonio.play.databinding.FragmentLibraryBinding;
import com.cappielloantonio.play.model.Genre;
import com.cappielloantonio.play.model.Playlist;
import com.cappielloantonio.play.ui.activities.MainActivity;
import com.cappielloantonio.play.viewmodel.LibraryViewModel;
import java.util.ArrayList;
import java.util.List;
public class LibraryFragment extends Fragment {
private static final String TAG = "LibraryFragment";
private FragmentLibraryBinding bind;
private MainActivity activity;
private LibraryViewModel libraryViewModel;
private AlbumAdapter albumAdapter;
private ArtistAdapter artistAdapter;
private GenreAdapter genreAdapter;
private PlaylistAdapter playlistAdapter;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentLibraryBinding.inflate(inflater, container, false);
View view = bind.getRoot();
libraryViewModel = new ViewModelProvider(requireActivity()).get(LibraryViewModel.class);
initAlbumView();
initArtistView();
initGenreView();
initPlaylistView();
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void initAlbumView() {
bind.albumRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.albumRecyclerView.setHasFixedSize(true);
albumAdapter = new AlbumAdapter(requireContext(), libraryViewModel.getAlbumSample());
albumAdapter.setClickListener((view, position) -> Toast.makeText(requireContext(), "Album: " + position, Toast.LENGTH_SHORT).show());
bind.albumRecyclerView.setAdapter(albumAdapter);
}
private void initArtistView() {
bind.artistRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.artistRecyclerView.setHasFixedSize(true);
artistAdapter = new ArtistAdapter(requireContext(), libraryViewModel.getArtistSample());
artistAdapter.setClickListener((view, position) -> Toast.makeText(requireContext(), "Artist: " + position, Toast.LENGTH_SHORT).show());
bind.artistRecyclerView.setAdapter(artistAdapter);
}
private void initGenreView() {
bind.genreRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 3, GridLayoutManager.HORIZONTAL, false));
bind.genreRecyclerView.setHasFixedSize(true);
genreAdapter = new GenreAdapter(requireContext(), new ArrayList<>());
genreAdapter.setClickListener((view, position) -> Toast.makeText(requireContext(), "Genre: " + position, Toast.LENGTH_SHORT).show());
bind.genreRecyclerView.setAdapter(genreAdapter);
libraryViewModel.getGenreList().observe(requireActivity(), genres -> genreAdapter.setItems(genres));
}
private void initPlaylistView() {
bind.playlistRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 2));
bind.playlistRecyclerView.setHasFixedSize(true);
playlistAdapter = new PlaylistAdapter(requireContext(), libraryViewModel.getPlaylist());
playlistAdapter.setClickListener((view, position) -> Toast.makeText(requireContext(), "Playlist: " + position, Toast.LENGTH_SHORT).show());
bind.playlistRecyclerView.setAdapter(playlistAdapter);
}
}

View File

@@ -0,0 +1,123 @@
package com.cappielloantonio.play.ui.fragment;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.cappielloantonio.play.App;
import com.cappielloantonio.play.databinding.FragmentLoginBinding;
import com.cappielloantonio.play.ui.activities.MainActivity;
import com.cappielloantonio.play.util.PreferenceUtil;
import org.jellyfin.apiclient.interaction.Response;
import org.jellyfin.apiclient.model.system.SystemInfo;
import org.jellyfin.apiclient.model.users.AuthenticationResult;
public class LoginFragment extends Fragment {
private static final String TAG = "LoginFragment";
private FragmentLoginBinding bind;
private MainActivity activity;
// private TextView usernameTextView;
// private TextView passwordTextView;
// private TextView serverTextView;
private String username;
private String password;
private String server;
// private Button loginButton;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentLoginBinding.inflate(inflater, container, false);
View view = bind.getRoot();
init();
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void init() {
bind.loginButton.setOnClickListener(v -> {
if (validateInput()) {
saveServerPreference(username, server);
authenticate();
}
});
}
private boolean validateInput() {
username = bind.usernameTextView.getText().toString().trim();
password = bind.passwordTextView.getText().toString().trim();
server = bind.serverTextView.getText().toString().trim();
if (TextUtils.isEmpty(username)) {
Toast.makeText(requireContext(), "Empty username", Toast.LENGTH_SHORT).show();
return false;
}
if (TextUtils.isEmpty(server)) {
Toast.makeText(requireContext(), "Empty server", Toast.LENGTH_SHORT).show();
return false;
}
return true;
}
private void saveServerPreference(String user, String server) {
PreferenceUtil.getInstance(requireContext()).setUser(user);
PreferenceUtil.getInstance(requireContext()).setServer(server);
}
private void authenticate() {
App.getApiClientInstance(requireContext()).ChangeServerLocation(server);
App.getApiClientInstance(requireContext()).AuthenticateUserAsync(username, password, new Response<AuthenticationResult>() {
@Override
public void onResponse(AuthenticationResult result) {
if (result.getAccessToken() == null) return;
enter(result.getUser().getId(), result.getAccessToken());
}
@Override
public void onError(Exception exception) {
if (exception.getMessage().contains("AuthFailureError")) {
Toast.makeText(requireContext(), "Fail to authenticate", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(requireContext(), "Server unreachable", Toast.LENGTH_SHORT).show();
}
}
});
}
private void enter(String user, String token) {
App.getApiClientInstance(requireContext()).GetSystemInfoAsync(new Response<SystemInfo>() {
@Override
public void onResponse(SystemInfo result) {
if (result.getVersion().charAt(0) == '1') {
PreferenceUtil.getInstance(requireContext()).setUser(user);
PreferenceUtil.getInstance(requireContext()).setToken(token);
activity.goFromLogin();
} else {
Toast.makeText(requireContext(), "Error version", Toast.LENGTH_SHORT).show();
}
}
});
}
}

View File

@@ -0,0 +1,118 @@
package com.cappielloantonio.play.ui.fragment;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.cappielloantonio.play.databinding.FragmentSearchBinding;
import com.cappielloantonio.play.ui.activities.MainActivity;
import com.paulrybitskyi.persistentsearchview.utils.VoiceRecognitionDelegate;
public class SearchFragment extends Fragment {
private static final String TAG = "SearchFragment";
public static final int REQUEST_CODE = 64545;
private FragmentSearchBinding bind;
private MainActivity activity;
protected LinearLayout emptyLinearLayout;
protected String query = "";
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentSearchBinding.inflate(inflater, container, false);
View view = bind.getRoot();
searchInit();
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
@Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) {
String code = data.getStringExtra("result");
search(code);
}
}
VoiceRecognitionDelegate.handleResult(bind.persistentSearchView, requestCode, resultCode, data);
}
private void searchInit() {
bind.persistentSearchView.showRightButton();
bind.persistentSearchView.setOnSearchQueryChangeListener((searchView, oldQuery, newQuery) -> {
});
bind.persistentSearchView.setOnLeftBtnClickListener(view -> {
});
bind.persistentSearchView.setOnRightBtnClickListener(view -> {
});
bind.persistentSearchView.setVoiceRecognitionDelegate(new VoiceRecognitionDelegate(this));
bind.persistentSearchView.setOnSearchConfirmedListener((searchView, query) -> {
if (!query.equals("")) {
searchView.collapse();
search(query);
}
});
bind.persistentSearchView.setSuggestionsDisabled(true);
}
public void search(String query) {
emptyScreen();
this.query = query;
bind.persistentSearchView.setInputQuery(query);
performSearch(query);
}
private void performSearch(String query) {
}
private void loadMoreItemSearch(String query, int page) {
manageProgressBar(true);
}
private void emptyScreen() {
emptyLinearLayout.setVisibility(View.GONE);
}
private void manageProgressBar(boolean show) {
if (show) {
bind.persistentSearchView.hideLeftButton();
bind.persistentSearchView.showProgressBar(true);
} else {
bind.persistentSearchView.showLeftButton();
bind.persistentSearchView.hideProgressBar(true);
}
}
}

View File

@@ -0,0 +1,28 @@
package com.cappielloantonio.play.ui.fragment;
import android.os.Bundle;
import androidx.preference.ListPreference;
import androidx.preference.PreferenceFragmentCompat;
import com.cappielloantonio.play.R;
import com.cappielloantonio.play.helper.ThemeHelper;
public class SettingsFragment extends PreferenceFragmentCompat {
private static final String TAG = "SettingsFragment";
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.global_preferences, rootKey);
ListPreference themePreference = findPreference("themePref");
if (themePreference != null) {
themePreference.setOnPreferenceChangeListener(
(preference, newValue) -> {
String themeOption = (String) newValue;
ThemeHelper.applyTheme(themeOption);
return true;
});
}
}
}

View File

@@ -0,0 +1,206 @@
package com.cappielloantonio.play.ui.fragment;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.cappielloantonio.play.databinding.FragmentSyncBinding;
import com.cappielloantonio.play.interfaces.MediaCallback;
import com.cappielloantonio.play.model.Album;
import com.cappielloantonio.play.model.Artist;
import com.cappielloantonio.play.model.Genre;
import com.cappielloantonio.play.model.Playlist;
import com.cappielloantonio.play.model.Song;
import com.cappielloantonio.play.repository.AlbumRepository;
import com.cappielloantonio.play.repository.ArtistRepository;
import com.cappielloantonio.play.repository.GenreRepository;
import com.cappielloantonio.play.repository.PlaylistRepository;
import com.cappielloantonio.play.repository.SongRepository;
import com.cappielloantonio.play.ui.activities.MainActivity;
import com.cappielloantonio.play.util.PreferenceUtil;
import com.cappielloantonio.play.util.SyncUtil;
import org.jellyfin.apiclient.model.dto.BaseItemDto;
import java.util.ArrayList;
import java.util.List;
public class SyncFragment extends Fragment {
private static final String TAG = "SyncFragment";
private MainActivity activity;
private FragmentSyncBinding bind;
private ArrayList<Integer> progressing;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentSyncBinding.inflate(inflater, container, false);
View view = bind.getRoot();
syncLibraries();
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void showProgressBar() {
bind.loadingProgressBar.setVisibility(View.VISIBLE);
}
private void syncLibraries() {
progressing = new ArrayList<>();
SyncUtil.getLibraries(requireContext(), new MediaCallback() {
@Override
public void onError(Exception exception) {
Log.e(TAG, "onError: " + exception.getMessage());
}
@Override
public void onLoadMedia(List<?> media) {
List<BaseItemDto> libraries = (List<BaseItemDto>) media;
for (BaseItemDto itemDto : libraries) {
if (itemDto.getCollectionType().equals("music"))
SyncUtil.musicLibrary = itemDto;
}
startSyncing();
}
});
}
private void startSyncing() {
showProgressBar();
syncAlbums();
syncArtists();
syncGenres();
syncPlaylist();
syncSongs();
}
private void syncAlbums() {
SyncUtil.getAlbums(requireContext(), new MediaCallback() {
@Override
public void onError(Exception exception) {
Log.e(TAG, "onError: " + exception.getMessage());
setProgress(false);
}
@Override
public void onLoadMedia(List<?> media) {
AlbumRepository repository = new AlbumRepository(activity.getApplication());
repository.insertAll((ArrayList<Album>) media);
setProgress(true);
}
});
}
private void syncArtists() {
SyncUtil.getArtists(requireContext(), new MediaCallback() {
@Override
public void onError(Exception exception) {
Log.e(TAG, "onError: " + exception.getMessage());
setProgress(false);
}
@Override
public void onLoadMedia(List<?> media) {
ArtistRepository repository = new ArtistRepository(activity.getApplication());
repository.insertAll((ArrayList<Artist>) media);
setProgress(true);
}
});
}
private void syncGenres() {
SyncUtil.getGenres(requireContext(), new MediaCallback() {
@Override
public void onError(Exception exception) {
Log.e(TAG, "onError: " + exception.getMessage());
setProgress(false);
}
@Override
public void onLoadMedia(List<?> media) {
GenreRepository repository = new GenreRepository(activity.getApplication());
repository.insertAll((ArrayList<Genre>) media);
setProgress(true);
}
});
}
private void syncPlaylist() {
SyncUtil.getPlaylists(requireContext(), new MediaCallback() {
@Override
public void onError(Exception exception) {
Log.e(TAG, "onError: " + exception.getMessage());
setProgress(false);
}
@Override
public void onLoadMedia(List<?> media) {
PlaylistRepository repository = new PlaylistRepository(activity.getApplication());
repository.insertAll((ArrayList<Playlist>) media);
setProgress(true);
}
});
}
private void syncSongs() {
SyncUtil.getSongs(requireContext(), new MediaCallback() {
@Override
public void onError(Exception exception) {
Log.e(TAG, "onError: " + exception.getMessage());
setProgress(false);
}
@Override
public void onLoadMedia(List<?> media) {
SongRepository repository = new SongRepository(activity.getApplication());
repository.insertAll((ArrayList<Song>) media);
setProgress(true);
}
});
}
private void setProgress(boolean step) {
if (step) {
progressing.add(25);
bind.loadingProgressBar.setProgress(bind.loadingProgressBar.getProgress() + 25, true);
} else {
progressing.add(0);
}
countProgress();
}
private void countProgress() {
if (progressing.size() == 5) {
if (bind.loadingProgressBar.getProgress() == 100)
terminate();
else
Toast.makeText(requireContext(), "Sync error", Toast.LENGTH_SHORT).show();
}
}
private void terminate() {
PreferenceUtil.getInstance(requireContext()).setSync(true);
activity.goToHome();
}
}

View File

@@ -0,0 +1,85 @@
package com.cappielloantonio.play.util;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.preference.PreferenceManager;
import com.cappielloantonio.play.helper.ThemeHelper;
public class PreferenceUtil {
public static final String SERVER = "server";
public static final String USER = "user";
public static final String TOKEN = "token";
public static final String SYNC = "sync";
public static final String HOST_URL = "host";
public static final String IMAGE_CACHE_SIZE = "image_cache_size";
private static PreferenceUtil sInstance;
private final SharedPreferences mPreferences;
private PreferenceUtil(final Context context) {
mPreferences = PreferenceManager.getDefaultSharedPreferences(context);
}
public static PreferenceUtil getInstance(final Context context) {
if (sInstance == null) {
sInstance = new PreferenceUtil(context.getApplicationContext());
}
return sInstance;
}
public String getTheme() { return mPreferences.getString("themePref", ThemeHelper.DEFAULT_MODE ); }
public String getServer() {
return mPreferences.getString(SERVER, "https://jellyfin.org");
}
public void setServer(String server) {
final SharedPreferences.Editor editor = mPreferences.edit();
editor.putString(SERVER, server);
editor.apply();
}
public String getUser() {
return mPreferences.getString(USER, "");
}
public void setUser(String user) {
final SharedPreferences.Editor editor = mPreferences.edit();
editor.putString(USER, user);
editor.apply();
}
public String getToken() {
return mPreferences.getString(TOKEN, "");
}
public void setToken(String token) {
final SharedPreferences.Editor editor = mPreferences.edit();
editor.putString(TOKEN, token);
editor.apply();
}
public Boolean getSync() {
return mPreferences.getBoolean(SYNC, false);
}
public void setSync(Boolean sync) {
final SharedPreferences.Editor editor = mPreferences.edit();
editor.putBoolean(SYNC, sync);
editor.apply();
}
public final String getHostUrl() {
return mPreferences.getString(HOST_URL, "undefined");
}
public final int getImageCacheSize() {
return Integer.parseInt(mPreferences.getString(IMAGE_CACHE_SIZE, "400000000"));
}
}

View File

@@ -0,0 +1,177 @@
package com.cappielloantonio.play.util;
import android.content.Context;
import com.cappielloantonio.play.App;
import com.cappielloantonio.play.interfaces.MediaCallback;
import com.cappielloantonio.play.model.Album;
import com.cappielloantonio.play.model.Artist;
import com.cappielloantonio.play.model.Genre;
import com.cappielloantonio.play.model.Playlist;
import com.cappielloantonio.play.model.Song;
import org.jellyfin.apiclient.interaction.Response;
import org.jellyfin.apiclient.model.dto.BaseItemDto;
import org.jellyfin.apiclient.model.querying.ArtistsQuery;
import org.jellyfin.apiclient.model.querying.ItemFields;
import org.jellyfin.apiclient.model.querying.ItemQuery;
import org.jellyfin.apiclient.model.querying.ItemsByNameQuery;
import org.jellyfin.apiclient.model.querying.ItemsResult;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class SyncUtil {
public static BaseItemDto musicLibrary;
public static void getLibraries(Context context, MediaCallback callback) {
String id = App.getApiClientInstance(context).getCurrentUserId();
App.getApiClientInstance(context).GetUserViews(id, new Response<ItemsResult>() {
@Override
public void onResponse(ItemsResult result) {
List<BaseItemDto> libraries = new ArrayList<>();
libraries.addAll(Arrays.asList(result.getItems()));
callback.onLoadMedia(libraries);
}
@Override
public void onError(Exception exception) {
exception.printStackTrace();
}
});
}
public static void getSongs(Context context, MediaCallback callback) {
ItemQuery query = new ItemQuery();
query.setIncludeItemTypes(new String[]{"Audio"});
query.setFields(new ItemFields[]{ItemFields.MediaSources});
query.setUserId(App.getApiClientInstance(context).getCurrentUserId());
query.setRecursive(true);
query.setParentId(musicLibrary.getId());
App.getApiClientInstance(context).GetItemsAsync(query, new Response<ItemsResult>() {
@Override
public void onResponse(ItemsResult result) {
ArrayList<Song> songs = new ArrayList<>();
for (BaseItemDto itemDto : result.getItems()) {
songs.add(new Song(itemDto));
}
callback.onLoadMedia(songs);
}
@Override
public void onError(Exception exception) {
callback.onError(exception);
}
});
}
public static void getAlbums(Context context, MediaCallback callback) {
ItemQuery query = new ItemQuery();
query.setIncludeItemTypes(new String[]{"MusicAlbum"});
query.setUserId(App.getApiClientInstance(context).getCurrentUserId());
query.setRecursive(true);
query.setParentId(musicLibrary.getId());
App.getApiClientInstance(context).GetItemsAsync(query, new Response<ItemsResult>() {
@Override
public void onResponse(ItemsResult result) {
List<Album> albums = new ArrayList<>();
for (BaseItemDto itemDto : result.getItems()) {
albums.add(new Album(itemDto));
}
callback.onLoadMedia(albums);
}
@Override
public void onError(Exception exception) {
exception.printStackTrace();
}
});
}
public static void getArtists(Context context, MediaCallback callback) {
ArtistsQuery query = new ArtistsQuery();
query.setFields(new ItemFields[]{ItemFields.Genres});
query.setUserId(App.getApiClientInstance(context).getCurrentUserId());
query.setRecursive(true);
query.setParentId(musicLibrary.getId());
App.getApiClientInstance(context).GetAlbumArtistsAsync(query, new Response<ItemsResult>() {
@Override
public void onResponse(ItemsResult result) {
List<Artist> artists = new ArrayList<>();
for (BaseItemDto itemDto : result.getItems()) {
artists.add(new Artist(itemDto));
}
callback.onLoadMedia(artists);
}
@Override
public void onError(Exception exception) {
exception.printStackTrace();
}
});
}
public static void getPlaylists(Context context, MediaCallback callback) {
ItemQuery query = new ItemQuery();
query.setIncludeItemTypes(new String[]{"Playlist"});
query.setUserId(App.getApiClientInstance(context).getCurrentUserId());
query.setRecursive(true);
query.setParentId(musicLibrary.getId());
App.getApiClientInstance(context).GetItemsAsync(query, new Response<ItemsResult>() {
@Override
public void onResponse(ItemsResult result) {
List<Playlist> playlists = new ArrayList<>();
for (BaseItemDto itemDto : result.getItems()) {
playlists.add(new Playlist(itemDto));
}
callback.onLoadMedia(playlists);
}
@Override
public void onError(Exception exception) {
exception.printStackTrace();
}
});
}
public static void getGenres(Context context, MediaCallback callback) {
ItemsByNameQuery query = new ItemsByNameQuery();
query.setUserId(App.getApiClientInstance(context).getCurrentUserId());
query.setRecursive(true);
query.setParentId(musicLibrary.getId());
App.getApiClientInstance(context).GetGenresAsync(query, new Response<ItemsResult>() {
@Override
public void onResponse(ItemsResult result) {
List<Genre> genres = new ArrayList<>();
for (BaseItemDto itemDto : result.getItems()) {
genres.add(new Genre(itemDto));
}
callback.onLoadMedia(genres);
}
@Override
public void onError(Exception exception) {
exception.printStackTrace();
}
});
}
}

View File

@@ -0,0 +1,41 @@
package com.cappielloantonio.play.viewmodel;
import androidx.lifecycle.ViewModel;
import com.cappielloantonio.play.model.Song;
import java.util.ArrayList;
import java.util.List;
public class HomeViewModel extends ViewModel {
public List<Song> getDiscoverSongList() {
List<Song> discover_songs = new ArrayList<>();
discover_songs.add(new Song("Holiday", "American Idiot"));
discover_songs.add(new Song("Brioschi", "Stanza Singola"));
discover_songs.add(new Song("HappySad", "Ceri Singles"));
discover_songs.add(new Song("Falling back to Earth", "Haken"));
return discover_songs;
}
public List<Song> getRecentSongList() {
List<Song> recent_songs = new ArrayList<>();
recent_songs.add(new Song("Holiday", "American Idiot"));
recent_songs.add(new Song("Brioschi", "Stanza Singola"));
recent_songs.add(new Song("HappySad", "Ceri Singles"));
recent_songs.add(new Song("Falling back to Earth", "Haken"));
return recent_songs;
}
public List<Song> getMostPlayedSongList() {
List<Song> most_played_songs = new ArrayList<>();
most_played_songs.add(new Song("Holiday", "American Idiot"));
most_played_songs.add(new Song("Brioschi", "Stanza Singola"));
most_played_songs.add(new Song("HappySad", "Ceri Singles"));
most_played_songs.add(new Song("Falling back to Earth", "Haken"));
return most_played_songs;
}
}

View File

@@ -0,0 +1,77 @@
package com.cappielloantonio.play.viewmodel;
import android.app.Application;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import com.cappielloantonio.play.model.Album;
import com.cappielloantonio.play.model.Artist;
import com.cappielloantonio.play.model.Genre;
import com.cappielloantonio.play.model.Playlist;
import com.cappielloantonio.play.repository.GenreRepository;
import java.util.ArrayList;
import java.util.List;
public class LibraryViewModel extends AndroidViewModel {
private GenreRepository genreRepository;
private LiveData<List<Genre>> allGenres;
public LibraryViewModel(@NonNull Application application) {
super(application);
genreRepository = new GenreRepository(application);
allGenres = genreRepository.getListLiveGenres();
}
public LiveData<List<Genre>> getGenreList() {
return allGenres;
}
public ArrayList<Album> getAlbumSample() {
ArrayList<Album> albums = new ArrayList<>();
albums.add(new Album("1", "aaaa", 1, "1", "qqqq", "", ""));
albums.add(new Album("2", "ssss", 1, "2", "wwww", "", ""));
albums.add(new Album("3", "dddd", 1, "3", "eeee", "", ""));
albums.add(new Album("4", "ffff", 1, "4", "rrrr", "", ""));
albums.add(new Album("5", "gggg", 1, "5", "tttt", "", ""));
albums.add(new Album("6", "hhhh", 1, "6", "yyyy", "", ""));
albums.add(new Album("7", "jjjj", 1, "7", "uuuu", "", ""));
albums.add(new Album("8", "kkkk", 1, "8", "iiii", "", ""));
albums.add(new Album("9", "llll", 1, "9", "oooo", "", ""));
return albums;
}
public ArrayList<Artist> getArtistSample() {
ArrayList<Artist> artists = new ArrayList<>();
artists.add(new Artist("1", "dhgr", "", ""));
artists.add(new Artist("2", "kdnu", "", ""));
artists.add(new Artist("3", "wfty", "", ""));
artists.add(new Artist("4", "hfds", "", ""));
artists.add(new Artist("5", "jgab", "", ""));
artists.add(new Artist("6", "iudg", "", ""));
artists.add(new Artist("7", "istr", "", ""));
artists.add(new Artist("8", "dger", "", ""));
artists.add(new Artist("9", "jhjk", "", ""));
return artists;
}
public ArrayList<Playlist> getPlaylist() {
ArrayList<Playlist> playlists = new ArrayList<>();
playlists.add(new Playlist("1", "sdad", "", ""));
playlists.add(new Playlist("2", "rwef", "", ""));
playlists.add(new Playlist("3", "khjf", "", ""));
playlists.add(new Playlist("4", "thfd", "", ""));
playlists.add(new Playlist("5", "jhku", "", ""));
playlists.add(new Playlist("6", "tuid", "", ""));
playlists.add(new Playlist("7", "hfrt", "", ""));
playlists.add(new Playlist("8", "qedg", "", ""));
playlists.add(new Playlist("9", "tugh", "", ""));
return playlists;
}
}

View File

@@ -0,0 +1,40 @@
package com.cappielloantonio.play.viewmodel;
import android.app.Application;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import com.cappielloantonio.play.model.Song;
import com.cappielloantonio.play.repository.SongRepository;
import java.util.List;
public class SongViewModel extends AndroidViewModel {
private SongRepository repository;
private LiveData<List<Song>> allSongs;
public SongViewModel(@NonNull Application application) {
super(application);
repository = new SongRepository(application);
allSongs = repository.getListLiveSongs();
}
public boolean exist(Song song) {
return repository.exist(song);
}
public void insert(Song song) {
repository.insert(song);
}
public void delete(Song song) {
repository.delete(song);
}
public LiveData<List<Song>> getAllSongs() {
return allSongs;
}
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/bottomNavIconPressedColor" android:state_pressed="true" />
<item android:color="@color/bottomNavIconSelectedColor" android:state_checked="true" />
<item android:color="@color/bottomNavIconColor" />
</selector>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/cardColor" />
<corners android:radius="4dp" />
</shape>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M16.5,3c-1.74,0 -3.41,0.81 -4.5,2.09C10.91,3.81 9.24,3 7.5,3 4.42,3 2,5.42 2,8.5c0,3.78 3.4,6.86 8.55,11.54L12,21.35l1.45,-1.32C18.6,15.36 22,12.28 22,8.5 22,5.42 19.58,3 16.5,3zM12.1,18.55l-0.1,0.1 -0.1,-0.1C7.14,14.24 4,11.39 4,8.5 4,6.5 5.5,5 7.5,5c1.54,0 3.04,0.99 3.57,2.36h1.87C13.46,5.99 14.96,5 16.5,5c2,0 3.5,1.5 3.5,3.5 0,2.89 -3.14,5.74 -7.9,10.05z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M4.25,5.61C6.27,8.2 10,13 10,13v6c0,0.55 0.45,1 1,1h2c0.55,0 1,-0.45 1,-1v-6c0,0 3.72,-4.8 5.74,-7.39C20.25,4.95 19.78,4 18.95,4H5.04C4.21,4 3.74,4.95 4.25,5.61z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M8,18c0.55,0 1,-0.45 1,-1L9,7c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v10c0,0.55 0.45,1 1,1zM12,22c0.55,0 1,-0.45 1,-1L13,3c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v18c0,0.55 0.45,1 1,1zM4,14c0.55,0 1,-0.45 1,-1v-2c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v2c0,0.55 0.45,1 1,1zM16,18c0.55,0 1,-0.45 1,-1L17,7c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v10c0,0.55 0.45,1 1,1zM19,11v2c0,0.55 0.45,1 1,1s1,-0.45 1,-1v-2c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M12,5.69l5,4.5V18h-2v-6H9v6H7v-7.81l5,-4.5M12,3L2,12h3v8h6v-6h2v6h6v-8h3L12,3z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M15,21h-2v-2h2V21zM13,14h-2v5h2V14zM21,12h-2v4h2V12zM19,10h-2v2h2V10zM7,12H5v2h2V12zM5,10H3v2h2V10zM12,5h2V3h-2V5zM4.5,4.5v3h3v-3H4.5zM9,9H3V3h6V9zM4.5,16.5v3h3v-3H4.5zM9,21H3v-6h6V21zM16.5,4.5v3h3v-3H16.5zM21,9h-6V3h6V9zM19,19v-3l-4,0v2h2v3h4v-2H19zM17,12l-4,0v2h4V12zM13,10H7v2h2v2h2v-2h2V10zM14,9V7h-2V5h-2v4L14,9zM6.75,5.25h-1.5v1.5h1.5V5.25zM6.75,17.25h-1.5v1.5h1.5V17.25zM18.75,5.25h-1.5v1.5h1.5V5.25z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94c0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33c-0.22,-0.08 -0.47,0 -0.59,0.22L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48l2.03,1.58C4.84,11.36 4.8,11.69 4.8,12s0.02,0.64 0.07,0.94l-2.03,1.58c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.39,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.39,0.96c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.94zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6s1.62,-3.6 3.6,-3.6s3.6,1.62 3.6,3.6S13.98,15.6 12,15.6z"/>
</vector>

View File

Binary file not shown.

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<font-family
xmlns:app="http://schemas.android.com/apk/res-auto">
<font app:font="@font/regular" app:fontStyle="normal" app:fontWeight="400" />
<font app:font="@font/bold" app:fontStyle="normal" app:fontWeight="700" />
</font-family>

View File

Binary file not shown.

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:imeOptions="actionDone">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="@drawable/bottom_nav_shape"
android:elevation="2dp"
android:visibility="gone"
app:itemIconTint="@drawable/bottom_nav_selector"
app:labelVisibilityMode="unlabeled"
app:layout_behavior="@string/hide_bottom_view_on_scroll_behavior"
app:layout_scrollFlags="scroll|enterAlways|snap"
app:menu="@menu/bottom_nav_menu" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -0,0 +1,194 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/hide_bottom_view_on_scroll_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="8dp">
<!-- Discover music -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- Label and button -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="8dp"
android:paddingTop="8dp"
android:paddingEnd="8dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:fontFamily="@font/open_sans_font_family"
android:paddingStart="8dp"
android:paddingTop="12dp"
android:paddingEnd="8dp"
android:text="Discover Music"
android:textColor="@color/titleTextColor"
android:textSize="22sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/open_sans_font_family"
android:paddingStart="8dp"
android:paddingTop="12dp"
android:paddingEnd="8dp"
android:text="See all"
android:textColor="@color/subtitleTextColor"
android:textSize="14sp" />
</LinearLayout>
<!-- slideview -->
<androidx.viewpager.widget.ViewPager
android:id="@+id/discover_song_view_pager"
android:layout_width="match_parent"
android:layout_height="152dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:clipToPadding="false"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingEnd="16dp"
android:paddingBottom="8dp" />
</LinearLayout>
<!-- Recently played tracks -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- Label and button -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="8dp"
android:paddingTop="8dp"
android:paddingEnd="8dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:fontFamily="@font/open_sans_font_family"
android:paddingStart="8dp"
android:paddingTop="12dp"
android:paddingEnd="8dp"
android:text="Recently played tracks"
android:textColor="@color/titleTextColor"
android:textSize="22sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/open_sans_font_family"
android:paddingStart="8dp"
android:paddingTop="12dp"
android:paddingEnd="8dp"
android:text="See all"
android:textColor="@color/subtitleTextColor"
android:textSize="14sp" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recently_played_tracks_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:clipToPadding="false"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingEnd="8dp"
android:paddingBottom="8dp" />
</LinearLayout>
<!-- Most played tracks -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- Label and button -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="8dp"
android:paddingTop="8dp"
android:paddingEnd="8dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:fontFamily="@font/open_sans_font_family"
android:paddingStart="8dp"
android:paddingTop="12dp"
android:paddingEnd="8dp"
android:text="Most played tracks"
android:textColor="@color/titleTextColor"
android:textSize="22sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/open_sans_font_family"
android:paddingStart="8dp"
android:paddingTop="12dp"
android:paddingEnd="8dp"
android:text="See all"
android:textColor="@color/subtitleTextColor"
android:textSize="14sp" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/most_played_tracks_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:clipToPadding="false"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingEnd="8dp"
android:paddingBottom="8dp" />
<Button
android:id="@+id/resync_button"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:text="Resync"
android:textAllCaps="false"
android:textColor="@color/normalTextColor"
app:cornerRadius="24dp"/>
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.fragment.LandingFragment">
</FrameLayout>

View File

@@ -0,0 +1,262 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/hide_bottom_view_on_scroll_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="8dp">
<!-- Album -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- Label and button -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="8dp"
android:paddingTop="8dp"
android:paddingEnd="8dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:fontFamily="@font/open_sans_font_family"
android:paddingStart="8dp"
android:paddingTop="12dp"
android:paddingEnd="8dp"
android:text="My Albums"
android:textColor="@color/titleTextColor"
android:textSize="22sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/open_sans_font_family"
android:paddingStart="8dp"
android:paddingTop="12dp"
android:paddingEnd="8dp"
android:text="See all"
android:textColor="@color/subtitleTextColor"
android:textSize="14sp" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/album_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:clipToPadding="false"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingEnd="8dp"
android:paddingBottom="8dp" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="8dp">
<!-- Artist -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- Label and button -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="8dp"
android:paddingTop="8dp"
android:paddingEnd="8dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:fontFamily="@font/open_sans_font_family"
android:paddingStart="8dp"
android:paddingTop="12dp"
android:paddingEnd="8dp"
android:text="My Artists"
android:textColor="@color/titleTextColor"
android:textSize="22sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/open_sans_font_family"
android:paddingStart="8dp"
android:paddingTop="12dp"
android:paddingEnd="8dp"
android:text="See all"
android:textColor="@color/subtitleTextColor"
android:textSize="14sp" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/artist_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:clipToPadding="false"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingEnd="8dp"
android:paddingBottom="8dp" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="8dp">
<!-- Genre -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- Label and button -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="8dp"
android:paddingTop="8dp"
android:paddingEnd="8dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:fontFamily="@font/open_sans_font_family"
android:paddingStart="8dp"
android:paddingTop="12dp"
android:paddingEnd="8dp"
android:text="My Genres"
android:textColor="@color/titleTextColor"
android:textSize="22sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/open_sans_font_family"
android:paddingStart="8dp"
android:paddingTop="12dp"
android:paddingEnd="8dp"
android:text="See all"
android:textColor="@color/subtitleTextColor"
android:textSize="14sp" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/genre_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:clipToPadding="false"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingEnd="8dp"
android:paddingBottom="8dp" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="8dp">
<!-- Playlist -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- Label and button -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="8dp"
android:paddingTop="8dp"
android:paddingEnd="8dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:fontFamily="@font/open_sans_font_family"
android:paddingStart="8dp"
android:paddingTop="12dp"
android:paddingEnd="8dp"
android:text="My Playlists"
android:textColor="@color/titleTextColor"
android:textSize="22sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/open_sans_font_family"
android:paddingStart="8dp"
android:paddingTop="12dp"
android:paddingEnd="8dp"
android:text="See all"
android:textColor="@color/subtitleTextColor"
android:textSize="14sp" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/playlist_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:clipToPadding="false"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingEnd="10dp"
android:paddingBottom="8dp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View File

@@ -0,0 +1,101 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/login_screen"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:textColorHint="?android:textColorHint"
app:boxCornerRadiusBottomEnd="24dp"
app:boxCornerRadiusBottomStart="24dp"
app:boxCornerRadiusTopEnd="24dp"
app:boxCornerRadiusTopStart="24dp"
app:boxStrokeColor="?android:textColorSecondary"
app:endIconMode="clear_text"
app:endIconTint="?android:textColorSecondary"
app:errorEnabled="true"
app:hintTextColor="?android:textColorSecondary">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/username_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Username"
android:inputType="textShortMessage"
android:text="Antonio" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:textColorHint="?android:textColorHint"
app:boxCornerRadiusBottomEnd="24dp"
app:boxCornerRadiusBottomStart="24dp"
app:boxCornerRadiusTopEnd="24dp"
app:boxCornerRadiusTopStart="24dp"
app:boxStrokeColor="?android:textColorSecondary"
app:endIconMode="password_toggle"
app:endIconTint="?android:textColorSecondary"
app:errorEnabled="true"
app:hintTextColor="?android:textColorSecondary">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/password_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Password"
android:inputType="textPassword"
android:text="D5S%TXefMwHg4!i%ucNHJ#57" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:textColorHint="?android:textColorHint"
app:boxCornerRadiusBottomEnd="24dp"
app:boxCornerRadiusBottomStart="24dp"
app:boxCornerRadiusTopEnd="24dp"
app:boxCornerRadiusTopStart="24dp"
app:boxStrokeColor="?android:textColorSecondary"
app:endIconMode="clear_text"
app:endIconTint="?android:textColorSecondary"
app:errorEnabled="true"
app:hintTextColor="?android:textColorSecondary">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/server_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Server"
android:inputType="textNoSuggestions"
android:text="http://192.168.1.81:8096" />
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/login_button"
android:layout_width="match_parent"
android:layout_height="64dp"
android:layout_margin="32dp"
android:backgroundTint="@color/colorAccent"
android:text="Login"
android:textAllCaps="false"
app:cornerRadius="24dp" />
</LinearLayout>

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.paulrybitskyi.persistentsearchview.PersistentSearchView
android:id="@+id/persistentSearchView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="4dp"
android:paddingLeft="4dp"
android:paddingTop="4dp"
android:paddingEnd="4dp"
android:paddingRight="4dp"
app:areSuggestionsDisabled="true"
app:cardBackgroundColor="@color/cardColor"
app:cardCornerRadius="4dp"
app:cardElevation="2dp"
app:clearInputButtonDrawable="@drawable/ic_close"
app:dividerColor="@color/dividerColor"
app:isClearInputButtonEnabled="true"
app:isDismissableOnTouchOutside="true"
app:isProgressBarEnabled="true"
app:isVoiceInputButtonEnabled="true"
app:leftButtonDrawable="@drawable/ic_search"
app:progressBarColor="@color/colorAccent"
app:queryInputBarIconColor="@color/darkIconColor"
app:queryInputCursorColor="@color/colorAccent"
app:queryInputHint="@string/search_hint"
app:queryInputHintColor="@color/hintTextColor"
app:queryInputTextColor="@color/hintTextColor"
app:rightButtonDrawable="@drawable/ic_filter"
app:shouldDimBehind="true" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:overScrollMode="never"
android:paddingStart="2dp"
android:paddingLeft="2dp"
android:paddingTop="60dp"
android:paddingEnd="2dp"
android:paddingRight="2dp"
android:paddingBottom="4dp" />
</RelativeLayout>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
</RelativeLayout>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
android:id="@+id/loading_progress_bar"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="false"
android:minWidth="128dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:visibility="gone"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:backgroundTint="@color/cardColor"
app:cardCornerRadius="4dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="136dp">
<TextView
android:id="@+id/title_discover_song_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginTop="18dp"
android:text="@string/label_placeholder"
android:textColor="@color/titleTextColor"
android:textSize="20sp" />
<TextView
android:id="@+id/artist_discover_song_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/title_discover_song_label"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:drawablePadding="10dp"
android:ellipsize="end"
android:maxLines="3"
android:text="@string/label_placeholder"
android:textColor="@color/subtitleTextColor"
android:textSize="14sp" />
</RelativeLayout>
</androidx.cardview.widget.CardView>

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingEnd="8dp">
<androidx.cardview.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/card_view"
android:layout_width="172dp"
android:layout_height="172dp"
android:layout_gravity="center"
android:backgroundTint="@color/cardColor"
card_view:cardCornerRadius="4dp"
card_view:cardElevation="2dp"
card_view:cardPreventCornerOverlap="false"
card_view:cardUseCompatPadding="true" />
<TextView
android:id="@+id/album_name_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/open_sans_font_family"
android:paddingStart="2dp"
android:paddingTop="8dp"
android:text="@string/label_placeholder"
android:textColor="@color/titleTextColor"
android:textSize="14sp"
android:textStyle="bold" />
<TextView
android:id="@+id/artist_name_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/open_sans_font_family"
android:paddingStart="2dp"
android:text="@string/label_placeholder"
android:textColor="@color/titleTextColor"
android:textSize="12sp" />
</LinearLayout>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingEnd="8dp">
<androidx.cardview.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/card_view"
android:layout_width="172dp"
android:layout_height="172dp"
android:layout_gravity="center"
android:backgroundTint="@color/cardColor"
card_view:cardCornerRadius="4dp"
card_view:cardElevation="2dp"
card_view:cardPreventCornerOverlap="false"
card_view:cardUseCompatPadding="true" />
<TextView
android:id="@+id/artist_name_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/open_sans_font_family"
android:paddingStart="2dp"
android:paddingTop="8dp"
android:text="@string/label_placeholder"
android:textColor="@color/titleTextColor"
android:textSize="14sp"
android:textStyle="bold" />
</LinearLayout>

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingEnd="12dp"
android:paddingBottom="8dp">
<androidx.cardview.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/card_view"
android:layout_width="172dp"
android:layout_height="54dp"
android:layout_gravity="center"
android:backgroundTint="@color/cardColor"
card_view:cardCornerRadius="4dp"
card_view:cardElevation="2dp"
card_view:cardPreventCornerOverlap="false"
card_view:cardUseCompatPadding="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<View
android:layout_width="6dp"
android:layout_height="match_parent"
android:background="@color/colorAccent"/>
<TextView
android:id="@+id/genre_label"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:fontFamily="@font/open_sans_font_family"
android:paddingStart="8dp"
android:text="@string/label_placeholder"
android:textColor="@color/titleTextColor"
android:textSize="14sp"
android:textStyle="bold"
android:textAlignment="gravity"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>

View File

@@ -0,0 +1,37 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="96dp"
android:paddingTop="4dp"
android:paddingEnd="4dp">
<androidx.cardview.widget.CardView
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/card_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:backgroundTint="@color/cardColor"
card_view:cardCornerRadius="4dp"
card_view:cardElevation="2dp"
card_view:cardPreventCornerOverlap="false"
card_view:cardUseCompatPadding="true">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/playlist_name_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:padding="18dp"
android:fontFamily="@font/open_sans_font_family"
android:text="@string/label_placeholder"
android:textColor="@color/titleTextColor"
android:textSize="16sp"
android:textStyle="bold"
android:textAlignment="center"/>
</RelativeLayout>
</androidx.cardview.widget.CardView>
</RelativeLayout>

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingEnd="8dp">
<androidx.cardview.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/card_view"
android:layout_width="156dp"
android:layout_height="156dp"
android:layout_gravity="center"
android:backgroundTint="@color/cardColor"
card_view:cardCornerRadius="4dp"
card_view:cardElevation="2dp"
card_view:cardPreventCornerOverlap="false"
card_view:cardUseCompatPadding="true" />
<TextView
android:id="@+id/title_track_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/open_sans_font_family"
android:paddingStart="2dp"
android:paddingTop="8dp"
android:text="@string/label_placeholder"
android:textColor="@color/titleTextColor"
android:textSize="14sp"
android:textStyle="bold" />
<TextView
android:id="@+id/artist_track_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/open_sans_font_family"
android:paddingStart="2dp"
android:text="@string/label_placeholder"
android:textColor="@color/titleTextColor"
android:textSize="12sp" />
</LinearLayout>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/homeFragment"
android:icon="@drawable/ic_home"
android:title="@string/home_menu_label" />
<item
android:id="@+id/libraryFragment"
android:icon="@drawable/ic_graphic_eq"
android:title="@string/library_menu_label" />
<item
android:id="@+id/searchFragment"
android:icon="@drawable/ic_search"
android:title="@string/search_menu_label" />
<item
android:id="@+id/settingsFragment"
android:icon="@drawable/ic_settings"
android:title="@string/settings_menu_label" />
</menu>

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation 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:id="@+id/nav_graph"
app:startDestination="@id/landingFragment">
<fragment
android:id="@+id/landingFragment"
android:name="com.cappielloantonio.play.ui.fragment.LandingFragment"
android:label="fragment_landing"
tools:layout="@layout/fragment_landing" >
<action
android:id="@+id/action_landingFragment_to_loginFragment"
app:destination="@id/loginFragment"
app:popUpTo="@id/landingFragment"
app:popUpToInclusive="true" />
<action
android:id="@+id/action_landingFragment_to_homeFragment"
app:destination="@id/homeFragment"
app:popUpTo="@id/landingFragment"
app:popUpToInclusive="true" />
<action
android:id="@+id/action_landingFragment_to_syncFragment"
app:destination="@id/syncFragment"
app:popUpTo="@id/landingFragment"
app:popUpToInclusive="true" />
</fragment>
<fragment
android:id="@+id/loginFragment"
android:name="com.cappielloantonio.play.ui.fragment.LoginFragment"
android:label="LoginFragment"
tools:layout="@layout/fragment_login">
<action
android:id="@+id/action_loginFragment_to_syncFragment"
app:destination="@id/syncFragment"
app:popUpTo="@id/loginFragment"
app:popUpToInclusive="true" />
<action
android:id="@+id/action_loginFragment_to_homeFragment"
app:destination="@id/homeFragment"
app:popUpTo="@id/landingFragment"
app:popUpToInclusive="true" />
</fragment>
<fragment
android:id="@+id/syncFragment"
android:name="com.cappielloantonio.play.ui.fragment.SyncFragment"
android:label="SyncFragment"
tools:layout="@layout/fragment_sync">
<action
android:id="@+id/action_syncFragment_to_homeFragment"
app:destination="@id/homeFragment"
app:popUpTo="@id/landingFragment"
app:popUpToInclusive="true" />
</fragment>
<fragment
android:id="@+id/homeFragment"
android:name="com.cappielloantonio.play.ui.fragment.HomeFragment"
android:label="HomeFragment"
tools:layout="@layout/fragment_home">
<action
android:id="@+id/action_homeFragment_to_syncFragment"
app:destination="@id/syncFragment"
app:popUpTo="@id/homeFragment"
app:popUpToInclusive="true" />
</fragment>
<fragment
android:id="@+id/libraryFragment"
android:name="com.cappielloantonio.play.ui.fragment.LibraryFragment"
android:label="LibraryFragment"
tools:layout="@layout/fragment_library"/>
<fragment
android:id="@+id/searchFragment"
android:name="com.cappielloantonio.play.ui.fragment.SearchFragment"
android:label="SearchFragment"
tools:layout="@layout/fragment_search"/>
<fragment
android:id="@+id/settingsFragment"
android:name="com.cappielloantonio.play.ui.fragment.SettingsFragment"
android:label="SettingsFragment"
tools:layout="@layout/fragment_settings"/>
</navigation>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="colorControlNormal">@color/colorPrimary</item>
<item name="android:statusBarColor">@color/colorPrimary</item>
<item name="android:navigationBarColor">@color/colorPrimary</item>
<item name="android:windowLightStatusBar">false</item>
<item name="android:windowLightNavigationBar">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:windowTranslucentNavigation">false</item>
<item name="dialogTheme">@style/AppTheme.Dialog</item>
<item name="alertDialogTheme">@style/AppTheme.Dialog</item>
</style>
<style name="AppTheme.Dialog" parent="Theme.MaterialComponents.DayNight.Dialog">
<item name="android:background">@color/colorPrimary</item>
<item name="colorPrimary">@color/colorAccent</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:windowMinWidthMajor">82%</item>
<item name="android:windowMinWidthMinor">82%</item>
</style>
</resources>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#121212</color>
<color name="colorPrimaryDark">#121212</color>
<color name="colorAccent">#733ae6</color>
<color name="colorAccentLight">#aa6aff</color>
<!-- Ui color -->
<color name="statusBarColor">#121212</color>
<color name="navigationBarColor">#121212</color>
<color name="navigationDrawerColor">#121211</color>
<color name="cardColor">#1D1D1D</color>
<color name="titleTextColor">#DADADA</color>
<color name="subtitleTextColor">#9B9B9B</color>
<color name="normalTextColor">#808080</color>
<color name="normalIconColor">#808080</color>
<color name="suggestionTextColor">#DADADA</color>
<color name="suggestionIconColor">#DADADA</color>
<color name="darkTextColor">#CFCFCF</color>
<color name="darkIconColor">#CFCFCF</color>
<color name="suggestionSelectedTextColor">#CCCCCC</color>
<color name="dividerColor">#707070</color>
<color name="hintTextColor">#CFCFCF</color>
<color name="iconSearchBarColor">#CFCFCF</color>
<color name="contentContainerColor">#121212</color>
<color name="navigationDrawerTextColor">#F3F3F3</color>
<color name="navigationDrawerIconColor">#A0A0A0</color>
<color name="bottomNavIconPressedColor">#FFFFFF</color>
<color name="bottomNavIconSelectedColor">#EEEEEE</color>
<color name="bottomNavIconColor">#707070</color>
</resources>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:statusBarColor">@color/colorPrimary</item>
<item name="android:windowLightStatusBar">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:windowTranslucentNavigation">false</item>
<item name="dialogTheme">@style/AppTheme.Dialog</item>
<item name="alertDialogTheme">@style/AppTheme.Dialog</item>
</style>
<style name="AppTheme.Dialog" parent="Theme.MaterialComponents.DayNight.Dialog">
<item name="android:background">@color/colorPrimary</item>
<item name="colorPrimary">@color/colorAccent</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:windowMinWidthMajor">82%</item>
<item name="android:windowMinWidthMinor">82%</item>
</style>
</resources>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="colorControlNormal">@color/colorPrimary</item>
<item name="android:statusBarColor">@color/colorPrimary</item>
<item name="android:navigationBarColor">@color/colorPrimary</item>
<item name="android:windowLightStatusBar">true</item>
<item name="android:windowLightNavigationBar">true</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:windowTranslucentNavigation">false</item>
<item name="dialogTheme">@style/AppTheme.Dialog</item>
<item name="alertDialogTheme">@style/AppTheme.Dialog</item>
</style>
<style name="AppTheme.Dialog" parent="Theme.MaterialComponents.DayNight.Dialog">
<item name="android:background">@color/colorPrimary</item>
<item name="colorPrimary">@color/colorAccent</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:windowMinWidthMajor">82%</item>
<item name="android:windowMinWidthMinor">82%</item>
</style>
</resources>

View File

@@ -0,0 +1,134 @@
<resources>
<!-- Reply Preference -->
<string-array name="reply_entries">
<item>Reply</item>
<item>Reply to all</item>
</string-array>
<string-array name="reply_values">
<item>reply</item>
<item>reply_all</item>
</string-array>
<string-array name="themeListArray">
<item>Light</item>
<item>Dark</item>
<item>System default</item>
</string-array>
<string-array name="themeEntryArray">
<item>light</item>
<item>dark</item>
<item>default</item>
</string-array>
<string-array name="searchByListArray">
<item>Title</item>
<item>Author(s)</item>
<item>Series</item>
<item>Publisher</item>
<item>Year</item>
<item>ISBN</item>
<item>Language</item>
<item>Tags</item>
<item>Extension</item>
<item>All</item>
</string-array>
<string-array name="searchByEntryArray">
<item>title</item>
<item>author</item>
<item>series</item>
<item>publisher</item>
<item>year</item>
<item>isbn</item>
<item>language</item>
<item>tags</item>
<item>extension</item>
<item>all</item>
</string-array>
<string-array name="searchMaskListArray">
<item>Yes</item>
<item>No</item>
</string-array>
<string-array name="searchMaskEntryArray">
<item>yes</item>
<item>no</item>
</string-array>
<string-array name="resultNumberListArray">
<item>25</item>
<item>50</item>
</string-array>
<string-array name="resultNumberEntryArray">
<item>25</item>
<item>50</item>
</string-array>
<string-array name="pref_cache_size_titles">
<item>Unlimited</item>
<item>2GB</item>
<item>1GB</item>
<item>400MB</item>
<item>200MB</item>
</string-array>
<string-array name="pref_cache_size_values">
<item>4000000000</item>
<item>2000000000</item>
<item>1000000000</item>
<item>400000000</item>
<item>200000000</item>
</string-array>
<string-array name="sortModeListArray">
<item>Ascending</item>
<item>Descending</item>
<item>Default</item>
</string-array>
<string-array name="sortModeEntryArray">
<item>ASC</item>
<item>DESC</item>
<item>def</item>
</string-array>
<string-array name="orderByListArray">
<item>ID</item>
<item>Author(s)</item>
<item>Author(s)</item>
<item>Publisher</item>
<item>Year</item>
<item>Pages</item>
<item>Language</item>
<item>Size</item>
<item>Extension</item>
<item>None</item>
</string-array>
<string-array name="orderByEntryArray">
<item>id</item>
<item>author</item>
<item>title</item>
<item>publisher</item>
<item>year</item>
<item>pages</item>
<item>language</item>
<item>filesize</item>
<item>extension</item>
<item>none</item>
</string-array>
<string-array name="saveFilterListArray">
<item>Yes</item>
<item>No</item>
</string-array>
<string-array name="saveFilterEntryArray">
<item>yes</item>
<item>no</item>
</string-array>
</resources>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#FFFFFF</color>
<color name="colorPrimaryDark">#FFFFFF</color>
<color name="colorAccent">#3700B3</color>
<color name="colorAccentLight">#733ae6</color>
<!-- Ui color -->
<color name="statusBarColor">#FFFFFF</color>
<color name="navigationBarColor">#FFFFFF</color>
<color name="navigationDrawerColor">#FFFFFF</color>
<color name="cardColor">#FFFFFF</color>
<color name="titleTextColor">#252525</color>
<color name="subtitleTextColor">#646464</color>
<color name="normalTextColor">#7f7f7f</color>
<color name="normalIconColor">#7f7f7f</color>
<color name="suggestionTextColor">#252525</color>
<color name="suggestionIconColor">#252525</color>
<color name="darkTextColor">#303030</color>
<color name="darkIconColor">#303030</color>
<color name="suggestionSelectedTextColor">#333333</color>
<color name="dividerColor">#AFAFAF</color>
<color name="hintTextColor">#303030</color>
<color name="iconSearchBarColor">#303030</color>
<color name="contentContainerColor">#EEEEEE</color>
<color name="navigationDrawerTextColor">#383838</color>
<color name="navigationDrawerIconColor">#757575</color>
<color name="bottomNavIconPressedColor">#252525</color>
<color name="bottomNavIconSelectedColor">#303030</color>
<color name="bottomNavIconColor">#AFAFAF</color>
</resources>

View File

@@ -0,0 +1,8 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="nav_header_vertical_spacing">8dp</dimen>
<dimen name="nav_header_height">176dp</dimen>
<dimen name="fab_margin">16dp</dimen>
</resources>

View File

@@ -0,0 +1,35 @@
<resources>
<string name="app_name">Play</string>
<string name="storage_permission_rationale">We need permissions because this and that</string>
<string name="title_activity_settings">Settings</string>
<string name="title_activity_filters">Filters</string>
<string name="title_activity_details">Details</string>
<string name="action_settings">Settings</string>
<string name="general_header">General</string>
<string name="search_hint">Search title, artists or albums</string>
<string name="menu_home">Home</string>
<string name="settings_menu_label">Settings</string>
<string name="download_label">Download</string>
<string name="shelf_label">Shelf</string>
<string name="theme_header">Theme</string>
<string name="theme_selection">Choose theme</string>
<string name="covers_cache">Covers cache</string>
<string name="search_label">Search</string>
<string name="favorites_label">Favorites</string>
<string name="categories_label">Categories</string>
<string name="library_menu_label">Library</string>
<string name="home_menu_label">Home</string>
<string name="search_menu_label">Search</string>
<string name="label_placeholder">--</string>
<string name="save_filters">Save filters between sessions</string>
<string name="battery_optimizations_title">Battery Optimizations</string>
<string name="battery_optimizations_message">Please disable battery optimizations for media playback while the screen is off.</string>
<string name="disable">Disable</string>
<string name="ignore">Ignore</string>
<!-- TODO: Remove or change this placeholder text -->
<string name="hello_blank_fragment">Hello blank fragment</string>
</resources>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:statusBarColor">@color/colorPrimary</item>
<item name="android:windowLightStatusBar">true</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:windowTranslucentNavigation">false</item>
<!-- <item name="android:dialogTheme">@style/AppTheme.Dialog</item>-->
<!-- <item name="android:alertDialogTheme">@style/AppTheme.Dialog</item>-->
<item name="dialogTheme">@style/AppTheme.Dialog</item>
<item name="alertDialogTheme">@style/AppTheme.Dialog</item>
</style>
<style name="AppTheme.Dialog" parent="Theme.MaterialComponents.DayNight.Dialog">
<item name="android:background">@color/colorPrimary</item>
<item name="colorPrimary">@color/colorAccent</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:windowMinWidthMajor">82%</item>
<item name="android:windowMinWidthMinor">82%</item>
</style>
</resources>

View File

@@ -0,0 +1,30 @@
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory app:title="@string/general_header">
<ListPreference
app:defaultValue="400000000"
app:dialogTitle="@string/covers_cache"
app:entries="@array/pref_cache_size_titles"
app:entryValues="@array/pref_cache_size_values"
app:key="image_cache_size"
app:title="@string/covers_cache"
app:useSimpleSummaryProvider="true" />
<ListPreference
app:defaultValue="default"
app:dialogTitle="@string/theme_selection"
app:entries="@array/themeListArray"
app:entryValues="@array/themeEntryArray"
app:key="themePref"
app:title="@string/theme_selection"
app:useSimpleSummaryProvider="true" />
<ListPreference
app:defaultValue="no"
app:dialogTitle="@string/save_filters"
app:entries="@array/saveFilterListArray"
app:entryValues="@array/saveFilterEntryArray"
app:key="filtersPref"
app:title="@string/save_filters"
app:useSimpleSummaryProvider="true" />
</PreferenceCategory>
</PreferenceScreen>

View File

@@ -0,0 +1,17 @@
package com.cappielloantonio.play;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}

27
build.gradle Normal file
View File

@@ -0,0 +1,27 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.0'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.10'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
maven { url 'https://jitpack.io' }
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}

19
gradle.properties Normal file
View File

@@ -0,0 +1,19 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true

Some files were not shown because too many files have changed in this diff Show More