diff --git a/app/build.gradle b/app/build.gradle index 080231ad..c09df6a8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -89,7 +89,7 @@ dependencies { implementation 'com.afollestad:material-cab:2.0.1' implementation 'me.zhanghai.android.materialprogressbar:library:1.6.1' - implementation 'com.github.QuadFlask:colorpicker:ef73ced217' + implementation 'com.github.QuadFlask:colorpicker:0.0.15' implementation 'com.github.codekidX:storage-chooser:2.0.4.4' implementation 'me.relex:circleindicator:2.1.6' implementation 'com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:1.0.0' diff --git a/app/src/main/java/com/dkanada/gramophone/activities/SettingsActivity.java b/app/src/main/java/com/dkanada/gramophone/activities/SettingsActivity.java index 3743eb68..e073b6a9 100644 --- a/app/src/main/java/com/dkanada/gramophone/activities/SettingsActivity.java +++ b/app/src/main/java/com/dkanada/gramophone/activities/SettingsActivity.java @@ -1,12 +1,19 @@ package com.dkanada.gramophone.activities; +import android.Manifest; +import android.content.Intent; import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.view.View; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceManager; @@ -20,6 +27,8 @@ import com.dkanada.gramophone.dialogs.preferences.NowPlayingPreferenceDialog; import com.dkanada.gramophone.activities.base.AbsBaseActivity; import com.dkanada.gramophone.util.PreferenceUtil; +import java.io.File; + public class SettingsActivity extends AbsBaseActivity { private ActivitySettingsBinding binding; @@ -43,6 +52,30 @@ public class SettingsActivity extends AbsBaseActivity { } public static class SettingsFragment extends PreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener { + + private ActivityResultLauncher dirPickerLauncher; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + dirPickerLauncher = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == RESULT_OK) { + Intent data = result.getData(); + if (data != null) { + Uri uri = data.getData(); + requireContext().getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(requireContext()).edit(); + editor.putString(PreferenceUtil.LOCATION_DOWNLOAD, uri.toString()); + editor.apply(); + invalidateSettings(); + } + } + }); + } + @Override public void onCreatePreferences(Bundle bundle, String s) { addPreferencesFromResource(R.xml.pref_library); @@ -93,14 +126,6 @@ public class SettingsActivity extends AbsBaseActivity { blurAlbumCoverPreference.setEnabled(false); } - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) { - downloadLocationPreference.setEnabled(false); - - // stock Android 11 removed the album cover on lock screens - // supported on LineageOS so we might want to add a check at some point - showAlbumCoverPreference.setEnabled(false); - } - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R) { // custom notification layouts were removed entirely in Android 12 classicNotification.setEnabled(false); @@ -117,13 +142,32 @@ public class SettingsActivity extends AbsBaseActivity { return false; }); - // use this to set default state for playback screen and notification style - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(requireContext()); + downloadLocationPreference.setOnPreferenceClickListener(preference -> { + openDirectoryPicker(); + return true; + }); + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(requireContext()); + String downloadLocation = preferences.getString(PreferenceUtil.LOCATION_DOWNLOAD, null); + if (downloadLocation != null) { + Uri uri = Uri.parse(downloadLocation); + File file = new File(uri.getPath()); + downloadLocationPreference.setSummary(file.getPath()); + } else { + downloadLocationPreference.setSummary(R.string.pref_title_download_location); + } + + + // use this to set default state for playback screen and notification style onSharedPreferenceChanged(preferences, PreferenceUtil.NOW_PLAYING_SCREEN); onSharedPreferenceChanged(preferences, PreferenceUtil.CLASSIC_NOTIFICATION); } + private void openDirectoryPicker() { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); + dirPickerLauncher.launch(intent); + } + @Override @SuppressWarnings("ConstantConditions") public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { diff --git a/app/src/main/java/com/dkanada/gramophone/service/DownloadService.java b/app/src/main/java/com/dkanada/gramophone/service/DownloadService.java index ca45ba51..5d8d8c0e 100644 --- a/app/src/main/java/com/dkanada/gramophone/service/DownloadService.java +++ b/app/src/main/java/com/dkanada/gramophone/service/DownloadService.java @@ -2,7 +2,9 @@ package com.dkanada.gramophone.service; import android.app.Service; import android.content.Intent; +import android.net.Uri; import android.os.IBinder; +import androidx.documentfile.provider.DocumentFile; import com.dkanada.gramophone.App; import com.dkanada.gramophone.BuildConfig; @@ -13,8 +15,6 @@ import com.dkanada.gramophone.util.MusicUtil; import com.dkanada.gramophone.util.PreferenceUtil; import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.net.URL; @@ -76,17 +76,28 @@ public class DownloadService extends Service { URL url = new URL(MusicUtil.getDownloadUri(song)); URLConnection connection = url.openConnection(); - String cache = PreferenceUtil.getInstance(App.getInstance()).getLocationCache(); - File download = new File(cache, "download/" + song.id); - File audio = new File(MusicUtil.getFileUri(song)); + String location = PreferenceUtil.getInstance(App.getInstance()).getLocationDownload(); + DocumentFile root; + if (location.equals(getApplicationContext().getCacheDir().toString())) { + root = DocumentFile.fromFile(new File(location)); + } else { + root = DocumentFile.fromTreeUri(this, Uri.parse(location)); + } - download.getParentFile().mkdirs(); - download.createNewFile(); - audio.getParentFile().mkdirs(); - audio.createNewFile(); + 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; + DocumentFile audio = album.createFile("audio/" + song.container, fileName); InputStream input = connection.getInputStream(); - OutputStream output = new FileOutputStream(download); + OutputStream output = getContentResolver().openOutputStream(audio.getUri()); connection.connect(); @@ -102,17 +113,6 @@ public class DownloadService extends Service { input.close(); output.close(); - input = new FileInputStream(download); - output = new FileOutputStream(audio); - - while ((count = input.read(data)) != -1) { - output.write(data, 0, count); - } - - input.close(); - output.close(); - - download.delete(); App.getDatabase().cacheDao().insertCache(new Cache(song)); notification.stop(song); } catch (Exception e) { diff --git a/app/src/main/java/com/dkanada/gramophone/util/MusicUtil.java b/app/src/main/java/com/dkanada/gramophone/util/MusicUtil.java index 2c89b648..583152ff 100644 --- a/app/src/main/java/com/dkanada/gramophone/util/MusicUtil.java +++ b/app/src/main/java/com/dkanada/gramophone/util/MusicUtil.java @@ -2,6 +2,7 @@ package com.dkanada.gramophone.util; import android.content.Context; import android.content.Intent; +import android.net.Uri; import android.text.TextUtils; import android.util.Log; import android.widget.Toast; @@ -78,7 +79,12 @@ public class MusicUtil { } public static String getFileUri(Song song) { - File root = new File(PreferenceUtil.getInstance(App.getInstance()).getLocationDownload(), "music"); + String location = PreferenceUtil.getInstance(App.getInstance()).getLocationDownload(); + File root = new File(location, "music"); + if (!location.equals(App.getInstance().getCacheDir().toString())) { + Uri uri = Uri.parse(location); + return new File(uri.getPath()).getAbsolutePath(); + } String path = "/" + ascii(song.artistName) + "/" + ascii(song.albumName); String name = "/" + song.discNumber + "." + song.trackNumber + " - " + ascii(song.title) + "." + song.container; diff --git a/app/src/main/java/com/dkanada/gramophone/util/PreferenceUtil.java b/app/src/main/java/com/dkanada/gramophone/util/PreferenceUtil.java index 59c2187a..c133659f 100644 --- a/app/src/main/java/com/dkanada/gramophone/util/PreferenceUtil.java +++ b/app/src/main/java/com/dkanada/gramophone/util/PreferenceUtil.java @@ -84,7 +84,6 @@ public final class PreferenceUtil { public static final String GAIN_OFFSET = "gain_offset"; public static final String LOCATION_DOWNLOAD = "location_download"; - public static final String LOCATION_CACHE = "location_cache"; public static final String IMAGE_CACHE_SIZE = "image_cache_size"; public static final String MEDIA_CACHE_SIZE = "media_cache_size"; @@ -97,7 +96,7 @@ public final class PreferenceUtil { preferences.edit().putString(GENERAL_THEME, Theme.valueOf(theme.toUpperCase()).toString()).commit(); preferences.edit().putString(IMAGE_CACHE_SIZE, imageSize.substring(0, imageSize.length() - 6)).commit(); - preferences.edit().putString(MEDIA_CACHE_SIZE, mediaSize.substring(0, imageSize.length() - 6)).commit(); + preferences.edit().putString(MEDIA_CACHE_SIZE, mediaSize.substring(0, mediaSize.length() - 6)).commit(); } }; @@ -424,7 +423,7 @@ public final class PreferenceUtil { } public final String getLocationCache() { - return mPreferences.getString(LOCATION_CACHE, mContext.getCacheDir().toString()); + return mContext.getCacheDir().toString(); } public final long getImageCacheSize() { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 20aa00a9..da4a38e3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -107,6 +107,7 @@ Library Lock Screen Notification + Storage Direct Play Codecs Categories @@ -143,6 +144,9 @@ Configure visibility and order of display categories. Disable direct play codecs to force transcoding. Adjust the gain of the music player\'s output + Save Lyrics + Cached Songs Location + Select where to store cached songs Delete Remove diff --git a/app/src/main/res/xml/pref_cache.xml b/app/src/main/res/xml/pref_cache.xml index 756d48ed..90c5e7dd 100644 --- a/app/src/main/res/xml/pref_cache.xml +++ b/app/src/main/res/xml/pref_cache.xml @@ -4,7 +4,7 @@ - diff --git a/app/src/main/res/xml/pref_storage.xml b/app/src/main/res/xml/pref_storage.xml new file mode 100644 index 00000000..122b1636 --- /dev/null +++ b/app/src/main/res/xml/pref_storage.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file