commit
28478b8755
34 changed files with 938 additions and 179 deletions
|
@ -13,8 +13,8 @@ android {
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
|
|
||||||
applicationId "code.name.monkey.retromusic"
|
applicationId "code.name.monkey.retromusic"
|
||||||
versionCode 346
|
versionCode 347
|
||||||
versionName '3.2.240'
|
versionName '3.3.00'
|
||||||
|
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
|
|
||||||
|
@ -107,6 +107,7 @@ dependencies {
|
||||||
implementation 'androidx.annotation:annotation:1.1.0'
|
implementation 'androidx.annotation:annotation:1.1.0'
|
||||||
implementation 'androidx.preference:preference:1.1.0-rc01'
|
implementation 'androidx.preference:preference:1.1.0-rc01'
|
||||||
implementation 'androidx.palette:palette-ktx:1.0.0'
|
implementation 'androidx.palette:palette-ktx:1.0.0'
|
||||||
|
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha2'
|
||||||
|
|
||||||
implementation 'com.google.android.material:material:1.1.0-alpha09'
|
implementation 'com.google.android.material:material:1.1.0-alpha09'
|
||||||
|
|
||||||
|
@ -131,21 +132,22 @@ dependencies {
|
||||||
transitive = true
|
transitive = true
|
||||||
}
|
}
|
||||||
|
|
||||||
implementation 'com.anjlab.android.iab.v3:library:1.0.44'
|
|
||||||
/*UI Library*/
|
/*UI Library*/
|
||||||
implementation 'me.zhanghai.android.materialprogressbar:library:1.6.1'
|
implementation 'me.zhanghai.android.materialprogressbar:library:1.6.1'
|
||||||
implementation 'com.r0adkll:slidableactivity:2.0.6'
|
|
||||||
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||||
implementation 'org.eclipse.mylyn.github:org.eclipse.egit.github.core:3.4.0.201406110918-r'
|
implementation 'org.eclipse.mylyn.github:org.eclipse.egit.github.core:3.4.0.201406110918-r'
|
||||||
|
|
||||||
implementation 'com.github.takahirom.downloadable.calligraphy:downloadable-calligraphy:0.1.3'
|
|
||||||
implementation 'com.github.ksoichiro:android-observablescrollview:1.6.0'
|
implementation 'com.github.ksoichiro:android-observablescrollview:1.6.0'
|
||||||
implementation 'com.github.kabouzeid:RecyclerView-FastScroll:1.0.16-kmod'
|
implementation 'com.github.kabouzeid:RecyclerView-FastScroll:1.0.16-kmod'
|
||||||
implementation 'com.github.kabouzeid:AndroidSlidingUpPanel:3.3.0-kmod3'
|
implementation 'com.github.kabouzeid:AndroidSlidingUpPanel:3.3.0-kmod3'
|
||||||
implementation 'com.github.AdrienPoupa:jaudiotagger:2.2.3'
|
implementation 'com.github.AdrienPoupa:jaudiotagger:2.2.3'
|
||||||
|
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha2'
|
implementation 'com.anjlab.android.iab.v3:library:1.0.44'
|
||||||
|
implementation 'com.r0adkll:slidableactivity:2.0.6'
|
||||||
|
implementation 'com.heinrichreimersoftware:material-intro:1.6'
|
||||||
|
|
||||||
implementation project(':appthemehelper')
|
implementation project(':appthemehelper')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -121,6 +121,7 @@
|
||||||
android:name=".appshortcuts.AppShortcutLauncherActivity"
|
android:name=".appshortcuts.AppShortcutLauncherActivity"
|
||||||
android:launchMode="singleInstance"
|
android:launchMode="singleInstance"
|
||||||
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
|
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
|
||||||
|
<activity android:name=".activities.saf.SAFGuideActivity" />
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.max_aspect"
|
android:name="android.max_aspect"
|
||||||
|
|
|
@ -28,9 +28,9 @@
|
||||||
|
|
||||||
<body style="padding: 1.0rem 0.5rem 1.0rem 0.5rem;">
|
<body style="padding: 1.0rem 0.5rem 1.0rem 0.5rem;">
|
||||||
<p><b><a href="https://github.com/kabouzeid/Phonograph" title="Phonograph"> Phonograph</a></b> by Karim Abou Zeid</p>
|
<p><b><a href="https://github.com/kabouzeid/Phonograph" title="Phonograph"> Phonograph</a></b> by Karim Abou Zeid</p>
|
||||||
<p><b><a href="https://github.com/ReactiveX/RxAndroid" title="RxAndroid"> RxAndroid</a></b> by RxAndroid authors</p>
|
<p><b><a href="https://github.com/AdrienPoupa/VinylMusicPlayer" title="VinylMusicPlayer"> VinylMusicPlayer</a></b> by Adrien Poupa</p>
|
||||||
<p><b><a href="https://github.com/ReactiveX/RxAndroid" title="RxJava"> RxJava</a></b> by RxJava authors</p>
|
<p><b><a href="https://github.com/ReactiveX/RxAndroid" title="RxJava"> RxJava</a></b> by RxJava authors</p>
|
||||||
<p> <b><a href="https://github.com/afollestad/material-cab" title="Material Dialogs"> Material Dialogs</a></b> by Aidan Michael Follestad</p>
|
<p> <b><a href="https://github.com/afollestad" title="Material Dialogs"> Material Dialogs and Cab</a></b> by Aidan Michael Follestad</p>
|
||||||
<p><b><a href="https://github.com/chrisjenx/Calligraphy" title="Calligraphy"> Calligraphy</a></b> by RxJava authors</p>
|
<p><b><a href="https://github.com/chrisjenx/Calligraphy" title="Calligraphy"> Calligraphy</a></b> by RxJava authors</p>
|
||||||
<p><b><a href="https://github.com/JetradarMobile/android-snowfall" title="Android-Snowfall"> Android-Snowfall</a></b> by JetRadar</p>
|
<p><b><a href="https://github.com/JetradarMobile/android-snowfall" title="Android-Snowfall"> Android-Snowfall</a></b> by JetRadar</p>
|
||||||
<p><b><a href="https://github.com/umano/AndroidSlidingUpPanel" title="Android Sliding Up Panel"> Android Sliding Up Panel</a></b>by The Umano Team</p>
|
<p><b><a href="https://github.com/umano/AndroidSlidingUpPanel" title="Android Sliding Up Panel"> Android Sliding Up Panel</a></b>by The Umano Team</p>
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -44,12 +44,6 @@ class App : MultiDexApplication() {
|
||||||
if (VersionUtils.hasNougatMR())
|
if (VersionUtils.hasNougatMR())
|
||||||
DynamicShortcutManager(this).initDynamicShortcuts()
|
DynamicShortcutManager(this).initDynamicShortcuts()
|
||||||
|
|
||||||
|
|
||||||
CalligraphyConfig.initDefault(CalligraphyConfig.Builder()
|
|
||||||
.setDefaultFont(R.font.circular_std_book)
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
|
|
||||||
// automatically restores purchases
|
// automatically restores purchases
|
||||||
billingProcessor = BillingProcessor(this, BuildConfig.GOOGLE_PLAY_LICENSING_KEY,
|
billingProcessor = BillingProcessor(this, BuildConfig.GOOGLE_PLAY_LICENSING_KEY,
|
||||||
object : BillingProcessor.IBillingHandler {
|
object : BillingProcessor.IBillingHandler {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package code.name.monkey.retromusic.activities.base
|
package code.name.monkey.retromusic.activities.base
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
@ -16,6 +17,8 @@ import code.name.monkey.appthemehelper.util.*
|
||||||
import code.name.monkey.retromusic.R
|
import code.name.monkey.retromusic.R
|
||||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||||
import code.name.monkey.retromusic.util.RetroUtil
|
import code.name.monkey.retromusic.util.RetroUtil
|
||||||
|
import uk.co.chrisjenx.calligraphy.CalligraphyContextWrapper
|
||||||
|
import uk.co.chrisjenx.calligraphy.CalligraphyTypefaceSpan
|
||||||
|
|
||||||
abstract class AbsThemeActivity : ATHActivity(), Runnable {
|
abstract class AbsThemeActivity : ATHActivity(), Runnable {
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019 Hemanth Savarala.
|
||||||
|
*
|
||||||
|
* Licensed under the GNU General Public License v3
|
||||||
|
*
|
||||||
|
* This is free software: you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the GNU General Public License for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package code.name.monkey.retromusic.activities.saf;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.heinrichreimersoftware.materialintro.app.IntroActivity;
|
||||||
|
import com.heinrichreimersoftware.materialintro.slide.SimpleSlide;
|
||||||
|
|
||||||
|
import code.name.monkey.retromusic.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by hemanths on 2019-07-31.
|
||||||
|
*/
|
||||||
|
public class SAFGuideActivity extends IntroActivity {
|
||||||
|
public static final int REQUEST_CODE_SAF_GUIDE = 98;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
setButtonCtaVisible(false);
|
||||||
|
setButtonNextVisible(false);
|
||||||
|
setButtonBackVisible(false);
|
||||||
|
|
||||||
|
setButtonCtaTintMode(BUTTON_CTA_TINT_MODE_TEXT);
|
||||||
|
|
||||||
|
String title = String.format(getString(R.string.saf_guide_slide1_title), getString(R.string.app_name));
|
||||||
|
|
||||||
|
addSlide(new SimpleSlide.Builder()
|
||||||
|
.title(title)
|
||||||
|
.description(Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1 ? R.string.saf_guide_slide1_description_before_o : R.string.saf_guide_slide1_description)
|
||||||
|
.image(R.drawable.saf_guide_1)
|
||||||
|
.background(R.color.md_deep_purple_300)
|
||||||
|
.backgroundDark(R.color.md_deep_purple_400)
|
||||||
|
.layout(R.layout.fragment_simple_slide_large_image)
|
||||||
|
.build());
|
||||||
|
addSlide(new SimpleSlide.Builder()
|
||||||
|
.title(R.string.saf_guide_slide2_title)
|
||||||
|
.description(R.string.saf_guide_slide2_description)
|
||||||
|
.image(R.drawable.saf_guide_2)
|
||||||
|
.background(R.color.md_deep_purple_500)
|
||||||
|
.backgroundDark(R.color.md_deep_purple_600)
|
||||||
|
.layout(R.layout.fragment_simple_slide_large_image)
|
||||||
|
.build());
|
||||||
|
addSlide(new SimpleSlide.Builder()
|
||||||
|
.title(R.string.saf_guide_slide3_title)
|
||||||
|
.description(R.string.saf_guide_slide3_description)
|
||||||
|
.image(R.drawable.saf_guide_3)
|
||||||
|
.background(R.color.md_deep_purple_700)
|
||||||
|
.backgroundDark(R.color.md_deep_purple_800)
|
||||||
|
.layout(R.layout.fragment_simple_slide_large_image)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ import android.content.res.ColorStateList
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
@ -16,9 +17,10 @@ import code.name.monkey.appthemehelper.ThemeStore
|
||||||
import code.name.monkey.appthemehelper.util.ColorUtil
|
import code.name.monkey.appthemehelper.util.ColorUtil
|
||||||
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
||||||
import code.name.monkey.appthemehelper.util.TintHelper
|
import code.name.monkey.appthemehelper.util.TintHelper
|
||||||
import code.name.monkey.retromusic.R
|
|
||||||
import code.name.monkey.retromusic.activities.base.AbsBaseActivity
|
import code.name.monkey.retromusic.activities.base.AbsBaseActivity
|
||||||
|
import code.name.monkey.retromusic.activities.saf.SAFGuideActivity
|
||||||
import code.name.monkey.retromusic.util.RetroUtil
|
import code.name.monkey.retromusic.util.RetroUtil
|
||||||
|
import code.name.monkey.retromusic.util.SAFUtil
|
||||||
import com.afollestad.materialdialogs.MaterialDialog
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
import com.afollestad.materialdialogs.list.listItems
|
import com.afollestad.materialdialogs.list.listItems
|
||||||
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
|
@ -27,6 +29,8 @@ import org.jaudiotagger.audio.AudioFile
|
||||||
import org.jaudiotagger.audio.AudioFileIO
|
import org.jaudiotagger.audio.AudioFileIO
|
||||||
import org.jaudiotagger.tag.FieldKey
|
import org.jaudiotagger.tag.FieldKey
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
abstract class AbsTagEditorActivity : AbsBaseActivity() {
|
abstract class AbsTagEditorActivity : AbsBaseActivity() {
|
||||||
|
|
||||||
|
@ -38,9 +42,14 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
|
||||||
private var songPaths: List<String>? = null
|
private var songPaths: List<String>? = null
|
||||||
lateinit var saveFab: ExtendedFloatingActionButton
|
lateinit var saveFab: ExtendedFloatingActionButton
|
||||||
|
|
||||||
|
private var savedSongPaths: List<String>? = null
|
||||||
|
private val currentSongPath: String? = null
|
||||||
|
private var savedTags: Map<FieldKey, String>? = null
|
||||||
|
private var savedArtworkInfo: ArtworkInfo? = null
|
||||||
|
|
||||||
protected val show: MaterialDialog
|
protected val show: MaterialDialog
|
||||||
get() = MaterialDialog(this@AbsTagEditorActivity).show {
|
get() = MaterialDialog(this@AbsTagEditorActivity).show {
|
||||||
title(R.string.update_image)
|
title(code.name.monkey.retromusic.R.string.update_image)
|
||||||
listItems(items = items) { _, position, _ ->
|
listItems(items = items) { _, position, _ ->
|
||||||
when (position) {
|
when (position) {
|
||||||
0 -> getImageFromLastFM()
|
0 -> getImageFromLastFM()
|
||||||
|
@ -174,7 +183,7 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(contentViewLayout)
|
setContentView(contentViewLayout)
|
||||||
|
|
||||||
saveFab = findViewById(R.id.saveTags)
|
saveFab = findViewById(code.name.monkey.retromusic.R.id.saveTags)
|
||||||
getIntentExtras()
|
getIntentExtras()
|
||||||
|
|
||||||
songPaths = getSongPaths()
|
songPaths = getSongPaths()
|
||||||
|
@ -204,14 +213,14 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
|
||||||
|
|
||||||
private fun setUpImageView() {
|
private fun setUpImageView() {
|
||||||
loadCurrentImage()
|
loadCurrentImage()
|
||||||
items = listOf(getString(R.string.download_from_last_fm), getString(R.string.pick_from_local_storage), getString(R.string.web_search), getString(R.string.remove_cover))
|
items = listOf(getString(code.name.monkey.retromusic.R.string.download_from_last_fm), getString(code.name.monkey.retromusic.R.string.pick_from_local_storage), getString(code.name.monkey.retromusic.R.string.web_search), getString(code.name.monkey.retromusic.R.string.remove_cover))
|
||||||
editorImage.setOnClickListener { show }
|
editorImage.setOnClickListener { show }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startImagePicker() {
|
private fun startImagePicker() {
|
||||||
val intent = Intent(Intent.ACTION_GET_CONTENT)
|
val intent = Intent(Intent.ACTION_GET_CONTENT)
|
||||||
intent.type = "image/*"
|
intent.type = "image/*"
|
||||||
startActivityForResult(Intent.createChooser(intent, getString(R.string.pick_from_local_storage)), REQUEST_CODE_SELECT_IMAGE)
|
startActivityForResult(Intent.createChooser(intent, getString(code.name.monkey.retromusic.R.string.pick_from_local_storage)), REQUEST_CODE_SELECT_IMAGE)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract fun loadCurrentImage()
|
protected abstract fun loadCurrentImage()
|
||||||
|
@ -295,9 +304,19 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
|
||||||
saveFab.isEnabled = true
|
saveFab.isEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun hideFab() {
|
||||||
|
saveFab.animate()
|
||||||
|
.setDuration(500)
|
||||||
|
.setInterpolator(OvershootInterpolator())
|
||||||
|
.scaleX(0.0f)
|
||||||
|
.scaleY(0.0f)
|
||||||
|
.start()
|
||||||
|
saveFab.isEnabled = false
|
||||||
|
}
|
||||||
|
|
||||||
protected fun setImageBitmap(bitmap: Bitmap?, bgColor: Int) {
|
protected fun setImageBitmap(bitmap: Bitmap?, bgColor: Int) {
|
||||||
if (bitmap == null) {
|
if (bitmap == null) {
|
||||||
editorImage.setImageResource(R.drawable.default_album_art)
|
editorImage.setImageResource(code.name.monkey.retromusic.R.drawable.default_album_art)
|
||||||
} else {
|
} else {
|
||||||
editorImage.setImageBitmap(bitmap)
|
editorImage.setImageBitmap(bitmap)
|
||||||
}
|
}
|
||||||
|
@ -312,16 +331,50 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
|
||||||
artworkInfo: ArtworkInfo?) {
|
artworkInfo: ArtworkInfo?) {
|
||||||
RetroUtil.hideSoftKeyboard(this)
|
RetroUtil.hideSoftKeyboard(this)
|
||||||
|
|
||||||
WriteTagsAsyncTask(this)
|
hideFab()
|
||||||
.execute(WriteTagsAsyncTask.LoadingInfo(getSongPaths(), fieldKeyValueMap, artworkInfo))
|
|
||||||
|
savedSongPaths = getSongPaths()
|
||||||
|
savedTags = fieldKeyValueMap
|
||||||
|
savedArtworkInfo = artworkInfo
|
||||||
|
|
||||||
|
if (!SAFUtil.isSAFRequired(savedSongPaths)) {
|
||||||
|
writeTags(savedSongPaths)
|
||||||
|
} else {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
if (SAFUtil.isSDCardAccessGranted(this)) {
|
||||||
|
writeTags(savedSongPaths)
|
||||||
|
} else {
|
||||||
|
startActivityForResult(Intent(this, SAFGuideActivity::class.java), SAFGuideActivity.REQUEST_CODE_SAF_GUIDE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
private fun writeTags(paths: List<String>?) {
|
||||||
super.onActivityResult(requestCode, resultCode, data)
|
WriteTagsAsyncTask(this).execute(WriteTagsAsyncTask.LoadingInfo(paths, savedTags, savedArtworkInfo))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, intent)
|
||||||
when (requestCode) {
|
when (requestCode) {
|
||||||
REQUEST_CODE_SELECT_IMAGE -> if (resultCode == Activity.RESULT_OK) {
|
REQUEST_CODE_SELECT_IMAGE -> if (resultCode == Activity.RESULT_OK) {
|
||||||
val selectedImage = data!!.data
|
intent?.data?.let {
|
||||||
loadImageFromFile(selectedImage)
|
loadImageFromFile(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SAFGuideActivity.REQUEST_CODE_SAF_GUIDE -> {
|
||||||
|
SAFUtil.openTreePicker(this)
|
||||||
|
}
|
||||||
|
SAFUtil.REQUEST_SAF_PICK_TREE -> {
|
||||||
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
|
SAFUtil.saveTreeUri(this, intent)
|
||||||
|
writeTags(savedSongPaths)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SAFUtil.REQUEST_SAF_PICK_FILE -> {
|
||||||
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
|
writeTags(Collections.singletonList(currentSongPath + SAFUtil.SEPARATOR + intent!!.dataString))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -335,7 +388,6 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
|
||||||
Log.e(TAG, "Could not read audio file $path", e)
|
Log.e(TAG, "Could not read audio file $path", e)
|
||||||
AudioFile()
|
AudioFile()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class ArtworkInfo constructor(val albumId: Int, val artwork: Bitmap?)
|
class ArtworkInfo constructor(val albumId: Int, val artwork: Bitmap?)
|
||||||
|
|
|
@ -56,6 +56,7 @@ class SongTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
|
||||||
|
|
||||||
songText.appHandleColor().addTextChangedListener(this)
|
songText.appHandleColor().addTextChangedListener(this)
|
||||||
albumText.appHandleColor().addTextChangedListener(this)
|
albumText.appHandleColor().addTextChangedListener(this)
|
||||||
|
albumArtistText.appHandleColor().addTextChangedListener(this)
|
||||||
artistText.appHandleColor().addTextChangedListener(this)
|
artistText.appHandleColor().addTextChangedListener(this)
|
||||||
genreText.appHandleColor().addTextChangedListener(this)
|
genreText.appHandleColor().addTextChangedListener(this)
|
||||||
yearText.appHandleColor().addTextChangedListener(this)
|
yearText.appHandleColor().addTextChangedListener(this)
|
||||||
|
|
|
@ -5,43 +5,43 @@ import android.app.Dialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.media.MediaScannerConnection;
|
import android.media.MediaScannerConnection;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
import com.afollestad.materialdialogs.MaterialDialog;
|
import androidx.annotation.NonNull;
|
||||||
import com.afollestad.materialdialogs.bottomsheets.BottomSheet;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
|
|
||||||
import org.jaudiotagger.audio.AudioFile;
|
import org.jaudiotagger.audio.AudioFile;
|
||||||
import org.jaudiotagger.audio.AudioFileIO;
|
import org.jaudiotagger.audio.AudioFileIO;
|
||||||
import org.jaudiotagger.audio.exceptions.CannotReadException;
|
|
||||||
import org.jaudiotagger.audio.exceptions.CannotWriteException;
|
|
||||||
import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException;
|
|
||||||
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException;
|
|
||||||
import org.jaudiotagger.tag.FieldKey;
|
import org.jaudiotagger.tag.FieldKey;
|
||||||
import org.jaudiotagger.tag.Tag;
|
import org.jaudiotagger.tag.Tag;
|
||||||
import org.jaudiotagger.tag.TagException;
|
|
||||||
import org.jaudiotagger.tag.images.Artwork;
|
import org.jaudiotagger.tag.images.Artwork;
|
||||||
import org.jaudiotagger.tag.images.ArtworkFactory;
|
import org.jaudiotagger.tag.images.ArtworkFactory;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import code.name.monkey.retromusic.R;
|
import code.name.monkey.retromusic.R;
|
||||||
import code.name.monkey.retromusic.misc.DialogAsyncTask;
|
import code.name.monkey.retromusic.misc.DialogAsyncTask;
|
||||||
import code.name.monkey.retromusic.misc.UpdateToastMediaScannerCompletionListener;
|
import code.name.monkey.retromusic.misc.UpdateToastMediaScannerCompletionListener;
|
||||||
import code.name.monkey.retromusic.util.MusicUtil;
|
import code.name.monkey.retromusic.util.MusicUtil;
|
||||||
|
import code.name.monkey.retromusic.util.SAFUtil;
|
||||||
|
|
||||||
public class WriteTagsAsyncTask extends
|
public class WriteTagsAsyncTask extends
|
||||||
DialogAsyncTask<WriteTagsAsyncTask.LoadingInfo, Integer, String[]> {
|
DialogAsyncTask<WriteTagsAsyncTask.LoadingInfo, Integer, String[]> {
|
||||||
|
|
||||||
private Context applicationContext;
|
private WeakReference<Activity> activity;
|
||||||
|
|
||||||
public WriteTagsAsyncTask(Context context) {
|
public WriteTagsAsyncTask(@NonNull Activity activity) {
|
||||||
super(context);
|
super(activity);
|
||||||
applicationContext = context;
|
this.activity = new WeakReference<>(activity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -68,6 +68,13 @@ public class WriteTagsAsyncTask extends
|
||||||
for (String filePath : info.filePaths) {
|
for (String filePath : info.filePaths) {
|
||||||
publishProgress(++counter, info.filePaths.size());
|
publishProgress(++counter, info.filePaths.size());
|
||||||
try {
|
try {
|
||||||
|
Uri safUri = null;
|
||||||
|
if (filePath.contains(SAFUtil.SEPARATOR)) {
|
||||||
|
String[] fragments = filePath.split(SAFUtil.SEPARATOR);
|
||||||
|
filePath = fragments[0];
|
||||||
|
safUri = Uri.parse(fragments[1]);
|
||||||
|
}
|
||||||
|
|
||||||
AudioFile audioFile = AudioFileIO.read(new File(filePath));
|
AudioFile audioFile = AudioFileIO.read(new File(filePath));
|
||||||
Tag tag = audioFile.getTagOrCreateAndSetDefault();
|
Tag tag = audioFile.getTagOrCreateAndSetDefault();
|
||||||
|
|
||||||
|
@ -92,8 +99,10 @@ public class WriteTagsAsyncTask extends
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
audioFile.commit();
|
Activity activity = this.activity.get();
|
||||||
} catch (@NonNull CannotReadException | IOException | CannotWriteException | TagException | ReadOnlyFileException | InvalidAudioFrameException e) {
|
SAFUtil.write(activity, audioFile, safUri);
|
||||||
|
|
||||||
|
} catch (@NonNull Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,7 +116,17 @@ public class WriteTagsAsyncTask extends
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return info.filePaths.toArray(new String[info.filePaths.size()]);
|
Collection<String> paths = info.filePaths;
|
||||||
|
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
|
||||||
|
paths = new ArrayList<>(info.filePaths.size());
|
||||||
|
for (String path : info.filePaths) {
|
||||||
|
if (path.contains(SAFUtil.SEPARATOR))
|
||||||
|
path = path.split(SAFUtil.SEPARATOR)[0];
|
||||||
|
paths.add(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths.toArray(new String[paths.size()]);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return null;
|
return null;
|
||||||
|
@ -127,18 +146,20 @@ public class WriteTagsAsyncTask extends
|
||||||
}
|
}
|
||||||
|
|
||||||
private void scan(String[] toBeScanned) {
|
private void scan(String[] toBeScanned) {
|
||||||
Context context = getContext();
|
Activity activity = this.activity.get();
|
||||||
MediaScannerConnection.scanFile(applicationContext, toBeScanned, null,
|
if (activity != null) {
|
||||||
context instanceof Activity ? new UpdateToastMediaScannerCompletionListener(
|
MediaScannerConnection.scanFile(activity, toBeScanned, null, new UpdateToastMediaScannerCompletionListener(activity, toBeScanned));
|
||||||
(Activity) context, toBeScanned) : null);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
protected Dialog createDialog(@NonNull Context context) {
|
protected Dialog createDialog(@NonNull Context context) {
|
||||||
return new MaterialDialog(context, new BottomSheet())
|
return new MaterialAlertDialogBuilder(context)
|
||||||
.title(R.string.saving_changes, "")
|
.setTitle(R.string.saving_changes)
|
||||||
.cancelable(false);
|
.setCancelable(false)
|
||||||
|
.setView(R.layout.loading)
|
||||||
|
.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,138 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019 Hemanth Savarala.
|
||||||
|
*
|
||||||
|
* Licensed under the GNU General Public License v3
|
||||||
|
*
|
||||||
|
* This is free software: you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the GNU General Public License for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package code.name.monkey.retromusic.dialogs;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.fragment.app.FragmentActivity;
|
||||||
|
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import code.name.monkey.retromusic.R;
|
||||||
|
import code.name.monkey.retromusic.activities.saf.SAFGuideActivity;
|
||||||
|
import code.name.monkey.retromusic.misc.DialogAsyncTask;
|
||||||
|
import code.name.monkey.retromusic.model.Song;
|
||||||
|
import code.name.monkey.retromusic.util.SAFUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by hemanths on 2019-07-31.
|
||||||
|
*/
|
||||||
|
public class DeleteSongsAsyncTask extends DialogAsyncTask<DeleteSongsAsyncTask.LoadingInfo, Integer, Void> {
|
||||||
|
private WeakReference<DeleteSongsDialog> dialogReference;
|
||||||
|
private WeakReference<FragmentActivity> activityWeakReference;
|
||||||
|
|
||||||
|
|
||||||
|
public DeleteSongsAsyncTask(@NonNull DeleteSongsDialog dialog) {
|
||||||
|
super(dialog.getActivity());
|
||||||
|
this.dialogReference = new WeakReference<>(dialog);
|
||||||
|
this.activityWeakReference = new WeakReference<>(dialog.getActivity());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected Dialog createDialog(@NonNull Context context) {
|
||||||
|
return new MaterialAlertDialogBuilder(context)
|
||||||
|
.setTitle(R.string.deleting_songs)
|
||||||
|
.setView(R.layout.loading)
|
||||||
|
.setCancelable(false)
|
||||||
|
.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(@NonNull LoadingInfo... loadingInfos) {
|
||||||
|
try {
|
||||||
|
LoadingInfo info = loadingInfos[0];
|
||||||
|
DeleteSongsDialog dialog = this.dialogReference.get();
|
||||||
|
FragmentActivity fragmentActivity = this.activityWeakReference.get();
|
||||||
|
|
||||||
|
if (dialog == null || fragmentActivity == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!info.isIntent) {
|
||||||
|
if (!SAFUtil.isSAFRequiredForSongs(info.songs)) {
|
||||||
|
dialog.deleteSongs(info.songs, null);
|
||||||
|
} else {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
if (SAFUtil.isSDCardAccessGranted(fragmentActivity)) {
|
||||||
|
dialog.deleteSongs(info.songs, null);
|
||||||
|
} else {
|
||||||
|
dialog.startActivityForResult(new Intent(fragmentActivity, SAFGuideActivity.class), SAFGuideActivity.REQUEST_CODE_SAF_GUIDE);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.i("Hmm", "doInBackground: kitkat delete songs");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (info.requestCode) {
|
||||||
|
case SAFUtil.REQUEST_SAF_PICK_TREE:
|
||||||
|
if (info.resultCode == Activity.RESULT_OK) {
|
||||||
|
SAFUtil.saveTreeUri(fragmentActivity, info.intent);
|
||||||
|
if (dialog.songsToRemove != null) {
|
||||||
|
dialog.deleteSongs(dialog.songsToRemove, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SAFUtil.REQUEST_SAF_PICK_FILE:
|
||||||
|
if (info.resultCode == Activity.RESULT_OK) {
|
||||||
|
dialog.deleteSongs(Collections.singletonList(dialog.currentSong), Collections.singletonList(info.intent.getData()));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class LoadingInfo {
|
||||||
|
public boolean isIntent;
|
||||||
|
|
||||||
|
public List<Song> songs;
|
||||||
|
public List<Uri> safUris;
|
||||||
|
|
||||||
|
public int requestCode;
|
||||||
|
public int resultCode;
|
||||||
|
public Intent intent;
|
||||||
|
|
||||||
|
public LoadingInfo(List<Song> songs, List<Uri> safUris) {
|
||||||
|
this.isIntent = false;
|
||||||
|
this.songs = songs;
|
||||||
|
this.safUris = safUris;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LoadingInfo(int requestCode, int resultCode, Intent intent) {
|
||||||
|
this.isIntent = true;
|
||||||
|
this.requestCode = requestCode;
|
||||||
|
this.resultCode = resultCode;
|
||||||
|
this.intent = intent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,41 +15,85 @@
|
||||||
package code.name.monkey.retromusic.dialogs
|
package code.name.monkey.retromusic.dialogs
|
||||||
|
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Html
|
import android.text.Html
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import code.name.monkey.retromusic.R
|
import code.name.monkey.retromusic.R
|
||||||
|
import code.name.monkey.retromusic.activities.saf.SAFGuideActivity
|
||||||
|
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||||
import code.name.monkey.retromusic.model.Song
|
import code.name.monkey.retromusic.model.Song
|
||||||
import code.name.monkey.retromusic.util.MusicUtil
|
import code.name.monkey.retromusic.util.MusicUtil
|
||||||
|
import code.name.monkey.retromusic.util.SAFUtil
|
||||||
import com.afollestad.materialdialogs.MaterialDialog
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
import com.afollestad.materialdialogs.bottomsheets.BottomSheet
|
import com.afollestad.materialdialogs.bottomsheets.BottomSheet
|
||||||
|
|
||||||
|
|
||||||
class DeleteSongsDialog : DialogFragment() {
|
class DeleteSongsDialog : DialogFragment() {
|
||||||
|
@JvmField
|
||||||
|
var currentSong: Song? = null
|
||||||
|
@JvmField
|
||||||
|
var songsToRemove: List<Song>? = null
|
||||||
|
|
||||||
|
private var deleteSongsAsyncTask: DeleteSongsAsyncTask? = null
|
||||||
|
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
val songs = arguments!!.getParcelableArrayList<Song>("songs")
|
val songs: ArrayList<Song>? = arguments?.getParcelableArrayList("songs")
|
||||||
val title: Int
|
var title = 0
|
||||||
val content: CharSequence
|
var content: CharSequence = ""
|
||||||
if (songs.size > 1) {
|
if (songs != null) {
|
||||||
title = R.string.delete_songs_title
|
if (songs.size > 1) {
|
||||||
content = Html.fromHtml(getString(R.string.delete_x_songs, songs.size))
|
title = R.string.delete_songs_title
|
||||||
} else {
|
content = Html.fromHtml(getString(R.string.delete_x_songs, songs.size))
|
||||||
title = R.string.delete_song_title
|
} else {
|
||||||
content = Html.fromHtml(getString(R.string.delete_song_x, songs.get(0).title))
|
title = R.string.delete_song_title
|
||||||
|
content = Html.fromHtml(getString(R.string.delete_song_x, songs[0].title))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return MaterialDialog(activity!!, BottomSheet()).show {
|
|
||||||
|
return MaterialDialog(requireActivity(), BottomSheet()).show {
|
||||||
title(title)
|
title(title)
|
||||||
message(text = content)
|
message(text = content)
|
||||||
negativeButton(android.R.string.cancel)
|
negativeButton(android.R.string.cancel) {
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
noAutoDismiss()
|
||||||
positiveButton(R.string.action_delete) {
|
positiveButton(R.string.action_delete) {
|
||||||
if (activity == null)
|
if (songs != null) {
|
||||||
return@positiveButton
|
if ((songs.size == 1) && MusicPlayerRemote.isPlaying(songs[0])) {
|
||||||
MusicUtil.deleteTracks(activity!!, songs);
|
MusicPlayerRemote.playNextSong()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
songsToRemove = songs
|
||||||
|
deleteSongsAsyncTask = DeleteSongsAsyncTask(this@DeleteSongsDialog)
|
||||||
|
deleteSongsAsyncTask?.execute(DeleteSongsAsyncTask.LoadingInfo(songs, null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
when (requestCode) {
|
||||||
|
SAFGuideActivity.REQUEST_CODE_SAF_GUIDE -> {
|
||||||
|
SAFUtil.openTreePicker(this)
|
||||||
|
}
|
||||||
|
SAFUtil.REQUEST_SAF_PICK_TREE,
|
||||||
|
SAFUtil.REQUEST_SAF_PICK_FILE -> {
|
||||||
|
if (deleteSongsAsyncTask != null) {
|
||||||
|
deleteSongsAsyncTask?.cancel(true)
|
||||||
|
}
|
||||||
|
deleteSongsAsyncTask = DeleteSongsAsyncTask(this)
|
||||||
|
deleteSongsAsyncTask?.execute(DeleteSongsAsyncTask.LoadingInfo(requestCode, resultCode, data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteSongs(songs: List<Song>, safUris: List<Uri>?) {
|
||||||
|
MusicUtil.deleteTracks(activity!!, songs, safUris) { this.dismiss() }
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
|
|
@ -15,12 +15,10 @@
|
||||||
package code.name.monkey.retromusic.dialogs
|
package code.name.monkey.retromusic.dialogs
|
||||||
|
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.graphics.drawable.ColorDrawable
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import code.name.monkey.appthemehelper.ThemeStore
|
|
||||||
import code.name.monkey.retromusic.R
|
import code.name.monkey.retromusic.R
|
||||||
import code.name.monkey.retromusic.activities.MainActivity
|
import code.name.monkey.retromusic.activities.MainActivity
|
||||||
import code.name.monkey.retromusic.util.NavigationUtil
|
import code.name.monkey.retromusic.util.NavigationUtil
|
||||||
|
@ -38,6 +36,7 @@ class OptionsSheetDialogFragment : DialogFragment(), View.OnClickListener {
|
||||||
R.id.actionFolders -> mainActivity.selectedFragment(R.id.action_folder)
|
R.id.actionFolders -> mainActivity.selectedFragment(R.id.action_folder)
|
||||||
R.id.actionLibrary -> mainActivity.selectedFragment(PreferenceUtil.getInstance().lastPage)
|
R.id.actionLibrary -> mainActivity.selectedFragment(PreferenceUtil.getInstance().lastPage)
|
||||||
R.id.actionSettings -> NavigationUtil.goToSettings(mainActivity)
|
R.id.actionSettings -> NavigationUtil.goToSettings(mainActivity)
|
||||||
|
R.id.actionRate -> NavigationUtil.goToPlayStore(mainActivity)
|
||||||
}
|
}
|
||||||
materialDialog.dismiss()
|
materialDialog.dismiss()
|
||||||
}
|
}
|
||||||
|
@ -45,11 +44,13 @@ class OptionsSheetDialogFragment : DialogFragment(), View.OnClickListener {
|
||||||
private lateinit var actionSettings: OptionMenuItemView
|
private lateinit var actionSettings: OptionMenuItemView
|
||||||
private lateinit var actionLibrary: OptionMenuItemView
|
private lateinit var actionLibrary: OptionMenuItemView
|
||||||
private lateinit var actionFolders: OptionMenuItemView
|
private lateinit var actionFolders: OptionMenuItemView
|
||||||
|
private lateinit var actionRate: OptionMenuItemView
|
||||||
private lateinit var materialDialog: MaterialDialog
|
private lateinit var materialDialog: MaterialDialog
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
val layout = LayoutInflater.from(context).inflate(R.layout.fragment_main_options, null)
|
val layout = LayoutInflater.from(context).inflate(R.layout.fragment_main_options, null)
|
||||||
actionSettings = layout.findViewById(R.id.actionSettings)
|
actionSettings = layout.findViewById(R.id.actionSettings)
|
||||||
|
actionRate = layout.findViewById(R.id.actionRate)
|
||||||
actionLibrary = layout.findViewById(R.id.actionLibrary)
|
actionLibrary = layout.findViewById(R.id.actionLibrary)
|
||||||
actionFolders = layout.findViewById(R.id.actionFolders)
|
actionFolders = layout.findViewById(R.id.actionFolders)
|
||||||
|
|
||||||
|
@ -60,9 +61,11 @@ class OptionsSheetDialogFragment : DialogFragment(), View.OnClickListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
actionSettings.setOnClickListener(this)
|
actionSettings.setOnClickListener(this)
|
||||||
|
actionRate.setOnClickListener(this)
|
||||||
actionLibrary.setOnClickListener(this)
|
actionLibrary.setOnClickListener(this)
|
||||||
actionFolders.setOnClickListener(this)
|
actionFolders.setOnClickListener(this)
|
||||||
|
|
||||||
|
|
||||||
materialDialog = MaterialDialog(activity!!, BottomSheet())
|
materialDialog = MaterialDialog(activity!!, BottomSheet())
|
||||||
.show {
|
.show {
|
||||||
icon(R.mipmap.ic_launcher_round)
|
icon(R.mipmap.ic_launcher_round)
|
||||||
|
@ -72,11 +75,8 @@ class OptionsSheetDialogFragment : DialogFragment(), View.OnClickListener {
|
||||||
return materialDialog
|
return materialDialog
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private const val TAG: String = "MainOptionsBottomSheetD"
|
|
||||||
|
|
||||||
private const val WHICH_ONE = "which_one"
|
private const val WHICH_ONE = "which_one"
|
||||||
@JvmField
|
@JvmField
|
||||||
var LIBRARY: Int = 0
|
var LIBRARY: Int = 0
|
||||||
|
|
|
@ -15,14 +15,14 @@ import android.widget.Toast
|
||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
import code.name.monkey.appthemehelper.ThemeStore
|
import code.name.monkey.appthemehelper.ThemeStore
|
||||||
import code.name.monkey.retromusic.R
|
import code.name.monkey.retromusic.R
|
||||||
|
import code.name.monkey.retromusic.activities.tageditor.AbsTagEditorActivity
|
||||||
|
import code.name.monkey.retromusic.activities.tageditor.SongTagEditorActivity
|
||||||
import code.name.monkey.retromusic.dialogs.*
|
import code.name.monkey.retromusic.dialogs.*
|
||||||
|
import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment
|
||||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||||
import code.name.monkey.retromusic.interfaces.PaletteColorHolder
|
import code.name.monkey.retromusic.interfaces.PaletteColorHolder
|
||||||
import code.name.monkey.retromusic.model.Song
|
import code.name.monkey.retromusic.model.Song
|
||||||
import code.name.monkey.retromusic.model.lyrics.Lyrics
|
import code.name.monkey.retromusic.model.lyrics.Lyrics
|
||||||
import code.name.monkey.retromusic.activities.tageditor.AbsTagEditorActivity
|
|
||||||
import code.name.monkey.retromusic.activities.tageditor.SongTagEditorActivity
|
|
||||||
import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment
|
|
||||||
import code.name.monkey.retromusic.util.*
|
import code.name.monkey.retromusic.util.*
|
||||||
import code.name.monkey.retromusic.views.FitSystemWindowsLayout
|
import code.name.monkey.retromusic.views.FitSystemWindowsLayout
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
|
@ -57,20 +57,15 @@ abstract class AbsPlayerFragment : AbsMusicServiceFragment(), Toolbar.OnMenuItem
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
R.id.action_share -> {
|
R.id.action_share -> {
|
||||||
if (fragmentManager != null) {
|
SongShareDialog.create(song).show(requireFragmentManager(), "SHARE_SONG")
|
||||||
SongShareDialog.create(song).show(fragmentManager!!, "SHARE_SONG")
|
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
R.id.action_delete_from_device -> {
|
R.id.action_delete_from_device -> {
|
||||||
DeleteSongsDialog.create(song)
|
DeleteSongsDialog.create(song).show(requireFragmentManager(), "DELETE_SONGS")
|
||||||
.show(activity!!.supportFragmentManager, "DELETE_SONGS")
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
R.id.action_add_to_playlist -> {
|
R.id.action_add_to_playlist -> {
|
||||||
if (fragmentManager != null) {
|
AddToPlaylistDialog.create(song).show(requireFragmentManager(), "ADD_PLAYLIST")
|
||||||
AddToPlaylistDialog.create(song).show(fragmentManager!!, "ADD_PLAYLIST")
|
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
R.id.action_clear_playing_queue -> {
|
R.id.action_clear_playing_queue -> {
|
||||||
|
@ -79,7 +74,7 @@ abstract class AbsPlayerFragment : AbsMusicServiceFragment(), Toolbar.OnMenuItem
|
||||||
}
|
}
|
||||||
R.id.action_save_playing_queue -> {
|
R.id.action_save_playing_queue -> {
|
||||||
CreatePlaylistDialog.create(MusicPlayerRemote.playingQueue)
|
CreatePlaylistDialog.create(MusicPlayerRemote.playingQueue)
|
||||||
.show(activity!!.supportFragmentManager, "ADD_TO_PLAYLIST")
|
.show(requireFragmentManager(), "ADD_TO_PLAYLIST")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
R.id.action_tag_editor -> {
|
R.id.action_tag_editor -> {
|
||||||
|
@ -89,45 +84,43 @@ abstract class AbsPlayerFragment : AbsMusicServiceFragment(), Toolbar.OnMenuItem
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
R.id.action_details -> {
|
R.id.action_details -> {
|
||||||
if (fragmentManager != null) {
|
SongDetailDialog.create(song).show(requireFragmentManager(), "SONG_DETAIL")
|
||||||
SongDetailDialog.create(song).show(fragmentManager!!, "SONG_DETAIL")
|
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
R.id.action_go_to_album -> {
|
R.id.action_go_to_album -> {
|
||||||
NavigationUtil.goToAlbum(activity!!, song.albumId)
|
NavigationUtil.goToAlbum(requireActivity(), song.albumId)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
R.id.action_go_to_artist -> {
|
R.id.action_go_to_artist -> {
|
||||||
NavigationUtil.goToArtist(activity!!, song.artistId)
|
NavigationUtil.goToArtist(requireActivity(), song.artistId)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
R.id.now_playing -> {
|
R.id.now_playing -> {
|
||||||
NavigationUtil.goToPlayingQueue(activity!!)
|
NavigationUtil.goToPlayingQueue(requireActivity())
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
R.id.action_show_lyrics -> {
|
R.id.action_show_lyrics -> {
|
||||||
NavigationUtil.goToLyrics(activity!!)
|
NavigationUtil.goToLyrics(requireActivity())
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
R.id.action_equalizer -> {
|
R.id.action_equalizer -> {
|
||||||
NavigationUtil.openEqualizer(activity!!)
|
NavigationUtil.openEqualizer(requireActivity())
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
R.id.action_sleep_timer -> {
|
R.id.action_sleep_timer -> {
|
||||||
SleepTimerDialog().show(fragmentManager!!, TAG)
|
SleepTimerDialog().show(requireFragmentManager(), TAG)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
R.id.action_set_as_ringtone -> {
|
R.id.action_set_as_ringtone -> {
|
||||||
if (RingtoneManager.requiresDialog(activity!!)) {
|
if (RingtoneManager.requiresDialog(requireActivity())) {
|
||||||
RingtoneManager.getDialog(activity!!)
|
RingtoneManager.getDialog(requireActivity())
|
||||||
}
|
}
|
||||||
val ringtoneManager = RingtoneManager(activity!!)
|
val ringtoneManager = RingtoneManager(requireActivity())
|
||||||
ringtoneManager.setRingtone(song)
|
ringtoneManager.setRingtone(song)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
R.id.action_settings -> {
|
R.id.action_settings -> {
|
||||||
NavigationUtil.goToSettings(activity!!)
|
NavigationUtil.goToSettings(requireActivity())
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
R.id.action_go_to_genre -> {
|
R.id.action_go_to_genre -> {
|
||||||
|
@ -146,7 +139,7 @@ abstract class AbsPlayerFragment : AbsMusicServiceFragment(), Toolbar.OnMenuItem
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun toggleFavorite(song: Song) {
|
protected open fun toggleFavorite(song: Song) {
|
||||||
MusicUtil.toggleFavorite(activity!!, song)
|
MusicUtil.toggleFavorite(requireActivity(), song)
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract fun playerToolbar(): Toolbar
|
abstract fun playerToolbar(): Toolbar
|
||||||
|
@ -252,7 +245,7 @@ abstract class AbsPlayerFragment : AbsMusicServiceFragment(), Toolbar.OnMenuItem
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
view.setBackgroundColor(ThemeStore.primaryColor(activity!!))
|
view.setBackgroundColor(ThemeStore.primaryColor(requireActivity()))
|
||||||
if (PreferenceUtil.getInstance().fullScreenMode && view.findViewById<View>(R.id.status_bar) != null) {
|
if (PreferenceUtil.getInstance().fullScreenMode && view.findViewById<View>(R.id.status_bar) != null) {
|
||||||
view.findViewById<View>(R.id.status_bar).visibility = View.GONE
|
view.findViewById<View>(R.id.status_bar).visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package code.name.monkey.retromusic.fragments.mainactivity.folders;
|
package code.name.monkey.retromusic.fragments.mainactivity.folders;
|
||||||
|
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.app.ProgressDialog;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.media.MediaScannerConnection;
|
import android.media.MediaScannerConnection;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
@ -27,6 +26,7 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.afollestad.materialcab.MaterialCab;
|
import com.afollestad.materialcab.MaterialCab;
|
||||||
import com.google.android.material.appbar.AppBarLayout;
|
import com.google.android.material.appbar.AppBarLayout;
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
import com.google.android.material.snackbar.Snackbar;
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView;
|
import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView;
|
||||||
|
|
||||||
|
@ -64,7 +64,6 @@ import code.name.monkey.retromusic.model.Song;
|
||||||
import code.name.monkey.retromusic.util.FileUtil;
|
import code.name.monkey.retromusic.util.FileUtil;
|
||||||
import code.name.monkey.retromusic.util.PreferenceUtil;
|
import code.name.monkey.retromusic.util.PreferenceUtil;
|
||||||
import code.name.monkey.retromusic.util.RetroColorUtil;
|
import code.name.monkey.retromusic.util.RetroColorUtil;
|
||||||
import code.name.monkey.retromusic.util.RetroUtil;
|
|
||||||
import code.name.monkey.retromusic.util.ViewUtil;
|
import code.name.monkey.retromusic.util.ViewUtil;
|
||||||
import code.name.monkey.retromusic.views.BreadCrumbLayout;
|
import code.name.monkey.retromusic.views.BreadCrumbLayout;
|
||||||
|
|
||||||
|
@ -729,14 +728,13 @@ public class FoldersFragment extends AbsMainActivityFragment implements
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Dialog createDialog(@NonNull Context context) {
|
protected Dialog createDialog(@NonNull Context context) {
|
||||||
ProgressDialog dialog = new ProgressDialog(context);
|
return new MaterialAlertDialogBuilder(context)
|
||||||
dialog.setIndeterminate(true);
|
.setTitle(R.string.listing_files)
|
||||||
dialog.setTitle(R.string.listing_files);
|
.setCancelable(false)
|
||||||
dialog.setCancelable(false);
|
.setView(R.layout.loading)
|
||||||
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
.setOnCancelListener(dialog -> cancel(false))
|
||||||
dialog.setOnCancelListener(dialog1 -> cancel(false));
|
.setOnDismissListener(dialog -> cancel(false))
|
||||||
dialog.setOnDismissListener(dialog1 -> cancel(false));
|
.create();
|
||||||
return dialog;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,8 +26,6 @@ import android.provider.DocumentsContract
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
|
||||||
import code.name.monkey.retromusic.R
|
|
||||||
import code.name.monkey.retromusic.loaders.SongLoader
|
import code.name.monkey.retromusic.loaders.SongLoader
|
||||||
import code.name.monkey.retromusic.model.Song
|
import code.name.monkey.retromusic.model.Song
|
||||||
import code.name.monkey.retromusic.service.MusicService
|
import code.name.monkey.retromusic.service.MusicService
|
||||||
|
@ -45,6 +43,13 @@ object MusicPlayerRemote {
|
||||||
val isPlaying: Boolean
|
val isPlaying: Boolean
|
||||||
get() = musicService != null && musicService!!.isPlaying
|
get() = musicService != null && musicService!!.isPlaying
|
||||||
|
|
||||||
|
fun isPlaying(song: Song): Boolean {
|
||||||
|
return if (!isPlaying) {
|
||||||
|
false
|
||||||
|
} else song.id == currentSong.id
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
val currentSong: Song
|
val currentSong: Song
|
||||||
get() = if (musicService != null) {
|
get() = if (musicService != null) {
|
||||||
musicService!!.currentSong
|
musicService!!.currentSong
|
||||||
|
@ -278,7 +283,7 @@ object MusicPlayerRemote {
|
||||||
queue.add(song)
|
queue.add(song)
|
||||||
openQueue(queue, 0, false)
|
openQueue(queue, 0, false)
|
||||||
}
|
}
|
||||||
Toast.makeText(musicService, musicService!!.resources.getString(R.string.added_title_to_playing_queue), Toast.LENGTH_SHORT).show()
|
Toast.makeText(musicService, musicService!!.resources.getString(code.name.monkey.retromusic.R.string.added_title_to_playing_queue), Toast.LENGTH_SHORT).show()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
@ -291,7 +296,7 @@ object MusicPlayerRemote {
|
||||||
} else {
|
} else {
|
||||||
openQueue(songs, 0, false)
|
openQueue(songs, 0, false)
|
||||||
}
|
}
|
||||||
val toast = if (songs.size == 1) musicService!!.resources.getString(R.string.added_title_to_playing_queue) else musicService!!.resources.getString(R.string.added_x_titles_to_playing_queue, songs.size)
|
val toast = if (songs.size == 1) musicService!!.resources.getString(code.name.monkey.retromusic.R.string.added_title_to_playing_queue) else musicService!!.resources.getString(code.name.monkey.retromusic.R.string.added_x_titles_to_playing_queue, songs.size)
|
||||||
Toast.makeText(musicService, toast, Toast.LENGTH_SHORT).show()
|
Toast.makeText(musicService, toast, Toast.LENGTH_SHORT).show()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -307,7 +312,7 @@ object MusicPlayerRemote {
|
||||||
queue.add(song)
|
queue.add(song)
|
||||||
openQueue(queue, 0, false)
|
openQueue(queue, 0, false)
|
||||||
}
|
}
|
||||||
Toast.makeText(musicService, musicService!!.resources.getString(R.string.added_title_to_playing_queue), Toast.LENGTH_SHORT).show()
|
Toast.makeText(musicService, musicService!!.resources.getString(code.name.monkey.retromusic.R.string.added_title_to_playing_queue), Toast.LENGTH_SHORT).show()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
@ -320,7 +325,7 @@ object MusicPlayerRemote {
|
||||||
} else {
|
} else {
|
||||||
openQueue(songs, 0, false)
|
openQueue(songs, 0, false)
|
||||||
}
|
}
|
||||||
val toast = if (songs.size == 1) musicService!!.resources.getString(R.string.added_title_to_playing_queue) else musicService!!.resources.getString(R.string.added_x_titles_to_playing_queue, songs.size)
|
val toast = if (songs.size == 1) musicService!!.resources.getString(code.name.monkey.retromusic.R.string.added_title_to_playing_queue) else musicService!!.resources.getString(code.name.monkey.retromusic.R.string.added_x_titles_to_playing_queue, songs.size)
|
||||||
Toast.makeText(musicService, toast, Toast.LENGTH_SHORT).show()
|
Toast.makeText(musicService, toast, Toast.LENGTH_SHORT).show()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,7 +57,6 @@ public final class FileUtil {
|
||||||
stream.close();
|
stream.close();
|
||||||
return baos.toByteArray();
|
return baos.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public static Observable<ArrayList<Song>> matchFilesWithMediaStore(@NonNull Context context,
|
public static Observable<ArrayList<Song>> matchFilesWithMediaStore(@NonNull Context context,
|
||||||
@Nullable List<File> files) {
|
@Nullable List<File> files) {
|
||||||
|
@ -263,4 +262,6 @@ public final class FileUtil {
|
||||||
return file.getAbsoluteFile();
|
return file.getAbsoluteFile();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,6 @@ import android.os.Environment;
|
||||||
import android.provider.BaseColumns;
|
import android.provider.BaseColumns;
|
||||||
import android.provider.MediaStore;
|
import android.provider.MediaStore;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
@ -263,64 +262,80 @@ public class MusicUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void deleteTracks(@NonNull final Activity activity,
|
public static void deleteTracks(@NonNull final Activity activity,
|
||||||
@NonNull final List<Song> songs) {
|
@NonNull final List<Song> songs,
|
||||||
|
@Nullable final List<Uri> safUris,
|
||||||
|
@Nullable final Runnable callback) {
|
||||||
final String[] projection = new String[]{
|
final String[] projection = new String[]{
|
||||||
BaseColumns._ID, MediaStore.MediaColumns.DATA
|
BaseColumns._ID, MediaStore.MediaColumns.DATA
|
||||||
};
|
};
|
||||||
final StringBuilder selection = new StringBuilder();
|
|
||||||
selection.append(BaseColumns._ID + " IN (");
|
// Split the query into multiple batches, and merge the resulting cursors
|
||||||
for (int i = 0; i < songs.size(); i++) {
|
int batchStart = 0;
|
||||||
selection.append(songs.get(i).getId());
|
int batchEnd = 0;
|
||||||
if (i < songs.size() - 1) {
|
final int batchSize = 1000000 / 10; // 10^6 being the SQLite limite on the query lenth in bytes, 10 being the max number of digits in an int, used to store the track ID
|
||||||
|
final int songCount = songs.size();
|
||||||
|
|
||||||
|
while (batchEnd < songCount) {
|
||||||
|
batchStart = batchEnd;
|
||||||
|
|
||||||
|
final StringBuilder selection = new StringBuilder();
|
||||||
|
selection.append(BaseColumns._ID + " IN (");
|
||||||
|
|
||||||
|
for (int i = 0; (i < batchSize - 1) && (batchEnd < songCount - 1); i++, batchEnd++) {
|
||||||
|
selection.append(songs.get(batchEnd).getId());
|
||||||
selection.append(",");
|
selection.append(",");
|
||||||
}
|
}
|
||||||
}
|
// The last element of a batch
|
||||||
selection.append(")");
|
selection.append(songs.get(batchEnd).getId());
|
||||||
|
batchEnd++;
|
||||||
|
selection.append(")");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final Cursor cursor = activity.getContentResolver().query(
|
final Cursor cursor = activity.getContentResolver().query(
|
||||||
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, selection.toString(),
|
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, selection.toString(),
|
||||||
null, null);
|
null, null);
|
||||||
if (cursor != null) {
|
// TODO: At this point, there is no guarantee that the size of the cursor is the same as the size of the selection string.
|
||||||
// Step 1: Remove selected tracks from the current playlist, as well
|
// Despite that, the Step 3 assumes that the safUris elements are tracking closely the content of the cursor.
|
||||||
// as from the album art cache
|
|
||||||
cursor.moveToFirst();
|
|
||||||
while (!cursor.isAfterLast()) {
|
|
||||||
final int id = cursor.getInt(0);
|
|
||||||
Song song = SongLoader.INSTANCE.getSong(activity, id).blockingFirst();
|
|
||||||
MusicPlayerRemote.INSTANCE.removeFromQueue(song);
|
|
||||||
cursor.moveToNext();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 2: Remove selected tracks from the database
|
if (cursor != null) {
|
||||||
activity.getContentResolver().delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
|
// Step 1: Remove selected tracks from the current playlist, as well
|
||||||
selection.toString(), null);
|
// as from the album art cache
|
||||||
|
cursor.moveToFirst();
|
||||||
// Step 3: Remove files from card
|
while (!cursor.isAfterLast()) {
|
||||||
cursor.moveToFirst();
|
final int id = cursor.getInt(0);
|
||||||
while (!cursor.isAfterLast()) {
|
final Song song = SongLoader.INSTANCE.getSong(activity, id).blockingFirst();
|
||||||
final String name = cursor.getString(1);
|
MusicPlayerRemote.INSTANCE.removeFromQueue(song);
|
||||||
try { // File.delete can throw a security exception
|
|
||||||
final File f = new File(name);
|
|
||||||
if (!f.delete()) {
|
|
||||||
// I'm not sure if we'd ever get here (deletion would
|
|
||||||
// have to fail, but no exception thrown)
|
|
||||||
Log.e("MusicUtils", "Failed to delete file " + name);
|
|
||||||
}
|
|
||||||
cursor.moveToNext();
|
cursor.moveToNext();
|
||||||
} catch (@NonNull final SecurityException ex) {
|
|
||||||
cursor.moveToNext();
|
|
||||||
} catch (NullPointerException e) {
|
|
||||||
Log.e("MusicUtils", "Failed to find file " + name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Step 2: Remove selected tracks from the database
|
||||||
|
activity.getContentResolver().delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
|
||||||
|
selection.toString(), null);
|
||||||
|
|
||||||
|
// Step 3: Remove files from card
|
||||||
|
cursor.moveToFirst();
|
||||||
|
int i = batchStart;
|
||||||
|
while (!cursor.isAfterLast()) {
|
||||||
|
final String name = cursor.getString(1);
|
||||||
|
final Uri safUri = safUris == null || safUris.size() <= i ? null : safUris.get(i);
|
||||||
|
SAFUtil.delete(activity, name, safUri);
|
||||||
|
i++;
|
||||||
|
cursor.moveToNext();
|
||||||
|
}
|
||||||
|
cursor.close();
|
||||||
}
|
}
|
||||||
cursor.close();
|
} catch (SecurityException ignored) {
|
||||||
}
|
}
|
||||||
activity.getContentResolver().notifyChange(Uri.parse("content://media"), null);
|
|
||||||
Toast.makeText(activity, activity.getString(R.string.deleted_x_songs, songs.size()),
|
|
||||||
Toast.LENGTH_SHORT).show();
|
|
||||||
} catch (SecurityException ignored) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
activity.getContentResolver().notifyChange(Uri.parse("content://media"), null);
|
||||||
|
|
||||||
|
activity.runOnUiThread(() -> {
|
||||||
|
Toast.makeText(activity, activity.getString(R.string.deleted_x_songs, songCount), Toast.LENGTH_SHORT).show();
|
||||||
|
if (callback != null) {
|
||||||
|
callback.run();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void deleteAlbumArt(@NonNull Context context, int albumId) {
|
public static void deleteAlbumArt(@NonNull Context context, int albumId) {
|
||||||
|
|
|
@ -19,6 +19,7 @@ import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.SharedPreferences.Editor;
|
import android.content.SharedPreferences.Editor;
|
||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
|
import android.net.Uri;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
|
||||||
import androidx.annotation.LayoutRes;
|
import androidx.annotation.LayoutRes;
|
||||||
|
@ -87,6 +88,7 @@ public final class PreferenceUtil {
|
||||||
public static final String ALBUM_COVER_STYLE = "album_cover_style_id";
|
public static final String ALBUM_COVER_STYLE = "album_cover_style_id";
|
||||||
public static final String ALBUM_COVER_TRANSFORM = "album_cover_transform";
|
public static final String ALBUM_COVER_TRANSFORM = "album_cover_transform";
|
||||||
public static final String TAB_TEXT_MODE = "tab_text_mode";
|
public static final String TAB_TEXT_MODE = "tab_text_mode";
|
||||||
|
public static final String SAF_SDCARD_URI = "saf_sdcard_uri";
|
||||||
private static final String GENRE_SORT_ORDER = "genre_sort_order";
|
private static final String GENRE_SORT_ORDER = "genre_sort_order";
|
||||||
private static final String LAST_PAGE = "last_start_page";
|
private static final String LAST_PAGE = "last_start_page";
|
||||||
private static final String LAST_MUSIC_CHOOSER = "last_music_chooser";
|
private static final String LAST_MUSIC_CHOOSER = "last_music_chooser";
|
||||||
|
@ -281,7 +283,6 @@ public final class PreferenceUtil {
|
||||||
return Integer.parseInt(mPreferences.getString(DEFAULT_START_PAGE, "-1"));
|
return Integer.parseInt(mPreferences.getString(DEFAULT_START_PAGE, "-1"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public final int getLastPage() {
|
public final int getLastPage() {
|
||||||
return mPreferences.getInt(LAST_PAGE, R.id.action_song);
|
return mPreferences.getInt(LAST_PAGE, R.id.action_song);
|
||||||
}
|
}
|
||||||
|
@ -292,7 +293,6 @@ public final class PreferenceUtil {
|
||||||
editor.apply();
|
editor.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void setLastLyricsType(int group) {
|
public void setLastLyricsType(int group) {
|
||||||
final SharedPreferences.Editor editor = mPreferences.edit();
|
final SharedPreferences.Editor editor = mPreferences.edit();
|
||||||
editor.putInt(LAST_KNOWN_LYRICS_TYPE, group);
|
editor.putInt(LAST_KNOWN_LYRICS_TYPE, group);
|
||||||
|
@ -388,7 +388,6 @@ public final class PreferenceUtil {
|
||||||
return mPreferences.getBoolean(IGNORE_MEDIA_STORE_ARTWORK, false);
|
return mPreferences.getBoolean(IGNORE_MEDIA_STORE_ARTWORK, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public int getLastSleepTimerValue() {
|
public int getLastSleepTimerValue() {
|
||||||
return mPreferences.getInt(LAST_SLEEP_TIMER_VALUE, 30);
|
return mPreferences.getInt(LAST_SLEEP_TIMER_VALUE, 30);
|
||||||
}
|
}
|
||||||
|
@ -673,7 +672,6 @@ public final class PreferenceUtil {
|
||||||
return mPreferences.getBoolean(TOGGLE_HEADSET, false);
|
return mPreferences.getBoolean(TOGGLE_HEADSET, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public boolean isDominantColor() {
|
public boolean isDominantColor() {
|
||||||
return mPreferences.getBoolean(DOMINANT_COLOR, false);
|
return mPreferences.getBoolean(DOMINANT_COLOR, false);
|
||||||
}
|
}
|
||||||
|
@ -698,7 +696,6 @@ public final class PreferenceUtil {
|
||||||
mPreferences.edit().putBoolean(CIRCULAR_ALBUM_ART, false).apply();
|
mPreferences.edit().putBoolean(CIRCULAR_ALBUM_ART, false).apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public String getAlbumDetailsStyle() {
|
public String getAlbumDetailsStyle() {
|
||||||
return mPreferences.getString(ALBUM_DETAIL_STYLE, "0");
|
return mPreferences.getString(ALBUM_DETAIL_STYLE, "0");
|
||||||
}
|
}
|
||||||
|
@ -738,7 +735,6 @@ public final class PreferenceUtil {
|
||||||
return mPreferences.getBoolean(PAUSE_ON_ZERO_VOLUME, false);
|
return mPreferences.getBoolean(PAUSE_ON_ZERO_VOLUME, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public ViewPager.PageTransformer getAlbumCoverTransform() {
|
public ViewPager.PageTransformer getAlbumCoverTransform() {
|
||||||
int style = Integer.parseInt(Objects.requireNonNull(mPreferences.getString(ALBUM_COVER_TRANSFORM, "0")));
|
int style = Integer.parseInt(Objects.requireNonNull(mPreferences.getString(ALBUM_COVER_TRANSFORM, "0")));
|
||||||
switch (style) {
|
switch (style) {
|
||||||
|
@ -859,4 +855,12 @@ public final class PreferenceUtil {
|
||||||
defaultCategoryInfos.add(new CategoryInfo(CategoryInfo.Category.GENRES, false));
|
defaultCategoryInfos.add(new CategoryInfo(CategoryInfo.Category.GENRES, false));
|
||||||
return defaultCategoryInfos;
|
return defaultCategoryInfos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final String getSAFSDCardUri() {
|
||||||
|
return mPreferences.getString(SAF_SDCARD_URI, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void setSAFSDCardUri(Uri uri) {
|
||||||
|
mPreferences.edit().putString(SAF_SDCARD_URI, uri.toString()).apply();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
303
app/src/main/java/code/name/monkey/retromusic/util/SAFUtil.java
Normal file
303
app/src/main/java/code/name/monkey/retromusic/util/SAFUtil.java
Normal file
|
@ -0,0 +1,303 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019 Hemanth Savarala.
|
||||||
|
*
|
||||||
|
* Licensed under the GNU General Public License v3
|
||||||
|
*
|
||||||
|
* This is free software: you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the GNU General Public License for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package code.name.monkey.retromusic.util;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.UriPermission;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.ParcelFileDescriptor;
|
||||||
|
import android.provider.DocumentsContract;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.documentfile.provider.DocumentFile;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
|
import org.jaudiotagger.audio.AudioFile;
|
||||||
|
import org.jaudiotagger.audio.exceptions.CannotWriteException;
|
||||||
|
import org.jaudiotagger.audio.generic.Utils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import code.name.monkey.retromusic.R;
|
||||||
|
import code.name.monkey.retromusic.model.Song;
|
||||||
|
|
||||||
|
public class SAFUtil {
|
||||||
|
|
||||||
|
public static final String TAG = SAFUtil.class.getSimpleName();
|
||||||
|
public static final String SEPARATOR = "###/SAF/###";
|
||||||
|
|
||||||
|
public static final int REQUEST_SAF_PICK_FILE = 42;
|
||||||
|
public static final int REQUEST_SAF_PICK_TREE = 43;
|
||||||
|
|
||||||
|
public static boolean isSAFRequired(File file) {
|
||||||
|
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && !file.canWrite();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isSAFRequired(String path) {
|
||||||
|
return isSAFRequired(new File(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isSAFRequired(AudioFile audio) {
|
||||||
|
return isSAFRequired(audio.getFile());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isSAFRequired(Song song) {
|
||||||
|
return isSAFRequired(song.getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isSAFRequired(List<String> paths) {
|
||||||
|
for (String path : paths) {
|
||||||
|
if (isSAFRequired(path)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isSAFRequiredForSongs(List<Song> songs) {
|
||||||
|
for (Song song : songs) {
|
||||||
|
if (isSAFRequired(song)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.KITKAT)
|
||||||
|
public static void openFilePicker(Activity activity) {
|
||||||
|
Intent i = new Intent(Intent.ACTION_CREATE_DOCUMENT);
|
||||||
|
i.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
|
i.setType("audio/*");
|
||||||
|
i.putExtra("android.content.extra.SHOW_ADVANCED", true);
|
||||||
|
activity.startActivityForResult(i, SAFUtil.REQUEST_SAF_PICK_FILE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.KITKAT)
|
||||||
|
public static void openFilePicker(Fragment fragment) {
|
||||||
|
Intent i = new Intent(Intent.ACTION_CREATE_DOCUMENT);
|
||||||
|
i.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
|
i.setType("audio/*");
|
||||||
|
i.putExtra("android.content.extra.SHOW_ADVANCED", true);
|
||||||
|
fragment.startActivityForResult(i, SAFUtil.REQUEST_SAF_PICK_FILE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||||
|
public static void openTreePicker(Activity activity) {
|
||||||
|
Intent i = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||||
|
i.putExtra("android.content.extra.SHOW_ADVANCED", true);
|
||||||
|
activity.startActivityForResult(i, SAFUtil.REQUEST_SAF_PICK_TREE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||||
|
public static void openTreePicker(Fragment fragment) {
|
||||||
|
Intent i = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||||
|
i.putExtra("android.content.extra.SHOW_ADVANCED", true);
|
||||||
|
fragment.startActivityForResult(i, SAFUtil.REQUEST_SAF_PICK_TREE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.KITKAT)
|
||||||
|
public static void saveTreeUri(Context context, Intent data) {
|
||||||
|
Uri uri = data.getData();
|
||||||
|
context.getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
|
PreferenceUtil.getInstance().setSAFSDCardUri(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||||
|
public static boolean isTreeUriSaved(Context context) {
|
||||||
|
return !TextUtils.isEmpty(PreferenceUtil.getInstance().getSAFSDCardUri());
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||||
|
public static boolean isSDCardAccessGranted(Context context) {
|
||||||
|
if (!isTreeUriSaved(context)) return false;
|
||||||
|
|
||||||
|
String sdcardUri = PreferenceUtil.getInstance().getSAFSDCardUri();
|
||||||
|
|
||||||
|
List<UriPermission> perms = context.getContentResolver().getPersistedUriPermissions();
|
||||||
|
for (UriPermission perm : perms) {
|
||||||
|
if (perm.getUri().toString().equals(sdcardUri) && perm.isWritePermission()) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://github.com/vanilla-music/vanilla-music-tag-editor/commit/e00e87fef289f463b6682674aa54be834179ccf0#diff-d436417358d5dfbb06846746d43c47a5R359
|
||||||
|
* Finds needed file through Document API for SAF. It's not optimized yet - you can still gain wrong URI on
|
||||||
|
* files such as "/a/b/c.mp3" and "/b/a/c.mp3", but I consider it complete enough to be usable.
|
||||||
|
*
|
||||||
|
* @param dir - document file representing current dir of search
|
||||||
|
* @param segments - path segments that are left to find
|
||||||
|
* @return URI for found file. Null if nothing found.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public static Uri findDocument(DocumentFile dir, List<String> segments) {
|
||||||
|
for (DocumentFile file : dir.listFiles()) {
|
||||||
|
int index = segments.indexOf(file.getName());
|
||||||
|
if (index == -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
segments.remove(file.getName());
|
||||||
|
return findDocument(file, segments);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.isFile() && index == segments.size() - 1) {
|
||||||
|
// got to the last part
|
||||||
|
return file.getUri();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void write(Context context, AudioFile audio, Uri safUri) {
|
||||||
|
if (isSAFRequired(audio)) {
|
||||||
|
writeSAF(context, audio, safUri);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
writeFile(audio);
|
||||||
|
} catch (CannotWriteException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void writeFile(AudioFile audio) throws CannotWriteException {
|
||||||
|
audio.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void writeSAF(Context context, AudioFile audio, Uri safUri) {
|
||||||
|
Uri uri = null;
|
||||||
|
|
||||||
|
if (context == null) {
|
||||||
|
Log.e(TAG, "writeSAF: context == null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isTreeUriSaved(context)) {
|
||||||
|
List<String> pathSegments = new ArrayList<>(Arrays.asList(audio.getFile().getAbsolutePath().split("/")));
|
||||||
|
Uri sdcard = Uri.parse(PreferenceUtil.getInstance().getSAFSDCardUri());
|
||||||
|
uri = findDocument(DocumentFile.fromTreeUri(context, sdcard), pathSegments);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uri == null) {
|
||||||
|
uri = safUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uri == null) {
|
||||||
|
Log.e(TAG, "writeSAF: Can't get SAF URI");
|
||||||
|
toast(context, context.getString(R.string.saf_error_uri));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// copy file to app folder to use jaudiotagger
|
||||||
|
final File original = audio.getFile();
|
||||||
|
File temp = File.createTempFile("tmp-media", '.' + Utils.getExtension(original));
|
||||||
|
Utils.copy(original, temp);
|
||||||
|
temp.deleteOnExit();
|
||||||
|
audio.setFile(temp);
|
||||||
|
writeFile(audio);
|
||||||
|
|
||||||
|
ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(uri, "rw");
|
||||||
|
if (pfd == null) {
|
||||||
|
Log.e(TAG, "writeSAF: SAF provided incorrect URI: " + uri);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// now read persisted data and write it to real FD provided by SAF
|
||||||
|
FileInputStream fis = new FileInputStream(temp);
|
||||||
|
byte[] audioContent = FileUtil.readBytes(fis);
|
||||||
|
FileOutputStream fos = new FileOutputStream(pfd.getFileDescriptor());
|
||||||
|
fos.write(audioContent);
|
||||||
|
fos.close();
|
||||||
|
|
||||||
|
temp.delete();
|
||||||
|
} catch (final Exception e) {
|
||||||
|
Log.e(TAG, "writeSAF: Failed to write to file descriptor provided by SAF", e);
|
||||||
|
|
||||||
|
toast(context, String.format(context.getString(R.string.saf_write_failed), e.getLocalizedMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void delete(Context context, String path, Uri safUri) {
|
||||||
|
if (isSAFRequired(path)) {
|
||||||
|
deleteSAF(context, path, safUri);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
deleteFile(path);
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
Log.e("MusicUtils", "Failed to find file " + path);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void deleteFile(String path) {
|
||||||
|
new File(path).delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.KITKAT)
|
||||||
|
public static void deleteSAF(Context context, String path, Uri safUri) {
|
||||||
|
Uri uri = null;
|
||||||
|
|
||||||
|
if (context == null) {
|
||||||
|
Log.e(TAG, "deleteSAF: context == null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isTreeUriSaved(context)) {
|
||||||
|
List<String> pathSegments = new ArrayList<>(Arrays.asList(path.split("/")));
|
||||||
|
Uri sdcard = Uri.parse(PreferenceUtil.getInstance().getSAFSDCardUri());
|
||||||
|
uri = findDocument(DocumentFile.fromTreeUri(context, sdcard), pathSegments);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uri == null) {
|
||||||
|
uri = safUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uri == null) {
|
||||||
|
Log.e(TAG, "deleteSAF: Can't get SAF URI");
|
||||||
|
toast(context, context.getString(R.string.saf_error_uri));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
DocumentsContract.deleteDocument(context.getContentResolver(), uri);
|
||||||
|
} catch (final Exception e) {
|
||||||
|
Log.e(TAG, "deleteSAF: Failed to delete a file descriptor provided by SAF", e);
|
||||||
|
|
||||||
|
toast(context, String.format(context.getString(R.string.saf_delete_failed), e.getLocalizedMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void toast(final Context context, final String message) {
|
||||||
|
if (context instanceof Activity) {
|
||||||
|
((Activity) context).runOnUiThread(() -> Toast.makeText(context, message, Toast.LENGTH_SHORT).show());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -56,6 +56,9 @@ public class OptionMenuItemView extends FrameLayout {
|
||||||
int accentColor = ThemeStore.Companion.accentColor(context);
|
int accentColor = ThemeStore.Companion.accentColor(context);
|
||||||
setBackground(ContextCompat.getDrawable(context, R.drawable.menu_item_background));
|
setBackground(ContextCompat.getDrawable(context, R.drawable.menu_item_background));
|
||||||
|
|
||||||
|
setClickable(true);
|
||||||
|
setFocusable(true);
|
||||||
|
|
||||||
inflate(context, R.layout.item_option_menu, this);
|
inflate(context, R.layout.item_option_menu, this);
|
||||||
|
|
||||||
setBackgroundTintList(ColorStateList.valueOf(ColorUtil.INSTANCE.adjustAlpha(accentColor, 0.22f)));
|
setBackgroundTintList(ColorStateList.valueOf(ColorUtil.INSTANCE.adjustAlpha(accentColor, 0.22f)));
|
||||||
|
|
BIN
app/src/main/res/drawable-v21/saf_guide_1.png
Normal file
BIN
app/src/main/res/drawable-v21/saf_guide_1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.2 KiB |
BIN
app/src/main/res/drawable-v21/saf_guide_2.png
Normal file
BIN
app/src/main/res/drawable-v21/saf_guide_2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.5 KiB |
BIN
app/src/main/res/drawable-v21/saf_guide_3.png
Normal file
BIN
app/src/main/res/drawable-v21/saf_guide_3.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
BIN
app/src/main/res/drawable-v26/saf_guide_1.png
Normal file
BIN
app/src/main/res/drawable-v26/saf_guide_1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.2 KiB |
BIN
app/src/main/res/drawable-v26/saf_guide_2.png
Normal file
BIN
app/src/main/res/drawable-v26/saf_guide_2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.1 KiB |
BIN
app/src/main/res/drawable-v26/saf_guide_3.png
Normal file
BIN
app/src/main/res/drawable-v26/saf_guide_3.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.6 KiB |
|
@ -12,7 +12,7 @@
|
||||||
~ See the GNU General Public License for more details.
|
~ See the GNU General Public License for more details.
|
||||||
-->
|
-->
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item android:state_selected="true">
|
<item android:state_selected="true" >
|
||||||
<shape>
|
<shape>
|
||||||
<corners android:bottomRightRadius="30dp" android:topRightRadius="30dp" />
|
<corners android:bottomRightRadius="30dp" android:topRightRadius="30dp" />
|
||||||
<solid android:color="@color/md_red_400" />
|
<solid android:color="@color/md_red_400" />
|
||||||
|
|
|
@ -13,9 +13,6 @@
|
||||||
android:id="@+id/actionLibrary"
|
android:id="@+id/actionLibrary"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="?rectSelector"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
app:optionIcon="@drawable/ic_library_music_white_24dp"
|
app:optionIcon="@drawable/ic_library_music_white_24dp"
|
||||||
app:optionTitle="@string/library" />
|
app:optionTitle="@string/library" />
|
||||||
|
|
||||||
|
@ -23,9 +20,6 @@
|
||||||
android:id="@+id/actionFolders"
|
android:id="@+id/actionFolders"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="?rectSelector"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
app:optionIcon="@drawable/ic_folder_white_24dp"
|
app:optionIcon="@drawable/ic_folder_white_24dp"
|
||||||
app:optionTitle="@string/folders" />
|
app:optionTitle="@string/folders" />
|
||||||
|
|
||||||
|
@ -33,10 +27,13 @@
|
||||||
android:id="@+id/actionSettings"
|
android:id="@+id/actionSettings"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="?rectSelector"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
app:optionIcon="@drawable/ic_settings_white_24dp"
|
app:optionIcon="@drawable/ic_settings_white_24dp"
|
||||||
app:optionTitle="@string/action_settings" />
|
app:optionTitle="@string/action_settings" />
|
||||||
|
|
||||||
|
<code.name.monkey.retromusic.views.OptionMenuItemView
|
||||||
|
android:id="@+id/actionRate"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:optionIcon="@drawable/ic_star_white_24dp"
|
||||||
|
app:optionTitle="@string/rate_app" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
|
@ -0,0 +1,56 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
~ Copyright (c) 2019 Hemanth Savarala.
|
||||||
|
~
|
||||||
|
~ Licensed under the GNU General Public License v3
|
||||||
|
~
|
||||||
|
~ This is free software: you can redistribute it and/or modify it under
|
||||||
|
~ the terms of the GNU General Public License as published by
|
||||||
|
~ the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||||
|
~
|
||||||
|
~ This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
|
~ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
~ See the GNU General Public License for more details.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<com.heinrichreimersoftware.materialintro.view.parallax.ParallaxLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:fitsSystemWindows="false"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="@dimen/mi_baseline">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@id/mi_image"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginBottom="@dimen/mi_baseline"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center"
|
||||||
|
app:layout_parallaxFactor="0.75"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@id/mi_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/mi_baseline"
|
||||||
|
android:fontFamily="sans-serif-medium"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Headline"
|
||||||
|
tools:ignore="UnusedAttribute"
|
||||||
|
tools:text="Lorem ipsum" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@id/mi_description"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/mi_baseline"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Subhead"
|
||||||
|
tools:text="Lorem ipsum dolor sit amet, consectetur, adipisci velit, …" />
|
||||||
|
|
||||||
|
</com.heinrichreimersoftware.materialintro.view.parallax.ParallaxLinearLayout>
|
28
app/src/main/res/layout/loading.xml
Normal file
28
app/src/main/res/layout/loading.xml
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
~ Copyright (c) 2019 Hemanth Savarala.
|
||||||
|
~
|
||||||
|
~ Licensed under the GNU General Public License v3
|
||||||
|
~
|
||||||
|
~ This is free software: you can redistribute it and/or modify it under
|
||||||
|
~ the terms of the GNU General Public License as published by
|
||||||
|
~ the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||||
|
~
|
||||||
|
~ This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
|
~ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
~ See the GNU General Public License for more details.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="14dp">
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:indeterminate="true" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
|
@ -420,7 +420,8 @@
|
||||||
<string name="hinge">合页</string>
|
<string name="hinge">合页</string>
|
||||||
<string name="select_preset">选择一个预设</string>
|
<string name="select_preset">选择一个预设</string>
|
||||||
<string name="on">开启</string>
|
<string name="on">开启</string>
|
||||||
<string name="shuffle">随机播放</string> `
|
<string name="shuffle">随机播放</string>
|
||||||
|
`
|
||||||
<string name="add_time_framed_lryics">加入带有时间轴的歌词</string>
|
<string name="add_time_framed_lryics">加入带有时间轴的歌词</string>
|
||||||
<string name="dialog_title_set_ringtone">设置铃声</string>
|
<string name="dialog_title_set_ringtone">设置铃声</string>
|
||||||
<string name="dialog_message_set_ringtone">请允许 Retro Music 更改音频设置</string>
|
<string name="dialog_message_set_ringtone">请允许 Retro Music 更改音频设置</string>
|
||||||
|
|
|
@ -632,4 +632,21 @@
|
||||||
<string name="pref_summary_library_categories">Configure visibility and order of library categories.</string>
|
<string name="pref_summary_library_categories">Configure visibility and order of library categories.</string>
|
||||||
<string name="pref_header_controls">Controls</string>
|
<string name="pref_header_controls">Controls</string>
|
||||||
<string name="pref_header_album">Album style</string>
|
<string name="pref_header_album">Album style</string>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- SAF -->
|
||||||
|
<string name="saf_error_uri">Can\'t get SAF URI</string>
|
||||||
|
<string name="saf_write_failed">File write failed: %s</string>
|
||||||
|
<string name="saf_delete_failed">File delete failed: %s</string>
|
||||||
|
<string name="saf_pick_sdcard">SD card access required. Please pick root directory of SD card</string>
|
||||||
|
<string name="saf_pick_file">File access required. Pick %s</string>
|
||||||
|
<!-- SAF guide -->
|
||||||
|
<string name="saf_guide_slide1_title">%s needs SD card access</string>
|
||||||
|
<string name="saf_guide_slide1_description_before_o">Enable \'Show SD card\' in overflow menu</string>
|
||||||
|
<string name="saf_guide_slide1_description">Open navigation drawer</string>
|
||||||
|
<string name="saf_guide_slide2_title">Select your SD card in navigation drawer</string>
|
||||||
|
<string name="saf_guide_slide2_description">You need to select your SD card root directory</string>
|
||||||
|
<string name="saf_guide_slide3_title">Tap \'select\' button at the bottom of the screen</string>
|
||||||
|
<string name="saf_guide_slide3_description">Do not open any sub-folders</string>
|
||||||
|
<string name="deleting_songs">Deleting songs</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -19,13 +19,25 @@
|
||||||
|
|
||||||
|
|
||||||
<!-- Deep Purple -->
|
<!-- Deep Purple -->
|
||||||
|
|
||||||
<color name="md_deep_purple_A400">#651FFF</color>
|
<color name="md_deep_purple_A400">#651FFF</color>
|
||||||
|
<color name="md_deep_purple_300">#9575CD</color>
|
||||||
|
<color name="md_deep_purple_400">#7E57C2</color>
|
||||||
|
<color name="md_deep_purple_600">#5E35B1</color>
|
||||||
|
<color name="md_deep_purple_700">#512DA8</color>
|
||||||
|
<color name="md_deep_purple_800">#4527A0</color>
|
||||||
<color name="md_deep_purple_500A12">#30673AB7</color>
|
<color name="md_deep_purple_500A12">#30673AB7</color>
|
||||||
<color name="md_deep_purple_500">#673AB7</color>
|
<color name="md_deep_purple_500">#673AB7</color>
|
||||||
<color name="md_deep_purple_A700">#6200EA</color>
|
<color name="md_deep_purple_A700">#6200EA</color>
|
||||||
|
|
||||||
<!-- Indigo -->
|
<!-- Indigo -->
|
||||||
<color name="md_indigo_500">#3F51B5</color>
|
<color name="md_indigo_500">#3F51B5</color>
|
||||||
|
<color name="md_indigo_300">#7986CB</color>
|
||||||
|
<color name="md_indigo_400">#5C6BC0</color>
|
||||||
|
<color name="md_indigo_600">#3949AB</color>
|
||||||
|
<color name="md_indigo_700">#303F9F</color>
|
||||||
|
<color name="md_indigo_800">#283593</color>
|
||||||
|
|
||||||
|
|
||||||
<!-- Blue -->
|
<!-- Blue -->
|
||||||
<color name="md_blue_500">#2196F3</color>
|
<color name="md_blue_500">#2196F3</color>
|
||||||
|
|
Loading…
Reference in a new issue