Fixed Audio Fade, Crossfade & Android Auto search
This commit is contained in:
parent
7f683eb0ee
commit
ee7545f64e
15 changed files with 269 additions and 79 deletions
|
@ -6,7 +6,6 @@ apply plugin: 'kotlin-parcelize'
|
|||
|
||||
android {
|
||||
compileSdkVersion 31
|
||||
buildToolsVersion = '29.0.3'
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
|
@ -33,6 +32,8 @@ android {
|
|||
buildTypes {
|
||||
release {
|
||||
//debuggable true
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
debug {
|
||||
|
@ -104,7 +105,7 @@ dependencies {
|
|||
//WebServer by NanoHttpd
|
||||
implementation "org.nanohttpd:nanohttpd:2.3.1"
|
||||
|
||||
def nav_version = '2.4.0-alpha09'
|
||||
def nav_version = "2.4.0-alpha09"
|
||||
implementation "androidx.navigation:navigation-runtime-ktx:$nav_version"
|
||||
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
|
||||
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
|
||||
|
@ -158,6 +159,7 @@ dependencies {
|
|||
implementation 'com.heinrichreimersoftware:material-intro:2.0.0'
|
||||
implementation 'com.github.dhaval2404:imagepicker:1.7.1'
|
||||
implementation 'me.zhanghai.android.fastscroll:library:1.1.7'
|
||||
implementation 'cat.ereza:customactivityoncrash:2.3.0'
|
||||
debugImplementation 'com.amitshekhar.android:debug-db:1.0.6'
|
||||
}
|
||||
|
||||
|
|
|
@ -265,7 +265,7 @@
|
|||
android:value="true" />
|
||||
|
||||
<!-- Android Auto -->
|
||||
<!-- <meta-data
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.car.application"
|
||||
android:resource="@xml/automotive_app_desc"/>
|
||||
<meta-data
|
||||
|
@ -274,7 +274,7 @@
|
|||
<meta-data
|
||||
android:name="com.google.android.gms.car.notification.SmallIcon"
|
||||
android:resource="@drawable/ic_notification"/>
|
||||
-->
|
||||
|
||||
<!-- ChromeCast -->
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
|
||||
|
|
|
@ -32,6 +32,7 @@ import code.name.monkey.retromusic.glide.RetroGlideExtension
|
|||
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||
import code.name.monkey.retromusic.model.Song
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil.blurAmount
|
||||
import code.name.monkey.retromusic.util.RetroUtil
|
||||
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
|
||||
|
||||
|
@ -110,8 +111,6 @@ class BlurPlayerFragment : AbsPlayerFragment(R.layout.fragment_blur),
|
|||
get() = lastColor
|
||||
|
||||
private fun updateBlur() {
|
||||
val blurAmount = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
.getInt(NEW_BLUR_AMOUNT, 25)
|
||||
binding.colorBackground.clearColorFilter()
|
||||
GlideApp.with(requireActivity()).asBitmapPalette()
|
||||
.songCoverOptions(MusicPlayerRemote.currentSong)
|
||||
|
|
|
@ -33,6 +33,7 @@ import code.name.monkey.retromusic.glide.RetroGlideExtension
|
|||
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||
import code.name.monkey.retromusic.model.Song
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil.blurAmount
|
||||
import code.name.monkey.retromusic.util.RetroUtil
|
||||
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
|
||||
import org.koin.android.ext.android.bind
|
||||
|
@ -136,8 +137,6 @@ class CardBlurFragment : AbsPlayerFragment(R.layout.fragment_card_blur_player),
|
|||
}
|
||||
|
||||
private fun updateBlur() {
|
||||
val blurAmount = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
.getInt(NEW_BLUR_AMOUNT, 25)
|
||||
binding.colorBackground.clearColorFilter()
|
||||
GlideApp.with(requireActivity()).asBitmapPalette()
|
||||
.songCoverOptions(MusicPlayerRemote.currentSong)
|
||||
|
|
|
@ -25,6 +25,7 @@ import android.os.IBinder
|
|||
import android.provider.DocumentsContract
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.ContextCompat
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.model.Song
|
||||
import code.name.monkey.retromusic.repository.SongRepository
|
||||
import code.name.monkey.retromusic.service.MusicService
|
||||
|
@ -300,7 +301,7 @@ object MusicPlayerRemote : KoinComponent {
|
|||
return false
|
||||
}
|
||||
|
||||
fun setShuffleMode(shuffleMode: Int): Boolean {
|
||||
private fun setShuffleMode(shuffleMode: Int): Boolean {
|
||||
if (musicService != null) {
|
||||
musicService!!.shuffleMode = shuffleMode
|
||||
return true
|
||||
|
@ -319,7 +320,7 @@ object MusicPlayerRemote : KoinComponent {
|
|||
}
|
||||
Toast.makeText(
|
||||
musicService,
|
||||
musicService!!.resources.getString(code.name.monkey.retromusic.R.string.added_title_to_playing_queue),
|
||||
musicService!!.resources.getString(R.string.added_title_to_playing_queue),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
return true
|
||||
|
@ -335,8 +336,8 @@ object MusicPlayerRemote : KoinComponent {
|
|||
openQueue(songs, 0, false)
|
||||
}
|
||||
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,
|
||||
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
|
||||
)
|
||||
Toast.makeText(musicService, toast, Toast.LENGTH_SHORT).show()
|
||||
|
@ -356,7 +357,7 @@ object MusicPlayerRemote : KoinComponent {
|
|||
}
|
||||
Toast.makeText(
|
||||
musicService,
|
||||
musicService!!.resources.getString(code.name.monkey.retromusic.R.string.added_title_to_playing_queue),
|
||||
musicService!!.resources.getString(R.string.added_title_to_playing_queue),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
return true
|
||||
|
@ -372,8 +373,8 @@ object MusicPlayerRemote : KoinComponent {
|
|||
openQueue(songs, 0, false)
|
||||
}
|
||||
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,
|
||||
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
|
||||
)
|
||||
Toast.makeText(musicService, toast, Toast.LENGTH_SHORT).show()
|
||||
|
|
|
@ -1,50 +1,66 @@
|
|||
package code.name.monkey.retromusic.service
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.ValueAnimator
|
||||
import android.media.MediaPlayer
|
||||
import androidx.core.animation.doOnEnd
|
||||
import code.name.monkey.retromusic.service.playback.Playback
|
||||
import java.util.*
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
|
||||
class AudioFader(
|
||||
private val player: Playback,
|
||||
durationMillis: Long,
|
||||
private val fadeIn: Boolean,
|
||||
private val doOnEnd: Runnable
|
||||
) {
|
||||
val timer = Timer()
|
||||
var volume = if (fadeIn) 0F else 1F
|
||||
val maxVolume = if (fadeIn) 1F else 0F
|
||||
private val volumeStep: Float = PERIOD / durationMillis.toFloat()
|
||||
class AudioFader {
|
||||
companion object {
|
||||
|
||||
fun start() {
|
||||
timer.scheduleAtFixedRate(
|
||||
object : TimerTask() {
|
||||
override fun run() {
|
||||
setVolume()
|
||||
if (volume < 0 || volume > 1) {
|
||||
player.setVolume(maxVolume)
|
||||
stop()
|
||||
doOnEnd.run()
|
||||
} else {
|
||||
player.setVolume(volume)
|
||||
}
|
||||
@JvmStatic
|
||||
inline fun createFadeAnimator(
|
||||
fadeIn: Boolean /* fadeIn -> true fadeOut -> false*/,
|
||||
mediaPlayer: MediaPlayer,
|
||||
crossinline endAction: (animator: Animator) -> Unit /* Code to run when Animator Ends*/
|
||||
): Animator? {
|
||||
val duration = PreferenceUtil.crossFadeDuration * 1000
|
||||
if (duration == 0) {
|
||||
return null
|
||||
}
|
||||
val startValue = if (fadeIn) 0f else 1.0f
|
||||
val endValue = if (fadeIn) 1.0f else 0f
|
||||
return ValueAnimator.ofFloat(startValue, endValue).apply {
|
||||
this.duration = duration.toLong()
|
||||
addUpdateListener { animation: ValueAnimator ->
|
||||
mediaPlayer.setVolume(
|
||||
animation.animatedValue as Float, animation.animatedValue as Float
|
||||
)
|
||||
}
|
||||
}, 0, PERIOD
|
||||
)
|
||||
}
|
||||
doOnEnd {
|
||||
endAction(it)
|
||||
// Set end values
|
||||
mediaPlayer.setVolume(endValue, endValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
timer.purge()
|
||||
timer.cancel()
|
||||
}
|
||||
|
||||
private fun setVolume() {
|
||||
if (fadeIn) {
|
||||
volume += volumeStep
|
||||
} else {
|
||||
volume -= volumeStep
|
||||
@JvmStatic
|
||||
fun startFadeAnimator(
|
||||
playback: Playback,
|
||||
fadeIn: Boolean /* fadeIn -> true fadeOut -> false*/,
|
||||
callback: Runnable /* Code to run when Animator Ends*/
|
||||
) {
|
||||
val duration = PreferenceUtil.audioFadeDuration.toLong()
|
||||
if (duration == 0L) {
|
||||
callback.run()
|
||||
return
|
||||
}
|
||||
val startValue = if (fadeIn) 0f else 1.0f
|
||||
val endValue = if (fadeIn) 1.0f else 0f
|
||||
val animator = ValueAnimator.ofFloat(startValue, endValue)
|
||||
animator.duration = duration
|
||||
animator.addUpdateListener { animation: ValueAnimator ->
|
||||
playback.setVolume(
|
||||
animation.animatedValue as Float
|
||||
)
|
||||
}
|
||||
animator.doOnEnd {
|
||||
callback.run()
|
||||
}
|
||||
animator.start()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val PERIOD = 100L
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@ package code.name.monkey.retromusic.service
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.provider.MediaStore
|
||||
import android.support.v4.media.session.MediaSessionCompat
|
||||
import code.name.monkey.retromusic.auto.AutoMediaIDHelper
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||
|
@ -31,7 +32,6 @@ import code.name.monkey.retromusic.service.MusicService.*
|
|||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
import org.koin.core.KoinComponent
|
||||
import org.koin.core.inject
|
||||
import java.util.*
|
||||
|
||||
|
||||
/**
|
||||
|
@ -56,8 +56,7 @@ class MediaSessionCallback(
|
|||
println(musicId)
|
||||
val itemId = musicId?.toLong() ?: -1
|
||||
val songs: ArrayList<Song> = ArrayList()
|
||||
val category = AutoMediaIDHelper.extractCategory(mediaId)
|
||||
when (category) {
|
||||
when (val category = AutoMediaIDHelper.extractCategory(mediaId)) {
|
||||
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ALBUM -> {
|
||||
val album: Album = albumRepository.album(itemId)
|
||||
songs.addAll(album.songs)
|
||||
|
@ -111,6 +110,43 @@ class MediaSessionCallback(
|
|||
musicService.play()
|
||||
}
|
||||
|
||||
override fun onPlayFromSearch(query: String?, extras: Bundle?) {
|
||||
val songs = ArrayList<Song>()
|
||||
if (query.isNullOrEmpty()) {
|
||||
// The user provided generic string e.g. 'Play music'
|
||||
// Build appropriate playlist queue
|
||||
songs.addAll(songRepository.songs())
|
||||
} else {
|
||||
// Build a queue based on songs that match "query" or "extras" param
|
||||
val mediaFocus: String? = extras?.getString(MediaStore.EXTRA_MEDIA_FOCUS)
|
||||
if (mediaFocus == MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE) {
|
||||
val artistQuery = extras.getString(MediaStore.EXTRA_MEDIA_ARTIST)
|
||||
if (artistQuery != null) {
|
||||
artistRepository.artists(artistQuery).forEach {
|
||||
songs.addAll(it.songs)
|
||||
}
|
||||
}
|
||||
} else if (mediaFocus == MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE) {
|
||||
val albumQuery = extras.getString(MediaStore.EXTRA_MEDIA_ALBUM)
|
||||
if (albumQuery != null) {
|
||||
albumRepository.albums(albumQuery).forEach {
|
||||
songs.addAll(it.songs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (songs.isEmpty()) {
|
||||
// No focus found, search by query for song title
|
||||
query?.also {
|
||||
songs.addAll(songRepository.songs(it))
|
||||
}
|
||||
}
|
||||
|
||||
musicService.openQueue(songs, 0, true)
|
||||
|
||||
musicService.play()
|
||||
}
|
||||
|
||||
override fun onPlay() {
|
||||
super.onPlay()
|
||||
|
|
|
@ -21,6 +21,7 @@ import static code.name.monkey.retromusic.ConstantsKt.CLASSIC_NOTIFICATION;
|
|||
import static code.name.monkey.retromusic.ConstantsKt.COLORED_NOTIFICATION;
|
||||
import static code.name.monkey.retromusic.ConstantsKt.CROSS_FADE_DURATION;
|
||||
import static code.name.monkey.retromusic.ConstantsKt.TOGGLE_HEADSET;
|
||||
import static code.name.monkey.retromusic.service.AudioFader.startFadeAnimator;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.appwidget.AppWidgetManager;
|
||||
|
@ -97,12 +98,14 @@ import code.name.monkey.retromusic.util.MusicUtil;
|
|||
import code.name.monkey.retromusic.util.PackageValidator;
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil;
|
||||
import code.name.monkey.retromusic.util.RetroUtil;
|
||||
import code.name.monkey.retromusic.volume.AudioVolumeObserver;
|
||||
import code.name.monkey.retromusic.volume.OnAudioVolumeChangedListener;
|
||||
|
||||
/**
|
||||
* @author Karim Abou Zeid (kabouzeid), Andrew Neal
|
||||
*/
|
||||
public class MusicService extends MediaBrowserServiceCompat
|
||||
implements SharedPreferences.OnSharedPreferenceChangeListener, Playback.PlaybackCallbacks {
|
||||
implements SharedPreferences.OnSharedPreferenceChangeListener, Playback.PlaybackCallbacks, OnAudioVolumeChangedListener {
|
||||
|
||||
public static final String TAG = MusicService.class.getSimpleName();
|
||||
public static final String RETRO_MUSIC_PACKAGE_NAME = "code.name.monkey.retromusic";
|
||||
|
@ -238,6 +241,7 @@ public class MusicService extends MediaBrowserServiceCompat
|
|||
private List<Song> originalPlayingQueue = new ArrayList<>();
|
||||
private List<Song> playingQueue = new ArrayList<>();
|
||||
private boolean pausedByTransientLossOfFocus;
|
||||
private AudioVolumeObserver audioVolumeObserver = null;
|
||||
|
||||
private final BroadcastReceiver becomingNoisyReceiver =
|
||||
new BroadcastReceiver() {
|
||||
|
@ -348,7 +352,6 @@ public class MusicService extends MediaBrowserServiceCompat
|
|||
private ThrottledSeekHandler throttledSeekHandler;
|
||||
private Handler uiThreadHandler;
|
||||
private PowerManager.WakeLock wakeLock;
|
||||
private AudioFader fader;
|
||||
|
||||
private static Bitmap copy(Bitmap bitmap) {
|
||||
Bitmap.Config config = bitmap.getConfig();
|
||||
|
@ -446,6 +449,9 @@ public class MusicService extends MediaBrowserServiceCompat
|
|||
.registerContentObserver(
|
||||
MediaStore.Audio.Playlists.INTERNAL_CONTENT_URI, true, mediaStoreObserver);
|
||||
|
||||
audioVolumeObserver = new AudioVolumeObserver(this);
|
||||
audioVolumeObserver.register(AudioManager.STREAM_MUSIC, this);
|
||||
|
||||
PreferenceUtil.INSTANCE.registerOnSharedPreferenceChangedListener(this);
|
||||
|
||||
restoreState();
|
||||
|
@ -491,6 +497,23 @@ public class MusicService extends MediaBrowserServiceCompat
|
|||
wakeLock.acquire(milli);
|
||||
}
|
||||
|
||||
boolean pausedByZeroVolume;
|
||||
|
||||
@Override
|
||||
public void onAudioVolumeChanged(int currentVolume, int maxVolume) {
|
||||
if (PreferenceUtil.INSTANCE.isPauseOnZeroVolume()) {
|
||||
if (isPlaying() && currentVolume < 1) {
|
||||
pause();
|
||||
System.out.println("Paused");
|
||||
pausedByZeroVolume = true;
|
||||
} else if (pausedByZeroVolume && currentVolume >= 1) {
|
||||
System.out.println("Played");
|
||||
play();
|
||||
pausedByZeroVolume = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addSong(int position, Song song) {
|
||||
playingQueue.add(position, song);
|
||||
originalPlayingQueue.add(position, song);
|
||||
|
@ -558,10 +581,10 @@ public class MusicService extends MediaBrowserServiceCompat
|
|||
}
|
||||
|
||||
public Song getNextSong() {
|
||||
if (!isLastTrack() || getRepeatMode() == REPEAT_MODE_THIS) {
|
||||
return getSongAt(getNextPosition(false));
|
||||
} else {
|
||||
if (isLastTrack() && getRepeatMode() == REPEAT_MODE_NONE) {
|
||||
return null;
|
||||
} else {
|
||||
return getSongAt(getNextPosition(false));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -926,6 +949,13 @@ public class MusicService extends MediaBrowserServiceCompat
|
|||
playerHandler.sendEmptyMessage(TRACK_ENDED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrackEndedWithCrossfade() {
|
||||
trackEndedByCrossfade = true;
|
||||
acquireWakeLock(30000);
|
||||
playerHandler.sendEmptyMessage(TRACK_ENDED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrackWentToNext() {
|
||||
playerHandler.sendEmptyMessage(TRACK_WENT_TO_NEXT);
|
||||
|
@ -981,16 +1011,11 @@ public class MusicService extends MediaBrowserServiceCompat
|
|||
public void pause() {
|
||||
pausedByTransientLossOfFocus = false;
|
||||
if (playback != null && playback.isPlaying()) {
|
||||
if (fader != null) {
|
||||
fader.stop();
|
||||
}
|
||||
fader = new AudioFader(playback, PreferenceUtil.INSTANCE.getAudioFadeDuration(), false, () -> {
|
||||
if (playback != null && playback.isPlaying()) {
|
||||
playback.pause();
|
||||
notifyChange(PLAY_STATE_CHANGED);
|
||||
}
|
||||
startFadeAnimator(playback, false, () -> {
|
||||
//Code to run when Animator Ends
|
||||
playback.pause();
|
||||
notifyChange(PLAY_STATE_CHANGED);
|
||||
});
|
||||
fader.start();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1013,10 +1038,8 @@ public class MusicService extends MediaBrowserServiceCompat
|
|||
if (MusicPlayerRemote.INSTANCE.isCasting()) {
|
||||
return;
|
||||
}
|
||||
if (fader != null) {
|
||||
fader.stop();
|
||||
}
|
||||
fader = new AudioFader(playback, PreferenceUtil.INSTANCE.getAudioFadeDuration(), false, () -> {
|
||||
startFadeAnimator(playback, true, () -> {
|
||||
// Code when Animator Ends
|
||||
if (!becomingNoisyReceiverRegistered) {
|
||||
registerReceiver(becomingNoisyReceiver, becomingNoisyReceiverIntentFilter);
|
||||
becomingNoisyReceiverRegistered = true;
|
||||
|
@ -1031,9 +1054,9 @@ public class MusicService extends MediaBrowserServiceCompat
|
|||
playerHandler.removeMessages(DUCK);
|
||||
playerHandler.sendEmptyMessage(UNDUCK);
|
||||
});
|
||||
//Start Playback with Animator
|
||||
playback.start();
|
||||
notifyChange(PLAY_STATE_CHANGED);
|
||||
fader.start();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -51,5 +51,7 @@ interface Playback {
|
|||
fun onTrackWentToNext()
|
||||
|
||||
fun onTrackEnded()
|
||||
|
||||
fun onTrackEndedWithCrossfade()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,13 +28,22 @@ import java.io.ByteArrayOutputStream;
|
|||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import code.name.monkey.retromusic.adapter.Storage;
|
||||
import code.name.monkey.retromusic.model.Song;
|
||||
import code.name.monkey.retromusic.repository.RealSongRepository;
|
||||
import code.name.monkey.retromusic.repository.SortedCursor;
|
||||
|
||||
import code.name.monkey.retromusic.model.Song;
|
||||
import code.name.monkey.retromusic.repository.RealSongRepository;
|
||||
|
@ -258,4 +267,76 @@ public final class FileUtil {
|
|||
return file.getAbsoluteFile();
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/DrKLO/Telegram/blob/ab221dafadbc17459d78d9ea3e643ae18e934b16/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertDocumentLayout.java#L939
|
||||
public static ArrayList<Storage> listRoots() {
|
||||
ArrayList<Storage> storageItems = new ArrayList<>();
|
||||
HashSet<String> paths = new HashSet<>();
|
||||
String defaultPath = Environment.getExternalStorageDirectory().getPath();
|
||||
String defaultPathState = Environment.getExternalStorageState();
|
||||
if (defaultPathState.equals(Environment.MEDIA_MOUNTED) || defaultPathState.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) {
|
||||
Storage ext = new Storage();
|
||||
if (Environment.isExternalStorageRemovable()) {
|
||||
ext.title = "SD Card";
|
||||
} else {
|
||||
ext.title = "Internal Storage";
|
||||
}
|
||||
ext.file = Environment.getExternalStorageDirectory();
|
||||
storageItems.add(ext);
|
||||
paths.add(defaultPath);
|
||||
}
|
||||
|
||||
BufferedReader bufferedReader = null;
|
||||
try {
|
||||
bufferedReader = new BufferedReader(new FileReader("/proc/mounts"));
|
||||
String line;
|
||||
while ((line = bufferedReader.readLine()) != null) {
|
||||
if (line.contains("vfat") || line.contains("/mnt")) {
|
||||
StringTokenizer tokens = new StringTokenizer(line, " ");
|
||||
tokens.nextToken();
|
||||
String path = tokens.nextToken();
|
||||
if (paths.contains(path)) {
|
||||
continue;
|
||||
}
|
||||
if (line.contains("/dev/block/vold")) {
|
||||
if (!line.contains("/mnt/secure") && !line.contains("/mnt/asec") && !line.contains("/mnt/obb") && !line.contains("/dev/mapper") && !line.contains("tmpfs")) {
|
||||
if (!new File(path).isDirectory()) {
|
||||
int index = path.lastIndexOf('/');
|
||||
if (index != -1) {
|
||||
String newPath = "/storage/" + path.substring(index + 1);
|
||||
if (new File(newPath).isDirectory()) {
|
||||
path = newPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
paths.add(path);
|
||||
try {
|
||||
Storage item = new Storage();
|
||||
if (path.toLowerCase().contains("sd")) {
|
||||
item.title = "SD Card";
|
||||
} else {
|
||||
item.title = "External Storage";
|
||||
}
|
||||
item.file = new File(path);
|
||||
storageItems.add(item);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (bufferedReader != null) {
|
||||
try {
|
||||
bufferedReader.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
return storageItems;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -269,6 +269,8 @@ object PreferenceUtil {
|
|||
BLURRED_ALBUM_ART, false
|
||||
)
|
||||
|
||||
val blurAmount get() = sharedPreferences.getInt(NEW_BLUR_AMOUNT, 25)
|
||||
|
||||
val isCarouselEffect
|
||||
get() = sharedPreferences.getBoolean(
|
||||
CAROUSEL_EFFECT, false
|
||||
|
@ -608,8 +610,7 @@ object PreferenceUtil {
|
|||
val isWhiteList: Boolean
|
||||
get() = sharedPreferences.getBoolean(WHITELIST_MUSIC, false)
|
||||
|
||||
var crossFadeDuration
|
||||
val crossFadeDuration
|
||||
get() = sharedPreferences
|
||||
.getInt(CROSS_FADE_DURATION, 0)
|
||||
set(value) = sharedPreferences.edit { putInt(CROSS_FADE_DURATION, value) }
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
android:defaultValue="0"
|
||||
android:key="cross_fade_duration"
|
||||
android:layout="@layout/list_item_view_seekbar"
|
||||
app:ateKey_pref_unit="s"
|
||||
android:max="12"
|
||||
android:summary="@string/pref_summary_cross_fade"
|
||||
android:title="@string/pref_title_cross_fade"
|
||||
|
|
|
@ -86,6 +86,7 @@
|
|||
android:summary="@string/pref_blur_amount_summary"
|
||||
android:title="@string/pref_blur_amount_title"
|
||||
app:icon="@drawable/ic_blur_on"
|
||||
app:ateKey_pref_unit="px"
|
||||
app:showSeekBarValue="true" />
|
||||
</code.name.monkey.appthemehelper.common.prefs.supportv7.ATEPreferenceCategory>
|
||||
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
package code.name.monkey.appthemehelper.common.prefs.supportv7
|
||||
|
||||
import android.content.Context
|
||||
import android.text.Editable
|
||||
import android.util.AttributeSet
|
||||
import android.widget.SeekBar
|
||||
import android.widget.TextView
|
||||
import androidx.core.graphics.BlendModeColorFilterCompat
|
||||
import androidx.core.graphics.BlendModeCompat
|
||||
import androidx.core.widget.doAfterTextChanged
|
||||
import androidx.preference.PreferenceViewHolder
|
||||
import androidx.preference.SeekBarPreference
|
||||
import code.name.monkey.appthemehelper.R
|
||||
|
@ -19,7 +22,16 @@ class ATESeekBarPreference @JvmOverloads constructor(
|
|||
defStyleRes: Int = -1
|
||||
) : SeekBarPreference(context, attrs, defStyleAttr, defStyleRes) {
|
||||
|
||||
var unit: String = ""
|
||||
|
||||
init {
|
||||
val attributes =
|
||||
context.obtainStyledAttributes(attrs, R.styleable.ATESeekBarPreference, 0, 0)
|
||||
|
||||
attributes.getString(R.styleable.ATESeekBarPreference_ateKey_pref_unit)?.let {
|
||||
unit = it
|
||||
}
|
||||
attributes.recycle()
|
||||
icon?.colorFilter = BlendModeColorFilterCompat.createBlendModeColorFilterCompat(
|
||||
ATHUtil.resolveColor(
|
||||
context,
|
||||
|
@ -32,5 +44,17 @@ class ATESeekBarPreference @JvmOverloads constructor(
|
|||
super.onBindViewHolder(view)
|
||||
val seekBar = view.findViewById(R.id.seekbar) as SeekBar
|
||||
TintHelper.setTintAuto(seekBar, ThemeStore.accentColor(context), false)
|
||||
(view.findViewById(R.id.seekbar_value) as TextView).apply {
|
||||
appendUnit(editableText)
|
||||
doAfterTextChanged {
|
||||
appendUnit(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun TextView.appendUnit(editable: Editable?) {
|
||||
if (!editable.toString().endsWith(unit)) {
|
||||
append(unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
4
appthemehelper/src/main/res/values/attrs.xml
Executable file → Normal file
4
appthemehelper/src/main/res/values/attrs.xml
Executable file → Normal file
|
@ -16,6 +16,10 @@
|
|||
<attr name="ateKey_pref_color" format="string" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="ATESeekBarPreference">
|
||||
<attr name="ateKey_pref_unit" format="string" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="ATEDialogPreference">
|
||||
<attr name="ateKey_pref_dialog" format="string" />
|
||||
</declare-styleable>
|
||||
|
|
Loading…
Reference in a new issue