Fixed Audio Fade, Crossfade & Android Auto search

main
Prathamesh More 2021-09-18 14:10:22 +05:30
parent 7f683eb0ee
commit ee7545f64e
15 changed files with 269 additions and 79 deletions

View File

@ -6,7 +6,6 @@ apply plugin: 'kotlin-parcelize'
android { android {
compileSdkVersion 31 compileSdkVersion 31
buildToolsVersion = '29.0.3'
defaultConfig { defaultConfig {
minSdkVersion 21 minSdkVersion 21
@ -33,6 +32,8 @@ android {
buildTypes { buildTypes {
release { release {
//debuggable true //debuggable true
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release signingConfig signingConfigs.release
} }
debug { debug {
@ -104,7 +105,7 @@ dependencies {
//WebServer by NanoHttpd //WebServer by NanoHttpd
implementation "org.nanohttpd:nanohttpd:2.3.1" 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-runtime-ktx:$nav_version"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-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.heinrichreimersoftware:material-intro:2.0.0'
implementation 'com.github.dhaval2404:imagepicker:1.7.1' implementation 'com.github.dhaval2404:imagepicker:1.7.1'
implementation 'me.zhanghai.android.fastscroll:library:1.1.7' 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' debugImplementation 'com.amitshekhar.android:debug-db:1.0.6'
} }

View File

@ -265,7 +265,7 @@
android:value="true" /> android:value="true" />
<!-- Android Auto --> <!-- Android Auto -->
<!-- <meta-data <meta-data
android:name="com.google.android.gms.car.application" android:name="com.google.android.gms.car.application"
android:resource="@xml/automotive_app_desc"/> android:resource="@xml/automotive_app_desc"/>
<meta-data <meta-data
@ -274,7 +274,7 @@
<meta-data <meta-data
android:name="com.google.android.gms.car.notification.SmallIcon" android:name="com.google.android.gms.car.notification.SmallIcon"
android:resource="@drawable/ic_notification"/> android:resource="@drawable/ic_notification"/>
-->
<!-- ChromeCast --> <!-- ChromeCast -->
<meta-data <meta-data
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME" android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"

View File

@ -32,6 +32,7 @@ import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.helper.MusicPlayerRemote 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.PreferenceUtil.blurAmount
import code.name.monkey.retromusic.util.RetroUtil import code.name.monkey.retromusic.util.RetroUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
@ -110,8 +111,6 @@ class BlurPlayerFragment : AbsPlayerFragment(R.layout.fragment_blur),
get() = lastColor get() = lastColor
private fun updateBlur() { private fun updateBlur() {
val blurAmount = PreferenceManager.getDefaultSharedPreferences(requireContext())
.getInt(NEW_BLUR_AMOUNT, 25)
binding.colorBackground.clearColorFilter() binding.colorBackground.clearColorFilter()
GlideApp.with(requireActivity()).asBitmapPalette() GlideApp.with(requireActivity()).asBitmapPalette()
.songCoverOptions(MusicPlayerRemote.currentSong) .songCoverOptions(MusicPlayerRemote.currentSong)

View File

@ -33,6 +33,7 @@ import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.helper.MusicPlayerRemote 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.PreferenceUtil.blurAmount
import code.name.monkey.retromusic.util.RetroUtil import code.name.monkey.retromusic.util.RetroUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import org.koin.android.ext.android.bind import org.koin.android.ext.android.bind
@ -136,8 +137,6 @@ class CardBlurFragment : AbsPlayerFragment(R.layout.fragment_card_blur_player),
} }
private fun updateBlur() { private fun updateBlur() {
val blurAmount = PreferenceManager.getDefaultSharedPreferences(requireContext())
.getInt(NEW_BLUR_AMOUNT, 25)
binding.colorBackground.clearColorFilter() binding.colorBackground.clearColorFilter()
GlideApp.with(requireActivity()).asBitmapPalette() GlideApp.with(requireActivity()).asBitmapPalette()
.songCoverOptions(MusicPlayerRemote.currentSong) .songCoverOptions(MusicPlayerRemote.currentSong)

View File

@ -25,6 +25,7 @@ import android.os.IBinder
import android.provider.DocumentsContract import android.provider.DocumentsContract
import android.widget.Toast import android.widget.Toast
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.repository.SongRepository import code.name.monkey.retromusic.repository.SongRepository
import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.service.MusicService
@ -300,7 +301,7 @@ object MusicPlayerRemote : KoinComponent {
return false return false
} }
fun setShuffleMode(shuffleMode: Int): Boolean { private fun setShuffleMode(shuffleMode: Int): Boolean {
if (musicService != null) { if (musicService != null) {
musicService!!.shuffleMode = shuffleMode musicService!!.shuffleMode = shuffleMode
return true return true
@ -319,7 +320,7 @@ object MusicPlayerRemote : KoinComponent {
} }
Toast.makeText( Toast.makeText(
musicService, 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 Toast.LENGTH_SHORT
).show() ).show()
return true return true
@ -335,8 +336,8 @@ object MusicPlayerRemote : KoinComponent {
openQueue(songs, 0, false) openQueue(songs, 0, false)
} }
val toast = val toast =
if (songs.size == 1) musicService!!.resources.getString(code.name.monkey.retromusic.R.string.added_title_to_playing_queue) else musicService!!.resources.getString( if (songs.size == 1) musicService!!.resources.getString(R.string.added_title_to_playing_queue) else musicService!!.resources.getString(
code.name.monkey.retromusic.R.string.added_x_titles_to_playing_queue, R.string.added_x_titles_to_playing_queue,
songs.size songs.size
) )
Toast.makeText(musicService, toast, Toast.LENGTH_SHORT).show() Toast.makeText(musicService, toast, Toast.LENGTH_SHORT).show()
@ -356,7 +357,7 @@ object MusicPlayerRemote : KoinComponent {
} }
Toast.makeText( Toast.makeText(
musicService, 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 Toast.LENGTH_SHORT
).show() ).show()
return true return true
@ -372,8 +373,8 @@ object MusicPlayerRemote : KoinComponent {
openQueue(songs, 0, false) openQueue(songs, 0, false)
} }
val toast = val toast =
if (songs.size == 1) musicService!!.resources.getString(code.name.monkey.retromusic.R.string.added_title_to_playing_queue) else musicService!!.resources.getString( if (songs.size == 1) musicService!!.resources.getString(R.string.added_title_to_playing_queue) else musicService!!.resources.getString(
code.name.monkey.retromusic.R.string.added_x_titles_to_playing_queue, R.string.added_x_titles_to_playing_queue,
songs.size songs.size
) )
Toast.makeText(musicService, toast, Toast.LENGTH_SHORT).show() Toast.makeText(musicService, toast, Toast.LENGTH_SHORT).show()

View File

@ -1,50 +1,66 @@
package code.name.monkey.retromusic.service 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 code.name.monkey.retromusic.service.playback.Playback
import java.util.* import code.name.monkey.retromusic.util.PreferenceUtil
class AudioFader( class AudioFader {
private val player: Playback, companion object {
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()
fun start() { @JvmStatic
timer.scheduleAtFixedRate( inline fun createFadeAnimator(
object : TimerTask() { fadeIn: Boolean /* fadeIn -> true fadeOut -> false*/,
override fun run() { mediaPlayer: MediaPlayer,
setVolume() crossinline endAction: (animator: Animator) -> Unit /* Code to run when Animator Ends*/
if (volume < 0 || volume > 1) { ): Animator? {
player.setVolume(maxVolume) val duration = PreferenceUtil.crossFadeDuration * 1000
stop() if (duration == 0) {
doOnEnd.run() return null
} else { }
player.setVolume(volume) 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() { @JvmStatic
timer.purge() fun startFadeAnimator(
timer.cancel() playback: Playback,
} fadeIn: Boolean /* fadeIn -> true fadeOut -> false*/,
callback: Runnable /* Code to run when Animator Ends*/
private fun setVolume() { ) {
if (fadeIn) { val duration = PreferenceUtil.audioFadeDuration.toLong()
volume += volumeStep if (duration == 0L) {
} else { callback.run()
volume -= volumeStep 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
}
} }

View File

@ -17,6 +17,7 @@ package code.name.monkey.retromusic.service
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.provider.MediaStore
import android.support.v4.media.session.MediaSessionCompat import android.support.v4.media.session.MediaSessionCompat
import code.name.monkey.retromusic.auto.AutoMediaIDHelper import code.name.monkey.retromusic.auto.AutoMediaIDHelper
import code.name.monkey.retromusic.helper.MusicPlayerRemote 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 code.name.monkey.retromusic.util.MusicUtil
import org.koin.core.KoinComponent import org.koin.core.KoinComponent
import org.koin.core.inject import org.koin.core.inject
import java.util.*
/** /**
@ -56,8 +56,7 @@ class MediaSessionCallback(
println(musicId) println(musicId)
val itemId = musicId?.toLong() ?: -1 val itemId = musicId?.toLong() ?: -1
val songs: ArrayList<Song> = ArrayList() val songs: ArrayList<Song> = ArrayList()
val category = AutoMediaIDHelper.extractCategory(mediaId) when (val category = AutoMediaIDHelper.extractCategory(mediaId)) {
when (category) {
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ALBUM -> { AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ALBUM -> {
val album: Album = albumRepository.album(itemId) val album: Album = albumRepository.album(itemId)
songs.addAll(album.songs) songs.addAll(album.songs)
@ -111,6 +110,43 @@ class MediaSessionCallback(
musicService.play() 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() { override fun onPlay() {
super.onPlay() super.onPlay()

View File

@ -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.COLORED_NOTIFICATION;
import static code.name.monkey.retromusic.ConstantsKt.CROSS_FADE_DURATION; 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.ConstantsKt.TOGGLE_HEADSET;
import static code.name.monkey.retromusic.service.AudioFader.startFadeAnimator;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.appwidget.AppWidgetManager; 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.PackageValidator;
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 code.name.monkey.retromusic.volume.AudioVolumeObserver;
import code.name.monkey.retromusic.volume.OnAudioVolumeChangedListener;
/** /**
* @author Karim Abou Zeid (kabouzeid), Andrew Neal * @author Karim Abou Zeid (kabouzeid), Andrew Neal
*/ */
public class MusicService extends MediaBrowserServiceCompat 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 TAG = MusicService.class.getSimpleName();
public static final String RETRO_MUSIC_PACKAGE_NAME = "code.name.monkey.retromusic"; 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> originalPlayingQueue = new ArrayList<>();
private List<Song> playingQueue = new ArrayList<>(); private List<Song> playingQueue = new ArrayList<>();
private boolean pausedByTransientLossOfFocus; private boolean pausedByTransientLossOfFocus;
private AudioVolumeObserver audioVolumeObserver = null;
private final BroadcastReceiver becomingNoisyReceiver = private final BroadcastReceiver becomingNoisyReceiver =
new BroadcastReceiver() { new BroadcastReceiver() {
@ -348,7 +352,6 @@ public class MusicService extends MediaBrowserServiceCompat
private ThrottledSeekHandler throttledSeekHandler; private ThrottledSeekHandler throttledSeekHandler;
private Handler uiThreadHandler; private Handler uiThreadHandler;
private PowerManager.WakeLock wakeLock; private PowerManager.WakeLock wakeLock;
private AudioFader fader;
private static Bitmap copy(Bitmap bitmap) { private static Bitmap copy(Bitmap bitmap) {
Bitmap.Config config = bitmap.getConfig(); Bitmap.Config config = bitmap.getConfig();
@ -446,6 +449,9 @@ public class MusicService extends MediaBrowserServiceCompat
.registerContentObserver( .registerContentObserver(
MediaStore.Audio.Playlists.INTERNAL_CONTENT_URI, true, mediaStoreObserver); MediaStore.Audio.Playlists.INTERNAL_CONTENT_URI, true, mediaStoreObserver);
audioVolumeObserver = new AudioVolumeObserver(this);
audioVolumeObserver.register(AudioManager.STREAM_MUSIC, this);
PreferenceUtil.INSTANCE.registerOnSharedPreferenceChangedListener(this); PreferenceUtil.INSTANCE.registerOnSharedPreferenceChangedListener(this);
restoreState(); restoreState();
@ -491,6 +497,23 @@ public class MusicService extends MediaBrowserServiceCompat
wakeLock.acquire(milli); 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) { public void addSong(int position, Song song) {
playingQueue.add(position, song); playingQueue.add(position, song);
originalPlayingQueue.add(position, song); originalPlayingQueue.add(position, song);
@ -558,10 +581,10 @@ public class MusicService extends MediaBrowserServiceCompat
} }
public Song getNextSong() { public Song getNextSong() {
if (!isLastTrack() || getRepeatMode() == REPEAT_MODE_THIS) { if (isLastTrack() && getRepeatMode() == REPEAT_MODE_NONE) {
return getSongAt(getNextPosition(false));
} else {
return null; return null;
} else {
return getSongAt(getNextPosition(false));
} }
} }
@ -926,6 +949,13 @@ public class MusicService extends MediaBrowserServiceCompat
playerHandler.sendEmptyMessage(TRACK_ENDED); playerHandler.sendEmptyMessage(TRACK_ENDED);
} }
@Override
public void onTrackEndedWithCrossfade() {
trackEndedByCrossfade = true;
acquireWakeLock(30000);
playerHandler.sendEmptyMessage(TRACK_ENDED);
}
@Override @Override
public void onTrackWentToNext() { public void onTrackWentToNext() {
playerHandler.sendEmptyMessage(TRACK_WENT_TO_NEXT); playerHandler.sendEmptyMessage(TRACK_WENT_TO_NEXT);
@ -981,16 +1011,11 @@ public class MusicService extends MediaBrowserServiceCompat
public void pause() { public void pause() {
pausedByTransientLossOfFocus = false; pausedByTransientLossOfFocus = false;
if (playback != null && playback.isPlaying()) { if (playback != null && playback.isPlaying()) {
if (fader != null) { startFadeAnimator(playback, false, () -> {
fader.stop(); //Code to run when Animator Ends
} playback.pause();
fader = new AudioFader(playback, PreferenceUtil.INSTANCE.getAudioFadeDuration(), false, () -> { notifyChange(PLAY_STATE_CHANGED);
if (playback != null && playback.isPlaying()) {
playback.pause();
notifyChange(PLAY_STATE_CHANGED);
}
}); });
fader.start();
} }
} }
@ -1013,10 +1038,8 @@ public class MusicService extends MediaBrowserServiceCompat
if (MusicPlayerRemote.INSTANCE.isCasting()) { if (MusicPlayerRemote.INSTANCE.isCasting()) {
return; return;
} }
if (fader != null) { startFadeAnimator(playback, true, () -> {
fader.stop(); // Code when Animator Ends
}
fader = new AudioFader(playback, PreferenceUtil.INSTANCE.getAudioFadeDuration(), false, () -> {
if (!becomingNoisyReceiverRegistered) { if (!becomingNoisyReceiverRegistered) {
registerReceiver(becomingNoisyReceiver, becomingNoisyReceiverIntentFilter); registerReceiver(becomingNoisyReceiver, becomingNoisyReceiverIntentFilter);
becomingNoisyReceiverRegistered = true; becomingNoisyReceiverRegistered = true;
@ -1031,9 +1054,9 @@ public class MusicService extends MediaBrowserServiceCompat
playerHandler.removeMessages(DUCK); playerHandler.removeMessages(DUCK);
playerHandler.sendEmptyMessage(UNDUCK); playerHandler.sendEmptyMessage(UNDUCK);
}); });
//Start Playback with Animator
playback.start(); playback.start();
notifyChange(PLAY_STATE_CHANGED); notifyChange(PLAY_STATE_CHANGED);
fader.start();
} }
} }
} else { } else {

View File

@ -51,5 +51,7 @@ interface Playback {
fun onTrackWentToNext() fun onTrackWentToNext()
fun onTrackEnded() fun onTrackEnded()
fun onTrackEndedWithCrossfade()
} }
} }

View File

@ -28,13 +28,22 @@ import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileFilter; import java.io.FileFilter;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; 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.model.Song;
import code.name.monkey.retromusic.repository.RealSongRepository; import code.name.monkey.retromusic.repository.RealSongRepository;
@ -258,4 +267,76 @@ public final class FileUtil {
return file.getAbsoluteFile(); 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;
}
} }

View File

@ -269,6 +269,8 @@ object PreferenceUtil {
BLURRED_ALBUM_ART, false BLURRED_ALBUM_ART, false
) )
val blurAmount get() = sharedPreferences.getInt(NEW_BLUR_AMOUNT, 25)
val isCarouselEffect val isCarouselEffect
get() = sharedPreferences.getBoolean( get() = sharedPreferences.getBoolean(
CAROUSEL_EFFECT, false CAROUSEL_EFFECT, false
@ -608,8 +610,7 @@ object PreferenceUtil {
val isWhiteList: Boolean val isWhiteList: Boolean
get() = sharedPreferences.getBoolean(WHITELIST_MUSIC, false) get() = sharedPreferences.getBoolean(WHITELIST_MUSIC, false)
var crossFadeDuration val crossFadeDuration
get() = sharedPreferences get() = sharedPreferences
.getInt(CROSS_FADE_DURATION, 0) .getInt(CROSS_FADE_DURATION, 0)
set(value) = sharedPreferences.edit { putInt(CROSS_FADE_DURATION, value) }
} }

View File

@ -21,6 +21,7 @@
android:defaultValue="0" android:defaultValue="0"
android:key="cross_fade_duration" android:key="cross_fade_duration"
android:layout="@layout/list_item_view_seekbar" android:layout="@layout/list_item_view_seekbar"
app:ateKey_pref_unit="s"
android:max="12" android:max="12"
android:summary="@string/pref_summary_cross_fade" android:summary="@string/pref_summary_cross_fade"
android:title="@string/pref_title_cross_fade" android:title="@string/pref_title_cross_fade"

View File

@ -86,6 +86,7 @@
android:summary="@string/pref_blur_amount_summary" android:summary="@string/pref_blur_amount_summary"
android:title="@string/pref_blur_amount_title" android:title="@string/pref_blur_amount_title"
app:icon="@drawable/ic_blur_on" app:icon="@drawable/ic_blur_on"
app:ateKey_pref_unit="px"
app:showSeekBarValue="true" /> app:showSeekBarValue="true" />
</code.name.monkey.appthemehelper.common.prefs.supportv7.ATEPreferenceCategory> </code.name.monkey.appthemehelper.common.prefs.supportv7.ATEPreferenceCategory>

View File

@ -1,10 +1,13 @@
package code.name.monkey.appthemehelper.common.prefs.supportv7 package code.name.monkey.appthemehelper.common.prefs.supportv7
import android.content.Context import android.content.Context
import android.text.Editable
import android.util.AttributeSet import android.util.AttributeSet
import android.widget.SeekBar import android.widget.SeekBar
import android.widget.TextView
import androidx.core.graphics.BlendModeColorFilterCompat import androidx.core.graphics.BlendModeColorFilterCompat
import androidx.core.graphics.BlendModeCompat import androidx.core.graphics.BlendModeCompat
import androidx.core.widget.doAfterTextChanged
import androidx.preference.PreferenceViewHolder import androidx.preference.PreferenceViewHolder
import androidx.preference.SeekBarPreference import androidx.preference.SeekBarPreference
import code.name.monkey.appthemehelper.R import code.name.monkey.appthemehelper.R
@ -19,7 +22,16 @@ class ATESeekBarPreference @JvmOverloads constructor(
defStyleRes: Int = -1 defStyleRes: Int = -1
) : SeekBarPreference(context, attrs, defStyleAttr, defStyleRes) { ) : SeekBarPreference(context, attrs, defStyleAttr, defStyleRes) {
var unit: String = ""
init { 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( icon?.colorFilter = BlendModeColorFilterCompat.createBlendModeColorFilterCompat(
ATHUtil.resolveColor( ATHUtil.resolveColor(
context, context,
@ -32,5 +44,17 @@ class ATESeekBarPreference @JvmOverloads constructor(
super.onBindViewHolder(view) super.onBindViewHolder(view)
val seekBar = view.findViewById(R.id.seekbar) as SeekBar val seekBar = view.findViewById(R.id.seekbar) as SeekBar
TintHelper.setTintAuto(seekBar, ThemeStore.accentColor(context), false) 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
View File

@ -16,6 +16,10 @@
<attr name="ateKey_pref_color" format="string" /> <attr name="ateKey_pref_color" format="string" />
</declare-styleable> </declare-styleable>
<declare-styleable name="ATESeekBarPreference">
<attr name="ateKey_pref_unit" format="string" />
</declare-styleable>
<declare-styleable name="ATEDialogPreference"> <declare-styleable name="ATEDialogPreference">
<attr name="ateKey_pref_dialog" format="string" /> <attr name="ateKey_pref_dialog" format="string" />
</declare-styleable> </declare-styleable>