diff --git a/README.md b/README.md index 52420d24..ae7eb67d 100644 --- a/README.md +++ b/README.md @@ -23,17 +23,12 @@ This is a native music player for Android devices that connects to Jellyfin medi * Playback history reporting * Filter content by library -# Requisites -- A Jellfin server. See how to setup one [here](https://jellyfin.org/docs/general/quick-start/). -- Android 4.4 or later - # Issues Since this was a small project intended mainly for myself, there are some things I haven't resolved yet. I would appreciate pull requests to fix any of these issues! * Artist sorting isn't available through the API -* Playlists and favorites will not update automatically when changed ([#5](https://github.com/adrianvic/jamfish/issues/5)) -* App may crash on really low end devices due exceeding the maximum bitmap memory ([#4](https://github.com/adrianvic/jamfish/issues/4)) +* Playlists and favorites will not update automatically when changed # Future Plans diff --git a/app/build.gradle b/app/build.gradle index 534ad6d3..30aa7bdf 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,12 +10,10 @@ android { minSdk 19 targetSdk 33 - versionCode 2 - versionName '1.4.1' + versionCode 1 + versionName '1.4.0' - // for SDK < 19 multiDexEnabled true - vectorDrawables { useSupportLibrary true } @@ -107,9 +105,8 @@ dependencies { implementation 'com.android.support:multidex:1.0.3' implementation 'com.mlegy.redscreenofdeath:red-screen-of-death:0.1.3' - // old version of retrofit to work with api < 21 - implementation 'com.squareup.retrofit2:retrofit:2.6.4' - implementation 'com.squareup.retrofit2:converter-gson:2.6.0' + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.retrofit2:converter-gson:2.9.0' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' @@ -120,10 +117,4 @@ dependencies { implementation 'com.github.bumptech.glide:annotations:4.12.0' implementation 'com.github.bumptech.glide:glide:4.12.0' implementation 'com.github.bumptech.glide:okhttp3-integration:4.12.0' - - // for supporting legacy android versions: - implementation('com.squareup.okhttp3:okhttp:3.12.13') - - implementation "androidx.multidex:multidex:2.0.1" - implementation 'org.conscrypt:conscrypt-android:2.5.2' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e5ab2f2f..2ad3febf 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,9 +2,7 @@ - - @@ -51,7 +49,6 @@ - @@ -67,7 +64,7 @@ android:name="org.adrianvictor.geleia.views.shortcuts.AppShortcutLauncherActivity" android:launchMode="singleInstance" android:theme="@android:style/Theme.Translucent.NoTitleBar" /> - + @@ -139,12 +136,6 @@ android:resource="@xml/provider_paths" /> - - diff --git a/app/src/main/java/org/adrianvictor/geleia/App.java b/app/src/main/java/org/adrianvictor/geleia/App.java index 231cee4c..3145450b 100644 --- a/app/src/main/java/org/adrianvictor/geleia/App.java +++ b/app/src/main/java/org/adrianvictor/geleia/App.java @@ -6,9 +6,6 @@ import android.content.Context; import android.os.Build; import android.provider.Settings; -import androidx.annotation.NonNull; -import androidx.appcompat.app.AppCompatDelegate; -import androidx.multidex.MultiDexApplication; import androidx.room.Room; import org.adrianvictor.geleia.database.JellyDatabase; @@ -17,7 +14,6 @@ import org.adrianvictor.geleia.util.PreferenceUtil; import org.adrianvictor.geleia.views.shortcuts.DynamicShortcutManager; import com.melegy.redscreenofdeath.RedScreenOfDeath; -import org.conscrypt.Conscrypt; import org.jellyfin.apiclient.interaction.AndroidDevice; import org.jellyfin.apiclient.interaction.ApiClient; import org.jellyfin.apiclient.interaction.VolleyHttpClient; @@ -26,16 +22,7 @@ import org.jellyfin.apiclient.interaction.http.IAsyncHttpClient; import org.jellyfin.apiclient.logging.AndroidLogger; import org.jellyfin.apiclient.logging.ILogger; -import com.bumptech.glide.Glide; -import com.bumptech.glide.Registry; -import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader; -import com.bumptech.glide.load.model.GlideUrl; -import java.io.InputStream; -import okhttp3.OkHttpClient; - -import java.security.Security; - -public class App extends MultiDexApplication { +public class App extends Application { private static App app; private static JellyDatabase database; @@ -43,14 +30,8 @@ public class App extends MultiDexApplication { @Override public void onCreate() { - // Initializing stuff for older Android APIs compatibility - Security.insertProviderAt(Conscrypt.newProvider(), 1); // To have SSL 1.2 on API < 19 - AppCompatDelegate.setCompatVectorFromResourcesEnabled(true); // To load vectors on API < 19 - super.onCreate(); - initializeGlide(this); - if (BuildConfig.DEBUG) { RedScreenOfDeath.init(this); } @@ -59,7 +40,7 @@ public class App extends MultiDexApplication { database = createDatabase(this); apiClient = createApiClient(this); - if (database.userDao().getUsers().isEmpty()) { + if (database.userDao().getUsers().size() == 0) { PreferenceUtil.getInstance(this).setServer(null); PreferenceUtil.getInstance(this).setUser(null); } @@ -95,7 +76,6 @@ public class App extends MultiDexApplication { IDevice device = new AndroidDevice(deviceId, deviceName); EventListener eventListener = new EventListener(); - return new ApiClient(httpClient, logger, server, appName, appVersion, device, eventListener); } @@ -110,15 +90,4 @@ public class App extends MultiDexApplication { public static App getInstance() { return app; } - - private void initializeGlide(@NonNull Context context) { - // This OkHttpClient is now created with Conscrypt as the SSL provider. - OkHttpClient client = new OkHttpClient.Builder().build(); - - // Manually create and register Glide's OkHttp component. - OkHttpUrlLoader.Factory factory = new OkHttpUrlLoader.Factory(client); - - // Ensure Glide is initialized and then register the component. - Glide.get(context).getRegistry().replace(GlideUrl.class, InputStream.class, factory); - } } diff --git a/app/src/main/java/org/adrianvictor/geleia/activities/AboutActivity.java b/app/src/main/java/org/adrianvictor/geleia/activities/AboutActivity.java index 56ffa5cf..50601fac 100644 --- a/app/src/main/java/org/adrianvictor/geleia/activities/AboutActivity.java +++ b/app/src/main/java/org/adrianvictor/geleia/activities/AboutActivity.java @@ -7,7 +7,6 @@ import android.view.View; import androidx.annotation.NonNull; -import org.adrianvictor.geleia.service.notifications.ErrorNotification; import org.adrianvictor.geleia.util.NavigationUtil; import org.adrianvictor.geleia.util.PreferenceUtil; import org.adrianvictor.geleia.databinding.ActivityAboutBinding; @@ -103,7 +102,7 @@ public class AboutActivity extends AbsBaseActivity implements View.OnClickListen try { return context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName; } catch (PackageManager.NameNotFoundException e) { - ErrorNotification.show(context, e.getMessage()); + e.printStackTrace(); } return "Unknown"; diff --git a/app/src/main/java/org/adrianvictor/geleia/activities/DirectoryPickerActivity.java b/app/src/main/java/org/adrianvictor/geleia/activities/DirectoryPickerActivity.java deleted file mode 100644 index 4452596d..00000000 --- a/app/src/main/java/org/adrianvictor/geleia/activities/DirectoryPickerActivity.java +++ /dev/null @@ -1,127 +0,0 @@ -package org.adrianvictor.geleia.activities; - -import android.content.Intent; -import android.os.Bundle; -import android.os.Environment; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.Button; -import android.widget.LinearLayout; -import android.widget.ListView; -import android.widget.TextView; - -import androidx.appcompat.app.AppCompatActivity; - -import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; - -public class DirectoryPickerActivity extends AppCompatActivity { - - public static final String EXTRA_RESULT_PATH = "result_path"; - - private File rootDir; - private File currentDir; - - private ArrayAdapter adapter; - private TextView pathView; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - rootDir = Environment.getExternalStorageDirectory(); - currentDir = rootDir; - - LinearLayout layout = new LinearLayout(this); - layout.setOrientation(LinearLayout.VERTICAL); - - pathView = new TextView(this); - pathView.setPadding(24, 24, 24, 24); - - ListView listView = new ListView(this); - - Button selectButton = new Button(this); - selectButton.setText("Select this folder"); - - layout.addView(pathView); - layout.addView(listView, new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f)); - layout.addView(selectButton); - - setContentView(layout); - - adapter = new ArrayAdapter(this, - android.R.layout.simple_list_item_1, new ArrayList<>()) { - @Override - public View getView(int position, View convertView, ViewGroup parent) { - TextView tv = (TextView) super.getView(position, convertView, parent); - File f = getItem(position); - - File parentFile = currentDir.getParentFile(); - - if (parentFile != null && parentFile.equals(f)) { - tv.setText(".."); - } else { - tv.setText(f.getName()); - } - - return tv; - } - }; - - listView.setAdapter(adapter); - - listView.setOnItemClickListener((p, v, pos, id) -> { - File dir = adapter.getItem(pos); - if (dir != null) openDir(dir); - }); - - selectButton.setOnClickListener(v -> { - Intent result = new Intent(); - result.putExtra(EXTRA_RESULT_PATH, currentDir.getAbsolutePath()); - setResult(RESULT_OK, result); - finish(); - }); - - openDir(rootDir); - } - - private void openDir(File dir) { - if (!dir.exists() || !dir.canRead()) return; - - File[] dirs = dir.listFiles(f -> - f.isDirectory() && f.canRead() - ); - - if (dirs == null) return; - - adapter.clear(); - - File parent = dir.getParentFile(); - if (parent != null && parent.getAbsolutePath().startsWith(rootDir.getAbsolutePath())) { - adapter.add(parent); - } - - Arrays.sort(dirs); - - for (File f : dirs) { - adapter.add(f); - } - - currentDir = dir; - pathView.setText(dir.getAbsolutePath()); - } - - @Override - public void onBackPressed() { - File parent = currentDir.getParentFile(); - if (parent != null && - parent.getAbsolutePath().startsWith(rootDir.getAbsolutePath())) { - openDir(parent); - } else { - super.onBackPressed(); - } - } -} diff --git a/app/src/main/java/org/adrianvictor/geleia/activities/MainActivity.java b/app/src/main/java/org/adrianvictor/geleia/activities/MainActivity.java index 41a46462..0f1df8a7 100644 --- a/app/src/main/java/org/adrianvictor/geleia/activities/MainActivity.java +++ b/app/src/main/java/org/adrianvictor/geleia/activities/MainActivity.java @@ -13,14 +13,11 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.drawerlayout.widget.DrawerLayout; -import androidx.lifecycle.Lifecycle; import com.afollestad.materialcab.attached.AttachedCab; import com.afollestad.materialcab.attached.AttachedCabKt; import org.adrianvictor.geleia.activities.base.AbsMusicContentActivity; -import org.adrianvictor.geleia.fragments.OfflineFragment; import org.adrianvictor.geleia.interfaces.CabHolder; -import org.adrianvictor.geleia.util.NavigationUtil; import org.adrianvictor.geleia.util.PreferenceUtil; import org.adrianvictor.geleia.util.ThemeUtil; import org.adrianvictor.geleia.databinding.ActivityMainContentBinding; @@ -45,7 +42,6 @@ public class MainActivity extends AbsMusicContentActivity implements CabHolder { private ActivityMainContentBinding contentBinding; private NavigationDrawerHeaderBinding navigationBinding; private boolean onLogout; - private boolean pendingShowOffline = false; @Nullable private AttachedCab cab; @@ -101,15 +97,6 @@ public class MainActivity extends AbsMusicContentActivity implements CabHolder { }); } - @Override - public void onStateOffline() { - if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) { - NavigationUtil.startUnreachable(this); - } else { - pendingShowOffline = true; - } - } - @Override public void onPause() { super.onPause(); @@ -121,16 +108,6 @@ public class MainActivity extends AbsMusicContentActivity implements CabHolder { } } - @Override - protected void onResume() { - super.onResume(); - - if (pendingShowOffline) { - setCurrentFragment(OfflineFragment.newInstance()); - pendingShowOffline = false; - } - } - private void setCurrentFragment(Fragment fragment) { getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, fragment, null).commit(); } diff --git a/app/src/main/java/org/adrianvictor/geleia/activities/SearchActivity.java b/app/src/main/java/org/adrianvictor/geleia/activities/SearchActivity.java index b483b51d..2958e587 100644 --- a/app/src/main/java/org/adrianvictor/geleia/activities/SearchActivity.java +++ b/app/src/main/java/org/adrianvictor/geleia/activities/SearchActivity.java @@ -93,9 +93,6 @@ public class SearchActivity extends AbsMusicContentActivity implements SearchVie public void onStateOnline() { } - @Override - public void onStateOffline() {} - private void setUpToolBar() { binding.toolbar.setBackgroundColor(PreferenceUtil.getInstance(this).getPrimaryColor()); setSupportActionBar(binding.toolbar); diff --git a/app/src/main/java/org/adrianvictor/geleia/activities/SettingsActivity.java b/app/src/main/java/org/adrianvictor/geleia/activities/SettingsActivity.java index ab860b08..b1406474 100644 --- a/app/src/main/java/org/adrianvictor/geleia/activities/SettingsActivity.java +++ b/app/src/main/java/org/adrianvictor/geleia/activities/SettingsActivity.java @@ -51,7 +51,6 @@ public class SettingsActivity extends AbsBaseActivity { public static class SettingsFragment extends PreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener { private ActivityResultLauncher dirPickerLauncher; - private ActivityResultLauncher legacyDirPickerLauncher; @Override public void onCreate(@Nullable Bundle savedInstanceState) { @@ -72,25 +71,6 @@ public class SettingsActivity extends AbsBaseActivity { } } }); - - legacyDirPickerLauncher = registerForActivityResult( - new ActivityResultContracts.StartActivityForResult(), - result -> { - if (result.getResultCode() == RESULT_OK && result.getData() != null) { - String path = result.getData().getStringExtra( - DirectoryPickerActivity.EXTRA_RESULT_PATH - ); - - if (path != null) { - SharedPreferences.Editor editor = - PreferenceManager.getDefaultSharedPreferences(requireContext()).edit(); - editor.putString(PreferenceUtil.LOCATION_DOWNLOAD, path); - editor.apply(); - invalidateSettings(); - } - } - } - ); } @Override @@ -181,13 +161,8 @@ public class SettingsActivity extends AbsBaseActivity { } private void openDirectoryPicker() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); - dirPickerLauncher.launch(intent); - } else { - Intent intent = new Intent(requireContext(), DirectoryPickerActivity.class); - legacyDirPickerLauncher.launch(intent); - } + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); + dirPickerLauncher.launch(intent); } @Override diff --git a/app/src/main/java/org/adrianvictor/geleia/activities/SplashActivity.java b/app/src/main/java/org/adrianvictor/geleia/activities/SplashActivity.java index d3460d22..88a3b8af 100644 --- a/app/src/main/java/org/adrianvictor/geleia/activities/SplashActivity.java +++ b/app/src/main/java/org/adrianvictor/geleia/activities/SplashActivity.java @@ -1,13 +1,8 @@ package org.adrianvictor.geleia.activities; -import android.content.BroadcastReceiver; -import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; -import android.os.Build; import android.os.Bundle; - -import androidx.annotation.NonNull; +import android.os.Handler; import org.adrianvictor.geleia.App; import org.adrianvictor.geleia.R; @@ -20,41 +15,10 @@ import org.adrianvictor.geleia.util.PreferenceUtil; import java.util.List; public class SplashActivity extends AbsBaseActivity { - - private final BroadcastReceiver receiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, @NonNull Intent intent) { - if (intent.getAction() == null) { - return; - } - - switch (intent.getAction()) { - case LoginService.STATE_ONLINE: - NavigationUtil.startMain(context); - finish(); - break; - case LoginService.STATE_OFFLINE: - NavigationUtil.startUnreachable(context); - finish(); - break; - } - } - }; - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - final IntentFilter filter = new IntentFilter(); - filter.addAction(LoginService.STATE_ONLINE); - filter.addAction(LoginService.STATE_OFFLINE); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - registerReceiver(receiver, filter, Context.RECEIVER_NOT_EXPORTED); - } else { - registerReceiver(receiver, filter); - } - setContentView(R.layout.activity_splash); } @@ -71,20 +35,13 @@ public class SplashActivity extends AbsBaseActivity { User user = App.getDatabase().userDao().getUser(PreferenceUtil.getInstance(this).getUser()); List available = App.getDatabase().userDao().getUsers(); - if (user == null && !available.isEmpty()) { + if (user == null && available.size() != 0) { NavigationUtil.startSelect(this); - finish(); } else if (user == null) { NavigationUtil.startLogin(this); - finish(); } else { startService(new Intent(this, LoginService.class)); + new Handler().postDelayed(() -> NavigationUtil.startMain(this), 1000); } } - - @Override - protected void onDestroy() { - unregisterReceiver(receiver); - super.onDestroy(); - } } diff --git a/app/src/main/java/org/adrianvictor/geleia/activities/UnreachableActivity.java b/app/src/main/java/org/adrianvictor/geleia/activities/UnreachableActivity.java deleted file mode 100644 index 94afba90..00000000 --- a/app/src/main/java/org/adrianvictor/geleia/activities/UnreachableActivity.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.adrianvictor.geleia.activities; - -import android.os.Bundle; -import android.view.View; - -import org.adrianvictor.geleia.R; -import org.adrianvictor.geleia.activities.base.AbsThemeActivity; -import org.adrianvictor.geleia.util.NavigationUtil; - -public class UnreachableActivity extends AbsThemeActivity { - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setContentView(R.layout.activity_unreachable); - } - - public void onSelectClick(View view) { - NavigationUtil.startSelect(this); - } -} diff --git a/app/src/main/java/org/adrianvictor/geleia/activities/base/AbsBaseActivity.java b/app/src/main/java/org/adrianvictor/geleia/activities/base/AbsBaseActivity.java index d64b6bc2..406f6237 100644 --- a/app/src/main/java/org/adrianvictor/geleia/activities/base/AbsBaseActivity.java +++ b/app/src/main/java/org/adrianvictor/geleia/activities/base/AbsBaseActivity.java @@ -59,7 +59,7 @@ public abstract class AbsBaseActivity extends AbsThemeActivity { .setPositiveButton(R.string.disable, (dialog, id) -> requestBatteryOptimization()); new Handler().postDelayed(builder::show, 2000); - } else if (!permissions.isEmpty() && ActivityCompat.shouldShowRequestPermissionRationale(this, permissions.get(0))) { + } else if (permissions.size() != 0 && ActivityCompat.shouldShowRequestPermissionRationale(this, permissions.get(0))) { builder.setMessage(getPermissionMessage()) .setTitle(R.string.permissions_denied) .setPositiveButton(R.string.action_grant, (dialog, id) -> requestPermissions()); @@ -127,11 +127,8 @@ public abstract class AbsBaseActivity extends AbsThemeActivity { private boolean checkBatteryOptimization() { String packageName = getPackageName(); PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - return pm.isIgnoringBatteryOptimizations(packageName); - } else { - return true; - } + + return pm.isIgnoringBatteryOptimizations(packageName); } @RequiresApi(api = Build.VERSION_CODES.M) @@ -141,11 +138,9 @@ public abstract class AbsBaseActivity extends AbsThemeActivity { @RequiresApi(api = Build.VERSION_CODES.M) private boolean checkPermissions() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - for (String permission : permissions) { - if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) { - return false; - } + for (String permission : permissions) { + if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) { + return false; } } diff --git a/app/src/main/java/org/adrianvictor/geleia/activities/base/AbsMusicContentActivity.java b/app/src/main/java/org/adrianvictor/geleia/activities/base/AbsMusicContentActivity.java index 90843227..904b928f 100644 --- a/app/src/main/java/org/adrianvictor/geleia/activities/base/AbsMusicContentActivity.java +++ b/app/src/main/java/org/adrianvictor/geleia/activities/base/AbsMusicContentActivity.java @@ -1,19 +1,14 @@ package org.adrianvictor.geleia.activities.base; -import static org.adrianvictor.geleia.adapter.CustomFragmentStatePagerAdapter.TAG; - import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.os.Build; import android.os.Bundle; -import android.util.Log; import androidx.annotation.NonNull; import org.adrianvictor.geleia.App; -import org.adrianvictor.geleia.fragments.OfflineFragment; import org.adrianvictor.geleia.interfaces.StateListener; import org.adrianvictor.geleia.service.LoginService; import org.adrianvictor.geleia.util.NavigationUtil; @@ -29,7 +24,7 @@ public abstract class AbsMusicContentActivity extends AbsMusicPanelActivity impl onStateOnline(); break; case LoginService.STATE_OFFLINE: - onStateOffline(); + NavigationUtil.startLogin(context); break; } } @@ -44,11 +39,7 @@ public abstract class AbsMusicContentActivity extends AbsMusicPanelActivity impl filter.addAction(LoginService.STATE_ONLINE); filter.addAction(LoginService.STATE_OFFLINE); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - registerReceiver(receiver, filter, Context.RECEIVER_NOT_EXPORTED); - } else { - registerReceiver(receiver, filter); - } + registerReceiver(receiver, filter); if (App.getApiClient() == null) { startService(new Intent(this, LoginService.class)); @@ -60,14 +51,24 @@ public abstract class AbsMusicContentActivity extends AbsMusicPanelActivity impl @Override protected void onResume() { super.onResume(); + + if (App.getApiClient() == null) { + startService(new Intent(this, LoginService.class)); + } } @Override protected void onDestroy() { unregisterReceiver(receiver); + super.onDestroy(); } @Override - public void onStatePolling() {} + public void onStatePolling() { + } + + @Override + public void onStateOffline() { + } } diff --git a/app/src/main/java/org/adrianvictor/geleia/activities/details/AlbumDetailActivity.java b/app/src/main/java/org/adrianvictor/geleia/activities/details/AlbumDetailActivity.java index ad920baa..9c7ca01b 100644 --- a/app/src/main/java/org/adrianvictor/geleia/activities/details/AlbumDetailActivity.java +++ b/app/src/main/java/org/adrianvictor/geleia/activities/details/AlbumDetailActivity.java @@ -74,11 +74,6 @@ public class AlbumDetailActivity extends AbsMusicContentActivity implements Pale }); } - @Override - public void onStateOffline() { - - } - @Override public void onOffsetChanged (AppBarLayout appBarLayout, int verticalOffset) { float headerAlpha = Math.max(0, Math.min(1, 1 + (2 * (float) verticalOffset / headerViewHeight))); @@ -236,6 +231,6 @@ public class AlbumDetailActivity extends AbsMusicContentActivity implements Pale binding.durationText.setText(MusicUtil.getReadableDurationString(MusicUtil.getTotalDuration(this, album.songs))); binding.albumYearText.setText(MusicUtil.getYearString(album.year)); - if (!album.songs.isEmpty()) adapter.swapDataSet(album.songs); + if (album.songs.size() != 0) adapter.swapDataSet(album.songs); } } diff --git a/app/src/main/java/org/adrianvictor/geleia/activities/details/ArtistDetailActivity.java b/app/src/main/java/org/adrianvictor/geleia/activities/details/ArtistDetailActivity.java index 2f98a935..d7cde428 100644 --- a/app/src/main/java/org/adrianvictor/geleia/activities/details/ArtistDetailActivity.java +++ b/app/src/main/java/org/adrianvictor/geleia/activities/details/ArtistDetailActivity.java @@ -87,11 +87,6 @@ public class ArtistDetailActivity extends AbsMusicContentActivity implements Pal }); } - @Override - public void onStateOffline() { - - } - @Override public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) { float headerAlpha = Math.max(0, Math.min(1, 1 + (2 * (float) verticalOffset / headerViewHeight))); @@ -263,7 +258,7 @@ public class ArtistDetailActivity extends AbsMusicContentActivity implements Pal binding.albumCountText.setText(MusicUtil.getAlbumCountString(this, artist.albums.size())); binding.durationText.setText(MusicUtil.getReadableDurationString(MusicUtil.getTotalDuration(this, artist.songs))); - if (!artist.songs.isEmpty()) songAdapter.swapDataSet(artist.songs); - if (!artist.albums.isEmpty()) albumAdapter.swapDataSet(artist.albums); + if (artist.songs.size() != 0) songAdapter.swapDataSet(artist.songs); + if (artist.albums.size() != 0) albumAdapter.swapDataSet(artist.albums); } } diff --git a/app/src/main/java/org/adrianvictor/geleia/activities/details/GenreDetailActivity.java b/app/src/main/java/org/adrianvictor/geleia/activities/details/GenreDetailActivity.java index aacc7db8..9449daa5 100644 --- a/app/src/main/java/org/adrianvictor/geleia/activities/details/GenreDetailActivity.java +++ b/app/src/main/java/org/adrianvictor/geleia/activities/details/GenreDetailActivity.java @@ -59,11 +59,6 @@ public class GenreDetailActivity extends AbsMusicContentActivity implements CabH }); } - @Override - public void onStateOffline() { - - } - @Override protected View createContentView() { binding = ActivityGenreDetailBinding.inflate(getLayoutInflater()); diff --git a/app/src/main/java/org/adrianvictor/geleia/activities/details/PlaylistDetailActivity.java b/app/src/main/java/org/adrianvictor/geleia/activities/details/PlaylistDetailActivity.java index bb3d1d57..f111dc14 100644 --- a/app/src/main/java/org/adrianvictor/geleia/activities/details/PlaylistDetailActivity.java +++ b/app/src/main/java/org/adrianvictor/geleia/activities/details/PlaylistDetailActivity.java @@ -71,11 +71,6 @@ public class PlaylistDetailActivity extends AbsMusicContentActivity implements C }); } - @Override - public void onStateOffline() { - - } - @Override protected View createContentView() { binding = ActivityPlaylistDetailBinding.inflate(getLayoutInflater()); diff --git a/app/src/main/java/org/adrianvictor/geleia/adapter/CustomFragmentStatePagerAdapter.java b/app/src/main/java/org/adrianvictor/geleia/adapter/CustomFragmentStatePagerAdapter.java index f6c1c4f5..d19a6ffe 100644 --- a/app/src/main/java/org/adrianvictor/geleia/adapter/CustomFragmentStatePagerAdapter.java +++ b/app/src/main/java/org/adrianvictor/geleia/adapter/CustomFragmentStatePagerAdapter.java @@ -180,7 +180,7 @@ public abstract class CustomFragmentStatePagerAdapter extends PagerAdapter { @Override public Parcelable saveState() { Bundle state = null; - if (!mSavedState.isEmpty()) { + if (mSavedState.size() > 0) { state = new Bundle(); Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()]; mSavedState.toArray(fss); diff --git a/app/src/main/java/org/adrianvictor/geleia/adapter/DownloadsAdapter.java b/app/src/main/java/org/adrianvictor/geleia/adapter/DownloadsAdapter.java deleted file mode 100644 index 25cf4c79..00000000 --- a/app/src/main/java/org/adrianvictor/geleia/adapter/DownloadsAdapter.java +++ /dev/null @@ -1,69 +0,0 @@ -package org.adrianvictor.geleia.adapter; - -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; - -import org.adrianvictor.geleia.R; -import org.adrianvictor.geleia.model.Song; - -import java.util.ArrayList; -import java.util.List; - -public class DownloadsAdapter extends RecyclerView.Adapter { - private final List mSongs; - private final int mLayoutId; - - public DownloadsAdapter(int layoutId) { - mLayoutId = layoutId; - this.mSongs = new ArrayList<>(); - } - - public void swapDataSet(List newSongs) { - mSongs.clear(); - if (newSongs != null) { - mSongs.addAll(newSongs); - } - notifyDataSetChanged(); - } - - @NonNull - @Override - public DownloadsAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View view = LayoutInflater.from(parent.getContext()).inflate(mLayoutId, parent, false); - return new ViewHolder(view); - } - - @Override - public void onBindViewHolder(@NonNull DownloadsAdapter.ViewHolder holder, int position) { - final Song song = mSongs.get(position); - - holder.title.setText(song.title); - holder.artist.setText(song.artistName); - - // TODO: Load album cover into holder.cover using Glide - } - - @Override - public int getItemCount() { - return mSongs.size(); - } - - public static class ViewHolder extends RecyclerView.ViewHolder { - public final TextView title; - public final TextView artist; - public final ImageView cover; - - public ViewHolder(View itemView) { - super(itemView); - title = itemView.findViewById(R.id.title); - artist = itemView.findViewById(R.id.text); - cover = itemView.findViewById(R.id.image); - } - } -} diff --git a/app/src/main/java/org/adrianvictor/geleia/adapter/MusicLibraryPagerAdapter.java b/app/src/main/java/org/adrianvictor/geleia/adapter/MusicLibraryPagerAdapter.java index 23c1162d..2979ee5c 100644 --- a/app/src/main/java/org/adrianvictor/geleia/adapter/MusicLibraryPagerAdapter.java +++ b/app/src/main/java/org/adrianvictor/geleia/adapter/MusicLibraryPagerAdapter.java @@ -10,9 +10,6 @@ import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentPagerAdapter; -import org.adrianvictor.geleia.activities.UnreachableActivity; -import org.adrianvictor.geleia.fragments.OfflineFragment; -import org.adrianvictor.geleia.fragments.library.DownloadsFragment; import org.adrianvictor.geleia.fragments.library.FavoritesFragment; import org.adrianvictor.geleia.model.Category; import org.adrianvictor.geleia.fragments.library.AlbumsFragment; @@ -160,8 +157,7 @@ public class MusicLibraryPagerAdapter extends FragmentPagerAdapter { ARTISTS(ArtistsFragment.class), GENRES(GenresFragment.class), PLAYLISTS(PlaylistsFragment.class), - FAVORITES(FavoritesFragment.class), - DOWNLOADS(DownloadsFragment.class); + FAVORITES(FavoritesFragment.class); private final Class mFragmentClass; diff --git a/app/src/main/java/org/adrianvictor/geleia/database/CacheDao.java b/app/src/main/java/org/adrianvictor/geleia/database/CacheDao.java index 54ff3733..8df0ded5 100644 --- a/app/src/main/java/org/adrianvictor/geleia/database/CacheDao.java +++ b/app/src/main/java/org/adrianvictor/geleia/database/CacheDao.java @@ -14,9 +14,6 @@ public interface CacheDao { @Insert(onConflict = OnConflictStrategy.REPLACE) void insertCache(Cache cache); - @Query("SELECT * FROM cache") - List getAll(); - @Query("SELECT * FROM songs LEFT JOIN cache USING(id) WHERE songs.id IN (:ids)") List getSongs(List ids); diff --git a/app/src/main/java/org/adrianvictor/geleia/dialogs/SongShareDialog.java b/app/src/main/java/org/adrianvictor/geleia/dialogs/SongShareDialog.java index d2becf6a..101c9377 100644 --- a/app/src/main/java/org/adrianvictor/geleia/dialogs/SongShareDialog.java +++ b/app/src/main/java/org/adrianvictor/geleia/dialogs/SongShareDialog.java @@ -31,7 +31,7 @@ public class SongShareDialog extends DialogFragment { final String currentlyListening = getString(R.string.currently_listening_to_x_by_x, song.title, song.artistName); return new MaterialDialog.Builder(requireActivity()) .title(R.string.what_do_you_want_to_share) - .items(getString(R.string.the_audio_file), "“" + currentlyListening + "”") + .items(getString(R.string.the_audio_file), "\u201C" + currentlyListening + "\u201D") .itemsCallback((materialDialog, view, i, charSequence) -> { switch (i) { case 0: diff --git a/app/src/main/java/org/adrianvictor/geleia/fragments/OfflineFragment.java b/app/src/main/java/org/adrianvictor/geleia/fragments/OfflineFragment.java deleted file mode 100644 index ed1d40a0..00000000 --- a/app/src/main/java/org/adrianvictor/geleia/fragments/OfflineFragment.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.adrianvictor.geleia.fragments; - -import android.app.Activity; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; - -import org.adrianvictor.geleia.R; - -public class OfflineFragment extends Fragment { - public static OfflineFragment newInstance() { - return new OfflineFragment(); - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_offline, container, false); - } -} diff --git a/app/src/main/java/org/adrianvictor/geleia/fragments/library/DownloadsFragment.java b/app/src/main/java/org/adrianvictor/geleia/fragments/library/DownloadsFragment.java deleted file mode 100644 index f6f259f5..00000000 --- a/app/src/main/java/org/adrianvictor/geleia/fragments/library/DownloadsFragment.java +++ /dev/null @@ -1,125 +0,0 @@ -package org.adrianvictor.geleia.fragments.library; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.GridLayoutManager; - -import org.adrianvictor.geleia.App; -import org.adrianvictor.geleia.adapter.DownloadsAdapter; - -import org.adrianvictor.geleia.database.Cache; -import org.adrianvictor.geleia.model.Song; -import org.adrianvictor.geleia.model.SortMethod; -import org.adrianvictor.geleia.model.SortOrder; - -import java.util.ArrayList; -import java.util.List; - -public class DownloadsFragment extends AbsLibraryPagerRecyclerViewCustomGridSizeFragment { - @NonNull - @Override - protected DownloadsAdapter createAdapter() { - return new DownloadsAdapter(getItemLayoutRes()); - } - - @NonNull - @Override - protected GridLayoutManager createLayoutManager() { - return new GridLayoutManager(getActivity(), getGridSize()); - } - - @NonNull - @Override - protected Void createQuery() { - return null; - } - - @Override - protected void loadItems(int index) { - new Thread(() -> { - List cachedEntries = App.getDatabase().cacheDao().getAll(); - - List songIds = new ArrayList<>(); - - for (Cache entry : cachedEntries) { - songIds.add(entry.id); - } - - final List downloadedSongs = App.getDatabase().cacheDao().getSongs(songIds); - - if (getActivity() != null) { - getActivity().runOnUiThread(() -> { - getAdapter().swapDataSet(downloadedSongs); - }); - } - }).start(); - } - @Override - protected int loadGridSize() { - return 1; - } - - @Override - protected void saveGridSize(int gridColumns) { - - } - - @Override - protected int loadGridSizeLand() { - return 1; - } - - @Override - protected void saveGridSizeLand(int gridColumns) { - - } - - @Override - protected void saveUsePalette(boolean usePalette) { - - } - - @Override - protected boolean loadUsePalette() { - return false; - } - - @Override - protected void setUsePalette(boolean usePalette) { - - } - - @Override - protected void setGridSize(int gridSize) { - - } - - @Override - protected SortMethod loadSortMethod() { - return SortMethod.ADDED; - } - - @Override - protected void saveSortMethod(SortMethod sortMethod) { - - } - - @Override - protected void setSortMethod(SortMethod sortMethod) { - - } - - @Override - protected SortOrder loadSortOrder() { - return SortOrder.ASCENDING; - } - - @Override - protected void saveSortOrder(SortOrder sortOrder) { - - } - - @Override - protected void setSortOrder(SortOrder sortOrder) { - - } -} diff --git a/app/src/main/java/org/adrianvictor/geleia/helper/MusicPlayerRemote.java b/app/src/main/java/org/adrianvictor/geleia/helper/MusicPlayerRemote.java index f409842c..4f658348 100644 --- a/app/src/main/java/org/adrianvictor/geleia/helper/MusicPlayerRemote.java +++ b/app/src/main/java/org/adrianvictor/geleia/helper/MusicPlayerRemote.java @@ -273,7 +273,7 @@ public class MusicPlayerRemote { public static boolean playNext(Song song) { if (musicService != null && musicService.queueManager != null) { - if (!getPlayingQueue().isEmpty()) { + if (getPlayingQueue().size() > 0) { musicService.queueManager.addSong(getPosition() + 1, song); } else { List queue = new ArrayList<>(); @@ -290,7 +290,7 @@ public class MusicPlayerRemote { public static boolean playNext(@NonNull List songs) { if (musicService != null && musicService.queueManager != null) { - if (!getPlayingQueue().isEmpty()) { + if (getPlayingQueue().size() > 0) { musicService.queueManager.addSongs(getPosition() + 1, songs); } else { openQueue(songs, 0, false); @@ -306,7 +306,7 @@ public class MusicPlayerRemote { public static boolean enqueue(Song song) { if (musicService != null && musicService.queueManager != null) { - if (!getPlayingQueue().isEmpty()) { + if (getPlayingQueue().size() > 0) { musicService.queueManager.addSong(song); } else { List queue = new ArrayList<>(); @@ -323,7 +323,7 @@ public class MusicPlayerRemote { public static boolean enqueue(@NonNull List songs) { if (musicService != null && musicService.queueManager != null) { - if (!getPlayingQueue().isEmpty()) { + if (getPlayingQueue().size() > 0) { musicService.queueManager.addSongs(songs); } else { openQueue(songs, 0, false); diff --git a/app/src/main/java/org/adrianvictor/geleia/model/Album.java b/app/src/main/java/org/adrianvictor/geleia/model/Album.java index 81e4e481..36ff5554 100644 --- a/app/src/main/java/org/adrianvictor/geleia/model/Album.java +++ b/app/src/main/java/org/adrianvictor/geleia/model/Album.java @@ -29,10 +29,10 @@ public class Album implements Parcelable { this.title = itemDto.getName(); this.year = itemDto.getProductionYear() != null ? itemDto.getProductionYear() : 0; - if (!itemDto.getAlbumArtists().isEmpty()) { + if (itemDto.getAlbumArtists().size() != 0) { this.artistId = itemDto.getAlbumArtists().get(0).getId(); this.artistName = itemDto.getAlbumArtists().get(0).getName(); - } else if (!itemDto.getArtistItems().isEmpty()) { + } else if (itemDto.getArtistItems().size() != 0) { this.artistId = itemDto.getArtistItems().get(0).getId(); this.artistName = itemDto.getArtistItems().get(0).getName(); } diff --git a/app/src/main/java/org/adrianvictor/geleia/model/Category.java b/app/src/main/java/org/adrianvictor/geleia/model/Category.java index 3c897e81..91e54717 100644 --- a/app/src/main/java/org/adrianvictor/geleia/model/Category.java +++ b/app/src/main/java/org/adrianvictor/geleia/model/Category.java @@ -10,8 +10,7 @@ public enum Category { ARTISTS(R.string.artists), GENRES(R.string.genres), PLAYLISTS(R.string.playlists), - FAVORITES(R.string.favorites), - DOWNLOADS(R.string.downloads); + FAVORITES(R.string.favorites); @StringRes public final int title; diff --git a/app/src/main/java/org/adrianvictor/geleia/model/Song.java b/app/src/main/java/org/adrianvictor/geleia/model/Song.java index b9218cb3..277a5942 100644 --- a/app/src/main/java/org/adrianvictor/geleia/model/Song.java +++ b/app/src/main/java/org/adrianvictor/geleia/model/Song.java @@ -67,10 +67,10 @@ public class Song implements Parcelable { this.albumId = itemDto.getAlbumId(); this.albumName = itemDto.getAlbum(); - if (!itemDto.getArtistItems().isEmpty()) { + if (itemDto.getArtistItems().size() != 0) { this.artistId = itemDto.getArtistItems().get(0).getId(); this.artistName = itemDto.getArtistItems().get(0).getName(); - } else if (!itemDto.getAlbumArtists().isEmpty()) { + } else if (itemDto.getAlbumArtists().size() != 0) { this.artistId = itemDto.getAlbumArtists().get(0).getId(); this.artistName = itemDto.getAlbumArtists().get(0).getName(); } @@ -93,7 +93,7 @@ public class Song implements Parcelable { this.supportsTranscoding = source.getSupportsTranscoding(); - if (source.getMediaStreams() != null && !source.getMediaStreams().isEmpty()) { + if (source.getMediaStreams() != null && source.getMediaStreams().size() != 0) { MediaStream stream = source.getMediaStreams().get(0); this.codec = stream.getCodec(); diff --git a/app/src/main/java/org/adrianvictor/geleia/service/DownloadService.java b/app/src/main/java/org/adrianvictor/geleia/service/DownloadService.java index 7cb72766..b3a61b5f 100644 --- a/app/src/main/java/org/adrianvictor/geleia/service/DownloadService.java +++ b/app/src/main/java/org/adrianvictor/geleia/service/DownloadService.java @@ -4,8 +4,6 @@ import android.app.Service; import android.content.Intent; import android.net.Uri; import android.os.IBinder; -import android.util.Log; - import androidx.documentfile.provider.DocumentFile; import org.adrianvictor.geleia.App; @@ -13,7 +11,6 @@ import org.adrianvictor.geleia.BuildConfig; import org.adrianvictor.geleia.database.Cache; import org.adrianvictor.geleia.model.Song; import org.adrianvictor.geleia.service.notifications.DownloadNotification; -import org.adrianvictor.geleia.service.notifications.ErrorNotification; import org.adrianvictor.geleia.util.MusicUtil; import org.adrianvictor.geleia.util.PreferenceUtil; @@ -26,8 +23,8 @@ import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +@SuppressWarnings("ResultOfMethodCallIgnored") public class DownloadService extends Service { - public static final String TAG = DownloadService.class.getSimpleName(); public static final String PACKAGE_NAME = BuildConfig.APPLICATION_ID; public static final String ACTION_START = PACKAGE_NAME + ".action.start"; public static final String ACTION_CANCEL = PACKAGE_NAME + ".action.cancel"; @@ -36,8 +33,6 @@ public class DownloadService extends Service { private ExecutorService executor; private DownloadNotification notification; - private static final Object lock = new Object(); - @Override public void onCreate() { super.onCreate(); @@ -60,7 +55,6 @@ public class DownloadService extends Service { break; case DownloadService.ACTION_START: List songs = intent.getParcelableArrayListExtra(EXTRA_SONGS); - assert songs != null; for (Song song : songs) { download(song); notification.start(song); @@ -84,24 +78,19 @@ public class DownloadService extends Service { String location = PreferenceUtil.getInstance(App.getInstance()).getLocationDownload(); DocumentFile root; - if (location.startsWith("content://")) { - root = DocumentFile.fromTreeUri(this, Uri.parse(location)); - } else { + if (location.equals(getApplicationContext().getCacheDir().toString())) { root = DocumentFile.fromFile(new File(location)); + } else { + root = DocumentFile.fromTreeUri(this, Uri.parse(location)); } - DocumentFile artist; - DocumentFile album; - - synchronized (lock) { - artist = root.findFile(MusicUtil.ascii(song.artistName)); - if (artist == null) { - artist = root.createDirectory(MusicUtil.ascii(song.artistName)); - } - album = artist.findFile(MusicUtil.ascii(song.albumName)); - if (album == null) { - album = artist.createDirectory(MusicUtil.ascii(song.albumName)); - } + DocumentFile artist = root.findFile(MusicUtil.ascii(song.artistName)); + if (artist == null) { + artist = root.createDirectory(MusicUtil.ascii(song.artistName)); + } + DocumentFile album = artist.findFile(MusicUtil.ascii(song.albumName)); + if (album == null) { + album = artist.createDirectory(MusicUtil.ascii(song.albumName)); } String fileName = song.discNumber + "." + song.trackNumber + " - " + MusicUtil.ascii(song.title) + "." + song.container; @@ -127,8 +116,7 @@ public class DownloadService extends Service { App.getDatabase().cacheDao().insertCache(new Cache(song)); notification.stop(song); } catch (Exception e) { - Log.e(TAG, "Failed to download song: " + song.title, e); - ErrorNotification.show(this, e.getMessage()); + e.printStackTrace(); } }); } diff --git a/app/src/main/java/org/adrianvictor/geleia/service/LoginService.java b/app/src/main/java/org/adrianvictor/geleia/service/LoginService.java index 55db8664..82f7a2a7 100644 --- a/app/src/main/java/org/adrianvictor/geleia/service/LoginService.java +++ b/app/src/main/java/org/adrianvictor/geleia/service/LoginService.java @@ -46,19 +46,9 @@ public class LoginService extends Service { if (user == null) { Toast.makeText(this, context.getResources().getString(R.string.error_unexpected), Toast.LENGTH_SHORT).show(); - sendBroadcast(new Intent(STATE_OFFLINE)); return; } - if (App.getApiClient() == null) { - try { - App.createApiClient(context); - } catch (Exception e) { - sendBroadcast(new Intent(STATE_OFFLINE)); - return; - } - } - App.getApiClient().ChangeServerLocation(user.server); App.getApiClient().SetAuthenticationInfo(user.token, user.id); App.getApiClient().GetSystemInfoAsync(new Response() { diff --git a/app/src/main/java/org/adrianvictor/geleia/service/notifications/DownloadNotification.java b/app/src/main/java/org/adrianvictor/geleia/service/notifications/DownloadNotification.java index 014eb570..fe6c0efa 100644 --- a/app/src/main/java/org/adrianvictor/geleia/service/notifications/DownloadNotification.java +++ b/app/src/main/java/org/adrianvictor/geleia/service/notifications/DownloadNotification.java @@ -1,134 +1,114 @@ package org.adrianvictor.geleia.service.notifications; -import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.os.Build; -import android.widget.RemoteViews; +import androidx.annotation.RequiresApi; import androidx.core.app.NotificationCompat; import org.adrianvictor.geleia.R; +import org.adrianvictor.geleia.activities.MainActivity; import org.adrianvictor.geleia.model.Song; import org.adrianvictor.geleia.service.DownloadService; -import java.util.Collections; -import java.util.LinkedList; +import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; + +import static android.content.Context.NOTIFICATION_SERVICE; public class DownloadNotification { - private static final String CHANNEL_ID = "download_channel"; - private final int ID = 2; + private static final String CHANNEL_ID = DownloadNotification.class.getSimpleName(); + private static final int NOTIFICATION_ID = 2; + private final Context context; private final NotificationManager notificationManager; - private final List queue = Collections.synchronizedList(new LinkedList<>()); - private long totalSize; - private long downloadedSize; - private int lastPercentage = -1; + private final List songs; + + private int current; + private int maximum; public DownloadNotification(Context context) { + this.notificationManager = (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE); this.context = context; - this.notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - createNotificationChannel(); + + this.songs = new ArrayList<>(); } - private void createNotificationChannel() { + public synchronized void start(Song song) { + this.songs.add(song); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - NotificationChannel channel = new NotificationChannel( - CHANNEL_ID, - context.getString(R.string.download_channel_name), - NotificationManager.IMPORTANCE_LOW - ); - notificationManager.createNotificationChannel(channel); + createNotificationChannel(); } } - public void start(Song song) { - queue.add(song); - if (queue.size() == 1) { // This is the first song of a new batch - totalSize = 0; - downloadedSize = 0; - lastPercentage = -1; - notificationManager.notify(ID, getNotification()); - } else if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { - // For KitKat, update for every new song to show correct count. - notificationManager.notify(ID, getNotification()); + public synchronized void update(int current, int maximum) { + this.current += current; + this.maximum += maximum; + + Intent action = new Intent(context, MainActivity.class).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + PendingIntent clickIntent = PendingIntent.getActivity(context, 0, action, PendingIntent.FLAG_IMMUTABLE); + + Intent cancel = new Intent(context, DownloadService.class).setAction(DownloadService.ACTION_CANCEL); + PendingIntent pendingCancel = PendingIntent.getService(context, 0, cancel, PendingIntent.FLAG_IMMUTABLE); + + NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle(); + for (Song item : songs.stream().limit(5).collect(Collectors.toList())) { + style.addLine(item.title); } - } - - public void update(long downloaded, long total) { - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { - return; // No progress updates for KitKat - } - - synchronized (this) { - totalSize += total; - downloadedSize += downloaded; - - int percentage = 0; - if (totalSize > 0) { - percentage = (int) ((downloadedSize * 100) / totalSize); - } - - if (percentage > lastPercentage) { - lastPercentage = percentage; - notificationManager.notify(ID, getNotification()); - } - } - } - - public void stop(Song song) { - queue.remove(song); - if (queue.isEmpty()) { - notificationManager.cancel(ID); - } else { - // Update notification to show new queue size. - // On KitKat, this is the only update after a download finishes. - notificationManager.notify(ID, getNotification()); - } - } - - public void cancelAll() { - queue.clear(); - notificationManager.cancel(ID); - } - - private Notification getNotification() { - Intent intent = new Intent(context, DownloadService.class); - intent.setAction(DownloadService.ACTION_CANCEL); - - int flags = 0; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - flags |= PendingIntent.FLAG_IMMUTABLE; - } - PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, flags); NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID) - .setSmallIcon(R.drawable.ic_notification) - .setContentTitle(context.getString(R.string.downloading_songs)) - .setOngoing(true) - .addAction(android.R.drawable.ic_menu_close_clear_cancel, context.getString(android.R.string.cancel), pendingIntent); + .setSmallIcon(R.drawable.ic_notification) + .setContentIntent(clickIntent) + .setContentTitle(String.format(context.getString(R.string.downloading_x_songs), songs.size())) + .setProgress(this.maximum, this.current, false) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + .addAction(R.drawable.ic_close_white_24dp, context.getString(R.string.action_cancel), pendingCancel) + .setStyle(style) + .setShowWhen(false); - String contentText = context.getResources().getQuantityString(R.plurals.downloading_s_songs, queue.size(), queue.size()); + notificationManager.notify(NOTIFICATION_ID, builder.build()); + } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.notification_download); - remoteViews.setTextViewText(R.id.notification_download_title, contentText); - int progress = lastPercentage > 0 ? lastPercentage : 0; - remoteViews.setProgressBar(R.id.notification_download_progress, 100, progress, totalSize == 0 && downloadedSize == 0); - builder.setCustomContentView(remoteViews); - } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { - int progress = lastPercentage > 0 ? lastPercentage : 0; - builder.setProgress(100, progress, totalSize == 0 && downloadedSize == 0); - builder.setContentText(contentText); - } else { // KitKat - builder.setContentText(contentText); + public synchronized void stop(Song song) { + if (song != null) { + songs.remove(song); + } else { + songs.clear(); } - return builder.build(); + if (songs.size() != 0) { + return; + } + + current = 0; + maximum = 0; + + notificationManager.cancel(NOTIFICATION_ID); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + notificationManager.deleteNotificationChannel(CHANNEL_ID); + } + } + + @RequiresApi(Build.VERSION_CODES.O) + private void createNotificationChannel() { + NotificationChannel notificationChannel = notificationManager.getNotificationChannel(CHANNEL_ID); + + if (notificationChannel == null) { + notificationChannel = new NotificationChannel(CHANNEL_ID, context.getString(R.string.action_download), NotificationManager.IMPORTANCE_LOW); + + notificationChannel.setDescription(context.getString(R.string.playing_notification_description)); + notificationChannel.enableLights(false); + notificationChannel.enableVibration(false); + + notificationManager.createNotificationChannel(notificationChannel); + } } } diff --git a/app/src/main/java/org/adrianvictor/geleia/service/notifications/ErrorNotification.java b/app/src/main/java/org/adrianvictor/geleia/service/notifications/ErrorNotification.java deleted file mode 100644 index 793a9db7..00000000 --- a/app/src/main/java/org/adrianvictor/geleia/service/notifications/ErrorNotification.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.adrianvictor.geleia.service.notifications; - -import static android.content.Context.NOTIFICATION_SERVICE; - -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.content.Context; -import android.os.Build; - -import androidx.core.app.NotificationCompat; - -import org.adrianvictor.geleia.R; - -public class ErrorNotification { - private static final String CHANNEL_ID = ErrorNotification.class.getSimpleName(); - private static final int NOTIFICATION_ID = 3; - - private ErrorNotification() {} - - public static void show(Context context, String error) { - NotificationManager notificationManager = (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE); - createNotificationChannel(notificationManager); - - NotificationCompat.BigTextStyle style = new NotificationCompat.BigTextStyle(); - style.setBigContentTitle("Error:").bigText(error); - - NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID) - .setSmallIcon(R.drawable.ic_bug_report_white_24dp) - .setContentTitle(context.getString(R.string.error_notification_title)) - .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) - .setStyle(style) - .setContentText("Expand the notification for details."); - } - - public static void createNotificationChannel(NotificationManager notificationManager) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - NotificationChannel channel = notificationManager.getNotificationChannel(CHANNEL_ID); - - if (channel == null) { - channel = new NotificationChannel(CHANNEL_ID, "Errors", NotificationManager.IMPORTANCE_LOW); - channel.setDescription("Displays all sorts of errors."); - channel.enableLights(false); - channel.enableVibration(true); - - notificationManager.createNotificationChannel(channel); - } - } - } -} diff --git a/app/src/main/java/org/adrianvictor/geleia/service/playback/LocalPlayer.java b/app/src/main/java/org/adrianvictor/geleia/service/playback/LocalPlayer.java index 2832d553..0a63184d 100644 --- a/app/src/main/java/org/adrianvictor/geleia/service/playback/LocalPlayer.java +++ b/app/src/main/java/org/adrianvictor/geleia/service/playback/LocalPlayer.java @@ -2,7 +2,6 @@ package org.adrianvictor.geleia.service.playback; import android.content.Context; import android.net.Uri; -import android.os.Build; import android.util.Log; import android.widget.Toast; @@ -142,17 +141,11 @@ public class LocalPlayer implements Playback { private List createMediaItems(List queue) { return queue.stream().map(song -> { - String fileUri = MusicUtil.getFileUri(song); - Uri uri; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && fileUri.startsWith("content://")) { - uri = Uri.parse(fileUri); - } else { - File audio = new File(fileUri); - uri = Uri.fromFile(audio); + File audio = new File(MusicUtil.getFileUri(song)); + Uri uri = Uri.fromFile(audio); - if (!audio.exists()) { - uri = Uri.parse(MusicUtil.getTranscodeUri(song)); - } + if (!audio.exists()) { + uri = Uri.parse(MusicUtil.getTranscodeUri(song)); } List containers = PreferenceUtil.getInstance(context).getDirectPlayCodecs().stream() diff --git a/app/src/main/java/org/adrianvictor/geleia/util/ImageUtil.java b/app/src/main/java/org/adrianvictor/geleia/util/ImageUtil.java index de326d91..1388f345 100644 --- a/app/src/main/java/org/adrianvictor/geleia/util/ImageUtil.java +++ b/app/src/main/java/org/adrianvictor/geleia/util/ImageUtil.java @@ -14,7 +14,6 @@ import androidx.annotation.ColorInt; import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.appcompat.content.res.AppCompatResources; import androidx.core.graphics.drawable.DrawableCompat; import androidx.core.content.res.ResourcesCompat; @@ -92,22 +91,18 @@ public class ImageUtil { } public static Drawable getTintedVectorDrawable(@NonNull Context context, @DrawableRes int resId, @ColorInt int color) { - final Drawable drawable = AppCompatResources.getDrawable(context, resId); + Drawable drawable = getVectorDrawable(context.getResources(), resId, context.getTheme()); - if (drawable != null) { - Drawable wrappedDrawable = DrawableCompat.wrap(drawable).mutate(); + DrawableCompat.setTintMode(drawable, PorterDuff.Mode.SRC_IN); + DrawableCompat.setTint(drawable, color); - DrawableCompat.setTintMode(wrappedDrawable, PorterDuff.Mode.SRC_IN); - DrawableCompat.setTint(wrappedDrawable, color); - return wrappedDrawable; - } - return null; + return drawable; } - public static Drawable getVectorDrawable(@NonNull Context context, @DrawableRes int resId) { - return AppCompatResources.getDrawable(context, resId); + return getVectorDrawable(context.getResources(), resId, context.getTheme()); } + public static Drawable resolveDrawable(@NonNull Context context, @AttrRes int drawableAttr) { TypedArray a = context.obtainStyledAttributes(new int[]{drawableAttr}); Drawable drawable = a.getDrawable(0); diff --git a/app/src/main/java/org/adrianvictor/geleia/util/MusicUtil.java b/app/src/main/java/org/adrianvictor/geleia/util/MusicUtil.java index 8d62cbbf..8831fe38 100644 --- a/app/src/main/java/org/adrianvictor/geleia/util/MusicUtil.java +++ b/app/src/main/java/org/adrianvictor/geleia/util/MusicUtil.java @@ -3,7 +3,6 @@ package org.adrianvictor.geleia.util; import android.content.Context; import android.content.Intent; import android.net.Uri; -import android.os.Build; import android.text.TextUtils; import android.util.Log; import android.widget.Toast; @@ -19,7 +18,6 @@ import org.adrianvictor.geleia.model.Codec; import org.adrianvictor.geleia.model.Genre; import org.adrianvictor.geleia.model.Song; -import org.adrianvictor.geleia.service.notifications.ErrorNotification; import org.jellyfin.apiclient.interaction.ApiClient; import org.jellyfin.apiclient.interaction.Response; import org.jellyfin.apiclient.model.dto.UserItemDataDto; @@ -51,7 +49,7 @@ public class MusicUtil { List codecs = preferenceUtil.getDirectPlayCodecs(); Stream values = codecs.stream().map(codec -> codec.value); - if (!codecs.isEmpty()) { + if (codecs.size() != 0) { builder.append("&Container=").append(values.collect(Collectors.joining(","))); } @@ -83,8 +81,7 @@ public class MusicUtil { public static String getFileUri(Song song) { String location = PreferenceUtil.getInstance(App.getInstance()).getLocationDownload(); File root = new File(location, "music"); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !location.equals(App.getInstance().getCacheDir().toString())) { + if (!location.equals(App.getInstance().getCacheDir().toString())) { Uri uri = Uri.parse(location); return new File(uri.getPath()).getAbsolutePath(); } @@ -105,7 +102,7 @@ public class MusicUtil { try { return new Intent(); } catch (IllegalArgumentException e) { - ErrorNotification.show(context, e.getMessage()); + e.printStackTrace(); Toast.makeText(context, R.string.error_share_file, Toast.LENGTH_SHORT).show(); return new Intent(); } @@ -113,7 +110,7 @@ public class MusicUtil { @NonNull public static String getArtistInfoString(@NonNull final Context context, @NonNull final Artist artist) { - return !artist.genres.isEmpty() ? artist.genres.get(0).name : ""; + return artist.genres.size() != 0 ? artist.genres.get(0).name : ""; } @NonNull diff --git a/app/src/main/java/org/adrianvictor/geleia/util/NavigationUtil.java b/app/src/main/java/org/adrianvictor/geleia/util/NavigationUtil.java index 30460c35..924c47ba 100644 --- a/app/src/main/java/org/adrianvictor/geleia/util/NavigationUtil.java +++ b/app/src/main/java/org/adrianvictor/geleia/util/NavigationUtil.java @@ -12,7 +12,6 @@ import androidx.core.util.Pair; import org.adrianvictor.geleia.activities.LoginActivity; import org.adrianvictor.geleia.activities.MainActivity; import org.adrianvictor.geleia.activities.SelectActivity; -import org.adrianvictor.geleia.activities.UnreachableActivity; import org.adrianvictor.geleia.model.Album; import org.adrianvictor.geleia.model.Artist; import org.adrianvictor.geleia.model.Genre; @@ -62,13 +61,6 @@ public class NavigationUtil { context.startActivity(intent); } - public static void startUnreachable(Context context) { - final Intent intent = new Intent(context, UnreachableActivity.class); - - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); - context.startActivity(intent); - } - public static void startSelect(Context context) { final Intent intent = new Intent(context, SelectActivity.class); diff --git a/app/src/main/java/org/adrianvictor/geleia/util/PlaylistUtil.java b/app/src/main/java/org/adrianvictor/geleia/util/PlaylistUtil.java index 731724ea..9144b16a 100644 --- a/app/src/main/java/org/adrianvictor/geleia/util/PlaylistUtil.java +++ b/app/src/main/java/org/adrianvictor/geleia/util/PlaylistUtil.java @@ -48,7 +48,7 @@ public class PlaylistUtil { PlaylistCreationRequest request = new PlaylistCreationRequest(); request.setUserId(App.getApiClient().getCurrentUserId()); request.setName(name); - if (!ids.isEmpty()) request.setItemIdList(ids); + if (ids.size() != 0) request.setItemIdList(ids); App.getApiClient().CreatePlaylist(request, new Response<>()); } diff --git a/app/src/main/java/org/adrianvictor/geleia/util/PreferenceUtil.java b/app/src/main/java/org/adrianvictor/geleia/util/PreferenceUtil.java index 71ff53d5..1becdcb9 100644 --- a/app/src/main/java/org/adrianvictor/geleia/util/PreferenceUtil.java +++ b/app/src/main/java/org/adrianvictor/geleia/util/PreferenceUtil.java @@ -448,6 +448,7 @@ public final class PreferenceUtil { }).collect(Collectors.toList()); } + @SuppressWarnings("SimplifyStreamApiCallChains") public void setCategories(List categories) { List values = categories.stream().map(category -> { return category.select ? category.toString() : category.toString().toLowerCase(); diff --git a/app/src/main/java/org/adrianvictor/geleia/views/IconImageView.java b/app/src/main/java/org/adrianvictor/geleia/views/IconImageView.java index 6af3adc6..557797cf 100644 --- a/app/src/main/java/org/adrianvictor/geleia/views/IconImageView.java +++ b/app/src/main/java/org/adrianvictor/geleia/views/IconImageView.java @@ -10,27 +10,23 @@ import org.adrianvictor.geleia.util.ThemeUtil; import org.adrianvictor.geleia.R; public class IconImageView extends AppCompatImageView { - public IconImageView(Context context) { super(context); + init(context); } public IconImageView(Context context, AttributeSet attrs) { super(context, attrs); + init(context); } public IconImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); + init(context); } - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - init(); - } - - private void init() { - if (getContext() == null) return; - setColorFilter(ThemeUtil.getColorResource(getContext(), R.attr.iconColor), PorterDuff.Mode.SRC_IN); + private void init(Context context) { + if (context == null) return; + setColorFilter(ThemeUtil.getColorResource(context, R.attr.iconColor), PorterDuff.Mode.SRC_IN); } } diff --git a/app/src/main/java/org/adrianvictor/geleia/views/shortcuts/DynamicShortcutManager.java b/app/src/main/java/org/adrianvictor/geleia/views/shortcuts/DynamicShortcutManager.java index c45ab175..ee889cd8 100644 --- a/app/src/main/java/org/adrianvictor/geleia/views/shortcuts/DynamicShortcutManager.java +++ b/app/src/main/java/org/adrianvictor/geleia/views/shortcuts/DynamicShortcutManager.java @@ -36,7 +36,7 @@ public class DynamicShortcutManager { } public void initDynamicShortcuts() { - if (shortcutManager.getDynamicShortcuts().isEmpty()) { + if (shortcutManager.getDynamicShortcuts().size() == 0) { shortcutManager.setDynamicShortcuts(getDefaultShortcuts()); } } diff --git a/app/src/main/res/drawable/ic_launcher_nodpi.xml b/app/src/main/res/drawable/ic_launcher_nodpi.xml index a8b409b1..1361edfa 100644 --- a/app/src/main/res/drawable/ic_launcher_nodpi.xml +++ b/app/src/main/res/drawable/ic_launcher_nodpi.xml @@ -1,4 +1,14 @@ - - - - \ No newline at end of file + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index 61ff6cc1..a807a132 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -19,122 +19,134 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" /> - + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintBottom_toTopOf="@id/username_layout" /> - + + + android:inputType="textNoSuggestions" + android:hint="@string/username" /> - + - + - + - + - + - + - + - + - - - - - - - + diff --git a/app/src/main/res/layout/activity_unreachable.xml b/app/src/main/res/layout/activity_unreachable.xml deleted file mode 100644 index ddc6f028..00000000 --- a/app/src/main/res/layout/activity_unreachable.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - -