diff --git a/app/build.gradle b/app/build.gradle index 462a02d1..c1eb4656 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -166,4 +166,6 @@ dependencies { implementation 'me.jorgecastillo:androidcolorx:0.2.0' implementation 'org.jsoup:jsoup:1.11.1' debugImplementation 'com.amitshekhar.android:debug-db:1.0.6' -} \ No newline at end of file +} + +apply from: '../spotless.gradle' \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/App.kt b/app/src/main/java/code/name/monkey/retromusic/App.kt index de916793..4de597d3 100644 --- a/app/src/main/java/code/name/monkey/retromusic/App.kt +++ b/app/src/main/java/code/name/monkey/retromusic/App.kt @@ -1,17 +1,17 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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 import android.widget.Toast diff --git a/app/src/main/java/code/name/monkey/retromusic/Constants.kt b/app/src/main/java/code/name/monkey/retromusic/Constants.kt index 55023db2..452b2fa3 100644 --- a/app/src/main/java/code/name/monkey/retromusic/Constants.kt +++ b/app/src/main/java/code/name/monkey/retromusic/Constants.kt @@ -1,17 +1,17 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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 import android.provider.BaseColumns @@ -47,9 +47,9 @@ object Constants { MediaStore.Audio.AudioColumns.ALBUM_ID, // 7 MediaStore.Audio.AudioColumns.ALBUM, // 8 MediaStore.Audio.AudioColumns.ARTIST_ID, // 9 - MediaStore.Audio.AudioColumns.ARTIST,// 10 - MediaStore.Audio.AudioColumns.COMPOSER,// 11 - "album_artist"//12 + MediaStore.Audio.AudioColumns.ARTIST, // 10 + MediaStore.Audio.AudioColumns.COMPOSER, // 11 + "album_artist" // 12 ) const val NUMBER_OF_TOP_TRACKS = 99 } @@ -135,4 +135,4 @@ const val TOGGLE_SHUFFLE = "toggle_shuffle" const val SONG_GRID_STYLE = "song_grid_style" const val PAUSE_ON_ZERO_VOLUME = "pause_on_zero_volume" const val FILTER_SONG = "filter_song" -const val EXPAND_NOW_PLAYING_PANEL = "expand_now_playing_panel" \ No newline at end of file +const val EXPAND_NOW_PLAYING_PANEL = "expand_now_playing_panel" diff --git a/app/src/main/java/code/name/monkey/retromusic/HomeSection.kt b/app/src/main/java/code/name/monkey/retromusic/HomeSection.kt index f5312054..4f1273a0 100644 --- a/app/src/main/java/code/name/monkey/retromusic/HomeSection.kt +++ b/app/src/main/java/code/name/monkey/retromusic/HomeSection.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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 import androidx.annotation.IntDef @@ -25,4 +39,4 @@ const val GENRES = 6 const val PLAYLISTS = 7 const val HISTORY_PLAYLIST = 8 const val LAST_ADDED_PLAYLIST = 9 -const val TOP_PLAYED_PLAYLIST = 10 \ No newline at end of file +const val TOP_PLAYED_PLAYLIST = 10 diff --git a/app/src/main/java/code/name/monkey/retromusic/LanguageContextWrapper.java b/app/src/main/java/code/name/monkey/retromusic/LanguageContextWrapper.java index 36f7f565..cbd9204e 100644 --- a/app/src/main/java/code/name/monkey/retromusic/LanguageContextWrapper.java +++ b/app/src/main/java/code/name/monkey/retromusic/LanguageContextWrapper.java @@ -5,39 +5,37 @@ import android.content.ContextWrapper; import android.content.res.Configuration; import android.content.res.Resources; import android.os.LocaleList; - -import java.util.Locale; - import code.name.monkey.appthemehelper.util.VersionUtils; +import java.util.Locale; public class LanguageContextWrapper extends ContextWrapper { - public LanguageContextWrapper(Context base) { - super(base); + public LanguageContextWrapper(Context base) { + super(base); + } + + public static LanguageContextWrapper wrap(Context context, Locale newLocale) { + Resources res = context.getResources(); + Configuration configuration = res.getConfiguration(); + + if (VersionUtils.INSTANCE.hasNougatMR()) { + configuration.setLocale(newLocale); + + LocaleList localeList = new LocaleList(newLocale); + LocaleList.setDefault(localeList); + configuration.setLocales(localeList); + + context = context.createConfigurationContext(configuration); + + } else if (VersionUtils.INSTANCE.hasLollipop()) { + configuration.setLocale(newLocale); + context = context.createConfigurationContext(configuration); + + } else { + configuration.locale = newLocale; + res.updateConfiguration(configuration, res.getDisplayMetrics()); } - public static LanguageContextWrapper wrap(Context context, Locale newLocale) { - Resources res = context.getResources(); - Configuration configuration = res.getConfiguration(); - - if (VersionUtils.INSTANCE.hasNougatMR()) { - configuration.setLocale(newLocale); - - LocaleList localeList = new LocaleList(newLocale); - LocaleList.setDefault(localeList); - configuration.setLocales(localeList); - - context = context.createConfigurationContext(configuration); - - } else if (VersionUtils.INSTANCE.hasLollipop()) { - configuration.setLocale(newLocale); - context = context.createConfigurationContext(configuration); - - } else { - configuration.locale = newLocale; - res.updateConfiguration(configuration, res.getDisplayMetrics()); - } - - return new LanguageContextWrapper(context); - } -} \ No newline at end of file + return new LanguageContextWrapper(context); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/RetroBottomSheetBehavior.java b/app/src/main/java/code/name/monkey/retromusic/RetroBottomSheetBehavior.java index abff707a..345cda93 100644 --- a/app/src/main/java/code/name/monkey/retromusic/RetroBottomSheetBehavior.java +++ b/app/src/main/java/code/name/monkey/retromusic/RetroBottomSheetBehavior.java @@ -4,36 +4,32 @@ import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; - import androidx.coordinatorlayout.widget.CoordinatorLayout; - import com.google.android.material.bottomsheet.BottomSheetBehavior; - import org.jetbrains.annotations.NotNull; - public class RetroBottomSheetBehavior extends BottomSheetBehavior { - private static final String TAG = "RetroBottomSheetBehavior"; + private static final String TAG = "RetroBottomSheetBehavior"; - private boolean allowDragging = true; + private boolean allowDragging = true; - public RetroBottomSheetBehavior() { + public RetroBottomSheetBehavior() {} + + public RetroBottomSheetBehavior(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public void setAllowDragging(boolean allowDragging) { + this.allowDragging = allowDragging; + } + + @Override + public boolean onInterceptTouchEvent( + @NotNull CoordinatorLayout parent, @NotNull V child, @NotNull MotionEvent event) { + if (!allowDragging) { + return false; } - - public RetroBottomSheetBehavior(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public void setAllowDragging(boolean allowDragging) { - this.allowDragging = allowDragging; - } - - @Override - public boolean onInterceptTouchEvent(@NotNull CoordinatorLayout parent, @NotNull V child, @NotNull MotionEvent event) { - if (!allowDragging) { - return false; - } - return super.onInterceptTouchEvent(parent, child, event); - } -} \ No newline at end of file + return super.onInterceptTouchEvent(parent, child, event); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/DriveModeActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/DriveModeActivity.kt index 13b75956..3f5e7fbc 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/DriveModeActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/DriveModeActivity.kt @@ -1,17 +1,17 @@ /* - * Copyright (c) 2020 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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 import android.animation.ObjectAnimator @@ -234,4 +234,4 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback { songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) } -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/LicenseActivity.java b/app/src/main/java/code/name/monkey/retromusic/activities/LicenseActivity.java index eea9299c..e66b7f81 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/LicenseActivity.java +++ b/app/src/main/java/code/name/monkey/retromusic/activities/LicenseActivity.java @@ -18,82 +18,86 @@ import android.graphics.Color; import android.os.Bundle; import android.view.MenuItem; import android.webkit.WebView; - import androidx.annotation.NonNull; import androidx.appcompat.widget.Toolbar; - -import org.jetbrains.annotations.Nullable; - -import java.io.BufferedReader; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; - import code.name.monkey.appthemehelper.ThemeStore; import code.name.monkey.appthemehelper.util.ATHUtil; import code.name.monkey.appthemehelper.util.ColorUtil; import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper; import code.name.monkey.retromusic.R; import code.name.monkey.retromusic.activities.base.AbsBaseActivity; +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import org.jetbrains.annotations.Nullable; -/** - * Created by hemanths on 2019-09-27. - */ +/** Created by hemanths on 2019-09-27. */ public class LicenseActivity extends AbsBaseActivity { - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - setDrawUnderStatusBar(); - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_license); - setStatusbarColorAuto(); - setNavigationbarColorAuto(); - setLightNavigationBar(true); - Toolbar toolbar = findViewById(R.id.toolbar); - setSupportActionBar(toolbar); - ToolbarContentTintHelper.colorBackButton(toolbar); - toolbar.setBackgroundColor(ATHUtil.INSTANCE.resolveColor(this, R.attr.colorSurface)); - WebView webView = findViewById(R.id.license); - try { - StringBuilder buf = new StringBuilder(); - InputStream json = getAssets().open("oldindex.html"); - BufferedReader in = new BufferedReader(new InputStreamReader(json, StandardCharsets.UTF_8)); - String str; - while ((str = in.readLine()) != null) { - buf.append(str); - } - in.close(); + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + setDrawUnderStatusBar(); + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_license); + setStatusbarColorAuto(); + setNavigationbarColorAuto(); + setLightNavigationBar(true); + Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + ToolbarContentTintHelper.colorBackButton(toolbar); + toolbar.setBackgroundColor(ATHUtil.INSTANCE.resolveColor(this, R.attr.colorSurface)); + WebView webView = findViewById(R.id.license); + try { + StringBuilder buf = new StringBuilder(); + InputStream json = getAssets().open("oldindex.html"); + BufferedReader in = new BufferedReader(new InputStreamReader(json, StandardCharsets.UTF_8)); + String str; + while ((str = in.readLine()) != null) { + buf.append(str); + } + in.close(); - // Inject color values for WebView body background and links - final boolean isDark = ATHUtil.INSTANCE.isWindowBackgroundDark(this); - final String backgroundColor = colorToCSS(ATHUtil.INSTANCE.resolveColor(this, R.attr.colorSurface, - Color.parseColor(isDark ? "#424242" : "#ffffff"))); - final String contentColor = colorToCSS(Color.parseColor(isDark ? "#ffffff" : "#000000")); - final String changeLog = buf.toString() - .replace("{style-placeholder}", - String.format("body { background-color: %s; color: %s; }", backgroundColor, contentColor)) - .replace("{link-color}", colorToCSS(ThemeStore.Companion.accentColor(this))) - .replace("{link-color-active}", - colorToCSS(ColorUtil.INSTANCE.lightenColor(ThemeStore.Companion.accentColor(this)))); - - webView.loadData(changeLog, "text/html", "UTF-8"); - } catch (Throwable e) { - webView.loadData("

Unable to load

" + e.getLocalizedMessage() + "

", "text/html", "UTF-8"); - } + // Inject color values for WebView body background and links + final boolean isDark = ATHUtil.INSTANCE.isWindowBackgroundDark(this); + final String backgroundColor = + colorToCSS( + ATHUtil.INSTANCE.resolveColor( + this, R.attr.colorSurface, Color.parseColor(isDark ? "#424242" : "#ffffff"))); + final String contentColor = colorToCSS(Color.parseColor(isDark ? "#ffffff" : "#000000")); + final String changeLog = + buf.toString() + .replace( + "{style-placeholder}", + String.format( + "body { background-color: %s; color: %s; }", backgroundColor, contentColor)) + .replace("{link-color}", colorToCSS(ThemeStore.Companion.accentColor(this))) + .replace( + "{link-color-active}", + colorToCSS( + ColorUtil.INSTANCE.lightenColor(ThemeStore.Companion.accentColor(this)))); + webView.loadData(changeLog, "text/html", "UTF-8"); + } catch (Throwable e) { + webView.loadData( + "

Unable to load

" + e.getLocalizedMessage() + "

", "text/html", "UTF-8"); } + } - @Override - public boolean onOptionsItemSelected(@NonNull MenuItem item) { - if (item.getItemId() == android.R.id.home) { - onBackPressed(); - return true; - } - return super.onOptionsItemSelected(item); + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + if (item.getItemId() == android.R.id.home) { + onBackPressed(); + return true; } + return super.onOptionsItemSelected(item); + } - private String colorToCSS(int color) { - return String.format("rgb(%d, %d, %d)", Color.red(color), Color.green(color), - Color.blue(color)); // on API 29, WebView doesn't load with hex colors - } + private String colorToCSS(int color) { + return String.format( + "rgb(%d, %d, %d)", + Color.red(color), + Color.green(color), + Color.blue(color)); // on API 29, WebView doesn't load with hex colors + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/LockScreenActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/LockScreenActivity.kt index 9ff98437..ad3fa148 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/LockScreenActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/LockScreenActivity.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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 import android.app.KeyguardManager @@ -101,4 +115,4 @@ class LockScreenActivity : AbsMusicServiceActivity() { } }) } -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/LyricsActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/LyricsActivity.kt index a4086842..25300b92 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/LyricsActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/LyricsActivity.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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 import android.os.Bundle @@ -23,7 +37,6 @@ import com.google.android.material.transition.platform.MaterialContainerTransfor import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback import kotlinx.android.synthetic.main.activity_lyrics.* - class LyricsActivity : AbsMusicServiceActivity(), MusicProgressViewUpdateHelper.Callback { private lateinit var updateHelper: MusicProgressViewUpdateHelper @@ -38,7 +51,7 @@ class LyricsActivity : AbsMusicServiceActivity(), MusicProgressViewUpdateHelper. return baseUrl } - private fun buildContainerTransform( ): MaterialContainerTransform { + private fun buildContainerTransform(): MaterialContainerTransform { val transform = MaterialContainerTransform() transform.setAllContainerColors( MaterialColors.getColor(findViewById(android.R.id.content), R.attr.colorSurface) @@ -53,8 +66,8 @@ class LyricsActivity : AbsMusicServiceActivity(), MusicProgressViewUpdateHelper. override fun onCreate(savedInstanceState: Bundle?) { findViewById(android.R.id.content).transitionName = "lyrics" setEnterSharedElementCallback(MaterialContainerTransformSharedElementCallback()) - window.sharedElementEnterTransition = buildContainerTransform( ) - window.sharedElementReturnTransition = buildContainerTransform( ) + window.sharedElementEnterTransition = buildContainerTransform() + window.sharedElementReturnTransition = buildContainerTransform() super.onCreate(savedInstanceState) setContentView(R.layout.activity_lyrics) setStatusbarColorAuto() @@ -145,4 +158,4 @@ class LyricsActivity : AbsMusicServiceActivity(), MusicProgressViewUpdateHelper. } return super.onOptionsItemSelected(item) } -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/MainActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/MainActivity.kt index a9d6bdbb..eeb6426e 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/MainActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/MainActivity.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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 import android.content.Intent @@ -47,7 +61,7 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis AppRater.appLaunched(this) updateTabs() - //NavigationUI.setupWithNavController(getBottomNavigationView(), findNavController(R.id.fragment_container)) + // NavigationUI.setupWithNavController(getBottomNavigationView(), findNavController(R.id.fragment_container)) setupNavigationController() if (!hasPermissions()) { findNavController(R.id.fragment_container).navigate(R.id.permissionFragment) @@ -66,7 +80,7 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis navController.graph = navGraph NavigationUI.setupWithNavController(getBottomNavigationView(), navController) navController.addOnDestinationChangedListener { _, _, _ -> - //appBarLayout.setExpanded(true, true) + // appBarLayout.setExpanded(true, true) } } @@ -156,11 +170,11 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis setIntent(Intent()) } } - } private fun parseLongFromIntent( - intent: Intent, longKey: String, + intent: Intent, + longKey: String, stringKey: String ): Long { var id = intent.getLongExtra(longKey, -1) @@ -176,4 +190,4 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis } return id } -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/PermissionActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/PermissionActivity.kt index de64f8f0..543ca1c3 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/PermissionActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/PermissionActivity.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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 import android.content.Intent @@ -15,7 +29,6 @@ import code.name.monkey.retromusic.util.RingtoneManager import kotlinx.android.synthetic.main.activity_permission.* import kotlinx.android.synthetic.main.fragment_library.appNameText - class PermissionActivity : AbsMusicServiceActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -39,7 +52,7 @@ class PermissionActivity : AbsMusicServiceActivity() { } finish.accentBackgroundColor() finish.setOnClickListener { - if (hasPermissions() ) { + if (hasPermissions()) { startActivity( Intent(this, MainActivity::class.java).addFlags( Intent.FLAG_ACTIVITY_NEW_TASK or @@ -60,4 +73,4 @@ class PermissionActivity : AbsMusicServiceActivity() { ) appNameText.text = appName } -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/PlayingQueueActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/PlayingQueueActivity.kt index b02622f8..e8a24424 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/PlayingQueueActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/PlayingQueueActivity.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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 import android.content.res.ColorStateList @@ -185,4 +199,4 @@ open class PlayingQueueActivity : AbsMusicServiceActivity() { clearQueue.iconTint = this } } -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/PurchaseActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/PurchaseActivity.kt index 99d30ab7..573ab253 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/PurchaseActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/PurchaseActivity.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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 import android.content.Intent @@ -17,8 +31,8 @@ import code.name.monkey.retromusic.R import code.name.monkey.retromusic.activities.base.AbsBaseActivity import com.anjlab.android.iab.v3.BillingProcessor import com.anjlab.android.iab.v3.TransactionDetails -import kotlinx.android.synthetic.main.activity_pro_version.* import java.lang.ref.WeakReference +import kotlinx.android.synthetic.main.activity_pro_version.* class PurchaseActivity : AbsBaseActivity(), BillingProcessor.IBillingHandler { @@ -47,7 +61,6 @@ class PurchaseActivity : AbsBaseActivity(), BillingProcessor.IBillingHandler { if (restorePurchaseAsyncTask == null || restorePurchaseAsyncTask!!.status != AsyncTask.Status.RUNNING) { restorePurchase() } - } purchaseButton.setOnClickListener { billingProcessor.purchase(this@PurchaseActivity, PRO_VERSION_PRODUCT_ID) diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/SettingsActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/SettingsActivity.kt index 2e138452..5fdada74 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/SettingsActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/SettingsActivity.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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 import android.os.Bundle @@ -49,7 +63,6 @@ class SettingsActivity : AbsBaseActivity(), ColorChooserDialog.ColorCallback { } override fun onColorChooserDismissed(dialog: ColorChooserDialog) { - } override fun onOptionsItemSelected(item: MenuItem): Boolean { @@ -58,4 +71,4 @@ class SettingsActivity : AbsBaseActivity(), ColorChooserDialog.ColorCallback { } return super.onOptionsItemSelected(item) } -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/ShareInstagramStory.kt b/app/src/main/java/code/name/monkey/retromusic/activities/ShareInstagramStory.kt index 3b3f3604..1bcd169c 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/ShareInstagramStory.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/ShareInstagramStory.kt @@ -1,17 +1,17 @@ /* - * Copyright (c) 2020 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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 import android.content.res.ColorStateList diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/SupportDevelopmentActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/SupportDevelopmentActivity.kt index b20d7045..7efc576a 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/SupportDevelopmentActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/SupportDevelopmentActivity.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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 import android.content.Intent @@ -28,9 +42,9 @@ import code.name.monkey.retromusic.extensions.textColorSecondary import com.anjlab.android.iab.v3.BillingProcessor import com.anjlab.android.iab.v3.SkuDetails import com.anjlab.android.iab.v3.TransactionDetails -import kotlinx.android.synthetic.main.activity_donation.* import java.lang.ref.WeakReference import java.util.* +import kotlinx.android.synthetic.main.activity_donation.* class SupportDevelopmentActivity : AbsBaseActivity(), BillingProcessor.IBillingHandler { @@ -91,7 +105,7 @@ class SupportDevelopmentActivity : AbsBaseActivity(), BillingProcessor.IBillingH } override fun onProductPurchased(productId: String, details: TransactionDetails?) { - //loadSkuDetails(); + // loadSkuDetails(); Toast.makeText(this, R.string.thank_you, Toast.LENGTH_SHORT).show() } @@ -100,7 +114,7 @@ class SupportDevelopmentActivity : AbsBaseActivity(), BillingProcessor.IBillingH } override fun onPurchaseHistoryRestored() { - //loadSkuDetails(); + // loadSkuDetails(); Toast.makeText(this, R.string.restored_previous_purchases, Toast.LENGTH_SHORT).show() } @@ -110,7 +124,7 @@ class SupportDevelopmentActivity : AbsBaseActivity(), BillingProcessor.IBillingH } if (requestCode == TEZ_REQUEST_CODE) { // Process based on the data in response. - //Log.d("result", data!!.getStringExtra("Status")) + // Log.d("result", data!!.getStringExtra("Status")) } } @@ -165,7 +179,8 @@ private class SkuDetailsLoadAsyncTask(supportDevelopmentActivity: SupportDevelop } class SkuDetailsAdapter( - private var donationsDialog: SupportDevelopmentActivity, objects: List + private var donationsDialog: SupportDevelopmentActivity, + objects: List ) : RecyclerView.Adapter() { private var skuDetailsList: List = ArrayList() diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/UserInfoActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/UserInfoActivity.kt index eda1e1b0..a3bf682a 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/UserInfoActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/UserInfoActivity.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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 import android.app.Activity @@ -27,15 +41,15 @@ import com.bumptech.glide.request.RequestListener import com.bumptech.glide.request.target.Target import com.github.dhaval2404.imagepicker.ImagePicker import com.github.dhaval2404.imagepicker.constant.ImageProvider +import java.io.BufferedOutputStream +import java.io.File +import java.io.FileOutputStream +import java.io.IOException import kotlinx.android.synthetic.main.activity_user_info.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import java.io.BufferedOutputStream -import java.io.File -import java.io.FileOutputStream -import java.io.IOException class UserInfoActivity : AbsBaseActivity() { @@ -114,7 +128,6 @@ class UserInfoActivity : AbsBaseActivity() { .start(PICK_IMAGE_REQUEST) } - public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (resultCode == Activity.RESULT_OK && requestCode == PICK_IMAGE_REQUEST) { @@ -209,9 +222,8 @@ class UserInfoActivity : AbsBaseActivity() { .into(userImage) } - companion object { private const val PICK_IMAGE_REQUEST = 9002 private const val PICK_BANNER_REQUEST = 9004 } -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/WhatsNewActivity.java b/app/src/main/java/code/name/monkey/retromusic/activities/WhatsNewActivity.java index 4f1a6ef3..00d16e9a 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/WhatsNewActivity.java +++ b/app/src/main/java/code/name/monkey/retromusic/activities/WhatsNewActivity.java @@ -1,23 +1,14 @@ package code.name.monkey.retromusic.activities; - import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.graphics.Color; import android.os.Bundle; import android.webkit.WebView; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.widget.Toolbar; - -import java.io.BufferedReader; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.util.Locale; - import code.name.monkey.appthemehelper.ThemeStore; import code.name.monkey.appthemehelper.util.ATHUtil; import code.name.monkey.appthemehelper.util.ColorUtil; @@ -26,65 +17,95 @@ import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper; import code.name.monkey.retromusic.R; import code.name.monkey.retromusic.activities.base.AbsBaseActivity; import code.name.monkey.retromusic.util.PreferenceUtil; +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Locale; public class WhatsNewActivity extends AbsBaseActivity { - private static String colorToCSS(int color) { - return String.format(Locale.getDefault(), "rgba(%d, %d, %d, %d)", Color.red(color), Color.green(color), - Color.blue(color), Color.alpha(color)); // on API 29, WebView doesn't load with hex colors + private static String colorToCSS(int color) { + return String.format( + Locale.getDefault(), + "rgba(%d, %d, %d, %d)", + Color.red(color), + Color.green(color), + Color.blue(color), + Color.alpha(color)); // on API 29, WebView doesn't load with hex colors + } + + private static void setChangelogRead(@NonNull Context context) { + try { + PackageInfo pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); + int currentVersion = pInfo.versionCode; + PreferenceUtil.INSTANCE.setLastVersion(currentVersion); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); } + } - private static void setChangelogRead(@NonNull Context context) { - try { - PackageInfo pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); - int currentVersion = pInfo.versionCode; - PreferenceUtil.INSTANCE.setLastVersion(currentVersion); - } catch (PackageManager.NameNotFoundException e) { - e.printStackTrace(); - } + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + setDrawUnderStatusBar(); + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_whats_new); + setStatusbarColorAuto(); + setNavigationbarColorAuto(); + setTaskDescriptionColorAuto(); + + WebView webView = findViewById(R.id.webView); + Toolbar toolbar = findViewById(R.id.toolbar); + toolbar.setBackgroundColor(ATHUtil.INSTANCE.resolveColor(this, R.attr.colorSurface)); + toolbar.setNavigationOnClickListener(v -> onBackPressed()); + ToolbarContentTintHelper.colorBackButton(toolbar); + + try { + StringBuilder buf = new StringBuilder(); + InputStream json = getAssets().open("retro-changelog.html"); + BufferedReader in = new BufferedReader(new InputStreamReader(json, StandardCharsets.UTF_8)); + String str; + while ((str = in.readLine()) != null) { + buf.append(str); + } + in.close(); + + // Inject color values for WebView body background and links + final boolean isDark = ATHUtil.INSTANCE.isWindowBackgroundDark(this); + final int accentColor = ThemeStore.Companion.accentColor(this); + final String backgroundColor = + colorToCSS( + ATHUtil.INSTANCE.resolveColor( + this, R.attr.colorSurface, Color.parseColor(isDark ? "#424242" : "#ffffff"))); + final String contentColor = colorToCSS(Color.parseColor(isDark ? "#ffffff" : "#000000")); + final String textColor = colorToCSS(Color.parseColor(isDark ? "#60FFFFFF" : "#80000000")); + final String accentColorString = colorToCSS(ThemeStore.Companion.accentColor(this)); + final String accentTextColor = + colorToCSS( + MaterialValueHelper.getPrimaryTextColor( + this, ColorUtil.INSTANCE.isColorLight(accentColor))); + final String changeLog = + buf.toString() + .replace( + "{style-placeholder}", + String.format( + "body { background-color: %s; color: %s; } li {color: %s;} .colorHeader {background-color: %s; color: %s;} .tag {color: %s;}", + backgroundColor, + contentColor, + textColor, + accentColorString, + accentTextColor, + accentColorString)) + .replace("{link-color}", colorToCSS(ThemeStore.Companion.accentColor(this))) + .replace( + "{link-color-active}", + colorToCSS( + ColorUtil.INSTANCE.lightenColor(ThemeStore.Companion.accentColor(this)))); + webView.loadData(changeLog, "text/html", "UTF-8"); + } catch (Throwable e) { + webView.loadData( + "

Unable to load

" + e.getLocalizedMessage() + "

", "text/html", "UTF-8"); } - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - setDrawUnderStatusBar(); - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_whats_new); - setStatusbarColorAuto(); - setNavigationbarColorAuto(); - setTaskDescriptionColorAuto(); - - WebView webView = findViewById(R.id.webView); - Toolbar toolbar = findViewById(R.id.toolbar); - toolbar.setBackgroundColor(ATHUtil.INSTANCE.resolveColor(this, R.attr.colorSurface)); - toolbar.setNavigationOnClickListener(v -> onBackPressed()); - ToolbarContentTintHelper.colorBackButton(toolbar); - - try { - StringBuilder buf = new StringBuilder(); - InputStream json = getAssets().open("retro-changelog.html"); - BufferedReader in = new BufferedReader(new InputStreamReader(json, StandardCharsets.UTF_8)); - String str; - while ((str = in.readLine()) != null) { - buf.append(str); - } - in.close(); - - // Inject color values for WebView body background and links - final boolean isDark = ATHUtil.INSTANCE.isWindowBackgroundDark(this); - final int accentColor = ThemeStore.Companion.accentColor(this); - final String backgroundColor = colorToCSS(ATHUtil.INSTANCE.resolveColor(this, R.attr.colorSurface, Color.parseColor(isDark ? "#424242" : "#ffffff"))); - final String contentColor = colorToCSS(Color.parseColor(isDark ? "#ffffff" : "#000000")); - final String textColor = colorToCSS(Color.parseColor(isDark ? "#60FFFFFF" : "#80000000")); - final String accentColorString = colorToCSS(ThemeStore.Companion.accentColor(this)); - final String accentTextColor = colorToCSS(MaterialValueHelper.getPrimaryTextColor(this, ColorUtil.INSTANCE.isColorLight(accentColor))); - final String changeLog = buf.toString() - .replace("{style-placeholder}", String.format("body { background-color: %s; color: %s; } li {color: %s;} .colorHeader {background-color: %s; color: %s;} .tag {color: %s;}", backgroundColor, contentColor, textColor, accentColorString, accentTextColor, accentColorString)) - .replace("{link-color}", colorToCSS(ThemeStore.Companion.accentColor(this))) - .replace("{link-color-active}", colorToCSS(ColorUtil.INSTANCE.lightenColor(ThemeStore.Companion.accentColor(this)))); - webView.loadData(changeLog, "text/html", "UTF-8"); - } catch (Throwable e) { - webView.loadData("

Unable to load

" + e.getLocalizedMessage() + "

", "text/html", "UTF-8"); - } - setChangelogRead(this); - } -} \ No newline at end of file + setChangelogRead(this); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsBaseActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsBaseActivity.kt index 71192a72..acec7d62 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsBaseActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsBaseActivity.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.base import android.Manifest @@ -46,7 +60,7 @@ abstract class AbsBaseActivity : AbsThemeActivity() { override fun onPostCreate(savedInstanceState: Bundle?) { super.onPostCreate(savedInstanceState) if (!hasPermissions()) { - //requestPermissions() + // requestPermissions() } } @@ -107,7 +121,7 @@ abstract class AbsBaseActivity : AbsThemeActivity() { this@AbsBaseActivity, Manifest.permission.WRITE_EXTERNAL_STORAGE ) ) { - //User has deny from permission dialog + // User has deny from permission dialog Snackbar.make( snackBarContainer, permissionDeniedMessage!!, diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsMusicServiceActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsMusicServiceActivity.kt index 7b13a4bc..cef13276 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsMusicServiceActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsMusicServiceActivity.kt @@ -1,7 +1,26 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.base import android.Manifest -import android.content.* +import android.content.BroadcastReceiver +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.content.ServiceConnection import android.os.Bundle import android.os.IBinder import androidx.lifecycle.lifecycleScope @@ -11,11 +30,11 @@ import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.interfaces.IMusicServiceEventListener import code.name.monkey.retromusic.repository.RealRepository import code.name.monkey.retromusic.service.MusicService.* +import java.lang.ref.WeakReference +import java.util.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.koin.android.ext.android.inject -import java.lang.ref.WeakReference -import java.util.* abstract class AbsMusicServiceActivity : AbsBaseActivity(), IMusicServiceEventListener { diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsSlidingMusicPanelActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsSlidingMusicPanelActivity.kt index 277040f6..009912af 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsSlidingMusicPanelActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsSlidingMusicPanelActivity.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.base import android.annotation.SuppressLint @@ -148,7 +162,7 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() { miniPlayerFragment?.view?.alpha = alpha miniPlayerFragment?.view?.visibility = if (alpha == 0f) View.GONE else View.VISIBLE bottomNavigationView.translationY = progress * 500 - //bottomNavigationView.alpha = alpha + // bottomNavigationView.alpha = alpha } open fun onPanelCollapsed() { @@ -177,7 +191,7 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() { STATE_EXPANDED -> onPanelExpanded() STATE_COLLAPSED -> onPanelCollapsed() else -> { - //playerFragment!!.onHide() + // playerFragment!!.onHide() } } } @@ -398,4 +412,4 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() { miniPlayerFragment = whichFragment(R.id.miniPlayerFragment) miniPlayerFragment?.view?.setOnClickListener { expandPanel() } } -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsThemeActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsThemeActivity.kt index 6746329b..780f50de 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsThemeActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsThemeActivity.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.base import android.content.Context @@ -37,7 +51,6 @@ abstract class AbsThemeActivity : ATHToolbarActivity(), Runnable { MaterialDialogsUtil.updateMaterialDialogsThemeSingleton(this) } - private fun updateTheme() { setTheme(ThemeManager.getThemeResValue(this)) setDefaultNightMode(ThemeManager.getNightMode(this)) @@ -204,4 +217,4 @@ abstract class AbsThemeActivity : ATHToolbarActivity(), Runnable { super.attachBaseContext(LanguageContextWrapper.wrap(newBase, Locale(code))) } else super.attachBaseContext(newBase) } -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/BugReportActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/BugReportActivity.kt index 5bb2b7ac..7c7eb81e 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/BugReportActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/BugReportActivity.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.bugreport import android.app.Activity @@ -31,6 +45,7 @@ import code.name.monkey.retromusic.misc.DialogAsyncTask import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.textfield.TextInputLayout +import java.io.IOException import kotlinx.android.synthetic.main.activity_bug_report.* import kotlinx.android.synthetic.main.bug_report_card_device_info.* import kotlinx.android.synthetic.main.bug_report_card_report.* @@ -38,7 +53,6 @@ import org.eclipse.egit.github.core.Issue import org.eclipse.egit.github.core.client.GitHubClient import org.eclipse.egit.github.core.client.RequestException import org.eclipse.egit.github.core.service.IssueService -import java.io.IOException private const val RESULT_SUCCESS = "RESULT_OK" private const val RESULT_BAD_CREDENTIALS = "RESULT_BAD_CREDENTIALS" @@ -306,7 +320,6 @@ open class BugReportActivity : AbsThemeActivity() { } } - companion object { fun report( diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/model/DeviceInfo.java b/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/model/DeviceInfo.java index 3a6e8032..79e9b0a0 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/model/DeviceInfo.java +++ b/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/model/DeviceInfo.java @@ -5,126 +5,196 @@ import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Build; - import androidx.annotation.IntRange; - +import code.name.monkey.retromusic.util.PreferenceUtil; import java.util.Arrays; import java.util.Locale; -import code.name.monkey.retromusic.util.PreferenceUtil; - public class DeviceInfo { - @SuppressLint("NewApi") - @SuppressWarnings("deprecation") - private final String[] abis = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? - Build.SUPPORTED_ABIS : new String[]{Build.CPU_ABI, Build.CPU_ABI2}; + @SuppressLint("NewApi") + @SuppressWarnings("deprecation") + private final String[] abis = + Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP + ? Build.SUPPORTED_ABIS + : new String[] {Build.CPU_ABI, Build.CPU_ABI2}; - @SuppressLint("NewApi") - private final String[] abis32Bits = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? - Build.SUPPORTED_32_BIT_ABIS : null; + @SuppressLint("NewApi") + private final String[] abis32Bits = + Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? Build.SUPPORTED_32_BIT_ABIS : null; - @SuppressLint("NewApi") - private final String[] abis64Bits = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? - Build.SUPPORTED_64_BIT_ABIS : null; + @SuppressLint("NewApi") + private final String[] abis64Bits = + Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? Build.SUPPORTED_64_BIT_ABIS : null; - private final String baseTheme; + private final String baseTheme; - private final String brand = Build.BRAND; + private final String brand = Build.BRAND; - private final String buildID = Build.DISPLAY; + private final String buildID = Build.DISPLAY; - private final String buildVersion = Build.VERSION.INCREMENTAL; + private final String buildVersion = Build.VERSION.INCREMENTAL; - private final String device = Build.DEVICE; + private final String device = Build.DEVICE; - private final String hardware = Build.HARDWARE; + private final String hardware = Build.HARDWARE; - private final boolean isAdaptive; + private final boolean isAdaptive; - private final String manufacturer = Build.MANUFACTURER; + private final String manufacturer = Build.MANUFACTURER; - private final String model = Build.MODEL; + private final String model = Build.MODEL; - private final String nowPlayingTheme; + private final String nowPlayingTheme; - private final String product = Build.PRODUCT; + private final String product = Build.PRODUCT; - private final String releaseVersion = Build.VERSION.RELEASE; + private final String releaseVersion = Build.VERSION.RELEASE; - @IntRange(from = 0) - private final int sdkVersion = Build.VERSION.SDK_INT; + @IntRange(from = 0) + private final int sdkVersion = Build.VERSION.SDK_INT; - private final int versionCode; + private final int versionCode; - private final String versionName; - private final String selectedLang; + private final String versionName; + private final String selectedLang; - public DeviceInfo(Context context) { - PackageInfo packageInfo; - try { - packageInfo = context.getPackageManager() - .getPackageInfo(context.getPackageName(), 0); - } catch (PackageManager.NameNotFoundException e) { - packageInfo = null; - } - if (packageInfo != null) { - versionCode = packageInfo.versionCode; - versionName = packageInfo.versionName; - } else { - versionCode = -1; - versionName = null; - } - baseTheme = PreferenceUtil.INSTANCE.getBaseTheme(); - nowPlayingTheme = context.getString(PreferenceUtil.INSTANCE.getNowPlayingScreen().getTitleRes()); - isAdaptive = PreferenceUtil.INSTANCE.isAdaptiveColor(); - selectedLang = PreferenceUtil.INSTANCE.getLanguageCode(); + public DeviceInfo(Context context) { + PackageInfo packageInfo; + try { + packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); + } catch (PackageManager.NameNotFoundException e) { + packageInfo = null; } - - public String toMarkdown() { - return "Device info:\n" - + "---\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
App version" + versionName + "
App version code" + versionCode + "
Android build version" + buildVersion + "
Android release version" + releaseVersion + "
Android SDK version" + sdkVersion + "
Android build ID" + buildID + "
Device brand" + brand + "
Device manufacturer" + manufacturer + "
Device name" + device + "
Device model" + model + "
Device product name" + product + "
Device hardware name" + hardware + "
ABIs" + Arrays.toString(abis) + "
ABIs (32bit)" + Arrays.toString(abis32Bits) + "
ABIs (64bit)" + Arrays.toString(abis64Bits) + "
Language" + selectedLang + "
\n"; + if (packageInfo != null) { + versionCode = packageInfo.versionCode; + versionName = packageInfo.versionName; + } else { + versionCode = -1; + versionName = null; } + baseTheme = PreferenceUtil.INSTANCE.getBaseTheme(); + nowPlayingTheme = + context.getString(PreferenceUtil.INSTANCE.getNowPlayingScreen().getTitleRes()); + isAdaptive = PreferenceUtil.INSTANCE.isAdaptiveColor(); + selectedLang = PreferenceUtil.INSTANCE.getLanguageCode(); + } - @Override - public String toString() { - return "App version: " + versionName + "\n" - + "App version code: " + versionCode + "\n" - + "Android build version: " + buildVersion + "\n" - + "Android release version: " + releaseVersion + "\n" - + "Android SDK version: " + sdkVersion + "\n" - + "Android build ID: " + buildID + "\n" - + "Device brand: " + brand + "\n" - + "Device manufacturer: " + manufacturer + "\n" - + "Device name: " + device + "\n" - + "Device model: " + model + "\n" - + "Device product name: " + product + "\n" - + "Device hardware name: " + hardware + "\n" - + "ABIs: " + Arrays.toString(abis) + "\n" - + "ABIs (32bit): " + Arrays.toString(abis32Bits) + "\n" - + "ABIs (64bit): " + Arrays.toString(abis64Bits) + "\n" - + "Base theme: " + baseTheme + "\n" - + "Now playing theme: " + nowPlayingTheme + "\n" - + "Adaptive: " + isAdaptive + "\n" - + "System language: " + Locale.getDefault().toLanguageTag() + "\n" - + "In-App Language: " + selectedLang; - } + public String toMarkdown() { + return "Device info:\n" + + "---\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
App version" + + versionName + + "
App version code" + + versionCode + + "
Android build version" + + buildVersion + + "
Android release version" + + releaseVersion + + "
Android SDK version" + + sdkVersion + + "
Android build ID" + + buildID + + "
Device brand" + + brand + + "
Device manufacturer" + + manufacturer + + "
Device name" + + device + + "
Device model" + + model + + "
Device product name" + + product + + "
Device hardware name" + + hardware + + "
ABIs" + + Arrays.toString(abis) + + "
ABIs (32bit)" + + Arrays.toString(abis32Bits) + + "
ABIs (64bit)" + + Arrays.toString(abis64Bits) + + "
Language" + + selectedLang + + "
\n"; + } + + @Override + public String toString() { + return "App version: " + + versionName + + "\n" + + "App version code: " + + versionCode + + "\n" + + "Android build version: " + + buildVersion + + "\n" + + "Android release version: " + + releaseVersion + + "\n" + + "Android SDK version: " + + sdkVersion + + "\n" + + "Android build ID: " + + buildID + + "\n" + + "Device brand: " + + brand + + "\n" + + "Device manufacturer: " + + manufacturer + + "\n" + + "Device name: " + + device + + "\n" + + "Device model: " + + model + + "\n" + + "Device product name: " + + product + + "\n" + + "Device hardware name: " + + hardware + + "\n" + + "ABIs: " + + Arrays.toString(abis) + + "\n" + + "ABIs (32bit): " + + Arrays.toString(abis32Bits) + + "\n" + + "ABIs (64bit): " + + Arrays.toString(abis64Bits) + + "\n" + + "Base theme: " + + baseTheme + + "\n" + + "Now playing theme: " + + nowPlayingTheme + + "\n" + + "Adaptive: " + + isAdaptive + + "\n" + + "System language: " + + Locale.getDefault().toLanguageTag() + + "\n" + + "In-App Language: " + + selectedLang; + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/model/Report.java b/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/model/Report.java index 1da9313b..ee1910c2 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/model/Report.java +++ b/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/model/Report.java @@ -1,33 +1,34 @@ package code.name.monkey.retromusic.activities.bugreport.model; - import code.name.monkey.retromusic.activities.bugreport.model.github.ExtraInfo; public class Report { - private final String description; + private final String description; - private final DeviceInfo deviceInfo; + private final DeviceInfo deviceInfo; - private final ExtraInfo extraInfo; + private final ExtraInfo extraInfo; - private final String title; + private final String title; - public Report(String title, String description, DeviceInfo deviceInfo, ExtraInfo extraInfo) { - this.title = title; - this.description = description; - this.deviceInfo = deviceInfo; - this.extraInfo = extraInfo; - } + public Report(String title, String description, DeviceInfo deviceInfo, ExtraInfo extraInfo) { + this.title = title; + this.description = description; + this.deviceInfo = deviceInfo; + this.extraInfo = extraInfo; + } - public String getDescription() { - return description + "\n\n" - + "-\n\n" - + deviceInfo.toMarkdown() + "\n\n" - + extraInfo.toMarkdown(); - } + public String getDescription() { + return description + + "\n\n" + + "-\n\n" + + deviceInfo.toMarkdown() + + "\n\n" + + extraInfo.toMarkdown(); + } - public String getTitle() { - return title; - } + public String getTitle() { + return title; + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/model/github/ExtraInfo.java b/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/model/github/ExtraInfo.java index ec27388c..4bc0e4bf 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/model/github/ExtraInfo.java +++ b/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/model/github/ExtraInfo.java @@ -5,58 +5,57 @@ import java.util.Map; public class ExtraInfo { - private final Map extraInfo = new LinkedHashMap<>(); + private final Map extraInfo = new LinkedHashMap<>(); - public void put(String key, String value) { - extraInfo.put(key, value); + public void put(String key, String value) { + extraInfo.put(key, value); + } + + public void put(String key, boolean value) { + extraInfo.put(key, Boolean.toString(value)); + } + + public void put(String key, double value) { + extraInfo.put(key, Double.toString(value)); + } + + public void put(String key, float value) { + extraInfo.put(key, Float.toString(value)); + } + + public void put(String key, long value) { + extraInfo.put(key, Long.toString(value)); + } + + public void put(String key, int value) { + extraInfo.put(key, Integer.toString(value)); + } + + public void put(String key, Object value) { + extraInfo.put(key, String.valueOf(value)); + } + + public void remove(String key) { + extraInfo.remove(key); + } + + public String toMarkdown() { + if (extraInfo.isEmpty()) { + return ""; } - public void put(String key, boolean value) { - extraInfo.put(key, Boolean.toString(value)); + StringBuilder output = new StringBuilder(); + output.append("Extra info:\n" + "---\n" + "\n"); + for (String key : extraInfo.keySet()) { + output + .append("\n"); } + output.append("
") + .append(key) + .append("") + .append(extraInfo.get(key)) + .append("
\n"); - public void put(String key, double value) { - extraInfo.put(key, Double.toString(value)); - } - - public void put(String key, float value) { - extraInfo.put(key, Float.toString(value)); - } - - public void put(String key, long value) { - extraInfo.put(key, Long.toString(value)); - } - - public void put(String key, int value) { - extraInfo.put(key, Integer.toString(value)); - } - - public void put(String key, Object value) { - extraInfo.put(key, String.valueOf(value)); - } - - public void remove(String key) { - extraInfo.remove(key); - } - - public String toMarkdown() { - if (extraInfo.isEmpty()) { - return ""; - } - - StringBuilder output = new StringBuilder(); - output.append("Extra info:\n" - + "---\n" - + "\n"); - for (String key : extraInfo.keySet()) { - output.append("\n"); - } - output.append("
") - .append(key) - .append("") - .append(extraInfo.get(key)) - .append("
\n"); - - return output.toString(); - } + return output.toString(); + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/model/github/GithubLogin.java b/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/model/github/GithubLogin.java index e388249c..71a0ce7b 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/model/github/GithubLogin.java +++ b/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/model/github/GithubLogin.java @@ -4,38 +4,37 @@ import android.text.TextUtils; public class GithubLogin { - private final String apiToken; + private final String apiToken; - private final String password; + private final String password; - private final String username; + private final String username; - public GithubLogin(String username, String password) { - this.username = username; - this.password = password; - this.apiToken = null; - } + public GithubLogin(String username, String password) { + this.username = username; + this.password = password; + this.apiToken = null; + } - public GithubLogin(String apiToken) { - this.username = null; - this.password = null; - this.apiToken = apiToken; - } + public GithubLogin(String apiToken) { + this.username = null; + this.password = null; + this.apiToken = apiToken; + } - public String getApiToken() { - return apiToken; - } + public String getApiToken() { + return apiToken; + } - public String getPassword() { - return password; - } + public String getPassword() { + return password; + } - public String getUsername() { - return username; - } - - public boolean shouldUseApiToken() { - return TextUtils.isEmpty(username) || TextUtils.isEmpty(password); - } + public String getUsername() { + return username; + } + public boolean shouldUseApiToken() { + return TextUtils.isEmpty(username) || TextUtils.isEmpty(password); + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/model/github/GithubTarget.java b/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/model/github/GithubTarget.java index 21126d30..9e533bc7 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/model/github/GithubTarget.java +++ b/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/model/github/GithubTarget.java @@ -2,20 +2,20 @@ package code.name.monkey.retromusic.activities.bugreport.model.github; public class GithubTarget { - private final String repository; + private final String repository; - private final String username; + private final String username; - public GithubTarget(String username, String repository) { - this.username = username; - this.repository = repository; - } + public GithubTarget(String username, String repository) { + this.username = username; + this.repository = repository; + } - public String getRepository() { - return repository; - } + public String getRepository() { + return repository; + } - public String getUsername() { - return username; - } + public String getUsername() { + return username; + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/saf/SAFGuideActivity.java b/app/src/main/java/code/name/monkey/retromusic/activities/saf/SAFGuideActivity.java index a5f2d908..46fdd786 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/saf/SAFGuideActivity.java +++ b/app/src/main/java/code/name/monkey/retromusic/activities/saf/SAFGuideActivity.java @@ -16,57 +16,58 @@ package code.name.monkey.retromusic.activities.saf; import android.os.Build; import android.os.Bundle; - import androidx.annotation.Nullable; - +import code.name.monkey.retromusic.R; 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. - */ +/** Created by hemanths on 2019-07-31. */ public class SAFGuideActivity extends IntroActivity { - public static final int REQUEST_CODE_SAF_GUIDE = 98; + public static final int REQUEST_CODE_SAF_GUIDE = 98; - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); - setButtonCtaVisible(false); - setButtonNextVisible(false); - setButtonBackVisible(false); + setButtonCtaVisible(false); + setButtonNextVisible(false); + setButtonBackVisible(false); - setButtonCtaTintMode(BUTTON_CTA_TINT_MODE_TEXT); + setButtonCtaTintMode(BUTTON_CTA_TINT_MODE_TEXT); - String title = String.format(getString(R.string.saf_guide_slide1_title), getString(R.string.app_name)); + 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()); - } + 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()); + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AbsTagEditorActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AbsTagEditorActivity.kt index 7bb8a49c..9f40db14 100755 --- a/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AbsTagEditorActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AbsTagEditorActivity.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.tageditor import android.app.Activity @@ -28,13 +42,13 @@ import code.name.monkey.retromusic.util.RetroUtil import code.name.monkey.retromusic.util.SAFUtil import com.google.android.material.button.MaterialButton import com.google.android.material.dialog.MaterialAlertDialogBuilder +import java.io.File +import java.util.* import kotlinx.android.synthetic.main.activity_album_tag_editor.* import org.jaudiotagger.audio.AudioFile import org.jaudiotagger.audio.AudioFileIO import org.jaudiotagger.tag.FieldKey import org.koin.android.ext.android.inject -import java.io.File -import java.util.* abstract class AbsTagEditorActivity : AbsBaseActivity() { val repository by inject() @@ -324,7 +338,8 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() { } protected fun writeValuesToFiles( - fieldKeyValueMap: Map, artworkInfo: ArtworkInfo? + fieldKeyValueMap: Map, + artworkInfo: ArtworkInfo? ) { RetroUtil.hideSoftKeyboard(this) @@ -405,5 +420,4 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() { private val TAG = AbsTagEditorActivity::class.java.simpleName private const val REQUEST_CODE_SELECT_IMAGE = 1000 } - } diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AlbumTagEditorActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AlbumTagEditorActivity.kt index a54633fa..957413ff 100755 --- a/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AlbumTagEditorActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AlbumTagEditorActivity.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.tageditor import android.app.Activity @@ -26,9 +40,9 @@ import com.bumptech.glide.Glide import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.request.animation.GlideAnimation import com.bumptech.glide.request.target.SimpleTarget +import java.util.* import kotlinx.android.synthetic.main.activity_album_tag_editor.* import org.jaudiotagger.tag.FieldKey -import java.util.* class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher { @@ -155,7 +169,7 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher { override fun save() { val fieldKeyValueMap = EnumMap(FieldKey::class.java) fieldKeyValueMap[FieldKey.ALBUM] = albumText.text.toString() - //android seems not to recognize album_artist field so we additionally write the normal artist field + // android seems not to recognize album_artist field so we additionally write the normal artist field fieldKeyValueMap[FieldKey.ARTIST] = albumArtistText.text.toString() fieldKeyValueMap[FieldKey.ALBUM_ARTIST] = albumArtistText.text.toString() fieldKeyValueMap[FieldKey.GENRE] = genreTitle.text.toString() diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/SongTagEditorActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/SongTagEditorActivity.kt index 35b02bda..4446bcfe 100755 --- a/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/SongTagEditorActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/SongTagEditorActivity.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.tageditor import android.net.Uri @@ -9,10 +23,10 @@ import code.name.monkey.appthemehelper.util.MaterialUtil import code.name.monkey.retromusic.R import code.name.monkey.retromusic.extensions.appHandleColor import code.name.monkey.retromusic.repository.SongRepository +import java.util.* import kotlinx.android.synthetic.main.activity_song_tag_editor.* import org.jaudiotagger.tag.FieldKey import org.koin.android.ext.android.inject -import java.util.* class SongTagEditorActivity : AbsTagEditorActivity(), TextWatcher { @@ -111,5 +125,3 @@ class SongTagEditorActivity : AbsTagEditorActivity(), TextWatcher { val TAG: String = SongTagEditorActivity::class.java.simpleName } } - - diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/WriteTagsAsyncTask.java b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/WriteTagsAsyncTask.java index 650fc6cc..405819e6 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/WriteTagsAsyncTask.java +++ b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/WriteTagsAsyncTask.java @@ -7,19 +7,14 @@ import android.graphics.Bitmap; import android.media.MediaScannerConnection; import android.net.Uri; import android.os.Build; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.misc.DialogAsyncTask; +import code.name.monkey.retromusic.misc.UpdateToastMediaScannerCompletionListener; +import code.name.monkey.retromusic.util.MusicUtil; +import code.name.monkey.retromusic.util.SAFUtil; import com.google.android.material.dialog.MaterialAlertDialogBuilder; - -import org.jaudiotagger.audio.AudioFile; -import org.jaudiotagger.audio.AudioFileIO; -import org.jaudiotagger.tag.FieldKey; -import org.jaudiotagger.tag.Tag; -import org.jaudiotagger.tag.images.Artwork; -import org.jaudiotagger.tag.images.ArtworkFactory; - import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -27,166 +22,171 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collection; import java.util.Map; +import org.jaudiotagger.audio.AudioFile; +import org.jaudiotagger.audio.AudioFileIO; +import org.jaudiotagger.tag.FieldKey; +import org.jaudiotagger.tag.Tag; +import org.jaudiotagger.tag.images.Artwork; +import org.jaudiotagger.tag.images.ArtworkFactory; -import code.name.monkey.retromusic.R; -import code.name.monkey.retromusic.misc.DialogAsyncTask; -import code.name.monkey.retromusic.misc.UpdateToastMediaScannerCompletionListener; -import code.name.monkey.retromusic.util.MusicUtil; -import code.name.monkey.retromusic.util.SAFUtil; +public class WriteTagsAsyncTask + extends DialogAsyncTask { -public class WriteTagsAsyncTask extends DialogAsyncTask { + private WeakReference activity; - private WeakReference activity; + public WriteTagsAsyncTask(@NonNull Activity activity) { + super(activity); + this.activity = new WeakReference<>(activity); + } - public WriteTagsAsyncTask(@NonNull Activity activity) { - super(activity); - this.activity = new WeakReference<>(activity); - } + @NonNull + @Override + protected Dialog createDialog(@NonNull Context context) { - @NonNull - @Override - protected Dialog createDialog(@NonNull Context context) { + return new MaterialAlertDialogBuilder(context) + .setTitle(R.string.saving_changes) + .setCancelable(false) + .setView(R.layout.loading) + .create(); + } - return new MaterialAlertDialogBuilder(context) - .setTitle(R.string.saving_changes) - .setCancelable(false) - .setView(R.layout.loading) - .create(); - } + @Override + protected String[] doInBackground(LoadingInfo... params) { + try { + LoadingInfo info = params[0]; - @Override - protected String[] doInBackground(LoadingInfo... params) { + Artwork artwork = null; + File albumArtFile = null; + if (info.artworkInfo != null && info.artworkInfo.getArtwork() != null) { try { - LoadingInfo info = params[0]; - - Artwork artwork = null; - File albumArtFile = null; - if (info.artworkInfo != null && info.artworkInfo.getArtwork() != null) { - try { - albumArtFile = MusicUtil.INSTANCE.createAlbumArtFile().getCanonicalFile(); - info.artworkInfo.getArtwork() - .compress(Bitmap.CompressFormat.PNG, 0, new FileOutputStream(albumArtFile)); - artwork = ArtworkFactory.createArtworkFromFile(albumArtFile); - } catch (IOException e) { - e.printStackTrace(); - } - } - - int counter = 0; - boolean wroteArtwork = false; - boolean deletedArtwork = false; - for (String filePath : info.filePaths) { - publishProgress(++counter, info.filePaths.size()); - 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)); - Tag tag = audioFile.getTagOrCreateAndSetDefault(); - - if (info.fieldKeyValueMap != null) { - for (Map.Entry entry : info.fieldKeyValueMap.entrySet()) { - try { - tag.setField(entry.getKey(), entry.getValue()); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - if (info.artworkInfo != null) { - if (info.artworkInfo.getArtwork() == null) { - tag.deleteArtworkField(); - deletedArtwork = true; - } else if (artwork != null) { - tag.deleteArtworkField(); - tag.setField(artwork); - wroteArtwork = true; - } - } - - Activity activity = this.activity.get(); - SAFUtil.write(activity, audioFile, safUri); - - } catch (@NonNull Exception e) { - e.printStackTrace(); - } - } - - Context context = getContext(); - if (context != null) { - if (wroteArtwork) { - MusicUtil.INSTANCE.insertAlbumArt(context, info.artworkInfo.getAlbumId(), albumArtFile.getPath()); - } else if (deletedArtwork) { - MusicUtil.INSTANCE.deleteAlbumArt(context, info.artworkInfo.getAlbumId()); - } - } - - Collection 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) { - e.printStackTrace(); - return null; + albumArtFile = MusicUtil.INSTANCE.createAlbumArtFile().getCanonicalFile(); + info.artworkInfo + .getArtwork() + .compress(Bitmap.CompressFormat.PNG, 0, new FileOutputStream(albumArtFile)); + artwork = ArtworkFactory.createArtworkFromFile(albumArtFile); + } catch (IOException e) { + e.printStackTrace(); } - } + } - @Override - protected void onCancelled(String[] toBeScanned) { - super.onCancelled(toBeScanned); - scan(toBeScanned); - } + int counter = 0; + boolean wroteArtwork = false; + boolean deletedArtwork = false; + for (String filePath : info.filePaths) { + publishProgress(++counter, info.filePaths.size()); + try { + Uri safUri = null; + if (filePath.contains(SAFUtil.SEPARATOR)) { + String[] fragments = filePath.split(SAFUtil.SEPARATOR); + filePath = fragments[0]; + safUri = Uri.parse(fragments[1]); + } - @Override - protected void onPostExecute(String[] toBeScanned) { - super.onPostExecute(toBeScanned); - scan(toBeScanned); - } + AudioFile audioFile = AudioFileIO.read(new File(filePath)); + Tag tag = audioFile.getTagOrCreateAndSetDefault(); - @Override - protected void onProgressUpdate(@NonNull Dialog dialog, Integer... values) { - super.onProgressUpdate(dialog, values); - //((MaterialDialog) dialog).setMaxProgress(values[1]); - //((MaterialDialog) dialog).setProgress(values[0]); - } + if (info.fieldKeyValueMap != null) { + for (Map.Entry entry : info.fieldKeyValueMap.entrySet()) { + try { + tag.setField(entry.getKey(), entry.getValue()); + } catch (Exception e) { + e.printStackTrace(); + } + } + } - private void scan(String[] toBeScanned) { - Activity activity = this.activity.get(); - if (activity != null) { - MediaScannerConnection.scanFile(activity, toBeScanned, null, - new UpdateToastMediaScannerCompletionListener(activity, toBeScanned)); + if (info.artworkInfo != null) { + if (info.artworkInfo.getArtwork() == null) { + tag.deleteArtworkField(); + deletedArtwork = true; + } else if (artwork != null) { + tag.deleteArtworkField(); + tag.setField(artwork); + wroteArtwork = true; + } + } + + Activity activity = this.activity.get(); + SAFUtil.write(activity, audioFile, safUri); + + } catch (@NonNull Exception e) { + e.printStackTrace(); } - } + } - public static class LoadingInfo { - - @Nullable - final Map fieldKeyValueMap; - - final Collection filePaths; - - @Nullable - private AbsTagEditorActivity.ArtworkInfo artworkInfo; - - public LoadingInfo(Collection filePaths, - @Nullable Map fieldKeyValueMap, - @Nullable AbsTagEditorActivity.ArtworkInfo artworkInfo) { - this.filePaths = filePaths; - this.fieldKeyValueMap = fieldKeyValueMap; - this.artworkInfo = artworkInfo; + Context context = getContext(); + if (context != null) { + if (wroteArtwork) { + MusicUtil.INSTANCE.insertAlbumArt( + context, info.artworkInfo.getAlbumId(), albumArtFile.getPath()); + } else if (deletedArtwork) { + MusicUtil.INSTANCE.deleteAlbumArt(context, info.artworkInfo.getAlbumId()); } + } + + Collection 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) { + e.printStackTrace(); + return null; } -} \ No newline at end of file + } + + @Override + protected void onCancelled(String[] toBeScanned) { + super.onCancelled(toBeScanned); + scan(toBeScanned); + } + + @Override + protected void onPostExecute(String[] toBeScanned) { + super.onPostExecute(toBeScanned); + scan(toBeScanned); + } + + @Override + protected void onProgressUpdate(@NonNull Dialog dialog, Integer... values) { + super.onProgressUpdate(dialog, values); + // ((MaterialDialog) dialog).setMaxProgress(values[1]); + // ((MaterialDialog) dialog).setProgress(values[0]); + } + + private void scan(String[] toBeScanned) { + Activity activity = this.activity.get(); + if (activity != null) { + MediaScannerConnection.scanFile( + activity, + toBeScanned, + null, + new UpdateToastMediaScannerCompletionListener(activity, toBeScanned)); + } + } + + public static class LoadingInfo { + + @Nullable final Map fieldKeyValueMap; + + final Collection filePaths; + + @Nullable private AbsTagEditorActivity.ArtworkInfo artworkInfo; + + public LoadingInfo( + Collection filePaths, + @Nullable Map fieldKeyValueMap, + @Nullable AbsTagEditorActivity.ArtworkInfo artworkInfo) { + this.filePaths = filePaths; + this.fieldKeyValueMap = fieldKeyValueMap; + this.artworkInfo = artworkInfo; + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/CategoryInfoAdapter.java b/app/src/main/java/code/name/monkey/retromusic/adapter/CategoryInfoAdapter.java index f079b318..eb54a5ac 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/CategoryInfoAdapter.java +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/CategoryInfoAdapter.java @@ -22,116 +22,119 @@ import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import android.widget.Toast; - import androidx.annotation.NonNull; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.RecyclerView; - -import com.google.android.material.checkbox.MaterialCheckBox; - -import java.util.List; - import code.name.monkey.appthemehelper.ThemeStore; import code.name.monkey.retromusic.R; import code.name.monkey.retromusic.model.CategoryInfo; import code.name.monkey.retromusic.util.SwipeAndDragHelper; +import com.google.android.material.checkbox.MaterialCheckBox; +import java.util.List; public class CategoryInfoAdapter extends RecyclerView.Adapter - implements SwipeAndDragHelper.ActionCompletionContract { + implements SwipeAndDragHelper.ActionCompletionContract { - private List categoryInfos; - private ItemTouchHelper touchHelper; + private List categoryInfos; + private ItemTouchHelper touchHelper; - public CategoryInfoAdapter() { - SwipeAndDragHelper swipeAndDragHelper = new SwipeAndDragHelper(this); - touchHelper = new ItemTouchHelper(swipeAndDragHelper); - } + public CategoryInfoAdapter() { + SwipeAndDragHelper swipeAndDragHelper = new SwipeAndDragHelper(this); + touchHelper = new ItemTouchHelper(swipeAndDragHelper); + } - public void attachToRecyclerView(RecyclerView recyclerView) { - touchHelper.attachToRecyclerView(recyclerView); - } + public void attachToRecyclerView(RecyclerView recyclerView) { + touchHelper.attachToRecyclerView(recyclerView); + } - @NonNull - public List getCategoryInfos() { - return categoryInfos; - } + @NonNull + public List getCategoryInfos() { + return categoryInfos; + } - public void setCategoryInfos(@NonNull List categoryInfos) { - this.categoryInfos = categoryInfos; - notifyDataSetChanged(); - } + public void setCategoryInfos(@NonNull List categoryInfos) { + this.categoryInfos = categoryInfos; + notifyDataSetChanged(); + } - @Override - public int getItemCount() { - return categoryInfos.size(); - } + @Override + public int getItemCount() { + return categoryInfos.size(); + } - @SuppressLint("ClickableViewAccessibility") - @Override - public void onBindViewHolder(@NonNull CategoryInfoAdapter.ViewHolder holder, int position) { - CategoryInfo categoryInfo = categoryInfos.get(position); + @SuppressLint("ClickableViewAccessibility") + @Override + public void onBindViewHolder(@NonNull CategoryInfoAdapter.ViewHolder holder, int position) { + CategoryInfo categoryInfo = categoryInfos.get(position); - holder.checkBox.setChecked(categoryInfo.isVisible()); - holder.title.setText(holder.title.getResources().getString(categoryInfo.getCategory().getStringRes())); + holder.checkBox.setChecked(categoryInfo.isVisible()); + holder.title.setText( + holder.title.getResources().getString(categoryInfo.getCategory().getStringRes())); - holder.itemView.setOnClickListener(v -> { - if (!(categoryInfo.isVisible() && isLastCheckedCategory(categoryInfo))) { - categoryInfo.setVisible(!categoryInfo.isVisible()); - holder.checkBox.setChecked(categoryInfo.isVisible()); - } else { - Toast.makeText(holder.itemView.getContext(), R.string.you_have_to_select_at_least_one_category, - Toast.LENGTH_SHORT).show(); - } + holder.itemView.setOnClickListener( + v -> { + if (!(categoryInfo.isVisible() && isLastCheckedCategory(categoryInfo))) { + categoryInfo.setVisible(!categoryInfo.isVisible()); + holder.checkBox.setChecked(categoryInfo.isVisible()); + } else { + Toast.makeText( + holder.itemView.getContext(), + R.string.you_have_to_select_at_least_one_category, + Toast.LENGTH_SHORT) + .show(); + } }); - holder.dragView.setOnTouchListener((view, event) -> { - if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { - touchHelper.startDrag(holder); - } - return false; - } - ); - } + holder.dragView.setOnTouchListener( + (view, event) -> { + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { + touchHelper.startDrag(holder); + } + return false; + }); + } - @Override - @NonNull - public CategoryInfoAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View view = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.preference_dialog_library_categories_listitem, parent, false); - return new ViewHolder(view); - } + @Override + @NonNull + public CategoryInfoAdapter.ViewHolder onCreateViewHolder( + @NonNull ViewGroup parent, int viewType) { + View view = + LayoutInflater.from(parent.getContext()) + .inflate(R.layout.preference_dialog_library_categories_listitem, parent, false); + return new ViewHolder(view); + } - @Override - public void onViewMoved(int oldPosition, int newPosition) { - CategoryInfo categoryInfo = categoryInfos.get(oldPosition); - categoryInfos.remove(oldPosition); - categoryInfos.add(newPosition, categoryInfo); - notifyItemMoved(oldPosition, newPosition); - } + @Override + public void onViewMoved(int oldPosition, int newPosition) { + CategoryInfo categoryInfo = categoryInfos.get(oldPosition); + categoryInfos.remove(oldPosition); + categoryInfos.add(newPosition, categoryInfo); + notifyItemMoved(oldPosition, newPosition); + } - private boolean isLastCheckedCategory(CategoryInfo categoryInfo) { - if (categoryInfo.isVisible()) { - for (CategoryInfo c : categoryInfos) { - if (c != categoryInfo && c.isVisible()) { - return false; - } - } + private boolean isLastCheckedCategory(CategoryInfo categoryInfo) { + if (categoryInfo.isVisible()) { + for (CategoryInfo c : categoryInfos) { + if (c != categoryInfo && c.isVisible()) { + return false; } - return true; + } } + return true; + } - static class ViewHolder extends RecyclerView.ViewHolder { - private MaterialCheckBox checkBox; - private View dragView; - private TextView title; + static class ViewHolder extends RecyclerView.ViewHolder { + private MaterialCheckBox checkBox; + private View dragView; + private TextView title; - ViewHolder(View view) { - super(view); - checkBox = view.findViewById(R.id.checkbox); - checkBox.setButtonTintList( - ColorStateList.valueOf(ThemeStore.Companion.accentColor(checkBox.getContext()))); - title = view.findViewById(R.id.title); - dragView = view.findViewById(R.id.drag_view); - } + ViewHolder(View view) { + super(view); + checkBox = view.findViewById(R.id.checkbox); + checkBox.setButtonTintList( + ColorStateList.valueOf(ThemeStore.Companion.accentColor(checkBox.getContext()))); + title = view.findViewById(R.id.title); + dragView = view.findViewById(R.id.drag_view); } -} \ No newline at end of file + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/ContributorAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/ContributorAdapter.kt index 84290ffe..28719c96 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/ContributorAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/ContributorAdapter.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.adapter import android.app.Activity diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/GenreAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/GenreAdapter.kt index 595eafae..2156b7c3 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/GenreAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/GenreAdapter.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.adapter import android.view.LayoutInflater diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/HomeAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/HomeAdapter.kt index 86e272e4..fef1c37d 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/HomeAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/HomeAdapter.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.adapter import android.view.LayoutInflater @@ -121,7 +135,6 @@ class HomeAdapter( viewHolder.bind(home) } PLAYLISTS -> { - } } } @@ -181,7 +194,6 @@ class HomeAdapter( .asBitmap() .build() .into(itemView.findViewById(id)) - } } } diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/SearchAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/SearchAdapter.kt index 6fe434c2..9c527ccc 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/SearchAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/SearchAdapter.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.adapter import android.view.LayoutInflater @@ -60,7 +74,7 @@ class SearchAdapter( override fun onBindViewHolder(holder: ViewHolder, position: Int) { when (getItemViewType(position)) { ALBUM -> { - holder. imageTextContainer?.isVisible = true + holder.imageTextContainer?.isVisible = true val album = dataSet[position] as Album holder.title?.text = album.title holder.text?.text = album.artistName @@ -68,7 +82,7 @@ class SearchAdapter( .checkIgnoreMediaStore().build().into(holder.image) } ARTIST -> { - holder. imageTextContainer?.isVisible = true + holder.imageTextContainer?.isVisible = true val artist = dataSet[position] as Artist holder.title?.text = artist.name holder.text?.text = MusicUtil.getArtistInfoString(activity, artist) diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/SongFileAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/SongFileAdapter.kt index 51133622..eabde5bf 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/SongFileAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/SongFileAdapter.kt @@ -1,17 +1,16 @@ /* - * Copyright 2019 Google LLC + * Copyright (c) 2020 Hemanth Savarla. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the GNU General Public License v3 * - * https://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. */ package code.name.monkey.retromusic.adapter @@ -33,11 +32,11 @@ import code.name.monkey.retromusic.util.RetroUtil import com.bumptech.glide.Glide import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.signature.MediaStoreSignature -import me.zhanghai.android.fastscroll.PopupTextProvider import java.io.File import java.text.DecimalFormat import kotlin.math.log10 import kotlin.math.pow +import me.zhanghai.android.fastscroll.PopupTextProvider class SongFileAdapter( private val activity: AppCompatActivity, @@ -148,7 +147,6 @@ class SongFileAdapter( return MusicUtil.getSectionName(dataSet[position].name) } - inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) { init { @@ -198,4 +196,4 @@ class SongFileAdapter( return DecimalFormat("#,##0.##").format(size / 1024.0.pow(digitGroups.toDouble())) + " " + units[digitGroups] } } -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/TranslatorsAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/TranslatorsAdapter.kt index 90585c36..9630c25c 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/TranslatorsAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/TranslatorsAdapter.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.adapter import android.app.Activity @@ -49,4 +63,4 @@ class TranslatorsAdapter( image.hide() } } -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumAdapter.kt index 2461fad5..178d16af 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumAdapter.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.adapter.album import android.content.res.ColorStateList @@ -129,7 +143,8 @@ open class AlbumAdapter( } override fun onMultipleItemAction( - menuItem: MenuItem, selection: List + menuItem: MenuItem, + selection: List ) { SongsMenuHelper.handleMenuClick(activity, getSongList(selection), menuItem.itemId) } diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumCoverPagerAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumCoverPagerAdapter.kt index d48fe2d4..d994c824 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumCoverPagerAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumCoverPagerAdapter.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.adapter.album import android.content.Intent @@ -205,4 +219,3 @@ class AlbumCoverPagerAdapter( val TAG: String = AlbumCoverPagerAdapter::class.java.simpleName } } - diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/album/HorizontalAlbumAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/album/HorizontalAlbumAdapter.kt index 6f743b6b..26a1958c 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/album/HorizontalAlbumAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/album/HorizontalAlbumAdapter.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.adapter.album import android.view.View @@ -29,8 +43,8 @@ class HorizontalAlbumAdapter( } override fun setColors(color: MediaNotificationProcessor, holder: ViewHolder) { - //holder.title?.setTextColor(ATHUtil.resolveColor(activity, android.R.attr.textColorPrimary)) - //holder.text?.setTextColor(ATHUtil.resolveColor(activity, android.R.attr.textColorSecondary)) + // holder.title?.setTextColor(ATHUtil.resolveColor(activity, android.R.attr.textColorPrimary)) + // holder.text?.setTextColor(ATHUtil.resolveColor(activity, android.R.attr.textColorSecondary)) } override fun loadAlbumCover(album: Album, holder: ViewHolder) { diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/artist/ArtistAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/artist/ArtistAdapter.kt index e925576c..7719166b 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/artist/ArtistAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/artist/ArtistAdapter.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.adapter.artist import android.content.res.ColorStateList @@ -22,8 +36,8 @@ import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.color.MediaNotificationProcessor import com.bumptech.glide.Glide -import me.zhanghai.android.fastscroll.PopupTextProvider import java.util.* +import me.zhanghai.android.fastscroll.PopupTextProvider class ArtistAdapter( val activity: FragmentActivity, @@ -107,7 +121,8 @@ class ArtistAdapter( } override fun onMultipleItemAction( - menuItem: MenuItem, selection: List + menuItem: MenuItem, + selection: List ) { SongsMenuHelper.handleMenuClick(activity, getSongList(selection), menuItem.itemId) } diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/base/AbsMultiSelectAdapter.java b/app/src/main/java/code/name/monkey/retromusic/adapter/base/AbsMultiSelectAdapter.java index 0e2667ae..f0d431ad 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/base/AbsMultiSelectAdapter.java +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/base/AbsMultiSelectAdapter.java @@ -3,132 +3,127 @@ package code.name.monkey.retromusic.adapter.base; import android.content.Context; import android.view.Menu; import android.view.MenuItem; - import androidx.annotation.MenuRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView; - +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.interfaces.ICabHolder; import com.afollestad.materialcab.MaterialCab; - import java.util.ArrayList; import java.util.List; -import code.name.monkey.retromusic.R; -import code.name.monkey.retromusic.interfaces.ICabHolder; +public abstract class AbsMultiSelectAdapter + extends RecyclerView.Adapter implements MaterialCab.Callback { + @Nullable private final ICabHolder ICabHolder; + private final Context context; + private MaterialCab cab; + private List checked; + private int menuRes; -public abstract class AbsMultiSelectAdapter extends RecyclerView.Adapter - implements MaterialCab.Callback { + public AbsMultiSelectAdapter( + @NonNull Context context, @Nullable ICabHolder ICabHolder, @MenuRes int menuRes) { + this.ICabHolder = ICabHolder; + checked = new ArrayList<>(); + this.menuRes = menuRes; + this.context = context; + } - @Nullable - private final ICabHolder ICabHolder; - private final Context context; - private MaterialCab cab; - private List checked; - private int menuRes; + @Override + public boolean onCabCreated(MaterialCab materialCab, Menu menu) { + return true; + } - public AbsMultiSelectAdapter(@NonNull Context context, @Nullable ICabHolder ICabHolder, @MenuRes int menuRes) { - this.ICabHolder = ICabHolder; - checked = new ArrayList<>(); - this.menuRes = menuRes; - this.context = context; + @Override + public boolean onCabFinished(MaterialCab materialCab) { + clearChecked(); + return true; + } + + @Override + public boolean onCabItemClicked(MenuItem menuItem) { + if (menuItem.getItemId() == R.id.action_multi_select_adapter_check_all) { + checkAll(); + } else { + onMultipleItemAction(menuItem, new ArrayList<>(checked)); + cab.finish(); + clearChecked(); } + return true; + } - @Override - public boolean onCabCreated(MaterialCab materialCab, Menu menu) { - return true; - } - - @Override - public boolean onCabFinished(MaterialCab materialCab) { - clearChecked(); - return true; - } - - @Override - public boolean onCabItemClicked(MenuItem menuItem) { - if (menuItem.getItemId() == R.id.action_multi_select_adapter_check_all) { - checkAll(); - } else { - onMultipleItemAction(menuItem, new ArrayList<>(checked)); - cab.finish(); - clearChecked(); + protected void checkAll() { + if (ICabHolder != null) { + checked.clear(); + for (int i = 0; i < getItemCount(); i++) { + I identifier = getIdentifier(i); + if (identifier != null) { + checked.add(identifier); } - return true; + } + notifyDataSetChanged(); + updateCab(); } + } - protected void checkAll() { - if (ICabHolder != null) { - checked.clear(); - for (int i = 0; i < getItemCount(); i++) { - I identifier = getIdentifier(i); - if (identifier != null) { - checked.add(identifier); - } - } - notifyDataSetChanged(); - updateCab(); - } - } + @Nullable + protected abstract I getIdentifier(int position); - @Nullable - protected abstract I getIdentifier(int position); + protected String getName(I object) { + return object.toString(); + } - protected String getName(I object) { - return object.toString(); - } + protected boolean isChecked(I identifier) { + return checked.contains(identifier); + } - protected boolean isChecked(I identifier) { - return checked.contains(identifier); - } + protected boolean isInQuickSelectMode() { + return cab != null && cab.isActive(); + } - protected boolean isInQuickSelectMode() { - return cab != null && cab.isActive(); - } + protected abstract void onMultipleItemAction(MenuItem menuItem, List selection); - protected abstract void onMultipleItemAction(MenuItem menuItem, List selection); + protected void setMultiSelectMenuRes(@MenuRes int menuRes) { + this.menuRes = menuRes; + } - protected void setMultiSelectMenuRes(@MenuRes int menuRes) { - this.menuRes = menuRes; - } - - protected boolean toggleChecked(final int position) { - if (ICabHolder != null) { - I identifier = getIdentifier(position); - if (identifier == null) { - return false; - } - - if (!checked.remove(identifier)) { - checked.add(identifier); - } - - notifyItemChanged(position); - updateCab(); - return true; - } + protected boolean toggleChecked(final int position) { + if (ICabHolder != null) { + I identifier = getIdentifier(position); + if (identifier == null) { return false; - } + } - private void clearChecked() { - checked.clear(); - notifyDataSetChanged(); - } + if (!checked.remove(identifier)) { + checked.add(identifier); + } - private void updateCab() { - if (ICabHolder != null) { - if (cab == null || !cab.isActive()) { - cab = ICabHolder.openCab(menuRes, this); - } - final int size = checked.size(); - if (size <= 0) { - cab.finish(); - } else if (size == 1) { - cab.setTitle(getName(checked.get(0))); - } else { - cab.setTitle(context.getString(R.string.x_selected, size)); - } - } + notifyItemChanged(position); + updateCab(); + return true; } + return false; + } + + private void clearChecked() { + checked.clear(); + notifyDataSetChanged(); + } + + private void updateCab() { + if (ICabHolder != null) { + if (cab == null || !cab.isActive()) { + cab = ICabHolder.openCab(menuRes, this); + } + final int size = checked.size(); + if (size <= 0) { + cab.finish(); + } else if (size == 1) { + cab.setTitle(getName(checked.get(0))); + } else { + cab.setTitle(context.getString(R.string.x_selected, size)); + } + } + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/base/MediaEntryViewHolder.java b/app/src/main/java/code/name/monkey/retromusic/adapter/base/MediaEntryViewHolder.java index ae558723..90347616 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/base/MediaEntryViewHolder.java +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/base/MediaEntryViewHolder.java @@ -19,123 +19,101 @@ import android.view.View; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView; - +import code.name.monkey.retromusic.R; import com.google.android.material.card.MaterialCardView; import com.h6ah4i.android.widget.advrecyclerview.utils.AbstractDraggableSwipeableItemViewHolder; -import code.name.monkey.retromusic.R; - public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHolder - implements View.OnLongClickListener, View.OnClickListener { + implements View.OnLongClickListener, View.OnClickListener { - @Nullable - public View dragView; + @Nullable public View dragView; - @Nullable - public View dummyContainer; + @Nullable public View dummyContainer; - @Nullable - public ImageView image; + @Nullable public ImageView image; - @Nullable - public ImageView artistImage; + @Nullable public ImageView artistImage; - @Nullable - public ImageView playerImage; + @Nullable public ImageView playerImage; - @Nullable - public MaterialCardView imageContainerCard; + @Nullable public MaterialCardView imageContainerCard; - @Nullable - public TextView imageText; + @Nullable public TextView imageText; - @Nullable - public MaterialCardView imageTextContainer; + @Nullable public MaterialCardView imageTextContainer; - @Nullable - public View mask; + @Nullable public View mask; - @Nullable - public View menu; + @Nullable public View menu; - @Nullable - public View paletteColorContainer; + @Nullable public View paletteColorContainer; - @Nullable - public ImageButton playSongs; + @Nullable public ImageButton playSongs; - @Nullable - public RecyclerView recyclerView; + @Nullable public RecyclerView recyclerView; - @Nullable - public TextView text; + @Nullable public TextView text; - @Nullable - public TextView text2; + @Nullable public TextView text2; - @Nullable - public TextView time; + @Nullable public TextView time; - @Nullable - public TextView title; + @Nullable public TextView title; - public MediaEntryViewHolder(@NonNull View itemView) { - super(itemView); - title = itemView.findViewById(R.id.title); - text = itemView.findViewById(R.id.text); - text2 = itemView.findViewById(R.id.text2); + public MediaEntryViewHolder(@NonNull View itemView) { + super(itemView); + title = itemView.findViewById(R.id.title); + text = itemView.findViewById(R.id.text); + text2 = itemView.findViewById(R.id.text2); - image = itemView.findViewById(R.id.image); - artistImage = itemView.findViewById(R.id.artistImage); - playerImage = itemView.findViewById(R.id.player_image); - time = itemView.findViewById(R.id.time); + image = itemView.findViewById(R.id.image); + artistImage = itemView.findViewById(R.id.artistImage); + playerImage = itemView.findViewById(R.id.player_image); + time = itemView.findViewById(R.id.time); - imageText = itemView.findViewById(R.id.imageText); - imageTextContainer = itemView.findViewById(R.id.imageTextContainer); - imageContainerCard = itemView.findViewById(R.id.imageContainerCard); + imageText = itemView.findViewById(R.id.imageText); + imageTextContainer = itemView.findViewById(R.id.imageTextContainer); + imageContainerCard = itemView.findViewById(R.id.imageContainerCard); - menu = itemView.findViewById(R.id.menu); - dragView = itemView.findViewById(R.id.drag_view); - paletteColorContainer = itemView.findViewById(R.id.paletteColorContainer); - recyclerView = itemView.findViewById(R.id.recycler_view); - mask = itemView.findViewById(R.id.mask); - playSongs = itemView.findViewById(R.id.playSongs); - dummyContainer = itemView.findViewById(R.id.dummy_view); + menu = itemView.findViewById(R.id.menu); + dragView = itemView.findViewById(R.id.drag_view); + paletteColorContainer = itemView.findViewById(R.id.paletteColorContainer); + recyclerView = itemView.findViewById(R.id.recycler_view); + mask = itemView.findViewById(R.id.mask); + playSongs = itemView.findViewById(R.id.playSongs); + dummyContainer = itemView.findViewById(R.id.dummy_view); - if (imageContainerCard != null) { - imageContainerCard.setCardBackgroundColor(Color.TRANSPARENT); - } - itemView.setOnClickListener(this); - itemView.setOnLongClickListener(this); + if (imageContainerCard != null) { + imageContainerCard.setCardBackgroundColor(Color.TRANSPARENT); } + itemView.setOnClickListener(this); + itemView.setOnLongClickListener(this); + } - @Nullable - @Override - public View getSwipeableContainerView() { - return null; - } - - @Override - public void onClick(View v) { - - } - - @Override - public boolean onLongClick(View v) { - return false; - } - - public void setImageTransitionName(@NonNull String transitionName) { - itemView.setTransitionName(transitionName); - /* if (imageContainerCard != null) { - imageContainerCard.setTransitionName(transitionName); - } - if (image != null) { - image.setTransitionName(transitionName); - }*/ + @Nullable + @Override + public View getSwipeableContainerView() { + return null; + } + + @Override + public void onClick(View v) {} + + @Override + public boolean onLongClick(View v) { + return false; + } + + public void setImageTransitionName(@NonNull String transitionName) { + itemView.setTransitionName(transitionName); + /* if (imageContainerCard != null) { + imageContainerCard.setTransitionName(transitionName); } + if (image != null) { + image.setTransitionName(transitionName); + }*/ + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/playlist/LegacyPlaylistAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/playlist/LegacyPlaylistAdapter.kt index c74c8a5f..9372e507 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/playlist/LegacyPlaylistAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/playlist/LegacyPlaylistAdapter.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.adapter.playlist import android.view.LayoutInflater @@ -49,4 +63,4 @@ class LegacyPlaylistAdapter( interface PlaylistClickListener { fun onPlaylistClick(playlist: Playlist) } -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/playlist/PlaylistAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/playlist/PlaylistAdapter.kt index 85c34eec..ddfe8c0c 100755 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/playlist/PlaylistAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/playlist/PlaylistAdapter.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.adapter.playlist import android.graphics.Bitmap @@ -56,7 +70,7 @@ class PlaylistAdapter( } override fun getItemId(position: Int): Long { - return dataSet[position].playlistEntity.playListId.toLong() + return dataSet[position].playlistEntity.playListId } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { @@ -88,7 +102,7 @@ class PlaylistAdapter( } else { holder.menu?.show() } - //PlaylistBitmapLoader(this, holder, playlist).execute() + // PlaylistBitmapLoader(this, holder, playlist).execute() } private fun getIconRes(): Drawable = TintHelper.createTintedDrawable( diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/song/AbsOffsetSongAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/song/AbsOffsetSongAdapter.kt index 35cf5791..03ed7233 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/song/AbsOffsetSongAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/song/AbsOffsetSongAdapter.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.adapter.song import android.view.LayoutInflater @@ -76,4 +90,4 @@ abstract class AbsOffsetSongAdapter( const val OFFSET_ITEM = 0 const val SONG = 1 } -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/song/OrderablePlaylistSongAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/song/OrderablePlaylistSongAdapter.kt index 64f9aa19..eafb93f4 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/song/OrderablePlaylistSongAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/song/OrderablePlaylistSongAdapter.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.adapter.song import android.view.MenuItem diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/song/PlayingQueueAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/song/PlayingQueueAdapter.kt index 7004909b..a6e98622 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/song/PlayingQueueAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/song/PlayingQueueAdapter.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.adapter.song import android.view.MenuItem @@ -196,17 +210,17 @@ class PlayingQueueAdapter( private val isPlaying: Boolean = MusicPlayerRemote.isPlaying private val songProgressMillis = 0 override fun onPerformAction() { - //currentlyShownSnackbar = null + // currentlyShownSnackbar = null } override fun onSlideAnimationEnd() { - //initializeSnackBar(adapter, position, activity, isPlaying) + // initializeSnackBar(adapter, position, activity, isPlaying) songToRemove = adapter.dataSet[position] - //If song removed was the playing song, then play the next song + // If song removed was the playing song, then play the next song if (isPlaying(songToRemove!!)) { playNextSong() } - //Swipe animation is much smoother when we do the heavy lifting after it's completed + // Swipe animation is much smoother when we do the heavy lifting after it's completed adapter.setSongToRemove(songToRemove!!) removeFromQueue(songToRemove!!) } diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/song/PlaylistSongAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/song/PlaylistSongAdapter.kt index 0153f0e8..612abcd5 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/song/PlaylistSongAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/song/PlaylistSongAdapter.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.adapter.song import android.view.MenuItem @@ -45,4 +59,4 @@ open class PlaylistSongAdapter( return super.onSongMenuItemClick(item) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/song/ShuffleButtonSongAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/song/ShuffleButtonSongAdapter.kt index 794cb8a4..8eda478e 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/song/ShuffleButtonSongAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/song/ShuffleButtonSongAdapter.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.adapter.song import android.view.View @@ -49,4 +63,4 @@ class ShuffleButtonSongAdapter( super.onClick(v) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/song/SimpleSongAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/song/SimpleSongAdapter.kt index 7bfc4a1c..5d3e6263 100755 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/song/SimpleSongAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/song/SimpleSongAdapter.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.adapter.song import android.view.LayoutInflater diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/song/SongAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/song/SongAdapter.kt index a6b6fe66..8c2f642e 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/song/SongAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/song/SongAdapter.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.adapter.song import android.content.res.ColorStateList diff --git a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/AppShortcutIconGenerator.kt b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/AppShortcutIconGenerator.kt index ff4dbf5a..3186d609 100644 --- a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/AppShortcutIconGenerator.kt +++ b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/AppShortcutIconGenerator.kt @@ -1,17 +1,17 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.appshortcuts import android.content.Context @@ -60,7 +60,10 @@ object AppShortcutIconGenerator { } private fun generateThemedIcon( - context: Context, iconId: Int, foregroundColor: Int, backgroundColor: Int + context: Context, + iconId: Int, + foregroundColor: Int, + backgroundColor: Int ): Icon { // Get and tint foreground and background drawables val vectorDrawable = RetroUtil.getTintedVectorDrawable(context, iconId, foregroundColor) diff --git a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/AppShortcutLauncherActivity.kt b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/AppShortcutLauncherActivity.kt index f41e6946..68d04c47 100644 --- a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/AppShortcutLauncherActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/AppShortcutLauncherActivity.kt @@ -1,17 +1,17 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.appshortcuts import android.app.Activity diff --git a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/DynamicShortcutManager.kt b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/DynamicShortcutManager.kt index c5f0ed6d..7de8c8ef 100644 --- a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/DynamicShortcutManager.kt +++ b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/DynamicShortcutManager.kt @@ -1,17 +1,17 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.appshortcuts import android.annotation.TargetApi @@ -39,9 +39,9 @@ class DynamicShortcutManager(private val context: Context) { ) fun initDynamicShortcuts() { - //if (shortcutManager.dynamicShortcuts.size == 0) { + // if (shortcutManager.dynamicShortcuts.size == 0) { shortcutManager.dynamicShortcuts = defaultShortcuts - //} + // } } fun updateDynamicShortcuts() { diff --git a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/BaseShortcutType.kt b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/BaseShortcutType.kt index c2b376de..1be5edcb 100644 --- a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/BaseShortcutType.kt +++ b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/BaseShortcutType.kt @@ -1,17 +1,17 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.appshortcuts.shortcuttype import android.annotation.TargetApi diff --git a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/LastAddedShortcutType.kt b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/LastAddedShortcutType.kt index ef80bc0e..4855f22e 100644 --- a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/LastAddedShortcutType.kt +++ b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/LastAddedShortcutType.kt @@ -1,17 +1,17 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.appshortcuts.shortcuttype import android.annotation.TargetApi diff --git a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/ShuffleAllShortcutType.kt b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/ShuffleAllShortcutType.kt index dc86fa83..4d13055e 100644 --- a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/ShuffleAllShortcutType.kt +++ b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/ShuffleAllShortcutType.kt @@ -1,17 +1,17 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.appshortcuts.shortcuttype import android.annotation.TargetApi diff --git a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/TopTracksShortcutType.kt b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/TopTracksShortcutType.kt index 1317977c..ab814f4a 100644 --- a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/TopTracksShortcutType.kt +++ b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/TopTracksShortcutType.kt @@ -1,17 +1,17 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.appshortcuts.shortcuttype import android.annotation.TargetApi diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetBig.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetBig.kt index 0a3b3c2c..6bad7c4c 100644 --- a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetBig.kt +++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetBig.kt @@ -1,17 +1,17 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.appwidgets import android.app.PendingIntent @@ -229,6 +229,5 @@ class AppWidgetBig : BaseAppWidget() { } return mInstance!! } - } } diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetCard.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetCard.kt index e4702eb5..7e756d6d 100644 --- a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetCard.kt +++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetCard.kt @@ -1,17 +1,17 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.appwidgets import android.app.PendingIntent diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetClassic.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetClassic.kt index 0cbff1cb..b57cc91c 100644 --- a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetClassic.kt +++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetClassic.kt @@ -1,17 +1,17 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.appwidgets import android.app.PendingIntent @@ -49,7 +49,6 @@ class AppWidgetClassic : BaseAppWidget() { override fun defaultAppWidget(context: Context, appWidgetIds: IntArray) { val appWidgetView = RemoteViews(context.packageName, R.layout.app_widget_classic) - appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE) appWidgetView.setImageViewResource(R.id.image, R.drawable.default_audio_art) appWidgetView.setImageViewBitmap( diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetSmall.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetSmall.kt index 70c1026d..f09b4849 100644 --- a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetSmall.kt +++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetSmall.kt @@ -1,17 +1,17 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.appwidgets import android.app.PendingIntent diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetText.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetText.kt index 777606ad..a09e3ff2 100644 --- a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetText.kt +++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetText.kt @@ -1,17 +1,17 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.appwidgets import android.app.PendingIntent @@ -153,10 +153,7 @@ class AppWidgetText : BaseAppWidget() { ) ) - - pushUpdate(service.applicationContext, appWidgetIds, appWidgetView) - } companion object { diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/BootReceiver.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/BootReceiver.kt index 5f82c2af..079e90ae 100644 --- a/app/src/main/java/code/name/monkey/retromusic/appwidgets/BootReceiver.kt +++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/BootReceiver.kt @@ -1,17 +1,17 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.appwidgets import android.appwidget.AppWidgetManager diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/base/BaseAppWidget.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/base/BaseAppWidget.kt index 29f0d748..5172a76b 100644 --- a/app/src/main/java/code/name/monkey/retromusic/appwidgets/base/BaseAppWidget.kt +++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/base/BaseAppWidget.kt @@ -1,17 +1,17 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.appwidgets.base import android.app.PendingIntent @@ -40,7 +40,9 @@ abstract class BaseAppWidget : AppWidgetProvider() { * {@inheritDoc} */ override fun onUpdate( - context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray + context: Context, + appWidgetManager: AppWidgetManager, + appWidgetIds: IntArray ) { defaultAppWidget(context, appWidgetIds) val updateIntent = Intent(APP_WIDGET_UPDATE) @@ -62,7 +64,9 @@ abstract class BaseAppWidget : AppWidgetProvider() { } protected fun pushUpdate( - context: Context, appWidgetIds: IntArray?, views: RemoteViews + context: Context, + appWidgetIds: IntArray?, + views: RemoteViews ) { val appWidgetManager = AppWidgetManager.getInstance(context) if (appWidgetIds != null) { @@ -86,7 +90,9 @@ abstract class BaseAppWidget : AppWidgetProvider() { } protected fun buildPendingIntent( - context: Context, action: String, serviceName: ComponentName + context: Context, + action: String, + serviceName: ComponentName ): PendingIntent { val intent = Intent(action) intent.component = serviceName @@ -169,7 +175,11 @@ abstract class BaseAppWidget : AppWidgetProvider() { } protected fun composeRoundedRectPath( - rect: RectF, tl: Float, tr: Float, bl: Float, br: Float + rect: RectF, + tl: Float, + tr: Float, + bl: Float, + br: Float ): Path { val path = Path() path.moveTo(rect.left + tl, rect.top) diff --git a/app/src/main/java/code/name/monkey/retromusic/db/BlackListStoreDao.kt b/app/src/main/java/code/name/monkey/retromusic/db/BlackListStoreDao.kt index db0dd0f6..0bf40b73 100644 --- a/app/src/main/java/code/name/monkey/retromusic/db/BlackListStoreDao.kt +++ b/app/src/main/java/code/name/monkey/retromusic/db/BlackListStoreDao.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.db import androidx.room.* @@ -18,4 +32,4 @@ interface BlackListStoreDao { @Query("SELECT * FROM BlackListStoreEntity") fun blackListPaths(): List -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/db/BlackListStoreEntity.kt b/app/src/main/java/code/name/monkey/retromusic/db/BlackListStoreEntity.kt index 5ccbce07..8592442a 100644 --- a/app/src/main/java/code/name/monkey/retromusic/db/BlackListStoreEntity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/db/BlackListStoreEntity.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.db import androidx.room.Entity @@ -7,4 +21,4 @@ import androidx.room.PrimaryKey class BlackListStoreEntity( @PrimaryKey val path: String -) \ No newline at end of file +) diff --git a/app/src/main/java/code/name/monkey/retromusic/db/HistoryDao.kt b/app/src/main/java/code/name/monkey/retromusic/db/HistoryDao.kt index 97288235..7f6a64e6 100644 --- a/app/src/main/java/code/name/monkey/retromusic/db/HistoryDao.kt +++ b/app/src/main/java/code/name/monkey/retromusic/db/HistoryDao.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.db import androidx.lifecycle.LiveData @@ -23,4 +37,4 @@ interface HistoryDao { @Query("SELECT * FROM HistoryEntity ORDER BY time_played DESC LIMIT $HISTORY_LIMIT") fun observableHistorySongs(): LiveData> -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/db/HistoryEntity.kt b/app/src/main/java/code/name/monkey/retromusic/db/HistoryEntity.kt index a4facd79..535a3796 100644 --- a/app/src/main/java/code/name/monkey/retromusic/db/HistoryEntity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/db/HistoryEntity.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.db import androidx.room.ColumnInfo @@ -29,4 +43,4 @@ class HistoryEntity( val albumArtist: String?, @ColumnInfo(name = "time_played") val timePlayed: Long -) \ No newline at end of file +) diff --git a/app/src/main/java/code/name/monkey/retromusic/db/LyricsDao.kt b/app/src/main/java/code/name/monkey/retromusic/db/LyricsDao.kt index a09b430a..fa14b1ac 100644 --- a/app/src/main/java/code/name/monkey/retromusic/db/LyricsDao.kt +++ b/app/src/main/java/code/name/monkey/retromusic/db/LyricsDao.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.db import androidx.room.* @@ -15,4 +29,4 @@ interface LyricsDao { @Update fun updateLyrics(lyricsEntity: LyricsEntity) -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/db/LyricsEntity.kt b/app/src/main/java/code/name/monkey/retromusic/db/LyricsEntity.kt index 0cec6431..91d987d6 100644 --- a/app/src/main/java/code/name/monkey/retromusic/db/LyricsEntity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/db/LyricsEntity.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.db import androidx.room.Entity @@ -7,4 +21,4 @@ import androidx.room.PrimaryKey class LyricsEntity( @PrimaryKey val songId: Int, val lyrics: String -) \ No newline at end of file +) diff --git a/app/src/main/java/code/name/monkey/retromusic/db/PlayCountDao.kt b/app/src/main/java/code/name/monkey/retromusic/db/PlayCountDao.kt index 4107b7ba..420b2d43 100644 --- a/app/src/main/java/code/name/monkey/retromusic/db/PlayCountDao.kt +++ b/app/src/main/java/code/name/monkey/retromusic/db/PlayCountDao.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.db import androidx.room.* @@ -24,4 +38,4 @@ interface PlayCountDao { @Query("UPDATE PlayCountEntity SET play_count = play_count + 1 WHERE id = :id") fun updateQuantity(id: Long) -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/db/PlayCountEntity.kt b/app/src/main/java/code/name/monkey/retromusic/db/PlayCountEntity.kt index 0fe6c088..2fa41b22 100644 --- a/app/src/main/java/code/name/monkey/retromusic/db/PlayCountEntity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/db/PlayCountEntity.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.db import androidx.room.ColumnInfo @@ -31,4 +45,4 @@ class PlayCountEntity( val timePlayed: Long, @ColumnInfo(name = "play_count") var playCount: Int -) \ No newline at end of file +) diff --git a/app/src/main/java/code/name/monkey/retromusic/db/PlaylistDao.kt b/app/src/main/java/code/name/monkey/retromusic/db/PlaylistDao.kt index 36a4e833..579777b9 100644 --- a/app/src/main/java/code/name/monkey/retromusic/db/PlaylistDao.kt +++ b/app/src/main/java/code/name/monkey/retromusic/db/PlaylistDao.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.db import androidx.lifecycle.LiveData @@ -45,12 +59,9 @@ interface PlaylistDao { @Delete suspend fun deletePlaylistSongs(songs: List) - @Query("SELECT * FROM SongEntity WHERE playlist_creator_id= :playlistId") fun favoritesSongsLiveData(playlistId: Long): LiveData> @Query("SELECT * FROM SongEntity WHERE playlist_creator_id= :playlistId") fun favoritesSongs(playlistId: Long): List - - -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/db/PlaylistEntity.kt b/app/src/main/java/code/name/monkey/retromusic/db/PlaylistEntity.kt index 236e9cb4..cda8bff9 100644 --- a/app/src/main/java/code/name/monkey/retromusic/db/PlaylistEntity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/db/PlaylistEntity.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.db import android.os.Parcelable @@ -14,4 +28,4 @@ class PlaylistEntity( val playListId: Long = 0, @ColumnInfo(name = "playlist_name") val playlistName: String -) : Parcelable \ No newline at end of file +) : Parcelable diff --git a/app/src/main/java/code/name/monkey/retromusic/db/PlaylistWithSongs.kt b/app/src/main/java/code/name/monkey/retromusic/db/PlaylistWithSongs.kt index 5a256bde..63c82e39 100644 --- a/app/src/main/java/code/name/monkey/retromusic/db/PlaylistWithSongs.kt +++ b/app/src/main/java/code/name/monkey/retromusic/db/PlaylistWithSongs.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.db import android.os.Parcelable @@ -14,4 +28,3 @@ data class PlaylistWithSongs( ) val songs: List ) : Parcelable - diff --git a/app/src/main/java/code/name/monkey/retromusic/db/RetroDatabase.kt b/app/src/main/java/code/name/monkey/retromusic/db/RetroDatabase.kt index 6be545b6..42eefa7b 100644 --- a/app/src/main/java/code/name/monkey/retromusic/db/RetroDatabase.kt +++ b/app/src/main/java/code/name/monkey/retromusic/db/RetroDatabase.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.db import androidx.room.Database @@ -14,4 +28,4 @@ abstract class RetroDatabase : RoomDatabase() { abstract fun playCountDao(): PlayCountDao abstract fun historyDao(): HistoryDao abstract fun lyricsDao(): LyricsDao -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/db/SongEntity.kt b/app/src/main/java/code/name/monkey/retromusic/db/SongEntity.kt index 6a6d236a..27946028 100644 --- a/app/src/main/java/code/name/monkey/retromusic/db/SongEntity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/db/SongEntity.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.db import android.os.Parcelable @@ -36,4 +50,3 @@ class SongEntity( @ColumnInfo(name = "album_artist") val albumArtist: String? ) : Parcelable - diff --git a/app/src/main/java/code/name/monkey/retromusic/db/SongExtension.kt b/app/src/main/java/code/name/monkey/retromusic/db/SongExtension.kt index ed1ea3be..b6d9f40d 100644 --- a/app/src/main/java/code/name/monkey/retromusic/db/SongExtension.kt +++ b/app/src/main/java/code/name/monkey/retromusic/db/SongExtension.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.db import code.name.monkey.retromusic.model.Song @@ -137,4 +151,3 @@ fun List.toSongsEntity(playlistEntity: PlaylistEntity): List { it.toSongEntity(playlistEntity.playListId) } } - diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/AddToPlaylistDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/AddToPlaylistDialog.kt index fc5d6a58..1dab03d4 100644 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/AddToPlaylistDialog.kt +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/AddToPlaylistDialog.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.Dialog @@ -47,7 +61,6 @@ class AddToPlaylistDialog : DialogFragment() { return adapter } - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val playlistEntities = extraNotNull>(EXTRA_PLAYLISTS).value val songs = extraNotNull>(EXTRA_SONG).value @@ -77,4 +90,4 @@ class AddToPlaylistDialog : DialogFragment() { private fun showCreateDialog(songs: List) { CreatePlaylistDialog.create(songs).show(requireActivity().supportFragmentManager, "Dialog") } -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/BlacklistFolderChooserDialog.java b/app/src/main/java/code/name/monkey/retromusic/dialogs/BlacklistFolderChooserDialog.java index 1e2099cc..3a15c2d8 100644 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/BlacklistFolderChooserDialog.java +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/BlacklistFolderChooserDialog.java @@ -7,150 +7,149 @@ import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.view.View; - import androidx.annotation.NonNull; import androidx.core.app.ActivityCompat; import androidx.fragment.app.DialogFragment; - +import code.name.monkey.retromusic.R; import com.afollestad.materialdialogs.MaterialDialog; - import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; -import code.name.monkey.retromusic.R; +public class BlacklistFolderChooserDialog extends DialogFragment + implements MaterialDialog.ListCallback { -public class BlacklistFolderChooserDialog extends DialogFragment implements MaterialDialog.ListCallback { + String initialPath = Environment.getExternalStorageDirectory().getAbsolutePath(); + private File parentFolder; + private File[] parentContents; + private boolean canGoUp = false; + private FolderCallback callback; - String initialPath = Environment.getExternalStorageDirectory().getAbsolutePath(); - private File parentFolder; - private File[] parentContents; - private boolean canGoUp = false; - private FolderCallback callback; + public static BlacklistFolderChooserDialog create() { + return new BlacklistFolderChooserDialog(); + } - public static BlacklistFolderChooserDialog create() { - return new BlacklistFolderChooserDialog(); + private String[] getContentsArray() { + if (parentContents == null) { + if (canGoUp) { + return new String[] {".."}; + } + return new String[] {}; } - - private String[] getContentsArray() { - if (parentContents == null) { - if (canGoUp) { - return new String[]{".."}; - } - return new String[]{}; - } - String[] results = new String[parentContents.length + (canGoUp ? 1 : 0)]; - if (canGoUp) { - results[0] = ".."; - } - for (int i = 0; i < parentContents.length; i++) { - results[canGoUp ? i + 1 : i] = parentContents[i].getName(); - } - return results; + String[] results = new String[parentContents.length + (canGoUp ? 1 : 0)]; + if (canGoUp) { + results[0] = ".."; } - - private File[] listFiles() { - File[] contents = parentFolder.listFiles(); - List results = new ArrayList<>(); - if (contents != null) { - for (File fi : contents) { - if (fi.isDirectory()) { - results.add(fi); - } - } - Collections.sort(results, new FolderSorter()); - return results.toArray(new File[results.size()]); - } - return null; + for (int i = 0; i < parentContents.length; i++) { + results[canGoUp ? i + 1 : i] = parentContents[i].getName(); } + return results; + } - @NonNull - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M - && ActivityCompat.checkSelfPermission( + private File[] listFiles() { + File[] contents = parentFolder.listFiles(); + List results = new ArrayList<>(); + if (contents != null) { + for (File fi : contents) { + if (fi.isDirectory()) { + results.add(fi); + } + } + Collections.sort(results, new FolderSorter()); + return results.toArray(new File[results.size()]); + } + return null; + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M + && ActivityCompat.checkSelfPermission( requireActivity(), Manifest.permission.READ_EXTERNAL_STORAGE) - != PackageManager.PERMISSION_GRANTED) { - return new MaterialDialog.Builder(requireActivity()) - .title(R.string.md_error_label) - .content(R.string.md_storage_perm_error) - .positiveText(android.R.string.ok) - .build(); - } - if (savedInstanceState == null) { - savedInstanceState = new Bundle(); - } - if (!savedInstanceState.containsKey("current_path")) { - savedInstanceState.putString("current_path", initialPath); - } - parentFolder = new File(savedInstanceState.getString("current_path", File.pathSeparator)); - checkIfCanGoUp(); - parentContents = listFiles(); - MaterialDialog.Builder builder = new MaterialDialog.Builder(requireContext()) - .title(parentFolder.getAbsolutePath()) - .items((CharSequence[]) getContentsArray()) - .itemsCallback(this) - .autoDismiss(false) - .onPositive((dialog, which) -> { - callback.onFolderSelection(BlacklistFolderChooserDialog.this, parentFolder); - dismiss(); + != PackageManager.PERMISSION_GRANTED) { + return new MaterialDialog.Builder(requireActivity()) + .title(R.string.md_error_label) + .content(R.string.md_storage_perm_error) + .positiveText(android.R.string.ok) + .build(); + } + if (savedInstanceState == null) { + savedInstanceState = new Bundle(); + } + if (!savedInstanceState.containsKey("current_path")) { + savedInstanceState.putString("current_path", initialPath); + } + parentFolder = new File(savedInstanceState.getString("current_path", File.pathSeparator)); + checkIfCanGoUp(); + parentContents = listFiles(); + MaterialDialog.Builder builder = + new MaterialDialog.Builder(requireContext()) + .title(parentFolder.getAbsolutePath()) + .items((CharSequence[]) getContentsArray()) + .itemsCallback(this) + .autoDismiss(false) + .onPositive( + (dialog, which) -> { + callback.onFolderSelection(BlacklistFolderChooserDialog.this, parentFolder); + dismiss(); }) - .onNegative((materialDialog, dialogAction) -> dismiss()) - .positiveText(R.string.add_action) - .negativeText(android.R.string.cancel); - return builder.build(); + .onNegative((materialDialog, dialogAction) -> dismiss()) + .positiveText(R.string.add_action) + .negativeText(android.R.string.cancel); + return builder.build(); + } + + @Override + public void onSelection(MaterialDialog materialDialog, View view, int i, CharSequence s) { + if (canGoUp && i == 0) { + parentFolder = parentFolder.getParentFile(); + if (parentFolder.getAbsolutePath().equals("/storage/emulated")) { + parentFolder = parentFolder.getParentFile(); + } + checkIfCanGoUp(); + } else { + parentFolder = parentContents[canGoUp ? i - 1 : i]; + canGoUp = true; + if (parentFolder.getAbsolutePath().equals("/storage/emulated")) { + parentFolder = Environment.getExternalStorageDirectory(); + } } + reload(); + } + + private void checkIfCanGoUp() { + canGoUp = parentFolder.getParent() != null; + } + + private void reload() { + parentContents = listFiles(); + MaterialDialog dialog = (MaterialDialog) getDialog(); + dialog.setTitle(parentFolder.getAbsolutePath()); + dialog.setItems((CharSequence[]) getContentsArray()); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putString("current_path", parentFolder.getAbsolutePath()); + } + + public void setCallback(FolderCallback callback) { + this.callback = callback; + } + + public interface FolderCallback { + void onFolderSelection(@NonNull BlacklistFolderChooserDialog dialog, @NonNull File folder); + } + + private static class FolderSorter implements Comparator { @Override - public void onSelection(MaterialDialog materialDialog, View view, int i, CharSequence s) { - if (canGoUp && i == 0) { - parentFolder = parentFolder.getParentFile(); - if (parentFolder.getAbsolutePath().equals("/storage/emulated")) { - parentFolder = parentFolder.getParentFile(); - } - checkIfCanGoUp(); - } else { - parentFolder = parentContents[canGoUp ? i - 1 : i]; - canGoUp = true; - if (parentFolder.getAbsolutePath().equals("/storage/emulated")) { - parentFolder = Environment.getExternalStorageDirectory(); - } - } - reload(); + public int compare(File lhs, File rhs) { + return lhs.getName().compareTo(rhs.getName()); } - - private void checkIfCanGoUp() { - canGoUp = parentFolder.getParent() != null; - } - - private void reload() { - parentContents = listFiles(); - MaterialDialog dialog = (MaterialDialog) getDialog(); - dialog.setTitle(parentFolder.getAbsolutePath()); - dialog.setItems((CharSequence[]) getContentsArray()); - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putString("current_path", parentFolder.getAbsolutePath()); - } - - public void setCallback(FolderCallback callback) { - this.callback = callback; - } - - public interface FolderCallback { - void onFolderSelection(@NonNull BlacklistFolderChooserDialog dialog, @NonNull File folder); - } - - private static class FolderSorter implements Comparator { - - @Override - public int compare(File lhs, File rhs) { - return lhs.getName().compareTo(rhs.getName()); - } - } -} \ No newline at end of file + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/CreatePlaylistDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/CreatePlaylistDialog.kt index d1e86e12..052e3a1d 100644 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/CreatePlaylistDialog.kt +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/CreatePlaylistDialog.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.Dialog @@ -72,4 +86,4 @@ class CreatePlaylistDialog : DialogFragment() { .create() .colorButtons() } -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/DeletePlaylistDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/DeletePlaylistDialog.kt index 1ee8953a..bfd321cc 100644 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/DeletePlaylistDialog.kt +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/DeletePlaylistDialog.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.Dialog @@ -65,5 +79,4 @@ class DeletePlaylistDialog : DialogFragment() { .create() .colorButtons() } - -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/DeleteSongsDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/DeleteSongsDialog.kt index 24a67a0f..2d7a9fc1 100644 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/DeleteSongsDialog.kt +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/DeleteSongsDialog.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.Dialog @@ -67,4 +81,4 @@ class DeleteSongsDialog : DialogFragment() { .create() .colorButtons() } -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/ImportPlaylistDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/ImportPlaylistDialog.kt index 359ef1c5..1429aa5a 100644 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/ImportPlaylistDialog.kt +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/ImportPlaylistDialog.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.Dialog @@ -21,4 +35,4 @@ class ImportPlaylistDialog : DialogFragment() { .create() .colorButtons() } -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/RemoveSongFromPlaylistDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/RemoveSongFromPlaylistDialog.kt index b52e9fef..93ac92f8 100644 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/RemoveSongFromPlaylistDialog.kt +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/RemoveSongFromPlaylistDialog.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.Dialog @@ -66,4 +80,4 @@ class RemoveSongFromPlaylistDialog : DialogFragment() { .create() .colorButtons() } -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/RenamePlaylistDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/RenamePlaylistDialog.kt index 6aa6939a..79b7d250 100644 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/RenamePlaylistDialog.kt +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/RenamePlaylistDialog.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.Dialog @@ -54,4 +68,4 @@ class RenamePlaylistDialog : DialogFragment() { .create() .colorButtons() } -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/SavePlaylistDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/SavePlaylistDialog.kt index bf726887..62770536 100644 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/SavePlaylistDialog.kt +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/SavePlaylistDialog.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.Dialog @@ -19,7 +33,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext - class SavePlaylistDialog : DialogFragment() { companion object { fun create(playlistWithSongs: PlaylistWithSongs): SavePlaylistDialog { @@ -41,7 +54,6 @@ class SavePlaylistDialog : DialogFragment() { arrayOf(file.path), null ) { _, _ -> - } withContext(Dispatchers.Main) { Toast.makeText( @@ -59,4 +71,4 @@ class SavePlaylistDialog : DialogFragment() { .setView(R.layout.loading) .create().colorButtons() } -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/SleepTimerDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/SleepTimerDialog.kt index 21c43f30..3c3fd6e2 100755 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/SleepTimerDialog.kt +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/SleepTimerDialog.kt @@ -1,17 +1,17 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.annotation.SuppressLint @@ -130,7 +130,6 @@ class SleepTimerDialog : DialogFragment() { } .create() .colorButtons() - } private fun updateTimeDisplayTime() { @@ -173,4 +172,4 @@ class SleepTimerDialog : DialogFragment() { updateCancelButton() } } -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/SongDetailDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/SongDetailDialog.kt index 3409b698..72e548a8 100644 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/SongDetailDialog.kt +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/SongDetailDialog.kt @@ -1,17 +1,17 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.annotation.SuppressLint @@ -31,13 +31,13 @@ import code.name.monkey.retromusic.extensions.colorButtons import code.name.monkey.retromusic.extensions.materialDialog import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.util.MusicUtil +import java.io.File +import java.io.IOException import org.jaudiotagger.audio.AudioFileIO import org.jaudiotagger.audio.exceptions.CannotReadException import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException import org.jaudiotagger.audio.exceptions.ReadOnlyFileException import org.jaudiotagger.tag.TagException -import java.io.File -import java.io.IOException class SongDetailDialog : DialogFragment() { diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/SongShareDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/SongShareDialog.kt index f3ee3d6c..1a5fbca6 100644 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/SongShareDialog.kt +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/SongShareDialog.kt @@ -1,17 +1,17 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.Dialog diff --git a/app/src/main/java/code/name/monkey/retromusic/extensions/ActivityEx.kt b/app/src/main/java/code/name/monkey/retromusic/extensions/ActivityEx.kt index 843cae01..718e5a9f 100644 --- a/app/src/main/java/code/name/monkey/retromusic/extensions/ActivityEx.kt +++ b/app/src/main/java/code/name/monkey/retromusic/extensions/ActivityEx.kt @@ -1,17 +1,17 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.extensions import android.app.Activity @@ -33,4 +33,4 @@ inline fun Activity.extra(key: String, default: T? = null) = l inline fun Activity.extraNotNull(key: String, default: T? = null) = lazy { val value = intent?.extras?.get(key) requireNotNull(if (value is T) value else default) { key } -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/extensions/ColorExt.kt b/app/src/main/java/code/name/monkey/retromusic/extensions/ColorExt.kt index 242bb287..ef6085c4 100644 --- a/app/src/main/java/code/name/monkey/retromusic/extensions/ColorExt.kt +++ b/app/src/main/java/code/name/monkey/retromusic/extensions/ColorExt.kt @@ -1,17 +1,17 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.extensions import android.app.Dialog @@ -180,7 +180,6 @@ fun ProgressIndicator.applyColor(color: Int) { } fun TextInputEditText.accentColor() { - } fun AppCompatImageView.accentColor(): Int { @@ -203,4 +202,3 @@ fun Drawable.tint(context: Context, @ColorRes color: Int): Drawable { fun Context.getColorCompat(@ColorRes colorRes: Int): Int { return ContextCompat.getColor(this, colorRes) } - diff --git a/app/src/main/java/code/name/monkey/retromusic/extensions/CursorExtensions.kt b/app/src/main/java/code/name/monkey/retromusic/extensions/CursorExtensions.kt index 22c8e866..a72eb155 100644 --- a/app/src/main/java/code/name/monkey/retromusic/extensions/CursorExtensions.kt +++ b/app/src/main/java/code/name/monkey/retromusic/extensions/CursorExtensions.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.extensions import android.database.Cursor @@ -34,4 +48,4 @@ internal fun Cursor.getStringOrNull(columnName: String): String? { } catch (ex: Throwable) { throw IllegalStateException("invalid column $columnName", ex) } -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/extensions/DialogExtension.kt b/app/src/main/java/code/name/monkey/retromusic/extensions/DialogExtension.kt index c56a4045..c25c15e9 100644 --- a/app/src/main/java/code/name/monkey/retromusic/extensions/DialogExtension.kt +++ b/app/src/main/java/code/name/monkey/retromusic/extensions/DialogExtension.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.extensions import androidx.appcompat.app.AlertDialog @@ -19,4 +33,4 @@ fun AlertDialog.colorButtons(): AlertDialog { getButton(AlertDialog.BUTTON_NEUTRAL).accentTextColor() } return this -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/extensions/DimenExtension.kt b/app/src/main/java/code/name/monkey/retromusic/extensions/DimenExtension.kt index f02e5187..6e5cdecc 100644 --- a/app/src/main/java/code/name/monkey/retromusic/extensions/DimenExtension.kt +++ b/app/src/main/java/code/name/monkey/retromusic/extensions/DimenExtension.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.extensions import android.app.Activity @@ -17,4 +31,4 @@ fun Activity.dipToPix(dpInFloat: Float): Float { fun Fragment.dipToPix(dpInFloat: Float): Float { val scale = resources.displayMetrics.density return dpInFloat * scale + 0.5f -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/extensions/DrawableExt.kt b/app/src/main/java/code/name/monkey/retromusic/extensions/DrawableExt.kt index dc86fb94..3281c57b 100644 --- a/app/src/main/java/code/name/monkey/retromusic/extensions/DrawableExt.kt +++ b/app/src/main/java/code/name/monkey/retromusic/extensions/DrawableExt.kt @@ -1,17 +1,17 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.extensions import android.content.Context @@ -59,4 +59,4 @@ fun getAdaptiveIconDrawable(context: Context): Drawable { } else { ContextCompat.getDrawable(context, R.drawable.color_circle_gradient)!! } -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/extensions/FragmentExt.kt b/app/src/main/java/code/name/monkey/retromusic/extensions/FragmentExt.kt index a826d153..6c74050a 100644 --- a/app/src/main/java/code/name/monkey/retromusic/extensions/FragmentExt.kt +++ b/app/src/main/java/code/name/monkey/retromusic/extensions/FragmentExt.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.extensions import android.content.Context @@ -27,7 +41,6 @@ fun Context.getIntRes(@IntegerRes int: Int): Int { val Context.generalThemeValue get() = PreferenceUtil.getGeneralThemeValue(isSystemDarkModeEnabled()) - fun Context.isSystemDarkModeEnabled(): Boolean { val isBatterySaverEnabled = (getSystemService(Context.POWER_SERVICE) as PowerManager?)?.isPowerSaveMode ?: false @@ -36,7 +49,6 @@ fun Context.isSystemDarkModeEnabled(): Boolean { return isBatterySaverEnabled or isDarkModeEnabled } - inline fun Fragment.extra(key: String, default: T? = null) = lazy { val value = arguments?.get(key) if (value is T) value else default @@ -84,4 +96,4 @@ fun Context.getDrawableCompat(@DrawableRes drawableRes: Int): Drawable { fun Fragment.getDrawableCompat(@DrawableRes drawableRes: Int): Drawable { return AppCompatResources.getDrawable(requireContext(), drawableRes)!! -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/extensions/NavigationExtensions.kt b/app/src/main/java/code/name/monkey/retromusic/extensions/NavigationExtensions.kt index 2ea5ade8..6bb0cebc 100644 --- a/app/src/main/java/code/name/monkey/retromusic/extensions/NavigationExtensions.kt +++ b/app/src/main/java/code/name/monkey/retromusic/extensions/NavigationExtensions.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.extensions import androidx.annotation.IdRes @@ -22,4 +36,4 @@ fun Fragment.findActivityNavController(@IdRes id: Int): NavController { fun AppCompatActivity.findNavController(@IdRes id: Int): NavController { val fragment = supportFragmentManager.findFragmentById(id) as NavHostFragment return fragment.navController -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/extensions/PaletteEX.kt b/app/src/main/java/code/name/monkey/retromusic/extensions/PaletteEX.kt index bcc9b632..58a63195 100644 --- a/app/src/main/java/code/name/monkey/retromusic/extensions/PaletteEX.kt +++ b/app/src/main/java/code/name/monkey/retromusic/extensions/PaletteEX.kt @@ -1,10 +1,23 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.extensions import androidx.annotation.ColorInt import androidx.core.graphics.ColorUtils import androidx.palette.graphics.Palette - fun getSuitableColorFor(palette: Palette, i: Int, i2: Int): Int { val dominantSwatch = palette.dominantSwatch if (dominantSwatch != null) { diff --git a/app/src/main/java/code/name/monkey/retromusic/extensions/Preference.kt b/app/src/main/java/code/name/monkey/retromusic/extensions/Preference.kt index 0fe53f4a..317844af 100644 --- a/app/src/main/java/code/name/monkey/retromusic/extensions/Preference.kt +++ b/app/src/main/java/code/name/monkey/retromusic/extensions/Preference.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.extensions import android.content.SharedPreferences diff --git a/app/src/main/java/code/name/monkey/retromusic/extensions/ViewExtensions.kt b/app/src/main/java/code/name/monkey/retromusic/extensions/ViewExtensions.kt index 1e78b690..674610d6 100644 --- a/app/src/main/java/code/name/monkey/retromusic/extensions/ViewExtensions.kt +++ b/app/src/main/java/code/name/monkey/retromusic/extensions/ViewExtensions.kt @@ -1,17 +1,17 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.extensions import android.animation.ObjectAnimator @@ -50,8 +50,7 @@ fun EditText.appHandleColor(): EditText { return this } - -fun View.translateXAnimate(value: Float) { +fun View.translateXAnimate(value: Float) { ObjectAnimator.ofFloat(this, "translationY", value) .apply { duration = 300 @@ -76,4 +75,3 @@ fun BottomSheetBehavior<*>.peekHeightAnimate(value: Int) { start() } } - diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/AlbumCoverStyle.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/AlbumCoverStyle.kt index b8746b2c..e0da98ca 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/AlbumCoverStyle.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/AlbumCoverStyle.kt @@ -1,10 +1,23 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.fragments import androidx.annotation.DrawableRes import androidx.annotation.StringRes import code.name.monkey.retromusic.R - enum class AlbumCoverStyle( @StringRes val titleRes: Int, @DrawableRes val drawableResId: Int, diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/CoroutineViewModel.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/CoroutineViewModel.kt index 9b6a5ca4..a1227d9c 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/CoroutineViewModel.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/CoroutineViewModel.kt @@ -1,8 +1,22 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.fragments import androidx.lifecycle.ViewModel -import kotlinx.coroutines.* import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.* open class CoroutineViewModel( private val mainDispatcher: CoroutineDispatcher @@ -20,4 +34,4 @@ open class CoroutineViewModel( super.onCleared() job.cancel() } -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/DetailListFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/DetailListFragment.kt index 361ba100..7037909c 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/DetailListFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/DetailListFragment.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.fragments import android.os.Bundle @@ -193,4 +207,4 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de ) ) } -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/LibraryViewModel.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/LibraryViewModel.kt index 1fb47763..6a554854 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/LibraryViewModel.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/LibraryViewModel.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.fragments import androidx.lifecycle.* @@ -166,7 +180,6 @@ class LibraryViewModel( override fun onPlayingMetaChanged() { println("onPlayingMetaChanged") - } override fun onPlayStateChanged() { @@ -299,4 +312,4 @@ enum class ReloadType { HomeSections, Playlists, Genres, -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/MiniPlayerFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/MiniPlayerFragment.kt index 9a16d656..399f5ed1 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/MiniPlayerFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/MiniPlayerFragment.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.fragments import android.animation.ObjectAnimator @@ -12,15 +26,19 @@ import android.view.MotionEvent import android.view.View import android.view.animation.DecelerateInterpolator import code.name.monkey.retromusic.R -import code.name.monkey.retromusic.extensions.* +import code.name.monkey.retromusic.extensions.accentColor +import code.name.monkey.retromusic.extensions.applyColor +import code.name.monkey.retromusic.extensions.show +import code.name.monkey.retromusic.extensions.textColorPrimary +import code.name.monkey.retromusic.extensions.textColorSecondary import code.name.monkey.retromusic.fragments.base.AbsMusicServiceFragment import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.RetroUtil -import kotlinx.android.synthetic.main.fragment_mini_player.* import kotlin.math.abs +import kotlinx.android.synthetic.main.fragment_mini_player.* open class MiniPlayerFragment : AbsMusicServiceFragment(R.layout.fragment_mini_player), MusicProgressViewUpdateHelper.Callback, View.OnClickListener { @@ -49,7 +67,6 @@ open class MiniPlayerFragment : AbsMusicServiceFragment(R.layout.fragment_mini_p actionPrevious.show() actionNext?.show() actionPrevious?.show() - } else { actionNext.visibility = if (PreferenceUtil.isExtraControls) View.VISIBLE else View.GONE actionPrevious.visibility = @@ -126,7 +143,7 @@ open class MiniPlayerFragment : AbsMusicServiceFragment(R.layout.fragment_mini_p } fun updateProgressBar(paletteColor: Int) { - progressBar.applyColor(paletteColor) + progressBar.applyColor(paletteColor) } class FlingPlayBackController(context: Context) : View.OnTouchListener { @@ -137,7 +154,9 @@ open class MiniPlayerFragment : AbsMusicServiceFragment(R.layout.fragment_mini_p flingPlayBackController = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() { override fun onFling( - e1: MotionEvent, e2: MotionEvent, velocityX: Float, + e1: MotionEvent, + e2: MotionEvent, + velocityX: Float, velocityY: Float ): Boolean { if (abs(velocityX) > abs(velocityY)) { diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/NowPlayingScreen.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/NowPlayingScreen.kt index 522a9329..12b514c3 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/NowPlayingScreen.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/NowPlayingScreen.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.fragments import androidx.annotation.DrawableRes diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/VolumeFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/VolumeFragment.kt index a6783bc0..ed2f88cc 100755 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/VolumeFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/VolumeFragment.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.fragments import android.content.Context @@ -28,7 +42,9 @@ class VolumeFragment : Fragment(), SeekBar.OnSeekBarChangeListener, OnAudioVolum get() = requireContext().getSystemService(Context.AUDIO_SERVICE) as AudioManager override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? ): View? { return inflater.inflate(R.layout.fragment_volume, container, false) } @@ -114,13 +130,12 @@ class VolumeFragment : Fragment(), SeekBar.OnSeekBarChangeListener, OnAudioVolum if (PreferenceUtil.isPauseOnZeroVolume) if (MusicPlayerRemote.isPlaying && pauseWhenZeroVolume) MusicPlayerRemote.pauseSong() - } fun setTintableColor(color: Int) { volumeDown.setColorFilter(color, PorterDuff.Mode.SRC_IN) volumeUp.setColorFilter(color, PorterDuff.Mode.SRC_IN) - //TintHelper.setTint(volumeSeekBar, color, false) + // TintHelper.setTint(volumeSeekBar, color, false) volumeSeekBar.applyColor(color) } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/about/AboutFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/about/AboutFragment.kt index abfea08e..86f5b2ed 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/about/AboutFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/about/AboutFragment.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.fragments.about import android.content.Intent @@ -31,7 +45,6 @@ class AboutFragment : Fragment(R.layout.fragment_about), View.OnClickListener { loadContributors() } - private fun openUrl(url: String) { val i = Intent(Intent.ACTION_VIEW) i.data = Uri.parse(url) diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumDetailsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumDetailsFragment.kt index 85bd13f2..e01f02fb 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumDetailsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumDetailsFragment.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.fragments.albums import android.app.ActivityOptions @@ -52,6 +66,7 @@ import code.name.monkey.retromusic.util.RetroUtil import code.name.monkey.retromusic.util.color.MediaNotificationProcessor import com.bumptech.glide.Glide import com.google.android.material.transition.MaterialContainerTransform +import java.util.* import kotlinx.android.synthetic.main.fragment_album_content.* import kotlinx.android.synthetic.main.fragment_album_details.* import kotlinx.coroutines.Dispatchers @@ -60,7 +75,6 @@ import kotlinx.coroutines.withContext import org.koin.android.ext.android.get import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf -import java.util.* class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_details), IAlbumClickListener { @@ -407,4 +421,4 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det companion object { const val TAG_EDITOR_REQUEST = 9002 } -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumDetailsViewModel.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumDetailsViewModel.kt index f764a98b..c3f85d72 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumDetailsViewModel.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumDetailsViewModel.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.fragments.albums import androidx.lifecycle.LiveData @@ -37,7 +51,6 @@ class AlbumDetailsViewModel( } override fun onMediaStoreChanged() { - } override fun onServiceConnected() {} @@ -47,4 +60,4 @@ class AlbumDetailsViewModel( override fun onPlayStateChanged() {} override fun onRepeatModeChanged() {} override fun onShuffleModeChanged() {} -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumsFragment.kt index 621aef7e..7158d797 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumsFragment.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.fragments.albums import android.os.Bundle @@ -22,7 +36,6 @@ import code.name.monkey.retromusic.util.RetroColorUtil import code.name.monkey.retromusic.util.RetroUtil import com.afollestad.materialcab.MaterialCab - class AlbumsFragment : AbsRecyclerViewCustomGridSizeFragment(), IAlbumClickListener, ICabHolder { @@ -95,7 +108,6 @@ class AlbumsFragment : AbsRecyclerViewCustomGridSizeFragment(), IArtistClickListener, ICabHolder { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -119,7 +132,6 @@ class ArtistsFragment : AbsRecyclerViewCustomGridSizeFragment } else R.layout.item_list } - fun setAndSaveLayoutRes(layoutRes: Int) { saveLayoutRes(layoutRes) invalidateAdapter() @@ -51,7 +64,6 @@ abstract class AbsRecyclerViewCustomGridSizeFragment return gridSize } - fun getSortOrder(): String? { if (sortOrder == null) { sortOrder = loadSortOrder() @@ -59,7 +71,6 @@ abstract class AbsRecyclerViewCustomGridSizeFragment return sortOrder } - fun setAndSaveSortOrder(sortOrder: String) { this.sortOrder = sortOrder saveSortOrder(sortOrder) diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/base/AbsRecyclerViewFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/base/AbsRecyclerViewFragment.kt index 4144ef04..1d06263b 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/base/AbsRecyclerViewFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/base/AbsRecyclerViewFragment.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.fragments.base import android.os.Bundle @@ -24,7 +38,6 @@ import kotlinx.android.synthetic.main.fragment_main_recycler.* import me.zhanghai.android.fastscroll.FastScroller import me.zhanghai.android.fastscroll.FastScrollerBuilder - abstract class AbsRecyclerViewFragment, LM : RecyclerView.LayoutManager> : AbsMainActivityFragment(R.layout.fragment_main_recycler), AppBarLayout.OnOffsetChangedListener { @@ -128,7 +141,6 @@ abstract class AbsRecyclerViewFragment, LM : Recycle } } - private fun initLayoutManager() { layoutManager = createLayoutManager() } @@ -206,4 +218,4 @@ abstract class AbsRecyclerViewFragment, LM : Recycle } return super.onOptionsItemSelected(item) } -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/folder/FoldersFragment.java b/app/src/main/java/code/name/monkey/retromusic/fragments/folder/FoldersFragment.java index 43cd9ae5..e0769e8f 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/folder/FoldersFragment.java +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/folder/FoldersFragment.java @@ -14,6 +14,8 @@ package code.name.monkey.retromusic.fragments.folder; +import static code.name.monkey.appthemehelper.common.ATHToolbarActivity.getToolbarBackgroundColor; + import android.app.Dialog; import android.content.Context; import android.media.MediaScannerConnection; @@ -32,7 +34,6 @@ import android.webkit.MimeTypeMap; import android.widget.PopupMenu; import android.widget.TextView; import android.widget.Toast; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.widget.Toolbar; @@ -42,23 +43,6 @@ import androidx.loader.content.Loader; import androidx.navigation.Navigation; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; - -import com.afollestad.materialcab.MaterialCab; -import com.google.android.material.dialog.MaterialAlertDialogBuilder; -import com.google.android.material.snackbar.Snackbar; - -import org.jetbrains.annotations.NotNull; - -import java.io.File; -import java.io.FileFilter; -import java.io.IOException; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.LinkedList; -import java.util.List; - import code.name.monkey.appthemehelper.ThemeStore; import code.name.monkey.appthemehelper.util.ATHUtil; import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper; @@ -83,700 +67,754 @@ import code.name.monkey.retromusic.util.RetroColorUtil; import code.name.monkey.retromusic.util.ThemedFastScroller; import code.name.monkey.retromusic.views.BreadCrumbLayout; import code.name.monkey.retromusic.views.ScrollingViewOnApplyWindowInsetsListener; +import com.afollestad.materialcab.MaterialCab; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.google.android.material.snackbar.Snackbar; +import java.io.File; +import java.io.FileFilter; +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedList; +import java.util.List; import me.zhanghai.android.fastscroll.FastScroller; +import org.jetbrains.annotations.NotNull; -import static code.name.monkey.appthemehelper.common.ATHToolbarActivity.getToolbarBackgroundColor; - -public class FoldersFragment extends AbsMainActivityFragment implements - IMainActivityFragmentCallbacks, +public class FoldersFragment extends AbsMainActivityFragment + implements IMainActivityFragmentCallbacks, ICabHolder, BreadCrumbLayout.SelectionCallback, ICallbacks, LoaderManager.LoaderCallbacks> { - public static final String TAG = FoldersFragment.class.getSimpleName(); - public static final FileFilter AUDIO_FILE_FILTER = file -> !file.isHidden() && (file.isDirectory() || - FileUtil.fileIsMimeType(file, "audio/*", MimeTypeMap.getSingleton()) || - FileUtil.fileIsMimeType(file, "application/opus", MimeTypeMap.getSingleton()) || - FileUtil.fileIsMimeType(file, "application/ogg", MimeTypeMap.getSingleton())); + public static final String TAG = FoldersFragment.class.getSimpleName(); + public static final FileFilter AUDIO_FILE_FILTER = + file -> + !file.isHidden() + && (file.isDirectory() + || FileUtil.fileIsMimeType(file, "audio/*", MimeTypeMap.getSingleton()) + || FileUtil.fileIsMimeType(file, "application/opus", MimeTypeMap.getSingleton()) + || FileUtil.fileIsMimeType(file, "application/ogg", MimeTypeMap.getSingleton())); - private static final String CRUMBS = "crumbs"; - private static final int LOADER_ID = 5; - private SongFileAdapter adapter; - private Toolbar toolbar; - private TextView appNameText; - private BreadCrumbLayout breadCrumbs; - private MaterialCab cab; - private View coordinatorLayout; - private View empty; - private TextView emojiText; - private Comparator fileComparator = (lhs, rhs) -> { + private static final String CRUMBS = "crumbs"; + private static final int LOADER_ID = 5; + private SongFileAdapter adapter; + private Toolbar toolbar; + private TextView appNameText; + private BreadCrumbLayout breadCrumbs; + private MaterialCab cab; + private View coordinatorLayout; + private View empty; + private TextView emojiText; + private Comparator fileComparator = + (lhs, rhs) -> { if (lhs.isDirectory() && !rhs.isDirectory()) { - return -1; + return -1; } else if (!lhs.isDirectory() && rhs.isDirectory()) { - return 1; + return 1; } else { - return lhs.getName().compareToIgnoreCase - (rhs.getName()); + return lhs.getName().compareToIgnoreCase(rhs.getName()); } - }; - private RecyclerView recyclerView; + }; + private RecyclerView recyclerView; - public FoldersFragment() { - super(R.layout.fragment_folder); + public FoldersFragment() { + super(R.layout.fragment_folder); + } + + public static File getDefaultStartDirectory() { + File musicDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC); + File startFolder; + if (musicDir.exists() && musicDir.isDirectory()) { + startFolder = musicDir; + } else { + File externalStorage = Environment.getExternalStorageDirectory(); + if (externalStorage.exists() && externalStorage.isDirectory()) { + startFolder = externalStorage; + } else { + startFolder = new File("/"); // root + } } + return startFolder; + } - public static File getDefaultStartDirectory() { - File musicDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC); - File startFolder; - if (musicDir.exists() && musicDir.isDirectory()) { - startFolder = musicDir; - } else { - File externalStorage = Environment.getExternalStorageDirectory(); - if (externalStorage.exists() && externalStorage.isDirectory()) { - startFolder = externalStorage; - } else { - startFolder = new File("/"); // root + private static File tryGetCanonicalFile(File file) { + try { + return file.getCanonicalFile(); + } catch (IOException e) { + e.printStackTrace(); + return file; + } + } + + @NonNull + @Override + public View onCreateView( + @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_folder, container, false); + initViews(view); + return view; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + getLibraryViewModel().setPanelState(NowPlayingPanelState.COLLAPSED_WITH); + getMainActivity().setSupportActionBar(toolbar); + getMainActivity().getSupportActionBar().setTitle(null); + setStatusBarColorAuto(view); + setUpAppbarColor(); + setUpBreadCrumbs(); + setUpRecyclerView(); + setUpAdapter(); + setUpTitle(); + } + + private void setUpTitle() { + toolbar.setNavigationOnClickListener( + v -> Navigation.findNavController(v).navigate(R.id.searchFragment, null, getNavOptions())); + int color = ThemeStore.Companion.accentColor(requireContext()); + String hexColor = String.format("#%06X", 0xFFFFFF & color); + Spanned appName = + HtmlCompat.fromHtml( + "Retro Music", + HtmlCompat.FROM_HTML_MODE_COMPACT); + appNameText.setText(appName); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + setHasOptionsMenu(true); + if (savedInstanceState == null) { + setCrumb( + new BreadCrumbLayout.Crumb( + FileUtil.safeGetCanonicalFile(PreferenceUtil.INSTANCE.getStartDirectory())), + true); + } else { + breadCrumbs.restoreFromStateWrapper(savedInstanceState.getParcelable(CRUMBS)); + LoaderManager.getInstance(this).initLoader(LOADER_ID, null, this); + } + } + + @Override + public void onPause() { + super.onPause(); + saveScrollPosition(); + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + if (breadCrumbs != null) { + outState.putParcelable(CRUMBS, breadCrumbs.getStateWrapper()); + } + } + + @Override + public boolean handleBackPress() { + if (cab != null && cab.isActive()) { + cab.finish(); + return true; + } + if (breadCrumbs != null && breadCrumbs.popHistory()) { + setCrumb(breadCrumbs.lastHistory(), false); + return true; + } + return false; + } + + @NonNull + @Override + public Loader> onCreateLoader(int id, Bundle args) { + return new AsyncFileLoader(this); + } + + @Override + public void onCrumbSelection(BreadCrumbLayout.Crumb crumb, int index) { + setCrumb(crumb, true); + } + + @Override + public void onFileMenuClicked(final File file, @NotNull View view) { + PopupMenu popupMenu = new PopupMenu(getActivity(), view); + if (file.isDirectory()) { + popupMenu.inflate(R.menu.menu_item_directory); + popupMenu.setOnMenuItemClickListener( + item -> { + final int itemId = item.getItemId(); + switch (itemId) { + case R.id.action_play_next: + case R.id.action_add_to_current_playing: + case R.id.action_add_to_playlist: + case R.id.action_delete_from_device: + new ListSongsAsyncTask( + getActivity(), + null, + (songs, extra) -> { + if (!songs.isEmpty()) { + SongsMenuHelper.INSTANCE.handleMenuClick( + requireActivity(), songs, itemId); + } + }) + .execute( + new ListSongsAsyncTask.LoadingInfo( + toList(file), AUDIO_FILE_FILTER, getFileComparator())); + return true; + case R.id.action_set_as_start_directory: + PreferenceUtil.INSTANCE.setStartDirectory(file); + Toast.makeText( + getActivity(), + String.format(getString(R.string.new_start_directory), file.getPath()), + Toast.LENGTH_SHORT) + .show(); + return true; + case R.id.action_scan: + new ListPathsAsyncTask(getActivity(), this::scanPaths) + .execute(new ListPathsAsyncTask.LoadingInfo(file, AUDIO_FILE_FILTER)); + return true; } - } - return startFolder; + return false; + }); + } else { + popupMenu.inflate(R.menu.menu_item_file); + popupMenu.setOnMenuItemClickListener( + item -> { + final int itemId = item.getItemId(); + switch (itemId) { + case R.id.action_play_next: + case R.id.action_add_to_current_playing: + case R.id.action_add_to_playlist: + case R.id.action_go_to_album: + case R.id.action_go_to_artist: + case R.id.action_share: + case R.id.action_tag_editor: + case R.id.action_details: + case R.id.action_set_as_ringtone: + case R.id.action_delete_from_device: + new ListSongsAsyncTask( + getActivity(), + null, + (songs, extra) -> + SongMenuHelper.INSTANCE.handleMenuClick( + requireActivity(), songs.get(0), itemId)) + .execute( + new ListSongsAsyncTask.LoadingInfo( + toList(file), AUDIO_FILE_FILTER, getFileComparator())); + return true; + case R.id.action_scan: + new ListPathsAsyncTask(getActivity(), this::scanPaths) + .execute(new ListPathsAsyncTask.LoadingInfo(file, AUDIO_FILE_FILTER)); + return true; + } + return false; + }); } + popupMenu.show(); + } - private static File tryGetCanonicalFile(File file) { - try { - return file.getCanonicalFile(); - } catch (IOException e) { - e.printStackTrace(); - return file; - } - } - - - @NonNull - @Override - public View onCreateView(@NonNull LayoutInflater inflater, - ViewGroup container, - Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_folder, container, false); - initViews(view); - return view; - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - getLibraryViewModel().setPanelState(NowPlayingPanelState.COLLAPSED_WITH); - getMainActivity().setSupportActionBar(toolbar); - getMainActivity().getSupportActionBar().setTitle(null); - setStatusBarColorAuto(view); - setUpAppbarColor(); - setUpBreadCrumbs(); - setUpRecyclerView(); - setUpAdapter(); - setUpTitle(); - } - - private void setUpTitle() { - toolbar.setNavigationOnClickListener(v -> - Navigation.findNavController(v).navigate(R.id.searchFragment, null, getNavOptions()) - ); - int color = ThemeStore.Companion.accentColor(requireContext()); - String hexColor = String.format("#%06X", 0xFFFFFF & color); - Spanned appName = HtmlCompat.fromHtml( - "Retro Music", - HtmlCompat.FROM_HTML_MODE_COMPACT - ); - appNameText.setText(appName); - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - setHasOptionsMenu(true); - if (savedInstanceState == null) { - setCrumb(new BreadCrumbLayout.Crumb(FileUtil.safeGetCanonicalFile(PreferenceUtil.INSTANCE.getStartDirectory())), true); - } else { - breadCrumbs.restoreFromStateWrapper(savedInstanceState.getParcelable(CRUMBS)); - LoaderManager.getInstance(this).initLoader(LOADER_ID, null, this); - } - } - - @Override - public void onPause() { - super.onPause(); - saveScrollPosition(); - } - - @Override - public void onSaveInstanceState(@NonNull Bundle outState) { - super.onSaveInstanceState(outState); - if (breadCrumbs != null) { - outState.putParcelable(CRUMBS, breadCrumbs.getStateWrapper()); - } - } - - @Override - public boolean handleBackPress() { - if (cab != null && cab.isActive()) { - cab.finish(); - return true; - } - if (breadCrumbs != null && breadCrumbs.popHistory()) { - setCrumb(breadCrumbs.lastHistory(), false); - return true; - } - return false; - } - - @NonNull - @Override - public Loader> onCreateLoader(int id, Bundle args) { - return new AsyncFileLoader(this); - } - - @Override - public void onCrumbSelection(BreadCrumbLayout.Crumb crumb, int index) { - setCrumb(crumb, true); - } - - @Override - public void onFileMenuClicked(final File file, @NotNull View view) { - PopupMenu popupMenu = new PopupMenu(getActivity(), view); - if (file.isDirectory()) { - popupMenu.inflate(R.menu.menu_item_directory); - popupMenu.setOnMenuItemClickListener(item -> { - final int itemId = item.getItemId(); - switch (itemId) { - case R.id.action_play_next: - case R.id.action_add_to_current_playing: - case R.id.action_add_to_playlist: - case R.id.action_delete_from_device: - new ListSongsAsyncTask(getActivity(), null, (songs, extra) -> { - if (!songs.isEmpty()) { - SongsMenuHelper.INSTANCE.handleMenuClick(requireActivity(), songs, itemId); - } - }).execute(new ListSongsAsyncTask.LoadingInfo(toList(file), AUDIO_FILE_FILTER, - getFileComparator())); - return true; - case R.id.action_set_as_start_directory: - PreferenceUtil.INSTANCE.setStartDirectory(file); - Toast.makeText(getActivity(), - String.format(getString(R.string.new_start_directory), file.getPath()), - Toast.LENGTH_SHORT).show(); - return true; - case R.id.action_scan: - new ListPathsAsyncTask(getActivity(), this::scanPaths) - .execute(new ListPathsAsyncTask.LoadingInfo(file, AUDIO_FILE_FILTER)); - return true; - } - return false; - }); - } else { - popupMenu.inflate(R.menu.menu_item_file); - popupMenu.setOnMenuItemClickListener(item -> { - final int itemId = item.getItemId(); - switch (itemId) { - case R.id.action_play_next: - case R.id.action_add_to_current_playing: - case R.id.action_add_to_playlist: - case R.id.action_go_to_album: - case R.id.action_go_to_artist: - case R.id.action_share: - case R.id.action_tag_editor: - case R.id.action_details: - case R.id.action_set_as_ringtone: - case R.id.action_delete_from_device: - new ListSongsAsyncTask(getActivity(), null, - (songs, extra) -> SongMenuHelper.INSTANCE.handleMenuClick(requireActivity(), - songs.get(0), itemId)) - .execute(new ListSongsAsyncTask.LoadingInfo(toList(file), AUDIO_FILE_FILTER, - getFileComparator())); - return true; - case R.id.action_scan: - new ListPathsAsyncTask(getActivity(), this::scanPaths) - .execute(new ListPathsAsyncTask.LoadingInfo(file, AUDIO_FILE_FILTER)); - return true; - } - return false; - }); - } - popupMenu.show(); - } - - @Override - public void onFileSelected(@NotNull File file) { - file = tryGetCanonicalFile(file); // important as we compare the path value later - if (file.isDirectory()) { - setCrumb(new BreadCrumbLayout.Crumb(file), true); - } else { - FileFilter fileFilter = pathname -> !pathname.isDirectory() && AUDIO_FILE_FILTER - .accept(pathname); - new ListSongsAsyncTask(getActivity(), file, (songs, extra) -> { + @Override + public void onFileSelected(@NotNull File file) { + file = tryGetCanonicalFile(file); // important as we compare the path value later + if (file.isDirectory()) { + setCrumb(new BreadCrumbLayout.Crumb(file), true); + } else { + FileFilter fileFilter = + pathname -> !pathname.isDirectory() && AUDIO_FILE_FILTER.accept(pathname); + new ListSongsAsyncTask( + getActivity(), + file, + (songs, extra) -> { File file1 = (File) extra; int startIndex = -1; for (int i = 0; i < songs.size(); i++) { - if (file1.getPath().equals(songs.get(i).getData())) { // path is already canonical here - startIndex = i; - break; - } + if (file1 + .getPath() + .equals(songs.get(i).getData())) { // path is already canonical here + startIndex = i; + break; + } } if (startIndex > -1) { - MusicPlayerRemote.openQueue(songs, startIndex, true); + MusicPlayerRemote.openQueue(songs, startIndex, true); } else { - final File finalFile = file1; - Snackbar.make(coordinatorLayout, Html.fromHtml( - String.format(getString(R.string.not_listed_in_media_store), file1.getName())), - Snackbar.LENGTH_LONG) - .setAction(R.string.action_scan, - v -> new ListPathsAsyncTask(requireActivity(), this::scanPaths) - .execute( - new ListPathsAsyncTask.LoadingInfo(finalFile, AUDIO_FILE_FILTER))) - .setActionTextColor(ThemeStore.Companion.accentColor(requireActivity())) - .show(); + final File finalFile = file1; + Snackbar.make( + coordinatorLayout, + Html.fromHtml( + String.format( + getString(R.string.not_listed_in_media_store), file1.getName())), + Snackbar.LENGTH_LONG) + .setAction( + R.string.action_scan, + v -> + new ListPathsAsyncTask(requireActivity(), this::scanPaths) + .execute( + new ListPathsAsyncTask.LoadingInfo( + finalFile, AUDIO_FILE_FILTER))) + .setActionTextColor(ThemeStore.Companion.accentColor(requireActivity())) + .show(); } - }).execute(new ListSongsAsyncTask.LoadingInfo(toList(file.getParentFile()), fileFilter, - getFileComparator())); - } + }) + .execute( + new ListSongsAsyncTask.LoadingInfo( + toList(file.getParentFile()), fileFilter, getFileComparator())); } + } - @Override - public void onLoadFinished(@NonNull Loader> loader, List data) { - updateAdapter(data); - } + @Override + public void onLoadFinished(@NonNull Loader> loader, List data) { + updateAdapter(data); + } - @Override - public void onLoaderReset(@NonNull Loader> loader) { - updateAdapter(new LinkedList()); - } + @Override + public void onLoaderReset(@NonNull Loader> loader) { + updateAdapter(new LinkedList()); + } - @Override - public void onMultipleItemAction(MenuItem item, @NotNull ArrayList files) { - final int itemId = item.getItemId(); - new ListSongsAsyncTask(getActivity(), null, - (songs, extra) -> SongsMenuHelper.INSTANCE.handleMenuClick(requireActivity(), songs, itemId)) - .execute(new ListSongsAsyncTask.LoadingInfo(files, AUDIO_FILE_FILTER, getFileComparator())); - } + @Override + public void onMultipleItemAction(MenuItem item, @NotNull ArrayList files) { + final int itemId = item.getItemId(); + new ListSongsAsyncTask( + getActivity(), + null, + (songs, extra) -> + SongsMenuHelper.INSTANCE.handleMenuClick(requireActivity(), songs, itemId)) + .execute(new ListSongsAsyncTask.LoadingInfo(files, AUDIO_FILE_FILTER, getFileComparator())); + } - @Override - public void onPrepareOptionsMenu(@NonNull Menu menu) { - super.onPrepareOptionsMenu(menu); - ToolbarContentTintHelper.handleOnPrepareOptionsMenu(requireActivity(), toolbar); - } + @Override + public void onPrepareOptionsMenu(@NonNull Menu menu) { + super.onPrepareOptionsMenu(menu); + ToolbarContentTintHelper.handleOnPrepareOptionsMenu(requireActivity(), toolbar); + } - @Override - public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) { - super.onCreateOptionsMenu(menu, inflater); - menu.add(0, R.id.action_scan, 0, R.string.scan_media).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); - menu.add(0, R.id.action_go_to_start_directory, 1, R.string.action_go_to_start_directory).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); - menu.removeItem(R.id.action_grid_size); - menu.removeItem(R.id.action_layout_type); - menu.removeItem(R.id.action_sort_order); - ToolbarContentTintHelper.handleOnCreateOptionsMenu( - requireContext(), - toolbar, - menu, - getToolbarBackgroundColor(toolbar) - ); - } + @Override + public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + menu.add(0, R.id.action_scan, 0, R.string.scan_media) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + menu.add(0, R.id.action_go_to_start_directory, 1, R.string.action_go_to_start_directory) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + menu.removeItem(R.id.action_grid_size); + menu.removeItem(R.id.action_layout_type); + menu.removeItem(R.id.action_sort_order); + ToolbarContentTintHelper.handleOnCreateOptionsMenu( + requireContext(), toolbar, menu, getToolbarBackgroundColor(toolbar)); + } - @Override - public boolean onOptionsItemSelected(@NonNull MenuItem item) { - switch (item.getItemId()) { - case R.id.action_go_to_start_directory: - setCrumb(new BreadCrumbLayout.Crumb( - tryGetCanonicalFile(PreferenceUtil.INSTANCE.getStartDirectory())), true); - return true; - case R.id.action_scan: - BreadCrumbLayout.Crumb crumb = getActiveCrumb(); - if (crumb != null) { - //noinspection Convert2MethodRef - new ListPathsAsyncTask(getActivity(), paths -> scanPaths(paths)) - .execute(new ListPathsAsyncTask.LoadingInfo(crumb.getFile(), - AUDIO_FILE_FILTER)); - } - return true; - } - return super.onOptionsItemSelected(item); - } - - @Override - public void onQueueChanged() { - super.onQueueChanged(); - checkForPadding(); - } - - @Override - public void onServiceConnected() { - super.onServiceConnected(); - checkForPadding(); - } - - @NonNull - @Override - public MaterialCab openCab(int menuRes, @NotNull MaterialCab.Callback callback) { - if (cab != null && cab.isActive()) { - cab.finish(); - } - cab = new MaterialCab(getMainActivity(), R.id.cab_stub) - .setMenu(menuRes) - .setCloseDrawableRes(R.drawable.ic_close) - .setBackgroundColor(RetroColorUtil.shiftBackgroundColorForLightText( - ATHUtil.INSTANCE.resolveColor(requireContext(), R.attr.colorSurface))) - .start(callback); - return cab; - } - - private void checkForPadding() { - final int count = adapter.getItemCount(); - final MarginLayoutParams params = (MarginLayoutParams) coordinatorLayout.getLayoutParams(); - params.bottomMargin = count > 0 && !MusicPlayerRemote.getPlayingQueue().isEmpty() ? DensityUtil - .dip2px(requireContext(), 104f) : DensityUtil.dip2px(requireContext(), 54f); - } - - private void checkIsEmpty() { - emojiText.setText(getEmojiByUnicode(0x1F631)); - if (empty != null) { - empty.setVisibility(adapter == null || adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE); - } - } - - @Nullable - private BreadCrumbLayout.Crumb getActiveCrumb() { - return breadCrumbs != null && breadCrumbs.size() > 0 ? breadCrumbs - .getCrumb(breadCrumbs.getActiveIndex()) : null; - } - - private String getEmojiByUnicode(int unicode) { - return new String(Character.toChars(unicode)); - } - - private Comparator getFileComparator() { - return fileComparator; - } - - private void initViews(View view) { - coordinatorLayout = view.findViewById(R.id.coordinatorLayout); - recyclerView = view.findViewById(R.id.recyclerView); - breadCrumbs = view.findViewById(R.id.breadCrumbs); - empty = view.findViewById(android.R.id.empty); - emojiText = view.findViewById(R.id.emptyEmoji); - toolbar = view.findViewById(R.id.toolbar); - appNameText = view.findViewById(R.id.appNameText); - } - - private void saveScrollPosition() { + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + switch (item.getItemId()) { + case R.id.action_go_to_start_directory: + setCrumb( + new BreadCrumbLayout.Crumb( + tryGetCanonicalFile(PreferenceUtil.INSTANCE.getStartDirectory())), + true); + return true; + case R.id.action_scan: BreadCrumbLayout.Crumb crumb = getActiveCrumb(); if (crumb != null) { - crumb.setScrollPosition( - ((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition()); + //noinspection Convert2MethodRef + new ListPathsAsyncTask(getActivity(), paths -> scanPaths(paths)) + .execute(new ListPathsAsyncTask.LoadingInfo(crumb.getFile(), AUDIO_FILE_FILTER)); } + return true; } + return super.onOptionsItemSelected(item); + } - private void scanPaths(@Nullable String[] toBeScanned) { - if (getActivity() == null) { - return; - } - if (toBeScanned == null || toBeScanned.length < 1) { - Toast.makeText(getActivity(), R.string.nothing_to_scan, Toast.LENGTH_SHORT).show(); - } else { - MediaScannerConnection.scanFile(getActivity().getApplicationContext(), toBeScanned, null, - new UpdateToastMediaScannerCompletionListener(getActivity(), toBeScanned)); - } + @Override + public void onQueueChanged() { + super.onQueueChanged(); + checkForPadding(); + } + + @Override + public void onServiceConnected() { + super.onServiceConnected(); + checkForPadding(); + } + + @NonNull + @Override + public MaterialCab openCab(int menuRes, @NotNull MaterialCab.Callback callback) { + if (cab != null && cab.isActive()) { + cab.finish(); } + cab = + new MaterialCab(getMainActivity(), R.id.cab_stub) + .setMenu(menuRes) + .setCloseDrawableRes(R.drawable.ic_close) + .setBackgroundColor( + RetroColorUtil.shiftBackgroundColorForLightText( + ATHUtil.INSTANCE.resolveColor(requireContext(), R.attr.colorSurface))) + .start(callback); + return cab; + } - private void setCrumb(BreadCrumbLayout.Crumb crumb, boolean addToHistory) { - if (crumb == null) { - return; - } - saveScrollPosition(); - breadCrumbs.setActiveOrAdd(crumb, false); - if (addToHistory) { - breadCrumbs.addHistory(crumb); - } - LoaderManager.getInstance(this).restartLoader(LOADER_ID, null, this); + private void checkForPadding() { + final int count = adapter.getItemCount(); + final MarginLayoutParams params = (MarginLayoutParams) coordinatorLayout.getLayoutParams(); + params.bottomMargin = + count > 0 && !MusicPlayerRemote.getPlayingQueue().isEmpty() + ? DensityUtil.dip2px(requireContext(), 104f) + : DensityUtil.dip2px(requireContext(), 54f); + } + + private void checkIsEmpty() { + emojiText.setText(getEmojiByUnicode(0x1F631)); + if (empty != null) { + empty.setVisibility( + adapter == null || adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE); } + } - private void setUpAdapter() { - adapter = new SongFileAdapter(getMainActivity(), new LinkedList<>(), R.layout.item_list, - this, this); - adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() { - @Override - public void onChanged() { - super.onChanged(); - checkIsEmpty(); - checkForPadding(); - } + @Nullable + private BreadCrumbLayout.Crumb getActiveCrumb() { + return breadCrumbs != null && breadCrumbs.size() > 0 + ? breadCrumbs.getCrumb(breadCrumbs.getActiveIndex()) + : null; + } + + private String getEmojiByUnicode(int unicode) { + return new String(Character.toChars(unicode)); + } + + private Comparator getFileComparator() { + return fileComparator; + } + + private void initViews(View view) { + coordinatorLayout = view.findViewById(R.id.coordinatorLayout); + recyclerView = view.findViewById(R.id.recyclerView); + breadCrumbs = view.findViewById(R.id.breadCrumbs); + empty = view.findViewById(android.R.id.empty); + emojiText = view.findViewById(R.id.emptyEmoji); + toolbar = view.findViewById(R.id.toolbar); + appNameText = view.findViewById(R.id.appNameText); + } + + private void saveScrollPosition() { + BreadCrumbLayout.Crumb crumb = getActiveCrumb(); + if (crumb != null) { + crumb.setScrollPosition( + ((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition()); + } + } + + private void scanPaths(@Nullable String[] toBeScanned) { + if (getActivity() == null) { + return; + } + if (toBeScanned == null || toBeScanned.length < 1) { + Toast.makeText(getActivity(), R.string.nothing_to_scan, Toast.LENGTH_SHORT).show(); + } else { + MediaScannerConnection.scanFile( + getActivity().getApplicationContext(), + toBeScanned, + null, + new UpdateToastMediaScannerCompletionListener(getActivity(), toBeScanned)); + } + } + + private void setCrumb(BreadCrumbLayout.Crumb crumb, boolean addToHistory) { + if (crumb == null) { + return; + } + saveScrollPosition(); + breadCrumbs.setActiveOrAdd(crumb, false); + if (addToHistory) { + breadCrumbs.addHistory(crumb); + } + LoaderManager.getInstance(this).restartLoader(LOADER_ID, null, this); + } + + private void setUpAdapter() { + adapter = + new SongFileAdapter(getMainActivity(), new LinkedList<>(), R.layout.item_list, this, this); + adapter.registerAdapterDataObserver( + new RecyclerView.AdapterDataObserver() { + @Override + public void onChanged() { + super.onChanged(); + checkIsEmpty(); + checkForPadding(); + } }); - recyclerView.setAdapter(adapter); - checkIsEmpty(); + recyclerView.setAdapter(adapter); + checkIsEmpty(); + } + + private void setUpAppbarColor() { + breadCrumbs.setActivatedContentColor( + ATHUtil.INSTANCE.resolveColor(requireContext(), android.R.attr.textColorPrimary)); + breadCrumbs.setDeactivatedContentColor( + ATHUtil.INSTANCE.resolveColor(requireContext(), android.R.attr.textColorSecondary)); + } + + private void setUpBreadCrumbs() { + breadCrumbs.setCallback(this); + } + + private void setUpRecyclerView() { + recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); + FastScroller fastScroller = ThemedFastScroller.INSTANCE.create(recyclerView); + recyclerView.setOnApplyWindowInsetsListener( + new ScrollingViewOnApplyWindowInsetsListener(recyclerView, fastScroller)); + } + + private ArrayList toList(File file) { + ArrayList files = new ArrayList<>(1); + files.add(file); + return files; + } + + private void updateAdapter(@NonNull List files) { + adapter.swapDataSet(files); + BreadCrumbLayout.Crumb crumb = getActiveCrumb(); + if (crumb != null && recyclerView != null) { + ((LinearLayoutManager) recyclerView.getLayoutManager()) + .scrollToPositionWithOffset(crumb.getScrollPosition(), 0); + } + } + + public static class ListPathsAsyncTask + extends ListingFilesDialogAsyncTask { + + private WeakReference onPathsListedCallbackWeakReference; + + public ListPathsAsyncTask(Context context, OnPathsListedCallback callback) { + super(context); + onPathsListedCallbackWeakReference = new WeakReference<>(callback); } - private void setUpAppbarColor() { - breadCrumbs.setActivatedContentColor( - ATHUtil.INSTANCE.resolveColor(requireContext(), android.R.attr.textColorPrimary)); - breadCrumbs.setDeactivatedContentColor( - ATHUtil.INSTANCE.resolveColor(requireContext(), android.R.attr.textColorSecondary)); + @Override + protected String[] doInBackground(LoadingInfo... params) { + try { + if (isCancelled() || checkCallbackReference() == null) { + return null; + } + + LoadingInfo info = params[0]; + + final String[] paths; + + if (info.file.isDirectory()) { + List files = FileUtil.listFilesDeep(info.file, info.fileFilter); + + if (isCancelled() || checkCallbackReference() == null) { + return null; + } + + paths = new String[files.size()]; + for (int i = 0; i < files.size(); i++) { + File f = files.get(i); + paths[i] = FileUtil.safeGetCanonicalPath(f); + + if (isCancelled() || checkCallbackReference() == null) { + return null; + } + } + } else { + paths = new String[1]; + paths[0] = info.file.getPath(); + } + + return paths; + } catch (Exception e) { + e.printStackTrace(); + cancel(false); + return null; + } } - private void setUpBreadCrumbs() { - breadCrumbs.setCallback(this); + @Override + protected void onPostExecute(String[] paths) { + super.onPostExecute(paths); + OnPathsListedCallback callback = checkCallbackReference(); + if (callback != null && paths != null) { + callback.onPathsListed(paths); + } } - private void setUpRecyclerView() { - recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); - FastScroller fastScroller = ThemedFastScroller.INSTANCE.create(recyclerView); - recyclerView.setOnApplyWindowInsetsListener( - new ScrollingViewOnApplyWindowInsetsListener(recyclerView, fastScroller)); + @Override + protected void onPreExecute() { + super.onPreExecute(); + checkCallbackReference(); } - private ArrayList toList(File file) { - ArrayList files = new ArrayList<>(1); - files.add(file); + private OnPathsListedCallback checkCallbackReference() { + OnPathsListedCallback callback = onPathsListedCallbackWeakReference.get(); + if (callback == null) { + cancel(false); + } + return callback; + } + + public interface OnPathsListedCallback { + + void onPathsListed(@NonNull String[] paths); + } + + public static class LoadingInfo { + + public final File file; + + final FileFilter fileFilter; + + public LoadingInfo(File file, FileFilter fileFilter) { + this.file = file; + this.fileFilter = fileFilter; + } + } + } + + private static class AsyncFileLoader extends WrappedAsyncTaskLoader> { + + private WeakReference fragmentWeakReference; + + AsyncFileLoader(FoldersFragment foldersFragment) { + super(foldersFragment.requireActivity()); + fragmentWeakReference = new WeakReference<>(foldersFragment); + } + + @Override + public List loadInBackground() { + FoldersFragment foldersFragment = fragmentWeakReference.get(); + File directory = null; + if (foldersFragment != null) { + BreadCrumbLayout.Crumb crumb = foldersFragment.getActiveCrumb(); + if (crumb != null) { + directory = crumb.getFile(); + } + } + if (directory != null) { + List files = FileUtil.listFiles(directory, AUDIO_FILE_FILTER); + Collections.sort(files, foldersFragment.getFileComparator()); return files; + } else { + return new LinkedList<>(); + } + } + } + + private static class ListSongsAsyncTask + extends ListingFilesDialogAsyncTask> { + + private final Object extra; + private WeakReference callbackWeakReference; + private WeakReference contextWeakReference; + + ListSongsAsyncTask(Context context, Object extra, OnSongsListedCallback callback) { + super(context); + this.extra = extra; + contextWeakReference = new WeakReference<>(context); + callbackWeakReference = new WeakReference<>(callback); } - private void updateAdapter(@NonNull List files) { - adapter.swapDataSet(files); - BreadCrumbLayout.Crumb crumb = getActiveCrumb(); - if (crumb != null && recyclerView != null) { - ((LinearLayoutManager) recyclerView.getLayoutManager()) - .scrollToPositionWithOffset(crumb.getScrollPosition(), 0); + @Override + protected List doInBackground(LoadingInfo... params) { + try { + LoadingInfo info = params[0]; + List files = FileUtil.listFilesDeep(info.files, info.fileFilter); + + if (isCancelled() || checkContextReference() == null || checkCallbackReference() == null) { + return null; } + + Collections.sort(files, info.fileComparator); + + Context context = checkContextReference(); + if (isCancelled() || context == null || checkCallbackReference() == null) { + return null; + } + + return FileUtil.matchFilesWithMediaStore(context, files); + } catch (Exception e) { + e.printStackTrace(); + cancel(false); + return null; + } } - public static class ListPathsAsyncTask extends - ListingFilesDialogAsyncTask { - - private WeakReference onPathsListedCallbackWeakReference; - - public ListPathsAsyncTask(Context context, OnPathsListedCallback callback) { - super(context); - onPathsListedCallbackWeakReference = new WeakReference<>(callback); - } - - @Override - protected String[] doInBackground(LoadingInfo... params) { - try { - if (isCancelled() || checkCallbackReference() == null) { - return null; - } - - LoadingInfo info = params[0]; - - final String[] paths; - - if (info.file.isDirectory()) { - List files = FileUtil.listFilesDeep(info.file, info.fileFilter); - - if (isCancelled() || checkCallbackReference() == null) { - return null; - } - - paths = new String[files.size()]; - for (int i = 0; i < files.size(); i++) { - File f = files.get(i); - paths[i] = FileUtil.safeGetCanonicalPath(f); - - if (isCancelled() || checkCallbackReference() == null) { - return null; - } - } - } else { - paths = new String[1]; - paths[0] = info.file.getPath(); - } - - return paths; - } catch (Exception e) { - e.printStackTrace(); - cancel(false); - return null; - } - } - - @Override - protected void onPostExecute(String[] paths) { - super.onPostExecute(paths); - OnPathsListedCallback callback = checkCallbackReference(); - if (callback != null && paths != null) { - callback.onPathsListed(paths); - } - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - checkCallbackReference(); - } - - private OnPathsListedCallback checkCallbackReference() { - OnPathsListedCallback callback = onPathsListedCallbackWeakReference.get(); - if (callback == null) { - cancel(false); - } - return callback; - } - - public interface OnPathsListedCallback { - - void onPathsListed(@NonNull String[] paths); - } - - public static class LoadingInfo { - - public final File file; - - final FileFilter fileFilter; - - public LoadingInfo(File file, FileFilter fileFilter) { - this.file = file; - this.fileFilter = fileFilter; - } - } + @Override + protected void onPostExecute(List songs) { + super.onPostExecute(songs); + OnSongsListedCallback callback = checkCallbackReference(); + if (songs != null && callback != null) { + callback.onSongsListed(songs, extra); + } } - private static class AsyncFileLoader extends WrappedAsyncTaskLoader> { - - private WeakReference fragmentWeakReference; - - AsyncFileLoader(FoldersFragment foldersFragment) { - super(foldersFragment.requireActivity()); - fragmentWeakReference = new WeakReference<>(foldersFragment); - } - - @Override - public List loadInBackground() { - FoldersFragment foldersFragment = fragmentWeakReference.get(); - File directory = null; - if (foldersFragment != null) { - BreadCrumbLayout.Crumb crumb = foldersFragment.getActiveCrumb(); - if (crumb != null) { - directory = crumb.getFile(); - } - } - if (directory != null) { - List files = FileUtil.listFiles(directory, AUDIO_FILE_FILTER); - Collections.sort(files, foldersFragment.getFileComparator()); - return files; - } else { - return new LinkedList<>(); - } - } + @Override + protected void onPreExecute() { + super.onPreExecute(); + checkCallbackReference(); + checkContextReference(); } - private static class ListSongsAsyncTask - extends ListingFilesDialogAsyncTask> { - - private final Object extra; - private WeakReference callbackWeakReference; - private WeakReference contextWeakReference; - - ListSongsAsyncTask(Context context, Object extra, OnSongsListedCallback callback) { - super(context); - this.extra = extra; - contextWeakReference = new WeakReference<>(context); - callbackWeakReference = new WeakReference<>(callback); - } - - @Override - protected List doInBackground(LoadingInfo... params) { - try { - LoadingInfo info = params[0]; - List files = FileUtil.listFilesDeep(info.files, info.fileFilter); - - if (isCancelled() || checkContextReference() == null - || checkCallbackReference() == null) { - return null; - } - - Collections.sort(files, info.fileComparator); - - Context context = checkContextReference(); - if (isCancelled() || context == null || checkCallbackReference() == null) { - return null; - } - - return FileUtil.matchFilesWithMediaStore(context, files); - } catch (Exception e) { - e.printStackTrace(); - cancel(false); - return null; - } - } - - @Override - protected void onPostExecute(List songs) { - super.onPostExecute(songs); - OnSongsListedCallback callback = checkCallbackReference(); - if (songs != null && callback != null) { - callback.onSongsListed(songs, extra); - } - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - checkCallbackReference(); - checkContextReference(); - } - - private OnSongsListedCallback checkCallbackReference() { - OnSongsListedCallback callback = callbackWeakReference.get(); - if (callback == null) { - cancel(false); - } - return callback; - } - - private Context checkContextReference() { - Context context = contextWeakReference.get(); - if (context == null) { - cancel(false); - } - return context; - } - - public interface OnSongsListedCallback { - - void onSongsListed(@NonNull List songs, Object extra); - } - - static class LoadingInfo { - - final Comparator fileComparator; - - final FileFilter fileFilter; - - final List files; - - LoadingInfo(@NonNull List files, @NonNull FileFilter fileFilter, - @NonNull Comparator fileComparator) { - this.fileComparator = fileComparator; - this.fileFilter = fileFilter; - this.files = files; - } - } + private OnSongsListedCallback checkCallbackReference() { + OnSongsListedCallback callback = callbackWeakReference.get(); + if (callback == null) { + cancel(false); + } + return callback; } - private static abstract class ListingFilesDialogAsyncTask extends - DialogAsyncTask { - - ListingFilesDialogAsyncTask(Context context) { - super(context); - } - - public ListingFilesDialogAsyncTask(Context context, int showDelay) { - super(context, showDelay); - } - - @Override - protected Dialog createDialog(@NonNull Context context) { - return new MaterialAlertDialogBuilder(context) - .setTitle(R.string.listing_files) - .setCancelable(false) - .setView(R.layout.loading) - .setOnCancelListener(dialog -> cancel(false)) - .setOnDismissListener(dialog -> cancel(false)) - .create(); - } + private Context checkContextReference() { + Context context = contextWeakReference.get(); + if (context == null) { + cancel(false); + } + return context; } + + public interface OnSongsListedCallback { + + void onSongsListed(@NonNull List songs, Object extra); + } + + static class LoadingInfo { + + final Comparator fileComparator; + + final FileFilter fileFilter; + + final List files; + + LoadingInfo( + @NonNull List files, + @NonNull FileFilter fileFilter, + @NonNull Comparator fileComparator) { + this.fileComparator = fileComparator; + this.fileFilter = fileFilter; + this.files = files; + } + } + } + + private abstract static class ListingFilesDialogAsyncTask + extends DialogAsyncTask { + + ListingFilesDialogAsyncTask(Context context) { + super(context); + } + + public ListingFilesDialogAsyncTask(Context context, int showDelay) { + super(context, showDelay); + } + + @Override + protected Dialog createDialog(@NonNull Context context) { + return new MaterialAlertDialogBuilder(context) + .setTitle(R.string.listing_files) + .setCancelable(false) + .setView(R.layout.loading) + .setOnCancelListener(dialog -> cancel(false)) + .setOnDismissListener(dialog -> cancel(false)) + .create(); + } + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/genres/GenreDetailsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/genres/GenreDetailsFragment.kt index cca506d1..c2190eb5 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/genres/GenreDetailsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/genres/GenreDetailsFragment.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.fragments.genres import android.os.Bundle @@ -17,10 +31,10 @@ import code.name.monkey.retromusic.helper.menu.GenreMenuHelper import code.name.monkey.retromusic.model.Genre import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.state.NowPlayingPanelState +import java.util.* import kotlinx.android.synthetic.main.fragment_playlist_detail.* import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf -import java.util.* class GenreDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playlist_detail) { private val arguments by navArgs() @@ -89,4 +103,4 @@ class GenreDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playlist_ override fun onOptionsItemSelected(item: MenuItem): Boolean { return GenreMenuHelper.handleMenuClick(requireActivity(), genre, item) } -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/genres/GenreDetailsViewModel.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/genres/GenreDetailsViewModel.kt index ceb419d8..43f941df 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/genres/GenreDetailsViewModel.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/genres/GenreDetailsViewModel.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.fragments.genres import androidx.lifecycle.LiveData @@ -46,4 +60,4 @@ class GenreDetailsViewModel( override fun onPlayStateChanged() {} override fun onRepeatModeChanged() {} override fun onShuffleModeChanged() {} -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/genres/GenresFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/genres/GenresFragment.kt index 3574c8a9..e7c5041a 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/genres/GenresFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/genres/GenresFragment.kt @@ -1,17 +1,17 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.fragments.genres import android.os.Bundle @@ -33,7 +33,6 @@ class GenresFragment : AbsRecyclerViewFragment> = realRepository.playlistSongs(playlist.playlistEntity.playListId) - override fun onMediaStoreChanged() { /*if (playlist !is AbsCustomPlaylist) { // Playlist deleted diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistsFragment.kt index 9744cb8e..27592935 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistsFragment.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.fragments.playlists import android.os.Bundle diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/queue/PlayingQueueFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/queue/PlayingQueueFragment.kt index 6376420c..0af0c72c 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/queue/PlayingQueueFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/queue/PlayingQueueFragment.kt @@ -1,15 +1,16 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.fragments.queue @@ -139,4 +140,4 @@ class PlayingQueueFragment : AbsRecyclerViewFragment(), ICabHolder { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/AlbumGlideRequest.java b/app/src/main/java/code/name/monkey/retromusic/glide/AlbumGlideRequest.java index 82e65da2..2158addc 100644 --- a/app/src/main/java/code/name/monkey/retromusic/glide/AlbumGlideRequest.java +++ b/app/src/main/java/code/name/monkey/retromusic/glide/AlbumGlideRequest.java @@ -2,9 +2,14 @@ package code.name.monkey.retromusic.glide; import android.content.Context; import android.graphics.Bitmap; - import androidx.annotation.NonNull; - +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.glide.audiocover.AudioFileCover; +import code.name.monkey.retromusic.glide.palette.BitmapPaletteTranscoder; +import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.util.MusicUtil; +import code.name.monkey.retromusic.util.PreferenceUtil; import com.bumptech.glide.BitmapRequestBuilder; import com.bumptech.glide.DrawableRequestBuilder; import com.bumptech.glide.DrawableTypeRequest; @@ -14,120 +19,112 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.load.resource.drawable.GlideDrawable; import com.bumptech.glide.signature.MediaStoreSignature; -import code.name.monkey.retromusic.R; -import code.name.monkey.retromusic.glide.audiocover.AudioFileCover; -import code.name.monkey.retromusic.glide.palette.BitmapPaletteTranscoder; -import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper; -import code.name.monkey.retromusic.model.Song; -import code.name.monkey.retromusic.util.MusicUtil; -import code.name.monkey.retromusic.util.PreferenceUtil; - public class AlbumGlideRequest { - private static final DiskCacheStrategy DEFAULT_DISK_CACHE_STRATEGY = DiskCacheStrategy.NONE; - private static final int DEFAULT_ERROR_IMAGE = R.drawable.default_album_art; - private static final int DEFAULT_ANIMATION = android.R.anim.fade_in; + private static final DiskCacheStrategy DEFAULT_DISK_CACHE_STRATEGY = DiskCacheStrategy.NONE; + private static final int DEFAULT_ERROR_IMAGE = R.drawable.default_album_art; + private static final int DEFAULT_ANIMATION = android.R.anim.fade_in; - @NonNull - private static DrawableTypeRequest createBaseRequest(@NonNull RequestManager requestManager, - @NonNull Song song, - boolean ignoreMediaStore) { - if (ignoreMediaStore) { - return requestManager.load(new AudioFileCover(song.getData())); - } else { - return requestManager.loadFromMediaStore(MusicUtil.INSTANCE.getMediaStoreAlbumCoverUri(song.getAlbumId())); - } + @NonNull + private static DrawableTypeRequest createBaseRequest( + @NonNull RequestManager requestManager, @NonNull Song song, boolean ignoreMediaStore) { + if (ignoreMediaStore) { + return requestManager.load(new AudioFileCover(song.getData())); + } else { + return requestManager.loadFromMediaStore( + MusicUtil.INSTANCE.getMediaStoreAlbumCoverUri(song.getAlbumId())); + } + } + + @NonNull + private static Key createSignature(@NonNull Song song) { + return new MediaStoreSignature("", song.getDateModified(), 0); + } + + public static class Builder { + final RequestManager requestManager; + final Song song; + boolean ignoreMediaStore; + + private Builder(@NonNull RequestManager requestManager, Song song) { + this.requestManager = requestManager; + this.song = song; } @NonNull - private static Key createSignature(@NonNull Song song) { - return new MediaStoreSignature("", song.getDateModified(), 0); + public static Builder from(@NonNull RequestManager requestManager, Song song) { + return new Builder(requestManager, song); } - public static class Builder { - final RequestManager requestManager; - final Song song; - boolean ignoreMediaStore; - - private Builder(@NonNull RequestManager requestManager, Song song) { - this.requestManager = requestManager; - this.song = song; - } - - @NonNull - public static Builder from(@NonNull RequestManager requestManager, Song song) { - return new Builder(requestManager, song); - } - - @NonNull - public PaletteBuilder generatePalette(@NonNull Context context) { - return new PaletteBuilder(this, context); - } - - @NonNull - public BitmapBuilder asBitmap() { - return new BitmapBuilder(this); - } - - @NonNull - public Builder checkIgnoreMediaStore() { - return ignoreMediaStore(PreferenceUtil.INSTANCE.isIgnoreMediaStoreArtwork()); - } - - @NonNull - public Builder ignoreMediaStore(boolean ignoreMediaStore) { - this.ignoreMediaStore = ignoreMediaStore; - return this; - } - - @NonNull - public DrawableRequestBuilder build() { - //noinspection unchecked - return createBaseRequest(requestManager, song, ignoreMediaStore) - .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) - .error(DEFAULT_ERROR_IMAGE) - .animate(DEFAULT_ANIMATION) - .signature(createSignature(song)); - } + @NonNull + public PaletteBuilder generatePalette(@NonNull Context context) { + return new PaletteBuilder(this, context); } - public static class BitmapBuilder { - private final Builder builder; - - BitmapBuilder(Builder builder) { - this.builder = builder; - } - - public BitmapRequestBuilder build() { - //noinspection unchecked - return createBaseRequest(builder.requestManager, builder.song, builder.ignoreMediaStore) - .asBitmap() - .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) - .error(DEFAULT_ERROR_IMAGE) - .animate(DEFAULT_ANIMATION) - .dontTransform() - .signature(createSignature(builder.song)); - } + @NonNull + public BitmapBuilder asBitmap() { + return new BitmapBuilder(this); } - public static class PaletteBuilder { - private final Context context; - private final Builder builder; - - PaletteBuilder(Builder builder, Context context) { - this.builder = builder; - this.context = context; - } - - public BitmapRequestBuilder build() { - - //noinspection unchecked - return createBaseRequest(builder.requestManager, builder.song, builder.ignoreMediaStore) - .asBitmap() - .transcode(new BitmapPaletteTranscoder(context), BitmapPaletteWrapper.class) - .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) - .error(DEFAULT_ERROR_IMAGE) - .animate(DEFAULT_ANIMATION) - .signature(createSignature(builder.song)); - } + @NonNull + public Builder checkIgnoreMediaStore() { + return ignoreMediaStore(PreferenceUtil.INSTANCE.isIgnoreMediaStoreArtwork()); } -} \ No newline at end of file + + @NonNull + public Builder ignoreMediaStore(boolean ignoreMediaStore) { + this.ignoreMediaStore = ignoreMediaStore; + return this; + } + + @NonNull + public DrawableRequestBuilder build() { + //noinspection unchecked + return createBaseRequest(requestManager, song, ignoreMediaStore) + .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) + .error(DEFAULT_ERROR_IMAGE) + .animate(DEFAULT_ANIMATION) + .signature(createSignature(song)); + } + } + + public static class BitmapBuilder { + private final Builder builder; + + BitmapBuilder(Builder builder) { + this.builder = builder; + } + + public BitmapRequestBuilder build() { + //noinspection unchecked + return createBaseRequest(builder.requestManager, builder.song, builder.ignoreMediaStore) + .asBitmap() + .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) + .error(DEFAULT_ERROR_IMAGE) + .animate(DEFAULT_ANIMATION) + .dontTransform() + .signature(createSignature(builder.song)); + } + } + + public static class PaletteBuilder { + private final Context context; + private final Builder builder; + + PaletteBuilder(Builder builder, Context context) { + this.builder = builder; + this.context = context; + } + + public BitmapRequestBuilder build() { + + //noinspection unchecked + return createBaseRequest(builder.requestManager, builder.song, builder.ignoreMediaStore) + .asBitmap() + .transcode(new BitmapPaletteTranscoder(context), BitmapPaletteWrapper.class) + .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) + .error(DEFAULT_ERROR_IMAGE) + .animate(DEFAULT_ANIMATION) + .signature(createSignature(builder.song)); + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/ArtistGlideRequest.java b/app/src/main/java/code/name/monkey/retromusic/glide/ArtistGlideRequest.java index c11ffcc2..b04854fa 100644 --- a/app/src/main/java/code/name/monkey/retromusic/glide/ArtistGlideRequest.java +++ b/app/src/main/java/code/name/monkey/retromusic/glide/ArtistGlideRequest.java @@ -17,20 +17,8 @@ package code.name.monkey.retromusic.glide; import android.content.Context; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; - import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; - -import com.bumptech.glide.BitmapRequestBuilder; -import com.bumptech.glide.DrawableRequestBuilder; -import com.bumptech.glide.DrawableTypeRequest; -import com.bumptech.glide.Priority; -import com.bumptech.glide.RequestManager; -import com.bumptech.glide.load.Key; -import com.bumptech.glide.load.engine.DiskCacheStrategy; -import com.bumptech.glide.load.resource.drawable.GlideDrawable; -import com.bumptech.glide.request.target.Target; - import code.name.monkey.appthemehelper.ThemeStore; import code.name.monkey.appthemehelper.util.TintHelper; import code.name.monkey.retromusic.App; @@ -41,128 +29,143 @@ import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper; import code.name.monkey.retromusic.model.Artist; import code.name.monkey.retromusic.util.ArtistSignatureUtil; import code.name.monkey.retromusic.util.CustomArtistImageUtil; - +import com.bumptech.glide.BitmapRequestBuilder; +import com.bumptech.glide.DrawableRequestBuilder; +import com.bumptech.glide.DrawableTypeRequest; +import com.bumptech.glide.Priority; +import com.bumptech.glide.RequestManager; +import com.bumptech.glide.load.Key; +import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.bumptech.glide.load.resource.drawable.GlideDrawable; +import com.bumptech.glide.request.target.Target; public class ArtistGlideRequest { - private static final int DEFAULT_ANIMATION = android.R.anim.fade_in; + private static final int DEFAULT_ANIMATION = android.R.anim.fade_in; - private static final DiskCacheStrategy DEFAULT_DISK_CACHE_STRATEGY = DiskCacheStrategy.SOURCE; + private static final DiskCacheStrategy DEFAULT_DISK_CACHE_STRATEGY = DiskCacheStrategy.SOURCE; - private static final int DEFAULT_ERROR_IMAGE = R.drawable.default_artist_art; + private static final int DEFAULT_ERROR_IMAGE = R.drawable.default_artist_art; - @NonNull - private static Key createSignature(@NonNull Artist artist) { - return ArtistSignatureUtil.getInstance(App.Companion.getContext()).getArtistSignature(artist.getName()); + @NonNull + private static Key createSignature(@NonNull Artist artist) { + return ArtistSignatureUtil.getInstance(App.Companion.getContext()) + .getArtistSignature(artist.getName()); + } + + @NonNull + private static DrawableTypeRequest createBaseRequest( + @NonNull RequestManager requestManager, + @NonNull Artist artist, + boolean noCustomImage, + boolean forceDownload) { + boolean hasCustomImage = + CustomArtistImageUtil.Companion.getInstance(App.Companion.getContext()) + .hasCustomArtistImage(artist); + if (noCustomImage || !hasCustomImage) { + return requestManager.load(new ArtistImage(artist)); + } else { + return requestManager.load(CustomArtistImageUtil.getFile(artist)); + } + } + + public static class Builder { + final Artist artist; + final RequestManager requestManager; + private Drawable error; + private boolean forceDownload; + private boolean noCustomImage; + + private Builder(@NonNull RequestManager requestManager, Artist artist) { + this.requestManager = requestManager; + this.artist = artist; + error = + TintHelper.createTintedDrawable( + ContextCompat.getDrawable(App.Companion.getContext(), R.drawable.default_artist_art), + ThemeStore.Companion.accentColor(App.Companion.getContext())); } - @NonNull - private static DrawableTypeRequest createBaseRequest(@NonNull RequestManager requestManager, - @NonNull Artist artist, - boolean noCustomImage, boolean forceDownload) { - boolean hasCustomImage = CustomArtistImageUtil.Companion.getInstance(App.Companion.getContext()) - .hasCustomArtistImage(artist); - if (noCustomImage || !hasCustomImage) { - return requestManager.load(new ArtistImage(artist)); - } else { - return requestManager.load(CustomArtistImageUtil.getFile(artist)); - } + public static Builder from(@NonNull RequestManager requestManager, Artist artist) { + return new Builder(requestManager, artist); } - public static class Builder { - final Artist artist; - final RequestManager requestManager; - private Drawable error; - private boolean forceDownload; - private boolean noCustomImage; - - private Builder(@NonNull RequestManager requestManager, Artist artist) { - this.requestManager = requestManager; - this.artist = artist; - error = TintHelper.createTintedDrawable(ContextCompat.getDrawable(App.Companion.getContext(), R.drawable.default_artist_art), ThemeStore.Companion.accentColor(App.Companion.getContext())); - } - - public static Builder from(@NonNull RequestManager requestManager, Artist artist) { - return new Builder(requestManager, artist); - } - - public BitmapBuilder asBitmap() { - return new BitmapBuilder(this); - } - - public DrawableRequestBuilder build() { - //noinspection unchecked - return createBaseRequest(requestManager, artist, noCustomImage, forceDownload) - .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) - .animate(DEFAULT_ANIMATION) - .error(DEFAULT_ERROR_IMAGE) - .priority(Priority.LOW) - .override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) - .dontTransform() - .signature(createSignature(artist)); - } - - public Builder forceDownload(boolean forceDownload) { - this.forceDownload = forceDownload; - return this; - } - - public PaletteBuilder generatePalette(Context context) { - return new PaletteBuilder(this, context); - } - - public Builder noCustomImage(boolean noCustomImage) { - this.noCustomImage = noCustomImage; - return this; - } + public BitmapBuilder asBitmap() { + return new BitmapBuilder(this); } - public static class BitmapBuilder { - - private final Builder builder; - - BitmapBuilder(Builder builder) { - this.builder = builder; - } - - public BitmapRequestBuilder build() { - //noinspection unchecked - return createBaseRequest(builder.requestManager, builder.artist, builder.noCustomImage, - builder.forceDownload) - .asBitmap() - .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) - .animate(DEFAULT_ANIMATION) - .error(DEFAULT_ERROR_IMAGE) - .priority(Priority.LOW) - .override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) - .dontTransform() - .signature(createSignature(builder.artist)); - } + public DrawableRequestBuilder build() { + //noinspection unchecked + return createBaseRequest(requestManager, artist, noCustomImage, forceDownload) + .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) + .animate(DEFAULT_ANIMATION) + .error(DEFAULT_ERROR_IMAGE) + .priority(Priority.LOW) + .override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) + .dontTransform() + .signature(createSignature(artist)); } - public static class PaletteBuilder { - - final Context context; - - private final Builder builder; - - PaletteBuilder(Builder builder, Context context) { - this.builder = builder; - this.context = context; - } - - public BitmapRequestBuilder build() { - //noinspection unchecked - return createBaseRequest(builder.requestManager, builder.artist, builder.noCustomImage, - builder.forceDownload) - .asBitmap() - .transcode(new BitmapPaletteTranscoder(context), BitmapPaletteWrapper.class) - .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) - .error(DEFAULT_ERROR_IMAGE) - .animate(DEFAULT_ANIMATION) - .priority(Priority.LOW) - .override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) - .dontTransform() - .signature(createSignature(builder.artist)); - } + public Builder forceDownload(boolean forceDownload) { + this.forceDownload = forceDownload; + return this; } -} \ No newline at end of file + + public PaletteBuilder generatePalette(Context context) { + return new PaletteBuilder(this, context); + } + + public Builder noCustomImage(boolean noCustomImage) { + this.noCustomImage = noCustomImage; + return this; + } + } + + public static class BitmapBuilder { + + private final Builder builder; + + BitmapBuilder(Builder builder) { + this.builder = builder; + } + + public BitmapRequestBuilder build() { + //noinspection unchecked + return createBaseRequest( + builder.requestManager, builder.artist, builder.noCustomImage, builder.forceDownload) + .asBitmap() + .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) + .animate(DEFAULT_ANIMATION) + .error(DEFAULT_ERROR_IMAGE) + .priority(Priority.LOW) + .override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) + .dontTransform() + .signature(createSignature(builder.artist)); + } + } + + public static class PaletteBuilder { + + final Context context; + + private final Builder builder; + + PaletteBuilder(Builder builder, Context context) { + this.builder = builder; + this.context = context; + } + + public BitmapRequestBuilder build() { + //noinspection unchecked + return createBaseRequest( + builder.requestManager, builder.artist, builder.noCustomImage, builder.forceDownload) + .asBitmap() + .transcode(new BitmapPaletteTranscoder(context), BitmapPaletteWrapper.class) + .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) + .error(DEFAULT_ERROR_IMAGE) + .animate(DEFAULT_ANIMATION) + .priority(Priority.LOW) + .override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) + .dontTransform() + .signature(createSignature(builder.artist)); + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/BlurTransformation.kt b/app/src/main/java/code/name/monkey/retromusic/glide/BlurTransformation.kt index 0c2af3db..0996b553 100644 --- a/app/src/main/java/code/name/monkey/retromusic/glide/BlurTransformation.kt +++ b/app/src/main/java/code/name/monkey/retromusic/glide/BlurTransformation.kt @@ -1,17 +1,17 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.glide import android.content.Context @@ -27,7 +27,6 @@ import code.name.monkey.retromusic.util.ImageUtil import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool import com.bumptech.glide.load.resource.bitmap.BitmapTransformation - class BlurTransformation : BitmapTransformation { private var context: Context? = null @@ -137,12 +136,10 @@ class BlurTransformation : BitmapTransformation { rs.destroy() return out - } catch (e: RSRuntimeException) { // on some devices RenderScript.create() throws: android.support.v8.renderscript.RSRuntimeException: Error loading libRSSupport library if (BuildConfig.DEBUG) e.printStackTrace() } - } return StackBlur.blur(out, blurRadius) @@ -155,4 +152,4 @@ class BlurTransformation : BitmapTransformation { companion object { val DEFAULT_BLUR_RADIUS = 5f } -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/ProfileBannerGlideRequest.java b/app/src/main/java/code/name/monkey/retromusic/glide/ProfileBannerGlideRequest.java index 27ec226d..8a27576a 100644 --- a/app/src/main/java/code/name/monkey/retromusic/glide/ProfileBannerGlideRequest.java +++ b/app/src/main/java/code/name/monkey/retromusic/glide/ProfileBannerGlideRequest.java @@ -1,79 +1,76 @@ package code.name.monkey.retromusic.glide; +import static code.name.monkey.retromusic.Constants.USER_BANNER; + import android.graphics.Bitmap; - import androidx.annotation.NonNull; - +import code.name.monkey.retromusic.App; +import code.name.monkey.retromusic.R; import com.bumptech.glide.BitmapRequestBuilder; import com.bumptech.glide.BitmapTypeRequest; import com.bumptech.glide.RequestManager; import com.bumptech.glide.load.Key; import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.signature.MediaStoreSignature; - import java.io.File; -import code.name.monkey.retromusic.App; -import code.name.monkey.retromusic.R; - -import static code.name.monkey.retromusic.Constants.USER_BANNER; - public class ProfileBannerGlideRequest { - private static final DiskCacheStrategy DEFAULT_DISK_CACHE_STRATEGY = DiskCacheStrategy.NONE; - private static final int DEFAULT_ERROR_IMAGE = R.drawable.material_design_default; - private static final int DEFAULT_ANIMATION = android.R.anim.fade_in; + private static final DiskCacheStrategy DEFAULT_DISK_CACHE_STRATEGY = DiskCacheStrategy.NONE; + private static final int DEFAULT_ERROR_IMAGE = R.drawable.material_design_default; + private static final int DEFAULT_ANIMATION = android.R.anim.fade_in; - public static File getBannerModel() { - File dir = App.Companion.getContext().getFilesDir(); - return new File(dir, USER_BANNER); + public static File getBannerModel() { + File dir = App.Companion.getContext().getFilesDir(); + return new File(dir, USER_BANNER); + } + + private static BitmapTypeRequest createBaseRequest( + RequestManager requestManager, File profile) { + return requestManager.load(profile).asBitmap(); + } + + private static Key createSignature(File file) { + return new MediaStoreSignature("", file.lastModified(), 0); + } + + public static class Builder { + private RequestManager requestManager; + private File profile; + + private Builder(RequestManager requestManager, File profile) { + this.requestManager = requestManager; + this.profile = profile; } - private static BitmapTypeRequest createBaseRequest(RequestManager requestManager, File profile) { - return requestManager.load(profile).asBitmap(); + public static Builder from(@NonNull RequestManager requestManager, File profile) { + return new Builder(requestManager, profile); } - private static Key createSignature(File file) { - return new MediaStoreSignature("", file.lastModified(), 0); + @NonNull + public BitmapRequestBuilder build() { + //noinspection unchecked + return createBaseRequest(requestManager, profile) + .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) + .placeholder(DEFAULT_ERROR_IMAGE) + .animate(DEFAULT_ANIMATION) + .signature(createSignature(profile)); + } + } + + public static class BitmapBuilder { + private final Builder builder; + + BitmapBuilder(Builder builder) { + this.builder = builder; } - public static class Builder { - private RequestManager requestManager; - private File profile; - - private Builder(RequestManager requestManager, File profile) { - this.requestManager = requestManager; - this.profile = profile; - } - - public static Builder from(@NonNull RequestManager requestManager, File profile) { - return new Builder(requestManager, profile); - } - - @NonNull - public BitmapRequestBuilder build() { - //noinspection unchecked - return createBaseRequest(requestManager, profile) - .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) - .placeholder(DEFAULT_ERROR_IMAGE) - .animate(DEFAULT_ANIMATION) - .signature(createSignature(profile)); - } - } - - public static class BitmapBuilder { - private final Builder builder; - - BitmapBuilder(Builder builder) { - this.builder = builder; - } - - public BitmapRequestBuilder build() { - //noinspection unchecked - return createBaseRequest(builder.requestManager, builder.profile) - .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) - .error(DEFAULT_ERROR_IMAGE) - .animate(DEFAULT_ANIMATION) - .signature(createSignature(builder.profile)); - } + public BitmapRequestBuilder build() { + //noinspection unchecked + return createBaseRequest(builder.requestManager, builder.profile) + .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) + .error(DEFAULT_ERROR_IMAGE) + .animate(DEFAULT_ANIMATION) + .signature(createSignature(builder.profile)); } + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/RetroMusicColoredTarget.kt b/app/src/main/java/code/name/monkey/retromusic/glide/RetroMusicColoredTarget.kt index c6f74097..023dc396 100644 --- a/app/src/main/java/code/name/monkey/retromusic/glide/RetroMusicColoredTarget.kt +++ b/app/src/main/java/code/name/monkey/retromusic/glide/RetroMusicColoredTarget.kt @@ -1,17 +1,17 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.glide import android.graphics.drawable.Drawable @@ -24,7 +24,6 @@ import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper import code.name.monkey.retromusic.util.color.MediaNotificationProcessor import com.bumptech.glide.request.animation.GlideAnimation - abstract class RetroMusicColoredTarget(view: ImageView) : BitmapPaletteTarget(view) { protected val defaultFooterColor: Int diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/RetroMusicGlideModule.kt b/app/src/main/java/code/name/monkey/retromusic/glide/RetroMusicGlideModule.kt index 078871cd..74fbe565 100644 --- a/app/src/main/java/code/name/monkey/retromusic/glide/RetroMusicGlideModule.kt +++ b/app/src/main/java/code/name/monkey/retromusic/glide/RetroMusicGlideModule.kt @@ -1,17 +1,17 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.glide import android.content.Context diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/SingleColorTarget.kt b/app/src/main/java/code/name/monkey/retromusic/glide/SingleColorTarget.kt index d4b2db1c..dbe27624 100644 --- a/app/src/main/java/code/name/monkey/retromusic/glide/SingleColorTarget.kt +++ b/app/src/main/java/code/name/monkey/retromusic/glide/SingleColorTarget.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.glide import android.graphics.drawable.Drawable @@ -9,7 +23,6 @@ import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper import code.name.monkey.retromusic.util.ColorUtil import com.bumptech.glide.request.animation.GlideAnimation - abstract class SingleColorTarget(view: ImageView) : BitmapPaletteTarget(view) { protected val defaultFooterColor: Int @@ -36,4 +49,4 @@ abstract class SingleColorTarget(view: ImageView) : BitmapPaletteTarget(view) { ) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/SongGlideRequest.java b/app/src/main/java/code/name/monkey/retromusic/glide/SongGlideRequest.java index c1ff5df9..5f424e4a 100644 --- a/app/src/main/java/code/name/monkey/retromusic/glide/SongGlideRequest.java +++ b/app/src/main/java/code/name/monkey/retromusic/glide/SongGlideRequest.java @@ -16,9 +16,14 @@ package code.name.monkey.retromusic.glide; import android.content.Context; import android.graphics.Bitmap; - import androidx.annotation.NonNull; - +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.glide.audiocover.AudioFileCover; +import code.name.monkey.retromusic.glide.palette.BitmapPaletteTranscoder; +import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.util.MusicUtil; +import code.name.monkey.retromusic.util.PreferenceUtil; import com.bumptech.glide.BitmapRequestBuilder; import com.bumptech.glide.DrawableRequestBuilder; import com.bumptech.glide.DrawableTypeRequest; @@ -28,122 +33,112 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.load.resource.drawable.GlideDrawable; import com.bumptech.glide.signature.MediaStoreSignature; -import code.name.monkey.retromusic.R; -import code.name.monkey.retromusic.glide.audiocover.AudioFileCover; -import code.name.monkey.retromusic.glide.palette.BitmapPaletteTranscoder; -import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper; -import code.name.monkey.retromusic.model.Song; -import code.name.monkey.retromusic.util.MusicUtil; -import code.name.monkey.retromusic.util.PreferenceUtil; - -/** - * Created by hemanths on 2019-09-15. - */ +/** Created by hemanths on 2019-09-15. */ public class SongGlideRequest { - private static final DiskCacheStrategy DEFAULT_DISK_CACHE_STRATEGY = DiskCacheStrategy.NONE; - private static final int DEFAULT_ERROR_IMAGE = R.drawable.default_audio_art; - private static final int DEFAULT_ANIMATION = android.R.anim.fade_in; + private static final DiskCacheStrategy DEFAULT_DISK_CACHE_STRATEGY = DiskCacheStrategy.NONE; + private static final int DEFAULT_ERROR_IMAGE = R.drawable.default_audio_art; + private static final int DEFAULT_ANIMATION = android.R.anim.fade_in; - @NonNull - private static DrawableTypeRequest createBaseRequest(@NonNull RequestManager requestManager, - @NonNull Song song, - boolean ignoreMediaStore) { - if (ignoreMediaStore) { - return requestManager.load(new AudioFileCover(song.getData())); - } else { - return requestManager.loadFromMediaStore(MusicUtil.INSTANCE.getMediaStoreAlbumCoverUri(song.getAlbumId())); - } + @NonNull + private static DrawableTypeRequest createBaseRequest( + @NonNull RequestManager requestManager, @NonNull Song song, boolean ignoreMediaStore) { + if (ignoreMediaStore) { + return requestManager.load(new AudioFileCover(song.getData())); + } else { + return requestManager.loadFromMediaStore( + MusicUtil.INSTANCE.getMediaStoreAlbumCoverUri(song.getAlbumId())); + } + } + + @NonNull + private static Key createSignature(@NonNull Song song) { + return new MediaStoreSignature("", song.getDateModified(), 0); + } + + public static class Builder { + final RequestManager requestManager; + final Song song; + boolean ignoreMediaStore; + + private Builder(@NonNull RequestManager requestManager, Song song) { + this.requestManager = requestManager; + this.song = song; } @NonNull - private static Key createSignature(@NonNull Song song) { - return new MediaStoreSignature("", song.getDateModified(), 0); + public static Builder from(@NonNull RequestManager requestManager, Song song) { + return new Builder(requestManager, song); } - public static class Builder { - final RequestManager requestManager; - final Song song; - boolean ignoreMediaStore; - - private Builder(@NonNull RequestManager requestManager, Song song) { - this.requestManager = requestManager; - this.song = song; - } - - @NonNull - public static Builder from(@NonNull RequestManager requestManager, Song song) { - return new Builder(requestManager, song); - } - - @NonNull - public PaletteBuilder generatePalette(@NonNull Context context) { - return new PaletteBuilder(this, context); - } - - @NonNull - public BitmapBuilder asBitmap() { - return new BitmapBuilder(this); - } - - @NonNull - public Builder checkIgnoreMediaStore(@NonNull Context context) { - return ignoreMediaStore(PreferenceUtil.INSTANCE.isIgnoreMediaStoreArtwork()); - } - - @NonNull - public Builder ignoreMediaStore(boolean ignoreMediaStore) { - this.ignoreMediaStore = ignoreMediaStore; - return this; - } - - @NonNull - public DrawableRequestBuilder build() { - //noinspection unchecked - return createBaseRequest(requestManager, song, ignoreMediaStore) - .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) - .error(DEFAULT_ERROR_IMAGE) - .animate(DEFAULT_ANIMATION) - .signature(createSignature(song)); - } + @NonNull + public PaletteBuilder generatePalette(@NonNull Context context) { + return new PaletteBuilder(this, context); } - public static class BitmapBuilder { - private final Builder builder; - - BitmapBuilder(Builder builder) { - this.builder = builder; - } - - public BitmapRequestBuilder build() { - //noinspection unchecked - return createBaseRequest(builder.requestManager, builder.song, builder.ignoreMediaStore) - .asBitmap() - .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) - .error(DEFAULT_ERROR_IMAGE) - .animate(DEFAULT_ANIMATION) - .signature(createSignature(builder.song)); - } + @NonNull + public BitmapBuilder asBitmap() { + return new BitmapBuilder(this); } - public static class PaletteBuilder { - final Context context; - private final Builder builder; - - PaletteBuilder(Builder builder, Context context) { - this.builder = builder; - this.context = context; - } - - public BitmapRequestBuilder build() { - //noinspection unchecked - return createBaseRequest(builder.requestManager, builder.song, builder.ignoreMediaStore) - .asBitmap() - .transcode(new BitmapPaletteTranscoder(context), BitmapPaletteWrapper.class) - .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) - .error(DEFAULT_ERROR_IMAGE) - .animate(DEFAULT_ANIMATION) - .signature(createSignature(builder.song)); - } + @NonNull + public Builder checkIgnoreMediaStore(@NonNull Context context) { + return ignoreMediaStore(PreferenceUtil.INSTANCE.isIgnoreMediaStoreArtwork()); } + + @NonNull + public Builder ignoreMediaStore(boolean ignoreMediaStore) { + this.ignoreMediaStore = ignoreMediaStore; + return this; + } + + @NonNull + public DrawableRequestBuilder build() { + //noinspection unchecked + return createBaseRequest(requestManager, song, ignoreMediaStore) + .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) + .error(DEFAULT_ERROR_IMAGE) + .animate(DEFAULT_ANIMATION) + .signature(createSignature(song)); + } + } + + public static class BitmapBuilder { + private final Builder builder; + + BitmapBuilder(Builder builder) { + this.builder = builder; + } + + public BitmapRequestBuilder build() { + //noinspection unchecked + return createBaseRequest(builder.requestManager, builder.song, builder.ignoreMediaStore) + .asBitmap() + .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) + .error(DEFAULT_ERROR_IMAGE) + .animate(DEFAULT_ANIMATION) + .signature(createSignature(builder.song)); + } + } + + public static class PaletteBuilder { + final Context context; + private final Builder builder; + + PaletteBuilder(Builder builder, Context context) { + this.builder = builder; + this.context = context; + } + + public BitmapRequestBuilder build() { + //noinspection unchecked + return createBaseRequest(builder.requestManager, builder.song, builder.ignoreMediaStore) + .asBitmap() + .transcode(new BitmapPaletteTranscoder(context), BitmapPaletteWrapper.class) + .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) + .error(DEFAULT_ERROR_IMAGE) + .animate(DEFAULT_ANIMATION) + .signature(createSignature(builder.song)); + } + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/UserProfileGlideRequest.java b/app/src/main/java/code/name/monkey/retromusic/glide/UserProfileGlideRequest.java index 29e9b1a7..f3b2fd42 100644 --- a/app/src/main/java/code/name/monkey/retromusic/glide/UserProfileGlideRequest.java +++ b/app/src/main/java/code/name/monkey/retromusic/glide/UserProfileGlideRequest.java @@ -1,82 +1,83 @@ package code.name.monkey.retromusic.glide; +import static code.name.monkey.retromusic.Constants.USER_PROFILE; + import android.graphics.Bitmap; import android.graphics.drawable.Drawable; - import androidx.annotation.NonNull; - +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.appthemehelper.util.TintHelper; +import code.name.monkey.retromusic.App; +import code.name.monkey.retromusic.R; import com.bumptech.glide.BitmapRequestBuilder; import com.bumptech.glide.BitmapTypeRequest; import com.bumptech.glide.RequestManager; import com.bumptech.glide.load.Key; import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.signature.MediaStoreSignature; - import java.io.File; -import code.name.monkey.appthemehelper.ThemeStore; -import code.name.monkey.appthemehelper.util.TintHelper; -import code.name.monkey.retromusic.App; -import code.name.monkey.retromusic.R; - -import static code.name.monkey.retromusic.Constants.USER_PROFILE; - public class UserProfileGlideRequest { - private static final DiskCacheStrategy DEFAULT_DISK_CACHE_STRATEGY = DiskCacheStrategy.NONE; - private static final int DEFAULT_ERROR_IMAGE = R.drawable.ic_account; - private static final int DEFAULT_ANIMATION = android.R.anim.fade_in; + private static final DiskCacheStrategy DEFAULT_DISK_CACHE_STRATEGY = DiskCacheStrategy.NONE; + private static final int DEFAULT_ERROR_IMAGE = R.drawable.ic_account; + private static final int DEFAULT_ANIMATION = android.R.anim.fade_in; - public static File getUserModel() { - File dir = App.Companion.getContext().getFilesDir(); - return new File(dir, USER_PROFILE); + public static File getUserModel() { + File dir = App.Companion.getContext().getFilesDir(); + return new File(dir, USER_PROFILE); + } + + private static BitmapTypeRequest createBaseRequest( + RequestManager requestManager, File profile) { + return requestManager.load(profile).asBitmap(); + } + + private static Key createSignature(File file) { + return new MediaStoreSignature("", file.lastModified(), 0); + } + + public static class Builder { + private RequestManager requestManager; + private File profile; + private Drawable error; + + private Builder(RequestManager requestManager, File profile) { + this.requestManager = requestManager; + this.profile = profile; + error = + TintHelper.createTintedDrawable( + App.Companion.getContext(), + R.drawable.ic_account, + ThemeStore.Companion.accentColor(App.Companion.getContext())); } - private static BitmapTypeRequest createBaseRequest(RequestManager requestManager, File profile) { - return requestManager.load(profile).asBitmap(); + public static Builder from(@NonNull RequestManager requestManager, File profile) { + return new Builder(requestManager, profile); } - private static Key createSignature(File file) { - return new MediaStoreSignature("", file.lastModified(), 0); + @NonNull + public BitmapRequestBuilder build() { + return createBaseRequest(requestManager, profile) + .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) + .error(error) + .animate(DEFAULT_ANIMATION) + .signature(createSignature(profile)); + } + } + + public static class BitmapBuilder { + private final Builder builder; + + BitmapBuilder(Builder builder) { + this.builder = builder; } - public static class Builder { - private RequestManager requestManager; - private File profile; - private Drawable error; - - private Builder(RequestManager requestManager, File profile) { - this.requestManager = requestManager; - this.profile = profile; - error = TintHelper.createTintedDrawable(App.Companion.getContext(), R.drawable.ic_account, ThemeStore.Companion.accentColor(App.Companion.getContext())); - } - - public static Builder from(@NonNull RequestManager requestManager, File profile) { - return new Builder(requestManager, profile); - } - - @NonNull - public BitmapRequestBuilder build() { - return createBaseRequest(requestManager, profile) - .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) - .error(error) - .animate(DEFAULT_ANIMATION) - .signature(createSignature(profile)); - } - } - - public static class BitmapBuilder { - private final Builder builder; - - BitmapBuilder(Builder builder) { - this.builder = builder; - } - - public BitmapRequestBuilder build() { - return createBaseRequest(builder.requestManager, builder.profile) - .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) - .error(builder.error) - .animate(DEFAULT_ANIMATION) - .signature(createSignature(builder.profile)); - } + public BitmapRequestBuilder build() { + return createBaseRequest(builder.requestManager, builder.profile) + .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) + .error(builder.error) + .animate(DEFAULT_ANIMATION) + .signature(createSignature(builder.profile)); } + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImageLoader.kt b/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImageLoader.kt index 9b67cb62..c07beeb7 100644 --- a/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImageLoader.kt +++ b/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImageLoader.kt @@ -1,17 +1,17 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.glide.artistimage import android.content.Context @@ -28,12 +28,12 @@ import com.bumptech.glide.load.model.GlideUrl import com.bumptech.glide.load.model.ModelLoader import com.bumptech.glide.load.model.ModelLoaderFactory import com.bumptech.glide.load.model.stream.StreamModelLoader -import okhttp3.Interceptor -import okhttp3.OkHttpClient -import okhttp3.logging.HttpLoggingInterceptor import java.io.IOException import java.io.InputStream import java.util.concurrent.TimeUnit +import okhttp3.Interceptor +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor class ArtistImage(val artist: Artist) @@ -70,7 +70,7 @@ class ArtistImageFetcher( val response = deezerService.getArtistImage(artists[0]).execute() if (!response.isSuccessful) { - throw IOException("Request failed with code: " + response.code()) + throw IOException("Request failed with code: " + response.code()) } if (isCancelled) return null @@ -100,7 +100,6 @@ class ArtistImageFetcher( return context.contentResolver.openInputStream(imageUri) } - private fun getHighestQuality(imageUrl: Data): String { return when { imageUrl.pictureXl.isNotEmpty() -> imageUrl.pictureXl diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCover.java b/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCover.java index cf3a82c5..9a5fd6a0 100644 --- a/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCover.java +++ b/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCover.java @@ -14,13 +14,11 @@ package code.name.monkey.retromusic.glide.audiocover; -/** - * @author Karim Abou Zeid (kabouzeid) - */ +/** @author Karim Abou Zeid (kabouzeid) */ public class AudioFileCover { - public final String filePath; + public final String filePath; - public AudioFileCover(String filePath) { - this.filePath = filePath; - } + public AudioFileCover(String filePath) { + this.filePath = filePath; + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverFetcher.java b/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverFetcher.java index 4ce9df5c..5240d1d1 100644 --- a/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverFetcher.java +++ b/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverFetcher.java @@ -15,64 +15,61 @@ package code.name.monkey.retromusic.glide.audiocover; import android.media.MediaMetadataRetriever; - import com.bumptech.glide.Priority; import com.bumptech.glide.load.data.DataFetcher; - import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; - public class AudioFileCoverFetcher implements DataFetcher { - private final AudioFileCover model; + private final AudioFileCover model; - private InputStream stream; + private InputStream stream; - public AudioFileCoverFetcher(AudioFileCover model) { + public AudioFileCoverFetcher(AudioFileCover model) { - this.model = model; + this.model = model; + } + + @Override + public String getId() { + // makes sure we never ever return null here + return String.valueOf(model.filePath); + } + + @Override + public InputStream loadData(final Priority priority) throws Exception { + + final MediaMetadataRetriever retriever = new MediaMetadataRetriever(); + try { + retriever.setDataSource(model.filePath); + byte[] picture = retriever.getEmbeddedPicture(); + if (picture != null) { + stream = new ByteArrayInputStream(picture); + } else { + stream = AudioFileCoverUtils.fallback(model.filePath); + } + } finally { + retriever.release(); } - @Override - public String getId() { - // makes sure we never ever return null here - return String.valueOf(model.filePath); + return stream; + } + + @Override + public void cleanup() { + // already cleaned up in loadData and ByteArrayInputStream will be GC'd + if (stream != null) { + try { + stream.close(); + } catch (IOException ignore) { + // can't do much about it + } } + } - @Override - public InputStream loadData(final Priority priority) throws Exception { - - final MediaMetadataRetriever retriever = new MediaMetadataRetriever(); - try { - retriever.setDataSource(model.filePath); - byte[] picture = retriever.getEmbeddedPicture(); - if (picture != null) { - stream = new ByteArrayInputStream(picture); - } else { - stream = AudioFileCoverUtils.fallback(model.filePath); - } - } finally { - retriever.release(); - } - - return stream; - } - - @Override - public void cleanup() { - // already cleaned up in loadData and ByteArrayInputStream will be GC'd - if (stream != null) { - try { - stream.close(); - } catch (IOException ignore) { - // can't do much about it - } - } - } - - @Override - public void cancel() { - // cannot cancel - } + @Override + public void cancel() { + // cannot cancel + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverLoader.java b/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverLoader.java index 1da3ec8c..02ffb16d 100644 --- a/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverLoader.java +++ b/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverLoader.java @@ -15,32 +15,28 @@ package code.name.monkey.retromusic.glide.audiocover; import android.content.Context; - import com.bumptech.glide.load.data.DataFetcher; import com.bumptech.glide.load.model.GenericLoaderFactory; import com.bumptech.glide.load.model.ModelLoader; import com.bumptech.glide.load.model.ModelLoaderFactory; import com.bumptech.glide.load.model.stream.StreamModelLoader; - import java.io.InputStream; - public class AudioFileCoverLoader implements StreamModelLoader { + @Override + public DataFetcher getResourceFetcher(AudioFileCover model, int width, int height) { + return new AudioFileCoverFetcher(model); + } + + public static class Factory implements ModelLoaderFactory { @Override - public DataFetcher getResourceFetcher(AudioFileCover model, int width, int height) { - return new AudioFileCoverFetcher(model); + public ModelLoader build( + Context context, GenericLoaderFactory factories) { + return new AudioFileCoverLoader(); } - public static class Factory implements ModelLoaderFactory { - @Override - public ModelLoader build(Context context, GenericLoaderFactory factories) { - return new AudioFileCoverLoader(); - } - - @Override - public void teardown() { - } - } + @Override + public void teardown() {} + } } - diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverUtils.java b/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverUtils.java index 7fc6bbdd..aaf612f1 100644 --- a/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverUtils.java +++ b/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverUtils.java @@ -14,50 +14,50 @@ package code.name.monkey.retromusic.glide.audiocover; -import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException; -import org.jaudiotagger.audio.exceptions.ReadOnlyFileException; -import org.jaudiotagger.audio.mp3.MP3File; -import org.jaudiotagger.tag.TagException; -import org.jaudiotagger.tag.images.Artwork; - import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException; +import org.jaudiotagger.audio.exceptions.ReadOnlyFileException; +import org.jaudiotagger.audio.mp3.MP3File; +import org.jaudiotagger.tag.TagException; +import org.jaudiotagger.tag.images.Artwork; public class AudioFileCoverUtils { - public static final String[] FALLBACKS = {"cover.jpg", "album.jpg", "folder.jpg", "cover.png", "album.png", "folder.png"}; + public static final String[] FALLBACKS = { + "cover.jpg", "album.jpg", "folder.jpg", "cover.png", "album.png", "folder.png" + }; - - public static InputStream fallback(String path) throws FileNotFoundException { - // Method 1: use embedded high resolution album art if there is any - try { - MP3File mp3File = new MP3File(path); - if (mp3File.hasID3v2Tag()) { - Artwork art = mp3File.getTag().getFirstArtwork(); - if (art != null) { - byte[] imageData = art.getBinaryData(); - return new ByteArrayInputStream(imageData); - } - } - // If there are any exceptions, we ignore them and continue to the other fallback method - } catch (ReadOnlyFileException ignored) { - } catch (InvalidAudioFrameException ignored) { - } catch (TagException ignored) { - } catch (IOException ignored) { + public static InputStream fallback(String path) throws FileNotFoundException { + // Method 1: use embedded high resolution album art if there is any + try { + MP3File mp3File = new MP3File(path); + if (mp3File.hasID3v2Tag()) { + Artwork art = mp3File.getTag().getFirstArtwork(); + if (art != null) { + byte[] imageData = art.getBinaryData(); + return new ByteArrayInputStream(imageData); } - - // Method 2: look for album art in external files - final File parent = new File(path).getParentFile(); - for (String fallback : FALLBACKS) { - File cover = new File(parent, fallback); - if (cover.exists()) { - return new FileInputStream(cover); - } - } - return null; + } + // If there are any exceptions, we ignore them and continue to the other fallback method + } catch (ReadOnlyFileException ignored) { + } catch (InvalidAudioFrameException ignored) { + } catch (TagException ignored) { + } catch (IOException ignored) { } -} \ No newline at end of file + + // Method 2: look for album art in external files + final File parent = new File(path).getParentFile(); + for (String fallback : FALLBACKS) { + File cover = new File(parent, fallback); + if (cover.exists()) { + return new FileInputStream(cover); + } + } + return null; + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteResource.java b/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteResource.java index dc954db2..d94c2c6d 100644 --- a/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteResource.java +++ b/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteResource.java @@ -20,28 +20,28 @@ import com.bumptech.glide.util.Util; public class BitmapPaletteResource implements Resource { - private final BitmapPaletteWrapper bitmapPaletteWrapper; - private final BitmapPool bitmapPool; + private final BitmapPaletteWrapper bitmapPaletteWrapper; + private final BitmapPool bitmapPool; - public BitmapPaletteResource(BitmapPaletteWrapper bitmapPaletteWrapper, BitmapPool bitmapPool) { - this.bitmapPaletteWrapper = bitmapPaletteWrapper; - this.bitmapPool = bitmapPool; - } + public BitmapPaletteResource(BitmapPaletteWrapper bitmapPaletteWrapper, BitmapPool bitmapPool) { + this.bitmapPaletteWrapper = bitmapPaletteWrapper; + this.bitmapPool = bitmapPool; + } - @Override - public BitmapPaletteWrapper get() { - return bitmapPaletteWrapper; - } + @Override + public BitmapPaletteWrapper get() { + return bitmapPaletteWrapper; + } - @Override - public int getSize() { - return Util.getBitmapByteSize(bitmapPaletteWrapper.getBitmap()); - } + @Override + public int getSize() { + return Util.getBitmapByteSize(bitmapPaletteWrapper.getBitmap()); + } - @Override - public void recycle() { - if (!bitmapPool.put(bitmapPaletteWrapper.getBitmap())) { - bitmapPaletteWrapper.getBitmap().recycle(); - } + @Override + public void recycle() { + if (!bitmapPool.put(bitmapPaletteWrapper.getBitmap())) { + bitmapPaletteWrapper.getBitmap().recycle(); } + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteTarget.java b/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteTarget.java index 5d478e67..2ce72775 100644 --- a/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteTarget.java +++ b/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteTarget.java @@ -15,16 +15,15 @@ package code.name.monkey.retromusic.glide.palette; import android.widget.ImageView; - import com.bumptech.glide.request.target.ImageViewTarget; public class BitmapPaletteTarget extends ImageViewTarget { - public BitmapPaletteTarget(ImageView view) { - super(view); - } + public BitmapPaletteTarget(ImageView view) { + super(view); + } - @Override - protected void setResource(BitmapPaletteWrapper bitmapPaletteWrapper) { - view.setImageBitmap(bitmapPaletteWrapper.getBitmap()); - } + @Override + protected void setResource(BitmapPaletteWrapper bitmapPaletteWrapper) { + view.setImageBitmap(bitmapPaletteWrapper.getBitmap()); + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteTranscoder.java b/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteTranscoder.java index 796bfa74..7fd4bfad 100644 --- a/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteTranscoder.java +++ b/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteTranscoder.java @@ -16,34 +16,33 @@ package code.name.monkey.retromusic.glide.palette; import android.content.Context; import android.graphics.Bitmap; - +import code.name.monkey.retromusic.util.RetroColorUtil; import com.bumptech.glide.Glide; import com.bumptech.glide.load.engine.Resource; import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; import com.bumptech.glide.load.resource.transcode.ResourceTranscoder; -import code.name.monkey.retromusic.util.RetroColorUtil; - public class BitmapPaletteTranscoder implements ResourceTranscoder { - private final BitmapPool bitmapPool; + private final BitmapPool bitmapPool; - public BitmapPaletteTranscoder(Context context) { - this(Glide.get(context).getBitmapPool()); - } + public BitmapPaletteTranscoder(Context context) { + this(Glide.get(context).getBitmapPool()); + } - public BitmapPaletteTranscoder(BitmapPool bitmapPool) { - this.bitmapPool = bitmapPool; - } + public BitmapPaletteTranscoder(BitmapPool bitmapPool) { + this.bitmapPool = bitmapPool; + } - @Override - public Resource transcode(Resource bitmapResource) { - Bitmap bitmap = bitmapResource.get(); - BitmapPaletteWrapper bitmapPaletteWrapper = new BitmapPaletteWrapper(bitmap, RetroColorUtil.generatePalette(bitmap)); - return new BitmapPaletteResource(bitmapPaletteWrapper, bitmapPool); - } + @Override + public Resource transcode(Resource bitmapResource) { + Bitmap bitmap = bitmapResource.get(); + BitmapPaletteWrapper bitmapPaletteWrapper = + new BitmapPaletteWrapper(bitmap, RetroColorUtil.generatePalette(bitmap)); + return new BitmapPaletteResource(bitmapPaletteWrapper, bitmapPool); + } - @Override - public String getId() { - return "BitmapPaletteTranscoder.com.kabouzeid.gramophone.glide.palette"; - } -} \ No newline at end of file + @Override + public String getId() { + return "BitmapPaletteTranscoder.com.kabouzeid.gramophone.glide.palette"; + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteWrapper.java b/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteWrapper.java index 105d09f0..df713937 100644 --- a/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteWrapper.java +++ b/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteWrapper.java @@ -15,23 +15,22 @@ package code.name.monkey.retromusic.glide.palette; import android.graphics.Bitmap; - import androidx.palette.graphics.Palette; public class BitmapPaletteWrapper { - private final Bitmap mBitmap; - private final Palette mPalette; + private final Bitmap mBitmap; + private final Palette mPalette; - public BitmapPaletteWrapper(Bitmap bitmap, Palette palette) { - mBitmap = bitmap; - mPalette = palette; - } + public BitmapPaletteWrapper(Bitmap bitmap, Palette palette) { + mBitmap = bitmap; + mPalette = palette; + } - public Bitmap getBitmap() { - return mBitmap; - } + public Bitmap getBitmap() { + return mBitmap; + } - public Palette getPalette() { - return mPalette; - } + public Palette getPalette() { + return mPalette; + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/HorizontalAdapterHelper.kt b/app/src/main/java/code/name/monkey/retromusic/helper/HorizontalAdapterHelper.kt index fca8c228..b155cbd9 100644 --- a/app/src/main/java/code/name/monkey/retromusic/helper/HorizontalAdapterHelper.kt +++ b/app/src/main/java/code/name/monkey/retromusic/helper/HorizontalAdapterHelper.kt @@ -1,24 +1,23 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.helper import android.content.Context import android.view.ViewGroup import code.name.monkey.retromusic.R - object HorizontalAdapterHelper { const val LAYOUT_RES = R.layout.item_album_card @@ -29,7 +28,8 @@ object HorizontalAdapterHelper { fun applyMarginToLayoutParams( context: Context, - layoutParams: ViewGroup.MarginLayoutParams, viewType: Int + layoutParams: ViewGroup.MarginLayoutParams, + viewType: Int ) { val listMargin = context.resources .getDimensionPixelSize(R.dimen.now_playing_top_margin) diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/M3UConstants.java b/app/src/main/java/code/name/monkey/retromusic/helper/M3UConstants.java index 9865cad2..f2956897 100644 --- a/app/src/main/java/code/name/monkey/retromusic/helper/M3UConstants.java +++ b/app/src/main/java/code/name/monkey/retromusic/helper/M3UConstants.java @@ -15,8 +15,8 @@ package code.name.monkey.retromusic.helper; public interface M3UConstants { - String EXTENSION = "m3u"; - String HEADER = "#EXTM3U"; - String ENTRY = "#EXTINF:"; - String DURATION_SEPARATOR = ","; -} \ No newline at end of file + String EXTENSION = "m3u"; + String HEADER = "#EXTM3U"; + String ENTRY = "#EXTINF:"; + String DURATION_SEPARATOR = ","; +} diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/M3UWriter.kt b/app/src/main/java/code/name/monkey/retromusic/helper/M3UWriter.kt index 35e87d80..249cd778 100644 --- a/app/src/main/java/code/name/monkey/retromusic/helper/M3UWriter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/helper/M3UWriter.kt @@ -1,15 +1,16 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.helper @@ -66,4 +67,4 @@ object M3UWriter : M3UConstants { } return file } -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/MusicPlayerRemote.kt b/app/src/main/java/code/name/monkey/retromusic/helper/MusicPlayerRemote.kt index 30c6afa1..fc815fe0 100644 --- a/app/src/main/java/code/name/monkey/retromusic/helper/MusicPlayerRemote.kt +++ b/app/src/main/java/code/name/monkey/retromusic/helper/MusicPlayerRemote.kt @@ -1,17 +1,17 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.helper import android.annotation.TargetApi @@ -29,10 +29,10 @@ import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.repository.SongRepository import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.util.PreferenceUtil -import org.koin.core.KoinComponent -import org.koin.core.inject import java.io.File import java.util.* +import org.koin.core.KoinComponent +import org.koin.core.inject object MusicPlayerRemote : KoinComponent { val TAG: String = MusicPlayerRemote::class.java.simpleName @@ -41,7 +41,6 @@ object MusicPlayerRemote : KoinComponent { private val songRepository by inject() - @JvmStatic val isPlaying: Boolean get() = musicService != null && musicService!!.isPlaying @@ -442,7 +441,7 @@ object MusicPlayerRemote : KoinComponent { if (songs != null && songs.isNotEmpty()) { openQueue(songs, 0, true) } else { - //TODO the file is not listed in the media store + // TODO the file is not listed in the media store println("The file is not listed in the media store") } } diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/MusicProgressViewUpdateHelper.kt b/app/src/main/java/code/name/monkey/retromusic/helper/MusicProgressViewUpdateHelper.kt index bbc46cbe..9cd2f3a9 100644 --- a/app/src/main/java/code/name/monkey/retromusic/helper/MusicProgressViewUpdateHelper.kt +++ b/app/src/main/java/code/name/monkey/retromusic/helper/MusicProgressViewUpdateHelper.kt @@ -1,17 +1,17 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.helper import android.os.Handler diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/PlayPauseButtonOnClickHandler.kt b/app/src/main/java/code/name/monkey/retromusic/helper/PlayPauseButtonOnClickHandler.kt index 24c1d351..50be9747 100644 --- a/app/src/main/java/code/name/monkey/retromusic/helper/PlayPauseButtonOnClickHandler.kt +++ b/app/src/main/java/code/name/monkey/retromusic/helper/PlayPauseButtonOnClickHandler.kt @@ -1,22 +1,21 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.helper import android.view.View - class PlayPauseButtonOnClickHandler : View.OnClickListener { override fun onClick(v: View) { if (MusicPlayerRemote.isPlaying) { diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/SearchQueryHelper.kt b/app/src/main/java/code/name/monkey/retromusic/helper/SearchQueryHelper.kt index e4ad5a91..936af2b9 100644 --- a/app/src/main/java/code/name/monkey/retromusic/helper/SearchQueryHelper.kt +++ b/app/src/main/java/code/name/monkey/retromusic/helper/SearchQueryHelper.kt @@ -1,17 +1,17 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.helper import android.app.SearchManager @@ -19,9 +19,9 @@ import android.os.Bundle import android.provider.MediaStore import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.repository.RealSongRepository +import java.util.* import org.koin.core.KoinComponent import org.koin.core.inject -import java.util.* object SearchQueryHelper : KoinComponent { private const val TITLE_SELECTION = "lower(" + MediaStore.Audio.AudioColumns.TITLE + ") = ?" diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/ShuffleHelper.kt b/app/src/main/java/code/name/monkey/retromusic/helper/ShuffleHelper.kt index 6615f619..02a621cb 100644 --- a/app/src/main/java/code/name/monkey/retromusic/helper/ShuffleHelper.kt +++ b/app/src/main/java/code/name/monkey/retromusic/helper/ShuffleHelper.kt @@ -1,22 +1,21 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.helper import code.name.monkey.retromusic.model.Song - object ShuffleHelper { fun makeShuffleList(listToShuffle: MutableList, current: Int) { diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/SortOrder.kt b/app/src/main/java/code/name/monkey/retromusic/helper/SortOrder.kt index 0d5eb6f1..b02ded2a 100644 --- a/app/src/main/java/code/name/monkey/retromusic/helper/SortOrder.kt +++ b/app/src/main/java/code/name/monkey/retromusic/helper/SortOrder.kt @@ -1,15 +1,16 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.helper @@ -55,8 +56,8 @@ class SortOrder { const val ALBUM_NUMBER_OF_SONGS = MediaStore.Audio.Albums.NUMBER_OF_SONGS + " DESC" /* Album sort order artist */ - const val ALBUM_ARTIST = (MediaStore.Audio.Artists.DEFAULT_SORT_ORDER - + ", " + MediaStore.Audio.Albums.DEFAULT_SORT_ORDER) + const val ALBUM_ARTIST = (MediaStore.Audio.Artists.DEFAULT_SORT_ORDER + + ", " + MediaStore.Audio.Albums.DEFAULT_SORT_ORDER) /* Album sort order year */ const val ALBUM_YEAR = MediaStore.Audio.Media.YEAR + " DESC" @@ -113,8 +114,8 @@ class SortOrder { const val SONG_Z_A = "$SONG_A_Z DESC" /* Album song sort order track list */ - const val SONG_TRACK_LIST = (MediaStore.Audio.Media.TRACK + ", " - + MediaStore.Audio.Media.DEFAULT_SORT_ORDER) + const val SONG_TRACK_LIST = (MediaStore.Audio.Media.TRACK + ", " + + MediaStore.Audio.Media.DEFAULT_SORT_ORDER) /* Album song sort order duration */ const val SONG_DURATION = SongSortOrder.SONG_DURATION @@ -183,4 +184,4 @@ class SortOrder { const val ALBUM_Z_A = "$GENRE_A_Z DESC" } } -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/StackBlur.java b/app/src/main/java/code/name/monkey/retromusic/helper/StackBlur.java index 112b554b..9756fa13 100644 --- a/app/src/main/java/code/name/monkey/retromusic/helper/StackBlur.java +++ b/app/src/main/java/code/name/monkey/retromusic/helper/StackBlur.java @@ -1,7 +1,6 @@ package code.name.monkey.retromusic.helper; import android.graphics.Bitmap; - import java.util.ArrayList; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; @@ -9,325 +8,312 @@ import java.util.concurrent.Executors; /** * Blur using Java code. - *

- * This is a compromise between Gaussian Blur and Box blur - * It creates much better looking blurs than Box Blur, but is - * 7x faster than my Gaussian Blur implementation. - *

- * I called it Stack Blur because this describes best how this - * filter works internally: it creates a kind of moving stack - * of colors whilst scanning through the image. Thereby it - * just has to add one new block of color to the right side - * of the stack and remove the leftmost color. The remaining - * colors on the topmost layer of the stack are either added on - * or reduced by one, depending on if they are on the right or - * on the left side of the stack. * - * @author Enrique López Mañas - * http://www.neo-tech.es - *

- * Author of the original algorithm: Mario Klingemann - *

- * Based heavily on http://vitiy.info/Code/stackblur.cpp - * See http://vitiy.info/stackblur-algorithm-multi-threaded-blur-for-cpp/ + *

This is a compromise between Gaussian Blur and Box blur It creates much better looking blurs + * than Box Blur, but is 7x faster than my Gaussian Blur implementation. + * + *

I called it Stack Blur because this describes best how this filter works internally: it + * creates a kind of moving stack of colors whilst scanning through the image. Thereby it just has + * to add one new block of color to the right side of the stack and remove the leftmost color. The + * remaining colors on the topmost layer of the stack are either added on or reduced by one, + * depending on if they are on the right or on the left side of the stack. + * + * @author Enrique López Mañas http://www.neo-tech.es + *

Author of the original algorithm: Mario Klingemann + *

Based heavily on http://vitiy.info/Code/stackblur.cpp See + * http://vitiy.info/stackblur-algorithm-multi-threaded-blur-for-cpp/ * @copyright: Enrique López Mañas * @license: Apache License 2.0 */ public class StackBlur { - static final int EXECUTOR_THREADS = Runtime.getRuntime().availableProcessors(); - static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(EXECUTOR_THREADS); + static final int EXECUTOR_THREADS = Runtime.getRuntime().availableProcessors(); + static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(EXECUTOR_THREADS); - private static final short[] stackblur_mul = { - 512, 512, 456, 512, 328, 456, 335, 512, 405, 328, 271, 456, 388, 335, 292, 512, - 454, 405, 364, 328, 298, 271, 496, 456, 420, 388, 360, 335, 312, 292, 273, 512, - 482, 454, 428, 405, 383, 364, 345, 328, 312, 298, 284, 271, 259, 496, 475, 456, - 437, 420, 404, 388, 374, 360, 347, 335, 323, 312, 302, 292, 282, 273, 265, 512, - 497, 482, 468, 454, 441, 428, 417, 405, 394, 383, 373, 364, 354, 345, 337, 328, - 320, 312, 305, 298, 291, 284, 278, 271, 265, 259, 507, 496, 485, 475, 465, 456, - 446, 437, 428, 420, 412, 404, 396, 388, 381, 374, 367, 360, 354, 347, 341, 335, - 329, 323, 318, 312, 307, 302, 297, 292, 287, 282, 278, 273, 269, 265, 261, 512, - 505, 497, 489, 482, 475, 468, 461, 454, 447, 441, 435, 428, 422, 417, 411, 405, - 399, 394, 389, 383, 378, 373, 368, 364, 359, 354, 350, 345, 341, 337, 332, 328, - 324, 320, 316, 312, 309, 305, 301, 298, 294, 291, 287, 284, 281, 278, 274, 271, - 268, 265, 262, 259, 257, 507, 501, 496, 491, 485, 480, 475, 470, 465, 460, 456, - 451, 446, 442, 437, 433, 428, 424, 420, 416, 412, 408, 404, 400, 396, 392, 388, - 385, 381, 377, 374, 370, 367, 363, 360, 357, 354, 350, 347, 344, 341, 338, 335, - 332, 329, 326, 323, 320, 318, 315, 312, 310, 307, 304, 302, 299, 297, 294, 292, - 289, 287, 285, 282, 280, 278, 275, 273, 271, 269, 267, 265, 263, 261, 259 - }; + private static final short[] stackblur_mul = { + 512, 512, 456, 512, 328, 456, 335, 512, 405, 328, 271, 456, 388, 335, 292, 512, + 454, 405, 364, 328, 298, 271, 496, 456, 420, 388, 360, 335, 312, 292, 273, 512, + 482, 454, 428, 405, 383, 364, 345, 328, 312, 298, 284, 271, 259, 496, 475, 456, + 437, 420, 404, 388, 374, 360, 347, 335, 323, 312, 302, 292, 282, 273, 265, 512, + 497, 482, 468, 454, 441, 428, 417, 405, 394, 383, 373, 364, 354, 345, 337, 328, + 320, 312, 305, 298, 291, 284, 278, 271, 265, 259, 507, 496, 485, 475, 465, 456, + 446, 437, 428, 420, 412, 404, 396, 388, 381, 374, 367, 360, 354, 347, 341, 335, + 329, 323, 318, 312, 307, 302, 297, 292, 287, 282, 278, 273, 269, 265, 261, 512, + 505, 497, 489, 482, 475, 468, 461, 454, 447, 441, 435, 428, 422, 417, 411, 405, + 399, 394, 389, 383, 378, 373, 368, 364, 359, 354, 350, 345, 341, 337, 332, 328, + 324, 320, 316, 312, 309, 305, 301, 298, 294, 291, 287, 284, 281, 278, 274, 271, + 268, 265, 262, 259, 257, 507, 501, 496, 491, 485, 480, 475, 470, 465, 460, 456, + 451, 446, 442, 437, 433, 428, 424, 420, 416, 412, 408, 404, 400, 396, 392, 388, + 385, 381, 377, 374, 370, 367, 363, 360, 357, 354, 350, 347, 344, 341, 338, 335, + 332, 329, 326, 323, 320, 318, 315, 312, 310, 307, 304, 302, 299, 297, 294, 292, + 289, 287, 285, 282, 280, 278, 275, 273, 271, 269, 267, 265, 263, 261, 259 + }; - private static final byte[] stackblur_shr = { - 9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17, - 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, - 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, - 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, - 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, - 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, - 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, - 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, - 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, - 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, - 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, - 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, - 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24 - }; + private static final byte[] stackblur_shr = { + 9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17, + 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24 + }; - public static Bitmap blur(Bitmap original, float radius) { - int w = original.getWidth(); - int h = original.getHeight(); - int[] currentPixels = new int[w * h]; - original.getPixels(currentPixels, 0, w, 0, 0, w, h); - int cores = EXECUTOR_THREADS; + public static Bitmap blur(Bitmap original, float radius) { + int w = original.getWidth(); + int h = original.getHeight(); + int[] currentPixels = new int[w * h]; + original.getPixels(currentPixels, 0, w, 0, 0, w, h); + int cores = EXECUTOR_THREADS; - ArrayList horizontal = new ArrayList(cores); - ArrayList vertical = new ArrayList(cores); - for (int i = 0; i < cores; i++) { - horizontal.add(new BlurTask(currentPixels, w, h, (int) radius, cores, i, 1)); - vertical.add(new BlurTask(currentPixels, w, h, (int) radius, cores, i, 2)); - } - - try { - EXECUTOR.invokeAll(horizontal); - } catch (InterruptedException e) { - return null; - } - - try { - EXECUTOR.invokeAll(vertical); - } catch (InterruptedException e) { - return null; - } - - return Bitmap.createBitmap(currentPixels, w, h, Bitmap.Config.ARGB_8888); + ArrayList horizontal = new ArrayList(cores); + ArrayList vertical = new ArrayList(cores); + for (int i = 0; i < cores; i++) { + horizontal.add(new BlurTask(currentPixels, w, h, (int) radius, cores, i, 1)); + vertical.add(new BlurTask(currentPixels, w, h, (int) radius, cores, i, 2)); } - private static void blurIteration(int[] src, int w, int h, int radius, int cores, int core, int step) { - int x, y, xp, yp, i; - int sp; - int stack_start; - int stack_i; - - int src_i; - int dst_i; - - long sum_r, sum_g, sum_b, - sum_in_r, sum_in_g, sum_in_b, - sum_out_r, sum_out_g, sum_out_b; - - int wm = w - 1; - int hm = h - 1; - int div = (radius * 2) + 1; - int mul_sum = stackblur_mul[radius]; - byte shr_sum = stackblur_shr[radius]; - int[] stack = new int[div]; - - if (step == 1) { - int minY = core * h / cores; - int maxY = (core + 1) * h / cores; - - for (y = minY; y < maxY; y++) { - sum_r = sum_g = sum_b = - sum_in_r = sum_in_g = sum_in_b = - sum_out_r = sum_out_g = sum_out_b = 0; - - src_i = w * y; // start of line (0,y) - - for (i = 0; i <= radius; i++) { - stack_i = i; - stack[stack_i] = src[src_i]; - sum_r += ((src[src_i] >>> 16) & 0xff) * (i + 1); - sum_g += ((src[src_i] >>> 8) & 0xff) * (i + 1); - sum_b += (src[src_i] & 0xff) * (i + 1); - sum_out_r += ((src[src_i] >>> 16) & 0xff); - sum_out_g += ((src[src_i] >>> 8) & 0xff); - sum_out_b += (src[src_i] & 0xff); - } - - - for (i = 1; i <= radius; i++) { - if (i <= wm) src_i += 1; - stack_i = i + radius; - stack[stack_i] = src[src_i]; - sum_r += ((src[src_i] >>> 16) & 0xff) * (radius + 1 - i); - sum_g += ((src[src_i] >>> 8) & 0xff) * (radius + 1 - i); - sum_b += (src[src_i] & 0xff) * (radius + 1 - i); - sum_in_r += ((src[src_i] >>> 16) & 0xff); - sum_in_g += ((src[src_i] >>> 8) & 0xff); - sum_in_b += (src[src_i] & 0xff); - } - - - sp = radius; - xp = radius; - if (xp > wm) xp = wm; - src_i = xp + y * w; // img.pix_ptr(xp, y); - dst_i = y * w; // img.pix_ptr(0, y); - for (x = 0; x < w; x++) { - src[dst_i] = (int) - ((src[dst_i] & 0xFFFFFFFF) | - ((((sum_r * mul_sum) >>> shr_sum) & 0xff) << 16) | - ((((sum_g * mul_sum) >>> shr_sum) & 0xff) << 8) | - ((((sum_b * mul_sum) >>> shr_sum) & 0xff))); - dst_i += 1; - - sum_r -= sum_out_r; - sum_g -= sum_out_g; - sum_b -= sum_out_b; - - stack_start = sp + div - radius; - if (stack_start >= div) stack_start -= div; - stack_i = stack_start; - - sum_out_r -= ((stack[stack_i] >>> 16) & 0xff); - sum_out_g -= ((stack[stack_i] >>> 8) & 0xff); - sum_out_b -= (stack[stack_i] & 0xff); - - if (xp < wm) { - src_i += 1; - ++xp; - } - - stack[stack_i] = src[src_i]; - - sum_in_r += ((src[src_i] >>> 16) & 0xff); - sum_in_g += ((src[src_i] >>> 8) & 0xff); - sum_in_b += (src[src_i] & 0xff); - sum_r += sum_in_r; - sum_g += sum_in_g; - sum_b += sum_in_b; - - ++sp; - if (sp >= div) sp = 0; - stack_i = sp; - - sum_out_r += ((stack[stack_i] >>> 16) & 0xff); - sum_out_g += ((stack[stack_i] >>> 8) & 0xff); - sum_out_b += (stack[stack_i] & 0xff); - sum_in_r -= ((stack[stack_i] >>> 16) & 0xff); - sum_in_g -= ((stack[stack_i] >>> 8) & 0xff); - sum_in_b -= (stack[stack_i] & 0xff); - } - - } - } - - // step 2 - else if (step == 2) { - int minX = core * w / cores; - int maxX = (core + 1) * w / cores; - - for (x = minX; x < maxX; x++) { - sum_r = sum_g = sum_b = - sum_in_r = sum_in_g = sum_in_b = - sum_out_r = sum_out_g = sum_out_b = 0; - - src_i = x; // x,0 - for (i = 0; i <= radius; i++) { - stack_i = i; - stack[stack_i] = src[src_i]; - sum_r += ((src[src_i] >>> 16) & 0xff) * (i + 1); - sum_g += ((src[src_i] >>> 8) & 0xff) * (i + 1); - sum_b += (src[src_i] & 0xff) * (i + 1); - sum_out_r += ((src[src_i] >>> 16) & 0xff); - sum_out_g += ((src[src_i] >>> 8) & 0xff); - sum_out_b += (src[src_i] & 0xff); - } - for (i = 1; i <= radius; i++) { - if (i <= hm) src_i += w; // +stride - - stack_i = i + radius; - stack[stack_i] = src[src_i]; - sum_r += ((src[src_i] >>> 16) & 0xff) * (radius + 1 - i); - sum_g += ((src[src_i] >>> 8) & 0xff) * (radius + 1 - i); - sum_b += (src[src_i] & 0xff) * (radius + 1 - i); - sum_in_r += ((src[src_i] >>> 16) & 0xff); - sum_in_g += ((src[src_i] >>> 8) & 0xff); - sum_in_b += (src[src_i] & 0xff); - } - - sp = radius; - yp = radius; - if (yp > hm) yp = hm; - src_i = x + yp * w; // img.pix_ptr(x, yp); - dst_i = x; // img.pix_ptr(x, 0); - for (y = 0; y < h; y++) { - src[dst_i] = (int) - ((src[dst_i] & 0xFFFFFFFF) | - ((((sum_r * mul_sum) >>> shr_sum) & 0xff) << 16) | - ((((sum_g * mul_sum) >>> shr_sum) & 0xff) << 8) | - ((((sum_b * mul_sum) >>> shr_sum) & 0xff))); - dst_i += w; - - sum_r -= sum_out_r; - sum_g -= sum_out_g; - sum_b -= sum_out_b; - - stack_start = sp + div - radius; - if (stack_start >= div) stack_start -= div; - stack_i = stack_start; - - sum_out_r -= ((stack[stack_i] >>> 16) & 0xff); - sum_out_g -= ((stack[stack_i] >>> 8) & 0xff); - sum_out_b -= (stack[stack_i] & 0xff); - - if (yp < hm) { - src_i += w; // stride - ++yp; - } - - stack[stack_i] = src[src_i]; - - sum_in_r += ((src[src_i] >>> 16) & 0xff); - sum_in_g += ((src[src_i] >>> 8) & 0xff); - sum_in_b += (src[src_i] & 0xff); - sum_r += sum_in_r; - sum_g += sum_in_g; - sum_b += sum_in_b; - - ++sp; - if (sp >= div) sp = 0; - stack_i = sp; - - sum_out_r += ((stack[stack_i] >>> 16) & 0xff); - sum_out_g += ((stack[stack_i] >>> 8) & 0xff); - sum_out_b += (stack[stack_i] & 0xff); - sum_in_r -= ((stack[stack_i] >>> 16) & 0xff); - sum_in_g -= ((stack[stack_i] >>> 8) & 0xff); - sum_in_b -= (stack[stack_i] & 0xff); - } - } - } - + try { + EXECUTOR.invokeAll(horizontal); + } catch (InterruptedException e) { + return null; } - private static class BlurTask implements Callable { - private final int[] _src; - private final int _w; - private final int _h; - private final int _radius; - private final int _totalCores; - private final int _coreIndex; - private final int _round; - - public BlurTask(int[] src, int w, int h, int radius, int totalCores, int coreIndex, int round) { - _src = src; - _w = w; - _h = h; - _radius = radius; - _totalCores = totalCores; - _coreIndex = coreIndex; - _round = round; - } - - @Override - public Void call() throws Exception { - blurIteration(_src, _w, _h, _radius, _totalCores, _coreIndex, _round); - return null; - } - + try { + EXECUTOR.invokeAll(vertical); + } catch (InterruptedException e) { + return null; } + + return Bitmap.createBitmap(currentPixels, w, h, Bitmap.Config.ARGB_8888); + } + + private static void blurIteration( + int[] src, int w, int h, int radius, int cores, int core, int step) { + int x, y, xp, yp, i; + int sp; + int stack_start; + int stack_i; + + int src_i; + int dst_i; + + long sum_r, sum_g, sum_b, sum_in_r, sum_in_g, sum_in_b, sum_out_r, sum_out_g, sum_out_b; + + int wm = w - 1; + int hm = h - 1; + int div = (radius * 2) + 1; + int mul_sum = stackblur_mul[radius]; + byte shr_sum = stackblur_shr[radius]; + int[] stack = new int[div]; + + if (step == 1) { + int minY = core * h / cores; + int maxY = (core + 1) * h / cores; + + for (y = minY; y < maxY; y++) { + sum_r = + sum_g = sum_b = sum_in_r = sum_in_g = sum_in_b = sum_out_r = sum_out_g = sum_out_b = 0; + + src_i = w * y; // start of line (0,y) + + for (i = 0; i <= radius; i++) { + stack_i = i; + stack[stack_i] = src[src_i]; + sum_r += ((src[src_i] >>> 16) & 0xff) * (i + 1); + sum_g += ((src[src_i] >>> 8) & 0xff) * (i + 1); + sum_b += (src[src_i] & 0xff) * (i + 1); + sum_out_r += ((src[src_i] >>> 16) & 0xff); + sum_out_g += ((src[src_i] >>> 8) & 0xff); + sum_out_b += (src[src_i] & 0xff); + } + + for (i = 1; i <= radius; i++) { + if (i <= wm) src_i += 1; + stack_i = i + radius; + stack[stack_i] = src[src_i]; + sum_r += ((src[src_i] >>> 16) & 0xff) * (radius + 1 - i); + sum_g += ((src[src_i] >>> 8) & 0xff) * (radius + 1 - i); + sum_b += (src[src_i] & 0xff) * (radius + 1 - i); + sum_in_r += ((src[src_i] >>> 16) & 0xff); + sum_in_g += ((src[src_i] >>> 8) & 0xff); + sum_in_b += (src[src_i] & 0xff); + } + + sp = radius; + xp = radius; + if (xp > wm) xp = wm; + src_i = xp + y * w; // img.pix_ptr(xp, y); + dst_i = y * w; // img.pix_ptr(0, y); + for (x = 0; x < w; x++) { + src[dst_i] = + (int) + ((src[dst_i] & 0xFFFFFFFF) + | ((((sum_r * mul_sum) >>> shr_sum) & 0xff) << 16) + | ((((sum_g * mul_sum) >>> shr_sum) & 0xff) << 8) + | ((((sum_b * mul_sum) >>> shr_sum) & 0xff))); + dst_i += 1; + + sum_r -= sum_out_r; + sum_g -= sum_out_g; + sum_b -= sum_out_b; + + stack_start = sp + div - radius; + if (stack_start >= div) stack_start -= div; + stack_i = stack_start; + + sum_out_r -= ((stack[stack_i] >>> 16) & 0xff); + sum_out_g -= ((stack[stack_i] >>> 8) & 0xff); + sum_out_b -= (stack[stack_i] & 0xff); + + if (xp < wm) { + src_i += 1; + ++xp; + } + + stack[stack_i] = src[src_i]; + + sum_in_r += ((src[src_i] >>> 16) & 0xff); + sum_in_g += ((src[src_i] >>> 8) & 0xff); + sum_in_b += (src[src_i] & 0xff); + sum_r += sum_in_r; + sum_g += sum_in_g; + sum_b += sum_in_b; + + ++sp; + if (sp >= div) sp = 0; + stack_i = sp; + + sum_out_r += ((stack[stack_i] >>> 16) & 0xff); + sum_out_g += ((stack[stack_i] >>> 8) & 0xff); + sum_out_b += (stack[stack_i] & 0xff); + sum_in_r -= ((stack[stack_i] >>> 16) & 0xff); + sum_in_g -= ((stack[stack_i] >>> 8) & 0xff); + sum_in_b -= (stack[stack_i] & 0xff); + } + } + } + + // step 2 + else if (step == 2) { + int minX = core * w / cores; + int maxX = (core + 1) * w / cores; + + for (x = minX; x < maxX; x++) { + sum_r = + sum_g = sum_b = sum_in_r = sum_in_g = sum_in_b = sum_out_r = sum_out_g = sum_out_b = 0; + + src_i = x; // x,0 + for (i = 0; i <= radius; i++) { + stack_i = i; + stack[stack_i] = src[src_i]; + sum_r += ((src[src_i] >>> 16) & 0xff) * (i + 1); + sum_g += ((src[src_i] >>> 8) & 0xff) * (i + 1); + sum_b += (src[src_i] & 0xff) * (i + 1); + sum_out_r += ((src[src_i] >>> 16) & 0xff); + sum_out_g += ((src[src_i] >>> 8) & 0xff); + sum_out_b += (src[src_i] & 0xff); + } + for (i = 1; i <= radius; i++) { + if (i <= hm) src_i += w; // +stride + + stack_i = i + radius; + stack[stack_i] = src[src_i]; + sum_r += ((src[src_i] >>> 16) & 0xff) * (radius + 1 - i); + sum_g += ((src[src_i] >>> 8) & 0xff) * (radius + 1 - i); + sum_b += (src[src_i] & 0xff) * (radius + 1 - i); + sum_in_r += ((src[src_i] >>> 16) & 0xff); + sum_in_g += ((src[src_i] >>> 8) & 0xff); + sum_in_b += (src[src_i] & 0xff); + } + + sp = radius; + yp = radius; + if (yp > hm) yp = hm; + src_i = x + yp * w; // img.pix_ptr(x, yp); + dst_i = x; // img.pix_ptr(x, 0); + for (y = 0; y < h; y++) { + src[dst_i] = + (int) + ((src[dst_i] & 0xFFFFFFFF) + | ((((sum_r * mul_sum) >>> shr_sum) & 0xff) << 16) + | ((((sum_g * mul_sum) >>> shr_sum) & 0xff) << 8) + | ((((sum_b * mul_sum) >>> shr_sum) & 0xff))); + dst_i += w; + + sum_r -= sum_out_r; + sum_g -= sum_out_g; + sum_b -= sum_out_b; + + stack_start = sp + div - radius; + if (stack_start >= div) stack_start -= div; + stack_i = stack_start; + + sum_out_r -= ((stack[stack_i] >>> 16) & 0xff); + sum_out_g -= ((stack[stack_i] >>> 8) & 0xff); + sum_out_b -= (stack[stack_i] & 0xff); + + if (yp < hm) { + src_i += w; // stride + ++yp; + } + + stack[stack_i] = src[src_i]; + + sum_in_r += ((src[src_i] >>> 16) & 0xff); + sum_in_g += ((src[src_i] >>> 8) & 0xff); + sum_in_b += (src[src_i] & 0xff); + sum_r += sum_in_r; + sum_g += sum_in_g; + sum_b += sum_in_b; + + ++sp; + if (sp >= div) sp = 0; + stack_i = sp; + + sum_out_r += ((stack[stack_i] >>> 16) & 0xff); + sum_out_g += ((stack[stack_i] >>> 8) & 0xff); + sum_out_b += (stack[stack_i] & 0xff); + sum_in_r -= ((stack[stack_i] >>> 16) & 0xff); + sum_in_g -= ((stack[stack_i] >>> 8) & 0xff); + sum_in_b -= (stack[stack_i] & 0xff); + } + } + } + } + + private static class BlurTask implements Callable { + private final int[] _src; + private final int _w; + private final int _h; + private final int _radius; + private final int _totalCores; + private final int _coreIndex; + private final int _round; + + public BlurTask(int[] src, int w, int h, int radius, int totalCores, int coreIndex, int round) { + _src = src; + _w = w; + _h = h; + _radius = radius; + _totalCores = totalCores; + _coreIndex = coreIndex; + _round = round; + } + + @Override + public Void call() throws Exception { + blurIteration(_src, _w, _h, _radius, _totalCores, _coreIndex, _round); + return null; + } + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/StopWatch.kt b/app/src/main/java/code/name/monkey/retromusic/helper/StopWatch.kt index 00062167..b287bac4 100644 --- a/app/src/main/java/code/name/monkey/retromusic/helper/StopWatch.kt +++ b/app/src/main/java/code/name/monkey/retromusic/helper/StopWatch.kt @@ -1,17 +1,17 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.helper /** diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/menu/GenreMenuHelper.kt b/app/src/main/java/code/name/monkey/retromusic/helper/menu/GenreMenuHelper.kt index 8f40adb5..7aba2b09 100644 --- a/app/src/main/java/code/name/monkey/retromusic/helper/menu/GenreMenuHelper.kt +++ b/app/src/main/java/code/name/monkey/retromusic/helper/menu/GenreMenuHelper.kt @@ -1,17 +1,17 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.helper.menu import android.view.MenuItem diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/menu/PlaylistMenuHelper.kt b/app/src/main/java/code/name/monkey/retromusic/helper/menu/PlaylistMenuHelper.kt index 971193e0..92f35bf2 100644 --- a/app/src/main/java/code/name/monkey/retromusic/helper/menu/PlaylistMenuHelper.kt +++ b/app/src/main/java/code/name/monkey/retromusic/helper/menu/PlaylistMenuHelper.kt @@ -1,20 +1,19 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.helper.menu - import android.view.MenuItem import androidx.fragment.app.FragmentActivity import code.name.monkey.retromusic.R @@ -33,12 +32,12 @@ import kotlinx.coroutines.withContext import org.koin.core.KoinComponent import org.koin.core.get - object PlaylistMenuHelper : KoinComponent { fun handleMenuClick( activity: FragmentActivity, - playlistWithSongs: PlaylistWithSongs, item: MenuItem + playlistWithSongs: PlaylistWithSongs, + item: MenuItem ): Boolean { when (item.itemId) { R.id.action_play -> { diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/menu/SongMenuHelper.kt b/app/src/main/java/code/name/monkey/retromusic/helper/menu/SongMenuHelper.kt index ac161880..76a6b585 100644 --- a/app/src/main/java/code/name/monkey/retromusic/helper/menu/SongMenuHelper.kt +++ b/app/src/main/java/code/name/monkey/retromusic/helper/menu/SongMenuHelper.kt @@ -1,17 +1,17 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.helper.menu import android.content.Intent diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/menu/SongsMenuHelper.kt b/app/src/main/java/code/name/monkey/retromusic/helper/menu/SongsMenuHelper.kt index 912e3c81..63ef5883 100644 --- a/app/src/main/java/code/name/monkey/retromusic/helper/menu/SongsMenuHelper.kt +++ b/app/src/main/java/code/name/monkey/retromusic/helper/menu/SongsMenuHelper.kt @@ -1,17 +1,17 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.helper.menu import androidx.fragment.app.FragmentActivity diff --git a/app/src/main/java/code/name/monkey/retromusic/interfaces/IAlbumClickListener.kt b/app/src/main/java/code/name/monkey/retromusic/interfaces/IAlbumClickListener.kt index 670a07de..c5412d0f 100644 --- a/app/src/main/java/code/name/monkey/retromusic/interfaces/IAlbumClickListener.kt +++ b/app/src/main/java/code/name/monkey/retromusic/interfaces/IAlbumClickListener.kt @@ -1,7 +1,21 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.interfaces import android.view.View interface IAlbumClickListener { fun onAlbumClick(albumId: Long, view: View) -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/interfaces/IArtistClickListener.kt b/app/src/main/java/code/name/monkey/retromusic/interfaces/IArtistClickListener.kt index 31e98076..f1c1f745 100644 --- a/app/src/main/java/code/name/monkey/retromusic/interfaces/IArtistClickListener.kt +++ b/app/src/main/java/code/name/monkey/retromusic/interfaces/IArtistClickListener.kt @@ -1,7 +1,21 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.interfaces import android.view.View interface IArtistClickListener { fun onArtist(artistId: Long, view: View) -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/interfaces/ICabHolder.kt b/app/src/main/java/code/name/monkey/retromusic/interfaces/ICabHolder.kt index c444933c..81052035 100644 --- a/app/src/main/java/code/name/monkey/retromusic/interfaces/ICabHolder.kt +++ b/app/src/main/java/code/name/monkey/retromusic/interfaces/ICabHolder.kt @@ -1,22 +1,21 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.interfaces import com.afollestad.materialcab.MaterialCab - interface ICabHolder { fun openCab(menuRes: Int, callback: MaterialCab.Callback): MaterialCab diff --git a/app/src/main/java/code/name/monkey/retromusic/interfaces/ICallbacks.kt b/app/src/main/java/code/name/monkey/retromusic/interfaces/ICallbacks.kt index 205970f4..b44a01f0 100644 --- a/app/src/main/java/code/name/monkey/retromusic/interfaces/ICallbacks.kt +++ b/app/src/main/java/code/name/monkey/retromusic/interfaces/ICallbacks.kt @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2020 Hemanth Savarla. + * + * 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.interfaces import android.view.MenuItem @@ -10,4 +24,4 @@ interface ICallbacks { fun onFileMenuClicked(file: File, view: View) fun onMultipleItemAction(item: MenuItem, files: ArrayList) -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/interfaces/IMainActivityFragmentCallbacks.kt b/app/src/main/java/code/name/monkey/retromusic/interfaces/IMainActivityFragmentCallbacks.kt index 03404014..14a93c47 100644 --- a/app/src/main/java/code/name/monkey/retromusic/interfaces/IMainActivityFragmentCallbacks.kt +++ b/app/src/main/java/code/name/monkey/retromusic/interfaces/IMainActivityFragmentCallbacks.kt @@ -1,15 +1,16 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.interfaces @@ -18,4 +19,4 @@ package code.name.monkey.retromusic.interfaces */ interface IMainActivityFragmentCallbacks { fun handleBackPress(): Boolean -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/interfaces/IMusicServiceEventListener.kt b/app/src/main/java/code/name/monkey/retromusic/interfaces/IMusicServiceEventListener.kt index 15e7a039..29669a3a 100644 --- a/app/src/main/java/code/name/monkey/retromusic/interfaces/IMusicServiceEventListener.kt +++ b/app/src/main/java/code/name/monkey/retromusic/interfaces/IMusicServiceEventListener.kt @@ -1,20 +1,19 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.interfaces - interface IMusicServiceEventListener { fun onServiceConnected() diff --git a/app/src/main/java/code/name/monkey/retromusic/interfaces/IPaletteColorHolder.kt b/app/src/main/java/code/name/monkey/retromusic/interfaces/IPaletteColorHolder.kt index e0534973..82c61707 100644 --- a/app/src/main/java/code/name/monkey/retromusic/interfaces/IPaletteColorHolder.kt +++ b/app/src/main/java/code/name/monkey/retromusic/interfaces/IPaletteColorHolder.kt @@ -1,17 +1,17 @@ /* - * Copyright (c) 2019 Hemanth Savarala. + * Copyright (c) 2020 Hemanth Savarla. * * 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 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.interfaces /** diff --git a/app/src/main/java/code/name/monkey/retromusic/lyrics/Lrc.java b/app/src/main/java/code/name/monkey/retromusic/lyrics/Lrc.java index 9405c68e..b3a0c736 100644 --- a/app/src/main/java/code/name/monkey/retromusic/lyrics/Lrc.java +++ b/app/src/main/java/code/name/monkey/retromusic/lyrics/Lrc.java @@ -1,30 +1,26 @@ package code.name.monkey.retromusic.lyrics; /** - * Desc : 歌词实体 - * Author : Lauzy - * Date : 2017/10/13 - * Blog : http://www.jianshu.com/u/e76853f863a9 - * Email : freedompaladin@gmail.com + * Desc : 歌词实体 Author : Lauzy Date : 2017/10/13 Blog : http://www.jianshu.com/u/e76853f863a9 Email : + * freedompaladin@gmail.com */ public class Lrc { - private long time; - private String text; + private long time; + private String text; - public long getTime() { - return time; - } + public long getTime() { + return time; + } - public void setTime(long time) { - this.time = time; - } + public void setTime(long time) { + this.time = time; + } - public String getText() { - return text; - } + public String getText() { + return text; + } - public void setText(String text) { - this.text = text; - } - -} \ No newline at end of file + public void setText(String text) { + this.text = text; + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcEntry.java b/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcEntry.java index 66e20083..81682f8a 100644 --- a/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcEntry.java +++ b/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcEntry.java @@ -19,99 +19,94 @@ import android.text.StaticLayout; import android.text.TextPaint; import android.text.TextUtils; -/** - * 一行歌词实体 - */ +/** 一行歌词实体 */ class LrcEntry implements Comparable { - public static final int GRAVITY_CENTER = 0; - public static final int GRAVITY_LEFT = 1; - public static final int GRAVITY_RIGHT = 2; - private long time; - private String text; - private String secondText; - private StaticLayout staticLayout; - /** - * 歌词距离视图顶部的距离 - */ - private float offset = Float.MIN_VALUE; + public static final int GRAVITY_CENTER = 0; + public static final int GRAVITY_LEFT = 1; + public static final int GRAVITY_RIGHT = 2; + private long time; + private String text; + private String secondText; + private StaticLayout staticLayout; + /** 歌词距离视图顶部的距离 */ + private float offset = Float.MIN_VALUE; - LrcEntry(long time, String text) { - this.time = time; - this.text = text; + LrcEntry(long time, String text) { + this.time = time; + this.text = text; + } + + LrcEntry(long time, String text, String secondText) { + this.time = time; + this.text = text; + this.secondText = secondText; + } + + void init(TextPaint paint, int width, int gravity) { + Layout.Alignment align; + switch (gravity) { + case GRAVITY_LEFT: + align = Layout.Alignment.ALIGN_NORMAL; + break; + + default: + case GRAVITY_CENTER: + align = Layout.Alignment.ALIGN_CENTER; + break; + + case GRAVITY_RIGHT: + align = Layout.Alignment.ALIGN_OPPOSITE; + break; } + staticLayout = new StaticLayout(getShowText(), paint, width, align, 1f, 0f, false); - LrcEntry(long time, String text, String secondText) { - this.time = time; - this.text = text; - this.secondText = secondText; + offset = Float.MIN_VALUE; + } + + long getTime() { + return time; + } + + StaticLayout getStaticLayout() { + return staticLayout; + } + + int getHeight() { + if (staticLayout == null) { + return 0; } + return staticLayout.getHeight(); + } - void init(TextPaint paint, int width, int gravity) { - Layout.Alignment align; - switch (gravity) { - case GRAVITY_LEFT: - align = Layout.Alignment.ALIGN_NORMAL; - break; + public float getOffset() { + return offset; + } - default: - case GRAVITY_CENTER: - align = Layout.Alignment.ALIGN_CENTER; - break; + public void setOffset(float offset) { + this.offset = offset; + } - case GRAVITY_RIGHT: - align = Layout.Alignment.ALIGN_OPPOSITE; - break; - } - staticLayout = new StaticLayout(getShowText(), paint, width, align, 1f, 0f, false); + String getText() { + return text; + } - offset = Float.MIN_VALUE; + void setSecondText(String secondText) { + this.secondText = secondText; + } + + private String getShowText() { + if (!TextUtils.isEmpty(secondText)) { + return text + "\n" + secondText; + } else { + return text; } + } - long getTime() { - return time; + @Override + public int compareTo(LrcEntry entry) { + if (entry == null) { + return -1; } - - StaticLayout getStaticLayout() { - return staticLayout; - } - - int getHeight() { - if (staticLayout == null) { - return 0; - } - return staticLayout.getHeight(); - } - - public float getOffset() { - return offset; - } - - public void setOffset(float offset) { - this.offset = offset; - } - - String getText() { - return text; - } - - - void setSecondText(String secondText) { - this.secondText = secondText; - } - - private String getShowText() { - if (!TextUtils.isEmpty(secondText)) { - return text + "\n" + secondText; - } else { - return text; - } - } - - @Override - public int compareTo(LrcEntry entry) { - if (entry == null) { - return -1; - } - return (int) (time - entry.getTime()); - } -} \ No newline at end of file + return (int) (time - entry.getTime()); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcHelper.java b/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcHelper.java index cf0e9970..d1b71467 100644 --- a/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcHelper.java +++ b/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcHelper.java @@ -1,7 +1,6 @@ package code.name.monkey.retromusic.lyrics; import android.content.Context; - import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; @@ -18,120 +17,121 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; /** - * Desc : 歌词解析 - * Author : Lauzy - * Date : 2017/10/13 - * Blog : http://www.jianshu.com/u/e76853f863a9 - * Email : freedompaladin@gmail.com + * Desc : 歌词解析 Author : Lauzy Date : 2017/10/13 Blog : http://www.jianshu.com/u/e76853f863a9 Email : + * freedompaladin@gmail.com */ public class LrcHelper { - private static final String CHARSET = "utf-8"; - //[03:56.00][03:18.00][02:06.00][01:07.00]原谅我这一生不羁放纵爱自由 - private static final String LINE_REGEX = "((\\[\\d{2}:\\d{2}\\.\\d{2}])+)(.*)"; - private static final String TIME_REGEX = "\\[(\\d{2}):(\\d{2})\\.(\\d{2})]"; + private static final String CHARSET = "utf-8"; + // [03:56.00][03:18.00][02:06.00][01:07.00]原谅我这一生不羁放纵爱自由 + private static final String LINE_REGEX = "((\\[\\d{2}:\\d{2}\\.\\d{2}])+)(.*)"; + private static final String TIME_REGEX = "\\[(\\d{2}):(\\d{2})\\.(\\d{2})]"; - public static List parseLrcFromAssets(Context context, String fileName) { - try { - return parseInputStream(context.getResources().getAssets().open(fileName)); - } catch (IOException e) { - e.printStackTrace(); - } - return null; + public static List parseLrcFromAssets(Context context, String fileName) { + try { + return parseInputStream(context.getResources().getAssets().open(fileName)); + } catch (IOException e) { + e.printStackTrace(); } + return null; + } - public static List parseLrcFromFile(File file) { - try { - return parseInputStream(new FileInputStream(file)); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } - return null; + public static List parseLrcFromFile(File file) { + try { + return parseInputStream(new FileInputStream(file)); + } catch (FileNotFoundException e) { + e.printStackTrace(); } + return null; + } - private static List parseInputStream(InputStream inputStream) { - List lrcs = new ArrayList<>(); - InputStreamReader isr = null; - BufferedReader br = null; - try { - isr = new InputStreamReader(inputStream, CHARSET); - br = new BufferedReader(isr); - String line; - while ((line = br.readLine()) != null) { - List lrcList = parseLrc(line); - if (lrcList != null && lrcList.size() != 0) { - lrcs.addAll(lrcList); - } - } - sortLrcs(lrcs); - return lrcs; - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } finally { - try { - if (isr != null) { - isr.close(); - } - if (br != null) { - br.close(); - } - } catch (IOException e1) { - e1.printStackTrace(); - } + private static List parseInputStream(InputStream inputStream) { + List lrcs = new ArrayList<>(); + InputStreamReader isr = null; + BufferedReader br = null; + try { + isr = new InputStreamReader(inputStream, CHARSET); + br = new BufferedReader(isr); + String line; + while ((line = br.readLine()) != null) { + List lrcList = parseLrc(line); + if (lrcList != null && lrcList.size() != 0) { + lrcs.addAll(lrcList); } - return lrcs; + } + sortLrcs(lrcs); + return lrcs; + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + if (isr != null) { + isr.close(); + } + if (br != null) { + br.close(); + } + } catch (IOException e1) { + e1.printStackTrace(); + } } + return lrcs; + } - private static void sortLrcs(List lrcs) { - Collections.sort(lrcs, new Comparator() { - @Override - public int compare(Lrc o1, Lrc o2) { - return (int) (o1.getTime() - o2.getTime()); - } + private static void sortLrcs(List lrcs) { + Collections.sort( + lrcs, + new Comparator() { + @Override + public int compare(Lrc o1, Lrc o2) { + return (int) (o1.getTime() - o2.getTime()); + } }); + } + + private static List parseLrc(String lrcLine) { + if (lrcLine.trim().isEmpty()) { + return null; + } + List lrcs = new ArrayList<>(); + Matcher matcher = Pattern.compile(LINE_REGEX).matcher(lrcLine); + if (!matcher.matches()) { + return null; } - private static List parseLrc(String lrcLine) { - if (lrcLine.trim().isEmpty()) { - return null; - } - List lrcs = new ArrayList<>(); - Matcher matcher = Pattern.compile(LINE_REGEX).matcher(lrcLine); - if (!matcher.matches()) { - return null; - } + String time = matcher.group(1); + String content = matcher.group(3); + Matcher timeMatcher = Pattern.compile(TIME_REGEX).matcher(time); - String time = matcher.group(1); - String content = matcher.group(3); - Matcher timeMatcher = Pattern.compile(TIME_REGEX).matcher(time); - - while (timeMatcher.find()) { - String min = timeMatcher.group(1); - String sec = timeMatcher.group(2); - String mil = timeMatcher.group(3); - Lrc lrc = new Lrc(); - if (content != null && content.length() != 0) { - lrc.setTime(Long.parseLong(min) * 60 * 1000 + Long.parseLong(sec) * 1000 - + Long.parseLong(mil) * 10); - lrc.setText(content); - lrcs.add(lrc); - } - } - return lrcs; + while (timeMatcher.find()) { + String min = timeMatcher.group(1); + String sec = timeMatcher.group(2); + String mil = timeMatcher.group(3); + Lrc lrc = new Lrc(); + if (content != null && content.length() != 0) { + lrc.setTime( + Long.parseLong(min) * 60 * 1000 + + Long.parseLong(sec) * 1000 + + Long.parseLong(mil) * 10); + lrc.setText(content); + lrcs.add(lrc); + } } + return lrcs; + } - public static String formatTime(long time) { - int min = (int) (time / 60000); - int sec = (int) (time / 1000 % 60); - return adjustFormat(min) + ":" + adjustFormat(sec); - } + public static String formatTime(long time) { + int min = (int) (time / 60000); + int sec = (int) (time / 1000 % 60); + return adjustFormat(min) + ":" + adjustFormat(sec); + } - private static String adjustFormat(int time) { - if (time < 10) { - return "0" + time; - } - return time + ""; + private static String adjustFormat(int time) { + if (time < 10) { + return "0" + time; } -} \ No newline at end of file + return time + ""; + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcUtils.java b/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcUtils.java index 23d7bfc3..a54c6c2e 100644 --- a/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcUtils.java +++ b/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcUtils.java @@ -17,7 +17,6 @@ package code.name.monkey.retromusic.lyrics; import android.animation.ValueAnimator; import android.text.TextUtils; import android.text.format.DateUtils; - import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.File; @@ -36,198 +35,186 @@ import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; -/** - * 工具类 - */ +/** 工具类 */ class LrcUtils { - private static final Pattern PATTERN_LINE = Pattern.compile("((\\[\\d\\d:\\d\\d\\.\\d{2,3}\\])+)(.+)"); - private static final Pattern PATTERN_TIME = Pattern.compile("\\[(\\d\\d):(\\d\\d)\\.(\\d{2,3})\\]"); + private static final Pattern PATTERN_LINE = + Pattern.compile("((\\[\\d\\d:\\d\\d\\.\\d{2,3}\\])+)(.+)"); + private static final Pattern PATTERN_TIME = + Pattern.compile("\\[(\\d\\d):(\\d\\d)\\.(\\d{2,3})\\]"); - /** - * 从文件解析双语歌词 - */ - static List parseLrc(File[] lrcFiles) { - if (lrcFiles == null || lrcFiles.length != 2 || lrcFiles[0] == null) { - return null; - } - - File mainLrcFile = lrcFiles[0]; - File secondLrcFile = lrcFiles[1]; - List mainEntryList = parseLrc(mainLrcFile); - List secondEntryList = parseLrc(secondLrcFile); - - if (mainEntryList != null && secondEntryList != null) { - for (LrcEntry mainEntry : mainEntryList) { - for (LrcEntry secondEntry : secondEntryList) { - if (mainEntry.getTime() == secondEntry.getTime()) { - mainEntry.setSecondText(secondEntry.getText()); - } - } - } - } - return mainEntryList; + /** 从文件解析双语歌词 */ + static List parseLrc(File[] lrcFiles) { + if (lrcFiles == null || lrcFiles.length != 2 || lrcFiles[0] == null) { + return null; } - /** - * 从文件解析歌词 - */ - private static List parseLrc(File lrcFile) { - if (lrcFile == null || !lrcFile.exists()) { - return null; - } + File mainLrcFile = lrcFiles[0]; + File secondLrcFile = lrcFiles[1]; + List mainEntryList = parseLrc(mainLrcFile); + List secondEntryList = parseLrc(secondLrcFile); - List entryList = new ArrayList<>(); - try { - BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(lrcFile), StandardCharsets.UTF_8)); - String line; - while ((line = br.readLine()) != null) { - List list = parseLine(line); - if (list != null && !list.isEmpty()) { - entryList.addAll(list); - } - } - br.close(); - } catch (IOException e) { - e.printStackTrace(); + if (mainEntryList != null && secondEntryList != null) { + for (LrcEntry mainEntry : mainEntryList) { + for (LrcEntry secondEntry : secondEntryList) { + if (mainEntry.getTime() == secondEntry.getTime()) { + mainEntry.setSecondText(secondEntry.getText()); + } } + } + } + return mainEntryList; + } - Collections.sort(entryList); - return entryList; + /** 从文件解析歌词 */ + private static List parseLrc(File lrcFile) { + if (lrcFile == null || !lrcFile.exists()) { + return null; } - /** - * 从文本解析双语歌词 - */ - static List parseLrc(String[] lrcTexts) { - if (lrcTexts == null || lrcTexts.length != 2 || TextUtils.isEmpty(lrcTexts[0])) { - return null; + List entryList = new ArrayList<>(); + try { + BufferedReader br = + new BufferedReader( + new InputStreamReader(new FileInputStream(lrcFile), StandardCharsets.UTF_8)); + String line; + while ((line = br.readLine()) != null) { + List list = parseLine(line); + if (list != null && !list.isEmpty()) { + entryList.addAll(list); } - - String mainLrcText = lrcTexts[0]; - String secondLrcText = lrcTexts[1]; - List mainEntryList = parseLrc(mainLrcText); - List secondEntryList = parseLrc(secondLrcText); - - if (mainEntryList != null && secondEntryList != null) { - for (LrcEntry mainEntry : mainEntryList) { - for (LrcEntry secondEntry : secondEntryList) { - if (mainEntry.getTime() == secondEntry.getTime()) { - mainEntry.setSecondText(secondEntry.getText()); - } - } - } - } - return mainEntryList; + } + br.close(); + } catch (IOException e) { + e.printStackTrace(); } - /** - * 从文本解析歌词 - */ - private static List parseLrc(String lrcText) { - if (TextUtils.isEmpty(lrcText)) { - return null; - } + Collections.sort(entryList); + return entryList; + } - if (lrcText.startsWith("\uFEFF")) { - lrcText = lrcText.replace("\uFEFF", ""); - } - - List entryList = new ArrayList<>(); - String[] array = lrcText.split("\\n"); - for (String line : array) { - List list = parseLine(line); - if (list != null && !list.isEmpty()) { - entryList.addAll(list); - } - } - - Collections.sort(entryList); - return entryList; + /** 从文本解析双语歌词 */ + static List parseLrc(String[] lrcTexts) { + if (lrcTexts == null || lrcTexts.length != 2 || TextUtils.isEmpty(lrcTexts[0])) { + return null; } - /** - * 获取网络文本,需要在工作线程中执行 - */ - static String getContentFromNetwork(String url, String charset) { - String lrcText = null; - try { - URL _url = new URL(url); - HttpURLConnection conn = (HttpURLConnection) _url.openConnection(); - conn.setRequestMethod("GET"); - conn.setConnectTimeout(10000); - conn.setReadTimeout(10000); - if (conn.getResponseCode() == 200) { - InputStream is = conn.getInputStream(); - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - byte[] buffer = new byte[1024]; - int len; - while ((len = is.read(buffer)) != -1) { - bos.write(buffer, 0, len); - } - is.close(); - bos.close(); - lrcText = bos.toString(charset); - } - } catch (Exception e) { - e.printStackTrace(); + String mainLrcText = lrcTexts[0]; + String secondLrcText = lrcTexts[1]; + List mainEntryList = parseLrc(mainLrcText); + List secondEntryList = parseLrc(secondLrcText); + + if (mainEntryList != null && secondEntryList != null) { + for (LrcEntry mainEntry : mainEntryList) { + for (LrcEntry secondEntry : secondEntryList) { + if (mainEntry.getTime() == secondEntry.getTime()) { + mainEntry.setSecondText(secondEntry.getText()); + } } - return lrcText; + } + } + return mainEntryList; + } + + /** 从文本解析歌词 */ + private static List parseLrc(String lrcText) { + if (TextUtils.isEmpty(lrcText)) { + return null; } - /** - * 解析一行歌词 - */ - private static List parseLine(String line) { - if (TextUtils.isEmpty(line)) { - return null; - } - - line = line.trim(); - // [00:17.65]让我掉下眼泪的 - Matcher lineMatcher = PATTERN_LINE.matcher(line); - if (!lineMatcher.matches()) { - return null; - } - - String times = lineMatcher.group(1); - String text = lineMatcher.group(3); - List entryList = new ArrayList<>(); - - // [00:17.65] - Matcher timeMatcher = PATTERN_TIME.matcher(times); - while (timeMatcher.find()) { - long min = Long.parseLong(timeMatcher.group(1)); - long sec = Long.parseLong(timeMatcher.group(2)); - String milString = timeMatcher.group(3); - long mil = Long.parseLong(milString); - // 如果毫秒是两位数,需要乘以10 - if (milString.length() == 2) { - mil = mil * 10; - } - long time = min * DateUtils.MINUTE_IN_MILLIS + sec * DateUtils.SECOND_IN_MILLIS + mil; - entryList.add(new LrcEntry(time, text)); - } - return entryList; + if (lrcText.startsWith("\uFEFF")) { + lrcText = lrcText.replace("\uFEFF", ""); } - /** - * 转为[分:秒] - */ - static String formatTime(long milli) { - int m = (int) (milli / DateUtils.MINUTE_IN_MILLIS); - int s = (int) ((milli / DateUtils.SECOND_IN_MILLIS) % 60); - String mm = String.format(Locale.getDefault(), "%02d", m); - String ss = String.format(Locale.getDefault(), "%02d", s); - return mm + ":" + ss; + List entryList = new ArrayList<>(); + String[] array = lrcText.split("\\n"); + for (String line : array) { + List list = parseLine(line); + if (list != null && !list.isEmpty()) { + entryList.addAll(list); + } } - static void resetDurationScale() { - try { - Field mField = ValueAnimator.class.getDeclaredField("sDurationScale"); - mField.setAccessible(true); - mField.setFloat(null, 1); - } catch (Exception e) { - e.printStackTrace(); + Collections.sort(entryList); + return entryList; + } + + /** 获取网络文本,需要在工作线程中执行 */ + static String getContentFromNetwork(String url, String charset) { + String lrcText = null; + try { + URL _url = new URL(url); + HttpURLConnection conn = (HttpURLConnection) _url.openConnection(); + conn.setRequestMethod("GET"); + conn.setConnectTimeout(10000); + conn.setReadTimeout(10000); + if (conn.getResponseCode() == 200) { + InputStream is = conn.getInputStream(); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int len; + while ((len = is.read(buffer)) != -1) { + bos.write(buffer, 0, len); } + is.close(); + bos.close(); + lrcText = bos.toString(charset); + } + } catch (Exception e) { + e.printStackTrace(); } -} \ No newline at end of file + return lrcText; + } + + /** 解析一行歌词 */ + private static List parseLine(String line) { + if (TextUtils.isEmpty(line)) { + return null; + } + + line = line.trim(); + // [00:17.65]让我掉下眼泪的 + Matcher lineMatcher = PATTERN_LINE.matcher(line); + if (!lineMatcher.matches()) { + return null; + } + + String times = lineMatcher.group(1); + String text = lineMatcher.group(3); + List entryList = new ArrayList<>(); + + // [00:17.65] + Matcher timeMatcher = PATTERN_TIME.matcher(times); + while (timeMatcher.find()) { + long min = Long.parseLong(timeMatcher.group(1)); + long sec = Long.parseLong(timeMatcher.group(2)); + String milString = timeMatcher.group(3); + long mil = Long.parseLong(milString); + // 如果毫秒是两位数,需要乘以10 + if (milString.length() == 2) { + mil = mil * 10; + } + long time = min * DateUtils.MINUTE_IN_MILLIS + sec * DateUtils.SECOND_IN_MILLIS + mil; + entryList.add(new LrcEntry(time, text)); + } + return entryList; + } + + /** 转为[分:秒] */ + static String formatTime(long milli) { + int m = (int) (milli / DateUtils.MINUTE_IN_MILLIS); + int s = (int) ((milli / DateUtils.SECOND_IN_MILLIS) % 60); + String mm = String.format(Locale.getDefault(), "%02d", m); + String ss = String.format(Locale.getDefault(), "%02d", s); + return mm + ":" + ss; + } + + static void resetDurationScale() { + try { + Field mField = ValueAnimator.class.getDeclaredField("sDurationScale"); + mField.setAccessible(true); + mField.setFloat(null, 1); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcView.java b/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcView.java index 63c4b8cf..92eec91f 100644 --- a/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcView.java +++ b/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcView.java @@ -34,720 +34,739 @@ import android.view.MotionEvent; import android.view.View; import android.view.animation.LinearInterpolator; import android.widget.Scroller; - +import code.name.monkey.retromusic.BuildConfig; +import code.name.monkey.retromusic.R; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import code.name.monkey.retromusic.BuildConfig; -import code.name.monkey.retromusic.R; - -/** - * 歌词 - * Created by wcy on 2015/11/9. - */ +/** 歌词 Created by wcy on 2015/11/9. */ @SuppressLint("StaticFieldLeak") public class LrcView extends View { - private static final long ADJUST_DURATION = 100; - private static final long TIMELINE_KEEP_TIME = 4 * DateUtils.SECOND_IN_MILLIS; + private static final long ADJUST_DURATION = 100; + private static final long TIMELINE_KEEP_TIME = 4 * DateUtils.SECOND_IN_MILLIS; - private List mLrcEntryList = new ArrayList<>(); - private TextPaint mLrcPaint = new TextPaint(); - private TextPaint mTimePaint = new TextPaint(); - private Paint.FontMetrics mTimeFontMetrics; - private Drawable mPlayDrawable; - private float mDividerHeight; - private long mAnimationDuration; - private int mNormalTextColor; - private float mNormalTextSize; - private int mCurrentTextColor; - private float mCurrentTextSize; - private int mTimelineTextColor; - private int mTimelineColor; - private int mTimeTextColor; - private int mDrawableWidth; - private int mTimeTextWidth; - private String mDefaultLabel; - private float mLrcPadding; - private OnPlayClickListener mOnPlayClickListener; - private ValueAnimator mAnimator; - private GestureDetector mGestureDetector; - private Scroller mScroller; - private float mOffset; - private int mCurrentLine; - private Object mFlag; - private boolean isShowTimeline; - private boolean isTouching; - private boolean isFling; - private int mTextGravity;//歌词显示位置,靠左/居中/靠右 - private Runnable hideTimelineRunnable = new Runnable() { + private List mLrcEntryList = new ArrayList<>(); + private TextPaint mLrcPaint = new TextPaint(); + private TextPaint mTimePaint = new TextPaint(); + private Paint.FontMetrics mTimeFontMetrics; + private Drawable mPlayDrawable; + private float mDividerHeight; + private long mAnimationDuration; + private int mNormalTextColor; + private float mNormalTextSize; + private int mCurrentTextColor; + private float mCurrentTextSize; + private int mTimelineTextColor; + private int mTimelineColor; + private int mTimeTextColor; + private int mDrawableWidth; + private int mTimeTextWidth; + private String mDefaultLabel; + private float mLrcPadding; + private OnPlayClickListener mOnPlayClickListener; + private ValueAnimator mAnimator; + private GestureDetector mGestureDetector; + private Scroller mScroller; + private float mOffset; + private int mCurrentLine; + private Object mFlag; + private boolean isShowTimeline; + private boolean isTouching; + private boolean isFling; + private int mTextGravity; // 歌词显示位置,靠左/居中/靠右 + private Runnable hideTimelineRunnable = + new Runnable() { @Override public void run() { - if (hasLrc() && isShowTimeline) { - isShowTimeline = false; - smoothScrollTo(mCurrentLine); - } + if (hasLrc() && isShowTimeline) { + isShowTimeline = false; + smoothScrollTo(mCurrentLine); + } } - }; - /** - * 手势监听器 - */ - private GestureDetector.SimpleOnGestureListener mSimpleOnGestureListener = new GestureDetector.SimpleOnGestureListener() { + }; + /** 手势监听器 */ + private GestureDetector.SimpleOnGestureListener mSimpleOnGestureListener = + new GestureDetector.SimpleOnGestureListener() { @Override public boolean onDown(MotionEvent e) { - if (hasLrc() && mOnPlayClickListener != null) { - mScroller.forceFinished(true); - removeCallbacks(hideTimelineRunnable); - isTouching = true; - isShowTimeline = true; - invalidate(); - return true; - } - return super.onDown(e); + if (hasLrc() && mOnPlayClickListener != null) { + mScroller.forceFinished(true); + removeCallbacks(hideTimelineRunnable); + isTouching = true; + isShowTimeline = true; + invalidate(); + return true; + } + return super.onDown(e); } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { - if (hasLrc()) { - mOffset += -distanceY; - mOffset = Math.min(mOffset, getOffset(0)); - mOffset = Math.max(mOffset, getOffset(mLrcEntryList.size() - 1)); - invalidate(); - return true; - } - return super.onScroll(e1, e2, distanceX, distanceY); + if (hasLrc()) { + mOffset += -distanceY; + mOffset = Math.min(mOffset, getOffset(0)); + mOffset = Math.max(mOffset, getOffset(mLrcEntryList.size() - 1)); + invalidate(); + return true; + } + return super.onScroll(e1, e2, distanceX, distanceY); } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { - if (hasLrc()) { - mScroller.fling(0, (int) mOffset, 0, (int) velocityY, 0, 0, (int) getOffset(mLrcEntryList.size() - 1), (int) getOffset(0)); - isFling = true; - return true; - } - return super.onFling(e1, e2, velocityX, velocityY); + if (hasLrc()) { + mScroller.fling( + 0, + (int) mOffset, + 0, + (int) velocityY, + 0, + 0, + (int) getOffset(mLrcEntryList.size() - 1), + (int) getOffset(0)); + isFling = true; + return true; + } + return super.onFling(e1, e2, velocityX, velocityY); } @Override public boolean onSingleTapConfirmed(MotionEvent e) { - if (hasLrc() && isShowTimeline && mPlayDrawable.getBounds().contains((int) e.getX(), (int) e.getY())) { - int centerLine = getCenterLine(); - long centerLineTime = mLrcEntryList.get(centerLine).getTime(); - // onPlayClick 消费了才更新 UI - if (mOnPlayClickListener != null && mOnPlayClickListener.onPlayClick(centerLineTime)) { - isShowTimeline = false; - removeCallbacks(hideTimelineRunnable); - mCurrentLine = centerLine; - invalidate(); - return true; - } + if (hasLrc() + && isShowTimeline + && mPlayDrawable.getBounds().contains((int) e.getX(), (int) e.getY())) { + int centerLine = getCenterLine(); + long centerLineTime = mLrcEntryList.get(centerLine).getTime(); + // onPlayClick 消费了才更新 UI + if (mOnPlayClickListener != null && mOnPlayClickListener.onPlayClick(centerLineTime)) { + isShowTimeline = false; + removeCallbacks(hideTimelineRunnable); + mCurrentLine = centerLine; + invalidate(); + return true; } - return super.onSingleTapConfirmed(e); + } + return super.onSingleTapConfirmed(e); } - }; + }; - public LrcView(Context context) { - this(context, null); + public LrcView(Context context) { + this(context, null); + } + + public LrcView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public LrcView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(attrs); + } + + private void init(AttributeSet attrs) { + TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.LrcView); + mCurrentTextSize = + ta.getDimension( + R.styleable.LrcView_lrcTextSize, getResources().getDimension(R.dimen.lrc_text_size)); + mNormalTextSize = + ta.getDimension( + R.styleable.LrcView_lrcNormalTextSize, + getResources().getDimension(R.dimen.lrc_text_size)); + if (mNormalTextSize == 0) { + mNormalTextSize = mCurrentTextSize; } - public LrcView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } + mDividerHeight = + ta.getDimension( + R.styleable.LrcView_lrcDividerHeight, + getResources().getDimension(R.dimen.lrc_divider_height)); + int defDuration = getResources().getInteger(R.integer.lrc_animation_duration); + mAnimationDuration = ta.getInt(R.styleable.LrcView_lrcAnimationDuration, defDuration); + mAnimationDuration = (mAnimationDuration < 0) ? defDuration : mAnimationDuration; + mNormalTextColor = + ta.getColor( + R.styleable.LrcView_lrcNormalTextColor, + getResources().getColor(R.color.lrc_normal_text_color)); + mCurrentTextColor = + ta.getColor( + R.styleable.LrcView_lrcCurrentTextColor, + getResources().getColor(R.color.lrc_current_text_color)); + mTimelineTextColor = + ta.getColor( + R.styleable.LrcView_lrcTimelineTextColor, + getResources().getColor(R.color.lrc_timeline_text_color)); + mDefaultLabel = ta.getString(R.styleable.LrcView_lrcLabel); + mDefaultLabel = + TextUtils.isEmpty(mDefaultLabel) ? getContext().getString(R.string.empty) : mDefaultLabel; + mLrcPadding = ta.getDimension(R.styleable.LrcView_lrcPadding, 0); + mTimelineColor = + ta.getColor( + R.styleable.LrcView_lrcTimelineColor, + getResources().getColor(R.color.lrc_timeline_color)); + float timelineHeight = + ta.getDimension( + R.styleable.LrcView_lrcTimelineHeight, + getResources().getDimension(R.dimen.lrc_timeline_height)); + mPlayDrawable = ta.getDrawable(R.styleable.LrcView_lrcPlayDrawable); + mPlayDrawable = + (mPlayDrawable == null) + ? getResources().getDrawable(R.drawable.ic_play_arrow) + : mPlayDrawable; + mTimeTextColor = + ta.getColor( + R.styleable.LrcView_lrcTimeTextColor, + getResources().getColor(R.color.lrc_time_text_color)); + float timeTextSize = + ta.getDimension( + R.styleable.LrcView_lrcTimeTextSize, + getResources().getDimension(R.dimen.lrc_time_text_size)); + mTextGravity = ta.getInteger(R.styleable.LrcView_lrcTextGravity, LrcEntry.GRAVITY_CENTER); - public LrcView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(attrs); - } + ta.recycle(); - private void init(AttributeSet attrs) { - TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.LrcView); - mCurrentTextSize = ta.getDimension(R.styleable.LrcView_lrcTextSize, getResources().getDimension(R.dimen.lrc_text_size)); - mNormalTextSize = ta.getDimension(R.styleable.LrcView_lrcNormalTextSize, getResources().getDimension(R.dimen.lrc_text_size)); - if (mNormalTextSize == 0) { - mNormalTextSize = mCurrentTextSize; + mDrawableWidth = (int) getResources().getDimension(R.dimen.lrc_drawable_width); + mTimeTextWidth = (int) getResources().getDimension(R.dimen.lrc_time_width); + + mLrcPaint.setAntiAlias(true); + mLrcPaint.setTextSize(mCurrentTextSize); + mLrcPaint.setTextAlign(Paint.Align.LEFT); + mTimePaint.setAntiAlias(true); + mTimePaint.setTextSize(timeTextSize); + mTimePaint.setTextAlign(Paint.Align.CENTER); + //noinspection SuspiciousNameCombination + mTimePaint.setStrokeWidth(timelineHeight); + mTimePaint.setStrokeCap(Paint.Cap.ROUND); + mTimeFontMetrics = mTimePaint.getFontMetrics(); + + mGestureDetector = new GestureDetector(getContext(), mSimpleOnGestureListener); + mGestureDetector.setIsLongpressEnabled(false); + mScroller = new Scroller(getContext()); + } + + /** 设置非当前行歌词字体颜色 */ + public void setNormalColor(int normalColor) { + mNormalTextColor = normalColor; + postInvalidate(); + } + + /** 普通歌词文本字体大小 */ + public void setNormalTextSize(float size) { + mNormalTextSize = size; + } + + /** 当前歌词文本字体大小 */ + public void setCurrentTextSize(float size) { + mCurrentTextSize = size; + } + + /** 设置当前行歌词的字体颜色 */ + public void setCurrentColor(int currentColor) { + mCurrentTextColor = currentColor; + postInvalidate(); + } + + /** 设置拖动歌词时选中歌词的字体颜色 */ + public void setTimelineTextColor(int timelineTextColor) { + mTimelineTextColor = timelineTextColor; + postInvalidate(); + } + + /** 设置拖动歌词时时间线的颜色 */ + public void setTimelineColor(int timelineColor) { + mTimelineColor = timelineColor; + postInvalidate(); + } + + /** 设置拖动歌词时右侧时间字体颜色 */ + public void setTimeTextColor(int timeTextColor) { + mTimeTextColor = timeTextColor; + postInvalidate(); + } + + /** + * 设置歌词是否允许拖动 + * + * @param draggable 是否允许拖动 + * @param onPlayClickListener 设置歌词拖动后播放按钮点击监听器,如果允许拖动,则不能为 null + */ + public void setDraggable(boolean draggable, OnPlayClickListener onPlayClickListener) { + if (draggable) { + if (onPlayClickListener == null) { + throw new IllegalArgumentException( + "if draggable == true, onPlayClickListener must not be null"); + } + mOnPlayClickListener = onPlayClickListener; + } else { + mOnPlayClickListener = null; + } + } + + /** + * 设置播放按钮点击监听器 + * + * @param onPlayClickListener 如果为非 null ,则激活歌词拖动功能,否则将将禁用歌词拖动功能 + * @deprecated use {@link #setDraggable(boolean, OnPlayClickListener)} instead + */ + @Deprecated + public void setOnPlayClickListener(OnPlayClickListener onPlayClickListener) { + mOnPlayClickListener = onPlayClickListener; + } + + /** 设置歌词为空时屏幕中央显示的文字,如“暂无歌词” */ + public void setLabel(String label) { + runOnUi( + () -> { + mDefaultLabel = label; + invalidate(); + }); + } + + /** + * 加载歌词文件 + * + * @param lrcFile 歌词文件 + */ + public void loadLrc(File lrcFile) { + loadLrc(lrcFile, null); + } + + /** + * 加载双语歌词文件,两种语言的歌词时间戳需要一致 + * + * @param mainLrcFile 第一种语言歌词文件 + * @param secondLrcFile 第二种语言歌词文件 + */ + public void loadLrc(File mainLrcFile, File secondLrcFile) { + runOnUi( + () -> { + reset(); + + StringBuilder sb = new StringBuilder("file://"); + sb.append(mainLrcFile.getPath()); + if (secondLrcFile != null) { + sb.append("#").append(secondLrcFile.getPath()); + } + String flag = sb.toString(); + setFlag(flag); + new AsyncTask>() { + @Override + protected List doInBackground(File... params) { + return LrcUtils.parseLrc(params); + } + + @Override + protected void onPostExecute(List lrcEntries) { + if (getFlag() == flag) { + onLrcLoaded(lrcEntries); + setFlag(null); + } + } + }.execute(mainLrcFile, secondLrcFile); + }); + } + + /** + * 加载歌词文本 + * + * @param lrcText 歌词文本 + */ + public void loadLrc(String lrcText) { + loadLrc(lrcText, null); + } + + /** + * 加载双语歌词文本,两种语言的歌词时间戳需要一致 + * + * @param mainLrcText 第一种语言歌词文本 + * @param secondLrcText 第二种语言歌词文本 + */ + public void loadLrc(String mainLrcText, String secondLrcText) { + runOnUi( + () -> { + reset(); + + StringBuilder sb = new StringBuilder("file://"); + sb.append(mainLrcText); + if (secondLrcText != null) { + sb.append("#").append(secondLrcText); + } + String flag = sb.toString(); + setFlag(flag); + new AsyncTask>() { + @Override + protected List doInBackground(String... params) { + return LrcUtils.parseLrc(params); + } + + @Override + protected void onPostExecute(List lrcEntries) { + if (getFlag() == flag) { + onLrcLoaded(lrcEntries); + setFlag(null); + } + } + }.execute(mainLrcText, secondLrcText); + }); + } + + /** + * 加载在线歌词,默认使用 utf-8 编码 + * + * @param lrcUrl 歌词文件的网络地址 + */ + public void loadLrcByUrl(String lrcUrl) { + loadLrcByUrl(lrcUrl, "utf-8"); + } + + /** + * 加载在线歌词 + * + * @param lrcUrl 歌词文件的网络地址 + * @param charset 编码格式 + */ + public void loadLrcByUrl(String lrcUrl, String charset) { + String flag = "url://" + lrcUrl; + setFlag(flag); + new AsyncTask() { + @Override + protected String doInBackground(String... params) { + return LrcUtils.getContentFromNetwork(params[0], params[1]); + } + + @Override + protected void onPostExecute(String lrcText) { + if (getFlag() == flag) { + loadLrc(lrcText); } + } + }.execute(lrcUrl, charset); + } - mDividerHeight = ta.getDimension(R.styleable.LrcView_lrcDividerHeight, getResources().getDimension(R.dimen.lrc_divider_height)); - int defDuration = getResources().getInteger(R.integer.lrc_animation_duration); - mAnimationDuration = ta.getInt(R.styleable.LrcView_lrcAnimationDuration, defDuration); - mAnimationDuration = (mAnimationDuration < 0) ? defDuration : mAnimationDuration; - mNormalTextColor = ta.getColor(R.styleable.LrcView_lrcNormalTextColor, getResources().getColor(R.color.lrc_normal_text_color)); - mCurrentTextColor = ta.getColor(R.styleable.LrcView_lrcCurrentTextColor, getResources().getColor(R.color.lrc_current_text_color)); - mTimelineTextColor = ta.getColor(R.styleable.LrcView_lrcTimelineTextColor, getResources().getColor(R.color.lrc_timeline_text_color)); - mDefaultLabel = ta.getString(R.styleable.LrcView_lrcLabel); - mDefaultLabel = TextUtils.isEmpty(mDefaultLabel) ? getContext().getString(R.string.empty) : mDefaultLabel; - mLrcPadding = ta.getDimension(R.styleable.LrcView_lrcPadding, 0); - mTimelineColor = ta.getColor(R.styleable.LrcView_lrcTimelineColor, getResources().getColor(R.color.lrc_timeline_color)); - float timelineHeight = ta.getDimension(R.styleable.LrcView_lrcTimelineHeight, getResources().getDimension(R.dimen.lrc_timeline_height)); - mPlayDrawable = ta.getDrawable(R.styleable.LrcView_lrcPlayDrawable); - mPlayDrawable = (mPlayDrawable == null) ? getResources().getDrawable(R.drawable.ic_play_arrow) : mPlayDrawable; - mTimeTextColor = ta.getColor(R.styleable.LrcView_lrcTimeTextColor, getResources().getColor(R.color.lrc_time_text_color)); - float timeTextSize = ta.getDimension(R.styleable.LrcView_lrcTimeTextSize, getResources().getDimension(R.dimen.lrc_time_text_size)); - mTextGravity = ta.getInteger(R.styleable.LrcView_lrcTextGravity, LrcEntry.GRAVITY_CENTER); + /** + * 歌词是否有效 + * + * @return true,如果歌词有效,否则false + */ + public boolean hasLrc() { + return !mLrcEntryList.isEmpty(); + } - ta.recycle(); + /** + * 刷新歌词 + * + * @param time 当前播放时间 + */ + public void updateTime(long time) { + runOnUi( + () -> { + if (!hasLrc()) { + return; + } - mDrawableWidth = (int) getResources().getDimension(R.dimen.lrc_drawable_width); - mTimeTextWidth = (int) getResources().getDimension(R.dimen.lrc_time_width); + int line = findShowLine(time); + if (line != mCurrentLine) { + mCurrentLine = line; + if (!isShowTimeline) { + smoothScrollTo(line); + } else { + invalidate(); + } + } + }); + } - mLrcPaint.setAntiAlias(true); + /** + * 将歌词滚动到指定时间 + * + * @param time 指定的时间 + * @deprecated 请使用 {@link #updateTime(long)} 代替 + */ + @Deprecated + public void onDrag(long time) { + updateTime(time); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + if (changed) { + initPlayDrawable(); + initEntryList(); + if (hasLrc()) { + smoothScrollTo(mCurrentLine, 0L); + } + } + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + int centerY = getHeight() / 2; + + // 无歌词文件 + if (!hasLrc()) { + mLrcPaint.setColor(mCurrentTextColor); + @SuppressLint("DrawAllocation") + StaticLayout staticLayout = + new StaticLayout( + mDefaultLabel, + mLrcPaint, + (int) getLrcWidth(), + Layout.Alignment.ALIGN_CENTER, + 1f, + 0f, + false); + drawText(canvas, staticLayout, centerY); + return; + } + + int centerLine = getCenterLine(); + + if (isShowTimeline) { + mPlayDrawable.draw(canvas); + + mTimePaint.setColor(mTimelineColor); + canvas.drawLine(mTimeTextWidth, centerY, getWidth() - mTimeTextWidth, centerY, mTimePaint); + + mTimePaint.setColor(mTimeTextColor); + String timeText = LrcUtils.formatTime(mLrcEntryList.get(centerLine).getTime()); + float timeX = getWidth() - mTimeTextWidth / 2; + float timeY = centerY - (mTimeFontMetrics.descent + mTimeFontMetrics.ascent) / 2; + canvas.drawText(timeText, timeX, timeY, mTimePaint); + } + + canvas.translate(0, mOffset); + + float y = 0; + for (int i = 0; i < mLrcEntryList.size(); i++) { + if (i > 0) { + y += + ((mLrcEntryList.get(i - 1).getHeight() + mLrcEntryList.get(i).getHeight()) >> 1) + + mDividerHeight; + } + if (BuildConfig.DEBUG) { + // mLrcPaint.setTypeface(ResourcesCompat.getFont(getContext(), R.font.sans)); + } + if (i == mCurrentLine) { mLrcPaint.setTextSize(mCurrentTextSize); - mLrcPaint.setTextAlign(Paint.Align.LEFT); - mTimePaint.setAntiAlias(true); - mTimePaint.setTextSize(timeTextSize); - mTimePaint.setTextAlign(Paint.Align.CENTER); - //noinspection SuspiciousNameCombination - mTimePaint.setStrokeWidth(timelineHeight); - mTimePaint.setStrokeCap(Paint.Cap.ROUND); - mTimeFontMetrics = mTimePaint.getFontMetrics(); + mLrcPaint.setColor(mCurrentTextColor); + } else if (isShowTimeline && i == centerLine) { + mLrcPaint.setColor(mTimelineTextColor); + } else { + mLrcPaint.setTextSize(mNormalTextSize); + mLrcPaint.setColor(mNormalTextColor); + } + drawText(canvas, mLrcEntryList.get(i).getStaticLayout(), y); + } + } - mGestureDetector = new GestureDetector(getContext(), mSimpleOnGestureListener); - mGestureDetector.setIsLongpressEnabled(false); - mScroller = new Scroller(getContext()); + /** + * 画一行歌词 + * + * @param y 歌词中心 Y 坐标 + */ + private void drawText(Canvas canvas, StaticLayout staticLayout, float y) { + canvas.save(); + canvas.translate(mLrcPadding, y - (staticLayout.getHeight() >> 1)); + staticLayout.draw(canvas); + canvas.restore(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_UP + || event.getAction() == MotionEvent.ACTION_CANCEL) { + isTouching = false; + if (hasLrc() && !isFling) { + adjustCenter(); + postDelayed(hideTimelineRunnable, TIMELINE_KEEP_TIME); + } + } + return mGestureDetector.onTouchEvent(event); + } + + @Override + public void computeScroll() { + if (mScroller.computeScrollOffset()) { + mOffset = mScroller.getCurrY(); + invalidate(); } - /** - * 设置非当前行歌词字体颜色 - */ - public void setNormalColor(int normalColor) { - mNormalTextColor = normalColor; - postInvalidate(); + if (isFling && mScroller.isFinished()) { + isFling = false; + if (hasLrc() && !isTouching) { + adjustCenter(); + postDelayed(hideTimelineRunnable, TIMELINE_KEEP_TIME); + } + } + } + + @Override + protected void onDetachedFromWindow() { + removeCallbacks(hideTimelineRunnable); + super.onDetachedFromWindow(); + } + + private void onLrcLoaded(List entryList) { + if (entryList != null && !entryList.isEmpty()) { + mLrcEntryList.addAll(entryList); } - /** - * 普通歌词文本字体大小 - */ - public void setNormalTextSize(float size) { - mNormalTextSize = size; + Collections.sort(mLrcEntryList); + + initEntryList(); + invalidate(); + } + + private void initPlayDrawable() { + int l = (mTimeTextWidth - mDrawableWidth) / 2; + int t = getHeight() / 2 - mDrawableWidth / 2; + int r = l + mDrawableWidth; + int b = t + mDrawableWidth; + mPlayDrawable.setBounds(l, t, r, b); + } + + private void initEntryList() { + if (!hasLrc() || getWidth() == 0) { + return; } - /** - * 当前歌词文本字体大小 - */ - public void setCurrentTextSize(float size) { - mCurrentTextSize = size; + for (LrcEntry lrcEntry : mLrcEntryList) { + lrcEntry.init(mLrcPaint, (int) getLrcWidth(), mTextGravity); } - /** - * 设置当前行歌词的字体颜色 - */ - public void setCurrentColor(int currentColor) { - mCurrentTextColor = currentColor; - postInvalidate(); - } + mOffset = getHeight() / 2; + } - /** - * 设置拖动歌词时选中歌词的字体颜色 - */ - public void setTimelineTextColor(int timelineTextColor) { - mTimelineTextColor = timelineTextColor; - postInvalidate(); - } + private void reset() { + endAnimation(); + mScroller.forceFinished(true); + isShowTimeline = false; + isTouching = false; + isFling = false; + removeCallbacks(hideTimelineRunnable); + mLrcEntryList.clear(); + mOffset = 0; + mCurrentLine = 0; + invalidate(); + } - /** - * 设置拖动歌词时时间线的颜色 - */ - public void setTimelineColor(int timelineColor) { - mTimelineColor = timelineColor; - postInvalidate(); - } + /** 将中心行微调至正中心 */ + private void adjustCenter() { + smoothScrollTo(getCenterLine(), ADJUST_DURATION); + } - /** - * 设置拖动歌词时右侧时间字体颜色 - */ - public void setTimeTextColor(int timeTextColor) { - mTimeTextColor = timeTextColor; - postInvalidate(); - } + /** 滚动到某一行 */ + private void smoothScrollTo(int line) { + smoothScrollTo(line, mAnimationDuration); + } - /** - * 设置歌词是否允许拖动 - * - * @param draggable 是否允许拖动 - * @param onPlayClickListener 设置歌词拖动后播放按钮点击监听器,如果允许拖动,则不能为 null - */ - public void setDraggable(boolean draggable, OnPlayClickListener onPlayClickListener) { - if (draggable) { - if (onPlayClickListener == null) { - throw new IllegalArgumentException("if draggable == true, onPlayClickListener must not be null"); - } - mOnPlayClickListener = onPlayClickListener; - } else { - mOnPlayClickListener = null; - } - } + /** 滚动到某一行 */ + private void smoothScrollTo(int line, long duration) { + float offset = getOffset(line); + endAnimation(); - /** - * 设置播放按钮点击监听器 - * - * @param onPlayClickListener 如果为非 null ,则激活歌词拖动功能,否则将将禁用歌词拖动功能 - * @deprecated use {@link #setDraggable(boolean, OnPlayClickListener)} instead - */ - @Deprecated - public void setOnPlayClickListener(OnPlayClickListener onPlayClickListener) { - mOnPlayClickListener = onPlayClickListener; - } - - /** - * 设置歌词为空时屏幕中央显示的文字,如“暂无歌词” - */ - public void setLabel(String label) { - runOnUi(() -> { - mDefaultLabel = label; - invalidate(); + mAnimator = ValueAnimator.ofFloat(mOffset, offset); + mAnimator.setDuration(duration); + mAnimator.setInterpolator(new LinearInterpolator()); + mAnimator.addUpdateListener( + animation -> { + mOffset = (float) animation.getAnimatedValue(); + invalidate(); }); + LrcUtils.resetDurationScale(); + mAnimator.start(); + } + + /** 结束滚动动画 */ + private void endAnimation() { + if (mAnimator != null && mAnimator.isRunning()) { + mAnimator.end(); + } + } + + /** 二分法查找当前时间应该显示的行数(最后一个 <= time 的行数) */ + private int findShowLine(long time) { + int left = 0; + int right = mLrcEntryList.size(); + while (left <= right) { + int middle = (left + right) / 2; + long middleTime = mLrcEntryList.get(middle).getTime(); + + if (time < middleTime) { + right = middle - 1; + } else { + if (middle + 1 >= mLrcEntryList.size() || time < mLrcEntryList.get(middle + 1).getTime()) { + return middle; + } + + left = middle + 1; + } } + return 0; + } + + /** 获取当前在视图中央的行数 */ + private int getCenterLine() { + int centerLine = 0; + float minDistance = Float.MAX_VALUE; + for (int i = 0; i < mLrcEntryList.size(); i++) { + if (Math.abs(mOffset - getOffset(i)) < minDistance) { + minDistance = Math.abs(mOffset - getOffset(i)); + centerLine = i; + } + } + return centerLine; + } + + /** 获取歌词距离视图顶部的距离 采用懒加载方式 */ + private float getOffset(int line) { + if (mLrcEntryList.get(line).getOffset() == Float.MIN_VALUE) { + float offset = getHeight() / 2; + for (int i = 1; i <= line; i++) { + offset -= + ((mLrcEntryList.get(i - 1).getHeight() + mLrcEntryList.get(i).getHeight()) >> 1) + + mDividerHeight; + } + mLrcEntryList.get(line).setOffset(offset); + } + + return mLrcEntryList.get(line).getOffset(); + } + + /** 获取歌词宽度 */ + private float getLrcWidth() { + return getWidth() - mLrcPadding * 2; + } + + /** 在主线程中运行 */ + private void runOnUi(Runnable r) { + if (Looper.myLooper() == Looper.getMainLooper()) { + r.run(); + } else { + post(r); + } + } + + private Object getFlag() { + return mFlag; + } + + private void setFlag(Object flag) { + this.mFlag = flag; + } + + /** 播放按钮点击监听器,点击后应该跳转到指定播放位置 */ + public interface OnPlayClickListener { /** - * 加载歌词文件 + * 播放按钮被点击,应该跳转到指定播放位置 * - * @param lrcFile 歌词文件 + * @return 是否成功消费该事件,如果成功消费,则会更新UI */ - public void loadLrc(File lrcFile) { - loadLrc(lrcFile, null); - } - - /** - * 加载双语歌词文件,两种语言的歌词时间戳需要一致 - * - * @param mainLrcFile 第一种语言歌词文件 - * @param secondLrcFile 第二种语言歌词文件 - */ - public void loadLrc(File mainLrcFile, File secondLrcFile) { - runOnUi(() -> { - reset(); - - StringBuilder sb = new StringBuilder("file://"); - sb.append(mainLrcFile.getPath()); - if (secondLrcFile != null) { - sb.append("#").append(secondLrcFile.getPath()); - } - String flag = sb.toString(); - setFlag(flag); - new AsyncTask>() { - @Override - protected List doInBackground(File... params) { - return LrcUtils.parseLrc(params); - } - - @Override - protected void onPostExecute(List lrcEntries) { - if (getFlag() == flag) { - onLrcLoaded(lrcEntries); - setFlag(null); - } - } - }.execute(mainLrcFile, secondLrcFile); - }); - } - - /** - * 加载歌词文本 - * - * @param lrcText 歌词文本 - */ - public void loadLrc(String lrcText) { - loadLrc(lrcText, null); - } - - /** - * 加载双语歌词文本,两种语言的歌词时间戳需要一致 - * - * @param mainLrcText 第一种语言歌词文本 - * @param secondLrcText 第二种语言歌词文本 - */ - public void loadLrc(String mainLrcText, String secondLrcText) { - runOnUi(() -> { - reset(); - - StringBuilder sb = new StringBuilder("file://"); - sb.append(mainLrcText); - if (secondLrcText != null) { - sb.append("#").append(secondLrcText); - } - String flag = sb.toString(); - setFlag(flag); - new AsyncTask>() { - @Override - protected List doInBackground(String... params) { - return LrcUtils.parseLrc(params); - } - - @Override - protected void onPostExecute(List lrcEntries) { - if (getFlag() == flag) { - onLrcLoaded(lrcEntries); - setFlag(null); - } - } - }.execute(mainLrcText, secondLrcText); - }); - } - - /** - * 加载在线歌词,默认使用 utf-8 编码 - * - * @param lrcUrl 歌词文件的网络地址 - */ - public void loadLrcByUrl(String lrcUrl) { - loadLrcByUrl(lrcUrl, "utf-8"); - } - - /** - * 加载在线歌词 - * - * @param lrcUrl 歌词文件的网络地址 - * @param charset 编码格式 - */ - public void loadLrcByUrl(String lrcUrl, String charset) { - String flag = "url://" + lrcUrl; - setFlag(flag); - new AsyncTask() { - @Override - protected String doInBackground(String... params) { - return LrcUtils.getContentFromNetwork(params[0], params[1]); - } - - @Override - protected void onPostExecute(String lrcText) { - if (getFlag() == flag) { - loadLrc(lrcText); - } - } - }.execute(lrcUrl, charset); - } - - /** - * 歌词是否有效 - * - * @return true,如果歌词有效,否则false - */ - public boolean hasLrc() { - return !mLrcEntryList.isEmpty(); - } - - /** - * 刷新歌词 - * - * @param time 当前播放时间 - */ - public void updateTime(long time) { - runOnUi(() -> { - if (!hasLrc()) { - return; - } - - int line = findShowLine(time); - if (line != mCurrentLine) { - mCurrentLine = line; - if (!isShowTimeline) { - smoothScrollTo(line); - } else { - invalidate(); - } - } - }); - } - - /** - * 将歌词滚动到指定时间 - * - * @param time 指定的时间 - * @deprecated 请使用 {@link #updateTime(long)} 代替 - */ - @Deprecated - public void onDrag(long time) { - updateTime(time); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - if (changed) { - initPlayDrawable(); - initEntryList(); - if (hasLrc()) { - smoothScrollTo(mCurrentLine, 0L); - } - } - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - int centerY = getHeight() / 2; - - // 无歌词文件 - if (!hasLrc()) { - mLrcPaint.setColor(mCurrentTextColor); - @SuppressLint("DrawAllocation") - StaticLayout staticLayout = new StaticLayout(mDefaultLabel, mLrcPaint, - (int) getLrcWidth(), Layout.Alignment.ALIGN_CENTER, 1f, 0f, false); - drawText(canvas, staticLayout, centerY); - return; - } - - int centerLine = getCenterLine(); - - if (isShowTimeline) { - mPlayDrawable.draw(canvas); - - mTimePaint.setColor(mTimelineColor); - canvas.drawLine(mTimeTextWidth, centerY, getWidth() - mTimeTextWidth, centerY, mTimePaint); - - mTimePaint.setColor(mTimeTextColor); - String timeText = LrcUtils.formatTime(mLrcEntryList.get(centerLine).getTime()); - float timeX = getWidth() - mTimeTextWidth / 2; - float timeY = centerY - (mTimeFontMetrics.descent + mTimeFontMetrics.ascent) / 2; - canvas.drawText(timeText, timeX, timeY, mTimePaint); - } - - canvas.translate(0, mOffset); - - float y = 0; - for (int i = 0; i < mLrcEntryList.size(); i++) { - if (i > 0) { - y += ((mLrcEntryList.get(i - 1).getHeight() + mLrcEntryList.get(i).getHeight()) >> 1) + mDividerHeight; - } - if (BuildConfig.DEBUG) { - //mLrcPaint.setTypeface(ResourcesCompat.getFont(getContext(), R.font.sans)); - } - if (i == mCurrentLine) { - mLrcPaint.setTextSize(mCurrentTextSize); - mLrcPaint.setColor(mCurrentTextColor); - } else if (isShowTimeline && i == centerLine) { - mLrcPaint.setColor(mTimelineTextColor); - } else { - mLrcPaint.setTextSize(mNormalTextSize); - mLrcPaint.setColor(mNormalTextColor); - } - drawText(canvas, mLrcEntryList.get(i).getStaticLayout(), y); - } - } - - /** - * 画一行歌词 - * - * @param y 歌词中心 Y 坐标 - */ - private void drawText(Canvas canvas, StaticLayout staticLayout, float y) { - canvas.save(); - canvas.translate(mLrcPadding, y - (staticLayout.getHeight() >> 1)); - staticLayout.draw(canvas); - canvas.restore(); - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) { - isTouching = false; - if (hasLrc() && !isFling) { - adjustCenter(); - postDelayed(hideTimelineRunnable, TIMELINE_KEEP_TIME); - } - } - return mGestureDetector.onTouchEvent(event); - } - - @Override - public void computeScroll() { - if (mScroller.computeScrollOffset()) { - mOffset = mScroller.getCurrY(); - invalidate(); - } - - if (isFling && mScroller.isFinished()) { - isFling = false; - if (hasLrc() && !isTouching) { - adjustCenter(); - postDelayed(hideTimelineRunnable, TIMELINE_KEEP_TIME); - } - } - } - - @Override - protected void onDetachedFromWindow() { - removeCallbacks(hideTimelineRunnable); - super.onDetachedFromWindow(); - } - - private void onLrcLoaded(List entryList) { - if (entryList != null && !entryList.isEmpty()) { - mLrcEntryList.addAll(entryList); - } - - Collections.sort(mLrcEntryList); - - initEntryList(); - invalidate(); - } - - private void initPlayDrawable() { - int l = (mTimeTextWidth - mDrawableWidth) / 2; - int t = getHeight() / 2 - mDrawableWidth / 2; - int r = l + mDrawableWidth; - int b = t + mDrawableWidth; - mPlayDrawable.setBounds(l, t, r, b); - } - - private void initEntryList() { - if (!hasLrc() || getWidth() == 0) { - return; - } - - for (LrcEntry lrcEntry : mLrcEntryList) { - lrcEntry.init(mLrcPaint, (int) getLrcWidth(), mTextGravity); - } - - mOffset = getHeight() / 2; - } - - private void reset() { - endAnimation(); - mScroller.forceFinished(true); - isShowTimeline = false; - isTouching = false; - isFling = false; - removeCallbacks(hideTimelineRunnable); - mLrcEntryList.clear(); - mOffset = 0; - mCurrentLine = 0; - invalidate(); - } - - /** - * 将中心行微调至正中心 - */ - private void adjustCenter() { - smoothScrollTo(getCenterLine(), ADJUST_DURATION); - } - - /** - * 滚动到某一行 - */ - private void smoothScrollTo(int line) { - smoothScrollTo(line, mAnimationDuration); - } - - /** - * 滚动到某一行 - */ - private void smoothScrollTo(int line, long duration) { - float offset = getOffset(line); - endAnimation(); - - mAnimator = ValueAnimator.ofFloat(mOffset, offset); - mAnimator.setDuration(duration); - mAnimator.setInterpolator(new LinearInterpolator()); - mAnimator.addUpdateListener(animation -> { - mOffset = (float) animation.getAnimatedValue(); - invalidate(); - }); - LrcUtils.resetDurationScale(); - mAnimator.start(); - } - - /** - * 结束滚动动画 - */ - private void endAnimation() { - if (mAnimator != null && mAnimator.isRunning()) { - mAnimator.end(); - } - } - - /** - * 二分法查找当前时间应该显示的行数(最后一个 <= time 的行数) - */ - private int findShowLine(long time) { - int left = 0; - int right = mLrcEntryList.size(); - while (left <= right) { - int middle = (left + right) / 2; - long middleTime = mLrcEntryList.get(middle).getTime(); - - if (time < middleTime) { - right = middle - 1; - } else { - if (middle + 1 >= mLrcEntryList.size() || time < mLrcEntryList.get(middle + 1).getTime()) { - return middle; - } - - left = middle + 1; - } - } - - return 0; - } - - /** - * 获取当前在视图中央的行数 - */ - private int getCenterLine() { - int centerLine = 0; - float minDistance = Float.MAX_VALUE; - for (int i = 0; i < mLrcEntryList.size(); i++) { - if (Math.abs(mOffset - getOffset(i)) < minDistance) { - minDistance = Math.abs(mOffset - getOffset(i)); - centerLine = i; - } - } - return centerLine; - } - - /** - * 获取歌词距离视图顶部的距离 - * 采用懒加载方式 - */ - private float getOffset(int line) { - if (mLrcEntryList.get(line).getOffset() == Float.MIN_VALUE) { - float offset = getHeight() / 2; - for (int i = 1; i <= line; i++) { - offset -= ((mLrcEntryList.get(i - 1).getHeight() + mLrcEntryList.get(i).getHeight()) >> 1) + mDividerHeight; - } - mLrcEntryList.get(line).setOffset(offset); - } - - return mLrcEntryList.get(line).getOffset(); - } - - /** - * 获取歌词宽度 - */ - private float getLrcWidth() { - return getWidth() - mLrcPadding * 2; - } - - /** - * 在主线程中运行 - */ - private void runOnUi(Runnable r) { - if (Looper.myLooper() == Looper.getMainLooper()) { - r.run(); - } else { - post(r); - } - } - - private Object getFlag() { - return mFlag; - } - - private void setFlag(Object flag) { - this.mFlag = flag; - } - - /** - * 播放按钮点击监听器,点击后应该跳转到指定播放位置 - */ - public interface OnPlayClickListener { - /** - * 播放按钮被点击,应该跳转到指定播放位置 - * - * @return 是否成功消费该事件,如果成功消费,则会更新UI - */ - boolean onPlayClick(long time); - } -} \ No newline at end of file + boolean onPlayClick(long time); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/misc/CustomFragmentStatePagerAdapter.java b/app/src/main/java/code/name/monkey/retromusic/misc/CustomFragmentStatePagerAdapter.java index 31ce1874..2c40d3bd 100644 --- a/app/src/main/java/code/name/monkey/retromusic/misc/CustomFragmentStatePagerAdapter.java +++ b/app/src/main/java/code/name/monkey/retromusic/misc/CustomFragmentStatePagerAdapter.java @@ -19,219 +19,222 @@ import android.os.Parcelable; import android.util.Log; import android.view.View; import android.view.ViewGroup; - import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentPagerAdapter; import androidx.fragment.app.FragmentTransaction; import androidx.viewpager.widget.PagerAdapter; - import java.util.ArrayList; /** - * Implementation of {@link PagerAdapter} that - * uses a {@link Fragment} to manage each page. This class also handles - * saving and restoring of fragment's state. - *

- *

This version of the pager is more useful when there are a large number - * of pages, working more like a list view. When pages are not visible to - * the user, their entire fragment may be destroyed, only keeping the saved - * state of that fragment. This allows the pager to hold on to much less - * memory associated with each visited page as compared to - * {@link FragmentPagerAdapter} at the cost of potentially more overhead when - * switching between pages. - *

- *

When using FragmentPagerAdapter the host ViewPager must have a - * valid ID set.

- *

- *

Subclasses only need to implement {@link #getItem(int)} - * and {@link #getCount()} to have a working adapter. - *

- *

Here is an example implementation of a pager containing fragments of - * lists: - *

- * {@sample development/samples/Support13Demos/src/com/example/android/supportv13/app/FragmentStatePagerSupport.java + * Implementation of {@link PagerAdapter} that uses a {@link Fragment} to manage each page. This + * class also handles saving and restoring of fragment's state. + * + *

+ * + *

This version of the pager is more useful when there are a large number of pages, working more + * like a list view. When pages are not visible to the user, their entire fragment may be destroyed, + * only keeping the saved state of that fragment. This allows the pager to hold on to much less + * memory associated with each visited page as compared to {@link FragmentPagerAdapter} at the cost + * of potentially more overhead when switching between pages. + * + *

+ * + *

When using FragmentPagerAdapter the host ViewPager must have a valid ID set. + * + *

+ * + *

Subclasses only need to implement {@link #getItem(int)} and {@link #getCount()} to have a + * working adapter. + * + *

+ * + *

Here is an example implementation of a pager containing fragments of lists: + * + *

{@sample + * development/samples/Support13Demos/src/com/example/android/supportv13/app/FragmentStatePagerSupport.java * complete} - *

+ * + *

+ * *

The R.layout.fragment_pager resource of the top-level fragment is: - *

- * {@sample development/samples/Support13Demos/res/layout/fragment_pager.xml - * complete} - *

- *

The R.layout.fragment_pager_list resource containing each - * individual fragment's layout is: - *

- * {@sample development/samples/Support13Demos/res/layout/fragment_pager_list.xml - * complete} + * + *

{@sample development/samples/Support13Demos/res/layout/fragment_pager.xml complete} + * + *

+ * + *

The R.layout.fragment_pager_list resource containing each individual fragment's + * layout is: + * + *

{@sample development/samples/Support13Demos/res/layout/fragment_pager_list.xml complete} */ public abstract class CustomFragmentStatePagerAdapter extends PagerAdapter { - public static final String TAG = CustomFragmentStatePagerAdapter.class.getSimpleName(); - private static final boolean DEBUG = false; + public static final String TAG = CustomFragmentStatePagerAdapter.class.getSimpleName(); + private static final boolean DEBUG = false; - private final FragmentManager mFragmentManager; - private FragmentTransaction mCurTransaction = null; + private final FragmentManager mFragmentManager; + private FragmentTransaction mCurTransaction = null; - private ArrayList mSavedState = new ArrayList(); - private ArrayList mFragments = new ArrayList(); - private Fragment mCurrentPrimaryItem = null; + private ArrayList mSavedState = new ArrayList(); + private ArrayList mFragments = new ArrayList(); + private Fragment mCurrentPrimaryItem = null; - public CustomFragmentStatePagerAdapter(FragmentManager fm) { - mFragmentManager = fm; + public CustomFragmentStatePagerAdapter(FragmentManager fm) { + mFragmentManager = fm; + } + + /** Return the Fragment associated with a specified position. */ + public abstract Fragment getItem(int position); + + @Override + public void startUpdate(ViewGroup container) {} + + @NonNull + @Override + public Object instantiateItem(ViewGroup container, int position) { + // If we already have this item instantiated, there is nothing + // to do. This can happen when we are restoring the entire pager + // from its saved state, where the fragment manager has already + // taken care of restoring the fragments we previously had instantiated. + if (mFragments.size() > position) { + Fragment f = mFragments.get(position); + if (f != null) { + return f; + } } - /** - * Return the Fragment associated with a specified position. - */ - public abstract Fragment getItem(int position); - - @Override - public void startUpdate(ViewGroup container) { + if (mCurTransaction == null) { + mCurTransaction = mFragmentManager.beginTransaction(); } - @NonNull - @Override - public Object instantiateItem(ViewGroup container, int position) { - // If we already have this item instantiated, there is nothing - // to do. This can happen when we are restoring the entire pager - // from its saved state, where the fragment manager has already - // taken care of restoring the fragments we previously had instantiated. - if (mFragments.size() > position) { - Fragment f = mFragments.get(position); - if (f != null) { - return f; + Fragment fragment = getItem(position); + if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment); + if (mSavedState.size() > position) { + Fragment.SavedState fss = mSavedState.get(position); + if (fss != null) { + fragment.setInitialSavedState(fss); + } + } + while (mFragments.size() <= position) { + mFragments.add(null); + } + fragment.setMenuVisibility(false); + fragment.setUserVisibleHint(false); + mFragments.set(position, fragment); + mCurTransaction.add(container.getId(), fragment); + + return fragment; + } + + @Override + public void destroyItem(ViewGroup container, int position, Object object) { + Fragment fragment = (Fragment) object; + + if (mCurTransaction == null) { + mCurTransaction = mFragmentManager.beginTransaction(); + } + if (DEBUG) + Log.v( + TAG, + "Removing item #" + position + ": f=" + object + " v=" + ((Fragment) object).getView()); + while (mSavedState.size() <= position) { + mSavedState.add(null); + } + mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment)); + mFragments.set(position, null); + + mCurTransaction.remove(fragment); + } + + @Override + public void setPrimaryItem(ViewGroup container, int position, Object object) { + Fragment fragment = (Fragment) object; + if (fragment != mCurrentPrimaryItem) { + if (mCurrentPrimaryItem != null) { + mCurrentPrimaryItem.setMenuVisibility(false); + mCurrentPrimaryItem.setUserVisibleHint(false); + } + if (fragment != null) { + fragment.setMenuVisibility(true); + fragment.setUserVisibleHint(true); + } + mCurrentPrimaryItem = fragment; + } + } + + @Override + public void finishUpdate(ViewGroup container) { + if (mCurTransaction != null) { + mCurTransaction.commitAllowingStateLoss(); + mCurTransaction = null; + mFragmentManager.executePendingTransactions(); + } + } + + @Override + public boolean isViewFromObject(View view, Object object) { + return ((Fragment) object).getView() == view; + } + + @Override + public Parcelable saveState() { + Bundle state = null; + if (mSavedState.size() > 0) { + state = new Bundle(); + Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()]; + mSavedState.toArray(fss); + state.putParcelableArray("states", fss); + } + for (int i = 0; i < mFragments.size(); i++) { + Fragment f = mFragments.get(i); + if (f != null && f.isAdded()) { + if (state == null) { + state = new Bundle(); + } + String key = "f" + i; + mFragmentManager.putFragment(state, key, f); + } + } + return state; + } + + @Override + public void restoreState(Parcelable state, ClassLoader loader) { + if (state != null) { + Bundle bundle = (Bundle) state; + bundle.setClassLoader(loader); + Parcelable[] fss = bundle.getParcelableArray("states"); + mSavedState.clear(); + mFragments.clear(); + if (fss != null) { + for (int i = 0; i < fss.length; i++) { + mSavedState.add((Fragment.SavedState) fss[i]); + } + } + Iterable keys = bundle.keySet(); + for (String key : keys) { + if (key.startsWith("f")) { + int index = Integer.parseInt(key.substring(1)); + Fragment f = mFragmentManager.getFragment(bundle, key); + if (f != null) { + while (mFragments.size() <= index) { + mFragments.add(null); } + f.setMenuVisibility(false); + mFragments.set(index, f); + } else { + Log.w(TAG, "Bad fragment at key " + key); + } } - - if (mCurTransaction == null) { - mCurTransaction = mFragmentManager.beginTransaction(); - } - - Fragment fragment = getItem(position); - if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment); - if (mSavedState.size() > position) { - Fragment.SavedState fss = mSavedState.get(position); - if (fss != null) { - fragment.setInitialSavedState(fss); - } - } - while (mFragments.size() <= position) { - mFragments.add(null); - } - fragment.setMenuVisibility(false); - fragment.setUserVisibleHint(false); - mFragments.set(position, fragment); - mCurTransaction.add(container.getId(), fragment); - - return fragment; + } } + } - @Override - public void destroyItem(ViewGroup container, int position, Object object) { - Fragment fragment = (Fragment) object; - - if (mCurTransaction == null) { - mCurTransaction = mFragmentManager.beginTransaction(); - } - if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object - + " v=" + ((Fragment) object).getView()); - while (mSavedState.size() <= position) { - mSavedState.add(null); - } - mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment)); - mFragments.set(position, null); - - mCurTransaction.remove(fragment); - } - - @Override - public void setPrimaryItem(ViewGroup container, int position, Object object) { - Fragment fragment = (Fragment) object; - if (fragment != mCurrentPrimaryItem) { - if (mCurrentPrimaryItem != null) { - mCurrentPrimaryItem.setMenuVisibility(false); - mCurrentPrimaryItem.setUserVisibleHint(false); - } - if (fragment != null) { - fragment.setMenuVisibility(true); - fragment.setUserVisibleHint(true); - } - mCurrentPrimaryItem = fragment; - } - } - - @Override - public void finishUpdate(ViewGroup container) { - if (mCurTransaction != null) { - mCurTransaction.commitAllowingStateLoss(); - mCurTransaction = null; - mFragmentManager.executePendingTransactions(); - } - } - - @Override - public boolean isViewFromObject(View view, Object object) { - return ((Fragment) object).getView() == view; - } - - @Override - public Parcelable saveState() { - Bundle state = null; - if (mSavedState.size() > 0) { - state = new Bundle(); - Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()]; - mSavedState.toArray(fss); - state.putParcelableArray("states", fss); - } - for (int i = 0; i < mFragments.size(); i++) { - Fragment f = mFragments.get(i); - if (f != null && f.isAdded()) { - if (state == null) { - state = new Bundle(); - } - String key = "f" + i; - mFragmentManager.putFragment(state, key, f); - } - } - return state; - } - - @Override - public void restoreState(Parcelable state, ClassLoader loader) { - if (state != null) { - Bundle bundle = (Bundle) state; - bundle.setClassLoader(loader); - Parcelable[] fss = bundle.getParcelableArray("states"); - mSavedState.clear(); - mFragments.clear(); - if (fss != null) { - for (int i = 0; i < fss.length; i++) { - mSavedState.add((Fragment.SavedState) fss[i]); - } - } - Iterable keys = bundle.keySet(); - for (String key : keys) { - if (key.startsWith("f")) { - int index = Integer.parseInt(key.substring(1)); - Fragment f = mFragmentManager.getFragment(bundle, key); - if (f != null) { - while (mFragments.size() <= index) { - mFragments.add(null); - } - f.setMenuVisibility(false); - mFragments.set(index, f); - } else { - Log.w(TAG, "Bad fragment at key " + key); - } - } - } - } - } - - public Fragment getFragment(int position) { - if (position < mFragments.size() && position >= 0) { - return mFragments.get(position); - } - return null; + public Fragment getFragment(int position) { + if (position < mFragments.size() && position >= 0) { + return mFragments.get(position); } + return null; + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/misc/DialogAsyncTask.java b/app/src/main/java/code/name/monkey/retromusic/misc/DialogAsyncTask.java index 88e38762..d83cc162 100644 --- a/app/src/main/java/code/name/monkey/retromusic/misc/DialogAsyncTask.java +++ b/app/src/main/java/code/name/monkey/retromusic/misc/DialogAsyncTask.java @@ -17,90 +17,86 @@ package code.name.monkey.retromusic.misc; import android.app.Dialog; import android.content.Context; import android.os.Handler; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import java.lang.ref.WeakReference; +public abstract class DialogAsyncTask + extends WeakContextAsyncTask { + private final int delay; -public abstract class DialogAsyncTask extends WeakContextAsyncTask { - private final int delay; + private WeakReference

dialogWeakReference; - private WeakReference dialogWeakReference; + private boolean supposedToBeDismissed; - private boolean supposedToBeDismissed; + public DialogAsyncTask(Context context) { + this(context, 0); + } - public DialogAsyncTask(Context context) { - this(context, 0); + public DialogAsyncTask(Context context, int showDelay) { + super(context); + this.delay = showDelay; + dialogWeakReference = new WeakReference<>(null); + } + + @Override + protected void onPreExecute() { + super.onPreExecute(); + if (delay > 0) { + new Handler().postDelayed(this::initAndShowDialog, delay); + } else { + initAndShowDialog(); } + } - public DialogAsyncTask(Context context, int showDelay) { - super(context); - this.delay = showDelay; - dialogWeakReference = new WeakReference<>(null); + private void initAndShowDialog() { + Context context = getContext(); + if (!supposedToBeDismissed && context != null) { + Dialog dialog = createDialog(context); + dialogWeakReference = new WeakReference<>(dialog); + dialog.show(); } + } - @Override - protected void onPreExecute() { - super.onPreExecute(); - if (delay > 0) { - new Handler().postDelayed(this::initAndShowDialog, delay); - } else { - initAndShowDialog(); - } + @SuppressWarnings("unchecked") + @Override + protected void onProgressUpdate(Progress... values) { + super.onProgressUpdate(values); + Dialog dialog = getDialog(); + if (dialog != null) { + onProgressUpdate(dialog, values); } + } - private void initAndShowDialog() { - Context context = getContext(); - if (!supposedToBeDismissed && context != null) { - Dialog dialog = createDialog(context); - dialogWeakReference = new WeakReference<>(dialog); - dialog.show(); - } + @SuppressWarnings("unchecked") + protected void onProgressUpdate(@NonNull Dialog dialog, Progress... values) {} + + @Nullable + protected Dialog getDialog() { + return dialogWeakReference.get(); + } + + @Override + protected void onCancelled(Result result) { + super.onCancelled(result); + tryToDismiss(); + } + + @Override + protected void onPostExecute(Result result) { + super.onPostExecute(result); + tryToDismiss(); + } + + private void tryToDismiss() { + supposedToBeDismissed = true; + try { + Dialog dialog = getDialog(); + if (dialog != null) dialog.dismiss(); + } catch (Exception e) { + e.printStackTrace(); } + } - @SuppressWarnings("unchecked") - @Override - protected void onProgressUpdate(Progress... values) { - super.onProgressUpdate(values); - Dialog dialog = getDialog(); - if (dialog != null) { - onProgressUpdate(dialog, values); - } - } - - @SuppressWarnings("unchecked") - protected void onProgressUpdate(@NonNull Dialog dialog, Progress... values) { - } - - @Nullable - protected Dialog getDialog() { - return dialogWeakReference.get(); - } - - @Override - protected void onCancelled(Result result) { - super.onCancelled(result); - tryToDismiss(); - } - - @Override - protected void onPostExecute(Result result) { - super.onPostExecute(result); - tryToDismiss(); - } - - private void tryToDismiss() { - supposedToBeDismissed = true; - try { - Dialog dialog = getDialog(); - if (dialog != null) - dialog.dismiss(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - protected abstract Dialog createDialog(@NonNull Context context); + protected abstract Dialog createDialog(@NonNull Context context); } diff --git a/app/src/main/java/code/name/monkey/retromusic/misc/GenericFileProvider.java b/app/src/main/java/code/name/monkey/retromusic/misc/GenericFileProvider.java index 1e87ad8e..61a0a80a 100644 --- a/app/src/main/java/code/name/monkey/retromusic/misc/GenericFileProvider.java +++ b/app/src/main/java/code/name/monkey/retromusic/misc/GenericFileProvider.java @@ -16,5 +16,4 @@ package code.name.monkey.retromusic.misc; import androidx.core.content.FileProvider; -public class GenericFileProvider extends FileProvider { -} \ No newline at end of file +public class GenericFileProvider extends FileProvider {} diff --git a/app/src/main/java/code/name/monkey/retromusic/misc/LagTracker.java b/app/src/main/java/code/name/monkey/retromusic/misc/LagTracker.java index 48baf8d0..402e2b38 100755 --- a/app/src/main/java/code/name/monkey/retromusic/misc/LagTracker.java +++ b/app/src/main/java/code/name/monkey/retromusic/misc/LagTracker.java @@ -15,62 +15,71 @@ package code.name.monkey.retromusic.misc; import android.util.Log; - import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; public class LagTracker { - private static Map mMap; - private static LagTracker mSingleton; - private boolean mEnabled = true; + private static Map mMap; + private static LagTracker mSingleton; + private boolean mEnabled = true; - private LagTracker() { - mMap = new HashMap(); - } + private LagTracker() { + mMap = new HashMap(); + } - public static LagTracker get() { - if (mSingleton == null) { - mSingleton = new LagTracker(); - } - return mSingleton; + public static LagTracker get() { + if (mSingleton == null) { + mSingleton = new LagTracker(); } + return mSingleton; + } - private void print(String str, long j) { - long toMillis = TimeUnit.NANOSECONDS.toMillis(j); - Log.d("LagTracker", "[" + str + " completed in]: " + j + " ns (" + toMillis + "ms, " + TimeUnit.NANOSECONDS.toSeconds(j) + "s)"); - } + private void print(String str, long j) { + long toMillis = TimeUnit.NANOSECONDS.toMillis(j); + Log.d( + "LagTracker", + "[" + + str + + " completed in]: " + + j + + " ns (" + + toMillis + + "ms, " + + TimeUnit.NANOSECONDS.toSeconds(j) + + "s)"); + } - public LagTracker disable() { - this.mEnabled = false; - return this; - } + public LagTracker disable() { + this.mEnabled = false; + return this; + } - public LagTracker enable() { - this.mEnabled = true; - return this; - } + public LagTracker enable() { + this.mEnabled = true; + return this; + } - public void end(String str) { - long nanoTime = System.nanoTime(); - if (this.mEnabled) { - if (mMap.containsKey(str)) { - print(str, nanoTime - mMap.get(str).longValue()); - mMap.remove(str); - return; - } - throw new IllegalStateException("No start time found for " + str); - } else if (!mMap.isEmpty()) { - mMap.clear(); - } + public void end(String str) { + long nanoTime = System.nanoTime(); + if (this.mEnabled) { + if (mMap.containsKey(str)) { + print(str, nanoTime - mMap.get(str).longValue()); + mMap.remove(str); + return; + } + throw new IllegalStateException("No start time found for " + str); + } else if (!mMap.isEmpty()) { + mMap.clear(); } + } - public void start(String str) { - long nanoTime = System.nanoTime(); - if (this.mEnabled) { - mMap.put(str, Long.valueOf(nanoTime)); - } else if (!mMap.isEmpty()) { - mMap.clear(); - } + public void start(String str) { + long nanoTime = System.nanoTime(); + if (this.mEnabled) { + mMap.put(str, Long.valueOf(nanoTime)); + } else if (!mMap.isEmpty()) { + mMap.clear(); } + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/misc/UpdateToastMediaScannerCompletionListener.java b/app/src/main/java/code/name/monkey/retromusic/misc/UpdateToastMediaScannerCompletionListener.java index 84aa17c1..03a45cac 100644 --- a/app/src/main/java/code/name/monkey/retromusic/misc/UpdateToastMediaScannerCompletionListener.java +++ b/app/src/main/java/code/name/monkey/retromusic/misc/UpdateToastMediaScannerCompletionListener.java @@ -19,49 +19,49 @@ import android.app.Activity; import android.media.MediaScannerConnection; import android.net.Uri; import android.widget.Toast; - +import code.name.monkey.retromusic.R; import java.lang.ref.WeakReference; -import code.name.monkey.retromusic.R; +/** @author Karim Abou Zeid (kabouzeid) */ +public class UpdateToastMediaScannerCompletionListener + implements MediaScannerConnection.OnScanCompletedListener { -/** - * @author Karim Abou Zeid (kabouzeid) - */ -public class UpdateToastMediaScannerCompletionListener implements MediaScannerConnection.OnScanCompletedListener { + private final WeakReference activityWeakReference; - private final WeakReference activityWeakReference; + private final String couldNotScanFiles; + private final String scannedFiles; + private final String[] toBeScanned; + private int failed = 0; + private int scanned = 0; + private Toast toast; - private final String couldNotScanFiles; - private final String scannedFiles; - private final String[] toBeScanned; - private int failed = 0; - private int scanned = 0; - private Toast toast; + @SuppressLint("ShowToast") + public UpdateToastMediaScannerCompletionListener(Activity activity, String[] toBeScanned) { + this.toBeScanned = toBeScanned; + scannedFiles = activity.getString(R.string.scanned_files); + couldNotScanFiles = activity.getString(R.string.could_not_scan_files); + toast = Toast.makeText(activity.getApplicationContext(), "", Toast.LENGTH_SHORT); + activityWeakReference = new WeakReference<>(activity); + } - @SuppressLint("ShowToast") - public UpdateToastMediaScannerCompletionListener(Activity activity, String[] toBeScanned) { - this.toBeScanned = toBeScanned; - scannedFiles = activity.getString(R.string.scanned_files); - couldNotScanFiles = activity.getString(R.string.could_not_scan_files); - toast = Toast.makeText(activity.getApplicationContext(), "", Toast.LENGTH_SHORT); - activityWeakReference = new WeakReference<>(activity); - } - - @Override - public void onScanCompleted(final String path, final Uri uri) { - Activity activity = activityWeakReference.get(); - if (activity != null) { - activity.runOnUiThread(() -> { - if (uri == null) { - failed++; - } else { - scanned++; - } - String text = " " + String.format(scannedFiles, scanned, toBeScanned.length) + (failed > 0 ? " " - + String.format(couldNotScanFiles, failed) : ""); - toast.setText(text); - toast.show(); - }); - } + @Override + public void onScanCompleted(final String path, final Uri uri) { + Activity activity = activityWeakReference.get(); + if (activity != null) { + activity.runOnUiThread( + () -> { + if (uri == null) { + failed++; + } else { + scanned++; + } + String text = + " " + + String.format(scannedFiles, scanned, toBeScanned.length) + + (failed > 0 ? " " + String.format(couldNotScanFiles, failed) : ""); + toast.setText(text); + toast.show(); + }); } + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/model/lyrics/AbsSynchronizedLyrics.java b/app/src/main/java/code/name/monkey/retromusic/model/lyrics/AbsSynchronizedLyrics.java index a3a5f0b8..f939003e 100644 --- a/app/src/main/java/code/name/monkey/retromusic/model/lyrics/AbsSynchronizedLyrics.java +++ b/app/src/main/java/code/name/monkey/retromusic/model/lyrics/AbsSynchronizedLyrics.java @@ -18,54 +18,55 @@ import android.util.SparseArray; public abstract class AbsSynchronizedLyrics extends Lyrics { - private static final int TIME_OFFSET_MS = 500; // time adjustment to display line before it actually starts + private static final int TIME_OFFSET_MS = + 500; // time adjustment to display line before it actually starts - protected final SparseArray lines = new SparseArray<>(); + protected final SparseArray lines = new SparseArray<>(); - protected int offset = 0; + protected int offset = 0; - public String getLine(int time) { - time += offset + AbsSynchronizedLyrics.TIME_OFFSET_MS; + public String getLine(int time) { + time += offset + AbsSynchronizedLyrics.TIME_OFFSET_MS; - int lastLineTime = lines.keyAt(0); + int lastLineTime = lines.keyAt(0); - for (int i = 0; i < lines.size(); i++) { - int lineTime = lines.keyAt(i); + for (int i = 0; i < lines.size(); i++) { + int lineTime = lines.keyAt(i); - if (time >= lineTime) { - lastLineTime = lineTime; - } else { - break; - } - } - - return lines.get(lastLineTime); + if (time >= lineTime) { + lastLineTime = lineTime; + } else { + break; + } } - @Override - public String getText() { - parse(false); + return lines.get(lastLineTime); + } - if (valid) { - StringBuilder sb = new StringBuilder(); + @Override + public String getText() { + parse(false); - for (int i = 0; i < lines.size(); i++) { - String line = lines.valueAt(i); - sb.append(line).append("\r\n"); - } + if (valid) { + StringBuilder sb = new StringBuilder(); - return sb.toString().trim().replaceAll("(\r?\n){3,}", "\r\n\r\n"); - } + for (int i = 0; i < lines.size(); i++) { + String line = lines.valueAt(i); + sb.append(line).append("\r\n"); + } - return super.getText(); + return sb.toString().trim().replaceAll("(\r?\n){3,}", "\r\n\r\n"); } - public boolean isSynchronized() { - return true; - } + return super.getText(); + } - public boolean isValid() { - parse(true); - return valid; - } + public boolean isSynchronized() { + return true; + } + + public boolean isValid() { + parse(true); + return valid; + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/model/lyrics/Lyrics.java b/app/src/main/java/code/name/monkey/retromusic/model/lyrics/Lyrics.java index 32bbfd4a..dad81ff6 100644 --- a/app/src/main/java/code/name/monkey/retromusic/model/lyrics/Lyrics.java +++ b/app/src/main/java/code/name/monkey/retromusic/model/lyrics/Lyrics.java @@ -14,74 +14,72 @@ package code.name.monkey.retromusic.model.lyrics; - -import java.util.ArrayList; - import code.name.monkey.retromusic.model.Song; +import java.util.ArrayList; public class Lyrics { - private static final ArrayList> FORMATS = new ArrayList<>(); + private static final ArrayList> FORMATS = new ArrayList<>(); - static { - Lyrics.FORMATS.add(SynchronizedLyricsLRC.class); - } + static { + Lyrics.FORMATS.add(SynchronizedLyricsLRC.class); + } - public String data; - public Song song; - protected boolean parsed = false; - protected boolean valid = false; + public String data; + public Song song; + protected boolean parsed = false; + protected boolean valid = false; - public static boolean isSynchronized(String data) { - for (Class format : Lyrics.FORMATS) { - try { - Lyrics lyrics = format.newInstance().setData(null, data); - if (lyrics.isValid()) { - return true; - } - } catch (Exception e) { - e.printStackTrace(); - } + public static boolean isSynchronized(String data) { + for (Class format : Lyrics.FORMATS) { + try { + Lyrics lyrics = format.newInstance().setData(null, data); + if (lyrics.isValid()) { + return true; } - return false; + } catch (Exception e) { + e.printStackTrace(); + } } + return false; + } - public static Lyrics parse(Song song, String data) { - for (Class format : Lyrics.FORMATS) { - try { - Lyrics lyrics = format.newInstance().setData(song, data); - if (lyrics.isValid()) { - return lyrics.parse(false); - } - } catch (Exception e) { - e.printStackTrace(); - } + public static Lyrics parse(Song song, String data) { + for (Class format : Lyrics.FORMATS) { + try { + Lyrics lyrics = format.newInstance().setData(song, data); + if (lyrics.isValid()) { + return lyrics.parse(false); } - return new Lyrics().setData(song, data).parse(false); + } catch (Exception e) { + e.printStackTrace(); + } } + return new Lyrics().setData(song, data).parse(false); + } - public String getText() { - return this.data.trim().replaceAll("(\r?\n){3,}", "\r\n\r\n"); - } + public String getText() { + return this.data.trim().replaceAll("(\r?\n){3,}", "\r\n\r\n"); + } - public boolean isSynchronized() { - return false; - } + public boolean isSynchronized() { + return false; + } - public boolean isValid() { - this.parse(true); - return this.valid; - } + public boolean isValid() { + this.parse(true); + return this.valid; + } - public Lyrics parse(boolean check) { - this.valid = true; - this.parsed = true; - return this; - } + public Lyrics parse(boolean check) { + this.valid = true; + this.parsed = true; + return this; + } - public Lyrics setData(Song song, String data) { - this.song = song; - this.data = data; - return this; - } + public Lyrics setData(Song song, String data) { + this.song = song; + this.data = data; + return this; + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/model/lyrics/SynchronizedLyricsLRC.java b/app/src/main/java/code/name/monkey/retromusic/model/lyrics/SynchronizedLyricsLRC.java index 02b8d1c4..40918491 100644 --- a/app/src/main/java/code/name/monkey/retromusic/model/lyrics/SynchronizedLyricsLRC.java +++ b/app/src/main/java/code/name/monkey/retromusic/model/lyrics/SynchronizedLyricsLRC.java @@ -19,72 +19,73 @@ import java.util.regex.Pattern; class SynchronizedLyricsLRC extends AbsSynchronizedLyrics { - private static final Pattern LRC_LINE_PATTERN = Pattern.compile("((?:\\[.*?\\])+)(.*)"); + private static final Pattern LRC_LINE_PATTERN = Pattern.compile("((?:\\[.*?\\])+)(.*)"); - private static final Pattern LRC_TIME_PATTERN = Pattern.compile("\\[(\\d+):(\\d{2}(?:\\.\\d+)?)\\]"); + private static final Pattern LRC_TIME_PATTERN = + Pattern.compile("\\[(\\d+):(\\d{2}(?:\\.\\d+)?)\\]"); - private static final Pattern LRC_ATTRIBUTE_PATTERN = Pattern.compile("\\[(\\D+):(.+)\\]"); + private static final Pattern LRC_ATTRIBUTE_PATTERN = Pattern.compile("\\[(\\D+):(.+)\\]"); - private static final float LRC_SECONDS_TO_MS_MULTIPLIER = 1000f; + private static final float LRC_SECONDS_TO_MS_MULTIPLIER = 1000f; - private static final int LRC_MINUTES_TO_MS_MULTIPLIER = 60000; + private static final int LRC_MINUTES_TO_MS_MULTIPLIER = 60000; - @Override - public SynchronizedLyricsLRC parse(boolean check) { - if (this.parsed || this.data == null || this.data.isEmpty()) { - return this; - } - - String[] lines = this.data.split("\r?\n"); - - for (String line : lines) { - line = line.trim(); - if (line.isEmpty()) { - continue; - } - - Matcher attrMatcher = SynchronizedLyricsLRC.LRC_ATTRIBUTE_PATTERN.matcher(line); - if (attrMatcher.find()) { - try { - String attr = attrMatcher.group(1).toLowerCase().trim(); - String value = attrMatcher.group(2).toLowerCase().trim(); - if ("offset".equals(attr)) { - this.offset = Integer.parseInt(value); - } - } catch (Exception ex) { - ex.printStackTrace(); - } - } else { - Matcher matcher = SynchronizedLyricsLRC.LRC_LINE_PATTERN.matcher(line); - if (matcher.find()) { - String time = matcher.group(1); - String text = matcher.group(2); - - Matcher timeMatcher = SynchronizedLyricsLRC.LRC_TIME_PATTERN.matcher(time); - while (timeMatcher.find()) { - int m = 0; - float s = 0f; - try { - m = Integer.parseInt(timeMatcher.group(1)); - s = Float.parseFloat(timeMatcher.group(2)); - } catch (NumberFormatException ex) { - ex.printStackTrace(); - } - int ms = (int) (s * LRC_SECONDS_TO_MS_MULTIPLIER) + m * LRC_MINUTES_TO_MS_MULTIPLIER; - - this.valid = true; - if (check) { - return this; - } - - this.lines.append(ms, text); - } - } - } - } - - this.parsed = true; - - return this; + @Override + public SynchronizedLyricsLRC parse(boolean check) { + if (this.parsed || this.data == null || this.data.isEmpty()) { + return this; } + + String[] lines = this.data.split("\r?\n"); + + for (String line : lines) { + line = line.trim(); + if (line.isEmpty()) { + continue; + } + + Matcher attrMatcher = SynchronizedLyricsLRC.LRC_ATTRIBUTE_PATTERN.matcher(line); + if (attrMatcher.find()) { + try { + String attr = attrMatcher.group(1).toLowerCase().trim(); + String value = attrMatcher.group(2).toLowerCase().trim(); + if ("offset".equals(attr)) { + this.offset = Integer.parseInt(value); + } + } catch (Exception ex) { + ex.printStackTrace(); + } + } else { + Matcher matcher = SynchronizedLyricsLRC.LRC_LINE_PATTERN.matcher(line); + if (matcher.find()) { + String time = matcher.group(1); + String text = matcher.group(2); + + Matcher timeMatcher = SynchronizedLyricsLRC.LRC_TIME_PATTERN.matcher(time); + while (timeMatcher.find()) { + int m = 0; + float s = 0f; + try { + m = Integer.parseInt(timeMatcher.group(1)); + s = Float.parseFloat(timeMatcher.group(2)); + } catch (NumberFormatException ex) { + ex.printStackTrace(); + } + int ms = (int) (s * LRC_SECONDS_TO_MS_MULTIPLIER) + m * LRC_MINUTES_TO_MS_MULTIPLIER; + + this.valid = true; + if (check) { + return this; + } + + this.lines.append(ms, text); + } + } + } + } + + this.parsed = true; + + return this; + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/network/model/LastFmAlbum.java b/app/src/main/java/code/name/monkey/retromusic/network/model/LastFmAlbum.java index 779a10b7..b912503c 100644 --- a/app/src/main/java/code/name/monkey/retromusic/network/model/LastFmAlbum.java +++ b/app/src/main/java/code/name/monkey/retromusic/network/model/LastFmAlbum.java @@ -16,158 +16,144 @@ package code.name.monkey.retromusic.network.model; import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; - import java.util.ArrayList; import java.util.List; public class LastFmAlbum { - @Expose - private Album album; + @Expose private Album album; - public Album getAlbum() { - return album; + public Album getAlbum() { + return album; + } + + public void setAlbum(Album album) { + this.album = album; + } + + public static class Album { + + @Expose public String listeners; + @Expose public String playcount; + @Expose private List image = new ArrayList<>(); + @Expose private String name; + @Expose private Tags tags; + @Expose private Wiki wiki; + + public List getImage() { + return image; } - public void setAlbum(Album album) { - this.album = album; + public void setImage(List image) { + this.image = image; } - public static class Album { - - @Expose - public String listeners; - @Expose - public String playcount; - @Expose - private List image = new ArrayList<>(); - @Expose - private String name; - @Expose - private Tags tags; - @Expose - private Wiki wiki; - - public List getImage() { - return image; - } - - public void setImage(List image) { - this.image = image; - } - - public String getListeners() { - return listeners; - } - - public void setListeners(final String listeners) { - this.listeners = listeners; - } - - public String getName() { - return name; - } - - public void setName(final String name) { - this.name = name; - } - - public String getPlaycount() { - return playcount; - } - - public void setPlaycount(final String playcount) { - this.playcount = playcount; - } - - public Tags getTags() { - return tags; - } - - public Wiki getWiki() { - return wiki; - } - - public void setWiki(Wiki wiki) { - this.wiki = wiki; - } - - public static class Image { - - @SerializedName("#text") - @Expose - private String Text; - - @Expose - private String size; - - public String getSize() { - return size; - } - - public void setSize(String size) { - this.size = size; - } - - public String getText() { - return Text; - } - - public void setText(String Text) { - this.Text = Text; - } - } - - public class Tags { - - @Expose - private List tag = null; - - public List getTag() { - return tag; - } - } - - public class Tag { - - @Expose - private String name; - - @Expose - private String url; - - public String getName() { - return name; - } - - public String getUrl() { - return url; - } - } - - public class Wiki { - - @Expose - private String content; - - @Expose - private String published; - - public String getContent() { - return content; - } - - public void setContent(String content) { - this.content = content; - } - - public String getPublished() { - return published; - } - - public void setPublished(final String published) { - this.published = published; - } - } + public String getListeners() { + return listeners; } + + public void setListeners(final String listeners) { + this.listeners = listeners; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + public String getPlaycount() { + return playcount; + } + + public void setPlaycount(final String playcount) { + this.playcount = playcount; + } + + public Tags getTags() { + return tags; + } + + public Wiki getWiki() { + return wiki; + } + + public void setWiki(Wiki wiki) { + this.wiki = wiki; + } + + public static class Image { + + @SerializedName("#text") + @Expose + private String Text; + + @Expose private String size; + + public String getSize() { + return size; + } + + public void setSize(String size) { + this.size = size; + } + + public String getText() { + return Text; + } + + public void setText(String Text) { + this.Text = Text; + } + } + + public class Tags { + + @Expose private List tag = null; + + public List getTag() { + return tag; + } + } + + public class Tag { + + @Expose private String name; + + @Expose private String url; + + public String getName() { + return name; + } + + public String getUrl() { + return url; + } + } + + public class Wiki { + + @Expose private String content; + + @Expose private String published; + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getPublished() { + return published; + } + + public void setPublished(final String published) { + this.published = published; + } + } + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/network/model/LastFmArtist.java b/app/src/main/java/code/name/monkey/retromusic/network/model/LastFmArtist.java index de593cd7..0f91b5e4 100644 --- a/app/src/main/java/code/name/monkey/retromusic/network/model/LastFmArtist.java +++ b/app/src/main/java/code/name/monkey/retromusic/network/model/LastFmArtist.java @@ -16,111 +16,102 @@ package code.name.monkey.retromusic.network.model; import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; - import java.util.ArrayList; import java.util.List; public class LastFmArtist { - @Expose - private Artist artist; + @Expose private Artist artist; - public Artist getArtist() { - return artist; + public Artist getArtist() { + return artist; + } + + public void setArtist(Artist artist) { + this.artist = artist; + } + + public static class Artist { + + @Expose public Stats stats; + @Expose private Bio bio; + @Expose private List image = new ArrayList<>(); + + public Bio getBio() { + return bio; } - public void setArtist(Artist artist) { - this.artist = artist; + public void setBio(Bio bio) { + this.bio = bio; } - public static class Artist { - - @Expose - public Stats stats; - @Expose - private Bio bio; - @Expose - private List image = new ArrayList<>(); - - public Bio getBio() { - return bio; - } - - public void setBio(Bio bio) { - this.bio = bio; - } - - public List getImage() { - return image; - } - - public void setImage(List image) { - this.image = image; - } - - public static class Image { - - @SerializedName("#text") - @Expose - private String Text; - - @Expose - private String size; - - public String getSize() { - return size; - } - - public void setSize(String size) { - this.size = size; - } - - public String getText() { - return Text; - } - - public void setText(String Text) { - this.Text = Text; - } - } - - public static class Stats { - - @Expose - public String listeners; - - @Expose - public String playcount; - - public String getListeners() { - return listeners; - } - - public void setListeners(final String listeners) { - this.listeners = listeners; - } - - public String getPlaycount() { - return playcount; - } - - public void setPlaycount(final String playcount) { - this.playcount = playcount; - } - } - - public class Bio { - - @Expose - private String content; - - public String getContent() { - return content; - } - - public void setContent(String content) { - this.content = content; - } - } + public List getImage() { + return image; } + + public void setImage(List image) { + this.image = image; + } + + public static class Image { + + @SerializedName("#text") + @Expose + private String Text; + + @Expose private String size; + + public String getSize() { + return size; + } + + public void setSize(String size) { + this.size = size; + } + + public String getText() { + return Text; + } + + public void setText(String Text) { + this.Text = Text; + } + } + + public static class Stats { + + @Expose public String listeners; + + @Expose public String playcount; + + public String getListeners() { + return listeners; + } + + public void setListeners(final String listeners) { + this.listeners = listeners; + } + + public String getPlaycount() { + return playcount; + } + + public void setPlaycount(final String playcount) { + this.playcount = playcount; + } + } + + public class Bio { + + @Expose private String content; + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + } + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/network/model/LastFmTrack.java b/app/src/main/java/code/name/monkey/retromusic/network/model/LastFmTrack.java index fcb9d71a..ee8848a3 100644 --- a/app/src/main/java/code/name/monkey/retromusic/network/model/LastFmTrack.java +++ b/app/src/main/java/code/name/monkey/retromusic/network/model/LastFmTrack.java @@ -16,173 +16,157 @@ package code.name.monkey.retromusic.network.model; import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; - import java.util.List; -/** - * Created by hemanths on 15/06/17. - */ - +/** Created by hemanths on 15/06/17. */ public class LastFmTrack { + @Expose private Track track; + + public Track getTrack() { + return track; + } + + public void setTrack(Track track) { + this.track = track; + } + + public static class Track { + @SerializedName("name") @Expose - private Track track; + private String name; - public Track getTrack() { - return track; + @Expose private Album album; + @Expose private Wiki wiki; + @Expose private Toptags toptags; + @Expose private Artist artist; + + public Album getAlbum() { + return album; } - public void setTrack(Track track) { - this.track = track; + public Wiki getWiki() { + return wiki; } - public static class Track { - @SerializedName("name") - @Expose - private String name; - @Expose - private Album album; - @Expose - private Wiki wiki; - @Expose - private Toptags toptags; - @Expose - private Artist artist; + public String getName() { + return name; + } - public Album getAlbum() { - return album; - } + public Toptags getToptags() { + return toptags; + } - public Wiki getWiki() { - return wiki; - } + public static class Artist { + + @Expose private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + public static class Wiki { + @Expose private String published; + + public String getPublished() { + return published; + } + + public void setPublished(String published) { + this.published = published; + } + } + + public static class Toptags { + @Expose private List tag = null; + + public List getTag() { + return tag; + } + + public static class Tag { + @Expose private String name; public String getName() { - return name; - } - - public Toptags getToptags() { - return toptags; - } - - public static class Artist { - - @Expose - private String name; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - } - - public static class Wiki { - @Expose - private String published; - - public String getPublished() { - return published; - } - - public void setPublished(String published) { - this.published = published; - } - } - - public static class Toptags { - @Expose - private List tag = null; - - - public List getTag() { - return tag; - } - - public static class Tag { - @Expose - private String name; - - public String getName() { - return name; - } - } - } - - public static class Album { - @Expose - private String artist; - @Expose - private List image = null; - @Expose - private String title; - @SerializedName("@attr") - @Expose - private Attr attr; - - public Attr getAttr() { - return attr; - } - - public void setAttr(Attr attr) { - this.attr = attr; - } - - public String getArtist() { - return artist; - } - - public void setArtist(String artist) { - this.artist = artist; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public List getImage() { - return image; - } - - public void setImage(List image) { - this.image = image; - } - - public static class Attr { - @Expose - private String position; - - public String getPosition() { - return position; - } - - public void setPosition(String position) { - this.position = position; - } - } - - public class Image { - - @SerializedName("#text") - @Expose - private String text; - @Expose - private String size; - - public String getSize() { - return size; - } - - public String getText() { - return text; - } - } + return name; } + } } + + public static class Album { + @Expose private String artist; + @Expose private List image = null; + @Expose private String title; + + @SerializedName("@attr") + @Expose + private Attr attr; + + public Attr getAttr() { + return attr; + } + + public void setAttr(Attr attr) { + this.attr = attr; + } + + public String getArtist() { + return artist; + } + + public void setArtist(String artist) { + this.artist = artist; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public List getImage() { + return image; + } + + public void setImage(List image) { + this.image = image; + } + + public static class Attr { + @Expose private String position; + + public String getPosition() { + return position; + } + + public void setPosition(String position) { + this.position = position; + } + } + + public class Image { + + @SerializedName("#text") + @Expose + private String text; + + @Expose private String size; + + public String getSize() { + return size; + } + + public String getText() { + return text; + } + } + } + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/providers/BlacklistStore.java b/app/src/main/java/code/name/monkey/retromusic/providers/BlacklistStore.java index bb775b93..ee4486a4 100644 --- a/app/src/main/java/code/name/monkey/retromusic/providers/BlacklistStore.java +++ b/app/src/main/java/code/name/monkey/retromusic/providers/BlacklistStore.java @@ -14,6 +14,8 @@ package code.name.monkey.retromusic.providers; +import static code.name.monkey.retromusic.service.MusicService.MEDIA_STORE_CHANGED; + import android.content.ContentValues; import android.content.Context; import android.content.Intent; @@ -21,150 +23,164 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.os.Environment; - import androidx.annotation.NonNull; - +import code.name.monkey.retromusic.util.FileUtil; +import code.name.monkey.retromusic.util.PreferenceUtil; import java.io.File; import java.util.ArrayList; -import code.name.monkey.retromusic.util.FileUtil; -import code.name.monkey.retromusic.util.PreferenceUtil; - -import static code.name.monkey.retromusic.service.MusicService.MEDIA_STORE_CHANGED; - public class BlacklistStore extends SQLiteOpenHelper { - public static final String DATABASE_NAME = "blacklist.db"; - private static final int VERSION = 2; - private static BlacklistStore sInstance = null; - private Context context; + public static final String DATABASE_NAME = "blacklist.db"; + private static final int VERSION = 2; + private static BlacklistStore sInstance = null; + private Context context; - public BlacklistStore(final Context context) { - super(context, DATABASE_NAME, null, VERSION); - this.context = context; + public BlacklistStore(final Context context) { + super(context, DATABASE_NAME, null, VERSION); + this.context = context; + } + + @NonNull + public static synchronized BlacklistStore getInstance(@NonNull final Context context) { + if (sInstance == null) { + sInstance = new BlacklistStore(context.getApplicationContext()); + if (!PreferenceUtil.INSTANCE.isInitializedBlacklist()) { + // blacklisted by default + sInstance.addPathImpl( + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_ALARMS)); + sInstance.addPathImpl( + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_NOTIFICATIONS)); + sInstance.addPathImpl( + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_RINGTONES)); + + PreferenceUtil.INSTANCE.setInitializedBlacklist(true); + } + } + return sInstance; + } + + @Override + public void onCreate(@NonNull final SQLiteDatabase db) { + db.execSQL( + "CREATE TABLE IF NOT EXISTS " + + BlacklistStoreColumns.NAME + + " (" + + BlacklistStoreColumns.PATH + + " STRING NOT NULL);"); + } + + @Override + public void onUpgrade( + @NonNull final SQLiteDatabase db, final int oldVersion, final int newVersion) { + db.execSQL("DROP TABLE IF EXISTS " + BlacklistStoreColumns.NAME); + onCreate(db); + } + + @Override + public void onDowngrade(@NonNull SQLiteDatabase db, int oldVersion, int newVersion) { + db.execSQL("DROP TABLE IF EXISTS " + BlacklistStoreColumns.NAME); + onCreate(db); + } + + public void addPath(File file) { + addPathImpl(file); + notifyMediaStoreChanged(); + } + + private void addPathImpl(File file) { + if (file == null || contains(file)) { + return; + } + String path = FileUtil.safeGetCanonicalPath(file); + + final SQLiteDatabase database = getWritableDatabase(); + database.beginTransaction(); + + try { + // add the entry + final ContentValues values = new ContentValues(1); + values.put(BlacklistStoreColumns.PATH, path); + database.insert(BlacklistStoreColumns.NAME, null, values); + + database.setTransactionSuccessful(); + } finally { + database.endTransaction(); + } + } + + public boolean contains(File file) { + if (file == null) { + return false; + } + String path = FileUtil.safeGetCanonicalPath(file); + + final SQLiteDatabase database = getReadableDatabase(); + Cursor cursor = + database.query( + BlacklistStoreColumns.NAME, + new String[] {BlacklistStoreColumns.PATH}, + BlacklistStoreColumns.PATH + "=?", + new String[] {path}, + null, + null, + null, + null); + + boolean containsPath = cursor != null && cursor.moveToFirst(); + if (cursor != null) { + cursor.close(); + } + return containsPath; + } + + public void removePath(File file) { + final SQLiteDatabase database = getWritableDatabase(); + String path = FileUtil.safeGetCanonicalPath(file); + + database.delete( + BlacklistStoreColumns.NAME, BlacklistStoreColumns.PATH + "=?", new String[] {path}); + + notifyMediaStoreChanged(); + } + + public void clear() { + final SQLiteDatabase database = getWritableDatabase(); + database.delete(BlacklistStoreColumns.NAME, null, null); + + notifyMediaStoreChanged(); + } + + private void notifyMediaStoreChanged() { + context.sendBroadcast(new Intent(MEDIA_STORE_CHANGED)); + } + + @NonNull + public ArrayList getPaths() { + Cursor cursor = + getReadableDatabase() + .query( + BlacklistStoreColumns.NAME, + new String[] {BlacklistStoreColumns.PATH}, + null, + null, + null, + null, + null); + + ArrayList paths = new ArrayList<>(); + if (cursor != null && cursor.moveToFirst()) { + do { + paths.add(cursor.getString(0)); + } while (cursor.moveToNext()); } - @NonNull - public static synchronized BlacklistStore getInstance(@NonNull final Context context) { - if (sInstance == null) { - sInstance = new BlacklistStore(context.getApplicationContext()); - if (!PreferenceUtil.INSTANCE.isInitializedBlacklist()) { - // blacklisted by default - sInstance.addPathImpl(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_ALARMS)); - sInstance.addPathImpl(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_NOTIFICATIONS)); - sInstance.addPathImpl(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_RINGTONES)); + if (cursor != null) cursor.close(); + return paths; + } - PreferenceUtil.INSTANCE.setInitializedBlacklist(true); - } - } - return sInstance; - } + public interface BlacklistStoreColumns { + String NAME = "blacklist"; - @Override - public void onCreate(@NonNull final SQLiteDatabase db) { - db.execSQL("CREATE TABLE IF NOT EXISTS " + BlacklistStoreColumns.NAME + " (" + BlacklistStoreColumns.PATH + " STRING NOT NULL);"); - } - - @Override - public void onUpgrade(@NonNull final SQLiteDatabase db, final int oldVersion, final int newVersion) { - db.execSQL("DROP TABLE IF EXISTS " + BlacklistStoreColumns.NAME); - onCreate(db); - } - - @Override - public void onDowngrade(@NonNull SQLiteDatabase db, int oldVersion, int newVersion) { - db.execSQL("DROP TABLE IF EXISTS " + BlacklistStoreColumns.NAME); - onCreate(db); - } - - public void addPath(File file) { - addPathImpl(file); - notifyMediaStoreChanged(); - } - - private void addPathImpl(File file) { - if (file == null || contains(file)) { - return; - } - String path = FileUtil.safeGetCanonicalPath(file); - - final SQLiteDatabase database = getWritableDatabase(); - database.beginTransaction(); - - try { - // add the entry - final ContentValues values = new ContentValues(1); - values.put(BlacklistStoreColumns.PATH, path); - database.insert(BlacklistStoreColumns.NAME, null, values); - - database.setTransactionSuccessful(); - } finally { - database.endTransaction(); - } - } - - public boolean contains(File file) { - if (file == null) { - return false; - } - String path = FileUtil.safeGetCanonicalPath(file); - - final SQLiteDatabase database = getReadableDatabase(); - Cursor cursor = database.query(BlacklistStoreColumns.NAME, - new String[]{BlacklistStoreColumns.PATH}, - BlacklistStoreColumns.PATH + "=?", - new String[]{path}, - null, null, null, null); - - boolean containsPath = cursor != null && cursor.moveToFirst(); - if (cursor != null) { - cursor.close(); - } - return containsPath; - } - - public void removePath(File file) { - final SQLiteDatabase database = getWritableDatabase(); - String path = FileUtil.safeGetCanonicalPath(file); - - database.delete(BlacklistStoreColumns.NAME, - BlacklistStoreColumns.PATH + "=?", - new String[]{path}); - - notifyMediaStoreChanged(); - } - - public void clear() { - final SQLiteDatabase database = getWritableDatabase(); - database.delete(BlacklistStoreColumns.NAME, null, null); - - notifyMediaStoreChanged(); - } - - private void notifyMediaStoreChanged() { - context.sendBroadcast(new Intent(MEDIA_STORE_CHANGED)); - } - - @NonNull - public ArrayList getPaths() { - Cursor cursor = getReadableDatabase().query(BlacklistStoreColumns.NAME, - new String[]{BlacklistStoreColumns.PATH}, - null, null, null, null, null); - - ArrayList paths = new ArrayList<>(); - if (cursor != null && cursor.moveToFirst()) { - do { - paths.add(cursor.getString(0)); - } while (cursor.moveToNext()); - } - - if (cursor != null) - cursor.close(); - return paths; - } - - public interface BlacklistStoreColumns { - String NAME = "blacklist"; - - String PATH = "path"; - } -} \ No newline at end of file + String PATH = "path"; + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/providers/HistoryStore.java b/app/src/main/java/code/name/monkey/retromusic/providers/HistoryStore.java index e981aead..996bb57a 100644 --- a/app/src/main/java/code/name/monkey/retromusic/providers/HistoryStore.java +++ b/app/src/main/java/code/name/monkey/retromusic/providers/HistoryStore.java @@ -19,148 +19,169 @@ import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; public class HistoryStore extends SQLiteOpenHelper { - public static final String DATABASE_NAME = "history.db"; - private static final int MAX_ITEMS_IN_DB = 100; - private static final int VERSION = 1; - @Nullable - private static HistoryStore sInstance = null; + public static final String DATABASE_NAME = "history.db"; + private static final int MAX_ITEMS_IN_DB = 100; + private static final int VERSION = 1; + @Nullable private static HistoryStore sInstance = null; - public HistoryStore(final Context context) { - super(context, DATABASE_NAME, null, VERSION); + public HistoryStore(final Context context) { + super(context, DATABASE_NAME, null, VERSION); + } + + @NonNull + public static synchronized HistoryStore getInstance(@NonNull final Context context) { + if (sInstance == null) { + sInstance = new HistoryStore(context.getApplicationContext()); + } + return sInstance; + } + + @Override + public void onCreate(@NonNull final SQLiteDatabase db) { + db.execSQL( + "CREATE TABLE IF NOT EXISTS " + + RecentStoreColumns.NAME + + " (" + + RecentStoreColumns.ID + + " LONG NOT NULL," + + RecentStoreColumns.TIME_PLAYED + + " LONG NOT NULL);"); + } + + @Override + public void onUpgrade(@NonNull SQLiteDatabase db, int oldVersion, int newVersion) { + db.execSQL("DROP TABLE IF EXISTS " + RecentStoreColumns.NAME); + onCreate(db); + } + + @Override + public void onDowngrade(@NonNull SQLiteDatabase db, int oldVersion, int newVersion) { + db.execSQL("DROP TABLE IF EXISTS " + RecentStoreColumns.NAME); + onCreate(db); + } + + public void addSongId(final long songId) { + if (songId == -1) { + return; } - @NonNull - public static synchronized HistoryStore getInstance(@NonNull final Context context) { - if (sInstance == null) { - sInstance = new HistoryStore(context.getApplicationContext()); + final SQLiteDatabase database = getWritableDatabase(); + database.beginTransaction(); + + try { + // remove previous entries + removeSongId(songId); + + // add the entry + final ContentValues values = new ContentValues(2); + values.put(RecentStoreColumns.ID, songId); + values.put(RecentStoreColumns.TIME_PLAYED, System.currentTimeMillis()); + database.insert(RecentStoreColumns.NAME, null, values); + + // if our db is too large, delete the extra items + Cursor oldest = null; + try { + oldest = + database.query( + RecentStoreColumns.NAME, + new String[] {RecentStoreColumns.TIME_PLAYED}, + null, + null, + null, + null, + RecentStoreColumns.TIME_PLAYED + " ASC"); + + if (oldest != null && oldest.getCount() > MAX_ITEMS_IN_DB) { + oldest.moveToPosition(oldest.getCount() - MAX_ITEMS_IN_DB); + long timeOfRecordToKeep = oldest.getLong(0); + + database.delete( + RecentStoreColumns.NAME, + RecentStoreColumns.TIME_PLAYED + " < ?", + new String[] {String.valueOf(timeOfRecordToKeep)}); } - return sInstance; - } - - @Override - public void onCreate(@NonNull final SQLiteDatabase db) { - db.execSQL("CREATE TABLE IF NOT EXISTS " + RecentStoreColumns.NAME + " (" - + RecentStoreColumns.ID + " LONG NOT NULL," + RecentStoreColumns.TIME_PLAYED - + " LONG NOT NULL);"); - } - - @Override - public void onUpgrade(@NonNull SQLiteDatabase db, int oldVersion, int newVersion) { - db.execSQL("DROP TABLE IF EXISTS " + RecentStoreColumns.NAME); - onCreate(db); - } - - @Override - public void onDowngrade(@NonNull SQLiteDatabase db, int oldVersion, int newVersion) { - db.execSQL("DROP TABLE IF EXISTS " + RecentStoreColumns.NAME); - onCreate(db); - } - - public void addSongId(final long songId) { - if (songId == -1) { - return; - } - - final SQLiteDatabase database = getWritableDatabase(); - database.beginTransaction(); - - try { - // remove previous entries - removeSongId(songId); - - // add the entry - final ContentValues values = new ContentValues(2); - values.put(RecentStoreColumns.ID, songId); - values.put(RecentStoreColumns.TIME_PLAYED, System.currentTimeMillis()); - database.insert(RecentStoreColumns.NAME, null, values); - - // if our db is too large, delete the extra items - Cursor oldest = null; - try { - oldest = database.query(RecentStoreColumns.NAME, - new String[]{RecentStoreColumns.TIME_PLAYED}, null, null, null, null, - RecentStoreColumns.TIME_PLAYED + " ASC"); - - if (oldest != null && oldest.getCount() > MAX_ITEMS_IN_DB) { - oldest.moveToPosition(oldest.getCount() - MAX_ITEMS_IN_DB); - long timeOfRecordToKeep = oldest.getLong(0); - - database.delete(RecentStoreColumns.NAME, - RecentStoreColumns.TIME_PLAYED + " < ?", - new String[]{String.valueOf(timeOfRecordToKeep)}); - - } - } finally { - if (oldest != null) { - oldest.close(); - } - } - } finally { - database.setTransactionSuccessful(); - database.endTransaction(); + } finally { + if (oldest != null) { + oldest.close(); } + } + } finally { + database.setTransactionSuccessful(); + database.endTransaction(); } + } - public void removeSongId(final long songId) { - final SQLiteDatabase database = getWritableDatabase(); - database.delete(RecentStoreColumns.NAME, RecentStoreColumns.ID + " = ?", new String[]{ - String.valueOf(songId) - }); + public void removeSongId(final long songId) { + final SQLiteDatabase database = getWritableDatabase(); + database.delete( + RecentStoreColumns.NAME, + RecentStoreColumns.ID + " = ?", + new String[] {String.valueOf(songId)}); + } + public void clear() { + final SQLiteDatabase database = getWritableDatabase(); + database.delete(RecentStoreColumns.NAME, null, null); + } + + public boolean contains(long id) { + final SQLiteDatabase database = getReadableDatabase(); + Cursor cursor = + database.query( + RecentStoreColumns.NAME, + new String[] {RecentStoreColumns.ID}, + RecentStoreColumns.ID + "=?", + new String[] {String.valueOf(id)}, + null, + null, + null, + null); + + boolean containsId = cursor != null && cursor.moveToFirst(); + if (cursor != null) { + cursor.close(); } + return containsId; + } - public void clear() { - final SQLiteDatabase database = getWritableDatabase(); - database.delete(RecentStoreColumns.NAME, null, null); - } + public Cursor queryRecentIds() { + final SQLiteDatabase database = getReadableDatabase(); + return database.query( + RecentStoreColumns.NAME, + new String[] {RecentStoreColumns.ID}, + null, + null, + null, + null, + RecentStoreColumns.TIME_PLAYED + " DESC"); + } - public boolean contains(long id) { - final SQLiteDatabase database = getReadableDatabase(); - Cursor cursor = database.query(RecentStoreColumns.NAME, - new String[]{RecentStoreColumns.ID}, - RecentStoreColumns.ID + "=?", - new String[]{String.valueOf(id)}, - null, null, null, null); + public Cursor queryRecentIds(long cutoff) { + final boolean noCutoffTime = (cutoff == 0); + final boolean reverseOrder = (cutoff < 0); + if (reverseOrder) cutoff = -cutoff; - boolean containsId = cursor != null && cursor.moveToFirst(); - if (cursor != null) { - cursor.close(); - } - return containsId; - } + final SQLiteDatabase database = getReadableDatabase(); - public Cursor queryRecentIds() { - final SQLiteDatabase database = getReadableDatabase(); - return database.query(RecentStoreColumns.NAME, - new String[]{RecentStoreColumns.ID}, null, null, null, null, - RecentStoreColumns.TIME_PLAYED + " DESC"); - } + return database.query( + RecentStoreColumns.NAME, + new String[] {RecentStoreColumns.ID}, + noCutoffTime ? null : RecentStoreColumns.TIME_PLAYED + (reverseOrder ? "?"), + noCutoffTime ? null : new String[] {String.valueOf(cutoff)}, + null, + null, + RecentStoreColumns.TIME_PLAYED + (reverseOrder ? " ASC" : " DESC")); + } - public Cursor queryRecentIds(long cutoff) { - final boolean noCutoffTime = (cutoff == 0); - final boolean reverseOrder = (cutoff < 0); - if (reverseOrder) cutoff = -cutoff; + public interface RecentStoreColumns { + String NAME = "recent_history"; - final SQLiteDatabase database = getReadableDatabase(); + String ID = "song_id"; - return database.query(RecentStoreColumns.NAME, - new String[]{RecentStoreColumns.ID}, - noCutoffTime ? null : RecentStoreColumns.TIME_PLAYED + (reverseOrder ? "?"), - noCutoffTime ? null : new String[]{String.valueOf(cutoff)}, - null, null, - RecentStoreColumns.TIME_PLAYED + (reverseOrder ? " ASC" : " DESC")); - } - - public interface RecentStoreColumns { - String NAME = "recent_history"; - - String ID = "song_id"; - - String TIME_PLAYED = "time_played"; - } + String TIME_PLAYED = "time_played"; + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/providers/MusicPlaybackQueueStore.java b/app/src/main/java/code/name/monkey/retromusic/providers/MusicPlaybackQueueStore.java index ee2e040e..1d1934a6 100644 --- a/app/src/main/java/code/name/monkey/retromusic/providers/MusicPlaybackQueueStore.java +++ b/app/src/main/java/code/name/monkey/retromusic/providers/MusicPlaybackQueueStore.java @@ -20,195 +20,190 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.provider.BaseColumns; import android.provider.MediaStore.Audio.AudioColumns; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - -import java.util.List; - import code.name.monkey.retromusic.App; import code.name.monkey.retromusic.model.Song; import code.name.monkey.retromusic.repository.RealSongRepository; +import java.util.List; /** * @author Andrew Neal, modified for Phonograph by Karim Abou Zeid - *

- * This keeps track of the music playback and history state of the playback service + *

This keeps track of the music playback and history state of the playback service */ public class MusicPlaybackQueueStore extends SQLiteOpenHelper { - public static final String DATABASE_NAME = "music_playback_state.db"; + public static final String DATABASE_NAME = "music_playback_state.db"; - public static final String PLAYING_QUEUE_TABLE_NAME = "playing_queue"; + public static final String PLAYING_QUEUE_TABLE_NAME = "playing_queue"; - public static final String ORIGINAL_PLAYING_QUEUE_TABLE_NAME = "original_playing_queue"; + public static final String ORIGINAL_PLAYING_QUEUE_TABLE_NAME = "original_playing_queue"; - private static final int VERSION = 12; + private static final int VERSION = 12; - @Nullable - private static MusicPlaybackQueueStore sInstance = null; + @Nullable private static MusicPlaybackQueueStore sInstance = null; - /** - * Constructor of MusicPlaybackState - * - * @param context The {@link Context} to use - */ - public MusicPlaybackQueueStore(final @NonNull Context context) { - super(context, DATABASE_NAME, null, VERSION); + /** + * Constructor of MusicPlaybackState + * + * @param context The {@link Context} to use + */ + public MusicPlaybackQueueStore(final @NonNull Context context) { + super(context, DATABASE_NAME, null, VERSION); + } + + /** + * @param context The {@link Context} to use + * @return A new instance of this class. + */ + @NonNull + public static synchronized MusicPlaybackQueueStore getInstance(@NonNull final Context context) { + if (sInstance == null) { + sInstance = new MusicPlaybackQueueStore(context.getApplicationContext()); + } + return sInstance; + } + + @Override + public void onCreate(@NonNull final SQLiteDatabase db) { + createTable(db, PLAYING_QUEUE_TABLE_NAME); + createTable(db, ORIGINAL_PLAYING_QUEUE_TABLE_NAME); + } + + @NonNull + public List getSavedOriginalPlayingQueue() { + return getQueue(ORIGINAL_PLAYING_QUEUE_TABLE_NAME); + } + + @NonNull + public List getSavedPlayingQueue() { + return getQueue(PLAYING_QUEUE_TABLE_NAME); + } + + @Override + public void onDowngrade(@NonNull SQLiteDatabase db, int oldVersion, int newVersion) { + // If we ever have downgrade, drop the table to be safe + db.execSQL("DROP TABLE IF EXISTS " + PLAYING_QUEUE_TABLE_NAME); + db.execSQL("DROP TABLE IF EXISTS " + ORIGINAL_PLAYING_QUEUE_TABLE_NAME); + onCreate(db); + } + + @Override + public void onUpgrade( + @NonNull final SQLiteDatabase db, final int oldVersion, final int newVersion) { + // not necessary yet + db.execSQL("DROP TABLE IF EXISTS " + PLAYING_QUEUE_TABLE_NAME); + db.execSQL("DROP TABLE IF EXISTS " + ORIGINAL_PLAYING_QUEUE_TABLE_NAME); + onCreate(db); + } + + public synchronized void saveQueues( + @NonNull final List playingQueue, @NonNull final List originalPlayingQueue) { + saveQueue(PLAYING_QUEUE_TABLE_NAME, playingQueue); + saveQueue(ORIGINAL_PLAYING_QUEUE_TABLE_NAME, originalPlayingQueue); + } + + private void createTable(@NonNull final SQLiteDatabase db, final String tableName) { + //noinspection StringBufferReplaceableByString + StringBuilder builder = new StringBuilder(); + builder.append("CREATE TABLE IF NOT EXISTS "); + builder.append(tableName); + builder.append("("); + + builder.append(BaseColumns._ID); + builder.append(" INT NOT NULL,"); + + builder.append(AudioColumns.TITLE); + builder.append(" STRING NOT NULL,"); + + builder.append(AudioColumns.TRACK); + builder.append(" INT NOT NULL,"); + + builder.append(AudioColumns.YEAR); + builder.append(" INT NOT NULL,"); + + builder.append(AudioColumns.DURATION); + builder.append(" LONG NOT NULL,"); + + builder.append(AudioColumns.DATA); + builder.append(" STRING NOT NULL,"); + + builder.append(AudioColumns.DATE_MODIFIED); + builder.append(" LONG NOT NULL,"); + + builder.append(AudioColumns.ALBUM_ID); + builder.append(" INT NOT NULL,"); + + builder.append(AudioColumns.ALBUM); + builder.append(" STRING NOT NULL,"); + + builder.append(AudioColumns.ARTIST_ID); + builder.append(" INT NOT NULL,"); + + builder.append(AudioColumns.ARTIST); + builder.append(" STRING NOT NULL,"); + + builder.append(AudioColumns.COMPOSER); + builder.append(" STRING,"); + + builder.append("album_artist"); + builder.append(" STRING);"); + + db.execSQL(builder.toString()); + } + + @NonNull + private List getQueue(@NonNull final String tableName) { + Cursor cursor = getReadableDatabase().query(tableName, null, null, null, null, null, null); + return new RealSongRepository(App.Companion.getContext()).songs(cursor); + } + + /** + * Clears the existing database and saves the queue into the db so that when the app is restarted, + * the tracks you were listening to is restored + * + * @param queue the queue to save + */ + private synchronized void saveQueue(final String tableName, @NonNull final List queue) { + final SQLiteDatabase database = getWritableDatabase(); + database.beginTransaction(); + + try { + database.delete(tableName, null, null); + database.setTransactionSuccessful(); + } finally { + database.endTransaction(); } - /** - * @param context The {@link Context} to use - * @return A new instance of this class. - */ - @NonNull - public static synchronized MusicPlaybackQueueStore getInstance(@NonNull final Context context) { - if (sInstance == null) { - sInstance = new MusicPlaybackQueueStore(context.getApplicationContext()); - } - return sInstance; - } - - @Override - public void onCreate(@NonNull final SQLiteDatabase db) { - createTable(db, PLAYING_QUEUE_TABLE_NAME); - createTable(db, ORIGINAL_PLAYING_QUEUE_TABLE_NAME); - } - - @NonNull - public List getSavedOriginalPlayingQueue() { - return getQueue(ORIGINAL_PLAYING_QUEUE_TABLE_NAME); - } - - @NonNull - public List getSavedPlayingQueue() { - return getQueue(PLAYING_QUEUE_TABLE_NAME); - } - - @Override - public void onDowngrade(@NonNull SQLiteDatabase db, int oldVersion, int newVersion) { - // If we ever have downgrade, drop the table to be safe - db.execSQL("DROP TABLE IF EXISTS " + PLAYING_QUEUE_TABLE_NAME); - db.execSQL("DROP TABLE IF EXISTS " + ORIGINAL_PLAYING_QUEUE_TABLE_NAME); - onCreate(db); - } - - @Override - public void onUpgrade(@NonNull final SQLiteDatabase db, final int oldVersion, final int newVersion) { - // not necessary yet - db.execSQL("DROP TABLE IF EXISTS " + PLAYING_QUEUE_TABLE_NAME); - db.execSQL("DROP TABLE IF EXISTS " + ORIGINAL_PLAYING_QUEUE_TABLE_NAME); - onCreate(db); - } - - public synchronized void saveQueues(@NonNull final List playingQueue, - @NonNull final List originalPlayingQueue) { - saveQueue(PLAYING_QUEUE_TABLE_NAME, playingQueue); - saveQueue(ORIGINAL_PLAYING_QUEUE_TABLE_NAME, originalPlayingQueue); - } - - private void createTable(@NonNull final SQLiteDatabase db, final String tableName) { - //noinspection StringBufferReplaceableByString - StringBuilder builder = new StringBuilder(); - builder.append("CREATE TABLE IF NOT EXISTS "); - builder.append(tableName); - builder.append("("); - - builder.append(BaseColumns._ID); - builder.append(" INT NOT NULL,"); - - builder.append(AudioColumns.TITLE); - builder.append(" STRING NOT NULL,"); - - builder.append(AudioColumns.TRACK); - builder.append(" INT NOT NULL,"); - - builder.append(AudioColumns.YEAR); - builder.append(" INT NOT NULL,"); - - builder.append(AudioColumns.DURATION); - builder.append(" LONG NOT NULL,"); - - builder.append(AudioColumns.DATA); - builder.append(" STRING NOT NULL,"); - - builder.append(AudioColumns.DATE_MODIFIED); - builder.append(" LONG NOT NULL,"); - - builder.append(AudioColumns.ALBUM_ID); - builder.append(" INT NOT NULL,"); - - builder.append(AudioColumns.ALBUM); - builder.append(" STRING NOT NULL,"); - - builder.append(AudioColumns.ARTIST_ID); - builder.append(" INT NOT NULL,"); - - builder.append(AudioColumns.ARTIST); - builder.append(" STRING NOT NULL,"); - - builder.append(AudioColumns.COMPOSER); - builder.append(" STRING,"); - - builder.append("album_artist"); - builder.append(" STRING);"); - - db.execSQL(builder.toString()); - } - - @NonNull - private List getQueue(@NonNull final String tableName) { - Cursor cursor = getReadableDatabase().query(tableName, null, - null, null, null, null, null); - return new RealSongRepository(App.Companion.getContext()).songs(cursor); - } - - /** - * Clears the existing database and saves the queue into the db so that when the - * app is restarted, the tracks you were listening to is restored - * - * @param queue the queue to save - */ - private synchronized void saveQueue(final String tableName, @NonNull final List queue) { - final SQLiteDatabase database = getWritableDatabase(); - database.beginTransaction(); - - try { - database.delete(tableName, null, null); - database.setTransactionSuccessful(); - } finally { - database.endTransaction(); - } - - final int NUM_PROCESS = 20; - int position = 0; - while (position < queue.size()) { - database.beginTransaction(); - try { - for (int i = position; i < queue.size() && i < position + NUM_PROCESS; i++) { - Song song = queue.get(i); - ContentValues values = new ContentValues(4); - - values.put(BaseColumns._ID, song.getId()); - values.put(AudioColumns.TITLE, song.getTitle()); - values.put(AudioColumns.TRACK, song.getTrackNumber()); - values.put(AudioColumns.YEAR, song.getYear()); - values.put(AudioColumns.DURATION, song.getDuration()); - values.put(AudioColumns.DATA, song.getData()); - values.put(AudioColumns.DATE_MODIFIED, song.getDateModified()); - values.put(AudioColumns.ALBUM_ID, song.getAlbumId()); - values.put(AudioColumns.ALBUM, song.getAlbumName()); - values.put(AudioColumns.ARTIST_ID, song.getArtistId()); - values.put(AudioColumns.ARTIST, song.getArtistName()); - values.put(AudioColumns.COMPOSER, song.getComposer()); - - database.insert(tableName, null, values); - } - database.setTransactionSuccessful(); - } finally { - database.endTransaction(); - position += NUM_PROCESS; - } + final int NUM_PROCESS = 20; + int position = 0; + while (position < queue.size()) { + database.beginTransaction(); + try { + for (int i = position; i < queue.size() && i < position + NUM_PROCESS; i++) { + Song song = queue.get(i); + ContentValues values = new ContentValues(4); + + values.put(BaseColumns._ID, song.getId()); + values.put(AudioColumns.TITLE, song.getTitle()); + values.put(AudioColumns.TRACK, song.getTrackNumber()); + values.put(AudioColumns.YEAR, song.getYear()); + values.put(AudioColumns.DURATION, song.getDuration()); + values.put(AudioColumns.DATA, song.getData()); + values.put(AudioColumns.DATE_MODIFIED, song.getDateModified()); + values.put(AudioColumns.ALBUM_ID, song.getAlbumId()); + values.put(AudioColumns.ALBUM, song.getAlbumName()); + values.put(AudioColumns.ARTIST_ID, song.getArtistId()); + values.put(AudioColumns.ARTIST, song.getArtistName()); + values.put(AudioColumns.COMPOSER, song.getComposer()); + + database.insert(tableName, null, values); } + database.setTransactionSuccessful(); + } finally { + database.endTransaction(); + position += NUM_PROCESS; + } } + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/providers/SongPlayCountStore.java b/app/src/main/java/code/name/monkey/retromusic/providers/SongPlayCountStore.java index 4701c062..c19903f9 100644 --- a/app/src/main/java/code/name/monkey/retromusic/providers/SongPlayCountStore.java +++ b/app/src/main/java/code/name/monkey/retromusic/providers/SongPlayCountStore.java @@ -21,383 +21,400 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.view.animation.AccelerateInterpolator; import android.view.animation.Interpolator; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; /** - * This database tracks the number of play counts for an individual song. This is used to drive - * the top played tracks as well as the playlist images + * This database tracks the number of play counts for an individual song. This is used to drive the + * top played tracks as well as the playlist images */ public class SongPlayCountStore extends SQLiteOpenHelper { - public static final String DATABASE_NAME = "song_play_count.db"; - private static final int VERSION = 3; - // how many weeks worth of playback to track - private static final int NUM_WEEKS = 52; - @Nullable - private static SongPlayCountStore sInstance = null; - // interpolator curve applied for measuring the curve - @NonNull - private static Interpolator sInterpolator = new AccelerateInterpolator(1.5f); - // how high to multiply the interpolation curve - @SuppressWarnings("FieldCanBeLocal") - private static int INTERPOLATOR_HEIGHT = 50; + public static final String DATABASE_NAME = "song_play_count.db"; + private static final int VERSION = 3; + // how many weeks worth of playback to track + private static final int NUM_WEEKS = 52; + @Nullable private static SongPlayCountStore sInstance = null; + // interpolator curve applied for measuring the curve + @NonNull private static Interpolator sInterpolator = new AccelerateInterpolator(1.5f); + // how high to multiply the interpolation curve + @SuppressWarnings("FieldCanBeLocal") + private static int INTERPOLATOR_HEIGHT = 50; - // how high the base value is. The ratio of the Height to Base is what really matters - @SuppressWarnings("FieldCanBeLocal") - private static int INTERPOLATOR_BASE = 25; + // how high the base value is. The ratio of the Height to Base is what really matters + @SuppressWarnings("FieldCanBeLocal") + private static int INTERPOLATOR_BASE = 25; - @SuppressWarnings("FieldCanBeLocal") - private static int ONE_WEEK_IN_MS = 1000 * 60 * 60 * 24 * 7; + @SuppressWarnings("FieldCanBeLocal") + private static int ONE_WEEK_IN_MS = 1000 * 60 * 60 * 24 * 7; - @NonNull - private static String WHERE_ID_EQUALS = SongPlayCountColumns.ID + "=?"; + @NonNull private static String WHERE_ID_EQUALS = SongPlayCountColumns.ID + "=?"; - // number of weeks since epoch time - private int mNumberOfWeeksSinceEpoch; + // number of weeks since epoch time + private int mNumberOfWeeksSinceEpoch; - // used to track if we've walked through the db and updated all the rows - private boolean mDatabaseUpdated; + // used to track if we've walked through the db and updated all the rows + private boolean mDatabaseUpdated; - public SongPlayCountStore(final Context context) { - super(context, DATABASE_NAME, null, VERSION); + public SongPlayCountStore(final Context context) { + super(context, DATABASE_NAME, null, VERSION); - long msSinceEpoch = System.currentTimeMillis(); - mNumberOfWeeksSinceEpoch = (int) (msSinceEpoch / ONE_WEEK_IN_MS); + long msSinceEpoch = System.currentTimeMillis(); + mNumberOfWeeksSinceEpoch = (int) (msSinceEpoch / ONE_WEEK_IN_MS); - mDatabaseUpdated = false; + mDatabaseUpdated = false; + } + + /** + * @param context The {@link Context} to use + * @return A new instance of this class. + */ + @NonNull + public static synchronized SongPlayCountStore getInstance(@NonNull final Context context) { + if (sInstance == null) { + sInstance = new SongPlayCountStore(context.getApplicationContext()); + } + return sInstance; + } + + /** + * Calculates the score of the song given the play counts + * + * @param playCounts an array of the # of times a song has been played for each week where + * playCounts[N] is the # of times it was played N weeks ago + * @return the score + */ + private static float calculateScore(@Nullable final int[] playCounts) { + if (playCounts == null) { + return 0; } - /** - * @param context The {@link Context} to use - * @return A new instance of this class. - */ - @NonNull - public static synchronized SongPlayCountStore getInstance(@NonNull final Context context) { - if (sInstance == null) { - sInstance = new SongPlayCountStore(context.getApplicationContext()); + float score = 0; + for (int i = 0; i < Math.min(playCounts.length, NUM_WEEKS); i++) { + score += playCounts[i] * getScoreMultiplierForWeek(i); + } + + return score; + } + + /** + * Gets the column name for each week # + * + * @param week number + * @return the column name + */ + @NonNull + private static String getColumnNameForWeek(final int week) { + return SongPlayCountColumns.WEEK_PLAY_COUNT + week; + } + + /** + * Gets the score multiplier for each week + * + * @param week number + * @return the multiplier to apply + */ + private static float getScoreMultiplierForWeek(final int week) { + return sInterpolator.getInterpolation(1 - (week / (float) NUM_WEEKS)) * INTERPOLATOR_HEIGHT + + INTERPOLATOR_BASE; + } + + /** + * For some performance gain, return a static value for the column index for a week WARNING: This + * function assumes you have selected all columns for it to work + * + * @param week number + * @return column index of that week + */ + private static int getColumnIndexForWeek(final int week) { + // ID, followed by the weeks columns + return 1 + week; + } + + @Override + public void onCreate(@NonNull final SQLiteDatabase db) { + // create the play count table + // WARNING: If you change the order of these columns + // please update getColumnIndexForWeek + StringBuilder builder = new StringBuilder(); + builder.append("CREATE TABLE IF NOT EXISTS "); + builder.append(SongPlayCountColumns.NAME); + builder.append("("); + builder.append(SongPlayCountColumns.ID); + builder.append(" INT UNIQUE,"); + + for (int i = 0; i < NUM_WEEKS; i++) { + builder.append(getColumnNameForWeek(i)); + builder.append(" INT DEFAULT 0,"); + } + + builder.append(SongPlayCountColumns.LAST_UPDATED_WEEK_INDEX); + builder.append(" INT NOT NULL,"); + + builder.append(SongPlayCountColumns.PLAY_COUNT_SCORE); + builder.append(" REAL DEFAULT 0);"); + + db.execSQL(builder.toString()); + } + + @Override + public void onUpgrade( + @NonNull final SQLiteDatabase db, final int oldVersion, final int newVersion) { + db.execSQL("DROP TABLE IF EXISTS " + SongPlayCountColumns.NAME); + onCreate(db); + } + + @Override + public void onDowngrade(@NonNull SQLiteDatabase db, int oldVersion, int newVersion) { + // If we ever have downgrade, drop the table to be safe + db.execSQL("DROP TABLE IF EXISTS " + SongPlayCountColumns.NAME); + onCreate(db); + } + + /** + * Increases the play count of a song by 1 + * + * @param songId The song id to increase the play count + */ + public void bumpPlayCount(final long songId) { + if (songId == -1) { + return; + } + + final SQLiteDatabase database = getWritableDatabase(); + updateExistingRow(database, songId, true); + } + + /** + * This creates a new entry that indicates a song has been played once as well as its score + * + * @param database a write able database + * @param songId the id of the track + */ + private void createNewPlayedEntry(@NonNull final SQLiteDatabase database, final long songId) { + // no row exists, create a new one + float newScore = getScoreMultiplierForWeek(0); + int newPlayCount = 1; + + final ContentValues values = new ContentValues(3); + values.put(SongPlayCountColumns.ID, songId); + values.put(SongPlayCountColumns.PLAY_COUNT_SCORE, newScore); + values.put(SongPlayCountColumns.LAST_UPDATED_WEEK_INDEX, mNumberOfWeeksSinceEpoch); + values.put(getColumnNameForWeek(0), newPlayCount); + + database.insert(SongPlayCountColumns.NAME, null, values); + } + + /** + * This function will take a song entry and update it to the latest week and increase the count + * for the current week by 1 if necessary + * + * @param database a writeable database + * @param id the id of the track to bump + * @param bumpCount whether to bump the current's week play count by 1 and adjust the score + */ + private void updateExistingRow( + @NonNull final SQLiteDatabase database, final long id, boolean bumpCount) { + String stringId = String.valueOf(id); + + // begin the transaction + database.beginTransaction(); + + // get the cursor of this content inside the transaction + final Cursor cursor = + database.query( + SongPlayCountColumns.NAME, + null, + WHERE_ID_EQUALS, + new String[] {stringId}, + null, + null, + null); + + // if we have a result + if (cursor != null && cursor.moveToFirst()) { + // figure how many weeks since we last updated + int lastUpdatedIndex = cursor.getColumnIndex(SongPlayCountColumns.LAST_UPDATED_WEEK_INDEX); + int lastUpdatedWeek = cursor.getInt(lastUpdatedIndex); + int weekDiff = mNumberOfWeeksSinceEpoch - lastUpdatedWeek; + + // if it's more than the number of weeks we track, delete it and create a new entry + if (Math.abs(weekDiff) >= NUM_WEEKS) { + // this entry needs to be dropped since it is too outdated + deleteEntry(database, stringId); + if (bumpCount) { + createNewPlayedEntry(database, id); } - return sInstance; - } + } else if (weekDiff != 0) { + // else, shift the weeks + int[] playCounts = new int[NUM_WEEKS]; - /** - * Calculates the score of the song given the play counts - * - * @param playCounts an array of the # of times a song has been played for each week - * where playCounts[N] is the # of times it was played N weeks ago - * @return the score - */ - private static float calculateScore(@Nullable final int[] playCounts) { - if (playCounts == null) { - return 0; + if (weekDiff > 0) { + // time is shifted forwards + for (int i = 0; i < NUM_WEEKS - weekDiff; i++) { + playCounts[i + weekDiff] = cursor.getInt(getColumnIndexForWeek(i)); + } + } else if (weekDiff < 0) { + // time is shifted backwards (by user) - nor typical behavior but we + // will still handle it + + // since weekDiff is -ve, NUM_WEEKS + weekDiff is the real # of weeks we have to + // transfer. Then we transfer the old week i - weekDiff to week i + // for example if the user shifted back 2 weeks, ie -2, then for 0 to + // NUM_WEEKS + (-2) we set the new week i = old week i - (-2) or i+2 + for (int i = 0; i < NUM_WEEKS + weekDiff; i++) { + playCounts[i] = cursor.getInt(getColumnIndexForWeek(i - weekDiff)); + } } - float score = 0; - for (int i = 0; i < Math.min(playCounts.length, NUM_WEEKS); i++) { - score += playCounts[i] * getScoreMultiplierForWeek(i); + // bump the count + if (bumpCount) { + playCounts[0]++; } - return score; - } + float score = calculateScore(playCounts); - /** - * Gets the column name for each week # - * - * @param week number - * @return the column name - */ - @NonNull - private static String getColumnNameForWeek(final int week) { - return SongPlayCountColumns.WEEK_PLAY_COUNT + week; - } + // if the score is non-existant, then delete it + if (score < .01f) { + deleteEntry(database, stringId); + } else { + // create the content values + ContentValues values = new ContentValues(NUM_WEEKS + 2); + values.put(SongPlayCountColumns.LAST_UPDATED_WEEK_INDEX, mNumberOfWeeksSinceEpoch); + values.put(SongPlayCountColumns.PLAY_COUNT_SCORE, score); - /** - * Gets the score multiplier for each week - * - * @param week number - * @return the multiplier to apply - */ - private static float getScoreMultiplierForWeek(final int week) { - return sInterpolator.getInterpolation(1 - (week / (float) NUM_WEEKS)) * INTERPOLATOR_HEIGHT - + INTERPOLATOR_BASE; - } + for (int i = 0; i < NUM_WEEKS; i++) { + values.put(getColumnNameForWeek(i), playCounts[i]); + } - /** - * For some performance gain, return a static value for the column index for a week - * WARNING: This function assumes you have selected all columns for it to work - * - * @param week number - * @return column index of that week - */ - private static int getColumnIndexForWeek(final int week) { - // ID, followed by the weeks columns - return 1 + week; - } - - @Override - public void onCreate(@NonNull final SQLiteDatabase db) { - // create the play count table - // WARNING: If you change the order of these columns - // please update getColumnIndexForWeek - StringBuilder builder = new StringBuilder(); - builder.append("CREATE TABLE IF NOT EXISTS "); - builder.append(SongPlayCountColumns.NAME); - builder.append("("); - builder.append(SongPlayCountColumns.ID); - builder.append(" INT UNIQUE,"); - - for (int i = 0; i < NUM_WEEKS; i++) { - builder.append(getColumnNameForWeek(i)); - builder.append(" INT DEFAULT 0,"); + // update the entry + database.update( + SongPlayCountColumns.NAME, values, WHERE_ID_EQUALS, new String[] {stringId}); } + } else if (bumpCount) { + // else no shifting, just update the scores + ContentValues values = new ContentValues(2); - builder.append(SongPlayCountColumns.LAST_UPDATED_WEEK_INDEX); - builder.append(" INT NOT NULL,"); + // increase the score by a single score amount + int scoreIndex = cursor.getColumnIndex(SongPlayCountColumns.PLAY_COUNT_SCORE); + float score = cursor.getFloat(scoreIndex) + getScoreMultiplierForWeek(0); + values.put(SongPlayCountColumns.PLAY_COUNT_SCORE, score); - builder.append(SongPlayCountColumns.PLAY_COUNT_SCORE); - builder.append(" REAL DEFAULT 0);"); + // increase the play count by 1 + values.put(getColumnNameForWeek(0), cursor.getInt(getColumnIndexForWeek(0)) + 1); - db.execSQL(builder.toString()); + // update the entry + database.update( + SongPlayCountColumns.NAME, values, WHERE_ID_EQUALS, new String[] {stringId}); + } + + cursor.close(); + } else if (bumpCount) { + // if we have no existing results, create a new one + createNewPlayedEntry(database, id); } - @Override - public void onUpgrade(@NonNull final SQLiteDatabase db, final int oldVersion, final int newVersion) { - db.execSQL("DROP TABLE IF EXISTS " + SongPlayCountColumns.NAME); - onCreate(db); + database.setTransactionSuccessful(); + database.endTransaction(); + } + + public void clear() { + final SQLiteDatabase database = getWritableDatabase(); + database.delete(SongPlayCountColumns.NAME, null, null); + } + + /** + * Gets a cursor containing the top songs played. Note this only returns songs that have been + * played at least once in the past NUM_WEEKS + * + * @param numResults number of results to limit by. If <= 0 it returns all results + * @return the top tracks + */ + public Cursor getTopPlayedResults(int numResults) { + updateResults(); + + final SQLiteDatabase database = getReadableDatabase(); + return database.query( + SongPlayCountColumns.NAME, + new String[] {SongPlayCountColumns.ID}, + null, + null, + null, + null, + SongPlayCountColumns.PLAY_COUNT_SCORE + " DESC", + (numResults <= 0 ? null : String.valueOf(numResults))); + } + + /** + * This updates all the results for the getTopPlayedResults so that we can get an accurate list of + * the top played results + */ + private synchronized void updateResults() { + if (mDatabaseUpdated) { + return; } - @Override - public void onDowngrade(@NonNull SQLiteDatabase db, int oldVersion, int newVersion) { - // If we ever have downgrade, drop the table to be safe - db.execSQL("DROP TABLE IF EXISTS " + SongPlayCountColumns.NAME); - onCreate(db); + final SQLiteDatabase database = getWritableDatabase(); + + database.beginTransaction(); + + int oldestWeekWeCareAbout = mNumberOfWeeksSinceEpoch - NUM_WEEKS + 1; + // delete rows we don't care about anymore + database.delete( + SongPlayCountColumns.NAME, + SongPlayCountColumns.LAST_UPDATED_WEEK_INDEX + " < " + oldestWeekWeCareAbout, + null); + + // get the remaining rows + Cursor cursor = + database.query( + SongPlayCountColumns.NAME, + new String[] {SongPlayCountColumns.ID}, + null, + null, + null, + null, + null); + + if (cursor != null && cursor.moveToFirst()) { + // for each row, update it + do { + updateExistingRow(database, cursor.getLong(0), false); + } while (cursor.moveToNext()); + + cursor.close(); } - /** - * Increases the play count of a song by 1 - * - * @param songId The song id to increase the play count - */ - public void bumpPlayCount(final long songId) { - if (songId == -1) { - return; - } + mDatabaseUpdated = true; + database.setTransactionSuccessful(); + database.endTransaction(); + } - final SQLiteDatabase database = getWritableDatabase(); - updateExistingRow(database, songId, true); - } + /** @param songId The song Id to remove. */ + public void removeItem(final long songId) { + final SQLiteDatabase database = getWritableDatabase(); + deleteEntry(database, String.valueOf(songId)); + } - /** - * This creates a new entry that indicates a song has been played once as well as its score - * - * @param database a write able database - * @param songId the id of the track - */ - private void createNewPlayedEntry(@NonNull final SQLiteDatabase database, final long songId) { - // no row exists, create a new one - float newScore = getScoreMultiplierForWeek(0); - int newPlayCount = 1; + /** + * Deletes the entry + * + * @param database database to use + * @param stringId id to delete + */ + private void deleteEntry(@NonNull final SQLiteDatabase database, final String stringId) { + database.delete(SongPlayCountColumns.NAME, WHERE_ID_EQUALS, new String[] {stringId}); + } - final ContentValues values = new ContentValues(3); - values.put(SongPlayCountColumns.ID, songId); - values.put(SongPlayCountColumns.PLAY_COUNT_SCORE, newScore); - values.put(SongPlayCountColumns.LAST_UPDATED_WEEK_INDEX, mNumberOfWeeksSinceEpoch); - values.put(getColumnNameForWeek(0), newPlayCount); + public interface SongPlayCountColumns { - database.insert(SongPlayCountColumns.NAME, null, values); - } + String NAME = "song_play_count"; - /** - * This function will take a song entry and update it to the latest week and increase the count - * for the current week by 1 if necessary - * - * @param database a writeable database - * @param id the id of the track to bump - * @param bumpCount whether to bump the current's week play count by 1 and adjust the score - */ - private void updateExistingRow(@NonNull final SQLiteDatabase database, final long id, boolean bumpCount) { - String stringId = String.valueOf(id); + String ID = "song_id"; - // begin the transaction - database.beginTransaction(); + String WEEK_PLAY_COUNT = "week"; - // get the cursor of this content inside the transaction - final Cursor cursor = database.query(SongPlayCountColumns.NAME, null, WHERE_ID_EQUALS, - new String[]{stringId}, null, null, null); + String LAST_UPDATED_WEEK_INDEX = "week_index"; - // if we have a result - if (cursor != null && cursor.moveToFirst()) { - // figure how many weeks since we last updated - int lastUpdatedIndex = cursor.getColumnIndex(SongPlayCountColumns.LAST_UPDATED_WEEK_INDEX); - int lastUpdatedWeek = cursor.getInt(lastUpdatedIndex); - int weekDiff = mNumberOfWeeksSinceEpoch - lastUpdatedWeek; - - // if it's more than the number of weeks we track, delete it and create a new entry - if (Math.abs(weekDiff) >= NUM_WEEKS) { - // this entry needs to be dropped since it is too outdated - deleteEntry(database, stringId); - if (bumpCount) { - createNewPlayedEntry(database, id); - } - } else if (weekDiff != 0) { - // else, shift the weeks - int[] playCounts = new int[NUM_WEEKS]; - - if (weekDiff > 0) { - // time is shifted forwards - for (int i = 0; i < NUM_WEEKS - weekDiff; i++) { - playCounts[i + weekDiff] = cursor.getInt(getColumnIndexForWeek(i)); - } - } else if (weekDiff < 0) { - // time is shifted backwards (by user) - nor typical behavior but we - // will still handle it - - // since weekDiff is -ve, NUM_WEEKS + weekDiff is the real # of weeks we have to - // transfer. Then we transfer the old week i - weekDiff to week i - // for example if the user shifted back 2 weeks, ie -2, then for 0 to - // NUM_WEEKS + (-2) we set the new week i = old week i - (-2) or i+2 - for (int i = 0; i < NUM_WEEKS + weekDiff; i++) { - playCounts[i] = cursor.getInt(getColumnIndexForWeek(i - weekDiff)); - } - } - - // bump the count - if (bumpCount) { - playCounts[0]++; - } - - float score = calculateScore(playCounts); - - // if the score is non-existant, then delete it - if (score < .01f) { - deleteEntry(database, stringId); - } else { - // create the content values - ContentValues values = new ContentValues(NUM_WEEKS + 2); - values.put(SongPlayCountColumns.LAST_UPDATED_WEEK_INDEX, mNumberOfWeeksSinceEpoch); - values.put(SongPlayCountColumns.PLAY_COUNT_SCORE, score); - - for (int i = 0; i < NUM_WEEKS; i++) { - values.put(getColumnNameForWeek(i), playCounts[i]); - } - - // update the entry - database.update(SongPlayCountColumns.NAME, values, WHERE_ID_EQUALS, - new String[]{stringId}); - } - } else if (bumpCount) { - // else no shifting, just update the scores - ContentValues values = new ContentValues(2); - - // increase the score by a single score amount - int scoreIndex = cursor.getColumnIndex(SongPlayCountColumns.PLAY_COUNT_SCORE); - float score = cursor.getFloat(scoreIndex) + getScoreMultiplierForWeek(0); - values.put(SongPlayCountColumns.PLAY_COUNT_SCORE, score); - - // increase the play count by 1 - values.put(getColumnNameForWeek(0), cursor.getInt(getColumnIndexForWeek(0)) + 1); - - // update the entry - database.update(SongPlayCountColumns.NAME, values, WHERE_ID_EQUALS, - new String[]{stringId}); - } - - cursor.close(); - } else if (bumpCount) { - // if we have no existing results, create a new one - createNewPlayedEntry(database, id); - } - - database.setTransactionSuccessful(); - database.endTransaction(); - } - - public void clear() { - final SQLiteDatabase database = getWritableDatabase(); - database.delete(SongPlayCountColumns.NAME, null, null); - } - - /** - * Gets a cursor containing the top songs played. Note this only returns songs that have been - * played at least once in the past NUM_WEEKS - * - * @param numResults number of results to limit by. If <= 0 it returns all results - * @return the top tracks - */ - public Cursor getTopPlayedResults(int numResults) { - updateResults(); - - final SQLiteDatabase database = getReadableDatabase(); - return database.query(SongPlayCountColumns.NAME, new String[]{SongPlayCountColumns.ID}, - null, null, null, null, SongPlayCountColumns.PLAY_COUNT_SCORE + " DESC", - (numResults <= 0 ? null : String.valueOf(numResults))); - } - - /** - * This updates all the results for the getTopPlayedResults so that we can get an - * accurate list of the top played results - */ - private synchronized void updateResults() { - if (mDatabaseUpdated) { - return; - } - - final SQLiteDatabase database = getWritableDatabase(); - - database.beginTransaction(); - - int oldestWeekWeCareAbout = mNumberOfWeeksSinceEpoch - NUM_WEEKS + 1; - // delete rows we don't care about anymore - database.delete(SongPlayCountColumns.NAME, SongPlayCountColumns.LAST_UPDATED_WEEK_INDEX - + " < " + oldestWeekWeCareAbout, null); - - // get the remaining rows - Cursor cursor = database.query(SongPlayCountColumns.NAME, - new String[]{SongPlayCountColumns.ID}, - null, null, null, null, null); - - if (cursor != null && cursor.moveToFirst()) { - // for each row, update it - do { - updateExistingRow(database, cursor.getLong(0), false); - } while (cursor.moveToNext()); - - cursor.close(); - } - - mDatabaseUpdated = true; - database.setTransactionSuccessful(); - database.endTransaction(); - } - - /** - * @param songId The song Id to remove. - */ - public void removeItem(final long songId) { - final SQLiteDatabase database = getWritableDatabase(); - deleteEntry(database, String.valueOf(songId)); - } - - /** - * Deletes the entry - * - * @param database database to use - * @param stringId id to delete - */ - private void deleteEntry(@NonNull final SQLiteDatabase database, final String stringId) { - database.delete(SongPlayCountColumns.NAME, WHERE_ID_EQUALS, new String[]{stringId}); - } - - public interface SongPlayCountColumns { - - String NAME = "song_play_count"; - - String ID = "song_id"; - - String WEEK_PLAY_COUNT = "week"; - - String LAST_UPDATED_WEEK_INDEX = "week_index"; - - String PLAY_COUNT_SCORE = "play_count_score"; - } + String PLAY_COUNT_SCORE = "play_count_score"; + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/repository/SortedCursor.java b/app/src/main/java/code/name/monkey/retromusic/repository/SortedCursor.java index 701d31da..f7705ca4 100644 --- a/app/src/main/java/code/name/monkey/retromusic/repository/SortedCursor.java +++ b/app/src/main/java/code/name/monkey/retromusic/repository/SortedCursor.java @@ -15,154 +15,150 @@ package code.name.monkey.retromusic.repository; import android.database.AbstractCursor; import android.database.Cursor; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; /** * This cursor basically wraps a song cursor and is given a list of the order of the ids of the - * contents of the cursor. It wraps the Cursor and simulates the internal cursor being sorted - * by moving the point to the appropriate spot + * contents of the cursor. It wraps the Cursor and simulates the internal cursor being sorted by + * moving the point to the appropriate spot */ public class SortedCursor extends AbstractCursor { - // cursor to wrap - private final Cursor mCursor; - // the map of external indices to internal indices - private ArrayList mOrderedPositions; - // this contains the ids that weren't found in the underlying cursor - private ArrayList mMissingValues; - // this contains the mapped cursor positions and afterwards the extra ids that weren't found - private HashMap mMapCursorPositions; + // cursor to wrap + private final Cursor mCursor; + // the map of external indices to internal indices + private ArrayList mOrderedPositions; + // this contains the ids that weren't found in the underlying cursor + private ArrayList mMissingValues; + // this contains the mapped cursor positions and afterwards the extra ids that weren't found + private HashMap mMapCursorPositions; - /** - * @param cursor to wrap - * @param order the list of unique ids in sorted order to display - * @param columnName the column name of the id to look up in the internal cursor - */ - public SortedCursor(@NonNull final Cursor cursor, @Nullable final String[] order, final String columnName) { - mCursor = cursor; - mMissingValues = buildCursorPositionMapping(order, columnName); - } + /** + * @param cursor to wrap + * @param order the list of unique ids in sorted order to display + * @param columnName the column name of the id to look up in the internal cursor + */ + public SortedCursor( + @NonNull final Cursor cursor, @Nullable final String[] order, final String columnName) { + mCursor = cursor; + mMissingValues = buildCursorPositionMapping(order, columnName); + } - /** - * This function populates mOrderedPositions with the cursor positions in the order based - * on the order passed in - * - * @param order the target order of the internal cursor - * @return returns the ids that aren't found in the underlying cursor - */ - @NonNull - private ArrayList buildCursorPositionMapping(@Nullable final String[] order, final String columnName) { - ArrayList missingValues = new ArrayList<>(); + /** + * This function populates mOrderedPositions with the cursor positions in the order based on the + * order passed in + * + * @param order the target order of the internal cursor + * @return returns the ids that aren't found in the underlying cursor + */ + @NonNull + private ArrayList buildCursorPositionMapping( + @Nullable final String[] order, final String columnName) { + ArrayList missingValues = new ArrayList<>(); - mOrderedPositions = new ArrayList<>(mCursor.getCount()); + mOrderedPositions = new ArrayList<>(mCursor.getCount()); - mMapCursorPositions = new HashMap<>(mCursor.getCount()); - final int valueColumnIndex = mCursor.getColumnIndex(columnName); + mMapCursorPositions = new HashMap<>(mCursor.getCount()); + final int valueColumnIndex = mCursor.getColumnIndex(columnName); - if (mCursor.moveToFirst()) { - // first figure out where each of the ids are in the cursor - do { - mMapCursorPositions.put(mCursor.getString(valueColumnIndex), mCursor.getPosition()); - } while (mCursor.moveToNext()); + if (mCursor.moveToFirst()) { + // first figure out where each of the ids are in the cursor + do { + mMapCursorPositions.put(mCursor.getString(valueColumnIndex), mCursor.getPosition()); + } while (mCursor.moveToNext()); - if (order != null) { - // now create the ordered positions to map to the internal cursor given the - // external sort order - for (final String value : order) { - if (mMapCursorPositions.containsKey(value)) { - mOrderedPositions.add(mMapCursorPositions.get(value)); - mMapCursorPositions.remove(value); - } else { - missingValues.add(value); - } - } - } - - mCursor.moveToFirst(); + if (order != null) { + // now create the ordered positions to map to the internal cursor given the + // external sort order + for (final String value : order) { + if (mMapCursorPositions.containsKey(value)) { + mOrderedPositions.add(mMapCursorPositions.get(value)); + mMapCursorPositions.remove(value); + } else { + missingValues.add(value); + } } + } - return missingValues; + mCursor.moveToFirst(); } - /** - * @return the list of ids that weren't found in the underlying cursor - */ - public ArrayList getMissingValues() { - return mMissingValues; + return missingValues; + } + + /** @return the list of ids that weren't found in the underlying cursor */ + public ArrayList getMissingValues() { + return mMissingValues; + } + + /** @return the list of ids that were in the underlying cursor but not part of the ordered list */ + @NonNull + public Collection getExtraValues() { + return mMapCursorPositions.keySet(); + } + + @Override + public void close() { + mCursor.close(); + + super.close(); + } + + @Override + public int getCount() { + return mOrderedPositions.size(); + } + + @Override + public String[] getColumnNames() { + return mCursor.getColumnNames(); + } + + @Override + public String getString(int column) { + return mCursor.getString(column); + } + + @Override + public short getShort(int column) { + return mCursor.getShort(column); + } + + @Override + public int getInt(int column) { + return mCursor.getInt(column); + } + + @Override + public long getLong(int column) { + return mCursor.getLong(column); + } + + @Override + public float getFloat(int column) { + return mCursor.getFloat(column); + } + + @Override + public double getDouble(int column) { + return mCursor.getDouble(column); + } + + @Override + public boolean isNull(int column) { + return mCursor.isNull(column); + } + + @Override + public boolean onMove(int oldPosition, int newPosition) { + if (newPosition >= 0 && newPosition < getCount()) { + mCursor.moveToPosition(mOrderedPositions.get(newPosition)); + return true; } - /** - * @return the list of ids that were in the underlying cursor but not part of the ordered list - */ - @NonNull - public Collection getExtraValues() { - return mMapCursorPositions.keySet(); - } - - @Override - public void close() { - mCursor.close(); - - super.close(); - } - - @Override - public int getCount() { - return mOrderedPositions.size(); - } - - @Override - public String[] getColumnNames() { - return mCursor.getColumnNames(); - } - - @Override - public String getString(int column) { - return mCursor.getString(column); - } - - @Override - public short getShort(int column) { - return mCursor.getShort(column); - } - - @Override - public int getInt(int column) { - return mCursor.getInt(column); - } - - @Override - public long getLong(int column) { - return mCursor.getLong(column); - } - - @Override - public float getFloat(int column) { - return mCursor.getFloat(column); - } - - @Override - public double getDouble(int column) { - return mCursor.getDouble(column); - } - - @Override - public boolean isNull(int column) { - return mCursor.isNull(column); - } - - @Override - public boolean onMove(int oldPosition, int newPosition) { - if (newPosition >= 0 && newPosition < getCount()) { - mCursor.moveToPosition(mOrderedPositions.get(newPosition)); - return true; - } - - return false; - } + return false; + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/repository/SortedLongCursor.java b/app/src/main/java/code/name/monkey/retromusic/repository/SortedLongCursor.java index 727cddd8..02ade994 100644 --- a/app/src/main/java/code/name/monkey/retromusic/repository/SortedLongCursor.java +++ b/app/src/main/java/code/name/monkey/retromusic/repository/SortedLongCursor.java @@ -15,154 +15,149 @@ package code.name.monkey.retromusic.repository; import android.database.AbstractCursor; import android.database.Cursor; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; /** * This cursor basically wraps a song cursor and is given a list of the order of the ids of the - * contents of the cursor. It wraps the Cursor and simulates the internal cursor being sorted - * by moving the point to the appropriate spot + * contents of the cursor. It wraps the Cursor and simulates the internal cursor being sorted by + * moving the point to the appropriate spot */ public class SortedLongCursor extends AbstractCursor { - // cursor to wrap - private final Cursor mCursor; - // the map of external indices to internal indices - private ArrayList mOrderedPositions; - // this contains the ids that weren't found in the underlying cursor - private ArrayList mMissingIds; - // this contains the mapped cursor positions and afterwards the extra ids that weren't found - private HashMap mMapCursorPositions; + // cursor to wrap + private final Cursor mCursor; + // the map of external indices to internal indices + private ArrayList mOrderedPositions; + // this contains the ids that weren't found in the underlying cursor + private ArrayList mMissingIds; + // this contains the mapped cursor positions and afterwards the extra ids that weren't found + private HashMap mMapCursorPositions; - /** - * @param cursor to wrap - * @param order the list of unique ids in sorted order to display - * @param columnName the column name of the id to look up in the internal cursor - */ - public SortedLongCursor(final Cursor cursor, final long[] order, final String columnName) { + /** + * @param cursor to wrap + * @param order the list of unique ids in sorted order to display + * @param columnName the column name of the id to look up in the internal cursor + */ + public SortedLongCursor(final Cursor cursor, final long[] order, final String columnName) { - mCursor = cursor; - mMissingIds = buildCursorPositionMapping(order, columnName); - } + mCursor = cursor; + mMissingIds = buildCursorPositionMapping(order, columnName); + } - /** - * This function populates mOrderedPositions with the cursor positions in the order based - * on the order passed in - * - * @param order the target order of the internal cursor - * @return returns the ids that aren't found in the underlying cursor - */ - @NonNull - private ArrayList buildCursorPositionMapping(@Nullable final long[] order, final String columnName) { - ArrayList missingIds = new ArrayList<>(); + /** + * This function populates mOrderedPositions with the cursor positions in the order based on the + * order passed in + * + * @param order the target order of the internal cursor + * @return returns the ids that aren't found in the underlying cursor + */ + @NonNull + private ArrayList buildCursorPositionMapping( + @Nullable final long[] order, final String columnName) { + ArrayList missingIds = new ArrayList<>(); - mOrderedPositions = new ArrayList<>(mCursor.getCount()); + mOrderedPositions = new ArrayList<>(mCursor.getCount()); - mMapCursorPositions = new HashMap<>(mCursor.getCount()); - final int idPosition = mCursor.getColumnIndex(columnName); + mMapCursorPositions = new HashMap<>(mCursor.getCount()); + final int idPosition = mCursor.getColumnIndex(columnName); - if (mCursor.moveToFirst()) { - // first figure out where each of the ids are in the cursor - do { - mMapCursorPositions.put(mCursor.getLong(idPosition), mCursor.getPosition()); - } while (mCursor.moveToNext()); + if (mCursor.moveToFirst()) { + // first figure out where each of the ids are in the cursor + do { + mMapCursorPositions.put(mCursor.getLong(idPosition), mCursor.getPosition()); + } while (mCursor.moveToNext()); - // now create the ordered positions to map to the internal cursor given the - // external sort order - for (int i = 0; order != null && i < order.length; i++) { - final long id = order[i]; - if (mMapCursorPositions.containsKey(id)) { - mOrderedPositions.add(mMapCursorPositions.get(id)); - mMapCursorPositions.remove(id); - } else { - missingIds.add(id); - } - } - - mCursor.moveToFirst(); + // now create the ordered positions to map to the internal cursor given the + // external sort order + for (int i = 0; order != null && i < order.length; i++) { + final long id = order[i]; + if (mMapCursorPositions.containsKey(id)) { + mOrderedPositions.add(mMapCursorPositions.get(id)); + mMapCursorPositions.remove(id); + } else { + missingIds.add(id); } + } - return missingIds; + mCursor.moveToFirst(); } - /** - * @return the list of ids that weren't found in the underlying cursor - */ - public ArrayList getMissingIds() { - return mMissingIds; + return missingIds; + } + + /** @return the list of ids that weren't found in the underlying cursor */ + public ArrayList getMissingIds() { + return mMissingIds; + } + + /** @return the list of ids that were in the underlying cursor but not part of the ordered list */ + @NonNull + public Collection getExtraIds() { + return mMapCursorPositions.keySet(); + } + + @Override + public void close() { + mCursor.close(); + + super.close(); + } + + @Override + public int getCount() { + return mOrderedPositions.size(); + } + + @Override + public String[] getColumnNames() { + return mCursor.getColumnNames(); + } + + @Override + public String getString(int column) { + return mCursor.getString(column); + } + + @Override + public short getShort(int column) { + return mCursor.getShort(column); + } + + @Override + public int getInt(int column) { + return mCursor.getInt(column); + } + + @Override + public long getLong(int column) { + return mCursor.getLong(column); + } + + @Override + public float getFloat(int column) { + return mCursor.getFloat(column); + } + + @Override + public double getDouble(int column) { + return mCursor.getDouble(column); + } + + @Override + public boolean isNull(int column) { + return mCursor.isNull(column); + } + + @Override + public boolean onMove(int oldPosition, int newPosition) { + if (newPosition >= 0 && newPosition < getCount()) { + mCursor.moveToPosition(mOrderedPositions.get(newPosition)); + return true; } - /** - * @return the list of ids that were in the underlying cursor but not part of the ordered list - */ - @NonNull - public Collection getExtraIds() { - return mMapCursorPositions.keySet(); - } - - @Override - public void close() { - mCursor.close(); - - super.close(); - } - - @Override - public int getCount() { - return mOrderedPositions.size(); - } - - @Override - public String[] getColumnNames() { - return mCursor.getColumnNames(); - } - - @Override - public String getString(int column) { - return mCursor.getString(column); - } - - @Override - public short getShort(int column) { - return mCursor.getShort(column); - } - - @Override - public int getInt(int column) { - return mCursor.getInt(column); - } - - @Override - public long getLong(int column) { - return mCursor.getLong(column); - } - - @Override - public float getFloat(int column) { - return mCursor.getFloat(column); - } - - @Override - public double getDouble(int column) { - return mCursor.getDouble(column); - } - - @Override - public boolean isNull(int column) { - return mCursor.isNull(column); - } - - @Override - public boolean onMove(int oldPosition, int newPosition) { - if (newPosition >= 0 && newPosition < getCount()) { - mCursor.moveToPosition(mOrderedPositions.get(newPosition)); - return true; - } - - return false; - } + return false; + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/service/MultiPlayer.java b/app/src/main/java/code/name/monkey/retromusic/service/MultiPlayer.java index 5f49414c..9ed12f5e 100644 --- a/app/src/main/java/code/name/monkey/retromusic/service/MultiPlayer.java +++ b/app/src/main/java/code/name/monkey/retromusic/service/MultiPlayer.java @@ -23,327 +23,300 @@ import android.net.Uri; import android.os.PowerManager; import android.util.Log; import android.widget.Toast; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import code.name.monkey.retromusic.R; import code.name.monkey.retromusic.service.playback.Playback; import code.name.monkey.retromusic.util.PreferenceUtil; -/** - * @author Andrew Neal, Karim Abou Zeid (kabouzeid) - */ -public class MultiPlayer implements Playback, MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener { - public static final String TAG = MultiPlayer.class.getSimpleName(); +/** @author Andrew Neal, Karim Abou Zeid (kabouzeid) */ +public class MultiPlayer + implements Playback, MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener { + public static final String TAG = MultiPlayer.class.getSimpleName(); - private MediaPlayer mCurrentMediaPlayer = new MediaPlayer(); - private MediaPlayer mNextMediaPlayer; + private MediaPlayer mCurrentMediaPlayer = new MediaPlayer(); + private MediaPlayer mNextMediaPlayer; - private Context context; - @Nullable - private Playback.PlaybackCallbacks callbacks; + private Context context; + @Nullable private Playback.PlaybackCallbacks callbacks; - private boolean mIsInitialized = false; + private boolean mIsInitialized = false; - /** - * Constructor of MultiPlayer - */ - MultiPlayer(final Context context) { - this.context = context; - mCurrentMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK); + /** Constructor of MultiPlayer */ + MultiPlayer(final Context context) { + this.context = context; + mCurrentMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK); + } + + /** + * @param path The path of the file, or the http/rtsp URL of the stream you want to play + * @return True if the player has been prepared and is ready to play, false otherwise + */ + @Override + public boolean setDataSource(@NonNull final String path) { + mIsInitialized = false; + mIsInitialized = setDataSourceImpl(mCurrentMediaPlayer, path); + if (mIsInitialized) { + setNextDataSource(null); } + return mIsInitialized; + } - /** - * @param path The path of the file, or the http/rtsp URL of the stream - * you want to play - * @return True if the player has been prepared and is - * ready to play, false otherwise - */ - @Override - public boolean setDataSource(@NonNull final String path) { - mIsInitialized = false; - mIsInitialized = setDataSourceImpl(mCurrentMediaPlayer, path); - if (mIsInitialized) { - setNextDataSource(null); - } - return mIsInitialized; + /** + * @param player The {@link MediaPlayer} to use + * @param path The path of the file, or the http/rtsp URL of the stream you want to play + * @return True if the player has been prepared and is ready to play, false otherwise + */ + private boolean setDataSourceImpl(@NonNull final MediaPlayer player, @NonNull final String path) { + if (context == null) { + return false; } + try { + player.reset(); + player.setOnPreparedListener(null); + if (path.startsWith("content://")) { + player.setDataSource(context, Uri.parse(path)); + } else { + player.setDataSource(path); + } + player.setAudioStreamType(AudioManager.STREAM_MUSIC); + player.prepare(); + } catch (Exception e) { + return false; + } + player.setOnCompletionListener(this); + player.setOnErrorListener(this); + final Intent intent = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION); + intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId()); + intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.getPackageName()); + intent.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC); + context.sendBroadcast(intent); + return true; + } - /** - * @param player The {@link MediaPlayer} to use - * @param path The path of the file, or the http/rtsp URL of the stream - * you want to play - * @return True if the player has been prepared and is - * ready to play, false otherwise - */ - private boolean setDataSourceImpl(@NonNull final MediaPlayer player, @NonNull final String path) { - if (context == null) { - return false; - } + /** + * Set the MediaPlayer to start when this MediaPlayer finishes playback. + * + * @param path The path of the file, or the http/rtsp URL of the stream you want to play + */ + @Override + public void setNextDataSource(@Nullable final String path) { + if (context == null) { + return; + } + try { + mCurrentMediaPlayer.setNextMediaPlayer(null); + } catch (IllegalArgumentException e) { + Log.i(TAG, "Next media player is current one, continuing"); + } catch (IllegalStateException e) { + Log.e(TAG, "Media player not initialized!"); + return; + } + if (mNextMediaPlayer != null) { + mNextMediaPlayer.release(); + mNextMediaPlayer = null; + } + if (path == null) { + return; + } + if (PreferenceUtil.INSTANCE.isGapLessPlayback()) { + mNextMediaPlayer = new MediaPlayer(); + mNextMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK); + mNextMediaPlayer.setAudioSessionId(getAudioSessionId()); + if (setDataSourceImpl(mNextMediaPlayer, path)) { try { - player.reset(); - player.setOnPreparedListener(null); - if (path.startsWith("content://")) { - player.setDataSource(context, Uri.parse(path)); - } else { - player.setDataSource(path); - } - player.setAudioStreamType(AudioManager.STREAM_MUSIC); - player.prepare(); - } catch (Exception e) { - return false; - } - player.setOnCompletionListener(this); - player.setOnErrorListener(this); - final Intent intent = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION); - intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId()); - intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.getPackageName()); - intent.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC); - context.sendBroadcast(intent); - return true; - } - - /** - * Set the MediaPlayer to start when this MediaPlayer finishes playback. - * - * @param path The path of the file, or the http/rtsp URL of the stream - * you want to play - */ - @Override - public void setNextDataSource(@Nullable final String path) { - if (context == null) { - return; - } - try { - mCurrentMediaPlayer.setNextMediaPlayer(null); - } catch (IllegalArgumentException e) { - Log.i(TAG, "Next media player is current one, continuing"); - } catch (IllegalStateException e) { - Log.e(TAG, "Media player not initialized!"); - return; - } - if (mNextMediaPlayer != null) { - mNextMediaPlayer.release(); - mNextMediaPlayer = null; - } - if (path == null) { - return; - } - if (PreferenceUtil.INSTANCE.isGapLessPlayback()) { - mNextMediaPlayer = new MediaPlayer(); - mNextMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK); - mNextMediaPlayer.setAudioSessionId(getAudioSessionId()); - if (setDataSourceImpl(mNextMediaPlayer, path)) { - try { - mCurrentMediaPlayer.setNextMediaPlayer(mNextMediaPlayer); - } catch (@NonNull IllegalArgumentException | IllegalStateException e) { - Log.e(TAG, "setNextDataSource: setNextMediaPlayer()", e); - if (mNextMediaPlayer != null) { - mNextMediaPlayer.release(); - mNextMediaPlayer = null; - } - } - } else { - if (mNextMediaPlayer != null) { - mNextMediaPlayer.release(); - mNextMediaPlayer = null; - } - } - } - } - - /** - * Sets the callbacks - * - * @param callbacks The callbacks to use - */ - @Override - public void setCallbacks(@Nullable final Playback.PlaybackCallbacks callbacks) { - this.callbacks = callbacks; - } - - /** - * @return True if the player is ready to go, false otherwise - */ - @Override - public boolean isInitialized() { - return mIsInitialized; - } - - /** - * Starts or resumes playback. - */ - @Override - public boolean start() { - try { - mCurrentMediaPlayer.start(); - return true; - } catch (IllegalStateException e) { - return false; - } - } - - /** - * Resets the MediaPlayer to its uninitialized state. - */ - @Override - public void stop() { - mCurrentMediaPlayer.reset(); - mIsInitialized = false; - } - - /** - * Releases resources associated with this MediaPlayer object. - */ - @Override - public void release() { - stop(); - mCurrentMediaPlayer.release(); - if (mNextMediaPlayer != null) { - mNextMediaPlayer.release(); - } - } - - /** - * Pauses playback. Call start() to resume. - */ - @Override - public boolean pause() { - try { - mCurrentMediaPlayer.pause(); - return true; - } catch (IllegalStateException e) { - return false; - } - } - - /** - * Checks whether the MultiPlayer is playing. - */ - @Override - public boolean isPlaying() { - return mIsInitialized && mCurrentMediaPlayer.isPlaying(); - } - - /** - * Gets the duration of the file. - * - * @return The duration in milliseconds - */ - @Override - public int duration() { - if (!mIsInitialized) { - return -1; - } - try { - return mCurrentMediaPlayer.getDuration(); - } catch (IllegalStateException e) { - return -1; - } - } - - /** - * Gets the current playback position. - * - * @return The current position in milliseconds - */ - @Override - public int position() { - if (!mIsInitialized) { - return -1; - } - try { - return mCurrentMediaPlayer.getCurrentPosition(); - } catch (IllegalStateException e) { - return -1; - } - } - - /** - * Gets the current playback position. - * - * @param whereto The offset in milliseconds from the start to seek to - * @return The offset in milliseconds from the start to seek to - */ - @Override - public int seek(final int whereto) { - try { - mCurrentMediaPlayer.seekTo(whereto); - return whereto; - } catch (IllegalStateException e) { - return -1; - } - } - - @Override - public boolean setVolume(final float vol) { - try { - mCurrentMediaPlayer.setVolume(vol, vol); - return true; - } catch (IllegalStateException e) { - return false; - } - } - - /** - * Sets the audio session ID. - * - * @param sessionId The audio session ID - */ - @Override - public boolean setAudioSessionId(final int sessionId) { - try { - mCurrentMediaPlayer.setAudioSessionId(sessionId); - return true; + mCurrentMediaPlayer.setNextMediaPlayer(mNextMediaPlayer); } catch (@NonNull IllegalArgumentException | IllegalStateException e) { - return false; - } - } - - /** - * Returns the audio session ID. - * - * @return The current audio session ID. - */ - @Override - public int getAudioSessionId() { - return mCurrentMediaPlayer.getAudioSessionId(); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean onError(final MediaPlayer mp, final int what, final int extra) { - mIsInitialized = false; - mCurrentMediaPlayer.release(); - mCurrentMediaPlayer = new MediaPlayer(); - mCurrentMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK); - if (context != null) { - Toast.makeText(context, context.getResources().getString(R.string.unplayable_file), Toast.LENGTH_SHORT).show(); - } - return false; - } - - /** - * {@inheritDoc} - */ - @Override - public void onCompletion(final MediaPlayer mp) { - if (mp.equals(mCurrentMediaPlayer) && mNextMediaPlayer != null) { - mIsInitialized = false; - mCurrentMediaPlayer.release(); - mCurrentMediaPlayer = mNextMediaPlayer; - mIsInitialized = true; + Log.e(TAG, "setNextDataSource: setNextMediaPlayer()", e); + if (mNextMediaPlayer != null) { + mNextMediaPlayer.release(); mNextMediaPlayer = null; - if (callbacks != null) - callbacks.onTrackWentToNext(); - } else { - if (callbacks != null) - callbacks.onTrackEnded(); + } } + } else { + if (mNextMediaPlayer != null) { + mNextMediaPlayer.release(); + mNextMediaPlayer = null; + } + } } + } + /** + * Sets the callbacks + * + * @param callbacks The callbacks to use + */ + @Override + public void setCallbacks(@Nullable final Playback.PlaybackCallbacks callbacks) { + this.callbacks = callbacks; + } -} \ No newline at end of file + /** @return True if the player is ready to go, false otherwise */ + @Override + public boolean isInitialized() { + return mIsInitialized; + } + + /** Starts or resumes playback. */ + @Override + public boolean start() { + try { + mCurrentMediaPlayer.start(); + return true; + } catch (IllegalStateException e) { + return false; + } + } + + /** Resets the MediaPlayer to its uninitialized state. */ + @Override + public void stop() { + mCurrentMediaPlayer.reset(); + mIsInitialized = false; + } + + /** Releases resources associated with this MediaPlayer object. */ + @Override + public void release() { + stop(); + mCurrentMediaPlayer.release(); + if (mNextMediaPlayer != null) { + mNextMediaPlayer.release(); + } + } + + /** Pauses playback. Call start() to resume. */ + @Override + public boolean pause() { + try { + mCurrentMediaPlayer.pause(); + return true; + } catch (IllegalStateException e) { + return false; + } + } + + /** Checks whether the MultiPlayer is playing. */ + @Override + public boolean isPlaying() { + return mIsInitialized && mCurrentMediaPlayer.isPlaying(); + } + + /** + * Gets the duration of the file. + * + * @return The duration in milliseconds + */ + @Override + public int duration() { + if (!mIsInitialized) { + return -1; + } + try { + return mCurrentMediaPlayer.getDuration(); + } catch (IllegalStateException e) { + return -1; + } + } + + /** + * Gets the current playback position. + * + * @return The current position in milliseconds + */ + @Override + public int position() { + if (!mIsInitialized) { + return -1; + } + try { + return mCurrentMediaPlayer.getCurrentPosition(); + } catch (IllegalStateException e) { + return -1; + } + } + + /** + * Gets the current playback position. + * + * @param whereto The offset in milliseconds from the start to seek to + * @return The offset in milliseconds from the start to seek to + */ + @Override + public int seek(final int whereto) { + try { + mCurrentMediaPlayer.seekTo(whereto); + return whereto; + } catch (IllegalStateException e) { + return -1; + } + } + + @Override + public boolean setVolume(final float vol) { + try { + mCurrentMediaPlayer.setVolume(vol, vol); + return true; + } catch (IllegalStateException e) { + return false; + } + } + + /** + * Sets the audio session ID. + * + * @param sessionId The audio session ID + */ + @Override + public boolean setAudioSessionId(final int sessionId) { + try { + mCurrentMediaPlayer.setAudioSessionId(sessionId); + return true; + } catch (@NonNull IllegalArgumentException | IllegalStateException e) { + return false; + } + } + + /** + * Returns the audio session ID. + * + * @return The current audio session ID. + */ + @Override + public int getAudioSessionId() { + return mCurrentMediaPlayer.getAudioSessionId(); + } + + /** {@inheritDoc} */ + @Override + public boolean onError(final MediaPlayer mp, final int what, final int extra) { + mIsInitialized = false; + mCurrentMediaPlayer.release(); + mCurrentMediaPlayer = new MediaPlayer(); + mCurrentMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK); + if (context != null) { + Toast.makeText( + context, + context.getResources().getString(R.string.unplayable_file), + Toast.LENGTH_SHORT) + .show(); + } + return false; + } + + /** {@inheritDoc} */ + @Override + public void onCompletion(final MediaPlayer mp) { + if (mp.equals(mCurrentMediaPlayer) && mNextMediaPlayer != null) { + mIsInitialized = false; + mCurrentMediaPlayer.release(); + mCurrentMediaPlayer = mNextMediaPlayer; + mIsInitialized = true; + mNextMediaPlayer = null; + if (callbacks != null) callbacks.onTrackWentToNext(); + } else { + if (callbacks != null) callbacks.onTrackEnded(); + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/service/MusicService.java b/app/src/main/java/code/name/monkey/retromusic/service/MusicService.java index ebc0a9ad..f9abe7d6 100644 --- a/app/src/main/java/code/name/monkey/retromusic/service/MusicService.java +++ b/app/src/main/java/code/name/monkey/retromusic/service/MusicService.java @@ -14,6 +14,13 @@ package code.name.monkey.retromusic.service; +import static code.name.monkey.retromusic.ConstantsKt.ALBUM_ART_ON_LOCK_SCREEN; +import static code.name.monkey.retromusic.ConstantsKt.BLURRED_ALBUM_ART; +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.GAP_LESS_PLAYBACK; +import static code.name.monkey.retromusic.ConstantsKt.TOGGLE_HEADSET; + import android.app.PendingIntent; import android.app.Service; import android.appwidget.AppWidgetManager; @@ -47,21 +54,9 @@ import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; import android.util.Log; import android.widget.Toast; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.preference.PreferenceManager; - -import com.bumptech.glide.BitmapRequestBuilder; -import com.bumptech.glide.Glide; -import com.bumptech.glide.request.animation.GlideAnimation; -import com.bumptech.glide.request.target.SimpleTarget; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.Random; - import code.name.monkey.retromusic.R; import code.name.monkey.retromusic.activities.LockScreenActivity; import code.name.monkey.retromusic.appwidgets.AppWidgetBig; @@ -84,1304 +79,1368 @@ import code.name.monkey.retromusic.service.playback.Playback; import code.name.monkey.retromusic.util.MusicUtil; import code.name.monkey.retromusic.util.PreferenceUtil; import code.name.monkey.retromusic.util.RetroUtil; +import com.bumptech.glide.BitmapRequestBuilder; +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.animation.GlideAnimation; +import com.bumptech.glide.request.target.SimpleTarget; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Random; -import static code.name.monkey.retromusic.ConstantsKt.ALBUM_ART_ON_LOCK_SCREEN; -import static code.name.monkey.retromusic.ConstantsKt.BLURRED_ALBUM_ART; -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.GAP_LESS_PLAYBACK; -import static code.name.monkey.retromusic.ConstantsKt.TOGGLE_HEADSET; +/** @author Karim Abou Zeid (kabouzeid), Andrew Neal */ +public class MusicService extends Service + implements SharedPreferences.OnSharedPreferenceChangeListener, Playback.PlaybackCallbacks { -/** - * @author Karim Abou Zeid (kabouzeid), Andrew Neal - */ -public class MusicService extends Service implements - SharedPreferences.OnSharedPreferenceChangeListener, Playback.PlaybackCallbacks { + public static final String TAG = MusicService.class.getSimpleName(); + public static final String RETRO_MUSIC_PACKAGE_NAME = "code.name.monkey.retromusic"; + public static final String MUSIC_PACKAGE_NAME = "com.android.music"; + public static final String ACTION_TOGGLE_PAUSE = RETRO_MUSIC_PACKAGE_NAME + ".togglepause"; + public static final String ACTION_PLAY = RETRO_MUSIC_PACKAGE_NAME + ".play"; + public static final String ACTION_PLAY_PLAYLIST = RETRO_MUSIC_PACKAGE_NAME + ".play.playlist"; + public static final String ACTION_PAUSE = RETRO_MUSIC_PACKAGE_NAME + ".pause"; + public static final String ACTION_STOP = RETRO_MUSIC_PACKAGE_NAME + ".stop"; + public static final String ACTION_SKIP = RETRO_MUSIC_PACKAGE_NAME + ".skip"; + public static final String ACTION_REWIND = RETRO_MUSIC_PACKAGE_NAME + ".rewind"; + public static final String ACTION_QUIT = RETRO_MUSIC_PACKAGE_NAME + ".quitservice"; + public static final String ACTION_PENDING_QUIT = RETRO_MUSIC_PACKAGE_NAME + ".pendingquitservice"; + public static final String INTENT_EXTRA_PLAYLIST = + RETRO_MUSIC_PACKAGE_NAME + "intentextra.playlist"; + public static final String INTENT_EXTRA_SHUFFLE_MODE = + RETRO_MUSIC_PACKAGE_NAME + ".intentextra.shufflemode"; + public static final String APP_WIDGET_UPDATE = RETRO_MUSIC_PACKAGE_NAME + ".appwidgetupdate"; + public static final String EXTRA_APP_WIDGET_NAME = RETRO_MUSIC_PACKAGE_NAME + "app_widget_name"; + // Do not change these three strings as it will break support with other apps (e.g. last.fm + // scrobbling) + public static final String META_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".metachanged"; + public static final String QUEUE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".queuechanged"; + public static final String PLAY_STATE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".playstatechanged"; + public static final String FAVORITE_STATE_CHANGED = + RETRO_MUSIC_PACKAGE_NAME + "favoritestatechanged"; + public static final String REPEAT_MODE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".repeatmodechanged"; + public static final String SHUFFLE_MODE_CHANGED = + RETRO_MUSIC_PACKAGE_NAME + ".shufflemodechanged"; + public static final String MEDIA_STORE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".mediastorechanged"; + public static final String CYCLE_REPEAT = RETRO_MUSIC_PACKAGE_NAME + ".cyclerepeat"; + public static final String TOGGLE_SHUFFLE = RETRO_MUSIC_PACKAGE_NAME + ".toggleshuffle"; + public static final String TOGGLE_FAVORITE = RETRO_MUSIC_PACKAGE_NAME + ".togglefavorite"; + public static final String SAVED_POSITION = "POSITION"; + public static final String SAVED_POSITION_IN_TRACK = "POSITION_IN_TRACK"; + public static final String SAVED_SHUFFLE_MODE = "SHUFFLE_MODE"; + public static final String SAVED_REPEAT_MODE = "REPEAT_MODE"; + public static final int RELEASE_WAKELOCK = 0; + public static final int TRACK_ENDED = 1; + public static final int TRACK_WENT_TO_NEXT = 2; + public static final int PLAY_SONG = 3; + public static final int PREPARE_NEXT = 4; + public static final int SET_POSITION = 5; + public static final int FOCUS_CHANGE = 6; + public static final int DUCK = 7; + public static final int UNDUCK = 8; + public static final int RESTORE_QUEUES = 9; + public static final int SHUFFLE_MODE_NONE = 0; + public static final int SHUFFLE_MODE_SHUFFLE = 1; + public static final int REPEAT_MODE_NONE = 0; + public static final int REPEAT_MODE_ALL = 1; + public static final int REPEAT_MODE_THIS = 2; + public static final int SAVE_QUEUES = 0; + private static final long MEDIA_SESSION_ACTIONS = + PlaybackStateCompat.ACTION_PLAY + | PlaybackStateCompat.ACTION_PAUSE + | PlaybackStateCompat.ACTION_PLAY_PAUSE + | PlaybackStateCompat.ACTION_SKIP_TO_NEXT + | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS + | PlaybackStateCompat.ACTION_STOP + | PlaybackStateCompat.ACTION_SEEK_TO; + private final IBinder musicBind = new MusicBinder(); + public int nextPosition = -1; - public static final String TAG = MusicService.class.getSimpleName(); - public static final String RETRO_MUSIC_PACKAGE_NAME = "code.name.monkey.retromusic"; - public static final String MUSIC_PACKAGE_NAME = "com.android.music"; - public static final String ACTION_TOGGLE_PAUSE = RETRO_MUSIC_PACKAGE_NAME + ".togglepause"; - public static final String ACTION_PLAY = RETRO_MUSIC_PACKAGE_NAME + ".play"; - public static final String ACTION_PLAY_PLAYLIST = RETRO_MUSIC_PACKAGE_NAME + ".play.playlist"; - public static final String ACTION_PAUSE = RETRO_MUSIC_PACKAGE_NAME + ".pause"; - public static final String ACTION_STOP = RETRO_MUSIC_PACKAGE_NAME + ".stop"; - public static final String ACTION_SKIP = RETRO_MUSIC_PACKAGE_NAME + ".skip"; - public static final String ACTION_REWIND = RETRO_MUSIC_PACKAGE_NAME + ".rewind"; - public static final String ACTION_QUIT = RETRO_MUSIC_PACKAGE_NAME + ".quitservice"; - public static final String ACTION_PENDING_QUIT = RETRO_MUSIC_PACKAGE_NAME + ".pendingquitservice"; - public static final String INTENT_EXTRA_PLAYLIST = RETRO_MUSIC_PACKAGE_NAME + "intentextra.playlist"; - public static final String INTENT_EXTRA_SHUFFLE_MODE = RETRO_MUSIC_PACKAGE_NAME + ".intentextra.shufflemode"; - public static final String APP_WIDGET_UPDATE = RETRO_MUSIC_PACKAGE_NAME + ".appwidgetupdate"; - public static final String EXTRA_APP_WIDGET_NAME = RETRO_MUSIC_PACKAGE_NAME + "app_widget_name"; - // Do not change these three strings as it will break support with other apps (e.g. last.fm scrobbling) - public static final String META_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".metachanged"; - public static final String QUEUE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".queuechanged"; - public static final String PLAY_STATE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".playstatechanged"; - public static final String FAVORITE_STATE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + "favoritestatechanged"; - public static final String REPEAT_MODE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".repeatmodechanged"; - public static final String SHUFFLE_MODE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".shufflemodechanged"; - public static final String MEDIA_STORE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".mediastorechanged"; - public static final String CYCLE_REPEAT = RETRO_MUSIC_PACKAGE_NAME + ".cyclerepeat"; - public static final String TOGGLE_SHUFFLE = RETRO_MUSIC_PACKAGE_NAME + ".toggleshuffle"; - public static final String TOGGLE_FAVORITE = RETRO_MUSIC_PACKAGE_NAME + ".togglefavorite"; - public static final String SAVED_POSITION = "POSITION"; - public static final String SAVED_POSITION_IN_TRACK = "POSITION_IN_TRACK"; - public static final String SAVED_SHUFFLE_MODE = "SHUFFLE_MODE"; - public static final String SAVED_REPEAT_MODE = "REPEAT_MODE"; - public static final int RELEASE_WAKELOCK = 0; - public static final int TRACK_ENDED = 1; - public static final int TRACK_WENT_TO_NEXT = 2; - public static final int PLAY_SONG = 3; - public static final int PREPARE_NEXT = 4; - public static final int SET_POSITION = 5; - public static final int FOCUS_CHANGE = 6; - public static final int DUCK = 7; - public static final int UNDUCK = 8; - public static final int RESTORE_QUEUES = 9; - public static final int SHUFFLE_MODE_NONE = 0; - public static final int SHUFFLE_MODE_SHUFFLE = 1; - public static final int REPEAT_MODE_NONE = 0; - public static final int REPEAT_MODE_ALL = 1; - public static final int REPEAT_MODE_THIS = 2; - public static final int SAVE_QUEUES = 0; - private static final long MEDIA_SESSION_ACTIONS = PlaybackStateCompat.ACTION_PLAY - | PlaybackStateCompat.ACTION_PAUSE - | PlaybackStateCompat.ACTION_PLAY_PAUSE - | PlaybackStateCompat.ACTION_SKIP_TO_NEXT - | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS - | PlaybackStateCompat.ACTION_STOP - | PlaybackStateCompat.ACTION_SEEK_TO; - private final IBinder musicBind = new MusicBinder(); - public int nextPosition = -1; + public boolean pendingQuit = false; - public boolean pendingQuit = false; + @Nullable public Playback playback; - @Nullable - public Playback playback; + public int position = -1; - public int position = -1; + private AppWidgetBig appWidgetBig = AppWidgetBig.Companion.getInstance(); - private AppWidgetBig appWidgetBig = AppWidgetBig.Companion.getInstance(); + private AppWidgetCard appWidgetCard = AppWidgetCard.Companion.getInstance(); - private AppWidgetCard appWidgetCard = AppWidgetCard.Companion.getInstance(); + private AppWidgetClassic appWidgetClassic = AppWidgetClassic.Companion.getInstance(); - private AppWidgetClassic appWidgetClassic = AppWidgetClassic.Companion.getInstance(); + private AppWidgetSmall appWidgetSmall = AppWidgetSmall.Companion.getInstance(); - private AppWidgetSmall appWidgetSmall = AppWidgetSmall.Companion.getInstance(); + private AppWidgetText appWidgetText = AppWidgetText.Companion.getInstance(); - private AppWidgetText appWidgetText = AppWidgetText.Companion.getInstance(); - - private final BroadcastReceiver widgetIntentReceiver = new BroadcastReceiver() { + private final BroadcastReceiver widgetIntentReceiver = + new BroadcastReceiver() { @Override public void onReceive(final Context context, final Intent intent) { - final String command = intent.getStringExtra(EXTRA_APP_WIDGET_NAME); - final int[] ids = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS); - if (command != null) { - switch (command) { - case AppWidgetClassic.NAME: { - appWidgetClassic.performUpdate(MusicService.this, ids); - break; - } - case AppWidgetSmall.NAME: { - appWidgetSmall.performUpdate(MusicService.this, ids); - break; - } - case AppWidgetBig.NAME: { - appWidgetBig.performUpdate(MusicService.this, ids); - break; - } - case AppWidgetCard.NAME: { - appWidgetCard.performUpdate(MusicService.this, ids); - break; - } - case AppWidgetText.NAME: { - appWidgetText.performUpdate(MusicService.this, ids); - break; - } + final String command = intent.getStringExtra(EXTRA_APP_WIDGET_NAME); + final int[] ids = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS); + if (command != null) { + switch (command) { + case AppWidgetClassic.NAME: + { + appWidgetClassic.performUpdate(MusicService.this, ids); + break; + } + case AppWidgetSmall.NAME: + { + appWidgetSmall.performUpdate(MusicService.this, ids); + break; + } + case AppWidgetBig.NAME: + { + appWidgetBig.performUpdate(MusicService.this, ids); + break; + } + case AppWidgetCard.NAME: + { + appWidgetCard.performUpdate(MusicService.this, ids); + break; + } + case AppWidgetText.NAME: + { + appWidgetText.performUpdate(MusicService.this, ids); + break; } } - + } } - }; - private AudioManager audioManager; - private IntentFilter becomingNoisyReceiverIntentFilter = new IntentFilter( - AudioManager.ACTION_AUDIO_BECOMING_NOISY); - private boolean becomingNoisyReceiverRegistered; - private IntentFilter bluetoothConnectedIntentFilter = new IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED); - private boolean bluetoothConnectedRegistered = false; - private IntentFilter headsetReceiverIntentFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG); - private boolean headsetReceiverRegistered = false; - private MediaSessionCompat mediaSession; - private ContentObserver mediaStoreObserver; - private HandlerThread musicPlayerHandlerThread; - private boolean notHandledMetaChangedForCurrentTrack; - private List originalPlayingQueue = new ArrayList<>(); - private List playingQueue = new ArrayList<>(); - private boolean pausedByTransientLossOfFocus; + }; + private AudioManager audioManager; + private IntentFilter becomingNoisyReceiverIntentFilter = + new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY); + private boolean becomingNoisyReceiverRegistered; + private IntentFilter bluetoothConnectedIntentFilter = + new IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED); + private boolean bluetoothConnectedRegistered = false; + private IntentFilter headsetReceiverIntentFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG); + private boolean headsetReceiverRegistered = false; + private MediaSessionCompat mediaSession; + private ContentObserver mediaStoreObserver; + private HandlerThread musicPlayerHandlerThread; + private boolean notHandledMetaChangedForCurrentTrack; + private List originalPlayingQueue = new ArrayList<>(); + private List playingQueue = new ArrayList<>(); + private boolean pausedByTransientLossOfFocus; - private final BroadcastReceiver becomingNoisyReceiver = new BroadcastReceiver() { + private final BroadcastReceiver becomingNoisyReceiver = + new BroadcastReceiver() { @Override public void onReceive(Context context, @NonNull Intent intent) { - if (intent.getAction() != null && intent.getAction().equals(AudioManager.ACTION_AUDIO_BECOMING_NOISY)) { - pause(); - } + if (intent.getAction() != null + && intent.getAction().equals(AudioManager.ACTION_AUDIO_BECOMING_NOISY)) { + pause(); + } } - }; + }; - private PlaybackHandler playerHandler; + private PlaybackHandler playerHandler; - private final AudioManager.OnAudioFocusChangeListener audioFocusListener - = new AudioManager.OnAudioFocusChangeListener() { + private final AudioManager.OnAudioFocusChangeListener audioFocusListener = + new AudioManager.OnAudioFocusChangeListener() { @Override public void onAudioFocusChange(final int focusChange) { - playerHandler.obtainMessage(FOCUS_CHANGE, focusChange, 0).sendToTarget(); + playerHandler.obtainMessage(FOCUS_CHANGE, focusChange, 0).sendToTarget(); } - }; + }; - private PlayingNotification playingNotification; - private final BroadcastReceiver updateFavoriteReceiver = new BroadcastReceiver() { + private PlayingNotification playingNotification; + private final BroadcastReceiver updateFavoriteReceiver = + new BroadcastReceiver() { @Override public void onReceive(final Context context, final Intent intent) { - updateNotification(); + updateNotification(); } - }; - private final BroadcastReceiver lockScreenReceiver = new BroadcastReceiver() { + }; + private final BroadcastReceiver lockScreenReceiver = + new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - if (PreferenceUtil.INSTANCE.isLockScreen() && isPlaying()) { - Intent lockIntent = new Intent(context, LockScreenActivity.class); - lockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(lockIntent); - } + if (PreferenceUtil.INSTANCE.isLockScreen() && isPlaying()) { + Intent lockIntent = new Intent(context, LockScreenActivity.class); + lockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(lockIntent); + } } - }; - private QueueSaveHandler queueSaveHandler; - private HandlerThread queueSaveHandlerThread; - private boolean queuesRestored; - private int repeatMode; - private int shuffleMode; - private SongPlayCountHelper songPlayCountHelper = new SongPlayCountHelper(); - private final BroadcastReceiver bluetoothReceiver = new BroadcastReceiver() { + }; + private QueueSaveHandler queueSaveHandler; + private HandlerThread queueSaveHandlerThread; + private boolean queuesRestored; + private int repeatMode; + private int shuffleMode; + private SongPlayCountHelper songPlayCountHelper = new SongPlayCountHelper(); + private final BroadcastReceiver bluetoothReceiver = + new BroadcastReceiver() { @Override public void onReceive(final Context context, final Intent intent) { - String action = intent.getAction(); - if (action != null) { - if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(action) && - PreferenceUtil.INSTANCE.isBluetoothSpeaker()) { - if (VERSION.SDK_INT >= VERSION_CODES.M) { - if (getAudioManager().getDevices(AudioManager.GET_DEVICES_OUTPUTS).length > 0) { - play(); - } - } else { - if (getAudioManager().isBluetoothA2dpOn()) { - play(); - } - } + String action = intent.getAction(); + if (action != null) { + if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(action) + && PreferenceUtil.INSTANCE.isBluetoothSpeaker()) { + if (VERSION.SDK_INT >= VERSION_CODES.M) { + if (getAudioManager().getDevices(AudioManager.GET_DEVICES_OUTPUTS).length > 0) { + play(); } + } else { + if (getAudioManager().isBluetoothA2dpOn()) { + play(); + } + } } + } } - }; - private PhoneStateListener phoneStateListener = new PhoneStateListener() { + }; + private PhoneStateListener phoneStateListener = + new PhoneStateListener() { @Override public void onCallStateChanged(int state, String incomingNumber) { - switch (state) { - case TelephonyManager.CALL_STATE_IDLE: - //Not in call: Play music - play(); - break; - case TelephonyManager.CALL_STATE_RINGING: - case TelephonyManager.CALL_STATE_OFFHOOK: - //A call is dialing, active or on hold - pause(); - break; - default: - } - super.onCallStateChanged(state, incomingNumber); + switch (state) { + case TelephonyManager.CALL_STATE_IDLE: + // Not in call: Play music + play(); + break; + case TelephonyManager.CALL_STATE_RINGING: + case TelephonyManager.CALL_STATE_OFFHOOK: + // A call is dialing, active or on hold + pause(); + break; + default: + } + super.onCallStateChanged(state, incomingNumber); } - }; - private BroadcastReceiver headsetReceiver = new BroadcastReceiver() { + }; + private BroadcastReceiver headsetReceiver = + new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (action != null) { - if (Intent.ACTION_HEADSET_PLUG.equals(action)) { - int state = intent.getIntExtra("state", -1); - switch (state) { - case 0: - pause(); - break; - case 1: - play(); - break; - } - } + String action = intent.getAction(); + if (action != null) { + if (Intent.ACTION_HEADSET_PLUG.equals(action)) { + int state = intent.getIntExtra("state", -1); + switch (state) { + case 0: + pause(); + break; + case 1: + play(); + break; + } } + } } - }; - private ThrottledSeekHandler throttledSeekHandler; - private Handler uiThreadHandler; - private PowerManager.WakeLock wakeLock; + }; + private ThrottledSeekHandler throttledSeekHandler; + private Handler uiThreadHandler; + private PowerManager.WakeLock wakeLock; - private static Bitmap copy(Bitmap bitmap) { - Bitmap.Config config = bitmap.getConfig(); - if (config == null) { - config = Bitmap.Config.RGB_565; + private static Bitmap copy(Bitmap bitmap) { + Bitmap.Config config = bitmap.getConfig(); + if (config == null) { + config = Bitmap.Config.RGB_565; + } + try { + return bitmap.copy(config, false); + } catch (OutOfMemoryError e) { + e.printStackTrace(); + return null; + } + } + + private static String getTrackUri(@NonNull Song song) { + return MusicUtil.INSTANCE.getSongFileUri(song.getId()).toString(); + } + + @Override + public void onCreate() { + super.onCreate(); + final TelephonyManager telephonyManager = + (TelephonyManager) getSystemService(TELEPHONY_SERVICE); + if (telephonyManager != null) { + telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE); + } + + final PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); + if (powerManager != null) { + wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName()); + } + wakeLock.setReferenceCounted(false); + + musicPlayerHandlerThread = new HandlerThread("PlaybackHandler"); + musicPlayerHandlerThread.start(); + playerHandler = new PlaybackHandler(this, musicPlayerHandlerThread.getLooper()); + + playback = new MultiPlayer(this); + playback.setCallbacks(this); + + setupMediaSession(); + + // queue saving needs to run on a separate thread so that it doesn't block the playback handler + // events + queueSaveHandlerThread = + new HandlerThread("QueueSaveHandler", Process.THREAD_PRIORITY_BACKGROUND); + queueSaveHandlerThread.start(); + queueSaveHandler = new QueueSaveHandler(this, queueSaveHandlerThread.getLooper()); + + uiThreadHandler = new Handler(); + + registerReceiver(widgetIntentReceiver, new IntentFilter(APP_WIDGET_UPDATE)); + registerReceiver(updateFavoriteReceiver, new IntentFilter(FAVORITE_STATE_CHANGED)); + registerReceiver(lockScreenReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF)); + + initNotification(); + + mediaStoreObserver = new MediaStoreObserver(this, playerHandler); + throttledSeekHandler = new ThrottledSeekHandler(this, playerHandler); + getContentResolver() + .registerContentObserver( + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, true, mediaStoreObserver); + getContentResolver() + .registerContentObserver( + MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, true, mediaStoreObserver); + getContentResolver() + .registerContentObserver( + MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, true, mediaStoreObserver); + getContentResolver() + .registerContentObserver( + MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI, true, mediaStoreObserver); + getContentResolver() + .registerContentObserver( + MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, true, mediaStoreObserver); + + getContentResolver() + .registerContentObserver( + MediaStore.Audio.Media.INTERNAL_CONTENT_URI, true, mediaStoreObserver); + getContentResolver() + .registerContentObserver( + MediaStore.Audio.Albums.INTERNAL_CONTENT_URI, true, mediaStoreObserver); + getContentResolver() + .registerContentObserver( + MediaStore.Audio.Artists.INTERNAL_CONTENT_URI, true, mediaStoreObserver); + getContentResolver() + .registerContentObserver( + MediaStore.Audio.Genres.INTERNAL_CONTENT_URI, true, mediaStoreObserver); + getContentResolver() + .registerContentObserver( + MediaStore.Audio.Playlists.INTERNAL_CONTENT_URI, true, mediaStoreObserver); + + PreferenceUtil.INSTANCE.registerOnSharedPreferenceChangedListener(this); + + restoreState(); + + sendBroadcast(new Intent("code.name.monkey.retromusic.RETRO_MUSIC_SERVICE_CREATED")); + + registerHeadsetEvents(); + registerBluetoothConnected(); + } + + @Override + public void onDestroy() { + unregisterReceiver(widgetIntentReceiver); + unregisterReceiver(updateFavoriteReceiver); + unregisterReceiver(lockScreenReceiver); + if (becomingNoisyReceiverRegistered) { + unregisterReceiver(becomingNoisyReceiver); + becomingNoisyReceiverRegistered = false; + } + if (headsetReceiverRegistered) { + unregisterReceiver(headsetReceiver); + headsetReceiverRegistered = false; + } + if (bluetoothConnectedRegistered) { + unregisterReceiver(bluetoothReceiver); + bluetoothConnectedRegistered = false; + } + mediaSession.setActive(false); + quit(); + releaseResources(); + getContentResolver().unregisterContentObserver(mediaStoreObserver); + PreferenceUtil.INSTANCE.unregisterOnSharedPreferenceChangedListener(this); + wakeLock.release(); + + sendBroadcast(new Intent("code.name.monkey.retromusic.RETRO_MUSIC_SERVICE_DESTROYED")); + } + + public void acquireWakeLock(long milli) { + wakeLock.acquire(milli); + } + + public void addSong(int position, Song song) { + playingQueue.add(position, song); + originalPlayingQueue.add(position, song); + notifyChange(QUEUE_CHANGED); + } + + public void addSong(Song song) { + playingQueue.add(song); + originalPlayingQueue.add(song); + notifyChange(QUEUE_CHANGED); + } + + public void addSongs(int position, List songs) { + playingQueue.addAll(position, songs); + originalPlayingQueue.addAll(position, songs); + notifyChange(QUEUE_CHANGED); + } + + public void addSongs(List songs) { + playingQueue.addAll(songs); + originalPlayingQueue.addAll(songs); + notifyChange(QUEUE_CHANGED); + } + + public void back(boolean force) { + if (getSongProgressMillis() > 2000) { + seek(0); + } else { + playPreviousSong(force); + } + } + + public void clearQueue() { + playingQueue.clear(); + originalPlayingQueue.clear(); + + setPosition(-1); + notifyChange(QUEUE_CHANGED); + } + + public void cycleRepeatMode() { + switch (getRepeatMode()) { + case REPEAT_MODE_NONE: + setRepeatMode(REPEAT_MODE_ALL); + break; + case REPEAT_MODE_ALL: + setRepeatMode(REPEAT_MODE_THIS); + break; + default: + setRepeatMode(REPEAT_MODE_NONE); + break; + } + } + + public int getAudioSessionId() { + if (playback != null) { + return playback.getAudioSessionId(); + } + return -1; + } + + @NonNull + public Song getCurrentSong() { + return getSongAt(getPosition()); + } + + @NonNull + public MediaSessionCompat getMediaSession() { + return mediaSession; + } + + public int getNextPosition(boolean force) { + int position = getPosition() + 1; + switch (getRepeatMode()) { + case REPEAT_MODE_ALL: + if (isLastTrack()) { + position = 0; } - try { - return bitmap.copy(config, false); - } catch (OutOfMemoryError e) { - e.printStackTrace(); - return null; - } - } - - private static String getTrackUri(@NonNull Song song) { - return MusicUtil.INSTANCE.getSongFileUri(song.getId()).toString(); - } - - @Override - public void onCreate() { - super.onCreate(); - final TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE); - if (telephonyManager != null) { - telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE); - } - - final PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); - if (powerManager != null) { - wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName()); - } - wakeLock.setReferenceCounted(false); - - musicPlayerHandlerThread = new HandlerThread("PlaybackHandler"); - musicPlayerHandlerThread.start(); - playerHandler = new PlaybackHandler(this, musicPlayerHandlerThread.getLooper()); - - playback = new MultiPlayer(this); - playback.setCallbacks(this); - - setupMediaSession(); - - // queue saving needs to run on a separate thread so that it doesn't block the playback handler events - queueSaveHandlerThread = new HandlerThread("QueueSaveHandler", Process.THREAD_PRIORITY_BACKGROUND); - queueSaveHandlerThread.start(); - queueSaveHandler = new QueueSaveHandler(this, queueSaveHandlerThread.getLooper()); - - uiThreadHandler = new Handler(); - - registerReceiver(widgetIntentReceiver, new IntentFilter(APP_WIDGET_UPDATE)); - registerReceiver(updateFavoriteReceiver, new IntentFilter(FAVORITE_STATE_CHANGED)); - registerReceiver(lockScreenReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF)); - - initNotification(); - - mediaStoreObserver = new MediaStoreObserver(this, playerHandler); - throttledSeekHandler = new ThrottledSeekHandler(this, playerHandler); - getContentResolver() - .registerContentObserver(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, true, mediaStoreObserver); - getContentResolver() - .registerContentObserver(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, true, mediaStoreObserver); - getContentResolver() - .registerContentObserver(MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, true, mediaStoreObserver); - getContentResolver() - .registerContentObserver(MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI, true, mediaStoreObserver); - getContentResolver() - .registerContentObserver(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, true, mediaStoreObserver); - - getContentResolver() - .registerContentObserver(MediaStore.Audio.Media.INTERNAL_CONTENT_URI, true, mediaStoreObserver); - getContentResolver() - .registerContentObserver(MediaStore.Audio.Albums.INTERNAL_CONTENT_URI, true, mediaStoreObserver); - getContentResolver() - .registerContentObserver(MediaStore.Audio.Artists.INTERNAL_CONTENT_URI, true, mediaStoreObserver); - getContentResolver() - .registerContentObserver(MediaStore.Audio.Genres.INTERNAL_CONTENT_URI, true, mediaStoreObserver); - getContentResolver() - .registerContentObserver(MediaStore.Audio.Playlists.INTERNAL_CONTENT_URI, true, mediaStoreObserver); - - PreferenceUtil.INSTANCE.registerOnSharedPreferenceChangedListener(this); - - restoreState(); - - sendBroadcast(new Intent("code.name.monkey.retromusic.RETRO_MUSIC_SERVICE_CREATED")); - - registerHeadsetEvents(); - registerBluetoothConnected(); - } - - @Override - public void onDestroy() { - unregisterReceiver(widgetIntentReceiver); - unregisterReceiver(updateFavoriteReceiver); - unregisterReceiver(lockScreenReceiver); - if (becomingNoisyReceiverRegistered) { - unregisterReceiver(becomingNoisyReceiver); - becomingNoisyReceiverRegistered = false; - } - if (headsetReceiverRegistered) { - unregisterReceiver(headsetReceiver); - headsetReceiverRegistered = false; - } - if (bluetoothConnectedRegistered) { - unregisterReceiver(bluetoothReceiver); - bluetoothConnectedRegistered = false; - } - mediaSession.setActive(false); - quit(); - releaseResources(); - getContentResolver().unregisterContentObserver(mediaStoreObserver); - PreferenceUtil.INSTANCE.unregisterOnSharedPreferenceChangedListener(this); - wakeLock.release(); - - sendBroadcast(new Intent("code.name.monkey.retromusic.RETRO_MUSIC_SERVICE_DESTROYED")); - } - - public void acquireWakeLock(long milli) { - wakeLock.acquire(milli); - } - - public void addSong(int position, Song song) { - playingQueue.add(position, song); - originalPlayingQueue.add(position, song); - notifyChange(QUEUE_CHANGED); - } - - public void addSong(Song song) { - playingQueue.add(song); - originalPlayingQueue.add(song); - notifyChange(QUEUE_CHANGED); - } - - public void addSongs(int position, List songs) { - playingQueue.addAll(position, songs); - originalPlayingQueue.addAll(position, songs); - notifyChange(QUEUE_CHANGED); - } - - public void addSongs(List songs) { - playingQueue.addAll(songs); - originalPlayingQueue.addAll(songs); - notifyChange(QUEUE_CHANGED); - } - - public void back(boolean force) { - if (getSongProgressMillis() > 2000) { - seek(0); + break; + case REPEAT_MODE_THIS: + if (force) { + if (isLastTrack()) { + position = 0; + } } else { - playPreviousSong(force); + position -= 1; } - } - - public void clearQueue() { - playingQueue.clear(); - originalPlayingQueue.clear(); - - setPosition(-1); - notifyChange(QUEUE_CHANGED); - } - - public void cycleRepeatMode() { - switch (getRepeatMode()) { - case REPEAT_MODE_NONE: - setRepeatMode(REPEAT_MODE_ALL); - break; - case REPEAT_MODE_ALL: - setRepeatMode(REPEAT_MODE_THIS); - break; - default: - setRepeatMode(REPEAT_MODE_NONE); - break; + break; + default: + case REPEAT_MODE_NONE: + if (isLastTrack()) { + position -= 1; } + break; } + return position; + } - public int getAudioSessionId() { - if (playback != null) { - return playback.getAudioSessionId(); + @Nullable + public List getPlayingQueue() { + return playingQueue; + } + + public int getPosition() { + return position; + } + + public void setPosition(final int position) { + // handle this on the handlers thread to avoid blocking the ui thread + playerHandler.removeMessages(SET_POSITION); + playerHandler.obtainMessage(SET_POSITION, position, 0).sendToTarget(); + } + + public int getPreviousPosition(boolean force) { + int newPosition = getPosition() - 1; + switch (repeatMode) { + case REPEAT_MODE_ALL: + if (newPosition < 0) { + if (getPlayingQueue() != null) { + newPosition = getPlayingQueue().size() - 1; + } } - return -1; - } - - @NonNull - public Song getCurrentSong() { - return getSongAt(getPosition()); - } - - @NonNull - public MediaSessionCompat getMediaSession() { - return mediaSession; - } - - public int getNextPosition(boolean force) { - int position = getPosition() + 1; - switch (getRepeatMode()) { - case REPEAT_MODE_ALL: - if (isLastTrack()) { - position = 0; - } - break; - case REPEAT_MODE_THIS: - if (force) { - if (isLastTrack()) { - position = 0; - } - } else { - position -= 1; - } - break; - default: - case REPEAT_MODE_NONE: - if (isLastTrack()) { - position -= 1; - } - break; - } - return position; - } - - @Nullable - public List getPlayingQueue() { - return playingQueue; - } - - public int getPosition() { - return position; - } - - public void setPosition(final int position) { - // handle this on the handlers thread to avoid blocking the ui thread - playerHandler.removeMessages(SET_POSITION); - playerHandler.obtainMessage(SET_POSITION, position, 0).sendToTarget(); - } - - public int getPreviousPosition(boolean force) { - int newPosition = getPosition() - 1; - switch (repeatMode) { - case REPEAT_MODE_ALL: - if (newPosition < 0) { - if (getPlayingQueue() != null) { - newPosition = getPlayingQueue().size() - 1; - } - } - break; - case REPEAT_MODE_THIS: - if (force) { - if (newPosition < 0) { - if (getPlayingQueue() != null) { - newPosition = getPlayingQueue().size() - 1; - } - } - } else { - newPosition = getPosition(); - } - break; - default: - case REPEAT_MODE_NONE: - if (newPosition < 0) { - newPosition = 0; - } - break; - } - return newPosition; - } - - public long getQueueDurationMillis(int position) { - long duration = 0; - for (int i = position + 1; i < playingQueue.size(); i++) { - duration += playingQueue.get(i).getDuration(); - } - return duration; - } - - public int getRepeatMode() { - return repeatMode; - } - - public void setRepeatMode(final int repeatMode) { - switch (repeatMode) { - case REPEAT_MODE_NONE: - case REPEAT_MODE_ALL: - case REPEAT_MODE_THIS: - this.repeatMode = repeatMode; - PreferenceManager.getDefaultSharedPreferences(this).edit() - .putInt(SAVED_REPEAT_MODE, repeatMode) - .apply(); - prepareNext(); - handleAndSendChangeInternal(REPEAT_MODE_CHANGED); - break; - } - } - - public int getShuffleMode() { - return shuffleMode; - } - - public void setShuffleMode(final int shuffleMode) { - PreferenceManager.getDefaultSharedPreferences(this).edit() - .putInt(SAVED_SHUFFLE_MODE, shuffleMode) - .apply(); - switch (shuffleMode) { - case SHUFFLE_MODE_SHUFFLE: - this.shuffleMode = shuffleMode; - if (this.getPlayingQueue() != null) { - ShuffleHelper.INSTANCE.makeShuffleList(this.getPlayingQueue(), getPosition()); - } - position = 0; - break; - case SHUFFLE_MODE_NONE: - this.shuffleMode = shuffleMode; - long currentSongId = Objects.requireNonNull(getCurrentSong()).getId(); - playingQueue = new ArrayList<>(originalPlayingQueue); - int newPosition = 0; - if (getPlayingQueue() != null) { - for (Song song : getPlayingQueue()) { - if (song.getId() == currentSongId) { - newPosition = getPlayingQueue().indexOf(song); - } - } - } - position = newPosition; - break; - } - handleAndSendChangeInternal(SHUFFLE_MODE_CHANGED); - notifyChange(QUEUE_CHANGED); - } - - @NonNull - public Song getSongAt(int position) { - if (position >= 0 && getPlayingQueue() != null && position < getPlayingQueue().size()) { - return getPlayingQueue().get(position); + break; + case REPEAT_MODE_THIS: + if (force) { + if (newPosition < 0) { + if (getPlayingQueue() != null) { + newPosition = getPlayingQueue().size() - 1; + } + } } else { - return Song.Companion.getEmptySong(); + newPosition = getPosition(); } - } - - public int getSongDurationMillis() { - if (playback != null) { - return playback.duration(); + break; + default: + case REPEAT_MODE_NONE: + if (newPosition < 0) { + newPosition = 0; } - return -1; + break; } + return newPosition; + } - public int getSongProgressMillis() { - if (playback != null) { - return playback.position(); + public long getQueueDurationMillis(int position) { + long duration = 0; + for (int i = position + 1; i < playingQueue.size(); i++) { + duration += playingQueue.get(i).getDuration(); + } + return duration; + } + + public int getRepeatMode() { + return repeatMode; + } + + public void setRepeatMode(final int repeatMode) { + switch (repeatMode) { + case REPEAT_MODE_NONE: + case REPEAT_MODE_ALL: + case REPEAT_MODE_THIS: + this.repeatMode = repeatMode; + PreferenceManager.getDefaultSharedPreferences(this) + .edit() + .putInt(SAVED_REPEAT_MODE, repeatMode) + .apply(); + prepareNext(); + handleAndSendChangeInternal(REPEAT_MODE_CHANGED); + break; + } + } + + public int getShuffleMode() { + return shuffleMode; + } + + public void setShuffleMode(final int shuffleMode) { + PreferenceManager.getDefaultSharedPreferences(this) + .edit() + .putInt(SAVED_SHUFFLE_MODE, shuffleMode) + .apply(); + switch (shuffleMode) { + case SHUFFLE_MODE_SHUFFLE: + this.shuffleMode = shuffleMode; + if (this.getPlayingQueue() != null) { + ShuffleHelper.INSTANCE.makeShuffleList(this.getPlayingQueue(), getPosition()); } - return -1; - } - - public void handleAndSendChangeInternal(@NonNull final String what) { - handleChangeInternal(what); - sendChangeInternal(what); - } - - public void initNotification() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && - !PreferenceUtil.INSTANCE.isClassicNotification()) { - playingNotification = new PlayingNotificationImpl(); - } else { - playingNotification = new PlayingNotificationOreo(); - } - playingNotification.init(this); - } - - public boolean isLastTrack() { + position = 0; + break; + case SHUFFLE_MODE_NONE: + this.shuffleMode = shuffleMode; + long currentSongId = Objects.requireNonNull(getCurrentSong()).getId(); + playingQueue = new ArrayList<>(originalPlayingQueue); + int newPosition = 0; if (getPlayingQueue() != null) { - return getPosition() == getPlayingQueue().size() - 1; - } - return false; - } - - public boolean isPausedByTransientLossOfFocus() { - return pausedByTransientLossOfFocus; - } - - public void setPausedByTransientLossOfFocus(boolean pausedByTransientLossOfFocus) { - this.pausedByTransientLossOfFocus = pausedByTransientLossOfFocus; - } - - public boolean isPlaying() { - return playback != null && playback.isPlaying(); - } - - public void moveSong(int from, int to) { - if (from == to) { - return; - } - final int currentPosition = getPosition(); - Song songToMove = playingQueue.remove(from); - playingQueue.add(to, songToMove); - if (getShuffleMode() == SHUFFLE_MODE_NONE) { - Song tmpSong = originalPlayingQueue.remove(from); - originalPlayingQueue.add(to, tmpSong); - } - if (from > currentPosition && to <= currentPosition) { - position = currentPosition + 1; - } else if (from < currentPosition && to >= currentPosition) { - position = currentPosition - 1; - } else if (from == currentPosition) { - position = to; - } - notifyChange(QUEUE_CHANGED); - } - - public void notifyChange(@NonNull final String what) { - handleAndSendChangeInternal(what); - sendPublicIntent(what); - } - - @NonNull - @Override - public IBinder onBind(Intent intent) { - return musicBind; - } - - @Override - public void onSharedPreferenceChanged(@NonNull SharedPreferences sharedPreferences, @NonNull String key) { - switch (key) { - case GAP_LESS_PLAYBACK: - if (sharedPreferences.getBoolean(key, false)) { - prepareNext(); - } else { - if (playback != null) { - playback.setNextDataSource(null); - } - } - break; - case ALBUM_ART_ON_LOCK_SCREEN: - case BLURRED_ALBUM_ART: - updateMediaSessionMetaData(); - break; - case COLORED_NOTIFICATION: - updateNotification(); - break; - case CLASSIC_NOTIFICATION: - initNotification(); - updateNotification(); - break; - case TOGGLE_HEADSET: - registerHeadsetEvents(); - break; - } - } - - @Override - public int onStartCommand(@Nullable Intent intent, int flags, int startId) { - if (intent != null && intent.getAction() != null) { - restoreQueuesAndPositionIfNecessary(); - String action = intent.getAction(); - switch (action) { - case ACTION_TOGGLE_PAUSE: - if (isPlaying()) { - pause(); - } else { - play(); - } - break; - case ACTION_PAUSE: - pause(); - break; - case ACTION_PLAY: - play(); - break; - case ACTION_PLAY_PLAYLIST: - playFromPlaylist(intent); - break; - case ACTION_REWIND: - back(true); - break; - case ACTION_SKIP: - playNextSong(true); - break; - case ACTION_STOP: - case ACTION_QUIT: - pendingQuit = false; - quit(); - break; - case ACTION_PENDING_QUIT: - pendingQuit = true; - break; - case TOGGLE_FAVORITE: - MusicUtil.INSTANCE.toggleFavorite(getApplicationContext(), getCurrentSong()); - break; + for (Song song : getPlayingQueue()) { + if (song.getId() == currentSongId) { + newPosition = getPlayingQueue().indexOf(song); } + } } - - return START_NOT_STICKY; + position = newPosition; + break; } + handleAndSendChangeInternal(SHUFFLE_MODE_CHANGED); + notifyChange(QUEUE_CHANGED); + } - @Override - public void onTrackEnded() { - acquireWakeLock(30000); - playerHandler.sendEmptyMessage(TRACK_ENDED); + @NonNull + public Song getSongAt(int position) { + if (position >= 0 && getPlayingQueue() != null && position < getPlayingQueue().size()) { + return getPlayingQueue().get(position); + } else { + return Song.Companion.getEmptySong(); } + } - @Override - public void onTrackWentToNext() { - playerHandler.sendEmptyMessage(TRACK_WENT_TO_NEXT); + public int getSongDurationMillis() { + if (playback != null) { + return playback.duration(); } + return -1; + } - @Override - public boolean onUnbind(Intent intent) { - if (!isPlaying()) { - stopSelf(); + public int getSongProgressMillis() { + if (playback != null) { + return playback.position(); + } + return -1; + } + + public void handleAndSendChangeInternal(@NonNull final String what) { + handleChangeInternal(what); + sendChangeInternal(what); + } + + public void initNotification() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N + && !PreferenceUtil.INSTANCE.isClassicNotification()) { + playingNotification = new PlayingNotificationImpl(); + } else { + playingNotification = new PlayingNotificationOreo(); + } + playingNotification.init(this); + } + + public boolean isLastTrack() { + if (getPlayingQueue() != null) { + return getPosition() == getPlayingQueue().size() - 1; + } + return false; + } + + public boolean isPausedByTransientLossOfFocus() { + return pausedByTransientLossOfFocus; + } + + public void setPausedByTransientLossOfFocus(boolean pausedByTransientLossOfFocus) { + this.pausedByTransientLossOfFocus = pausedByTransientLossOfFocus; + } + + public boolean isPlaying() { + return playback != null && playback.isPlaying(); + } + + public void moveSong(int from, int to) { + if (from == to) { + return; + } + final int currentPosition = getPosition(); + Song songToMove = playingQueue.remove(from); + playingQueue.add(to, songToMove); + if (getShuffleMode() == SHUFFLE_MODE_NONE) { + Song tmpSong = originalPlayingQueue.remove(from); + originalPlayingQueue.add(to, tmpSong); + } + if (from > currentPosition && to <= currentPosition) { + position = currentPosition + 1; + } else if (from < currentPosition && to >= currentPosition) { + position = currentPosition - 1; + } else if (from == currentPosition) { + position = to; + } + notifyChange(QUEUE_CHANGED); + } + + public void notifyChange(@NonNull final String what) { + handleAndSendChangeInternal(what); + sendPublicIntent(what); + } + + @NonNull + @Override + public IBinder onBind(Intent intent) { + return musicBind; + } + + @Override + public void onSharedPreferenceChanged( + @NonNull SharedPreferences sharedPreferences, @NonNull String key) { + switch (key) { + case GAP_LESS_PLAYBACK: + if (sharedPreferences.getBoolean(key, false)) { + prepareNext(); + } else { + if (playback != null) { + playback.setNextDataSource(null); + } } - return true; + break; + case ALBUM_ART_ON_LOCK_SCREEN: + case BLURRED_ALBUM_ART: + updateMediaSessionMetaData(); + break; + case COLORED_NOTIFICATION: + updateNotification(); + break; + case CLASSIC_NOTIFICATION: + initNotification(); + updateNotification(); + break; + case TOGGLE_HEADSET: + registerHeadsetEvents(); + break; + } + } + + @Override + public int onStartCommand(@Nullable Intent intent, int flags, int startId) { + if (intent != null && intent.getAction() != null) { + restoreQueuesAndPositionIfNecessary(); + String action = intent.getAction(); + switch (action) { + case ACTION_TOGGLE_PAUSE: + if (isPlaying()) { + pause(); + } else { + play(); + } + break; + case ACTION_PAUSE: + pause(); + break; + case ACTION_PLAY: + play(); + break; + case ACTION_PLAY_PLAYLIST: + playFromPlaylist(intent); + break; + case ACTION_REWIND: + back(true); + break; + case ACTION_SKIP: + playNextSong(true); + break; + case ACTION_STOP: + case ACTION_QUIT: + pendingQuit = false; + quit(); + break; + case ACTION_PENDING_QUIT: + pendingQuit = true; + break; + case TOGGLE_FAVORITE: + MusicUtil.INSTANCE.toggleFavorite(getApplicationContext(), getCurrentSong()); + break; + } } - public void openQueue(@Nullable final List playingQueue, final int startPosition, - final boolean startPlaying) { - if (playingQueue != null && !playingQueue.isEmpty() && startPosition >= 0 && startPosition < playingQueue - .size()) { - // it is important to copy the playing queue here first as we might add/remove songs later - originalPlayingQueue = new ArrayList<>(playingQueue); - this.playingQueue = new ArrayList<>(originalPlayingQueue); + return START_NOT_STICKY; + } - int position = startPosition; - if (shuffleMode == SHUFFLE_MODE_SHUFFLE) { - ShuffleHelper.INSTANCE.makeShuffleList(this.playingQueue, startPosition); - position = 0; + @Override + public void onTrackEnded() { + acquireWakeLock(30000); + playerHandler.sendEmptyMessage(TRACK_ENDED); + } + + @Override + public void onTrackWentToNext() { + playerHandler.sendEmptyMessage(TRACK_WENT_TO_NEXT); + } + + @Override + public boolean onUnbind(Intent intent) { + if (!isPlaying()) { + stopSelf(); + } + return true; + } + + public void openQueue( + @Nullable final List playingQueue, + final int startPosition, + final boolean startPlaying) { + if (playingQueue != null + && !playingQueue.isEmpty() + && startPosition >= 0 + && startPosition < playingQueue.size()) { + // it is important to copy the playing queue here first as we might add/remove songs later + originalPlayingQueue = new ArrayList<>(playingQueue); + this.playingQueue = new ArrayList<>(originalPlayingQueue); + + int position = startPosition; + if (shuffleMode == SHUFFLE_MODE_SHUFFLE) { + ShuffleHelper.INSTANCE.makeShuffleList(this.playingQueue, startPosition); + position = 0; + } + if (startPlaying) { + playSongAt(position); + } else { + setPosition(position); + } + notifyChange(QUEUE_CHANGED); + } + } + + public boolean openTrackAndPrepareNextAt(int position) { + synchronized (this) { + this.position = position; + boolean prepared = openCurrent(); + if (prepared) { + prepareNextImpl(); + } + notifyChange(META_CHANGED); + notHandledMetaChangedForCurrentTrack = false; + return prepared; + } + } + + public void pause() { + pausedByTransientLossOfFocus = false; + if (playback != null && playback.isPlaying()) { + playback.pause(); + notifyChange(PLAY_STATE_CHANGED); + } + } + + public void play() { + synchronized (this) { + if (requestFocus()) { + if (playback != null && !playback.isPlaying()) { + if (!playback.isInitialized()) { + playSongAt(getPosition()); + } else { + playback.start(); + if (!becomingNoisyReceiverRegistered) { + registerReceiver(becomingNoisyReceiver, becomingNoisyReceiverIntentFilter); + becomingNoisyReceiverRegistered = true; } - if (startPlaying) { - playSongAt(position); - } else { - setPosition(position); + if (notHandledMetaChangedForCurrentTrack) { + handleChangeInternal(META_CHANGED); + notHandledMetaChangedForCurrentTrack = false; } - notifyChange(QUEUE_CHANGED); - } - } - - public boolean openTrackAndPrepareNextAt(int position) { - synchronized (this) { - this.position = position; - boolean prepared = openCurrent(); - if (prepared) { - prepareNextImpl(); - } - notifyChange(META_CHANGED); - notHandledMetaChangedForCurrentTrack = false; - return prepared; - } - } - - public void pause() { - pausedByTransientLossOfFocus = false; - if (playback != null && playback.isPlaying()) { - playback.pause(); notifyChange(PLAY_STATE_CHANGED); + + // fixes a bug where the volume would stay ducked because the + // AudioManager.AUDIOFOCUS_GAIN event is not sent + playerHandler.removeMessages(DUCK); + playerHandler.sendEmptyMessage(UNDUCK); + } } + } else { + Toast.makeText( + this, getResources().getString(R.string.audio_focus_denied), Toast.LENGTH_SHORT) + .show(); + } + } + } + + public void playNextSong(boolean force) { + playSongAt(getNextPosition(force)); + } + + public void playPreviousSong(boolean force) { + playSongAt(getPreviousPosition(force)); + } + + public void playSongAt(final int position) { + // handle this on the handlers thread to avoid blocking the ui thread + playerHandler.removeMessages(PLAY_SONG); + playerHandler.obtainMessage(PLAY_SONG, position, 0).sendToTarget(); + } + + public void playSongAtImpl(int position) { + if (openTrackAndPrepareNextAt(position)) { + play(); + } else { + Toast.makeText(this, getResources().getString(R.string.unplayable_file), Toast.LENGTH_SHORT) + .show(); + } + } + + public void playSongs(ArrayList songs, int shuffleMode) { + if (songs != null && !songs.isEmpty()) { + if (shuffleMode == SHUFFLE_MODE_SHUFFLE) { + int startPosition = new Random().nextInt(songs.size()); + openQueue(songs, startPosition, false); + setShuffleMode(shuffleMode); + } else { + openQueue(songs, 0, false); + } + play(); + } else { + Toast.makeText(getApplicationContext(), R.string.playlist_is_empty, Toast.LENGTH_LONG).show(); + } + } + + public boolean prepareNextImpl() { + synchronized (this) { + try { + int nextPosition = getNextPosition(false); + if (playback != null) { + playback.setNextDataSource(getTrackUri(Objects.requireNonNull(getSongAt(nextPosition)))); + } + this.nextPosition = nextPosition; + return true; + } catch (Exception e) { + return false; + } + } + } + + public void quit() { + pause(); + playingNotification.stop(); + + closeAudioEffectSession(); + getAudioManager().abandonAudioFocus(audioFocusListener); + stopSelf(); + } + + public void releaseWakeLock() { + if (wakeLock.isHeld()) { + wakeLock.release(); + } + } + + public void removeSong(int position) { + if (getShuffleMode() == SHUFFLE_MODE_NONE) { + playingQueue.remove(position); + originalPlayingQueue.remove(position); + } else { + originalPlayingQueue.remove(playingQueue.remove(position)); } - public void play() { - synchronized (this) { - if (requestFocus()) { - if (playback != null && !playback.isPlaying()) { - if (!playback.isInitialized()) { - playSongAt(getPosition()); - } else { - playback.start(); - if (!becomingNoisyReceiverRegistered) { - registerReceiver(becomingNoisyReceiver, becomingNoisyReceiverIntentFilter); - becomingNoisyReceiverRegistered = true; - } - if (notHandledMetaChangedForCurrentTrack) { - handleChangeInternal(META_CHANGED); - notHandledMetaChangedForCurrentTrack = false; - } - notifyChange(PLAY_STATE_CHANGED); + rePosition(position); - // fixes a bug where the volume would stay ducked because the AudioManager.AUDIOFOCUS_GAIN event is not sent - playerHandler.removeMessages(DUCK); - playerHandler.sendEmptyMessage(UNDUCK); + notifyChange(QUEUE_CHANGED); + } + + public void removeSong(@NonNull Song song) { + for (int i = 0; i < playingQueue.size(); i++) { + if (playingQueue.get(i).getId() == song.getId()) { + playingQueue.remove(i); + rePosition(i); + } + } + for (int i = 0; i < originalPlayingQueue.size(); i++) { + if (originalPlayingQueue.get(i).getId() == song.getId()) { + originalPlayingQueue.remove(i); + } + } + notifyChange(QUEUE_CHANGED); + } + + public synchronized void restoreQueuesAndPositionIfNecessary() { + if (!queuesRestored && playingQueue.isEmpty()) { + List restoredQueue = MusicPlaybackQueueStore.getInstance(this).getSavedPlayingQueue(); + List restoredOriginalQueue = + MusicPlaybackQueueStore.getInstance(this).getSavedOriginalPlayingQueue(); + int restoredPosition = + PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_POSITION, -1); + int restoredPositionInTrack = + PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_POSITION_IN_TRACK, -1); + + if (restoredQueue.size() > 0 + && restoredQueue.size() == restoredOriginalQueue.size() + && restoredPosition != -1) { + this.originalPlayingQueue = restoredOriginalQueue; + this.playingQueue = restoredQueue; + + position = restoredPosition; + openCurrent(); + prepareNext(); + + if (restoredPositionInTrack > 0) { + seek(restoredPositionInTrack); + } + + notHandledMetaChangedForCurrentTrack = true; + sendChangeInternal(META_CHANGED); + sendChangeInternal(QUEUE_CHANGED); + } + } + queuesRestored = true; + } + + public void runOnUiThread(Runnable runnable) { + uiThreadHandler.post(runnable); + } + + public void savePositionInTrack() { + PreferenceManager.getDefaultSharedPreferences(this) + .edit() + .putInt(SAVED_POSITION_IN_TRACK, getSongProgressMillis()) + .apply(); + } + + public void saveQueuesImpl() { + MusicPlaybackQueueStore.getInstance(this).saveQueues(playingQueue, originalPlayingQueue); + } + + public void saveState() { + saveQueues(); + savePosition(); + savePositionInTrack(); + } + + public int seek(int millis) { + synchronized (this) { + try { + int newPosition = 0; + if (playback != null) { + newPosition = playback.seek(millis); + } + throttledSeekHandler.notifySeek(); + return newPosition; + } catch (Exception e) { + return -1; + } + } + } + + // to let other apps know whats playing. i.E. last.fm (scrobbling) or musixmatch + public void sendPublicIntent(@NonNull final String what) { + final Intent intent = new Intent(what.replace(RETRO_MUSIC_PACKAGE_NAME, MUSIC_PACKAGE_NAME)); + + final Song song = getCurrentSong(); + + if (song != null) { + intent.putExtra("id", song.getId()); + intent.putExtra("artist", song.getArtistName()); + intent.putExtra("album", song.getAlbumName()); + intent.putExtra("track", song.getTitle()); + intent.putExtra("duration", song.getDuration()); + intent.putExtra("position", (long) getSongProgressMillis()); + intent.putExtra("playing", isPlaying()); + intent.putExtra("scrobbling_source", RETRO_MUSIC_PACKAGE_NAME); + sendStickyBroadcast(intent); + } + } + + public void toggleShuffle() { + if (getShuffleMode() == SHUFFLE_MODE_NONE) { + setShuffleMode(SHUFFLE_MODE_SHUFFLE); + } else { + setShuffleMode(SHUFFLE_MODE_NONE); + } + } + + public void updateMediaSessionPlaybackState() { + PlaybackStateCompat.Builder stateBuilder = + new PlaybackStateCompat.Builder() + .setActions(MEDIA_SESSION_ACTIONS) + .setState( + isPlaying() ? PlaybackStateCompat.STATE_PLAYING : PlaybackStateCompat.STATE_PAUSED, + getSongProgressMillis(), + 1); + + setCustomAction(stateBuilder); + + mediaSession.setPlaybackState(stateBuilder.build()); + } + + public void updateNotification() { + if (playingNotification != null && getCurrentSong().getId() != -1) { + playingNotification.update(); + } + } + + void updateMediaSessionMetaData() { + final Song song = getCurrentSong(); + + if (song.getId() == -1) { + mediaSession.setMetadata(null); + return; + } + + final MediaMetadataCompat.Builder metaData = + new MediaMetadataCompat.Builder() + .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, song.getArtistName()) + .putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, song.getArtistName()) + .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, song.getAlbumName()) + .putString(MediaMetadataCompat.METADATA_KEY_TITLE, song.getTitle()) + .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, song.getDuration()) + .putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, getPosition() + 1) + .putLong(MediaMetadataCompat.METADATA_KEY_YEAR, song.getYear()) + .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, null) + .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, getPlayingQueue().size()); + + if (PreferenceUtil.INSTANCE.isAlbumArtOnLockScreen()) { + final Point screenSize = RetroUtil.getScreenSize(MusicService.this); + final BitmapRequestBuilder request = + SongGlideRequest.Builder.from(Glide.with(MusicService.this), song) + .checkIgnoreMediaStore(MusicService.this) + .asBitmap() + .build(); + if (PreferenceUtil.INSTANCE.isBlurredAlbumArt()) { + request.transform(new BlurTransformation.Builder(MusicService.this).build()); + } + runOnUiThread( + new Runnable() { + @Override + public void run() { + request.into( + new SimpleTarget(screenSize.x, screenSize.y) { + @Override + public void onLoadFailed(Exception e, Drawable errorDrawable) { + super.onLoadFailed(e, errorDrawable); + mediaSession.setMetadata(metaData.build()); } - } - } else { - Toast.makeText(this, getResources().getString(R.string.audio_focus_denied), Toast.LENGTH_SHORT) - .show(); + + @Override + public void onResourceReady( + Bitmap resource, GlideAnimation glideAnimation) { + metaData.putBitmap( + MediaMetadataCompat.METADATA_KEY_ALBUM_ART, copy(resource)); + mediaSession.setMetadata(metaData.build()); + } + }); } + }); + } else { + mediaSession.setMetadata(metaData.build()); + } + } + + private void closeAudioEffectSession() { + final Intent audioEffectsIntent = + new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION); + if (playback != null) { + audioEffectsIntent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, playback.getAudioSessionId()); + } + audioEffectsIntent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName()); + sendBroadcast(audioEffectsIntent); + } + + private AudioManager getAudioManager() { + if (audioManager == null) { + audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + } + return audioManager; + } + + private void handleChangeInternal(@NonNull final String what) { + switch (what) { + case PLAY_STATE_CHANGED: + updateNotification(); + updateMediaSessionPlaybackState(); + final boolean isPlaying = isPlaying(); + if (!isPlaying && getSongProgressMillis() > 0) { + savePositionInTrack(); } - } - - public void playNextSong(boolean force) { - playSongAt(getNextPosition(force)); - } - - public void playPreviousSong(boolean force) { - playSongAt(getPreviousPosition(force)); - } - - public void playSongAt(final int position) { - // handle this on the handlers thread to avoid blocking the ui thread - playerHandler.removeMessages(PLAY_SONG); - playerHandler.obtainMessage(PLAY_SONG, position, 0).sendToTarget(); - } - - public void playSongAtImpl(int position) { - if (openTrackAndPrepareNextAt(position)) { - play(); - } else { - Toast.makeText(this, getResources().getString(R.string.unplayable_file), Toast.LENGTH_SHORT).show(); - } - } - - public void playSongs(ArrayList songs, int shuffleMode) { - if (songs != null && !songs.isEmpty()) { - if (shuffleMode == SHUFFLE_MODE_SHUFFLE) { - int startPosition = new Random().nextInt(songs.size()); - openQueue(songs, startPosition, false); - setShuffleMode(shuffleMode); - } else { - openQueue(songs, 0, false); - } - play(); - } else { - Toast.makeText(getApplicationContext(), R.string.playlist_is_empty, Toast.LENGTH_LONG).show(); - } - } - - public boolean prepareNextImpl() { - synchronized (this) { - try { - int nextPosition = getNextPosition(false); - if (playback != null) { - playback.setNextDataSource(getTrackUri(Objects.requireNonNull(getSongAt(nextPosition)))); - } - this.nextPosition = nextPosition; - return true; - } catch (Exception e) { - return false; - } - } - } - - public void quit() { - pause(); - playingNotification.stop(); - - closeAudioEffectSession(); - getAudioManager().abandonAudioFocus(audioFocusListener); - stopSelf(); - } - - public void releaseWakeLock() { - if (wakeLock.isHeld()) { - wakeLock.release(); - } - } - - public void removeSong(int position) { - if (getShuffleMode() == SHUFFLE_MODE_NONE) { - playingQueue.remove(position); - originalPlayingQueue.remove(position); - } else { - originalPlayingQueue.remove(playingQueue.remove(position)); - } - - rePosition(position); - - notifyChange(QUEUE_CHANGED); - } - - public void removeSong(@NonNull Song song) { - for (int i = 0; i < playingQueue.size(); i++) { - if (playingQueue.get(i).getId() == song.getId()) { - playingQueue.remove(i); - rePosition(i); - } - } - for (int i = 0; i < originalPlayingQueue.size(); i++) { - if (originalPlayingQueue.get(i).getId() == song.getId()) { - originalPlayingQueue.remove(i); - } - } - notifyChange(QUEUE_CHANGED); - } - - public synchronized void restoreQueuesAndPositionIfNecessary() { - if (!queuesRestored && playingQueue.isEmpty()) { - List restoredQueue = MusicPlaybackQueueStore.getInstance(this).getSavedPlayingQueue(); - List restoredOriginalQueue = MusicPlaybackQueueStore.getInstance(this).getSavedOriginalPlayingQueue(); - int restoredPosition = PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_POSITION, -1); - int restoredPositionInTrack = PreferenceManager.getDefaultSharedPreferences(this) - .getInt(SAVED_POSITION_IN_TRACK, -1); - - if (restoredQueue.size() > 0 && restoredQueue.size() == restoredOriginalQueue.size() - && restoredPosition != -1) { - this.originalPlayingQueue = restoredOriginalQueue; - this.playingQueue = restoredQueue; - - position = restoredPosition; - openCurrent(); - prepareNext(); - - if (restoredPositionInTrack > 0) { - seek(restoredPositionInTrack); - } - - notHandledMetaChangedForCurrentTrack = true; - sendChangeInternal(META_CHANGED); - sendChangeInternal(QUEUE_CHANGED); - } - } - queuesRestored = true; - } - - public void runOnUiThread(Runnable runnable) { - uiThreadHandler.post(runnable); - } - - public void savePositionInTrack() { - PreferenceManager.getDefaultSharedPreferences(this).edit() - .putInt(SAVED_POSITION_IN_TRACK, getSongProgressMillis()).apply(); - } - - public void saveQueuesImpl() { - MusicPlaybackQueueStore.getInstance(this).saveQueues(playingQueue, originalPlayingQueue); - } - - public void saveState() { - saveQueues(); + songPlayCountHelper.notifyPlayStateChanged(isPlaying); + break; + case FAVORITE_STATE_CHANGED: + case META_CHANGED: + updateNotification(); + updateMediaSessionMetaData(); savePosition(); savePositionInTrack(); - } - - public int seek(int millis) { - synchronized (this) { - try { - int newPosition = 0; - if (playback != null) { - newPosition = playback.seek(millis); - } - throttledSeekHandler.notifySeek(); - return newPosition; - } catch (Exception e) { - return -1; - } + final Song currentSong = getCurrentSong(); + if (currentSong != null) { + HistoryStore.getInstance(this).addSongId(currentSong.getId()); } - } - - // to let other apps know whats playing. i.E. last.fm (scrobbling) or musixmatch - public void sendPublicIntent(@NonNull final String what) { - final Intent intent = new Intent(what.replace(RETRO_MUSIC_PACKAGE_NAME, MUSIC_PACKAGE_NAME)); - - final Song song = getCurrentSong(); - - if (song != null) { - intent.putExtra("id", song.getId()); - intent.putExtra("artist", song.getArtistName()); - intent.putExtra("album", song.getAlbumName()); - intent.putExtra("track", song.getTitle()); - intent.putExtra("duration", song.getDuration()); - intent.putExtra("position", (long) getSongProgressMillis()); - intent.putExtra("playing", isPlaying()); - intent.putExtra("scrobbling_source", RETRO_MUSIC_PACKAGE_NAME); - sendStickyBroadcast(intent); + if (songPlayCountHelper.shouldBumpPlayCount()) { + SongPlayCountStore.getInstance(this).bumpPlayCount(songPlayCountHelper.getSong().getId()); } - } - - public void toggleShuffle() { - if (getShuffleMode() == SHUFFLE_MODE_NONE) { - setShuffleMode(SHUFFLE_MODE_SHUFFLE); + if (currentSong != null) { + songPlayCountHelper.notifySongChanged(currentSong); + } + break; + case QUEUE_CHANGED: + updateMediaSessionMetaData(); // because playing queue size might have changed + saveState(); + if (playingQueue.size() > 0) { + prepareNext(); } else { - setShuffleMode(SHUFFLE_MODE_NONE); + playingNotification.stop(); } + break; } + } - public void updateMediaSessionPlaybackState() { - PlaybackStateCompat.Builder stateBuilder = new PlaybackStateCompat.Builder() - .setActions(MEDIA_SESSION_ACTIONS) - .setState(isPlaying() ? PlaybackStateCompat.STATE_PLAYING : PlaybackStateCompat.STATE_PAUSED, - getSongProgressMillis(), 1); - - setCustomAction(stateBuilder); - - mediaSession.setPlaybackState(stateBuilder.build()); - } - - public void updateNotification() { - if (playingNotification != null && getCurrentSong().getId() != -1) { - playingNotification.update(); - } - } - - void updateMediaSessionMetaData() { - final Song song = getCurrentSong(); - - if (song.getId() == -1) { - mediaSession.setMetadata(null); - return; - } - - final MediaMetadataCompat.Builder metaData = new MediaMetadataCompat.Builder() - .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, song.getArtistName()) - .putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, song.getArtistName()) - .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, song.getAlbumName()) - .putString(MediaMetadataCompat.METADATA_KEY_TITLE, song.getTitle()) - .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, song.getDuration()) - .putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, getPosition() + 1) - .putLong(MediaMetadataCompat.METADATA_KEY_YEAR, song.getYear()) - .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, null) - .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, getPlayingQueue().size()); - - if (PreferenceUtil.INSTANCE.isAlbumArtOnLockScreen()) { - final Point screenSize = RetroUtil.getScreenSize(MusicService.this); - final BitmapRequestBuilder request = SongGlideRequest.Builder - .from(Glide.with(MusicService.this), song) - .checkIgnoreMediaStore(MusicService.this) - .asBitmap().build(); - if (PreferenceUtil.INSTANCE.isBlurredAlbumArt()) { - request.transform(new BlurTransformation.Builder(MusicService.this).build()); - } - runOnUiThread(new Runnable() { - @Override - public void run() { - request.into(new SimpleTarget(screenSize.x, screenSize.y) { - @Override - public void onLoadFailed(Exception e, Drawable errorDrawable) { - super.onLoadFailed(e, errorDrawable); - mediaSession.setMetadata(metaData.build()); - } - - @Override - public void onResourceReady(Bitmap resource, GlideAnimation glideAnimation) { - metaData.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, copy(resource)); - mediaSession.setMetadata(metaData.build()); - } - }); - } - }); - } else { - mediaSession.setMetadata(metaData.build()); - } - } - - private void closeAudioEffectSession() { - final Intent audioEffectsIntent = new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION); + private boolean openCurrent() { + synchronized (this) { + try { if (playback != null) { - audioEffectsIntent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, playback.getAudioSessionId()); - } - audioEffectsIntent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName()); - sendBroadcast(audioEffectsIntent); - } - - private AudioManager getAudioManager() { - if (audioManager == null) { - audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); - } - return audioManager; - } - - private void handleChangeInternal(@NonNull final String what) { - switch (what) { - case PLAY_STATE_CHANGED: - updateNotification(); - updateMediaSessionPlaybackState(); - final boolean isPlaying = isPlaying(); - if (!isPlaying && getSongProgressMillis() > 0) { - savePositionInTrack(); - } - songPlayCountHelper.notifyPlayStateChanged(isPlaying); - break; - case FAVORITE_STATE_CHANGED: - case META_CHANGED: - updateNotification(); - updateMediaSessionMetaData(); - savePosition(); - savePositionInTrack(); - final Song currentSong = getCurrentSong(); - if (currentSong != null) { - HistoryStore.getInstance(this).addSongId(currentSong.getId()); - } - if (songPlayCountHelper.shouldBumpPlayCount()) { - SongPlayCountStore.getInstance(this).bumpPlayCount(songPlayCountHelper.getSong().getId()); - } - if (currentSong != null) { - songPlayCountHelper.notifySongChanged(currentSong); - } - break; - case QUEUE_CHANGED: - updateMediaSessionMetaData(); // because playing queue size might have changed - saveState(); - if (playingQueue.size() > 0) { - prepareNext(); - } else { - playingNotification.stop(); - } - break; - } - } - - private boolean openCurrent() { - synchronized (this) { - try { - if (playback != null) { - return playback.setDataSource(getTrackUri(Objects.requireNonNull(getCurrentSong()))); - } - } catch (Exception e) { - return false; - } + return playback.setDataSource(getTrackUri(Objects.requireNonNull(getCurrentSong()))); } + } catch (Exception e) { return false; + } } + return false; + } - private void playFromPlaylist(Intent intent) { - Playlist playlist = intent.getParcelableExtra(INTENT_EXTRA_PLAYLIST); - int shuffleMode = intent.getIntExtra(INTENT_EXTRA_SHUFFLE_MODE, getShuffleMode()); - if (playlist != null) { - List playlistSongs = playlist.getSongs(); - if (!playlistSongs.isEmpty()) { - if (shuffleMode == SHUFFLE_MODE_SHUFFLE) { - int startPosition = new Random().nextInt(playlistSongs.size()); - openQueue(playlistSongs, startPosition, true); - setShuffleMode(shuffleMode); - } else { - openQueue(playlistSongs, 0, true); - } - } else { - Toast.makeText(getApplicationContext(), R.string.playlist_is_empty, Toast.LENGTH_LONG).show(); - } + private void playFromPlaylist(Intent intent) { + Playlist playlist = intent.getParcelableExtra(INTENT_EXTRA_PLAYLIST); + int shuffleMode = intent.getIntExtra(INTENT_EXTRA_SHUFFLE_MODE, getShuffleMode()); + if (playlist != null) { + List playlistSongs = playlist.getSongs(); + if (!playlistSongs.isEmpty()) { + if (shuffleMode == SHUFFLE_MODE_SHUFFLE) { + int startPosition = new Random().nextInt(playlistSongs.size()); + openQueue(playlistSongs, startPosition, true); + setShuffleMode(shuffleMode); } else { - Toast.makeText(getApplicationContext(), R.string.playlist_is_empty, Toast.LENGTH_LONG).show(); + openQueue(playlistSongs, 0, true); } + } else { + Toast.makeText(getApplicationContext(), R.string.playlist_is_empty, Toast.LENGTH_LONG) + .show(); + } + } else { + Toast.makeText(getApplicationContext(), R.string.playlist_is_empty, Toast.LENGTH_LONG).show(); } + } - private void prepareNext() { - playerHandler.removeMessages(PREPARE_NEXT); - playerHandler.obtainMessage(PREPARE_NEXT).sendToTarget(); + private void prepareNext() { + playerHandler.removeMessages(PREPARE_NEXT); + playerHandler.obtainMessage(PREPARE_NEXT).sendToTarget(); + } + + private void rePosition(int deletedPosition) { + int currentPosition = getPosition(); + if (deletedPosition < currentPosition) { + position = currentPosition - 1; + } else if (deletedPosition == currentPosition) { + if (playingQueue.size() > deletedPosition) { + setPosition(position); + } else { + setPosition(position - 1); + } } + } - private void rePosition(int deletedPosition) { - int currentPosition = getPosition(); - if (deletedPosition < currentPosition) { - position = currentPosition - 1; - } else if (deletedPosition == currentPosition) { - if (playingQueue.size() > deletedPosition) { - setPosition(position); - } else { - setPosition(position - 1); - } - } + private void registerBluetoothConnected() { + Log.i(TAG, "registerBluetoothConnected: "); + if (!bluetoothConnectedRegistered) { + registerReceiver(bluetoothReceiver, bluetoothConnectedIntentFilter); + bluetoothConnectedRegistered = true; } + } - private void registerBluetoothConnected() { - Log.i(TAG, "registerBluetoothConnected: "); - if (!bluetoothConnectedRegistered) { - registerReceiver(bluetoothReceiver, bluetoothConnectedIntentFilter); - bluetoothConnectedRegistered = true; - } + private void registerHeadsetEvents() { + if (!headsetReceiverRegistered && PreferenceUtil.INSTANCE.isHeadsetPlugged()) { + registerReceiver(headsetReceiver, headsetReceiverIntentFilter); + headsetReceiverRegistered = true; } + } - private void registerHeadsetEvents() { - if (!headsetReceiverRegistered && PreferenceUtil.INSTANCE.isHeadsetPlugged()) { - registerReceiver(headsetReceiver, headsetReceiverIntentFilter); - headsetReceiverRegistered = true; - } + private void releaseResources() { + playerHandler.removeCallbacksAndMessages(null); + musicPlayerHandlerThread.quitSafely(); + queueSaveHandler.removeCallbacksAndMessages(null); + queueSaveHandlerThread.quitSafely(); + if (playback != null) { + playback.release(); } + playback = null; + mediaSession.release(); + } - private void releaseResources() { - playerHandler.removeCallbacksAndMessages(null); - musicPlayerHandlerThread.quitSafely(); - queueSaveHandler.removeCallbacksAndMessages(null); - queueSaveHandlerThread.quitSafely(); - if (playback != null) { - playback.release(); - } - playback = null; - mediaSession.release(); + private boolean requestFocus() { + return (getAudioManager() + .requestAudioFocus( + audioFocusListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN) + == AudioManager.AUDIOFOCUS_REQUEST_GRANTED); + } + + private void restoreState() { + shuffleMode = PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_SHUFFLE_MODE, 0); + repeatMode = PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_REPEAT_MODE, 0); + handleAndSendChangeInternal(SHUFFLE_MODE_CHANGED); + handleAndSendChangeInternal(REPEAT_MODE_CHANGED); + + playerHandler.removeMessages(RESTORE_QUEUES); + playerHandler.sendEmptyMessage(RESTORE_QUEUES); + } + + private void savePosition() { + PreferenceManager.getDefaultSharedPreferences(this) + .edit() + .putInt(SAVED_POSITION, getPosition()) + .apply(); + } + + private void saveQueues() { + queueSaveHandler.removeMessages(SAVE_QUEUES); + queueSaveHandler.sendEmptyMessage(SAVE_QUEUES); + } + + private void sendChangeInternal(final String what) { + sendBroadcast(new Intent(what)); + appWidgetBig.notifyChange(this, what); + appWidgetClassic.notifyChange(this, what); + appWidgetSmall.notifyChange(this, what); + appWidgetCard.notifyChange(this, what); + appWidgetText.notifyChange(this, what); + } + + private void setCustomAction(PlaybackStateCompat.Builder stateBuilder) { + int repeatIcon = R.drawable.ic_repeat; // REPEAT_MODE_NONE + if (getRepeatMode() == REPEAT_MODE_THIS) { + repeatIcon = R.drawable.ic_repeat_one; + } else if (getRepeatMode() == REPEAT_MODE_ALL) { + repeatIcon = R.drawable.ic_repeat_white_circle; } - - private boolean requestFocus() { - return (getAudioManager() - .requestAudioFocus(audioFocusListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN) - == AudioManager.AUDIOFOCUS_REQUEST_GRANTED); - } - - private void restoreState() { - shuffleMode = PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_SHUFFLE_MODE, 0); - repeatMode = PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_REPEAT_MODE, 0); - handleAndSendChangeInternal(SHUFFLE_MODE_CHANGED); - handleAndSendChangeInternal(REPEAT_MODE_CHANGED); - - playerHandler.removeMessages(RESTORE_QUEUES); - playerHandler.sendEmptyMessage(RESTORE_QUEUES); - } - - private void savePosition() { - PreferenceManager.getDefaultSharedPreferences(this).edit().putInt(SAVED_POSITION, getPosition()).apply(); - } - - private void saveQueues() { - queueSaveHandler.removeMessages(SAVE_QUEUES); - queueSaveHandler.sendEmptyMessage(SAVE_QUEUES); - } - - private void sendChangeInternal(final String what) { - sendBroadcast(new Intent(what)); - appWidgetBig.notifyChange(this, what); - appWidgetClassic.notifyChange(this, what); - appWidgetSmall.notifyChange(this, what); - appWidgetCard.notifyChange(this, what); - appWidgetText.notifyChange(this, what); - } - - private void setCustomAction(PlaybackStateCompat.Builder stateBuilder) { - int repeatIcon = R.drawable.ic_repeat; // REPEAT_MODE_NONE - if (getRepeatMode() == REPEAT_MODE_THIS) { - repeatIcon = R.drawable.ic_repeat_one; - } else if (getRepeatMode() == REPEAT_MODE_ALL) { - repeatIcon = R.drawable.ic_repeat_white_circle; - } - stateBuilder.addCustomAction(new PlaybackStateCompat.CustomAction.Builder( + stateBuilder.addCustomAction( + new PlaybackStateCompat.CustomAction.Builder( CYCLE_REPEAT, getString(R.string.action_cycle_repeat), repeatIcon) - .build()); + .build()); - final int shuffleIcon = getShuffleMode() == SHUFFLE_MODE_NONE ? R.drawable.ic_shuffle_off_circled - : R.drawable.ic_shuffle_on_circled; - stateBuilder.addCustomAction(new PlaybackStateCompat.CustomAction.Builder( + final int shuffleIcon = + getShuffleMode() == SHUFFLE_MODE_NONE + ? R.drawable.ic_shuffle_off_circled + : R.drawable.ic_shuffle_on_circled; + stateBuilder.addCustomAction( + new PlaybackStateCompat.CustomAction.Builder( TOGGLE_SHUFFLE, getString(R.string.action_toggle_shuffle), shuffleIcon) - .build()); + .build()); - final int favoriteIcon = MusicUtil.INSTANCE.isFavorite(getApplicationContext(), getCurrentSong()) - ? R.drawable.ic_favorite : R.drawable.ic_favorite_border; - stateBuilder.addCustomAction(new PlaybackStateCompat.CustomAction.Builder( + final int favoriteIcon = + MusicUtil.INSTANCE.isFavorite(getApplicationContext(), getCurrentSong()) + ? R.drawable.ic_favorite + : R.drawable.ic_favorite_border; + stateBuilder.addCustomAction( + new PlaybackStateCompat.CustomAction.Builder( TOGGLE_FAVORITE, getString(R.string.action_toggle_favorite), favoriteIcon) - .build()); + .build()); + } + + private void setupMediaSession() { + ComponentName mediaButtonReceiverComponentName = + new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class); + + Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); + mediaButtonIntent.setComponent(mediaButtonReceiverComponentName); + + PendingIntent mediaButtonReceiverPendingIntent = + PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0); + + mediaSession = + new MediaSessionCompat( + this, + "RetroMusicPlayer", + mediaButtonReceiverComponentName, + mediaButtonReceiverPendingIntent); + MediaSessionCallback mediasessionCallback = + new MediaSessionCallback(getApplicationContext(), this); + mediaSession.setFlags( + MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS + | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); + mediaSession.setCallback(mediasessionCallback); + mediaSession.setActive(true); + mediaSession.setMediaButtonReceiver(mediaButtonReceiverPendingIntent); + } + + public class MusicBinder extends Binder { + + @NonNull + public MusicService getService() { + return MusicService.this; } - - private void setupMediaSession() { - ComponentName mediaButtonReceiverComponentName = new ComponentName( - getApplicationContext(), - MediaButtonIntentReceiver.class); - - Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); - mediaButtonIntent.setComponent(mediaButtonReceiverComponentName); - - PendingIntent mediaButtonReceiverPendingIntent = PendingIntent.getBroadcast( - getApplicationContext(), - 0, - mediaButtonIntent, - 0); - - mediaSession = new MediaSessionCompat(this, - "RetroMusicPlayer", - mediaButtonReceiverComponentName, - mediaButtonReceiverPendingIntent); - MediaSessionCallback mediasessionCallback = new MediaSessionCallback( - getApplicationContext(), this); - mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS - | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS - ); - mediaSession.setCallback(mediasessionCallback); - mediaSession.setActive(true); - mediaSession.setMediaButtonReceiver(mediaButtonReceiverPendingIntent); - - } - - public class MusicBinder extends Binder { - - @NonNull - public MusicService getService() { - return MusicService.this; - } - } -} \ No newline at end of file + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/service/PlaybackHandler.java b/app/src/main/java/code/name/monkey/retromusic/service/PlaybackHandler.java index 39b1258c..5928a533 100644 --- a/app/src/main/java/code/name/monkey/retromusic/service/PlaybackHandler.java +++ b/app/src/main/java/code/name/monkey/retromusic/service/PlaybackHandler.java @@ -14,17 +14,6 @@ package code.name.monkey.retromusic.service; -import android.media.AudioManager; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; - -import androidx.annotation.NonNull; - -import java.lang.ref.WeakReference; - -import code.name.monkey.retromusic.util.PreferenceUtil; - import static code.name.monkey.retromusic.service.MusicService.DUCK; import static code.name.monkey.retromusic.service.MusicService.META_CHANGED; import static code.name.monkey.retromusic.service.MusicService.PLAY_STATE_CHANGED; @@ -32,140 +21,148 @@ import static code.name.monkey.retromusic.service.MusicService.REPEAT_MODE_NONE; import static code.name.monkey.retromusic.service.MusicService.TRACK_ENDED; import static code.name.monkey.retromusic.service.MusicService.TRACK_WENT_TO_NEXT; +import android.media.AudioManager; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import androidx.annotation.NonNull; +import code.name.monkey.retromusic.util.PreferenceUtil; +import java.lang.ref.WeakReference; + class PlaybackHandler extends Handler { - @NonNull - private final WeakReference mService; - private float currentDuckVolume = 1.0f; + @NonNull private final WeakReference mService; + private float currentDuckVolume = 1.0f; - PlaybackHandler(final MusicService service, @NonNull final Looper looper) { - super(looper); - mService = new WeakReference<>(service); + PlaybackHandler(final MusicService service, @NonNull final Looper looper) { + super(looper); + mService = new WeakReference<>(service); + } + + @Override + public void handleMessage(@NonNull final Message msg) { + final MusicService service = mService.get(); + if (service == null) { + return; } - @Override - public void handleMessage(@NonNull final Message msg) { - final MusicService service = mService.get(); - if (service == null) { - return; + switch (msg.what) { + case MusicService.DUCK: + if (PreferenceUtil.INSTANCE.isAudioDucking()) { + currentDuckVolume -= .05f; + if (currentDuckVolume > .2f) { + sendEmptyMessageDelayed(DUCK, 10); + } else { + currentDuckVolume = .2f; + } + } else { + currentDuckVolume = 1f; } + service.playback.setVolume(currentDuckVolume); + break; - switch (msg.what) { - case MusicService.DUCK: - if (PreferenceUtil.INSTANCE.isAudioDucking()) { - currentDuckVolume -= .05f; - if (currentDuckVolume > .2f) { - sendEmptyMessageDelayed(DUCK, 10); - } else { - currentDuckVolume = .2f; - } - } else { - currentDuckVolume = 1f; - } - service.playback.setVolume(currentDuckVolume); - break; - - case MusicService.UNDUCK: - if (PreferenceUtil.INSTANCE.isAudioDucking()) { - currentDuckVolume += .03f; - if (currentDuckVolume < 1f) { - sendEmptyMessageDelayed(MusicService.UNDUCK, 10); - } else { - currentDuckVolume = 1f; - } - } else { - currentDuckVolume = 1f; - } - service.playback.setVolume(currentDuckVolume); - break; - - case TRACK_WENT_TO_NEXT: - if (service.pendingQuit || service.getRepeatMode() == REPEAT_MODE_NONE && service.isLastTrack()) { - service.pause(); - service.seek(0); - if (service.pendingQuit) { - service.pendingQuit = false; - service.quit(); - break; - } - } else { - service.position = service.nextPosition; - service.prepareNextImpl(); - service.notifyChange(META_CHANGED); - } - break; - - case TRACK_ENDED: - // if there is a timer finished, don't continue - if (service.pendingQuit || - service.getRepeatMode() == REPEAT_MODE_NONE && service.isLastTrack()) { - service.notifyChange(PLAY_STATE_CHANGED); - service.seek(0); - if (service.pendingQuit) { - service.pendingQuit = false; - service.quit(); - break; - } - } else { - service.playNextSong(false); - } - sendEmptyMessage(MusicService.RELEASE_WAKELOCK); - break; - - case MusicService.RELEASE_WAKELOCK: - service.releaseWakeLock(); - break; - - case MusicService.PLAY_SONG: - service.playSongAtImpl(msg.arg1); - break; - - case MusicService.SET_POSITION: - service.openTrackAndPrepareNextAt(msg.arg1); - service.notifyChange(PLAY_STATE_CHANGED); - break; - - case MusicService.PREPARE_NEXT: - service.prepareNextImpl(); - break; - - case MusicService.RESTORE_QUEUES: - service.restoreQueuesAndPositionIfNecessary(); - break; - - case MusicService.FOCUS_CHANGE: - switch (msg.arg1) { - case AudioManager.AUDIOFOCUS_GAIN: - if (!service.isPlaying() && service.isPausedByTransientLossOfFocus()) { - service.play(); - service.setPausedByTransientLossOfFocus(false); - } - removeMessages(DUCK); - sendEmptyMessage(MusicService.UNDUCK); - break; - - case AudioManager.AUDIOFOCUS_LOSS: - // Lost focus for an unbounded amount of time: stop playback and release media playback - service.pause(); - break; - - case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: - // Lost focus for a short time, but we have to stop - // playback. We don't release the media playback because playback - // is likely to resume - boolean wasPlaying = service.isPlaying(); - service.pause(); - service.setPausedByTransientLossOfFocus(wasPlaying); - break; - - case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: - // Lost focus for a short time, but it's ok to keep playing - // at an attenuated level - removeMessages(MusicService.UNDUCK); - sendEmptyMessage(DUCK); - break; - } - break; + case MusicService.UNDUCK: + if (PreferenceUtil.INSTANCE.isAudioDucking()) { + currentDuckVolume += .03f; + if (currentDuckVolume < 1f) { + sendEmptyMessageDelayed(MusicService.UNDUCK, 10); + } else { + currentDuckVolume = 1f; + } + } else { + currentDuckVolume = 1f; } + service.playback.setVolume(currentDuckVolume); + break; + + case TRACK_WENT_TO_NEXT: + if (service.pendingQuit + || service.getRepeatMode() == REPEAT_MODE_NONE && service.isLastTrack()) { + service.pause(); + service.seek(0); + if (service.pendingQuit) { + service.pendingQuit = false; + service.quit(); + break; + } + } else { + service.position = service.nextPosition; + service.prepareNextImpl(); + service.notifyChange(META_CHANGED); + } + break; + + case TRACK_ENDED: + // if there is a timer finished, don't continue + if (service.pendingQuit + || service.getRepeatMode() == REPEAT_MODE_NONE && service.isLastTrack()) { + service.notifyChange(PLAY_STATE_CHANGED); + service.seek(0); + if (service.pendingQuit) { + service.pendingQuit = false; + service.quit(); + break; + } + } else { + service.playNextSong(false); + } + sendEmptyMessage(MusicService.RELEASE_WAKELOCK); + break; + + case MusicService.RELEASE_WAKELOCK: + service.releaseWakeLock(); + break; + + case MusicService.PLAY_SONG: + service.playSongAtImpl(msg.arg1); + break; + + case MusicService.SET_POSITION: + service.openTrackAndPrepareNextAt(msg.arg1); + service.notifyChange(PLAY_STATE_CHANGED); + break; + + case MusicService.PREPARE_NEXT: + service.prepareNextImpl(); + break; + + case MusicService.RESTORE_QUEUES: + service.restoreQueuesAndPositionIfNecessary(); + break; + + case MusicService.FOCUS_CHANGE: + switch (msg.arg1) { + case AudioManager.AUDIOFOCUS_GAIN: + if (!service.isPlaying() && service.isPausedByTransientLossOfFocus()) { + service.play(); + service.setPausedByTransientLossOfFocus(false); + } + removeMessages(DUCK); + sendEmptyMessage(MusicService.UNDUCK); + break; + + case AudioManager.AUDIOFOCUS_LOSS: + // Lost focus for an unbounded amount of time: stop playback and release media playback + service.pause(); + break; + + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: + // Lost focus for a short time, but we have to stop + // playback. We don't release the media playback because playback + // is likely to resume + boolean wasPlaying = service.isPlaying(); + service.pause(); + service.setPausedByTransientLossOfFocus(wasPlaying); + break; + + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: + // Lost focus for a short time, but it's ok to keep playing + // at an attenuated level + removeMessages(MusicService.UNDUCK); + sendEmptyMessage(DUCK); + break; + } + break; } + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/util/ArtistSignatureUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/ArtistSignatureUtil.java index fece1ea5..6418dae5 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/ArtistSignatureUtil.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/ArtistSignatureUtil.java @@ -17,42 +17,38 @@ package code.name.monkey.retromusic.util; import android.annotation.SuppressLint; import android.content.Context; import android.content.SharedPreferences; - import androidx.annotation.NonNull; - import com.bumptech.glide.signature.StringSignature; -/** - * @author Karim Abou Zeid (kabouzeid) - */ +/** @author Karim Abou Zeid (kabouzeid) */ public class ArtistSignatureUtil { - private static final String ARTIST_SIGNATURE_PREFS = "artist_signatures"; + private static final String ARTIST_SIGNATURE_PREFS = "artist_signatures"; - private static ArtistSignatureUtil sInstance; + private static ArtistSignatureUtil sInstance; - private final SharedPreferences mPreferences; + private final SharedPreferences mPreferences; - private ArtistSignatureUtil(@NonNull final Context context) { - mPreferences = context.getSharedPreferences(ARTIST_SIGNATURE_PREFS, Context.MODE_PRIVATE); + private ArtistSignatureUtil(@NonNull final Context context) { + mPreferences = context.getSharedPreferences(ARTIST_SIGNATURE_PREFS, Context.MODE_PRIVATE); + } + + public static ArtistSignatureUtil getInstance(@NonNull final Context context) { + if (sInstance == null) { + sInstance = new ArtistSignatureUtil(context.getApplicationContext()); } + return sInstance; + } - public static ArtistSignatureUtil getInstance(@NonNull final Context context) { - if (sInstance == null) { - sInstance = new ArtistSignatureUtil(context.getApplicationContext()); - } - return sInstance; - } + @SuppressLint("CommitPrefEdits") + public void updateArtistSignature(String artistName) { + mPreferences.edit().putLong(artistName, System.currentTimeMillis()).commit(); + } - @SuppressLint("CommitPrefEdits") - public void updateArtistSignature(String artistName) { - mPreferences.edit().putLong(artistName, System.currentTimeMillis()).commit(); - } + public long getArtistSignatureRaw(String artistName) { + return mPreferences.getLong(artistName, 0); + } - public long getArtistSignatureRaw(String artistName) { - return mPreferences.getLong(artistName, 0); - } - - public StringSignature getArtistSignature(String artistName) { - return new StringSignature(String.valueOf(getArtistSignatureRaw(artistName))); - } + public StringSignature getArtistSignature(String artistName) { + return new StringSignature(String.valueOf(getArtistSignatureRaw(artistName))); + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/util/AutoGeneratedPlaylistBitmap.java b/app/src/main/java/code/name/monkey/retromusic/util/AutoGeneratedPlaylistBitmap.java index b0ee4cf5..026c3719 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/AutoGeneratedPlaylistBitmap.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/AutoGeneratedPlaylistBitmap.java @@ -8,182 +8,181 @@ import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; import android.util.Log; - import androidx.annotation.NonNull; - +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.model.Song; import com.bumptech.glide.Glide; - import java.util.ArrayList; import java.util.List; -import code.name.monkey.retromusic.R; -import code.name.monkey.retromusic.model.Song; - - public class AutoGeneratedPlaylistBitmap { - private static final String TAG = "AutoGeneratedPB"; + private static final String TAG = "AutoGeneratedPB"; + /* + public static Bitmap getBitmapWithCollectionFrame(Context context, List songPlaylist, boolean round, boolean blur) { + Bitmap bitmap = getBitmap(context,songPlaylist,round,blur); + int w = bitmap.getWidth(); + Bitmap ret = Bitmap.createBitmap(w,w,Bitmap.Config.ARGB_8888); + } + */ + public static Bitmap getBitmap( + Context context, List songPlaylist, boolean round, boolean blur) { + if (songPlaylist == null) return null; + long start = System.currentTimeMillis(); + // lấy toàn bộ album id, loại bỏ trùng nhau + List albumID = new ArrayList<>(); + for (Song song : songPlaylist) { + if (!albumID.contains(song.getAlbumId())) albumID.add(song.getAlbumId()); + } + + long start2 = System.currentTimeMillis() - start; + + // lấy toàn bộ art tồn tại + List art = new ArrayList(); + for (Long id : albumID) { + Bitmap bitmap = getBitmapWithAlbumId(context, id); + if (bitmap != null) art.add(bitmap); + if (art.size() == 6) break; + } + return MergedImageUtils.INSTANCE.joinImages(art); /* - public static Bitmap getBitmapWithCollectionFrame(Context context, List songPlaylist, boolean round, boolean blur) { - Bitmap bitmap = getBitmap(context,songPlaylist,round,blur); - int w = bitmap.getWidth(); - Bitmap ret = Bitmap.createBitmap(w,w,Bitmap.Config.ARGB_8888); + + long start3 = System.currentTimeMillis() - start2 - start; + Bitmap ret; + switch (art.size()) { + // lấy hình mặc định + case 0: + ret = getDefaultBitmap(context, round).copy(Bitmap.Config.ARGB_8888, false); + break; + // dùng hình duy nhất + case 1: + if (round) + ret = BitmapEditor.getRoundedCornerBitmap(art.get(0), art.get(0).getWidth() / 40); + else ret = art.get(0); + break; + // từ 2 trở lên ta cần vẽ canvas + default: + ret = getBitmapCollection(art, round); } - */ - public static Bitmap getBitmap(Context context, List songPlaylist, boolean round, boolean blur) { - if (songPlaylist == null) return null; - long start = System.currentTimeMillis(); - // lấy toàn bộ album id, loại bỏ trùng nhau - List albumID = new ArrayList<>(); - for (Song song : songPlaylist) { - if (!albumID.contains(song.getAlbumId())) albumID.add(song.getAlbumId()); - } + int w = ret.getWidth(); + if (blur) + return BitmapEditor.GetRoundedBitmapWithBlurShadow(context, ret, w / 24, w / 24, w / 24, w / 24, 0, 200, w / 40, 1); - long start2 = System.currentTimeMillis() - start; + Log.d(TAG, "getBitmap: time = " + (System.currentTimeMillis() - start) + ", start2 = " + start2 + ", start3 = " + start3); + return ret;*/ + } - // lấy toàn bộ art tồn tại - List art = new ArrayList(); - for (Long id : albumID) { - Bitmap bitmap = getBitmapWithAlbumId(context, id); - if (bitmap != null) art.add(bitmap); - if (art.size() == 6) break; - } - return MergedImageUtils.INSTANCE.joinImages(art); - /* + private static Bitmap getBitmapCollection(ArrayList art, boolean round) { + long start = System.currentTimeMillis(); + // lấy kích thước là kích thước của bitmap lớn nhất + int max_width = art.get(0).getWidth(); + for (Bitmap b : art) if (max_width < b.getWidth()) max_width = b.getWidth(); + Bitmap bitmap = Bitmap.createBitmap(max_width, max_width, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + Paint paint = new Paint(); + paint.setAntiAlias(false); + paint.setStyle(Paint.Style.STROKE); + paint.setStrokeWidth(max_width / 100); + paint.setColor(0xffffffff); + switch (art.size()) { + case 2: + canvas.drawBitmap(art.get(1), null, new Rect(0, 0, max_width, max_width), null); + canvas.drawBitmap( + art.get(0), null, new Rect(-max_width / 2, 0, max_width / 2, max_width), null); + canvas.drawLine(max_width / 2, 0, max_width / 2, max_width, paint); + break; + case 3: + canvas.drawBitmap( + art.get(0), null, new Rect(-max_width / 4, 0, 3 * max_width / 4, max_width), null); + canvas.drawBitmap( + art.get(1), null, new Rect(max_width / 2, 0, max_width, max_width / 2), null); + canvas.drawBitmap( + art.get(2), null, new Rect(max_width / 2, max_width / 2, max_width, max_width), null); + canvas.drawLine(max_width / 2, 0, max_width / 2, max_width, paint); + canvas.drawLine(max_width / 2, max_width / 2, max_width, max_width / 2, paint); + break; + case 4: + canvas.drawBitmap(art.get(0), null, new Rect(0, 0, max_width / 2, max_width / 2), null); + canvas.drawBitmap( + art.get(1), null, new Rect(max_width / 2, 0, max_width, max_width / 2), null); + canvas.drawBitmap( + art.get(2), null, new Rect(0, max_width / 2, max_width / 2, max_width), null); + canvas.drawBitmap( + art.get(3), null, new Rect(max_width / 2, max_width / 2, max_width, max_width), null); + canvas.drawLine(max_width / 2, 0, max_width / 2, max_width, paint); + canvas.drawLine(0, max_width / 2, max_width, max_width / 2, paint); + break; + // default: canvas.drawBitmap(art.get(0),null,new Rect(0,0,max_width,max_width),null); + default: - long start3 = System.currentTimeMillis() - start2 - start; - Bitmap ret; - switch (art.size()) { - // lấy hình mặc định + // độ rộng của des bitmap + float w = (float) (Math.sqrt(2) / 2 * max_width); + float b = (float) (max_width / Math.sqrt(5)); + // khoảng cách định nghĩa, dùng để tính vị trí tâm của 4 bức hình xung quanh + float d = (float) (max_width * (0.5f - 1 / Math.sqrt(10))); + float deg = 45; + + for (int i = 0; i < 5; i++) { + canvas.save(); + switch (i) { case 0: - ret = getDefaultBitmap(context, round).copy(Bitmap.Config.ARGB_8888, false); - break; - // dùng hình duy nhất + canvas.translate(max_width / 2, max_width / 2); + canvas.rotate(deg); + // b = (float) (max_width*Math.sqrt(2/5f)); + canvas.drawBitmap(art.get(0), null, new RectF(-b / 2, -b / 2, b / 2, b / 2), null); + break; case 1: - if (round) - ret = BitmapEditor.getRoundedCornerBitmap(art.get(0), art.get(0).getWidth() / 40); - else ret = art.get(0); - break; - // từ 2 trở lên ta cần vẽ canvas - default: - ret = getBitmapCollection(art, round); - } - int w = ret.getWidth(); - if (blur) - return BitmapEditor.GetRoundedBitmapWithBlurShadow(context, ret, w / 24, w / 24, w / 24, w / 24, 0, 200, w / 40, 1); - - Log.d(TAG, "getBitmap: time = " + (System.currentTimeMillis() - start) + ", start2 = " + start2 + ", start3 = " + start3); - return ret;*/ - } - - private static Bitmap getBitmapCollection(ArrayList art, boolean round) { - long start = System.currentTimeMillis(); - // lấy kích thước là kích thước của bitmap lớn nhất - int max_width = art.get(0).getWidth(); - for (Bitmap b : art) if (max_width < b.getWidth()) max_width = b.getWidth(); - Bitmap bitmap = Bitmap.createBitmap(max_width, max_width, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - Paint paint = new Paint(); - paint.setAntiAlias(false); - paint.setStyle(Paint.Style.STROKE); - paint.setStrokeWidth(max_width / 100); - paint.setColor(0xffffffff); - switch (art.size()) { + canvas.translate(d, 0); + canvas.rotate(deg); + canvas.drawBitmap(art.get(i), null, new RectF(-w / 2, -w / 2, w / 2, w / 2), null); + paint.setAntiAlias(true); + canvas.drawLine(w / 2, -w / 2, w / 2, w / 2, paint); + break; case 2: - canvas.drawBitmap(art.get(1), null, new Rect(0, 0, max_width, max_width), null); - canvas.drawBitmap(art.get(0), null, new Rect(-max_width / 2, 0, max_width / 2, max_width), null); - canvas.drawLine(max_width / 2, 0, max_width / 2, max_width, paint); - break; + canvas.translate(max_width, d); + canvas.rotate(deg); + canvas.drawBitmap(art.get(i), null, new RectF(-w / 2, -w / 2, w / 2, w / 2), null); + paint.setAntiAlias(true); + canvas.drawLine(-w / 2, w / 2, w / 2, w / 2, paint); + break; case 3: - canvas.drawBitmap(art.get(0), null, new Rect(-max_width / 4, 0, 3 * max_width / 4, max_width), null); - canvas.drawBitmap(art.get(1), null, new Rect(max_width / 2, 0, max_width, max_width / 2), null); - canvas.drawBitmap(art.get(2), null, new Rect(max_width / 2, max_width / 2, max_width, max_width), null); - canvas.drawLine(max_width / 2, 0, max_width / 2, max_width, paint); - canvas.drawLine(max_width / 2, max_width / 2, max_width, max_width / 2, paint); - break; + canvas.translate(max_width - d, max_width); + canvas.rotate(deg); + canvas.drawBitmap(art.get(i), null, new RectF(-w / 2, -w / 2, w / 2, w / 2), null); + paint.setAntiAlias(true); + canvas.drawLine(-w / 2, -w / 2, -w / 2, w / 2, paint); + break; case 4: - canvas.drawBitmap(art.get(0), null, new Rect(0, 0, max_width / 2, max_width / 2), null); - canvas.drawBitmap(art.get(1), null, new Rect(max_width / 2, 0, max_width, max_width / 2), null); - canvas.drawBitmap(art.get(2), null, new Rect(0, max_width / 2, max_width / 2, max_width), null); - canvas.drawBitmap(art.get(3), null, new Rect(max_width / 2, max_width / 2, max_width, max_width), null); - canvas.drawLine(max_width / 2, 0, max_width / 2, max_width, paint); - canvas.drawLine(0, max_width / 2, max_width, max_width / 2, paint); - break; - // default: canvas.drawBitmap(art.get(0),null,new Rect(0,0,max_width,max_width),null); - default: - - // độ rộng của des bitmap - float w = (float) (Math.sqrt(2) / 2 * max_width); - float b = (float) (max_width / Math.sqrt(5)); - // khoảng cách định nghĩa, dùng để tính vị trí tâm của 4 bức hình xung quanh - float d = (float) (max_width * (0.5f - 1 / Math.sqrt(10))); - float deg = 45; - - for (int i = 0; i < 5; i++) { - canvas.save(); - switch (i) { - case 0: - canvas.translate(max_width / 2, max_width / 2); - canvas.rotate(deg); - // b = (float) (max_width*Math.sqrt(2/5f)); - canvas.drawBitmap(art.get(0), null, new RectF(-b / 2, -b / 2, b / 2, b / 2), null); - break; - case 1: - canvas.translate(d, 0); - canvas.rotate(deg); - canvas.drawBitmap(art.get(i), null, new RectF(-w / 2, -w / 2, w / 2, w / 2), null); - paint.setAntiAlias(true); - canvas.drawLine(w / 2, -w / 2, w / 2, w / 2, paint); - break; - case 2: - canvas.translate(max_width, d); - canvas.rotate(deg); - canvas.drawBitmap(art.get(i), null, new RectF(-w / 2, -w / 2, w / 2, w / 2), null); - paint.setAntiAlias(true); - canvas.drawLine(-w / 2, w / 2, w / 2, w / 2, paint); - break; - case 3: - canvas.translate(max_width - d, max_width); - canvas.rotate(deg); - canvas.drawBitmap(art.get(i), null, new RectF(-w / 2, -w / 2, w / 2, w / 2), null); - paint.setAntiAlias(true); - canvas.drawLine(-w / 2, -w / 2, -w / 2, w / 2, paint); - break; - case 4: - canvas.translate(0, max_width - d); - canvas.rotate(deg); - canvas.drawBitmap(art.get(i), null, new RectF(-w / 2, -w / 2, w / 2, w / 2), null); - paint.setAntiAlias(true); - canvas.drawLine(-w / 2, -w / 2, w / 2, -w / 2, paint); - break; - } - canvas.restore(); - } - - - } - Log.d(TAG, "getBitmapCollection: smalltime = " + (System.currentTimeMillis() - start)); - if (round) - return BitmapEditor.getRoundedCornerBitmap(bitmap, bitmap.getWidth() / 40); - else return bitmap; - } - - private static Bitmap getBitmapWithAlbumId(@NonNull Context context, Long id) { - try { - return Glide.with(context) - .load(MusicUtil.INSTANCE.getMediaStoreAlbumCoverUri(id)) - .asBitmap() - .into(200, 200) - .get(); - } catch (Exception e) { - return null; + canvas.translate(0, max_width - d); + canvas.rotate(deg); + canvas.drawBitmap(art.get(i), null, new RectF(-w / 2, -w / 2, w / 2, w / 2), null); + paint.setAntiAlias(true); + canvas.drawLine(-w / 2, -w / 2, w / 2, -w / 2, paint); + break; + } + canvas.restore(); } } + Log.d(TAG, "getBitmapCollection: smalltime = " + (System.currentTimeMillis() - start)); + if (round) return BitmapEditor.getRoundedCornerBitmap(bitmap, bitmap.getWidth() / 40); + else return bitmap; + } - public static Bitmap getDefaultBitmap(@NonNull Context context, boolean round) { - if (round) - return BitmapFactory.decodeResource(context.getResources(), R.drawable.default_album_art); - return BitmapFactory.decodeResource(context.getResources(), R.drawable.default_album_art); + private static Bitmap getBitmapWithAlbumId(@NonNull Context context, Long id) { + try { + return Glide.with(context) + .load(MusicUtil.INSTANCE.getMediaStoreAlbumCoverUri(id)) + .asBitmap() + .into(200, 200) + .get(); + } catch (Exception e) { + return null; } + } -} \ No newline at end of file + public static Bitmap getDefaultBitmap(@NonNull Context context, boolean round) { + if (round) + return BitmapFactory.decodeResource(context.getResources(), R.drawable.default_album_art); + return BitmapFactory.decodeResource(context.getResources(), R.drawable.default_album_art); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/util/BitmapEditor.java b/app/src/main/java/code/name/monkey/retromusic/util/BitmapEditor.java index 0f796141..fac5bae2 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/BitmapEditor.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/BitmapEditor.java @@ -25,944 +25,975 @@ import android.renderscript.ScriptIntrinsicBlur; import android.view.View; import android.widget.ImageView; -/** - * Created by trung on 7/11/2017. - */ - +/** Created by trung on 7/11/2017. */ public final class BitmapEditor { - /** - * Stack Blur v1.0 from - * http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html - * Java Author: Mario Klingemann - * http://incubator.quasimondo.com - *

- * created Feburary 29, 2004 - * Android port : Yahel Bouaziz - * http://www.kayenko.com - * ported april 5th, 2012 - *

- * This is A compromise between Gaussian Blur and Box blur - * It creates much better looking blurs than Box Blur, but is - * 7x faster than my Gaussian Blur implementation. - *

- * I called it Stack Blur because this describes best how this - * filter works internally: it creates A kind of moving stack - * of colors whilst scanning through the image. Thereby it - * just has to add one new block of color to the right side - * of the stack and removeFromParent the leftmost color. The remaining - * colors on the topmost layer of the stack are either added on - * or reduced by one, depending on if they are on the right or - * on the x side of the stack. - *

- * If you are using this algorithm in your code please add - * the following line: - * Stack Blur Algorithm by Mario Klingemann - */ + /** + * Stack Blur v1.0 from http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html Java + * Author: Mario Klingemann http://incubator.quasimondo.com + * + *

created Feburary 29, 2004 Android port : Yahel Bouaziz + * http://www.kayenko.com ported april 5th, 2012 + * + *

This is A compromise between Gaussian Blur and Box blur It creates much better looking blurs + * than Box Blur, but is 7x faster than my Gaussian Blur implementation. + * + *

I called it Stack Blur because this describes best how this filter works internally: it + * creates A kind of moving stack of colors whilst scanning through the image. Thereby it just has + * to add one new block of color to the right side of the stack and removeFromParent the leftmost + * color. The remaining colors on the topmost layer of the stack are either added on or reduced by + * one, depending on if they are on the right or on the x side of the stack. + * + *

If you are using this algorithm in your code please add the following line: Stack Blur + * Algorithm by Mario Klingemann + */ + public static Bitmap FastBlurSupportAlpha(Bitmap sentBitmap, float scale, int radius) { - public static Bitmap FastBlurSupportAlpha(Bitmap sentBitmap, float scale, int radius) { + int width = Math.round(sentBitmap.getWidth() * scale); + int height = Math.round(sentBitmap.getHeight() * scale); + sentBitmap = Bitmap.createScaledBitmap(sentBitmap, width, height, false); - int width = Math.round(sentBitmap.getWidth() * scale); - int height = Math.round(sentBitmap.getHeight() * scale); - sentBitmap = Bitmap.createScaledBitmap(sentBitmap, width, height, false); + Bitmap bitmap = sentBitmap.copy(sentBitmap.getConfig(), true); - Bitmap bitmap = sentBitmap.copy(sentBitmap.getConfig(), true); - - if (radius < 1) { - return (null); - } - - int w = bitmap.getWidth(); - int h = bitmap.getHeight(); - - int[] pix = new int[w * h]; - // Log.e("pix", w + " " + h + " " + pix.length); - bitmap.getPixels(pix, 0, w, 0, 0, w, h); - - int wm = w - 1; - int hm = h - 1; - int wh = w * h; - int div = radius + radius + 1; - - int[] r = new int[wh]; - int[] g = new int[wh]; - int[] b = new int[wh]; - int[] a = new int[wh]; - int rsum, gsum, bsum, asum, x, y, i, p, yp, yi, yw; - int[] vmin = new int[Math.max(w, h)]; - - int divsum = (div + 1) >> 1; - divsum *= divsum; - int[] dv = new int[256 * divsum]; - for (i = 0; i < 256 * divsum; i++) { - dv[i] = (i / divsum); - } - - yw = yi = 0; - - int[][] stack = new int[div][4]; - int stackpointer; - int stackstart; - int[] sir; - int rbs; - int r1 = radius + 1; - int routsum, goutsum, boutsum, aoutsum; - int rinsum, ginsum, binsum, ainsum; - - for (y = 0; y < h; y++) { - rinsum = ginsum = binsum = ainsum = routsum = goutsum = boutsum = aoutsum = rsum = gsum = bsum = asum = 0; - for (i = -radius; i <= radius; i++) { - p = pix[yi + Math.min(wm, Math.max(i, 0))]; - sir = stack[i + radius]; - sir[0] = (p & 0xff0000) >> 16; - sir[1] = (p & 0x00ff00) >> 8; - sir[2] = (p & 0x0000ff); - sir[3] = 0xff & (p >> 24); - - rbs = r1 - Math.abs(i); - rsum += sir[0] * rbs; - gsum += sir[1] * rbs; - bsum += sir[2] * rbs; - asum += sir[3] * rbs; - if (i > 0) { - rinsum += sir[0]; - ginsum += sir[1]; - binsum += sir[2]; - ainsum += sir[3]; - } else { - routsum += sir[0]; - goutsum += sir[1]; - boutsum += sir[2]; - aoutsum += sir[3]; - } - } - stackpointer = radius; - - for (x = 0; x < w; x++) { - - r[yi] = dv[rsum]; - g[yi] = dv[gsum]; - b[yi] = dv[bsum]; - a[yi] = dv[asum]; - - rsum -= routsum; - gsum -= goutsum; - bsum -= boutsum; - asum -= aoutsum; - - stackstart = stackpointer - radius + div; - sir = stack[stackstart % div]; - - routsum -= sir[0]; - goutsum -= sir[1]; - boutsum -= sir[2]; - aoutsum -= sir[3]; - - if (y == 0) { - vmin[x] = Math.min(x + radius + 1, wm); - } - p = pix[yw + vmin[x]]; - - sir[0] = (p & 0xff0000) >> 16; - sir[1] = (p & 0x00ff00) >> 8; - sir[2] = (p & 0x0000ff); - sir[3] = 0xff & (p >> 24); - - rinsum += sir[0]; - ginsum += sir[1]; - binsum += sir[2]; - ainsum += sir[3]; - - rsum += rinsum; - gsum += ginsum; - bsum += binsum; - asum += ainsum; - - stackpointer = (stackpointer + 1) % div; - sir = stack[(stackpointer) % div]; - - routsum += sir[0]; - goutsum += sir[1]; - boutsum += sir[2]; - aoutsum += sir[3]; - - rinsum -= sir[0]; - ginsum -= sir[1]; - binsum -= sir[2]; - ainsum -= sir[3]; - - yi++; - } - yw += w; - } - for (x = 0; x < w; x++) { - rinsum = ginsum = binsum = ainsum = routsum = goutsum = boutsum = aoutsum = rsum = gsum = bsum = asum = 0; - yp = -radius * w; - for (i = -radius; i <= radius; i++) { - yi = Math.max(0, yp) + x; - - sir = stack[i + radius]; - - sir[0] = r[yi]; - sir[1] = g[yi]; - sir[2] = b[yi]; - sir[3] = a[yi]; - - rbs = r1 - Math.abs(i); - - rsum += r[yi] * rbs; - gsum += g[yi] * rbs; - bsum += b[yi] * rbs; - asum += a[yi] * rbs; - - if (i > 0) { - rinsum += sir[0]; - ginsum += sir[1]; - binsum += sir[2]; - ainsum += sir[3]; - } else { - routsum += sir[0]; - goutsum += sir[1]; - boutsum += sir[2]; - aoutsum += sir[3]; - } - - if (i < hm) { - yp += w; - } - } - yi = x; - stackpointer = radius; - for (y = 0; y < h; y++) { - pix[yi] = (dv[asum] << 24) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum]; - - rsum -= routsum; - gsum -= goutsum; - bsum -= boutsum; - asum -= aoutsum; - - stackstart = stackpointer - radius + div; - sir = stack[stackstart % div]; - - routsum -= sir[0]; - goutsum -= sir[1]; - boutsum -= sir[2]; - aoutsum -= sir[3]; - - if (x == 0) { - vmin[y] = Math.min(y + r1, hm) * w; - } - p = x + vmin[y]; - - - sir[0] = r[p]; - sir[1] = g[p]; - sir[2] = b[p]; - sir[3] = a[p]; - - rinsum += sir[0]; - ginsum += sir[1]; - binsum += sir[2]; - ainsum += sir[3]; - - rsum += rinsum; - gsum += ginsum; - bsum += binsum; - asum += ainsum; - - stackpointer = (stackpointer + 1) % div; - sir = stack[stackpointer]; - - routsum += sir[0]; - goutsum += sir[1]; - boutsum += sir[2]; - aoutsum += sir[3]; - - rinsum -= sir[0]; - ginsum -= sir[1]; - binsum -= sir[2]; - ainsum -= sir[3]; - - yi += w; - } - } - - // Log.e("pix", w + " " + h + " " + pix.length); - bitmap.setPixels(pix, 0, w, 0, 0, w, h); - - return (bitmap); + if (radius < 1) { + return (null); } - public static boolean PerceivedBrightness(int will_White, int[] c) { - double TBT = Math.sqrt(c[0] * c[0] * .241 + c[1] * c[1] * .691 + c[2] * c[2] * .068); - // Log.d("themee",TBT+""); - return !(TBT > will_White); + int w = bitmap.getWidth(); + int h = bitmap.getHeight(); + + int[] pix = new int[w * h]; + // Log.e("pix", w + " " + h + " " + pix.length); + bitmap.getPixels(pix, 0, w, 0, 0, w, h); + + int wm = w - 1; + int hm = h - 1; + int wh = w * h; + int div = radius + radius + 1; + + int[] r = new int[wh]; + int[] g = new int[wh]; + int[] b = new int[wh]; + int[] a = new int[wh]; + int rsum, gsum, bsum, asum, x, y, i, p, yp, yi, yw; + int[] vmin = new int[Math.max(w, h)]; + + int divsum = (div + 1) >> 1; + divsum *= divsum; + int[] dv = new int[256 * divsum]; + for (i = 0; i < 256 * divsum; i++) { + dv[i] = (i / divsum); } - public static int[] getAverageColorRGB(Bitmap bitmap) { - final int width = bitmap.getWidth(); - final int height = bitmap.getHeight(); - int size = width * height; - int pixelColor; - int r, g, b; - r = g = b = 0; - for (int x = 0; x < width; ++x) { - for (int y = 0; y < height; ++y) { - pixelColor = bitmap.getPixel(x, y); - if (pixelColor == 0) { - size--; - continue; - } - r += Color.red(pixelColor); - g += Color.green(pixelColor); - b += Color.blue(pixelColor); - } - } - r /= size; - g /= size; - b /= size; - return new int[]{ - r, g, b - }; - } + yw = yi = 0; - public static Bitmap updateSat(Bitmap src, float settingSat) { + int[][] stack = new int[div][4]; + int stackpointer; + int stackstart; + int[] sir; + int rbs; + int r1 = radius + 1; + int routsum, goutsum, boutsum, aoutsum; + int rinsum, ginsum, binsum, ainsum; - int w = src.getWidth(); - int h = src.getHeight(); + for (y = 0; y < h; y++) { + rinsum = + ginsum = + binsum = + ainsum = routsum = goutsum = boutsum = aoutsum = rsum = gsum = bsum = asum = 0; + for (i = -radius; i <= radius; i++) { + p = pix[yi + Math.min(wm, Math.max(i, 0))]; + sir = stack[i + radius]; + sir[0] = (p & 0xff0000) >> 16; + sir[1] = (p & 0x00ff00) >> 8; + sir[2] = (p & 0x0000ff); + sir[3] = 0xff & (p >> 24); - Bitmap bitmapResult = - Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); - Canvas canvasResult = new Canvas(bitmapResult); - Paint paint = new Paint(); - ColorMatrix colorMatrix = new ColorMatrix(); - colorMatrix.setSaturation(settingSat); - ColorMatrixColorFilter filter = new ColorMatrixColorFilter(colorMatrix); - paint.setColorFilter(filter); - canvasResult.drawBitmap(src, 0, 0, paint); - canvasResult.setBitmap(null); - canvasResult = null; - return bitmapResult; - } - - /** - * Stack Blur v1.0 from - * http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html - * Java Author: Mario Klingemann - * http://incubator.quasimondo.com - *

- * created Feburary 29, 2004 - * Android port : Yahel Bouaziz - * http://www.kayenko.com - * ported april 5th, 2012 - *

- * This is A compromise between Gaussian Blur and Box blur - * It creates much better looking blurs than Box Blur, but is - * 7x faster than my Gaussian Blur implementation. - *

- * I called it Stack Blur because this describes best how this - * filter works internally: it creates A kind of moving stack - * of colors whilst scanning through the image. Thereby it - * just has to add one new block of color to the right side - * of the stack and removeFromParent the leftmost color. The remaining - * colors on the topmost layer of the stack are either added on - * or reduced by one, depending on if they are on the right or - * on the x side of the stack. - *

- * If you are using this algorithm in your code please add - * the following line: - * Stack Blur Algorithm by Mario Klingemann - */ - - public static Bitmap fastblur(Bitmap sentBitmap, float scale, int radius) { - - Bitmap afterscaleSentBitmap; - Bitmap bitmap; - if (scale != 1) { - int width = Math.round(sentBitmap.getWidth() * scale); //lấy chiều rộng làm tròn - int height = Math.round(sentBitmap.getHeight() * scale); // lấy chiều cao làm tròn - afterscaleSentBitmap = Bitmap.createScaledBitmap(sentBitmap, width, height, false); // tạo bitmap scaled - bitmap = afterscaleSentBitmap.copy(afterscaleSentBitmap.getConfig(), true); - afterscaleSentBitmap.recycle(); + rbs = r1 - Math.abs(i); + rsum += sir[0] * rbs; + gsum += sir[1] * rbs; + bsum += sir[2] * rbs; + asum += sir[3] * rbs; + if (i > 0) { + rinsum += sir[0]; + ginsum += sir[1]; + binsum += sir[2]; + ainsum += sir[3]; } else { - bitmap = sentBitmap.copy(sentBitmap.getConfig(), true); // đơn giản chỉ copy + routsum += sir[0]; + goutsum += sir[1]; + boutsum += sir[2]; + aoutsum += sir[3]; } + } + stackpointer = radius; + for (x = 0; x < w; x++) { - if (radius < 1) { - return (sentBitmap.copy(sentBitmap.getConfig(), true)); + r[yi] = dv[rsum]; + g[yi] = dv[gsum]; + b[yi] = dv[bsum]; + a[yi] = dv[asum]; + + rsum -= routsum; + gsum -= goutsum; + bsum -= boutsum; + asum -= aoutsum; + + stackstart = stackpointer - radius + div; + sir = stack[stackstart % div]; + + routsum -= sir[0]; + goutsum -= sir[1]; + boutsum -= sir[2]; + aoutsum -= sir[3]; + + if (y == 0) { + vmin[x] = Math.min(x + radius + 1, wm); } + p = pix[yw + vmin[x]]; - int w = bitmap.getWidth(); // w is the width of sample bitmap - int h = bitmap.getHeight(); // h is the height of sample bitmap + sir[0] = (p & 0xff0000) >> 16; + sir[1] = (p & 0x00ff00) >> 8; + sir[2] = (p & 0x0000ff); + sir[3] = 0xff & (p >> 24); - int[] pix = new int[w * h]; // pix is the arrary of all bitmap pixel + rinsum += sir[0]; + ginsum += sir[1]; + binsum += sir[2]; + ainsum += sir[3]; - bitmap.getPixels(pix, 0, w, 0, 0, w, h); + rsum += rinsum; + gsum += ginsum; + bsum += binsum; + asum += ainsum; - int wm = w - 1; - int hm = h - 1; - int wh = w * h; - int div = radius + radius + 1; + stackpointer = (stackpointer + 1) % div; + sir = stack[(stackpointer) % div]; - int[] r = new int[wh]; - int[] g = new int[wh]; - int[] b = new int[wh]; - int rsum, gsum, bsum, x, y, i, p, yp, yi, yw; - int[] vmin = new int[Math.max(w, h)]; + routsum += sir[0]; + goutsum += sir[1]; + boutsum += sir[2]; + aoutsum += sir[3]; - int divsum = (div + 1) >> 1; - divsum *= divsum; - int[] dv = new int[256 * divsum]; - for (i = 0; i < 256 * divsum; i++) { - dv[i] = (i / divsum); - } + rinsum -= sir[0]; + ginsum -= sir[1]; + binsum -= sir[2]; + ainsum -= sir[3]; - yw = yi = 0; - - int[][] stack = new int[div][3]; - int stackpointer; - int stackstart; - int[] sir; - int rbs; - int r1 = radius + 1; - int routsum, goutsum, boutsum; - int rinsum, ginsum, binsum; - - for (y = 0; y < h; y++) { - rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; - for (i = -radius; i <= radius; i++) { - p = pix[yi + Math.min(wm, Math.max(i, 0))]; - sir = stack[i + radius]; - sir[0] = (p & 0xff0000) >> 16; - sir[1] = (p & 0x00ff00) >> 8; - sir[2] = (p & 0x0000ff); - rbs = r1 - Math.abs(i); - rsum += sir[0] * rbs; - gsum += sir[1] * rbs; - bsum += sir[2] * rbs; - if (i > 0) { - rinsum += sir[0]; - ginsum += sir[1]; - binsum += sir[2]; - } else { - routsum += sir[0]; - goutsum += sir[1]; - boutsum += sir[2]; - } - } - stackpointer = radius; - - for (x = 0; x < w; x++) { - - r[yi] = dv[rsum]; - g[yi] = dv[gsum]; - b[yi] = dv[bsum]; - - rsum -= routsum; - gsum -= goutsum; - bsum -= boutsum; - - stackstart = stackpointer - radius + div; - sir = stack[stackstart % div]; - - routsum -= sir[0]; - goutsum -= sir[1]; - boutsum -= sir[2]; - - if (y == 0) { - vmin[x] = Math.min(x + radius + 1, wm); - } - p = pix[yw + vmin[x]]; - - sir[0] = (p & 0xff0000) >> 16; - sir[1] = (p & 0x00ff00) >> 8; - sir[2] = (p & 0x0000ff); - - rinsum += sir[0]; - ginsum += sir[1]; - binsum += sir[2]; - - rsum += rinsum; - gsum += ginsum; - bsum += binsum; - - stackpointer = (stackpointer + 1) % div; - sir = stack[(stackpointer) % div]; - - routsum += sir[0]; - goutsum += sir[1]; - boutsum += sir[2]; - - rinsum -= sir[0]; - ginsum -= sir[1]; - binsum -= sir[2]; - - yi++; - } - yw += w; - } - for (x = 0; x < w; x++) { - rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; - yp = -radius * w; - for (i = -radius; i <= radius; i++) { - yi = Math.max(0, yp) + x; - - sir = stack[i + radius]; - - sir[0] = r[yi]; - sir[1] = g[yi]; - sir[2] = b[yi]; - - rbs = r1 - Math.abs(i); - - rsum += r[yi] * rbs; - gsum += g[yi] * rbs; - bsum += b[yi] * rbs; - - if (i > 0) { - rinsum += sir[0]; - ginsum += sir[1]; - binsum += sir[2]; - } else { - routsum += sir[0]; - goutsum += sir[1]; - boutsum += sir[2]; - } - - if (i < hm) { - yp += w; - } - } - yi = x; - stackpointer = radius; - for (y = 0; y < h; y++) { - // Preserve alpha channel: ( 0xff000000 & pix[yi] ) - pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum]; - - rsum -= routsum; - gsum -= goutsum; - bsum -= boutsum; - - stackstart = stackpointer - radius + div; - sir = stack[stackstart % div]; - - routsum -= sir[0]; - goutsum -= sir[1]; - boutsum -= sir[2]; - - if (x == 0) { - vmin[y] = Math.min(y + r1, hm) * w; - } - p = x + vmin[y]; - - sir[0] = r[p]; - sir[1] = g[p]; - sir[2] = b[p]; - - rinsum += sir[0]; - ginsum += sir[1]; - binsum += sir[2]; - - rsum += rinsum; - gsum += ginsum; - bsum += binsum; - - stackpointer = (stackpointer + 1) % div; - sir = stack[stackpointer]; - - routsum += sir[0]; - goutsum += sir[1]; - boutsum += sir[2]; - - rinsum -= sir[0]; - ginsum -= sir[1]; - binsum -= sir[2]; - - yi += w; - } - } - - - bitmap.setPixels(pix, 0, w, 0, 0, w, h); - - return (bitmap); + yi++; + } + yw += w; } + for (x = 0; x < w; x++) { + rinsum = + ginsum = + binsum = + ainsum = routsum = goutsum = boutsum = aoutsum = rsum = gsum = bsum = asum = 0; + yp = -radius * w; + for (i = -radius; i <= radius; i++) { + yi = Math.max(0, yp) + x; - public static Bitmap getRoundedCornerBitmap(Bitmap bitmap, int pixels) { - Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap - .getHeight(), Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(output); + sir = stack[i + radius]; - final int color = 0xff424242; - final Paint paint = new Paint(); - final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); - // final ScreenSize rectF = new ScreenSize(rect); - final float roundPx = pixels; + sir[0] = r[yi]; + sir[1] = g[yi]; + sir[2] = b[yi]; + sir[3] = a[yi]; - paint.setAntiAlias(true); - canvas.drawARGB(0, 0, 0, 0); - paint.setColor(color); - // canvas.drawRoundRect(rectF, roundPx, roundPx, paint); - canvas.drawPath(BitmapEditor.RoundedRect(0, 0, bitmap.getWidth(), bitmap.getHeight(), roundPx, roundPx, false), paint); - paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); - canvas.drawBitmap(bitmap, rect, rect, paint); + rbs = r1 - Math.abs(i); - return output; - } + rsum += r[yi] * rbs; + gsum += g[yi] * rbs; + bsum += b[yi] * rbs; + asum += a[yi] * rbs; - /** - * getResizedBitmap method is used to Resized the Image according to custom width and height - * - * @param image - * @param newHeight (new desired height) - * @param newWidth (new desired Width) - * @return image (new resized image) - */ - public static Bitmap getResizedBitmap(Bitmap image, int newHeight, int newWidth) { - int width = image.getWidth(); - int height = image.getHeight(); - float scaleWidth = ((float) newWidth) / width; - float scaleHeight = ((float) newHeight) / height; - // create A matrix for the manipulation - Matrix matrix = new Matrix(); - // onTap the bit map - matrix.postScale(scaleWidth, scaleHeight); - // recreate the new Bitmap - Bitmap resizedBitmap = Bitmap.createBitmap(image, 0, 0, width, height, - matrix, false); - return resizedBitmap; - } - - public static boolean TrueIfBitmapBigger(Bitmap bitmap, int size) { - int sizeBitmap = (bitmap.getHeight() > bitmap.getWidth()) ? bitmap.getHeight() : bitmap.getWidth(); - return sizeBitmap > size; - } - - public static Bitmap GetRoundedBitmapWithBlurShadow(Bitmap original, int paddingTop, int paddingBottom, int paddingLeft, int paddingRight) { - int original_width = original.getWidth(); - int orginal_height = original.getHeight(); - int bitmap_width = original_width + paddingLeft + paddingRight; - int bitmap_height = orginal_height + paddingTop + paddingBottom; - Bitmap bitmap = Bitmap.createBitmap(bitmap_width, bitmap_height, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - Paint paint = new Paint(); - paint.setStyle(Paint.Style.FILL); - //paint.setAlpha(60); - // canvas.drawRect(0,0,bitmap_width,bitmap_height,paint); - paint.setAntiAlias(true); - canvas.drawBitmap(original, paddingLeft, paddingTop, paint); - Bitmap blurred_bitmap = getBlurredWithGoodPerformance(bitmap, 1, 6, 4); - canvas.setBitmap(null); - bitmap.recycle(); - return blurred_bitmap; - } - - // Activity. - // | Original bitmap. - // | | To make the blur background, the original must to padding. - // | | | | | | - // V V V V V V - public static Bitmap GetRoundedBitmapWithBlurShadow(Context context, Bitmap original, int paddingTop, int paddingBottom, int paddingLeft, int paddingRight, - int TopBack // this value makes the overview bitmap is higher or belower the background. - , int alphaBlurBackground // this is the alpha of the background Bitmap, you need A number between 0 -> 255, the value recommend is 180. - , int valueBlurBackground // this is the value used to blur the background Bitmap, the recommended one is 12. - , int valueSaturationBlurBackground // this is the value used to background Bitmap more colorful, if valueBlur is 12, the valudeSaturation should be 2. - ) { - int original_width = original.getWidth(); - int orginal_height = original.getHeight(); - int bitmap_width = original_width + paddingLeft + paddingRight; - int bitmap_height = orginal_height + paddingTop + paddingBottom; - Bitmap bitmap = Bitmap.createBitmap(bitmap_width, bitmap_height, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - Paint paint = new Paint(); - paint.setStyle(Paint.Style.FILL); - paint.setAntiAlias(true); - canvas.drawBitmap(original, paddingLeft, paddingTop, paint); - Bitmap blurred_bitmap = getBlurredWithGoodPerformance(context, bitmap, 1, valueBlurBackground, valueSaturationBlurBackground); - // Bitmap blurred_bitmap= getBlurredWithGoodPerformance(context, bitmap,1,15,3); - Bitmap end_bitmap = Bitmap.createBitmap(bitmap_width, bitmap_height, Bitmap.Config.ARGB_8888); - canvas.setBitmap(end_bitmap); - paint.setAlpha(alphaBlurBackground); - - canvas.drawBitmap(blurred_bitmap, new Rect(0, 0, blurred_bitmap.getWidth(), blurred_bitmap.getHeight()), new Rect(0, 0, bitmap_width, bitmap_height), paint); - paint.setAlpha(255); - - canvas.drawBitmap(bitmap, 0, TopBack, paint); // drawVisualWave cái lớn - canvas.setBitmap(null); - blurred_bitmap.recycle(); - bitmap.recycle(); - return end_bitmap; - } - - public static void setBitmapforImageView(ImageView imv, Bitmap apply) { - Bitmap old = ((BitmapDrawable) imv.getDrawable()).getBitmap(); - imv.setImageBitmap(apply); - if (old != null) - old.recycle(); - } - - public static Bitmap getBlurredWithGoodPerformance(Bitmap bitmap, int scale, int radius, int saturation) { - BitmapFactory.Options options = new BitmapFactory.Options(); - Bitmap bitmap1 = getResizedBitmap(bitmap, 50, 50); - Bitmap updateSatBitmap = updateSat(bitmap1, saturation); - Bitmap blurredBitmap = FastBlurSupportAlpha(updateSatBitmap, scale, radius); - - updateSatBitmap.recycle(); - bitmap1.recycle(); - return blurredBitmap; - } - - static public Path RoundedRect(float left, float top, float right, float bottom, float rx, float ry, boolean conformToOriginalPost) { - Path path = new Path(); - if (rx < 0) rx = 0; - if (ry < 0) ry = 0; - float width = right - left; - float height = bottom - top; - if (rx > width / 2) rx = width / 2; - if (ry > height / 2) ry = height / 2; - float widthMinusCorners = (width - (2 * rx)); // do dai phan "thang" cua chieu rong - float heightMinusCorners = (height - (2 * ry)); // do dai phan "thang" cua chieu dai - - path.moveTo(right, top + ry); // bat dau tu day - path.rQuadTo(0, -ry, -rx, -ry);//y-right corner - path.rLineTo(-widthMinusCorners, 0); - path.rQuadTo(-rx, 0, -rx, ry); //y-x corner - path.rLineTo(0, heightMinusCorners); - - if (conformToOriginalPost) { - path.rLineTo(0, ry); - path.rLineTo(width, 0); - path.rLineTo(0, -ry); + if (i > 0) { + rinsum += sir[0]; + ginsum += sir[1]; + binsum += sir[2]; + ainsum += sir[3]; } else { - - path.rQuadTo(0, ry, rx, ry);//bottom-x corner - path.rLineTo(widthMinusCorners, 0); - path.rQuadTo(rx, 0, rx, -ry); //bottom-right corner + routsum += sir[0]; + goutsum += sir[1]; + boutsum += sir[2]; + aoutsum += sir[3]; } - path.rLineTo(0, -heightMinusCorners); - - path.close();//Given close, last lineto can be removed. - - return path; - } - - public static int mixTwoColors(int color1, int color2, float amount) { - final byte ALPHA_CHANNEL = 24; - final byte RED_CHANNEL = 16; - final byte GREEN_CHANNEL = 8; - final byte BLUE_CHANNEL = 0; - - final float inverseAmount = 1.0f - amount; - - int a = ((int) (((float) (color1 >> ALPHA_CHANNEL & 0xff) * amount) + - ((float) (color2 >> ALPHA_CHANNEL & 0xff) * inverseAmount))) & 0xff; - int r = ((int) (((float) (color1 >> RED_CHANNEL & 0xff) * amount) + - ((float) (color2 >> RED_CHANNEL & 0xff) * inverseAmount))) & 0xff; - int g = ((int) (((float) (color1 >> GREEN_CHANNEL & 0xff) * amount) + - ((float) (color2 >> GREEN_CHANNEL & 0xff) * inverseAmount))) & 0xff; - int b = ((int) (((float) (color1 & 0xff) * amount) + - ((float) (color2 & 0xff) * inverseAmount))) & 0xff; - - return a << ALPHA_CHANNEL | r << RED_CHANNEL | g << GREEN_CHANNEL | b << BLUE_CHANNEL; - } - - public static Bitmap getBlurredWithGoodPerformance(Context context, Bitmap bitmap, int scale, int radius, float saturation) { - Bitmap bitmap1 = getResizedBitmap(bitmap, 150, 150); - Bitmap updateSatBimap = updateSat(bitmap1, saturation); - Bitmap blurredBitmap = BlurBitmapWithRenderScript(context, updateSatBimap, radius); - updateSatBimap.recycle(); - bitmap1.recycle(); - return blurredBitmap; - } - - public static Bitmap getBlurredBimapWithRenderScript(Context context, Bitmap bitmapOriginal, float radius) { - //define this only once if blurring multiple times - RenderScript rs = RenderScript.create(context); - -//this will blur the bitmapOriginal with A radius of 8 and save it in bitmapOriginal - final Allocation input = Allocation.createFromBitmap(rs, bitmapOriginal); //use this constructor for best performance, because it uses USAGE_SHARED mode which reuses memory - final Allocation output = Allocation.createTyped(rs, input.getType()); - final ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs)); - script.setRadius(radius); - script.setInput(input); - script.forEach(output); - output.copyTo(bitmapOriginal); - return bitmapOriginal; - } - - public static Bitmap BlurBitmapWithRenderScript(Context context, Bitmap bitmap, float radius) { - //Let's create an empty bitmap with the same size of the bitmap we want to blur - Bitmap outBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); - - //Instantiate A new Renderscript - RenderScript rs = RenderScript.create(context); - - //Create an Intrinsic Blur Script using the Renderscript - ScriptIntrinsicBlur blurScript = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs)); - - //Create the Allocations (in/out) with the Renderscript and the in/out bitmaps - Allocation allIn = Allocation.createFromBitmap(rs, bitmap); - Allocation allOut = Allocation.createFromBitmap(rs, outBitmap); - //Set the radius of the blur - blurScript.setRadius(radius); - - //Perform the Renderscript - blurScript.setInput(allIn); - blurScript.forEach(allOut); - - //Copy the final bitmap created by the out Allocation to the outBitmap - allOut.copyTo(outBitmap); - - //recycle the original bitmap - - //After finishing everything, we destroy the Renderscript. - rs.destroy(); - - return outBitmap; - - - } - - - public static Drawable covertBitmapToDrawable(Context context, Bitmap bitmap) { - Drawable d = new BitmapDrawable(context.getResources(), bitmap); - return d; - } - - public static Bitmap convertDrawableToBitmap(Drawable drawable) { - if (drawable instanceof BitmapDrawable) { - return ((BitmapDrawable) drawable).getBitmap(); + if (i < hm) { + yp += w; } - Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), - drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); - drawable.draw(canvas); - return bitmap; - } + } + yi = x; + stackpointer = radius; + for (y = 0; y < h; y++) { + pix[yi] = (dv[asum] << 24) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum]; - public static Bitmap changeBitmapColor(Bitmap sourceBitmap, int color) { - Bitmap resultBitmap = sourceBitmap.copy(sourceBitmap.getConfig(), true); - Paint paint = new Paint(); - ColorFilter filter = new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN); - paint.setColorFilter(filter); - Canvas canvas = new Canvas(resultBitmap); - canvas.drawBitmap(resultBitmap, 0, 0, paint); - return resultBitmap; - } + rsum -= routsum; + gsum -= goutsum; + bsum -= boutsum; + asum -= aoutsum; - /** - * @param mode - * @return 0 : CLEAR - *
1 : SRC - *
2 : DST - *
3 : SRC_OVER - *
4 : DST_OVER - *
5 : SRC_IN - *
6 : DST_IN - *
7 : SRC_OUT - *
8 : DST_OUT - *
9 : SRC_ATOP - *
10 : DST_ATOP - *
11 : XOR - *
12 : ADD - *
13 : MULTIPLY - *
14 : SCREEN - *
15 : OVERLAY - *
16 : DARKEN - *
17 : LIGHTEN - */ - public static PorterDuff.Mode getPorterMode(int mode) { - switch (mode) { - default: - case 0: - return PorterDuff.Mode.CLEAR; - case 1: - return PorterDuff.Mode.SRC; - case 2: - return PorterDuff.Mode.DST; - case 3: - return PorterDuff.Mode.SRC_OVER; - case 4: - return PorterDuff.Mode.DST_OVER; - case 5: - return PorterDuff.Mode.SRC_IN; - case 6: - return PorterDuff.Mode.DST_IN; - case 7: - return PorterDuff.Mode.SRC_OUT; - case 8: - return PorterDuff.Mode.DST_OUT; - case 9: - return PorterDuff.Mode.SRC_ATOP; - case 10: - return PorterDuff.Mode.DST_ATOP; - case 11: - return PorterDuff.Mode.XOR; - case 16: - return PorterDuff.Mode.DARKEN; - case 17: - return PorterDuff.Mode.LIGHTEN; - case 13: - return PorterDuff.Mode.MULTIPLY; - case 14: - return PorterDuff.Mode.SCREEN; - case 12: - return PorterDuff.Mode.ADD; - case 15: - return PorterDuff.Mode.OVERLAY; + stackstart = stackpointer - radius + div; + sir = stack[stackstart % div]; + + routsum -= sir[0]; + goutsum -= sir[1]; + boutsum -= sir[2]; + aoutsum -= sir[3]; + + if (x == 0) { + vmin[y] = Math.min(y + r1, hm) * w; } + p = x + vmin[y]; + + sir[0] = r[p]; + sir[1] = g[p]; + sir[2] = b[p]; + sir[3] = a[p]; + + rinsum += sir[0]; + ginsum += sir[1]; + binsum += sir[2]; + ainsum += sir[3]; + + rsum += rinsum; + gsum += ginsum; + bsum += binsum; + asum += ainsum; + + stackpointer = (stackpointer + 1) % div; + sir = stack[stackpointer]; + + routsum += sir[0]; + goutsum += sir[1]; + boutsum += sir[2]; + aoutsum += sir[3]; + + rinsum -= sir[0]; + ginsum -= sir[1]; + binsum -= sir[2]; + ainsum -= sir[3]; + + yi += w; + } } - public static void applyNewColor4Bitmap(Context context, int[] idBitmaps, ImageView[] imageViews, int color, float alpha) { - android.content.res.Resources resource = context.getResources(); - int size = idBitmaps.length; - Bitmap usingBitmap, resultBitmap; - for (int i = 0; i < size; i++) { - usingBitmap = BitmapFactory.decodeResource(resource, idBitmaps[i]); - resultBitmap = changeBitmapColor(usingBitmap, color); - imageViews[i].setImageBitmap(resultBitmap); - imageViews[i].setAlpha(alpha); + // Log.e("pix", w + " " + h + " " + pix.length); + bitmap.setPixels(pix, 0, w, 0, 0, w, h); + + return (bitmap); + } + + public static boolean PerceivedBrightness(int will_White, int[] c) { + double TBT = Math.sqrt(c[0] * c[0] * .241 + c[1] * c[1] * .691 + c[2] * c[2] * .068); + // Log.d("themee",TBT+""); + return !(TBT > will_White); + } + + public static int[] getAverageColorRGB(Bitmap bitmap) { + final int width = bitmap.getWidth(); + final int height = bitmap.getHeight(); + int size = width * height; + int pixelColor; + int r, g, b; + r = g = b = 0; + for (int x = 0; x < width; ++x) { + for (int y = 0; y < height; ++y) { + pixelColor = bitmap.getPixel(x, y); + if (pixelColor == 0) { + size--; + continue; } + r += Color.red(pixelColor); + g += Color.green(pixelColor); + b += Color.blue(pixelColor); + } + } + r /= size; + g /= size; + b /= size; + return new int[] {r, g, b}; + } + + public static Bitmap updateSat(Bitmap src, float settingSat) { + + int w = src.getWidth(); + int h = src.getHeight(); + + Bitmap bitmapResult = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); + Canvas canvasResult = new Canvas(bitmapResult); + Paint paint = new Paint(); + ColorMatrix colorMatrix = new ColorMatrix(); + colorMatrix.setSaturation(settingSat); + ColorMatrixColorFilter filter = new ColorMatrixColorFilter(colorMatrix); + paint.setColorFilter(filter); + canvasResult.drawBitmap(src, 0, 0, paint); + canvasResult.setBitmap(null); + canvasResult = null; + return bitmapResult; + } + + /** + * Stack Blur v1.0 from http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html Java + * Author: Mario Klingemann http://incubator.quasimondo.com + * + *

created Feburary 29, 2004 Android port : Yahel Bouaziz + * http://www.kayenko.com ported april 5th, 2012 + * + *

This is A compromise between Gaussian Blur and Box blur It creates much better looking blurs + * than Box Blur, but is 7x faster than my Gaussian Blur implementation. + * + *

I called it Stack Blur because this describes best how this filter works internally: it + * creates A kind of moving stack of colors whilst scanning through the image. Thereby it just has + * to add one new block of color to the right side of the stack and removeFromParent the leftmost + * color. The remaining colors on the topmost layer of the stack are either added on or reduced by + * one, depending on if they are on the right or on the x side of the stack. + * + *

If you are using this algorithm in your code please add the following line: Stack Blur + * Algorithm by Mario Klingemann + */ + public static Bitmap fastblur(Bitmap sentBitmap, float scale, int radius) { + + Bitmap afterscaleSentBitmap; + Bitmap bitmap; + if (scale != 1) { + int width = Math.round(sentBitmap.getWidth() * scale); // lấy chiều rộng làm tròn + int height = Math.round(sentBitmap.getHeight() * scale); // lấy chiều cao làm tròn + afterscaleSentBitmap = + Bitmap.createScaledBitmap(sentBitmap, width, height, false); // tạo bitmap scaled + bitmap = afterscaleSentBitmap.copy(afterscaleSentBitmap.getConfig(), true); + afterscaleSentBitmap.recycle(); + } else { + bitmap = sentBitmap.copy(sentBitmap.getConfig(), true); // đơn giản chỉ copy } - public static void applyNewColor4Bitmap(Context context, int idBitmap, ImageView applyView, int color, float alpha) { - - android.content.res.Resources resource = context.getResources(); - Bitmap usingBitmap = BitmapFactory.decodeResource(resource, idBitmap); - Bitmap resultBitmap = changeBitmapColor(usingBitmap, color); - applyView.setImageBitmap(resultBitmap); - applyView.setAlpha(alpha); - + if (radius < 1) { + return (sentBitmap.copy(sentBitmap.getConfig(), true)); } - public static Bitmap getBitmapFromView(View view) { - Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888); - Canvas c = new Canvas(bitmap); - view.layout(view.getLeft(), view.getTop(), view.getRight(), view.getBottom()); - view.draw(c); - return bitmap; + int w = bitmap.getWidth(); // w is the width of sample bitmap + int h = bitmap.getHeight(); // h is the height of sample bitmap + + int[] pix = new int[w * h]; // pix is the arrary of all bitmap pixel + + bitmap.getPixels(pix, 0, w, 0, 0, w, h); + + int wm = w - 1; + int hm = h - 1; + int wh = w * h; + int div = radius + radius + 1; + + int[] r = new int[wh]; + int[] g = new int[wh]; + int[] b = new int[wh]; + int rsum, gsum, bsum, x, y, i, p, yp, yi, yw; + int[] vmin = new int[Math.max(w, h)]; + + int divsum = (div + 1) >> 1; + divsum *= divsum; + int[] dv = new int[256 * divsum]; + for (i = 0; i < 256 * divsum; i++) { + dv[i] = (i / divsum); } - public static Bitmap getBitmapFromView(View view, int left, int top, int right, int bottom) { - Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888); - Canvas c = new Canvas(bitmap); - view.layout(left, top, right, bottom); - view.draw(c); - return bitmap; + yw = yi = 0; + + int[][] stack = new int[div][3]; + int stackpointer; + int stackstart; + int[] sir; + int rbs; + int r1 = radius + 1; + int routsum, goutsum, boutsum; + int rinsum, ginsum, binsum; + + for (y = 0; y < h; y++) { + rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; + for (i = -radius; i <= radius; i++) { + p = pix[yi + Math.min(wm, Math.max(i, 0))]; + sir = stack[i + radius]; + sir[0] = (p & 0xff0000) >> 16; + sir[1] = (p & 0x00ff00) >> 8; + sir[2] = (p & 0x0000ff); + rbs = r1 - Math.abs(i); + rsum += sir[0] * rbs; + gsum += sir[1] * rbs; + bsum += sir[2] * rbs; + if (i > 0) { + rinsum += sir[0]; + ginsum += sir[1]; + binsum += sir[2]; + } else { + routsum += sir[0]; + goutsum += sir[1]; + boutsum += sir[2]; + } + } + stackpointer = radius; + + for (x = 0; x < w; x++) { + + r[yi] = dv[rsum]; + g[yi] = dv[gsum]; + b[yi] = dv[bsum]; + + rsum -= routsum; + gsum -= goutsum; + bsum -= boutsum; + + stackstart = stackpointer - radius + div; + sir = stack[stackstart % div]; + + routsum -= sir[0]; + goutsum -= sir[1]; + boutsum -= sir[2]; + + if (y == 0) { + vmin[x] = Math.min(x + radius + 1, wm); + } + p = pix[yw + vmin[x]]; + + sir[0] = (p & 0xff0000) >> 16; + sir[1] = (p & 0x00ff00) >> 8; + sir[2] = (p & 0x0000ff); + + rinsum += sir[0]; + ginsum += sir[1]; + binsum += sir[2]; + + rsum += rinsum; + gsum += ginsum; + bsum += binsum; + + stackpointer = (stackpointer + 1) % div; + sir = stack[(stackpointer) % div]; + + routsum += sir[0]; + goutsum += sir[1]; + boutsum += sir[2]; + + rinsum -= sir[0]; + ginsum -= sir[1]; + binsum -= sir[2]; + + yi++; + } + yw += w; + } + for (x = 0; x < w; x++) { + rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; + yp = -radius * w; + for (i = -radius; i <= radius; i++) { + yi = Math.max(0, yp) + x; + + sir = stack[i + radius]; + + sir[0] = r[yi]; + sir[1] = g[yi]; + sir[2] = b[yi]; + + rbs = r1 - Math.abs(i); + + rsum += r[yi] * rbs; + gsum += g[yi] * rbs; + bsum += b[yi] * rbs; + + if (i > 0) { + rinsum += sir[0]; + ginsum += sir[1]; + binsum += sir[2]; + } else { + routsum += sir[0]; + goutsum += sir[1]; + boutsum += sir[2]; + } + + if (i < hm) { + yp += w; + } + } + yi = x; + stackpointer = radius; + for (y = 0; y < h; y++) { + // Preserve alpha channel: ( 0xff000000 & pix[yi] ) + pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum]; + + rsum -= routsum; + gsum -= goutsum; + bsum -= boutsum; + + stackstart = stackpointer - radius + div; + sir = stack[stackstart % div]; + + routsum -= sir[0]; + goutsum -= sir[1]; + boutsum -= sir[2]; + + if (x == 0) { + vmin[y] = Math.min(y + r1, hm) * w; + } + p = x + vmin[y]; + + sir[0] = r[p]; + sir[1] = g[p]; + sir[2] = b[p]; + + rinsum += sir[0]; + ginsum += sir[1]; + binsum += sir[2]; + + rsum += rinsum; + gsum += ginsum; + bsum += binsum; + + stackpointer = (stackpointer + 1) % div; + sir = stack[stackpointer]; + + routsum += sir[0]; + goutsum += sir[1]; + boutsum += sir[2]; + + rinsum -= sir[0]; + ginsum -= sir[1]; + binsum -= sir[2]; + + yi += w; + } } - public static Bitmap getBackgroundBitmapAViewWithParent(View childView, View parentView) { - int[] pos_child = new int[2]; - childView.getLocationOnScreen(pos_child); - return getBitmapFromView(parentView, pos_child[0], pos_child[1], parentView.getRight(), parentView.getBottom()); + bitmap.setPixels(pix, 0, w, 0, 0, w, h); + + return (bitmap); + } + + public static Bitmap getRoundedCornerBitmap(Bitmap bitmap, int pixels) { + Bitmap output = + Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(output); + + final int color = 0xff424242; + final Paint paint = new Paint(); + final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); + // final ScreenSize rectF = new ScreenSize(rect); + final float roundPx = pixels; + + paint.setAntiAlias(true); + canvas.drawARGB(0, 0, 0, 0); + paint.setColor(color); + // canvas.drawRoundRect(rectF, roundPx, roundPx, paint); + canvas.drawPath( + BitmapEditor.RoundedRect( + 0, 0, bitmap.getWidth(), bitmap.getHeight(), roundPx, roundPx, false), + paint); + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); + canvas.drawBitmap(bitmap, rect, rect, paint); + + return output; + } + + /** + * getResizedBitmap method is used to Resized the Image according to custom width and height + * + * @param image + * @param newHeight (new desired height) + * @param newWidth (new desired Width) + * @return image (new resized image) + */ + public static Bitmap getResizedBitmap(Bitmap image, int newHeight, int newWidth) { + int width = image.getWidth(); + int height = image.getHeight(); + float scaleWidth = ((float) newWidth) / width; + float scaleHeight = ((float) newHeight) / height; + // create A matrix for the manipulation + Matrix matrix = new Matrix(); + // onTap the bit map + matrix.postScale(scaleWidth, scaleHeight); + // recreate the new Bitmap + Bitmap resizedBitmap = Bitmap.createBitmap(image, 0, 0, width, height, matrix, false); + return resizedBitmap; + } + + public static boolean TrueIfBitmapBigger(Bitmap bitmap, int size) { + int sizeBitmap = + (bitmap.getHeight() > bitmap.getWidth()) ? bitmap.getHeight() : bitmap.getWidth(); + return sizeBitmap > size; + } + + public static Bitmap GetRoundedBitmapWithBlurShadow( + Bitmap original, int paddingTop, int paddingBottom, int paddingLeft, int paddingRight) { + int original_width = original.getWidth(); + int orginal_height = original.getHeight(); + int bitmap_width = original_width + paddingLeft + paddingRight; + int bitmap_height = orginal_height + paddingTop + paddingBottom; + Bitmap bitmap = Bitmap.createBitmap(bitmap_width, bitmap_height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + Paint paint = new Paint(); + paint.setStyle(Paint.Style.FILL); + // paint.setAlpha(60); + // canvas.drawRect(0,0,bitmap_width,bitmap_height,paint); + paint.setAntiAlias(true); + canvas.drawBitmap(original, paddingLeft, paddingTop, paint); + Bitmap blurred_bitmap = getBlurredWithGoodPerformance(bitmap, 1, 6, 4); + canvas.setBitmap(null); + bitmap.recycle(); + return blurred_bitmap; + } + + // Activity. + // | + // Original bitmap. + // | + // | To make the blur background, the original must to padding. + // | + // | | | | + // | + // V + // V V V V + // V + public static Bitmap GetRoundedBitmapWithBlurShadow( + Context context, + Bitmap original, + int paddingTop, + int paddingBottom, + int paddingLeft, + int paddingRight, + int TopBack // this value makes the overview bitmap is higher or belower the background. + , + int alphaBlurBackground // this is the alpha of the background Bitmap, you need A number + // between 0 -> 255, the value recommend is 180. + , + int valueBlurBackground // this is the value used to blur the background Bitmap, the + // recommended one is 12. + , + int valueSaturationBlurBackground // this is the value used to background Bitmap more + // colorful, if valueBlur is 12, the valudeSaturation should + // be 2. + ) { + int original_width = original.getWidth(); + int orginal_height = original.getHeight(); + int bitmap_width = original_width + paddingLeft + paddingRight; + int bitmap_height = orginal_height + paddingTop + paddingBottom; + Bitmap bitmap = Bitmap.createBitmap(bitmap_width, bitmap_height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + Paint paint = new Paint(); + paint.setStyle(Paint.Style.FILL); + paint.setAntiAlias(true); + canvas.drawBitmap(original, paddingLeft, paddingTop, paint); + Bitmap blurred_bitmap = + getBlurredWithGoodPerformance( + context, bitmap, 1, valueBlurBackground, valueSaturationBlurBackground); + // Bitmap blurred_bitmap= getBlurredWithGoodPerformance(context, bitmap,1,15,3); + Bitmap end_bitmap = Bitmap.createBitmap(bitmap_width, bitmap_height, Bitmap.Config.ARGB_8888); + canvas.setBitmap(end_bitmap); + paint.setAlpha(alphaBlurBackground); + + canvas.drawBitmap( + blurred_bitmap, + new Rect(0, 0, blurred_bitmap.getWidth(), blurred_bitmap.getHeight()), + new Rect(0, 0, bitmap_width, bitmap_height), + paint); + paint.setAlpha(255); + + canvas.drawBitmap(bitmap, 0, TopBack, paint); // drawVisualWave cái lớn + canvas.setBitmap(null); + blurred_bitmap.recycle(); + bitmap.recycle(); + return end_bitmap; + } + + public static void setBitmapforImageView(ImageView imv, Bitmap apply) { + Bitmap old = ((BitmapDrawable) imv.getDrawable()).getBitmap(); + imv.setImageBitmap(apply); + if (old != null) old.recycle(); + } + + public static Bitmap getBlurredWithGoodPerformance( + Bitmap bitmap, int scale, int radius, int saturation) { + BitmapFactory.Options options = new BitmapFactory.Options(); + Bitmap bitmap1 = getResizedBitmap(bitmap, 50, 50); + Bitmap updateSatBitmap = updateSat(bitmap1, saturation); + Bitmap blurredBitmap = FastBlurSupportAlpha(updateSatBitmap, scale, radius); + + updateSatBitmap.recycle(); + bitmap1.recycle(); + return blurredBitmap; + } + + public static Path RoundedRect( + float left, + float top, + float right, + float bottom, + float rx, + float ry, + boolean conformToOriginalPost) { + Path path = new Path(); + if (rx < 0) rx = 0; + if (ry < 0) ry = 0; + float width = right - left; + float height = bottom - top; + if (rx > width / 2) rx = width / 2; + if (ry > height / 2) ry = height / 2; + float widthMinusCorners = (width - (2 * rx)); // do dai phan "thang" cua chieu rong + float heightMinusCorners = (height - (2 * ry)); // do dai phan "thang" cua chieu dai + + path.moveTo(right, top + ry); // bat dau tu day + path.rQuadTo(0, -ry, -rx, -ry); // y-right corner + path.rLineTo(-widthMinusCorners, 0); + path.rQuadTo(-rx, 0, -rx, ry); // y-x corner + path.rLineTo(0, heightMinusCorners); + + if (conformToOriginalPost) { + path.rLineTo(0, ry); + path.rLineTo(width, 0); + path.rLineTo(0, -ry); + } else { + + path.rQuadTo(0, ry, rx, ry); // bottom-x corner + path.rLineTo(widthMinusCorners, 0); + path.rQuadTo(rx, 0, rx, -ry); // bottom-right corner } - public static Bitmap getBackgroundBlurAViewWithParent(Activity activity, View childView, View parentView) { - Bitmap b1 = getBackgroundBitmapAViewWithParent(childView, parentView); - Bitmap b2 = getBlurredWithGoodPerformance(activity, b1, 1, 8, 2); - b1.recycle(); - return b2; + path.rLineTo(0, -heightMinusCorners); + + path.close(); // Given close, last lineto can be removed. + + return path; + } + + public static int mixTwoColors(int color1, int color2, float amount) { + final byte ALPHA_CHANNEL = 24; + final byte RED_CHANNEL = 16; + final byte GREEN_CHANNEL = 8; + final byte BLUE_CHANNEL = 0; + + final float inverseAmount = 1.0f - amount; + + int a = + ((int) + (((float) (color1 >> ALPHA_CHANNEL & 0xff) * amount) + + ((float) (color2 >> ALPHA_CHANNEL & 0xff) * inverseAmount))) + & 0xff; + int r = + ((int) + (((float) (color1 >> RED_CHANNEL & 0xff) * amount) + + ((float) (color2 >> RED_CHANNEL & 0xff) * inverseAmount))) + & 0xff; + int g = + ((int) + (((float) (color1 >> GREEN_CHANNEL & 0xff) * amount) + + ((float) (color2 >> GREEN_CHANNEL & 0xff) * inverseAmount))) + & 0xff; + int b = + ((int) (((float) (color1 & 0xff) * amount) + ((float) (color2 & 0xff) * inverseAmount))) + & 0xff; + + return a << ALPHA_CHANNEL | r << RED_CHANNEL | g << GREEN_CHANNEL | b << BLUE_CHANNEL; + } + + public static Bitmap getBlurredWithGoodPerformance( + Context context, Bitmap bitmap, int scale, int radius, float saturation) { + Bitmap bitmap1 = getResizedBitmap(bitmap, 150, 150); + Bitmap updateSatBimap = updateSat(bitmap1, saturation); + Bitmap blurredBitmap = BlurBitmapWithRenderScript(context, updateSatBimap, radius); + updateSatBimap.recycle(); + bitmap1.recycle(); + return blurredBitmap; + } + + public static Bitmap getBlurredBimapWithRenderScript( + Context context, Bitmap bitmapOriginal, float radius) { + // define this only once if blurring multiple times + RenderScript rs = RenderScript.create(context); + + // this will blur the bitmapOriginal with A radius of 8 and save it in bitmapOriginal + final Allocation input = + Allocation.createFromBitmap( + rs, bitmapOriginal); // use this constructor for best performance, because it uses + // USAGE_SHARED mode which reuses memory + final Allocation output = Allocation.createTyped(rs, input.getType()); + final ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs)); + script.setRadius(radius); + script.setInput(input); + script.forEach(output); + output.copyTo(bitmapOriginal); + return bitmapOriginal; + } + + public static Bitmap BlurBitmapWithRenderScript(Context context, Bitmap bitmap, float radius) { + // Let's create an empty bitmap with the same size of the bitmap we want to blur + Bitmap outBitmap = + Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); + + // Instantiate A new Renderscript + RenderScript rs = RenderScript.create(context); + + // Create an Intrinsic Blur Script using the Renderscript + ScriptIntrinsicBlur blurScript = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs)); + + // Create the Allocations (in/out) with the Renderscript and the in/out bitmaps + Allocation allIn = Allocation.createFromBitmap(rs, bitmap); + Allocation allOut = Allocation.createFromBitmap(rs, outBitmap); + // Set the radius of the blur + blurScript.setRadius(radius); + + // Perform the Renderscript + blurScript.setInput(allIn); + blurScript.forEach(allOut); + + // Copy the final bitmap created by the out Allocation to the outBitmap + allOut.copyTo(outBitmap); + + // recycle the original bitmap + + // After finishing everything, we destroy the Renderscript. + rs.destroy(); + + return outBitmap; + } + + public static Drawable covertBitmapToDrawable(Context context, Bitmap bitmap) { + Drawable d = new BitmapDrawable(context.getResources(), bitmap); + return d; + } + + public static Bitmap convertDrawableToBitmap(Drawable drawable) { + if (drawable instanceof BitmapDrawable) { + return ((BitmapDrawable) drawable).getBitmap(); } -} \ No newline at end of file + Bitmap bitmap = + Bitmap.createBitmap( + drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + return bitmap; + } + + public static Bitmap changeBitmapColor(Bitmap sourceBitmap, int color) { + Bitmap resultBitmap = sourceBitmap.copy(sourceBitmap.getConfig(), true); + Paint paint = new Paint(); + ColorFilter filter = new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN); + paint.setColorFilter(filter); + Canvas canvas = new Canvas(resultBitmap); + canvas.drawBitmap(resultBitmap, 0, 0, paint); + return resultBitmap; + } + + /** + * @param mode + * @return 0 : CLEAR
+ * 1 : SRC
+ * 2 : DST
+ * 3 : SRC_OVER
+ * 4 : DST_OVER
+ * 5 : SRC_IN
+ * 6 : DST_IN
+ * 7 : SRC_OUT
+ * 8 : DST_OUT
+ * 9 : SRC_ATOP
+ * 10 : DST_ATOP
+ * 11 : XOR
+ * 12 : ADD
+ * 13 : MULTIPLY
+ * 14 : SCREEN
+ * 15 : OVERLAY
+ * 16 : DARKEN
+ * 17 : LIGHTEN + */ + public static PorterDuff.Mode getPorterMode(int mode) { + switch (mode) { + default: + case 0: + return PorterDuff.Mode.CLEAR; + case 1: + return PorterDuff.Mode.SRC; + case 2: + return PorterDuff.Mode.DST; + case 3: + return PorterDuff.Mode.SRC_OVER; + case 4: + return PorterDuff.Mode.DST_OVER; + case 5: + return PorterDuff.Mode.SRC_IN; + case 6: + return PorterDuff.Mode.DST_IN; + case 7: + return PorterDuff.Mode.SRC_OUT; + case 8: + return PorterDuff.Mode.DST_OUT; + case 9: + return PorterDuff.Mode.SRC_ATOP; + case 10: + return PorterDuff.Mode.DST_ATOP; + case 11: + return PorterDuff.Mode.XOR; + case 16: + return PorterDuff.Mode.DARKEN; + case 17: + return PorterDuff.Mode.LIGHTEN; + case 13: + return PorterDuff.Mode.MULTIPLY; + case 14: + return PorterDuff.Mode.SCREEN; + case 12: + return PorterDuff.Mode.ADD; + case 15: + return PorterDuff.Mode.OVERLAY; + } + } + + public static void applyNewColor4Bitmap( + Context context, int[] idBitmaps, ImageView[] imageViews, int color, float alpha) { + android.content.res.Resources resource = context.getResources(); + int size = idBitmaps.length; + Bitmap usingBitmap, resultBitmap; + for (int i = 0; i < size; i++) { + usingBitmap = BitmapFactory.decodeResource(resource, idBitmaps[i]); + resultBitmap = changeBitmapColor(usingBitmap, color); + imageViews[i].setImageBitmap(resultBitmap); + imageViews[i].setAlpha(alpha); + } + } + + public static void applyNewColor4Bitmap( + Context context, int idBitmap, ImageView applyView, int color, float alpha) { + + android.content.res.Resources resource = context.getResources(); + Bitmap usingBitmap = BitmapFactory.decodeResource(resource, idBitmap); + Bitmap resultBitmap = changeBitmapColor(usingBitmap, color); + applyView.setImageBitmap(resultBitmap); + applyView.setAlpha(alpha); + } + + public static Bitmap getBitmapFromView(View view) { + Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(bitmap); + view.layout(view.getLeft(), view.getTop(), view.getRight(), view.getBottom()); + view.draw(c); + return bitmap; + } + + public static Bitmap getBitmapFromView(View view, int left, int top, int right, int bottom) { + Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(bitmap); + view.layout(left, top, right, bottom); + view.draw(c); + return bitmap; + } + + public static Bitmap getBackgroundBitmapAViewWithParent(View childView, View parentView) { + int[] pos_child = new int[2]; + childView.getLocationOnScreen(pos_child); + return getBitmapFromView( + parentView, pos_child[0], pos_child[1], parentView.getRight(), parentView.getBottom()); + } + + public static Bitmap getBackgroundBlurAViewWithParent( + Activity activity, View childView, View parentView) { + Bitmap b1 = getBackgroundBitmapAViewWithParent(childView, parentView); + Bitmap b2 = getBlurredWithGoodPerformance(activity, b1, 1, 8, 2); + b1.recycle(); + return b2; + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/util/CalendarUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/CalendarUtil.java index e126a37c..b396df9a 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/CalendarUtil.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/CalendarUtil.java @@ -17,126 +17,124 @@ package code.name.monkey.retromusic.util; import java.util.Calendar; import java.util.GregorianCalendar; -/** - * @author Eugene Cheung (arkon) - */ +/** @author Eugene Cheung (arkon) */ public class CalendarUtil { - private static final long MS_PER_MINUTE = 60 * 1000; - private static final long MS_PER_DAY = 24 * 60 * MS_PER_MINUTE; + private static final long MS_PER_MINUTE = 60 * 1000; + private static final long MS_PER_DAY = 24 * 60 * MS_PER_MINUTE; - private Calendar calendar; + private Calendar calendar; - public CalendarUtil() { - this.calendar = Calendar.getInstance(); + public CalendarUtil() { + this.calendar = Calendar.getInstance(); + } + + /** + * Returns the time elapsed so far today in milliseconds. + * + * @return Time elapsed today in milliseconds. + */ + public long getElapsedToday() { + // Time elapsed so far today + return (calendar.get(Calendar.HOUR_OF_DAY) * 60 + calendar.get(Calendar.MINUTE)) * MS_PER_MINUTE + + calendar.get(Calendar.SECOND) * 1000 + + calendar.get(Calendar.MILLISECOND); + } + + /** + * Returns the time elapsed so far this week in milliseconds. + * + * @return Time elapsed this week in milliseconds. + */ + public long getElapsedWeek() { + // Today + days passed this week + long elapsed = getElapsedToday(); + + final int passedWeekdays = + calendar.get(Calendar.DAY_OF_WEEK) - 1 - calendar.getFirstDayOfWeek(); + if (passedWeekdays > 0) { + elapsed += passedWeekdays * MS_PER_DAY; } - /** - * Returns the time elapsed so far today in milliseconds. - * - * @return Time elapsed today in milliseconds. - */ - public long getElapsedToday() { - // Time elapsed so far today - return (calendar.get(Calendar.HOUR_OF_DAY) * 60 + calendar.get(Calendar.MINUTE)) * MS_PER_MINUTE - + calendar.get(Calendar.SECOND) * 1000 - + calendar.get(Calendar.MILLISECOND); + return elapsed; + } + + /** + * Returns the time elapsed so far this month in milliseconds. + * + * @return Time elapsed this month in milliseconds. + */ + public long getElapsedMonth() { + // Today + rest of this month + return getElapsedToday() + ((calendar.get(Calendar.DAY_OF_MONTH) - 1) * MS_PER_DAY); + } + + /** + * Returns the time elapsed so far this month and the last numMonths months in milliseconds. + * + * @param numMonths Additional number of months prior to the current month to calculate. + * @return Time elapsed this month and the last numMonths months in milliseconds. + */ + public long getElapsedMonths(int numMonths) { + // Today + rest of this month + long elapsed = getElapsedMonth(); + + // Previous numMonths months + int month = calendar.get(Calendar.MONTH); + int year = calendar.get(Calendar.YEAR); + for (int i = 0; i < numMonths; i++) { + month--; + + if (month < Calendar.JANUARY) { + month = Calendar.DECEMBER; + year--; + } + + elapsed += getDaysInMonth(month) * MS_PER_DAY; } - /** - * Returns the time elapsed so far this week in milliseconds. - * - * @return Time elapsed this week in milliseconds. - */ - public long getElapsedWeek() { - // Today + days passed this week - long elapsed = getElapsedToday(); + return elapsed; + } - final int passedWeekdays = calendar.get(Calendar.DAY_OF_WEEK) - 1 - calendar.getFirstDayOfWeek(); - if (passedWeekdays > 0) { - elapsed += passedWeekdays * MS_PER_DAY; - } + /** + * Returns the time elapsed so far this year in milliseconds. + * + * @return Time elapsed this year in milliseconds. + */ + public long getElapsedYear() { + // Today + rest of this month + previous months until January + long elapsed = getElapsedMonth(); - return elapsed; + int month = calendar.get(Calendar.MONTH) - 1; + int year = calendar.get(Calendar.YEAR); + while (month > Calendar.JANUARY) { + elapsed += getDaysInMonth(month) * MS_PER_DAY; + + month--; } - /** - * Returns the time elapsed so far this month in milliseconds. - * - * @return Time elapsed this month in milliseconds. - */ - public long getElapsedMonth() { - // Today + rest of this month - return getElapsedToday() + - ((calendar.get(Calendar.DAY_OF_MONTH) - 1) * MS_PER_DAY); - } + return elapsed; + } - /** - * Returns the time elapsed so far this month and the last numMonths months in milliseconds. - * - * @param numMonths Additional number of months prior to the current month to calculate. - * @return Time elapsed this month and the last numMonths months in milliseconds. - */ - public long getElapsedMonths(int numMonths) { - // Today + rest of this month - long elapsed = getElapsedMonth(); + /** + * Gets the number of days for the given month in the given year. + * + * @param month The month (1 - 12). + * @return The days in that month/year. + */ + private int getDaysInMonth(int month) { + final Calendar monthCal = new GregorianCalendar(calendar.get(Calendar.YEAR), month, 1); + return monthCal.getActualMaximum(Calendar.DAY_OF_MONTH); + } - // Previous numMonths months - int month = calendar.get(Calendar.MONTH); - int year = calendar.get(Calendar.YEAR); - for (int i = 0; i < numMonths; i++) { - month--; + /** + * Returns the time elapsed so far last N days in milliseconds. + * + * @return Time elapsed since N days in milliseconds. + */ + public long getElapsedDays(int numDays) { + long elapsed = getElapsedToday(); + elapsed += numDays * MS_PER_DAY; - if (month < Calendar.JANUARY) { - month = Calendar.DECEMBER; - year--; - } - - elapsed += getDaysInMonth(month) * MS_PER_DAY; - } - - return elapsed; - } - - /** - * Returns the time elapsed so far this year in milliseconds. - * - * @return Time elapsed this year in milliseconds. - */ - public long getElapsedYear() { - // Today + rest of this month + previous months until January - long elapsed = getElapsedMonth(); - - int month = calendar.get(Calendar.MONTH) - 1; - int year = calendar.get(Calendar.YEAR); - while (month > Calendar.JANUARY) { - elapsed += getDaysInMonth(month) * MS_PER_DAY; - - month--; - } - - return elapsed; - } - - /** - * Gets the number of days for the given month in the given year. - * - * @param month The month (1 - 12). - * @return The days in that month/year. - */ - private int getDaysInMonth(int month) { - final Calendar monthCal = new GregorianCalendar(calendar.get(Calendar.YEAR), month, 1); - return monthCal.getActualMaximum(Calendar.DAY_OF_MONTH); - } - - /** - * Returns the time elapsed so far last N days in milliseconds. - * - * @return Time elapsed since N days in milliseconds. - */ - public long getElapsedDays(int numDays) { - long elapsed = getElapsedToday(); - elapsed += numDays * MS_PER_DAY; - - return elapsed; - } + return elapsed; + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/util/ColorUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/ColorUtil.java index e4133392..0f94d96a 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/ColorUtil.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/ColorUtil.java @@ -1,58 +1,55 @@ package code.name.monkey.retromusic.util; import android.graphics.Bitmap; - import androidx.annotation.ColorInt; import androidx.annotation.Nullable; import androidx.palette.graphics.Palette; - import java.util.Collections; import java.util.Comparator; public class ColorUtil { - @Nullable - public static Palette generatePalette(Bitmap bitmap) { - if (bitmap == null) return null; - return Palette.from(bitmap).generate(); + @Nullable + public static Palette generatePalette(Bitmap bitmap) { + if (bitmap == null) return null; + return Palette.from(bitmap).generate(); + } + + @ColorInt + public static int getColor(@Nullable Palette palette, int fallback) { + if (palette != null) { + if (palette.getVibrantSwatch() != null) { + return palette.getVibrantSwatch().getRgb(); + } else if (palette.getMutedSwatch() != null) { + return palette.getMutedSwatch().getRgb(); + } else if (palette.getDarkVibrantSwatch() != null) { + return palette.getDarkVibrantSwatch().getRgb(); + } else if (palette.getDarkMutedSwatch() != null) { + return palette.getDarkMutedSwatch().getRgb(); + } else if (palette.getLightVibrantSwatch() != null) { + return palette.getLightVibrantSwatch().getRgb(); + } else if (palette.getLightMutedSwatch() != null) { + return palette.getLightMutedSwatch().getRgb(); + } else if (!palette.getSwatches().isEmpty()) { + return Collections.max(palette.getSwatches(), SwatchComparator.getInstance()).getRgb(); + } + } + return fallback; + } + + private static class SwatchComparator implements Comparator { + private static SwatchComparator sInstance; + + static SwatchComparator getInstance() { + if (sInstance == null) { + sInstance = new SwatchComparator(); + } + return sInstance; } - @ColorInt - public static int getColor(@Nullable Palette palette, int fallback) { - if (palette != null) { - if (palette.getVibrantSwatch() != null) { - return palette.getVibrantSwatch().getRgb(); - } else if (palette.getMutedSwatch() != null) { - return palette.getMutedSwatch().getRgb(); - } else if (palette.getDarkVibrantSwatch() != null) { - return palette.getDarkVibrantSwatch().getRgb(); - } else if (palette.getDarkMutedSwatch() != null) { - return palette.getDarkMutedSwatch().getRgb(); - } else if (palette.getLightVibrantSwatch() != null) { - return palette.getLightVibrantSwatch().getRgb(); - } else if (palette.getLightMutedSwatch() != null) { - return palette.getLightMutedSwatch().getRgb(); - } else if (!palette.getSwatches().isEmpty()) { - return Collections.max(palette.getSwatches(), SwatchComparator.getInstance()).getRgb(); - } - } - return fallback; + @Override + public int compare(Palette.Swatch lhs, Palette.Swatch rhs) { + return lhs.getPopulation() - rhs.getPopulation(); } - - private static class SwatchComparator implements Comparator { - private static SwatchComparator sInstance; - - static SwatchComparator getInstance() { - if (sInstance == null) { - sInstance = new SwatchComparator(); - } - return sInstance; - } - - @Override - public int compare(Palette.Swatch lhs, Palette.Swatch rhs) { - return lhs.getPopulation() - rhs.getPopulation(); - } - } - -} \ No newline at end of file + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/util/Compressor.java b/app/src/main/java/code/name/monkey/retromusic/util/Compressor.java index 312a1f30..21050b74 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/Compressor.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/Compressor.java @@ -16,63 +16,64 @@ package code.name.monkey.retromusic.util; import android.content.Context; import android.graphics.Bitmap; - import java.io.File; import java.io.IOException; /** - * Created on : June 18, 2016 - * Author : zetbaitsu - * Name : Zetra - * GitHub : https://github.com/zetbaitsu + * Created on : June 18, 2016 Author : zetbaitsu Name : Zetra GitHub : https://github.com/zetbaitsu */ public class Compressor { - //max width and height values of the compressed image is taken as 612x816 - private int maxWidth = 612; - private int maxHeight = 816; - private Bitmap.CompressFormat compressFormat = Bitmap.CompressFormat.JPEG; - private int quality = 80; - private String destinationDirectoryPath; + // max width and height values of the compressed image is taken as 612x816 + private int maxWidth = 612; + private int maxHeight = 816; + private Bitmap.CompressFormat compressFormat = Bitmap.CompressFormat.JPEG; + private int quality = 80; + private String destinationDirectoryPath; - public Compressor(Context context) { - destinationDirectoryPath = context.getCacheDir().getPath() + File.separator + "images"; - } + public Compressor(Context context) { + destinationDirectoryPath = context.getCacheDir().getPath() + File.separator + "images"; + } - public Compressor setMaxWidth(int maxWidth) { - this.maxWidth = maxWidth; - return this; - } + public Compressor setMaxWidth(int maxWidth) { + this.maxWidth = maxWidth; + return this; + } - public Compressor setMaxHeight(int maxHeight) { - this.maxHeight = maxHeight; - return this; - } + public Compressor setMaxHeight(int maxHeight) { + this.maxHeight = maxHeight; + return this; + } - public Compressor setCompressFormat(Bitmap.CompressFormat compressFormat) { - this.compressFormat = compressFormat; - return this; - } + public Compressor setCompressFormat(Bitmap.CompressFormat compressFormat) { + this.compressFormat = compressFormat; + return this; + } - public Compressor setQuality(int quality) { - this.quality = quality; - return this; - } + public Compressor setQuality(int quality) { + this.quality = quality; + return this; + } - public Compressor setDestinationDirectoryPath(String destinationDirectoryPath) { - this.destinationDirectoryPath = destinationDirectoryPath; - return this; - } + public Compressor setDestinationDirectoryPath(String destinationDirectoryPath) { + this.destinationDirectoryPath = destinationDirectoryPath; + return this; + } - public File compressToFile(File imageFile) throws IOException { - return compressToFile(imageFile, imageFile.getName()); - } + public File compressToFile(File imageFile) throws IOException { + return compressToFile(imageFile, imageFile.getName()); + } - public File compressToFile(File imageFile, String compressedFileName) throws IOException { - return ImageUtil.compressImage(imageFile, maxWidth, maxHeight, compressFormat, quality, - destinationDirectoryPath + File.separator + compressedFileName); - } + public File compressToFile(File imageFile, String compressedFileName) throws IOException { + return ImageUtil.compressImage( + imageFile, + maxWidth, + maxHeight, + compressFormat, + quality, + destinationDirectoryPath + File.separator + compressedFileName); + } - public Bitmap compressToBitmap(File imageFile) throws IOException { - return ImageUtil.decodeSampledBitmapFromFile(imageFile, maxWidth, maxHeight); - } + public Bitmap compressToBitmap(File imageFile) throws IOException { + return ImageUtil.decodeSampledBitmapFromFile(imageFile, maxWidth, maxHeight); + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/util/FileUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/FileUtil.java index cbf13f2b..9b3126e7 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/FileUtil.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/FileUtil.java @@ -19,10 +19,11 @@ import android.database.Cursor; import android.os.Environment; import android.provider.MediaStore; import android.webkit.MimeTypeMap; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.repository.RealSongRepository; +import code.name.monkey.retromusic.repository.SortedCursor; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.File; @@ -36,228 +37,222 @@ import java.util.Collections; import java.util.LinkedList; import java.util.List; -import code.name.monkey.retromusic.model.Song; -import code.name.monkey.retromusic.repository.RealSongRepository; -import code.name.monkey.retromusic.repository.SortedCursor; - - public final class FileUtil { - private FileUtil() { + private FileUtil() {} + + public static byte[] readBytes(InputStream stream) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[4096]; + int count; + while ((count = stream.read(buffer)) != -1) { + baos.write(buffer, 0, count); + } + stream.close(); + return baos.toByteArray(); + } + + @NonNull + public static List matchFilesWithMediaStore( + @NonNull Context context, @Nullable List files) { + return new RealSongRepository(context).songs(makeSongCursor(context, files)); + } + + public static String safeGetCanonicalPath(File file) { + try { + return file.getCanonicalPath(); + } catch (IOException e) { + e.printStackTrace(); + return file.getAbsolutePath(); + } + } + + @Nullable + public static SortedCursor makeSongCursor( + @NonNull final Context context, @Nullable final List files) { + String selection = null; + String[] paths = null; + + if (files != null) { + paths = toPathArray(files); + + if (files.size() > 0 + && files.size() < 999) { // 999 is the max amount Androids SQL implementation can handle. + selection = + MediaStore.Audio.AudioColumns.DATA + " IN (" + makePlaceholders(files.size()) + ")"; + } } - public static byte[] readBytes(InputStream stream) throws IOException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - byte[] buffer = new byte[4096]; - int count; - while ((count = stream.read(buffer)) != -1) { - baos.write(buffer, 0, count); - } - stream.close(); - return baos.toByteArray(); - } + Cursor songCursor = + new RealSongRepository(context).makeSongCursor(selection, selection == null ? null : paths); - @NonNull - public static List matchFilesWithMediaStore(@NonNull Context context, - @Nullable List files) { - return new RealSongRepository(context).songs(makeSongCursor(context, files)); - } + return songCursor == null + ? null + : new SortedCursor(songCursor, paths, MediaStore.Audio.AudioColumns.DATA); + } - public static String safeGetCanonicalPath(File file) { - try { - return file.getCanonicalPath(); + private static String makePlaceholders(int len) { + StringBuilder sb = new StringBuilder(len * 2 - 1); + sb.append("?"); + for (int i = 1; i < len; i++) { + sb.append(",?"); + } + return sb.toString(); + } + + @Nullable + private static String[] toPathArray(@Nullable List files) { + if (files != null) { + String[] paths = new String[files.size()]; + for (int i = 0; i < files.size(); i++) { + /*try { + paths[i] = files.get(i).getCanonicalPath(); // canonical path is important here because we want to compare the path with the media store entry later } catch (IOException e) { e.printStackTrace(); - return file.getAbsolutePath(); - } + paths[i] = files.get(i).getPath(); + }*/ + paths[i] = safeGetCanonicalPath(files.get(i)); + } + return paths; } + return null; + } - @Nullable - public static SortedCursor makeSongCursor(@NonNull final Context context, - @Nullable final List files) { - String selection = null; - String[] paths = null; - - if (files != null) { - paths = toPathArray(files); - - if (files.size() > 0 - && files.size() < 999) { // 999 is the max amount Androids SQL implementation can handle. - selection = - MediaStore.Audio.AudioColumns.DATA + " IN (" + makePlaceholders(files.size()) + ")"; - } - } - - Cursor songCursor = new RealSongRepository(context).makeSongCursor(selection, selection == null ? null : paths); - - return songCursor == null ? null - : new SortedCursor(songCursor, paths, MediaStore.Audio.AudioColumns.DATA); + @NonNull + public static List listFiles(@NonNull File directory, @Nullable FileFilter fileFilter) { + List fileList = new LinkedList<>(); + File[] found = directory.listFiles(fileFilter); + if (found != null) { + Collections.addAll(fileList, found); } + return fileList; + } - private static String makePlaceholders(int len) { - StringBuilder sb = new StringBuilder(len * 2 - 1); - sb.append("?"); - for (int i = 1; i < len; i++) { - sb.append(",?"); - } - return sb.toString(); + @NonNull + public static List listFilesDeep(@NonNull File directory, @Nullable FileFilter fileFilter) { + List files = new LinkedList<>(); + internalListFilesDeep(files, directory, fileFilter); + return files; + } + + @NonNull + public static List listFilesDeep( + @NonNull Collection files, @Nullable FileFilter fileFilter) { + List resFiles = new LinkedList<>(); + for (File file : files) { + if (file.isDirectory()) { + internalListFilesDeep(resFiles, file, fileFilter); + } else if (fileFilter == null || fileFilter.accept(file)) { + resFiles.add(file); + } } + return resFiles; + } - @Nullable - private static String[] toPathArray(@Nullable List files) { - if (files != null) { - String[] paths = new String[files.size()]; - for (int i = 0; i < files.size(); i++) { - /*try { - paths[i] = files.get(i).getCanonicalPath(); // canonical path is important here because we want to compare the path with the media store entry later - } catch (IOException e) { - e.printStackTrace(); - paths[i] = files.get(i).getPath(); - }*/ - paths[i] = safeGetCanonicalPath(files.get(i)); - } - return paths; - } - return null; - } + private static void internalListFilesDeep( + @NonNull Collection files, @NonNull File directory, @Nullable FileFilter fileFilter) { + File[] found = directory.listFiles(fileFilter); - @NonNull - public static List listFiles(@NonNull File directory, @Nullable FileFilter fileFilter) { - List fileList = new LinkedList<>(); - File[] found = directory.listFiles(fileFilter); - if (found != null) { - Collections.addAll(fileList, found); - } - return fileList; - } - - @NonNull - public static List listFilesDeep(@NonNull File directory, @Nullable FileFilter fileFilter) { - List files = new LinkedList<>(); - internalListFilesDeep(files, directory, fileFilter); - return files; - } - - @NonNull - public static List listFilesDeep(@NonNull Collection files, - @Nullable FileFilter fileFilter) { - List resFiles = new LinkedList<>(); - for (File file : files) { - if (file.isDirectory()) { - internalListFilesDeep(resFiles, file, fileFilter); - } else if (fileFilter == null || fileFilter.accept(file)) { - resFiles.add(file); - } - } - return resFiles; - } - - private static void internalListFilesDeep(@NonNull Collection files, - @NonNull File directory, @Nullable FileFilter fileFilter) { - File[] found = directory.listFiles(fileFilter); - - if (found != null) { - for (File file : found) { - if (file.isDirectory()) { - internalListFilesDeep(files, file, fileFilter); - } else { - files.add(file); - } - } - } - } - - public static boolean fileIsMimeType(File file, String mimeType, MimeTypeMap mimeTypeMap) { - if (mimeType == null || mimeType.equals("*/*")) { - return true; + if (found != null) { + for (File file : found) { + if (file.isDirectory()) { + internalListFilesDeep(files, file, fileFilter); } else { - // get the file mime type - String filename = file.toURI().toString(); - int dotPos = filename.lastIndexOf('.'); - if (dotPos == -1) { - return false; - } - String fileExtension = filename.substring(dotPos + 1).toLowerCase(); - String fileType = mimeTypeMap.getMimeTypeFromExtension(fileExtension); - if (fileType == null) { - return false; - } - // check the 'type/subtype' pattern - if (fileType.equals(mimeType)) { - return true; - } - // check the 'type/*' pattern - int mimeTypeDelimiter = mimeType.lastIndexOf('/'); - if (mimeTypeDelimiter == -1) { - return false; - } - String mimeTypeMainType = mimeType.substring(0, mimeTypeDelimiter); - String mimeTypeSubtype = mimeType.substring(mimeTypeDelimiter + 1); - if (!mimeTypeSubtype.equals("*")) { - return false; - } - int fileTypeDelimiter = fileType.lastIndexOf('/'); - if (fileTypeDelimiter == -1) { - return false; - } - String fileTypeMainType = fileType.substring(0, fileTypeDelimiter); - if (fileTypeMainType.equals(mimeTypeMainType)) { - return true; - } - return fileTypeMainType.equals(mimeTypeMainType); + files.add(file); } + } } + } - public static String stripExtension(String str) { - if (str == null) { - return null; - } - int pos = str.lastIndexOf('.'); - if (pos == -1) { - return str; - } - return str.substring(0, pos); + public static boolean fileIsMimeType(File file, String mimeType, MimeTypeMap mimeTypeMap) { + if (mimeType == null || mimeType.equals("*/*")) { + return true; + } else { + // get the file mime type + String filename = file.toURI().toString(); + int dotPos = filename.lastIndexOf('.'); + if (dotPos == -1) { + return false; + } + String fileExtension = filename.substring(dotPos + 1).toLowerCase(); + String fileType = mimeTypeMap.getMimeTypeFromExtension(fileExtension); + if (fileType == null) { + return false; + } + // check the 'type/subtype' pattern + if (fileType.equals(mimeType)) { + return true; + } + // check the 'type/*' pattern + int mimeTypeDelimiter = mimeType.lastIndexOf('/'); + if (mimeTypeDelimiter == -1) { + return false; + } + String mimeTypeMainType = mimeType.substring(0, mimeTypeDelimiter); + String mimeTypeSubtype = mimeType.substring(mimeTypeDelimiter + 1); + if (!mimeTypeSubtype.equals("*")) { + return false; + } + int fileTypeDelimiter = fileType.lastIndexOf('/'); + if (fileTypeDelimiter == -1) { + return false; + } + String fileTypeMainType = fileType.substring(0, fileTypeDelimiter); + if (fileTypeMainType.equals(mimeTypeMainType)) { + return true; + } + return fileTypeMainType.equals(mimeTypeMainType); } + } - public static String readFromStream(InputStream is) throws Exception { - BufferedReader reader = new BufferedReader(new InputStreamReader(is)); - StringBuilder sb = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) { - if (sb.length() > 0) { - sb.append("\n"); - } - sb.append(line); - } - reader.close(); - return sb.toString(); + public static String stripExtension(String str) { + if (str == null) { + return null; } - - public static String read(File file) throws Exception { - FileInputStream fin = new FileInputStream(file); - String ret = readFromStream(fin); - fin.close(); - return ret; + int pos = str.lastIndexOf('.'); + if (pos == -1) { + return str; } + return str.substring(0, pos); + } - public static boolean isExternalMemoryAvailable() { - Boolean isSDPresent = Environment.getExternalStorageState() - .equals(android.os.Environment.MEDIA_MOUNTED); - Boolean isSDSupportedDevice = Environment.isExternalStorageRemovable(); - - // yes SD-card is present - // Sorry - return isSDSupportedDevice && isSDPresent; + public static String readFromStream(InputStream is) throws Exception { + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + StringBuilder sb = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + if (sb.length() > 0) { + sb.append("\n"); + } + sb.append(line); } + reader.close(); + return sb.toString(); + } - public static File safeGetCanonicalFile(File file) { - try { - return file.getCanonicalFile(); - } catch (IOException e) { - e.printStackTrace(); - return file.getAbsoluteFile(); - } + public static String read(File file) throws Exception { + FileInputStream fin = new FileInputStream(file); + String ret = readFromStream(fin); + fin.close(); + return ret; + } + + public static boolean isExternalMemoryAvailable() { + Boolean isSDPresent = + Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED); + Boolean isSDSupportedDevice = Environment.isExternalStorageRemovable(); + + // yes SD-card is present + // Sorry + return isSDSupportedDevice && isSDPresent; + } + + public static File safeGetCanonicalFile(File file) { + try { + return file.getCanonicalFile(); + } catch (IOException e) { + e.printStackTrace(); + return file.getAbsoluteFile(); } - - + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/util/ImageUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/ImageUtil.java index 46b03a1b..a4169fc1 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/ImageUtil.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/ImageUtil.java @@ -26,262 +26,268 @@ import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; import android.media.ExifInterface; import android.os.Build; - import androidx.annotation.ColorInt; import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat; - +import code.name.monkey.appthemehelper.util.TintHelper; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import code.name.monkey.appthemehelper.util.TintHelper; - /** - * Created on : June 18, 2016 Author : zetbaitsu Name : Zetra GitHub : - * https://github.com/zetbaitsu + * Created on : June 18, 2016 Author : zetbaitsu Name : Zetra GitHub : https://github.com/zetbaitsu */ public class ImageUtil { - private static final int TOLERANCE = 20; - // Alpha amount for which values below are considered transparent. - private static final int ALPHA_TOLERANCE = 50; - private static int[] mTempBuffer; + private static final int TOLERANCE = 20; + // Alpha amount for which values below are considered transparent. + private static final int ALPHA_TOLERANCE = 50; + private static int[] mTempBuffer; - private ImageUtil() { + private ImageUtil() {} + public static boolean isGrayscale(Bitmap bitmap) { + final int height = bitmap.getHeight(); + final int width = bitmap.getWidth(); + int size = height * width; + ensureBufferSize(size); + bitmap.getPixels(mTempBuffer, 0, width, 0, 0, width, height); + for (int i = 0; i < size; i++) { + if (!isGrayscale(mTempBuffer[i])) { + return false; + } + } + return true; + } + + public static Bitmap createBitmap(Drawable drawable) { + return createBitmap(drawable, 1f); + } + + public static Bitmap createBitmap(Drawable drawable, float sizeMultiplier) { + Bitmap bitmap = + Bitmap.createBitmap( + (int) (drawable.getIntrinsicWidth() * sizeMultiplier), + (int) (drawable.getIntrinsicHeight() * sizeMultiplier), + Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(bitmap); + drawable.setBounds(0, 0, c.getWidth(), c.getHeight()); + drawable.draw(c); + return bitmap; + } + + public static Drawable getTintedVectorDrawable( + @NonNull Resources res, + @DrawableRes int resId, + @Nullable Resources.Theme theme, + @ColorInt int color) { + return TintHelper.createTintedDrawable(getVectorDrawable(res, resId, theme), color); + } + + public static Drawable getTintedVectorDrawable( + @NonNull Context context, @DrawableRes int id, @ColorInt int color) { + return TintHelper.createTintedDrawable( + getVectorDrawable(context.getResources(), id, context.getTheme()), color); + } + + public static Drawable getVectorDrawable(@NonNull Context context, @DrawableRes int id) { + return getVectorDrawable(context.getResources(), id, context.getTheme()); + } + + public static Drawable getVectorDrawable( + @NonNull Resources res, @DrawableRes int resId, @Nullable Resources.Theme theme) { + if (Build.VERSION.SDK_INT >= 21) { + return res.getDrawable(resId, theme); + } + return VectorDrawableCompat.create(res, resId, theme); + } + + /** Makes sure that {@code mTempBuffer} has at least length {@code size}. */ + private static void ensureBufferSize(int size) { + if (mTempBuffer == null || mTempBuffer.length < size) { + mTempBuffer = new int[size]; + } + } + + public static Bitmap setBitmapColor(Bitmap bitmap, int color) { + Bitmap result = + Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth() - 1, bitmap.getHeight() - 1); + Paint paint = new Paint(); + paint.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP)); + + Canvas canvas = new Canvas(result); + canvas.drawBitmap(result, 0, 0, paint); + + return result; + } + + public static boolean isGrayscale(int color) { + int alpha = 0xFF & (color >> 24); + if (alpha < ALPHA_TOLERANCE) { + return true; + } + int r = 0xFF & (color >> 16); + int g = 0xFF & (color >> 8); + int b = 0xFF & color; + return Math.abs(r - g) < TOLERANCE + && Math.abs(r - b) < TOLERANCE + && Math.abs(g - b) < TOLERANCE; + } // Amount (max is 255) that two channels can differ before the color is no longer "gray". + + public static Bitmap resizeBitmap(@NonNull Bitmap src, int maxForSmallerSize) { + int width = src.getWidth(); + int height = src.getHeight(); + + final int dstWidth; + final int dstHeight; + + if (width < height) { + if (maxForSmallerSize >= width) { + return src; + } + float ratio = (float) height / width; + dstWidth = maxForSmallerSize; + dstHeight = Math.round(maxForSmallerSize * ratio); + } else { + if (maxForSmallerSize >= height) { + return src; + } + float ratio = (float) width / height; + dstWidth = Math.round(maxForSmallerSize * ratio); + dstHeight = maxForSmallerSize; } - public static boolean isGrayscale(Bitmap bitmap) { - final int height = bitmap.getHeight(); - final int width = bitmap.getWidth(); - int size = height * width; - ensureBufferSize(size); - bitmap.getPixels(mTempBuffer, 0, width, 0, 0, width, height); - for (int i = 0; i < size; i++) { - if (!isGrayscale(mTempBuffer[i])) { - return false; - } - } - return true; + return Bitmap.createScaledBitmap(src, dstWidth, dstHeight, false); + } + + public static int calculateInSampleSize(int width, int height, int reqWidth) { + // setting reqWidth matching to desired 1:1 ratio and screen-size + if (width < height) { + reqWidth = (height / width) * reqWidth; + } else { + reqWidth = (width / height) * reqWidth; } - public static Bitmap createBitmap(Drawable drawable) { - return createBitmap(drawable, 1f); + int inSampleSize = 1; + + if (height > reqWidth || width > reqWidth) { + final int halfHeight = height / 2; + final int halfWidth = width / 2; + + // Calculate the largest inSampleSize value that is a power of 2 and keeps both + // height and width larger than the requested height and width. + while ((halfHeight / inSampleSize) > reqWidth && (halfWidth / inSampleSize) > reqWidth) { + inSampleSize *= 2; + } } - public static Bitmap createBitmap(Drawable drawable, float sizeMultiplier) { - Bitmap bitmap = Bitmap.createBitmap((int) (drawable.getIntrinsicWidth() * sizeMultiplier), (int) (drawable.getIntrinsicHeight() * sizeMultiplier), Bitmap.Config.ARGB_8888); - Canvas c = new Canvas(bitmap); - drawable.setBounds(0, 0, c.getWidth(), c.getHeight()); - drawable.draw(c); - return bitmap; + return inSampleSize; + } + + static File compressImage( + File imageFile, + int reqWidth, + int reqHeight, + Bitmap.CompressFormat compressFormat, + int quality, + String destinationPath) + throws IOException { + FileOutputStream fileOutputStream = null; + File file = new File(destinationPath).getParentFile(); + if (!file.exists()) { + file.mkdirs(); + } + try { + fileOutputStream = new FileOutputStream(destinationPath); + // write the compressed bitmap at the destination specified by destinationPath. + decodeSampledBitmapFromFile(imageFile, reqWidth, reqHeight) + .compress(compressFormat, quality, fileOutputStream); + } finally { + if (fileOutputStream != null) { + fileOutputStream.flush(); + fileOutputStream.close(); + } } - public static Drawable getTintedVectorDrawable(@NonNull Resources res, @DrawableRes int resId, @Nullable Resources.Theme theme, @ColorInt int color) { - return TintHelper.createTintedDrawable(getVectorDrawable(res, resId, theme), color); + return new File(destinationPath); + } + + static Bitmap decodeSampledBitmapFromFile(File imageFile, int reqWidth, int reqHeight) + throws IOException { + // First decode with inJustDecodeBounds=true to check dimensions + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options); + + // Calculate inSampleSize + options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); + + // Decode bitmap with inSampleSize set + options.inJustDecodeBounds = false; + + Bitmap scaledBitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options); + + // check the rotation of the image and display it properly + ExifInterface exif; + exif = new ExifInterface(imageFile.getAbsolutePath()); + int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0); + Matrix matrix = new Matrix(); + if (orientation == 6) { + matrix.postRotate(90); + } else if (orientation == 3) { + matrix.postRotate(180); + } else if (orientation == 8) { + matrix.postRotate(270); + } + scaledBitmap = + Bitmap.createBitmap( + scaledBitmap, 0, 0, scaledBitmap.getWidth(), scaledBitmap.getHeight(), matrix, true); + return scaledBitmap; + } + + private static int calculateInSampleSize( + BitmapFactory.Options options, int reqWidth, int reqHeight) { + // Raw height and width of image + final int height = options.outHeight; + final int width = options.outWidth; + int inSampleSize = 1; + + if (height > reqHeight || width > reqWidth) { + + final int halfHeight = height / 2; + final int halfWidth = width / 2; + + // Calculate the largest inSampleSize value that is a power of 2 and keeps both + // height and width larger than the requested height and width. + while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) { + inSampleSize *= 2; + } } - public static Drawable getTintedVectorDrawable(@NonNull Context context, @DrawableRes int id, @ColorInt int color) { - return TintHelper.createTintedDrawable(getVectorDrawable(context.getResources(), id, context.getTheme()), color); + return inSampleSize; + } + + @NonNull + public static Bitmap getResizedBitmap(@NonNull Bitmap image, int maxSize) { + int width = image.getWidth(); + int height = image.getHeight(); + + float bitmapRatio = (float) width / (float) height; + if (bitmapRatio > 1) { + width = maxSize; + height = (int) (width / bitmapRatio); + } else { + height = maxSize; + width = (int) (height * bitmapRatio); } + return Bitmap.createScaledBitmap(image, width, height, true); + } - public static Drawable getVectorDrawable(@NonNull Context context, @DrawableRes int id) { - return getVectorDrawable(context.getResources(), id, context.getTheme()); - } - - public static Drawable getVectorDrawable(@NonNull Resources res, @DrawableRes int resId, @Nullable Resources.Theme theme) { - if (Build.VERSION.SDK_INT >= 21) { - return res.getDrawable(resId, theme); - } - return VectorDrawableCompat.create(res, resId, theme); - } - - /** - * Makes sure that {@code mTempBuffer} has at least length {@code size}. - */ - private static void ensureBufferSize(int size) { - if (mTempBuffer == null || mTempBuffer.length < size) { - mTempBuffer = new int[size]; - } - } - - public static Bitmap setBitmapColor(Bitmap bitmap, int color) { - Bitmap result = Bitmap - .createBitmap(bitmap, 0, 0, bitmap.getWidth() - 1, bitmap.getHeight() - 1); - Paint paint = new Paint(); - paint.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP)); - - Canvas canvas = new Canvas(result); - canvas.drawBitmap(result, 0, 0, paint); - - return result; - } - - public static boolean isGrayscale(int color) { - int alpha = 0xFF & (color >> 24); - if (alpha < ALPHA_TOLERANCE) { - return true; - } - int r = 0xFF & (color >> 16); - int g = 0xFF & (color >> 8); - int b = 0xFF & color; - return Math.abs(r - g) < TOLERANCE - && Math.abs(r - b) < TOLERANCE - && Math.abs(g - b) < TOLERANCE; - } // Amount (max is 255) that two channels can differ before the color is no longer "gray". - - public static Bitmap resizeBitmap(@NonNull Bitmap src, int maxForSmallerSize) { - int width = src.getWidth(); - int height = src.getHeight(); - - final int dstWidth; - final int dstHeight; - - if (width < height) { - if (maxForSmallerSize >= width) { - return src; - } - float ratio = (float) height / width; - dstWidth = maxForSmallerSize; - dstHeight = Math.round(maxForSmallerSize * ratio); - } else { - if (maxForSmallerSize >= height) { - return src; - } - float ratio = (float) width / height; - dstWidth = Math.round(maxForSmallerSize * ratio); - dstHeight = maxForSmallerSize; - } - - return Bitmap.createScaledBitmap(src, dstWidth, dstHeight, false); - } - - public static int calculateInSampleSize(int width, int height, int reqWidth) { - // setting reqWidth matching to desired 1:1 ratio and screen-size - if (width < height) { - reqWidth = (height / width) * reqWidth; - } else { - reqWidth = (width / height) * reqWidth; - } - - int inSampleSize = 1; - - if (height > reqWidth || width > reqWidth) { - final int halfHeight = height / 2; - final int halfWidth = width / 2; - - // Calculate the largest inSampleSize value that is a power of 2 and keeps both - // height and width larger than the requested height and width. - while ((halfHeight / inSampleSize) > reqWidth - && (halfWidth / inSampleSize) > reqWidth) { - inSampleSize *= 2; - } - } - - return inSampleSize; - } - - static File compressImage(File imageFile, int reqWidth, int reqHeight, - Bitmap.CompressFormat compressFormat, int quality, String destinationPath) - throws IOException { - FileOutputStream fileOutputStream = null; - File file = new File(destinationPath).getParentFile(); - if (!file.exists()) { - file.mkdirs(); - } - try { - fileOutputStream = new FileOutputStream(destinationPath); - // write the compressed bitmap at the destination specified by destinationPath. - decodeSampledBitmapFromFile(imageFile, reqWidth, reqHeight) - .compress(compressFormat, quality, fileOutputStream); - } finally { - if (fileOutputStream != null) { - fileOutputStream.flush(); - fileOutputStream.close(); - } - } - - return new File(destinationPath); - } - - static Bitmap decodeSampledBitmapFromFile(File imageFile, int reqWidth, int reqHeight) - throws IOException { - // First decode with inJustDecodeBounds=true to check dimensions - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; - BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options); - - // Calculate inSampleSize - options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); - - // Decode bitmap with inSampleSize set - options.inJustDecodeBounds = false; - - Bitmap scaledBitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options); - - //check the rotation of the image and display it properly - ExifInterface exif; - exif = new ExifInterface(imageFile.getAbsolutePath()); - int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0); - Matrix matrix = new Matrix(); - if (orientation == 6) { - matrix.postRotate(90); - } else if (orientation == 3) { - matrix.postRotate(180); - } else if (orientation == 8) { - matrix.postRotate(270); - } - scaledBitmap = Bitmap - .createBitmap(scaledBitmap, 0, 0, scaledBitmap.getWidth(), scaledBitmap.getHeight(), matrix, - true); - return scaledBitmap; - } - - private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, - int reqHeight) { - // Raw height and width of image - final int height = options.outHeight; - final int width = options.outWidth; - int inSampleSize = 1; - - if (height > reqHeight || width > reqWidth) { - - final int halfHeight = height / 2; - final int halfWidth = width / 2; - - // Calculate the largest inSampleSize value that is a power of 2 and keeps both - // height and width larger than the requested height and width. - while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) { - inSampleSize *= 2; - } - } - - return inSampleSize; - } - - @NonNull - public static Bitmap getResizedBitmap(@NonNull Bitmap image, int maxSize) { - int width = image.getWidth(); - int height = image.getHeight(); - - float bitmapRatio = (float) width / (float) height; - if (bitmapRatio > 1) { - width = maxSize; - height = (int) (width / bitmapRatio); - } else { - height = maxSize; - width = (int) (height * bitmapRatio); - } - return Bitmap.createScaledBitmap(image, width, height, true); - } - - public static Bitmap resize(InputStream stream, int scaledWidth, int scaledHeight) { - final Bitmap bitmap = BitmapFactory.decodeStream(stream); - return Bitmap.createScaledBitmap(bitmap, scaledWidth, scaledHeight, true); - - } + public static Bitmap resize(InputStream stream, int scaledWidth, int scaledHeight) { + final Bitmap bitmap = BitmapFactory.decodeStream(stream); + return Bitmap.createScaledBitmap(bitmap, scaledWidth, scaledHeight, true); + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/util/LyricUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/LyricUtil.java index ce182217..8b506064 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/LyricUtil.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/LyricUtil.java @@ -15,10 +15,8 @@ package code.name.monkey.retromusic.util; import android.util.Base64; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; @@ -28,110 +26,109 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; -/** - * Created by hefuyi on 2016/11/8. - */ - +/** Created by hefuyi on 2016/11/8. */ public class LyricUtil { - private static final String lrcRootPath = android.os.Environment - .getExternalStorageDirectory().toString() + "/RetroMusic/lyrics/"; - private static final String TAG = "LyricUtil"; + private static final String lrcRootPath = + android.os.Environment.getExternalStorageDirectory().toString() + "/RetroMusic/lyrics/"; + private static final String TAG = "LyricUtil"; - @Nullable - public static File writeLrcToLoc(@NonNull String title, @NonNull String artist, @NonNull String lrcContext) { - FileWriter writer = null; - try { - File file = new File(getLrcPath(title, artist)); - if (!file.getParentFile().exists()) { - file.getParentFile().mkdirs(); - } - writer = new FileWriter(getLrcPath(title, artist)); - writer.write(lrcContext); - return file; - } catch (IOException e) { - e.printStackTrace(); - return null; - } finally { - try { - if (writer != null) - writer.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } + @Nullable + public static File writeLrcToLoc( + @NonNull String title, @NonNull String artist, @NonNull String lrcContext) { + FileWriter writer = null; + try { + File file = new File(getLrcPath(title, artist)); + if (!file.getParentFile().exists()) { + file.getParentFile().mkdirs(); + } + writer = new FileWriter(getLrcPath(title, artist)); + writer.write(lrcContext); + return file; + } catch (IOException e) { + e.printStackTrace(); + return null; + } finally { + try { + if (writer != null) writer.close(); + } catch (IOException e) { + e.printStackTrace(); + } } + } - public static boolean deleteLrcFile(@NonNull String title, @NonNull String artist) { - File file = new File(getLrcPath(title, artist)); - return file.delete(); - } + public static boolean deleteLrcFile(@NonNull String title, @NonNull String artist) { + File file = new File(getLrcPath(title, artist)); + return file.delete(); + } - public static boolean isLrcFileExist(@NonNull String title, @NonNull String artist) { - File file = new File(getLrcPath(title, artist)); - return file.exists(); - } + public static boolean isLrcFileExist(@NonNull String title, @NonNull String artist) { + File file = new File(getLrcPath(title, artist)); + return file.exists(); + } - public static boolean isLrcOriginalFileExist(@NonNull String path) { - File file = new File(getLrcOriginalPath(path)); - return file.exists(); - } + public static boolean isLrcOriginalFileExist(@NonNull String path) { + File file = new File(getLrcOriginalPath(path)); + return file.exists(); + } - @Nullable - public static File getLocalLyricFile(@NonNull String title, @NonNull String artist) { - File file = new File(getLrcPath(title, artist)); - if (file.exists()) { - return file; - } else { - return null; - } + @Nullable + public static File getLocalLyricFile(@NonNull String title, @NonNull String artist) { + File file = new File(getLrcPath(title, artist)); + if (file.exists()) { + return file; + } else { + return null; } + } - @Nullable - public static File getLocalLyricOriginalFile(@NonNull String path) { - File file = new File(getLrcOriginalPath(path)); - if (file.exists()) { - return file; - } else { - return null; - } + @Nullable + public static File getLocalLyricOriginalFile(@NonNull String path) { + File file = new File(getLrcOriginalPath(path)); + if (file.exists()) { + return file; + } else { + return null; } + } - private static String getLrcPath(String title, String artist) { - return lrcRootPath + title + " - " + artist + ".lrc"; - } + private static String getLrcPath(String title, String artist) { + return lrcRootPath + title + " - " + artist + ".lrc"; + } - private static String getLrcOriginalPath(String filePath) { - return filePath.replace(filePath.substring(filePath.lastIndexOf(".") + 1), "lrc"); - } + private static String getLrcOriginalPath(String filePath) { + return filePath.replace(filePath.substring(filePath.lastIndexOf(".") + 1), "lrc"); + } - @NonNull - public static String decryptBASE64(@NonNull String str) { - if (str == null || str.length() == 0) { - return null; - } - byte[] encode = str.getBytes(StandardCharsets.UTF_8); - // base64 解密 - return new String(Base64.decode(encode, 0, encode.length, Base64.DEFAULT), StandardCharsets.UTF_8); + @NonNull + public static String decryptBASE64(@NonNull String str) { + if (str == null || str.length() == 0) { + return null; } + byte[] encode = str.getBytes(StandardCharsets.UTF_8); + // base64 解密 + return new String( + Base64.decode(encode, 0, encode.length, Base64.DEFAULT), StandardCharsets.UTF_8); + } - @NonNull - public static String getStringFromFile(@NonNull String title, @NonNull String artist) throws Exception { - File file = new File(getLrcPath(title, artist)); - FileInputStream fin = new FileInputStream(file); - String ret = convertStreamToString(fin); - fin.close(); - return ret; - } + @NonNull + public static String getStringFromFile(@NonNull String title, @NonNull String artist) + throws Exception { + File file = new File(getLrcPath(title, artist)); + FileInputStream fin = new FileInputStream(file); + String ret = convertStreamToString(fin); + fin.close(); + return ret; + } - private static String convertStreamToString(InputStream is) throws Exception { - BufferedReader reader = new BufferedReader(new InputStreamReader(is)); - StringBuilder sb = new StringBuilder(); - String line = null; - while ((line = reader.readLine()) != null) { - sb.append(line).append("\n"); - } - reader.close(); - return sb.toString(); + private static String convertStreamToString(InputStream is) throws Exception { + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + StringBuilder sb = new StringBuilder(); + String line = null; + while ((line = reader.readLine()) != null) { + sb.append(line).append("\n"); } + reader.close(); + return sb.toString(); + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/util/NavigationUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/NavigationUtil.java index 406bb22b..d1aa3c41 100755 --- a/app/src/main/java/code/name/monkey/retromusic/util/NavigationUtil.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/NavigationUtil.java @@ -21,12 +21,8 @@ import android.content.Context; import android.content.Intent; import android.media.audiofx.AudioEffect; import android.widget.Toast; - import androidx.annotation.NonNull; import androidx.core.app.ActivityCompat; - -import org.jetbrains.annotations.NotNull; - import code.name.monkey.retromusic.R; import code.name.monkey.retromusic.activities.DriveModeActivity; import code.name.monkey.retromusic.activities.LicenseActivity; @@ -38,71 +34,74 @@ import code.name.monkey.retromusic.activities.UserInfoActivity; import code.name.monkey.retromusic.activities.WhatsNewActivity; import code.name.monkey.retromusic.activities.bugreport.BugReportActivity; import code.name.monkey.retromusic.helper.MusicPlayerRemote; - +import org.jetbrains.annotations.NotNull; public class NavigationUtil { - public static void bugReport(@NonNull Activity activity) { - ActivityCompat.startActivity(activity, new Intent(activity, BugReportActivity.class), null); + public static void bugReport(@NonNull Activity activity) { + ActivityCompat.startActivity(activity, new Intent(activity, BugReportActivity.class), null); + } + + public static void goToLyrics(@NonNull Activity activity) { + Intent intent = new Intent(activity, LyricsActivity.class); + ActivityCompat.startActivity(activity, intent, null); + } + + public static void goToOpenSource(@NonNull Activity activity) { + ActivityCompat.startActivity(activity, new Intent(activity, LicenseActivity.class), null); + } + + public static void goToPlayingQueue(@NonNull Activity activity) { + Intent intent = new Intent(activity, PlayingQueueActivity.class); + ActivityCompat.startActivity(activity, intent, null); + } + + public static void goToProVersion(@NonNull Context context) { + ActivityCompat.startActivity(context, new Intent(context, PurchaseActivity.class), null); + } + + public static void goToSupportDevelopment(@NonNull Activity activity) { + ActivityCompat.startActivity( + activity, new Intent(activity, SupportDevelopmentActivity.class), null); + } + + public static void goToUserInfo( + @NonNull Activity activity, @NonNull ActivityOptions activityOptions) { + ActivityCompat.startActivity( + activity, new Intent(activity, UserInfoActivity.class), activityOptions.toBundle()); + } + + public static void gotoDriveMode(@NotNull final Activity activity) { + ActivityCompat.startActivity(activity, new Intent(activity, DriveModeActivity.class), null); + } + + public static void gotoWhatNews(@NonNull Activity activity) { + ActivityCompat.startActivity(activity, new Intent(activity, WhatsNewActivity.class), null); + } + + public static void openEqualizer(@NonNull final Activity activity) { + stockEqalizer(activity); + } + + private static void stockEqalizer(@NonNull Activity activity) { + final int sessionId = MusicPlayerRemote.INSTANCE.getAudioSessionId(); + if (sessionId == AudioEffect.ERROR_BAD_VALUE) { + Toast.makeText( + activity, activity.getResources().getString(R.string.no_audio_ID), Toast.LENGTH_LONG) + .show(); + } else { + try { + final Intent effects = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL); + effects.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, sessionId); + effects.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC); + activity.startActivityForResult(effects, 0); + } catch (@NonNull final ActivityNotFoundException notFound) { + Toast.makeText( + activity, + activity.getResources().getString(R.string.no_equalizer), + Toast.LENGTH_SHORT) + .show(); + } } - - public static void goToLyrics(@NonNull Activity activity) { - Intent intent = new Intent(activity, LyricsActivity.class); - ActivityCompat.startActivity(activity, intent, null); - } - - public static void goToOpenSource(@NonNull Activity activity) { - ActivityCompat.startActivity(activity, new Intent(activity, LicenseActivity.class), null); - } - - public static void goToPlayingQueue(@NonNull Activity activity) { - Intent intent = new Intent(activity, PlayingQueueActivity.class); - ActivityCompat.startActivity(activity, intent, null); - } - - public static void goToProVersion(@NonNull Context context) { - ActivityCompat.startActivity(context, new Intent(context, PurchaseActivity.class), null); - } - - public static void goToSupportDevelopment(@NonNull Activity activity) { - ActivityCompat.startActivity(activity, new Intent(activity, SupportDevelopmentActivity.class), null); - } - - public static void goToUserInfo(@NonNull Activity activity, - @NonNull ActivityOptions activityOptions) { - ActivityCompat.startActivity(activity, new Intent(activity, UserInfoActivity.class), - activityOptions.toBundle()); - } - - public static void gotoDriveMode(@NotNull final Activity activity) { - ActivityCompat.startActivity(activity, new Intent(activity, DriveModeActivity.class), null); - } - - public static void gotoWhatNews(@NonNull Activity activity) { - ActivityCompat.startActivity(activity, new Intent(activity, WhatsNewActivity.class), null); - } - - public static void openEqualizer(@NonNull final Activity activity) { - stockEqalizer(activity); - } - - private static void stockEqalizer(@NonNull Activity activity) { - final int sessionId = MusicPlayerRemote.INSTANCE.getAudioSessionId(); - if (sessionId == AudioEffect.ERROR_BAD_VALUE) { - Toast.makeText(activity, activity.getResources().getString(R.string.no_audio_ID), - Toast.LENGTH_LONG).show(); - } else { - try { - final Intent effects = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL); - effects.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, sessionId); - effects.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC); - activity.startActivityForResult(effects, 0); - } catch (@NonNull final ActivityNotFoundException notFound) { - Toast.makeText(activity, activity.getResources().getString(R.string.no_equalizer), - Toast.LENGTH_SHORT).show(); - } - } - } - - + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/util/PlaylistsUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/PlaylistsUtil.java index 14ee82bd..bd3a2c9d 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/PlaylistsUtil.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/PlaylistsUtil.java @@ -14,6 +14,8 @@ package code.name.monkey.retromusic.util; +import static android.provider.MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI; + import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; @@ -23,259 +25,306 @@ import android.os.Environment; import android.provider.BaseColumns; import android.provider.MediaStore; import android.widget.Toast; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - import code.name.monkey.retromusic.R; import code.name.monkey.retromusic.db.PlaylistWithSongs; import code.name.monkey.retromusic.helper.M3UWriter; import code.name.monkey.retromusic.model.Playlist; import code.name.monkey.retromusic.model.PlaylistSong; import code.name.monkey.retromusic.model.Song; - -import static android.provider.MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; public class PlaylistsUtil { - public static long createPlaylist(@NonNull final Context context, @Nullable final String name) { - int id = -1; - if (name != null && name.length() > 0) { - try { - Cursor cursor = context.getContentResolver().query(EXTERNAL_CONTENT_URI, - new String[]{MediaStore.Audio.Playlists._ID}, - MediaStore.Audio.PlaylistsColumns.NAME + "=?", new String[]{name}, - null); - if (cursor == null || cursor.getCount() < 1) { - final ContentValues values = new ContentValues(1); - values.put(MediaStore.Audio.PlaylistsColumns.NAME, name); - final Uri uri = context.getContentResolver().insert( - EXTERNAL_CONTENT_URI, - values); - if (uri != null) { - // Necessary because somehow the MediaStoreObserver is not notified when adding a playlist - context.getContentResolver().notifyChange(Uri.parse("content://media"), null); - Toast.makeText(context, context.getResources().getString( - R.string.created_playlist_x, name), Toast.LENGTH_SHORT).show(); - id = Integer.parseInt(uri.getLastPathSegment()); - } - } else { - // Playlist exists - if (cursor.moveToFirst()) { - id = cursor.getInt(cursor.getColumnIndex(MediaStore.Audio.Playlists._ID)); - } - } - if (cursor != null) { - cursor.close(); - } - } catch (SecurityException e) { - e.printStackTrace(); - } - } - if (id == -1) { - Toast.makeText(context, context.getResources().getString( - R.string.could_not_create_playlist), Toast.LENGTH_SHORT).show(); - } - return id; - } - - public static void deletePlaylists(@NonNull final Context context, @NonNull final List playlists) { - final StringBuilder selection = new StringBuilder(); - selection.append(MediaStore.Audio.Playlists._ID + " IN ("); - for (int i = 0; i < playlists.size(); i++) { - selection.append(playlists.get(i).getId()); - if (i < playlists.size() - 1) { - selection.append(","); - } - } - selection.append(")"); - try { - context.getContentResolver().delete(EXTERNAL_CONTENT_URI, selection.toString(), null); - context.getContentResolver().notifyChange(Uri.parse("content://media"), null); - } catch (SecurityException ignored) { - } - } - - public static void addToPlaylist(@NonNull final Context context, final Song song, final long playlistId, final boolean showToastOnFinish) { - List helperList = new ArrayList<>(); - helperList.add(song); - addToPlaylist(context, helperList, playlistId, showToastOnFinish); - } - - public static void addToPlaylist(@NonNull final Context context, @NonNull final List songs, final long playlistId, final boolean showToastOnFinish) { - final int size = songs.size(); - final ContentResolver resolver = context.getContentResolver(); - final String[] projection = new String[]{ - "max(" + MediaStore.Audio.Playlists.Members.PLAY_ORDER + ")", - }; - final Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId); - Cursor cursor = null; - int base = 0; - - try { - try { - cursor = resolver.query(uri, projection, null, null, null); - - if (cursor != null && cursor.moveToFirst()) { - base = cursor.getInt(0) + 1; - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - - int numInserted = 0; - for (int offSet = 0; offSet < size; offSet += 1000) - numInserted += resolver.bulkInsert(uri, makeInsertItems(songs, offSet, 1000, base)); - - if (showToastOnFinish) { - Toast.makeText(context, context.getResources().getString( - R.string.inserted_x_songs_into_playlist_x, numInserted, getNameForPlaylist(context, playlistId)), Toast.LENGTH_SHORT).show(); - } - } catch (SecurityException ignored) { - ignored.printStackTrace(); - } - } - - @NonNull - public static ContentValues[] makeInsertItems(@NonNull final List songs, final int offset, int len, final int base) { - if (offset + len > songs.size()) { - len = songs.size() - offset; - } - - ContentValues[] contentValues = new ContentValues[len]; - - for (int i = 0; i < len; i++) { - contentValues[i] = new ContentValues(); - contentValues[i].put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, base + offset + i); - contentValues[i].put(MediaStore.Audio.Playlists.Members.AUDIO_ID, songs.get(offset + i).getId()); - } - return contentValues; - } - - public static String getNameForPlaylist(@NonNull final Context context, final long id) { - try { - Cursor cursor = context.getContentResolver().query(EXTERNAL_CONTENT_URI, - new String[]{MediaStore.Audio.PlaylistsColumns.NAME}, - BaseColumns._ID + "=?", - new String[]{String.valueOf(id)}, + public static long createPlaylist(@NonNull final Context context, @Nullable final String name) { + int id = -1; + if (name != null && name.length() > 0) { + try { + Cursor cursor = + context + .getContentResolver() + .query( + EXTERNAL_CONTENT_URI, + new String[] {MediaStore.Audio.Playlists._ID}, + MediaStore.Audio.PlaylistsColumns.NAME + "=?", + new String[] {name}, null); - if (cursor != null) { - try { - if (cursor.moveToFirst()) { - return cursor.getString(0); - } - } finally { - cursor.close(); - } - } - } catch (SecurityException ignored) { - } - return ""; - } - - public static void removeFromPlaylist(@NonNull final Context context, @NonNull final Song song, long playlistId) { - Uri uri = MediaStore.Audio.Playlists.Members.getContentUri( - "external", playlistId); - String selection = MediaStore.Audio.Playlists.Members.AUDIO_ID + " =?"; - String[] selectionArgs = new String[]{String.valueOf(song.getId())}; - - try { - context.getContentResolver().delete(uri, selection, selectionArgs); - } catch (SecurityException ignored) { - } - } - - public static void removeFromPlaylist(@NonNull final Context context, @NonNull final List songs) { - final long playlistId = songs.get(0).getPlaylistId(); - Uri uri = MediaStore.Audio.Playlists.Members.getContentUri( - "external", playlistId); - String[] selectionArgs = new String[songs.size()]; - for (int i = 0; i < selectionArgs.length; i++) { - selectionArgs[i] = String.valueOf(songs.get(i).getIdInPlayList()); - } - String selection = MediaStore.Audio.Playlists.Members._ID + " in ("; - //noinspection unused - for (String selectionArg : selectionArgs) selection += "?, "; - selection = selection.substring(0, selection.length() - 2) + ")"; - - try { - context.getContentResolver().delete(uri, selection, selectionArgs); - } catch (SecurityException ignored) { - } - } - - public static boolean doPlaylistContains(@NonNull final Context context, final long playlistId, final long songId) { - if (playlistId != -1) { - try { - Cursor c = context.getContentResolver().query( - MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId), - new String[]{MediaStore.Audio.Playlists.Members.AUDIO_ID}, MediaStore.Audio.Playlists.Members.AUDIO_ID + "=?", new String[]{String.valueOf(songId)}, null); - int count = 0; - if (c != null) { - count = c.getCount(); - c.close(); - } - return count > 0; - } catch (SecurityException ignored) { - } - } - return false; - } - - public static boolean moveItem(@NonNull final Context context, long playlistId, int from, int to) { - return MediaStore.Audio.Playlists.Members.moveItem(context.getContentResolver(), - playlistId, from, to); - } - - public static void renamePlaylist(@NonNull final Context context, final long id, final String newName) { - ContentValues contentValues = new ContentValues(); - contentValues.put(MediaStore.Audio.PlaylistsColumns.NAME, newName); - try { - context.getContentResolver().update(EXTERNAL_CONTENT_URI, - contentValues, - MediaStore.Audio.Playlists._ID + "=?", - new String[]{String.valueOf(id)}); + if (cursor == null || cursor.getCount() < 1) { + final ContentValues values = new ContentValues(1); + values.put(MediaStore.Audio.PlaylistsColumns.NAME, name); + final Uri uri = context.getContentResolver().insert(EXTERNAL_CONTENT_URI, values); + if (uri != null) { + // Necessary because somehow the MediaStoreObserver is not notified when adding a + // playlist context.getContentResolver().notifyChange(Uri.parse("content://media"), null); - } catch (SecurityException ignored) { + Toast.makeText( + context, + context.getResources().getString(R.string.created_playlist_x, name), + Toast.LENGTH_SHORT) + .show(); + id = Integer.parseInt(uri.getLastPathSegment()); + } + } else { + // Playlist exists + if (cursor.moveToFirst()) { + id = cursor.getInt(cursor.getColumnIndex(MediaStore.Audio.Playlists._ID)); + } } - } - - public static File savePlaylist(Context context, Playlist playlist) throws IOException { - return M3UWriter.write(new File(Environment.getExternalStorageDirectory(), "Playlists"), playlist); - } - - public static File savePlaylistWithSongs(PlaylistWithSongs playlist) throws IOException { - return M3UWriter.writeIO(new File(Environment.getExternalStorageDirectory(), "Playlists"), playlist); - } - - public static boolean doesPlaylistExist(@NonNull final Context context, final int playlistId) { - return playlistId != -1 && doesPlaylistExist(context, - MediaStore.Audio.Playlists._ID + "=?", - new String[]{String.valueOf(playlistId)}); - } - - public static boolean doesPlaylistExist(@NonNull final Context context, final String name) { - return doesPlaylistExist(context, - MediaStore.Audio.PlaylistsColumns.NAME + "=?", - new String[]{name}); - } - - private static boolean doesPlaylistExist(@NonNull Context context, @NonNull final String selection, @NonNull final String[] values) { - Cursor cursor = context.getContentResolver().query(EXTERNAL_CONTENT_URI, - new String[]{}, selection, values, null); - - boolean exists = false; if (cursor != null) { - exists = cursor.getCount() != 0; - cursor.close(); + cursor.close(); } - return exists; + } catch (SecurityException e) { + e.printStackTrace(); + } } -} \ No newline at end of file + if (id == -1) { + Toast.makeText( + context, + context.getResources().getString(R.string.could_not_create_playlist), + Toast.LENGTH_SHORT) + .show(); + } + return id; + } + + public static void deletePlaylists( + @NonNull final Context context, @NonNull final List playlists) { + final StringBuilder selection = new StringBuilder(); + selection.append(MediaStore.Audio.Playlists._ID + " IN ("); + for (int i = 0; i < playlists.size(); i++) { + selection.append(playlists.get(i).getId()); + if (i < playlists.size() - 1) { + selection.append(","); + } + } + selection.append(")"); + try { + context.getContentResolver().delete(EXTERNAL_CONTENT_URI, selection.toString(), null); + context.getContentResolver().notifyChange(Uri.parse("content://media"), null); + } catch (SecurityException ignored) { + } + } + + public static void addToPlaylist( + @NonNull final Context context, + final Song song, + final long playlistId, + final boolean showToastOnFinish) { + List helperList = new ArrayList<>(); + helperList.add(song); + addToPlaylist(context, helperList, playlistId, showToastOnFinish); + } + + public static void addToPlaylist( + @NonNull final Context context, + @NonNull final List songs, + final long playlistId, + final boolean showToastOnFinish) { + final int size = songs.size(); + final ContentResolver resolver = context.getContentResolver(); + final String[] projection = + new String[] { + "max(" + MediaStore.Audio.Playlists.Members.PLAY_ORDER + ")", + }; + final Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId); + Cursor cursor = null; + int base = 0; + + try { + try { + cursor = resolver.query(uri, projection, null, null, null); + + if (cursor != null && cursor.moveToFirst()) { + base = cursor.getInt(0) + 1; + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + + int numInserted = 0; + for (int offSet = 0; offSet < size; offSet += 1000) + numInserted += resolver.bulkInsert(uri, makeInsertItems(songs, offSet, 1000, base)); + + if (showToastOnFinish) { + Toast.makeText( + context, + context + .getResources() + .getString( + R.string.inserted_x_songs_into_playlist_x, + numInserted, + getNameForPlaylist(context, playlistId)), + Toast.LENGTH_SHORT) + .show(); + } + } catch (SecurityException ignored) { + ignored.printStackTrace(); + } + } + + @NonNull + public static ContentValues[] makeInsertItems( + @NonNull final List songs, final int offset, int len, final int base) { + if (offset + len > songs.size()) { + len = songs.size() - offset; + } + + ContentValues[] contentValues = new ContentValues[len]; + + for (int i = 0; i < len; i++) { + contentValues[i] = new ContentValues(); + contentValues[i].put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, base + offset + i); + contentValues[i].put( + MediaStore.Audio.Playlists.Members.AUDIO_ID, songs.get(offset + i).getId()); + } + return contentValues; + } + + public static String getNameForPlaylist(@NonNull final Context context, final long id) { + try { + Cursor cursor = + context + .getContentResolver() + .query( + EXTERNAL_CONTENT_URI, + new String[] {MediaStore.Audio.PlaylistsColumns.NAME}, + BaseColumns._ID + "=?", + new String[] {String.valueOf(id)}, + null); + if (cursor != null) { + try { + if (cursor.moveToFirst()) { + return cursor.getString(0); + } + } finally { + cursor.close(); + } + } + } catch (SecurityException ignored) { + } + return ""; + } + + public static void removeFromPlaylist( + @NonNull final Context context, @NonNull final Song song, long playlistId) { + Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId); + String selection = MediaStore.Audio.Playlists.Members.AUDIO_ID + " =?"; + String[] selectionArgs = new String[] {String.valueOf(song.getId())}; + + try { + context.getContentResolver().delete(uri, selection, selectionArgs); + } catch (SecurityException ignored) { + } + } + + public static void removeFromPlaylist( + @NonNull final Context context, @NonNull final List songs) { + final long playlistId = songs.get(0).getPlaylistId(); + Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId); + String[] selectionArgs = new String[songs.size()]; + for (int i = 0; i < selectionArgs.length; i++) { + selectionArgs[i] = String.valueOf(songs.get(i).getIdInPlayList()); + } + String selection = MediaStore.Audio.Playlists.Members._ID + " in ("; + //noinspection unused + for (String selectionArg : selectionArgs) selection += "?, "; + selection = selection.substring(0, selection.length() - 2) + ")"; + + try { + context.getContentResolver().delete(uri, selection, selectionArgs); + } catch (SecurityException ignored) { + } + } + + public static boolean doPlaylistContains( + @NonNull final Context context, final long playlistId, final long songId) { + if (playlistId != -1) { + try { + Cursor c = + context + .getContentResolver() + .query( + MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId), + new String[] {MediaStore.Audio.Playlists.Members.AUDIO_ID}, + MediaStore.Audio.Playlists.Members.AUDIO_ID + "=?", + new String[] {String.valueOf(songId)}, + null); + int count = 0; + if (c != null) { + count = c.getCount(); + c.close(); + } + return count > 0; + } catch (SecurityException ignored) { + } + } + return false; + } + + public static boolean moveItem( + @NonNull final Context context, long playlistId, int from, int to) { + return MediaStore.Audio.Playlists.Members.moveItem( + context.getContentResolver(), playlistId, from, to); + } + + public static void renamePlaylist( + @NonNull final Context context, final long id, final String newName) { + ContentValues contentValues = new ContentValues(); + contentValues.put(MediaStore.Audio.PlaylistsColumns.NAME, newName); + try { + context + .getContentResolver() + .update( + EXTERNAL_CONTENT_URI, + contentValues, + MediaStore.Audio.Playlists._ID + "=?", + new String[] {String.valueOf(id)}); + context.getContentResolver().notifyChange(Uri.parse("content://media"), null); + } catch (SecurityException ignored) { + } + } + + public static File savePlaylist(Context context, Playlist playlist) throws IOException { + return M3UWriter.write( + new File(Environment.getExternalStorageDirectory(), "Playlists"), playlist); + } + + public static File savePlaylistWithSongs(PlaylistWithSongs playlist) throws IOException { + return M3UWriter.writeIO( + new File(Environment.getExternalStorageDirectory(), "Playlists"), playlist); + } + + public static boolean doesPlaylistExist(@NonNull final Context context, final int playlistId) { + return playlistId != -1 + && doesPlaylistExist( + context, + MediaStore.Audio.Playlists._ID + "=?", + new String[] {String.valueOf(playlistId)}); + } + + public static boolean doesPlaylistExist(@NonNull final Context context, final String name) { + return doesPlaylistExist( + context, MediaStore.Audio.PlaylistsColumns.NAME + "=?", new String[] {name}); + } + + private static boolean doesPlaylistExist( + @NonNull Context context, @NonNull final String selection, @NonNull final String[] values) { + Cursor cursor = + context + .getContentResolver() + .query(EXTERNAL_CONTENT_URI, new String[] {}, selection, values, null); + + boolean exists = false; + if (cursor != null) { + exists = cursor.getCount() != 0; + cursor.close(); + } + return exists; + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/util/RetroColorUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/RetroColorUtil.java index 310c76fa..47fbdd8b 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/RetroColorUtil.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/RetroColorUtil.java @@ -18,203 +18,204 @@ import android.content.Context; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Color; - import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.palette.graphics.Palette; - +import code.name.monkey.appthemehelper.util.ColorUtil; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; -import code.name.monkey.appthemehelper.util.ColorUtil; - public class RetroColorUtil { - public static int desaturateColor(int color, float ratio) { - float[] hsv = new float[3]; - Color.colorToHSV(color, hsv); + public static int desaturateColor(int color, float ratio) { + float[] hsv = new float[3]; + Color.colorToHSV(color, hsv); - hsv[1] = (hsv[1] / 1 * ratio) + (0.2f * (1.0f - ratio)); + hsv[1] = (hsv[1] / 1 * ratio) + (0.2f * (1.0f - ratio)); - return Color.HSVToColor(hsv); + return Color.HSVToColor(hsv); + } + + @Nullable + public static Palette generatePalette(@Nullable Bitmap bitmap) { + return bitmap == null ? null : Palette.from(bitmap).clearFilters().generate(); + } + + public static int getTextColor(@Nullable Palette palette) { + if (palette == null) { + return -1; } - @Nullable - public static Palette generatePalette(@Nullable Bitmap bitmap) { - return bitmap == null ? null : Palette.from(bitmap).clearFilters().generate(); + int inverse = -1; + if (palette.getVibrantSwatch() != null) { + inverse = palette.getVibrantSwatch().getRgb(); + } else if (palette.getLightVibrantSwatch() != null) { + inverse = palette.getLightVibrantSwatch().getRgb(); + } else if (palette.getDarkVibrantSwatch() != null) { + inverse = palette.getDarkVibrantSwatch().getRgb(); } - public static int getTextColor(@Nullable Palette palette) { - if (palette == null) { - return -1; - } + int background = getSwatch(palette).getRgb(); - int inverse = -1; - if (palette.getVibrantSwatch() != null) { - inverse = palette.getVibrantSwatch().getRgb(); - } else if (palette.getLightVibrantSwatch() != null) { - inverse = palette.getLightVibrantSwatch().getRgb(); - } else if (palette.getDarkVibrantSwatch() != null) { - inverse = palette.getDarkVibrantSwatch().getRgb(); - } - - int background = getSwatch(palette).getRgb(); - - if (inverse != -1) { - return ColorUtil.INSTANCE.getReadableText(inverse, background, 150); - } - return ColorUtil.INSTANCE.stripAlpha(getSwatch(palette).getTitleTextColor()); + if (inverse != -1) { + return ColorUtil.INSTANCE.getReadableText(inverse, background, 150); } + return ColorUtil.INSTANCE.stripAlpha(getSwatch(palette).getTitleTextColor()); + } - @NonNull - public static Palette.Swatch getSwatch(@Nullable Palette palette) { - if (palette == null) { - return new Palette.Swatch(Color.WHITE, 1); - } + @NonNull + public static Palette.Swatch getSwatch(@Nullable Palette palette) { + if (palette == null) { + return new Palette.Swatch(Color.WHITE, 1); + } + return getBestPaletteSwatchFrom(palette.getSwatches()); + } + + public static int getMatColor(Context context, String typeColor) { + int returnColor = Color.BLACK; + int arrayId = + context + .getResources() + .getIdentifier( + "md_" + typeColor, "array", context.getApplicationContext().getPackageName()); + + if (arrayId != 0) { + TypedArray colors = context.getResources().obtainTypedArray(arrayId); + int index = (int) (Math.random() * colors.length()); + returnColor = colors.getColor(index, Color.BLACK); + colors.recycle(); + } + return returnColor; + } + + @ColorInt + public static int getColor(@Nullable Palette palette, int fallback) { + if (palette != null) { + if (palette.getVibrantSwatch() != null) { + return palette.getVibrantSwatch().getRgb(); + } else if (palette.getDarkVibrantSwatch() != null) { + return palette.getDarkVibrantSwatch().getRgb(); + } else if (palette.getLightVibrantSwatch() != null) { + return palette.getLightVibrantSwatch().getRgb(); + } else if (palette.getMutedSwatch() != null) { + return palette.getMutedSwatch().getRgb(); + } else if (palette.getLightMutedSwatch() != null) { + return palette.getLightMutedSwatch().getRgb(); + } else if (palette.getDarkMutedSwatch() != null) { + return palette.getDarkMutedSwatch().getRgb(); + } else if (!palette.getSwatches().isEmpty()) { + return Collections.max(palette.getSwatches(), SwatchComparator.getInstance()).getRgb(); + } + } + return fallback; + } + + private static Palette.Swatch getTextSwatch(@Nullable Palette palette) { + if (palette == null) { + return new Palette.Swatch(Color.BLACK, 1); + } + if (palette.getVibrantSwatch() != null) { + return palette.getVibrantSwatch(); + } else { + return new Palette.Swatch(Color.BLACK, 1); + } + } + + @ColorInt + public static int getBackgroundColor(@Nullable Palette palette) { + return getProperBackgroundSwatch(palette).getRgb(); + } + + private static Palette.Swatch getProperBackgroundSwatch(@Nullable Palette palette) { + if (palette == null) { + return new Palette.Swatch(Color.BLACK, 1); + } + if (palette.getDarkMutedSwatch() != null) { + return palette.getDarkMutedSwatch(); + } else if (palette.getMutedSwatch() != null) { + return palette.getMutedSwatch(); + } else if (palette.getLightMutedSwatch() != null) { + return palette.getLightMutedSwatch(); + } else { + return new Palette.Swatch(Color.BLACK, 1); + } + } + + private static Palette.Swatch getBestPaletteSwatchFrom(Palette palette) { + if (palette != null) { + if (palette.getVibrantSwatch() != null) { + return palette.getVibrantSwatch(); + } else if (palette.getMutedSwatch() != null) { + return palette.getMutedSwatch(); + } else if (palette.getDarkVibrantSwatch() != null) { + return palette.getDarkVibrantSwatch(); + } else if (palette.getDarkMutedSwatch() != null) { + return palette.getDarkMutedSwatch(); + } else if (palette.getLightVibrantSwatch() != null) { + return palette.getLightVibrantSwatch(); + } else if (palette.getLightMutedSwatch() != null) { + return palette.getLightMutedSwatch(); + } else if (!palette.getSwatches().isEmpty()) { return getBestPaletteSwatchFrom(palette.getSwatches()); - + } } + return null; + } - public static int getMatColor(Context context, String typeColor) { - int returnColor = Color.BLACK; - int arrayId = context.getResources().getIdentifier("md_" + typeColor, "array", - context.getApplicationContext().getPackageName()); - - if (arrayId != 0) { - TypedArray colors = context.getResources().obtainTypedArray(arrayId); - int index = (int) (Math.random() * colors.length()); - returnColor = colors.getColor(index, Color.BLACK); - colors.recycle(); - } - return returnColor; + private static Palette.Swatch getBestPaletteSwatchFrom(List swatches) { + if (swatches == null) { + return null; } - - @ColorInt - public static int getColor(@Nullable Palette palette, int fallback) { - if (palette != null) { - if (palette.getVibrantSwatch() != null) { - return palette.getVibrantSwatch().getRgb(); - } else if (palette.getDarkVibrantSwatch() != null) { - return palette.getDarkVibrantSwatch().getRgb(); - } else if (palette.getLightVibrantSwatch() != null) { - return palette.getLightVibrantSwatch().getRgb(); - } else if (palette.getMutedSwatch() != null) { - return palette.getMutedSwatch().getRgb(); - } else if (palette.getLightMutedSwatch() != null) { - return palette.getLightMutedSwatch().getRgb(); - } else if (palette.getDarkMutedSwatch() != null) { - return palette.getDarkMutedSwatch().getRgb(); - } else if (!palette.getSwatches().isEmpty()) { - return Collections.max(palette.getSwatches(), SwatchComparator.getInstance()).getRgb(); - } - } - return fallback; - } - - private static Palette.Swatch getTextSwatch(@Nullable Palette palette) { - if (palette == null) { - return new Palette.Swatch(Color.BLACK, 1); - } - if (palette.getVibrantSwatch() != null) { - return palette.getVibrantSwatch(); - } else { - return new Palette.Swatch(Color.BLACK, 1); - } - } - - @ColorInt - public static int getBackgroundColor(@Nullable Palette palette) { - return getProperBackgroundSwatch(palette).getRgb(); - } - - private static Palette.Swatch getProperBackgroundSwatch(@Nullable Palette palette) { - if (palette == null) { - return new Palette.Swatch(Color.BLACK, 1); - } - if (palette.getDarkMutedSwatch() != null) { - return palette.getDarkMutedSwatch(); - } else if (palette.getMutedSwatch() != null) { - return palette.getMutedSwatch(); - } else if (palette.getLightMutedSwatch() != null) { - return palette.getLightMutedSwatch(); - } else { - return new Palette.Swatch(Color.BLACK, 1); - } - } - - private static Palette.Swatch getBestPaletteSwatchFrom(Palette palette) { - if (palette != null) { - if (palette.getVibrantSwatch() != null) { - return palette.getVibrantSwatch(); - } else if (palette.getMutedSwatch() != null) { - return palette.getMutedSwatch(); - } else if (palette.getDarkVibrantSwatch() != null) { - return palette.getDarkVibrantSwatch(); - } else if (palette.getDarkMutedSwatch() != null) { - return palette.getDarkMutedSwatch(); - } else if (palette.getLightVibrantSwatch() != null) { - return palette.getLightVibrantSwatch(); - } else if (palette.getLightMutedSwatch() != null) { - return palette.getLightMutedSwatch(); - } else if (!palette.getSwatches().isEmpty()) { - return getBestPaletteSwatchFrom(palette.getSwatches()); - } - } - return null; - } - - private static Palette.Swatch getBestPaletteSwatchFrom(List swatches) { - if (swatches == null) { - return null; - } - return Collections.max(swatches, (opt1, opt2) -> { - int a = opt1 == null ? 0 : opt1.getPopulation(); - int b = opt2 == null ? 0 : opt2.getPopulation(); - return a - b; + return Collections.max( + swatches, + (opt1, opt2) -> { + int a = opt1 == null ? 0 : opt1.getPopulation(); + int b = opt2 == null ? 0 : opt2.getPopulation(); + return a - b; }); + } + + public static int getDominantColor(Bitmap bitmap, int defaultFooterColor) { + List swatchesTemp = Palette.from(bitmap).generate().getSwatches(); + List swatches = new ArrayList(swatchesTemp); + Collections.sort( + swatches, (swatch1, swatch2) -> swatch2.getPopulation() - swatch1.getPopulation()); + return swatches.size() > 0 ? swatches.get(0).getRgb() : defaultFooterColor; + } + + @ColorInt + public static int shiftBackgroundColorForLightText(@ColorInt int backgroundColor) { + while (ColorUtil.INSTANCE.isColorLight(backgroundColor)) { + backgroundColor = ColorUtil.INSTANCE.darkenColor(backgroundColor); + } + return backgroundColor; + } + + @ColorInt + public static int shiftBackgroundColorForDarkText(@ColorInt int backgroundColor) { + int color = backgroundColor; + while (!ColorUtil.INSTANCE.isColorLight(backgroundColor)) { + color = ColorUtil.INSTANCE.lightenColor(backgroundColor); + } + return color; + } + + private static class SwatchComparator implements Comparator { + + private static SwatchComparator sInstance; + + static SwatchComparator getInstance() { + if (sInstance == null) { + sInstance = new SwatchComparator(); + } + return sInstance; } - - public static int getDominantColor(Bitmap bitmap, int defaultFooterColor) { - List swatchesTemp = Palette.from(bitmap).generate().getSwatches(); - List swatches = new ArrayList(swatchesTemp); - Collections.sort(swatches, (swatch1, swatch2) -> swatch2.getPopulation() - swatch1.getPopulation()); - return swatches.size() > 0 ? swatches.get(0).getRgb() : defaultFooterColor; - } - - @ColorInt - public static int shiftBackgroundColorForLightText(@ColorInt int backgroundColor) { - while (ColorUtil.INSTANCE.isColorLight(backgroundColor)) { - backgroundColor = ColorUtil.INSTANCE.darkenColor(backgroundColor); - } - return backgroundColor; - } - - @ColorInt - public static int shiftBackgroundColorForDarkText(@ColorInt int backgroundColor) { - int color = backgroundColor; - while (!ColorUtil.INSTANCE.isColorLight(backgroundColor)) { - color = ColorUtil.INSTANCE.lightenColor(backgroundColor); - } - return color; - } - - private static class SwatchComparator implements Comparator { - - private static SwatchComparator sInstance; - - static SwatchComparator getInstance() { - if (sInstance == null) { - sInstance = new SwatchComparator(); - } - return sInstance; - } - - @Override - public int compare(Palette.Swatch lhs, Palette.Swatch rhs) { - return lhs.getPopulation() - rhs.getPopulation(); - } + @Override + public int compare(Palette.Swatch lhs, Palette.Swatch rhs) { + return lhs.getPopulation() - rhs.getPopulation(); } + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/util/RetroUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/RetroUtil.java index f1b1c665..61630ce2 100755 --- a/app/src/main/java/code/name/monkey/retromusic/util/RetroUtil.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/RetroUtil.java @@ -35,165 +35,176 @@ import android.view.View; import android.view.Window; import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; - import androidx.annotation.ColorInt; import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat; - -import java.text.DecimalFormat; - import code.name.monkey.appthemehelper.util.TintHelper; import code.name.monkey.retromusic.App; +import java.text.DecimalFormat; public class RetroUtil { - private static final int[] TEMP_ARRAY = new int[1]; + private static final int[] TEMP_ARRAY = new int[1]; - private static final String SHOW_NAV_BAR_RES_NAME = "config_showNavigationBar"; + private static final String SHOW_NAV_BAR_RES_NAME = "config_showNavigationBar"; - public static int calculateNoOfColumns(@NonNull Context context) { - DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); - float dpWidth = displayMetrics.widthPixels / displayMetrics.density; - return (int) (dpWidth / 180); + public static int calculateNoOfColumns(@NonNull Context context) { + DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); + float dpWidth = displayMetrics.widthPixels / displayMetrics.density; + return (int) (dpWidth / 180); + } + + @NonNull + public static Bitmap createBitmap(@NonNull Drawable drawable, float sizeMultiplier) { + Bitmap bitmap = + Bitmap.createBitmap( + (int) (drawable.getIntrinsicWidth() * sizeMultiplier), + (int) (drawable.getIntrinsicHeight() * sizeMultiplier), + Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(bitmap); + drawable.setBounds(0, 0, c.getWidth(), c.getHeight()); + drawable.draw(c); + return bitmap; + } + + public static String formatValue(float value) { + String[] arr = {"", "K", "M", "B", "T", "P", "E"}; + int index = 0; + while ((value / 1000) >= 1) { + value = value / 1000; + index++; } + DecimalFormat decimalFormat = new DecimalFormat("#.##"); + return String.format("%s %s", decimalFormat.format(value), arr[index]); + } - @NonNull - public static Bitmap createBitmap(@NonNull Drawable drawable, float sizeMultiplier) { - Bitmap bitmap = Bitmap.createBitmap((int) (drawable.getIntrinsicWidth() * sizeMultiplier), - (int) (drawable.getIntrinsicHeight() * sizeMultiplier), Bitmap.Config.ARGB_8888); - Canvas c = new Canvas(bitmap); - drawable.setBounds(0, 0, c.getWidth(), c.getHeight()); - drawable.draw(c); - return bitmap; + public static float frequencyCount(int frequency) { + return (float) (frequency / 1000.0); + } + + public static Point getScreenSize(@NonNull Context c) { + Display display = null; + if (c.getSystemService(Context.WINDOW_SERVICE) != null) { + display = ((WindowManager) c.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); } + Point size = new Point(); + if (display != null) { + display.getSize(size); + } + return size; + } - public static String formatValue(float value) { - String[] arr = {"", "K", "M", "B", "T", "P", "E"}; - int index = 0; - while ((value / 1000) >= 1) { - value = value / 1000; - index++; + public static int getStatusBarHeight() { + int result = 0; + int resourceId = + App.Companion.getContext() + .getResources() + .getIdentifier("status_bar_height", "dimen", "android"); + if (resourceId > 0) { + result = App.Companion.getContext().getResources().getDimensionPixelSize(resourceId); + } + return result; + } + + @Nullable + public static Drawable getTintedVectorDrawable( + @NonNull Context context, @DrawableRes int id, @ColorInt int color) { + return TintHelper.createTintedDrawable( + getVectorDrawable(context.getResources(), id, context.getTheme()), color); + } + + @Nullable + public static Drawable getTintedVectorDrawable( + @NonNull Resources res, + @DrawableRes int resId, + @Nullable Resources.Theme theme, + @ColorInt int color) { + return TintHelper.createTintedDrawable(getVectorDrawable(res, resId, theme), color); + } + + @Nullable + public static Drawable getVectorDrawable( + @NonNull Resources res, @DrawableRes int resId, @Nullable Resources.Theme theme) { + if (Build.VERSION.SDK_INT >= 21) { + return res.getDrawable(resId, theme); + } + return VectorDrawableCompat.create(res, resId, theme); + } + + public static void hideSoftKeyboard(@Nullable Activity activity) { + if (activity != null) { + View currentFocus = activity.getCurrentFocus(); + if (currentFocus != null) { + InputMethodManager inputMethodManager = + (InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE); + if (inputMethodManager != null) { + inputMethodManager.hideSoftInputFromWindow(currentFocus.getWindowToken(), 0); } - DecimalFormat decimalFormat = new DecimalFormat("#.##"); - return String.format("%s %s", decimalFormat.format(value), arr[index]); + } } + } - public static float frequencyCount(int frequency) { - return (float) (frequency / 1000.0); + public static boolean isAllowedToDownloadMetadata(final @NonNull Context context) { + switch (PreferenceUtil.INSTANCE.getAutoDownloadImagesPolicy()) { + case "always": + return true; + case "only_wifi": + final ConnectivityManager connectivityManager = + (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo netInfo = connectivityManager.getActiveNetworkInfo(); + return netInfo != null + && netInfo.getType() == ConnectivityManager.TYPE_WIFI + && netInfo.isConnectedOrConnecting(); + case "never": + default: + return false; } + } - public static Point getScreenSize(@NonNull Context c) { - Display display = null; - if (c.getSystemService(Context.WINDOW_SERVICE) != null) { - display = ((WindowManager) c.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); - } - Point size = new Point(); - if (display != null) { - display.getSize(size); - } - return size; - } + public static boolean isLandscape() { + return App.Companion.getContext().getResources().getConfiguration().orientation + == Configuration.ORIENTATION_LANDSCAPE; + } - public static int getStatusBarHeight() { - int result = 0; - int resourceId = App.Companion.getContext().getResources() - .getIdentifier("status_bar_height", "dimen", "android"); - if (resourceId > 0) { - result = App.Companion.getContext().getResources().getDimensionPixelSize(resourceId); - } - return result; - } + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) + public static boolean isRTL(@NonNull Context context) { + Configuration config = context.getResources().getConfiguration(); + return config.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; + } - @Nullable - public static Drawable getTintedVectorDrawable(@NonNull Context context, @DrawableRes int id, - @ColorInt int color) { - return TintHelper.createTintedDrawable( - getVectorDrawable(context.getResources(), id, context.getTheme()), color); - } + public static boolean isTablet() { + return App.Companion.getContext().getResources().getConfiguration().smallestScreenWidthDp + >= 600; + } - @Nullable - public static Drawable getTintedVectorDrawable(@NonNull Resources res, @DrawableRes int resId, - @Nullable Resources.Theme theme, @ColorInt int color) { - return TintHelper.createTintedDrawable(getVectorDrawable(res, resId, theme), color); - } + public static void openUrl(@NonNull Activity context, @NonNull String str) { + Intent intent = new Intent("android.intent.action.VIEW"); + intent.setData(Uri.parse(str)); + intent.setFlags(268435456); + context.startActivity(intent); + } - @Nullable - public static Drawable getVectorDrawable(@NonNull Resources res, @DrawableRes int resId, - @Nullable Resources.Theme theme) { - if (Build.VERSION.SDK_INT >= 21) { - return res.getDrawable(resId, theme); - } - return VectorDrawableCompat.create(res, resId, theme); - } + public static void setAllowDrawUnderNavigationBar(Window window) { + window.setNavigationBarColor(Color.TRANSPARENT); + window + .getDecorView() + .setSystemUiVisibility( + Build.VERSION.SDK_INT >= Build.VERSION_CODES.O + ? View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + : View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); + } - public static void hideSoftKeyboard(@Nullable Activity activity) { - if (activity != null) { - View currentFocus = activity.getCurrentFocus(); - if (currentFocus != null) { - InputMethodManager inputMethodManager = (InputMethodManager) activity - .getSystemService(Activity.INPUT_METHOD_SERVICE); - if (inputMethodManager != null) { - inputMethodManager.hideSoftInputFromWindow(currentFocus.getWindowToken(), 0); - } - } - } - } - - public static boolean isAllowedToDownloadMetadata(final @NonNull Context context) { - switch (PreferenceUtil.INSTANCE.getAutoDownloadImagesPolicy()) { - case "always": - return true; - case "only_wifi": - final ConnectivityManager connectivityManager = (ConnectivityManager) context - .getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo netInfo = connectivityManager.getActiveNetworkInfo(); - return netInfo != null && netInfo.getType() == ConnectivityManager.TYPE_WIFI && netInfo - .isConnectedOrConnecting(); - case "never": - default: - return false; - } - } - - public static boolean isLandscape() { - return App.Companion.getContext().getResources().getConfiguration().orientation - == Configuration.ORIENTATION_LANDSCAPE; - } - - @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) - public static boolean isRTL(@NonNull Context context) { - Configuration config = context.getResources().getConfiguration(); - return config.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; - } - - public static boolean isTablet() { - return App.Companion.getContext().getResources().getConfiguration().smallestScreenWidthDp >= 600; - } - - public static void openUrl(@NonNull Activity context, @NonNull String str) { - Intent intent = new Intent("android.intent.action.VIEW"); - intent.setData(Uri.parse(str)); - intent.setFlags(268435456); - context.startActivity(intent); - } - - public static void setAllowDrawUnderNavigationBar(Window window) { - window.setNavigationBarColor(Color.TRANSPARENT); - window.getDecorView().setSystemUiVisibility(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? - View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION : - View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - ); - } - - public static void setAllowDrawUnderStatusBar(@NonNull Window window) { - window.getDecorView().setSystemUiVisibility( - View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); - } + public static void setAllowDrawUnderStatusBar(@NonNull Window window) { + window + .getDecorView() + .setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/util/RippleUtils.java b/app/src/main/java/code/name/monkey/retromusic/util/RippleUtils.java index 6a61c5db..53916675 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/RippleUtils.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/RippleUtils.java @@ -5,142 +5,142 @@ import android.content.res.ColorStateList; import android.graphics.Color; import android.os.Build; import android.util.StateSet; - import androidx.annotation.ColorInt; import androidx.annotation.Nullable; import androidx.core.graphics.ColorUtils; public class RippleUtils { - public static final boolean USE_FRAMEWORK_RIPPLE = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; - private static final int[] PRESSED_STATE_SET = { - android.R.attr.state_pressed, - }; - private static final int[] HOVERED_FOCUSED_STATE_SET = { - android.R.attr.state_hovered, android.R.attr.state_focused, - }; - private static final int[] FOCUSED_STATE_SET = { - android.R.attr.state_focused, - }; - private static final int[] HOVERED_STATE_SET = { - android.R.attr.state_hovered, - }; + public static final boolean USE_FRAMEWORK_RIPPLE = + Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; + private static final int[] PRESSED_STATE_SET = { + android.R.attr.state_pressed, + }; + private static final int[] HOVERED_FOCUSED_STATE_SET = { + android.R.attr.state_hovered, android.R.attr.state_focused, + }; + private static final int[] FOCUSED_STATE_SET = { + android.R.attr.state_focused, + }; + private static final int[] HOVERED_STATE_SET = { + android.R.attr.state_hovered, + }; - private static final int[] SELECTED_PRESSED_STATE_SET = { - android.R.attr.state_selected, android.R.attr.state_pressed, - }; - private static final int[] SELECTED_HOVERED_FOCUSED_STATE_SET = { - android.R.attr.state_selected, android.R.attr.state_hovered, android.R.attr.state_focused, - }; - private static final int[] SELECTED_FOCUSED_STATE_SET = { - android.R.attr.state_selected, android.R.attr.state_focused, - }; - private static final int[] SELECTED_HOVERED_STATE_SET = { - android.R.attr.state_selected, android.R.attr.state_hovered, - }; - private static final int[] SELECTED_STATE_SET = { - android.R.attr.state_selected, - }; + private static final int[] SELECTED_PRESSED_STATE_SET = { + android.R.attr.state_selected, android.R.attr.state_pressed, + }; + private static final int[] SELECTED_HOVERED_FOCUSED_STATE_SET = { + android.R.attr.state_selected, android.R.attr.state_hovered, android.R.attr.state_focused, + }; + private static final int[] SELECTED_FOCUSED_STATE_SET = { + android.R.attr.state_selected, android.R.attr.state_focused, + }; + private static final int[] SELECTED_HOVERED_STATE_SET = { + android.R.attr.state_selected, android.R.attr.state_hovered, + }; + private static final int[] SELECTED_STATE_SET = { + android.R.attr.state_selected, + }; - private static final int[] ENABLED_PRESSED_STATE_SET = { - android.R.attr.state_enabled, android.R.attr.state_pressed - }; + private static final int[] ENABLED_PRESSED_STATE_SET = { + android.R.attr.state_enabled, android.R.attr.state_pressed + }; - public static ColorStateList convertToRippleDrawableColor(@Nullable ColorStateList rippleColor) { - if (USE_FRAMEWORK_RIPPLE) { - int size = 2; + public static ColorStateList convertToRippleDrawableColor(@Nullable ColorStateList rippleColor) { + if (USE_FRAMEWORK_RIPPLE) { + int size = 2; - final int[][] states = new int[size][]; - final int[] colors = new int[size]; - int i = 0; + final int[][] states = new int[size][]; + final int[] colors = new int[size]; + int i = 0; - // Ideally we would define a different composite color for each state, but that causes the - // ripple animation to abort prematurely. - // So we only allow two base states: selected, and non-selected. For each base state, we only - // base the ripple composite on its pressed state. + // Ideally we would define a different composite color for each state, but that causes the + // ripple animation to abort prematurely. + // So we only allow two base states: selected, and non-selected. For each base state, we only + // base the ripple composite on its pressed state. - // Selected base state. - states[i] = SELECTED_STATE_SET; - colors[i] = getColorForState(rippleColor, SELECTED_PRESSED_STATE_SET); - i++; + // Selected base state. + states[i] = SELECTED_STATE_SET; + colors[i] = getColorForState(rippleColor, SELECTED_PRESSED_STATE_SET); + i++; - // Non-selected base state. - states[i] = StateSet.NOTHING; - colors[i] = getColorForState(rippleColor, PRESSED_STATE_SET); - i++; + // Non-selected base state. + states[i] = StateSet.NOTHING; + colors[i] = getColorForState(rippleColor, PRESSED_STATE_SET); + i++; - return new ColorStateList(states, colors); - } else { - int size = 10; + return new ColorStateList(states, colors); + } else { + int size = 10; - final int[][] states = new int[size][]; - final int[] colors = new int[size]; - int i = 0; + final int[][] states = new int[size][]; + final int[] colors = new int[size]; + int i = 0; - states[i] = SELECTED_PRESSED_STATE_SET; - colors[i] = getColorForState(rippleColor, SELECTED_PRESSED_STATE_SET); - i++; + states[i] = SELECTED_PRESSED_STATE_SET; + colors[i] = getColorForState(rippleColor, SELECTED_PRESSED_STATE_SET); + i++; - states[i] = SELECTED_HOVERED_FOCUSED_STATE_SET; - colors[i] = getColorForState(rippleColor, SELECTED_HOVERED_FOCUSED_STATE_SET); - i++; + states[i] = SELECTED_HOVERED_FOCUSED_STATE_SET; + colors[i] = getColorForState(rippleColor, SELECTED_HOVERED_FOCUSED_STATE_SET); + i++; - states[i] = SELECTED_FOCUSED_STATE_SET; - colors[i] = getColorForState(rippleColor, SELECTED_FOCUSED_STATE_SET); - i++; + states[i] = SELECTED_FOCUSED_STATE_SET; + colors[i] = getColorForState(rippleColor, SELECTED_FOCUSED_STATE_SET); + i++; - states[i] = SELECTED_HOVERED_STATE_SET; - colors[i] = getColorForState(rippleColor, SELECTED_HOVERED_STATE_SET); - i++; + states[i] = SELECTED_HOVERED_STATE_SET; + colors[i] = getColorForState(rippleColor, SELECTED_HOVERED_STATE_SET); + i++; - // Checked state. - states[i] = SELECTED_STATE_SET; - colors[i] = Color.TRANSPARENT; - i++; + // Checked state. + states[i] = SELECTED_STATE_SET; + colors[i] = Color.TRANSPARENT; + i++; - states[i] = PRESSED_STATE_SET; - colors[i] = getColorForState(rippleColor, PRESSED_STATE_SET); - i++; + states[i] = PRESSED_STATE_SET; + colors[i] = getColorForState(rippleColor, PRESSED_STATE_SET); + i++; - states[i] = HOVERED_FOCUSED_STATE_SET; - colors[i] = getColorForState(rippleColor, HOVERED_FOCUSED_STATE_SET); - i++; + states[i] = HOVERED_FOCUSED_STATE_SET; + colors[i] = getColorForState(rippleColor, HOVERED_FOCUSED_STATE_SET); + i++; - states[i] = FOCUSED_STATE_SET; - colors[i] = getColorForState(rippleColor, FOCUSED_STATE_SET); - i++; + states[i] = FOCUSED_STATE_SET; + colors[i] = getColorForState(rippleColor, FOCUSED_STATE_SET); + i++; - states[i] = HOVERED_STATE_SET; - colors[i] = getColorForState(rippleColor, HOVERED_STATE_SET); - i++; + states[i] = HOVERED_STATE_SET; + colors[i] = getColorForState(rippleColor, HOVERED_STATE_SET); + i++; - // Default state. - states[i] = StateSet.NOTHING; - colors[i] = Color.TRANSPARENT; - i++; + // Default state. + states[i] = StateSet.NOTHING; + colors[i] = Color.TRANSPARENT; + i++; - return new ColorStateList(states, colors); - } + return new ColorStateList(states, colors); } + } - @ColorInt - private static int getColorForState(@Nullable ColorStateList rippleColor, int[] state) { - int color; - if (rippleColor != null) { - color = rippleColor.getColorForState(state, rippleColor.getDefaultColor()); - } else { - color = Color.TRANSPARENT; - } - return USE_FRAMEWORK_RIPPLE ? doubleAlpha(color) : color; + @ColorInt + private static int getColorForState(@Nullable ColorStateList rippleColor, int[] state) { + int color; + if (rippleColor != null) { + color = rippleColor.getColorForState(state, rippleColor.getDefaultColor()); + } else { + color = Color.TRANSPARENT; } + return USE_FRAMEWORK_RIPPLE ? doubleAlpha(color) : color; + } - /** - * On API 21+, the framework composites a ripple color onto the display at about 50% opacity. - * Since we are providing precise ripple colors, cancel that out by doubling the opacity here. - */ - @ColorInt - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - private static int doubleAlpha(@ColorInt int color) { - int alpha = Math.min(2 * Color.alpha(color), 255); - return ColorUtils.setAlphaComponent(color, alpha); - } + /** + * On API 21+, the framework composites a ripple color onto the display at about 50% opacity. + * Since we are providing precise ripple colors, cancel that out by doubling the opacity here. + */ + @ColorInt + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + private static int doubleAlpha(@ColorInt int color) { + int alpha = Math.min(2 * Color.alpha(color), 255); + return ColorUtils.setAlphaComponent(color, alpha); + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/util/SAFUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/SAFUtil.java index 10da6d03..64150ed0 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/SAFUtil.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/SAFUtil.java @@ -26,278 +26,283 @@ 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 code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.model.Song; 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; +import org.jaudiotagger.audio.AudioFile; +import org.jaudiotagger.audio.exceptions.CannotWriteException; +import org.jaudiotagger.audio.generic.Utils; public class SAFUtil { - public static final String TAG = SAFUtil.class.getSimpleName(); - public static final String SEPARATOR = "###/SAF/###"; + 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 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(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 paths) { + for (String path : paths) { + if (isSAFRequired(path)) return true; + } + return false; + } + + public static boolean isSAFRequiredForSongs(List 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.INSTANCE.setSafSdCardUri(uri.toString()); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public static boolean isTreeUriSaved(Context context) { + return !TextUtils.isEmpty(PreferenceUtil.INSTANCE.getSafSdCardUri()); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public static boolean isSDCardAccessGranted(Context context) { + if (!isTreeUriSaved(context)) return false; + + String sdcardUri = PreferenceUtil.INSTANCE.getSafSdCardUri(); + + List perms = context.getContentResolver().getPersistedUriPermissions(); + for (UriPermission perm : perms) { + if (perm.getUri().toString().equals(sdcardUri) && perm.isWritePermission()) return true; } - public static boolean isSAFRequired(String path) { - return isSAFRequired(new File(path)); + 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 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(); + } } - public static boolean isSAFRequired(AudioFile audio) { - return isSAFRequired(audio.getFile()); + 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; } - public static boolean isSAFRequired(Song song) { - return isSAFRequired(song.getData()); + if (isTreeUriSaved(context)) { + List pathSegments = + new ArrayList<>(Arrays.asList(audio.getFile().getAbsolutePath().split("/"))); + Uri sdcard = Uri.parse(PreferenceUtil.INSTANCE.getSafSdCardUri()); + uri = findDocument(DocumentFile.fromTreeUri(context, sdcard), pathSegments); } - public static boolean isSAFRequired(List paths) { - for (String path : paths) { - if (isSAFRequired(path)) return true; - } - return false; + if (uri == null) { + uri = safUri; } - public static boolean isSAFRequiredForSongs(List songs) { - for (Song song : songs) { - if (isSAFRequired(song)) return true; - } - return false; + if (uri == null) { + Log.e(TAG, "writeSAF: Can't get SAF URI"); + toast(context, context.getString(R.string.saf_error_uri)); + return; } - @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); + 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; } - @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); + if (isTreeUriSaved(context)) { + List pathSegments = new ArrayList<>(Arrays.asList(path.split("/"))); + Uri sdcard = Uri.parse(PreferenceUtil.INSTANCE.getSafSdCardUri()); + uri = findDocument(DocumentFile.fromTreeUri(context, sdcard), pathSegments); } - @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); + if (uri == null) { + uri = safUri; } - @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); + if (uri == null) { + Log.e(TAG, "deleteSAF: Can't get SAF URI"); + toast(context, context.getString(R.string.saf_error_uri)); + return; } - @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.INSTANCE.setSafSdCardUri(uri.toString()); + 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())); } + } - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - public static boolean isTreeUriSaved(Context context) { - return !TextUtils.isEmpty(PreferenceUtil.INSTANCE.getSafSdCardUri()); + 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()); } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - public static boolean isSDCardAccessGranted(Context context) { - if (!isTreeUriSaved(context)) return false; - - String sdcardUri = PreferenceUtil.INSTANCE.getSafSdCardUri(); - - List 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 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 pathSegments = new ArrayList<>(Arrays.asList(audio.getFile().getAbsolutePath().split("/"))); - Uri sdcard = Uri.parse(PreferenceUtil.INSTANCE.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 pathSegments = new ArrayList<>(Arrays.asList(path.split("/"))); - Uri sdcard = Uri.parse(PreferenceUtil.INSTANCE.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()); - } - } - + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/util/SwipeAndDragHelper.java b/app/src/main/java/code/name/monkey/retromusic/util/SwipeAndDragHelper.java index 1c82c7aa..2ab25e18 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/SwipeAndDragHelper.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/SwipeAndDragHelper.java @@ -15,58 +15,58 @@ package code.name.monkey.retromusic.util; import android.graphics.Canvas; - import androidx.annotation.NonNull; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.RecyclerView; public class SwipeAndDragHelper extends ItemTouchHelper.Callback { - private ActionCompletionContract contract; + private ActionCompletionContract contract; - public SwipeAndDragHelper(@NonNull ActionCompletionContract contract) { - this.contract = contract; - } - - @Override - public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { - int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN; - return makeMovementFlags(dragFlags, 0); - } - - @Override - public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { - contract.onViewMoved(viewHolder.getLayoutPosition(), target.getLayoutPosition()); - return true; - } - - @Override - public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { - } - - @Override - public boolean isLongPressDragEnabled() { - return false; - } - - @Override - public void onChildDraw(Canvas c, - RecyclerView recyclerView, - RecyclerView.ViewHolder viewHolder, - float dX, - float dY, - int actionState, - boolean isCurrentlyActive) { - if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) { - float alpha = 1 - (Math.abs(dX) / recyclerView.getWidth()); - viewHolder.itemView.setAlpha(alpha); - } - super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); - } - - public interface ActionCompletionContract { - void onViewMoved(int oldPosition, int newPosition); + public SwipeAndDragHelper(@NonNull ActionCompletionContract contract) { + this.contract = contract; + } + + @Override + public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { + int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN; + return makeMovementFlags(dragFlags, 0); + } + + @Override + public boolean onMove( + RecyclerView recyclerView, + RecyclerView.ViewHolder viewHolder, + RecyclerView.ViewHolder target) { + contract.onViewMoved(viewHolder.getLayoutPosition(), target.getLayoutPosition()); + return true; + } + + @Override + public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {} + + @Override + public boolean isLongPressDragEnabled() { + return false; + } + + @Override + public void onChildDraw( + Canvas c, + RecyclerView recyclerView, + RecyclerView.ViewHolder viewHolder, + float dX, + float dY, + int actionState, + boolean isCurrentlyActive) { + if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) { + float alpha = 1 - (Math.abs(dX) / recyclerView.getWidth()); + viewHolder.itemView.setAlpha(alpha); } + super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); + } + public interface ActionCompletionContract { + void onViewMoved(int oldPosition, int newPosition); + } } - diff --git a/app/src/main/java/code/name/monkey/retromusic/util/TempUtils.java b/app/src/main/java/code/name/monkey/retromusic/util/TempUtils.java index 2a0fac4e..1f5f1e2b 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/TempUtils.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/TempUtils.java @@ -14,69 +14,67 @@ package code.name.monkey.retromusic.util; -/** - * @author Hemanth S (h4h13). - */ +/** @author Hemanth S (h4h13). */ public class TempUtils { - // Enums - public static final int TEMPO_STROLL = 0; - public static final int TEMPO_WALK = 1; - public static final int TEMPO_LIGHT_JOG = 2; - public static final int TEMPO_JOG = 3; - public static final int TEMPO_RUN = 4; - public static final int TEMPO_SPRINT = 5; - public static final int TEMPO_UNKNOWN = 6; + // Enums + public static final int TEMPO_STROLL = 0; + public static final int TEMPO_WALK = 1; + public static final int TEMPO_LIGHT_JOG = 2; + public static final int TEMPO_JOG = 3; + public static final int TEMPO_RUN = 4; + public static final int TEMPO_SPRINT = 5; + public static final int TEMPO_UNKNOWN = 6; - // take BPM as an int - public static int getTempoFromBPM(int bpm) { + // take BPM as an int + public static int getTempoFromBPM(int bpm) { - // STROLL less than 60 - if (bpm < 60) { - return TEMPO_STROLL; - } - - // WALK between 60 and 70, or between 120 and 140 - else if (bpm < 70 || bpm >= 120 && bpm < 140) { - return TEMPO_WALK; - } - - // LIGHT_JOG between 70 and 80, or between 140 and 160 - else if (bpm < 80 || bpm >= 140 && bpm < 160) { - return TEMPO_LIGHT_JOG; - } - - // JOG between 80 and 90, or between 160 and 180 - else if (bpm < 90 || bpm >= 160 && bpm < 180) { - return TEMPO_JOG; - } - - // RUN between 90 and 100, or between 180 and 200 - else if (bpm < 100 || bpm >= 180 && bpm < 200) { - return TEMPO_RUN; - } - - // SPRINT between 100 and 120 - else if (bpm < 120) { - return TEMPO_SPRINT; - } - - // UNKNOWN - else { - return TEMPO_UNKNOWN; - } + // STROLL less than 60 + if (bpm < 60) { + return TEMPO_STROLL; } - // take BPM as a string - public static int getTempoFromBPM(String bpm) { - // cast to an int from string - try { - // convert the string to an int - return getTempoFromBPM(Integer.parseInt(bpm.trim())); - } catch (NumberFormatException nfe) { - - // - return TEMPO_UNKNOWN; - } + // WALK between 60 and 70, or between 120 and 140 + else if (bpm < 70 || bpm >= 120 && bpm < 140) { + return TEMPO_WALK; } + + // LIGHT_JOG between 70 and 80, or between 140 and 160 + else if (bpm < 80 || bpm >= 140 && bpm < 160) { + return TEMPO_LIGHT_JOG; + } + + // JOG between 80 and 90, or between 160 and 180 + else if (bpm < 90 || bpm >= 160 && bpm < 180) { + return TEMPO_JOG; + } + + // RUN between 90 and 100, or between 180 and 200 + else if (bpm < 100 || bpm >= 180 && bpm < 200) { + return TEMPO_RUN; + } + + // SPRINT between 100 and 120 + else if (bpm < 120) { + return TEMPO_SPRINT; + } + + // UNKNOWN + else { + return TEMPO_UNKNOWN; + } + } + + // take BPM as a string + public static int getTempoFromBPM(String bpm) { + // cast to an int from string + try { + // convert the string to an int + return getTempoFromBPM(Integer.parseInt(bpm.trim())); + } catch (NumberFormatException nfe) { + + // + return TEMPO_UNKNOWN; + } + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/util/color/ImageUtils.java b/app/src/main/java/code/name/monkey/retromusic/util/color/ImageUtils.java index b22bab5d..53dfc3e5 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/color/ImageUtils.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/color/ImageUtils.java @@ -30,118 +30,111 @@ import android.graphics.drawable.Drawable; * @hide */ public class ImageUtils { - // Amount (max is 255) that two channels can differ before the color is no longer "gray". - private static final int TOLERANCE = 20; - // Alpha amount for which values below are considered transparent. - private static final int ALPHA_TOLERANCE = 50; - // Size of the smaller bitmap we're actually going to scan. - private static final int COMPACT_BITMAP_SIZE = 64; // pixels - private final Matrix mTempMatrix = new Matrix(); - private int[] mTempBuffer; - private Bitmap mTempCompactBitmap; - private Canvas mTempCompactBitmapCanvas; - private Paint mTempCompactBitmapPaint; + // Amount (max is 255) that two channels can differ before the color is no longer "gray". + private static final int TOLERANCE = 20; + // Alpha amount for which values below are considered transparent. + private static final int ALPHA_TOLERANCE = 50; + // Size of the smaller bitmap we're actually going to scan. + private static final int COMPACT_BITMAP_SIZE = 64; // pixels + private final Matrix mTempMatrix = new Matrix(); + private int[] mTempBuffer; + private Bitmap mTempCompactBitmap; + private Canvas mTempCompactBitmapCanvas; + private Paint mTempCompactBitmapPaint; - /** - * Classifies a color as grayscale or not. Grayscale here means "very close to a perfect - * gray"; if all three channels are approximately equal, this will return true. - *

- * Note that really transparent colors are always grayscale. - */ - public static boolean isGrayscale(int color) { - int alpha = 0xFF & (color >> 24); - if (alpha < ALPHA_TOLERANCE) { - return true; - } - int r = 0xFF & (color >> 16); - int g = 0xFF & (color >> 8); - int b = 0xFF & color; - return Math.abs(r - g) < TOLERANCE - && Math.abs(r - b) < TOLERANCE - && Math.abs(g - b) < TOLERANCE; + /** + * Classifies a color as grayscale or not. Grayscale here means "very close to a perfect gray"; if + * all three channels are approximately equal, this will return true. + * + *

Note that really transparent colors are always grayscale. + */ + public static boolean isGrayscale(int color) { + int alpha = 0xFF & (color >> 24); + if (alpha < ALPHA_TOLERANCE) { + return true; } + int r = 0xFF & (color >> 16); + int g = 0xFF & (color >> 8); + int b = 0xFF & color; + return Math.abs(r - g) < TOLERANCE + && Math.abs(r - b) < TOLERANCE + && Math.abs(g - b) < TOLERANCE; + } - /** - * Convert a drawable to a bitmap, scaled to fit within maxWidth and maxHeight. - */ - public static Bitmap buildScaledBitmap(Drawable drawable, int maxWidth, - int maxHeight) { - if (drawable == null) { - return null; - } - int originalWidth = drawable.getIntrinsicWidth(); - int originalHeight = drawable.getIntrinsicHeight(); - if ((originalWidth <= maxWidth) && (originalHeight <= maxHeight) && - (drawable instanceof BitmapDrawable)) { - return ((BitmapDrawable) drawable).getBitmap(); - } - if (originalHeight <= 0 || originalWidth <= 0) { - return null; - } - // create a new bitmap, scaling down to fit the max dimensions of - // a large notification icon if necessary - float ratio = Math.min((float) maxWidth / (float) originalWidth, - (float) maxHeight / (float) originalHeight); - ratio = Math.min(1.0f, ratio); - int scaledWidth = (int) (ratio * originalWidth); - int scaledHeight = (int) (ratio * originalHeight); - Bitmap result = Bitmap.createBitmap(scaledWidth, scaledHeight, Config.ARGB_8888); - // and paint our app bitmap on it - Canvas canvas = new Canvas(result); - drawable.setBounds(0, 0, scaledWidth, scaledHeight); - drawable.draw(canvas); - return result; + /** Convert a drawable to a bitmap, scaled to fit within maxWidth and maxHeight. */ + public static Bitmap buildScaledBitmap(Drawable drawable, int maxWidth, int maxHeight) { + if (drawable == null) { + return null; } - - /** - * Checks whether a bitmap is grayscale. Grayscale here means "very close to a perfect - * gray". - *

- * Instead of scanning every pixel in the bitmap, we first resize the bitmap to no more than - * COMPACT_BITMAP_SIZE^2 pixels using filtering. The hope is that any non-gray color elements - * will survive the squeezing process, contaminating the result with color. - */ - public boolean isGrayscale(Bitmap bitmap) { - int height = bitmap.getHeight(); - int width = bitmap.getWidth(); - - // shrink to a more manageable (yet hopefully no more or less colorful) size - if (height > COMPACT_BITMAP_SIZE || width > COMPACT_BITMAP_SIZE) { - if (mTempCompactBitmap == null) { - mTempCompactBitmap = Bitmap.createBitmap( - COMPACT_BITMAP_SIZE, COMPACT_BITMAP_SIZE, Config.ARGB_8888 - ); - mTempCompactBitmapCanvas = new Canvas(mTempCompactBitmap); - mTempCompactBitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mTempCompactBitmapPaint.setFilterBitmap(true); - } - mTempMatrix.reset(); - mTempMatrix.setScale( - (float) COMPACT_BITMAP_SIZE / width, - (float) COMPACT_BITMAP_SIZE / height, - 0, 0); - mTempCompactBitmapCanvas.drawColor(0, PorterDuff.Mode.SRC); // select all, erase - mTempCompactBitmapCanvas.drawBitmap(bitmap, mTempMatrix, mTempCompactBitmapPaint); - bitmap = mTempCompactBitmap; - width = height = COMPACT_BITMAP_SIZE; - } - final int size = height * width; - ensureBufferSize(size); - bitmap.getPixels(mTempBuffer, 0, width, 0, 0, width, height); - for (int i = 0; i < size; i++) { - if (!isGrayscale(mTempBuffer[i])) { - return false; - } - } - return true; + int originalWidth = drawable.getIntrinsicWidth(); + int originalHeight = drawable.getIntrinsicHeight(); + if ((originalWidth <= maxWidth) + && (originalHeight <= maxHeight) + && (drawable instanceof BitmapDrawable)) { + return ((BitmapDrawable) drawable).getBitmap(); } - - /** - * Makes sure that {@code mTempBuffer} has at least length {@code size}. - */ - private void ensureBufferSize(int size) { - if (mTempBuffer == null || mTempBuffer.length < size) { - mTempBuffer = new int[size]; - } + if (originalHeight <= 0 || originalWidth <= 0) { + return null; } -} \ No newline at end of file + // create a new bitmap, scaling down to fit the max dimensions of + // a large notification icon if necessary + float ratio = + Math.min( + (float) maxWidth / (float) originalWidth, (float) maxHeight / (float) originalHeight); + ratio = Math.min(1.0f, ratio); + int scaledWidth = (int) (ratio * originalWidth); + int scaledHeight = (int) (ratio * originalHeight); + Bitmap result = Bitmap.createBitmap(scaledWidth, scaledHeight, Config.ARGB_8888); + // and paint our app bitmap on it + Canvas canvas = new Canvas(result); + drawable.setBounds(0, 0, scaledWidth, scaledHeight); + drawable.draw(canvas); + return result; + } + + /** + * Checks whether a bitmap is grayscale. Grayscale here means "very close to a perfect gray". + * + *

Instead of scanning every pixel in the bitmap, we first resize the bitmap to no more than + * COMPACT_BITMAP_SIZE^2 pixels using filtering. The hope is that any non-gray color elements will + * survive the squeezing process, contaminating the result with color. + */ + public boolean isGrayscale(Bitmap bitmap) { + int height = bitmap.getHeight(); + int width = bitmap.getWidth(); + + // shrink to a more manageable (yet hopefully no more or less colorful) size + if (height > COMPACT_BITMAP_SIZE || width > COMPACT_BITMAP_SIZE) { + if (mTempCompactBitmap == null) { + mTempCompactBitmap = + Bitmap.createBitmap(COMPACT_BITMAP_SIZE, COMPACT_BITMAP_SIZE, Config.ARGB_8888); + mTempCompactBitmapCanvas = new Canvas(mTempCompactBitmap); + mTempCompactBitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mTempCompactBitmapPaint.setFilterBitmap(true); + } + mTempMatrix.reset(); + mTempMatrix.setScale( + (float) COMPACT_BITMAP_SIZE / width, (float) COMPACT_BITMAP_SIZE / height, 0, 0); + mTempCompactBitmapCanvas.drawColor(0, PorterDuff.Mode.SRC); // select all, erase + mTempCompactBitmapCanvas.drawBitmap(bitmap, mTempMatrix, mTempCompactBitmapPaint); + bitmap = mTempCompactBitmap; + width = height = COMPACT_BITMAP_SIZE; + } + final int size = height * width; + ensureBufferSize(size); + bitmap.getPixels(mTempBuffer, 0, width, 0, 0, width, height); + for (int i = 0; i < size; i++) { + if (!isGrayscale(mTempBuffer[i])) { + return false; + } + } + return true; + } + + /** Makes sure that {@code mTempBuffer} has at least length {@code size}. */ + private void ensureBufferSize(int size) { + if (mTempBuffer == null || mTempBuffer.length < size) { + mTempBuffer = new int[size]; + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/util/color/MediaNotificationProcessor.java b/app/src/main/java/code/name/monkey/retromusic/util/color/MediaNotificationProcessor.java index cb8482c5..2e1cb4b1 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/color/MediaNotificationProcessor.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/color/MediaNotificationProcessor.java @@ -16,6 +16,8 @@ package code.name.monkey.retromusic.util.color; +import static androidx.core.graphics.ColorUtils.RGBToXYZ; + import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; @@ -23,485 +25,472 @@ import android.graphics.Color; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Handler; - import androidx.annotation.ColorInt; import androidx.annotation.FloatRange; import androidx.annotation.NonNull; import androidx.palette.graphics.Palette; - -import java.util.List; - import code.name.monkey.appthemehelper.util.ATHUtil; import code.name.monkey.appthemehelper.util.ColorUtil; import code.name.monkey.retromusic.R; +import java.util.List; -import static androidx.core.graphics.ColorUtils.RGBToXYZ; - -/** - * A class the processes media notifications and extracts the right text and background colors. - */ +/** A class the processes media notifications and extracts the right text and background colors. */ public class MediaNotificationProcessor { - /** - * The fraction below which we select the vibrant instead of the light/dark vibrant color - */ - private static final float POPULATION_FRACTION_FOR_MORE_VIBRANT = 1.0f; + /** The fraction below which we select the vibrant instead of the light/dark vibrant color */ + private static final float POPULATION_FRACTION_FOR_MORE_VIBRANT = 1.0f; - /** - * Minimum saturation that a muted color must have if there exists if deciding between two - * colors - */ - private static final float MIN_SATURATION_WHEN_DECIDING = 0.19f; + /** + * Minimum saturation that a muted color must have if there exists if deciding between two colors + */ + private static final float MIN_SATURATION_WHEN_DECIDING = 0.19f; - /** - * Minimum fraction that any color must have to be picked up as a text color - */ - private static final double MINIMUM_IMAGE_FRACTION = 0.002; + /** Minimum fraction that any color must have to be picked up as a text color */ + private static final double MINIMUM_IMAGE_FRACTION = 0.002; - /** - * The population fraction to select the dominant color as the text color over a the colored - * ones. - */ - private static final float POPULATION_FRACTION_FOR_DOMINANT = 0.01f; + /** + * The population fraction to select the dominant color as the text color over a the colored ones. + */ + private static final float POPULATION_FRACTION_FOR_DOMINANT = 0.01f; - /** - * The population fraction to select a white or black color as the background over a color. - */ - private static final float POPULATION_FRACTION_FOR_WHITE_OR_BLACK = 2.5f; - private static final float BLACK_MAX_LIGHTNESS = 0.08f; - private static final float WHITE_MIN_LIGHTNESS = 0.90f; - private static final int RESIZE_BITMAP_AREA = 150 * 150; - private static final ThreadLocal TEMP_ARRAY = new ThreadLocal<>(); - /** - * The lightness difference that has to be added to the primary text color to obtain the - * secondary text color when the background is light. - */ - private static final int LIGHTNESS_TEXT_DIFFERENCE_LIGHT = 20; - /** - * The lightness difference that has to be added to the primary text color to obtain the - * secondary text color when the background is dark. - * A bit less then the above value, since it looks better on dark backgrounds. - */ - private static final int LIGHTNESS_TEXT_DIFFERENCE_DARK = -10; - private static final String TAG = "ColorPicking"; - private float[] mFilteredBackgroundHsl = null; - private Palette.Filter mBlackWhiteFilter = new Palette.Filter() { + /** The population fraction to select a white or black color as the background over a color. */ + private static final float POPULATION_FRACTION_FOR_WHITE_OR_BLACK = 2.5f; + + private static final float BLACK_MAX_LIGHTNESS = 0.08f; + private static final float WHITE_MIN_LIGHTNESS = 0.90f; + private static final int RESIZE_BITMAP_AREA = 150 * 150; + private static final ThreadLocal TEMP_ARRAY = new ThreadLocal<>(); + /** + * The lightness difference that has to be added to the primary text color to obtain the secondary + * text color when the background is light. + */ + private static final int LIGHTNESS_TEXT_DIFFERENCE_LIGHT = 20; + /** + * The lightness difference that has to be added to the primary text color to obtain the secondary + * text color when the background is dark. A bit less then the above value, since it looks better + * on dark backgrounds. + */ + private static final int LIGHTNESS_TEXT_DIFFERENCE_DARK = -10; + + private static final String TAG = "ColorPicking"; + private float[] mFilteredBackgroundHsl = null; + private Palette.Filter mBlackWhiteFilter = + new Palette.Filter() { @Override public boolean isAllowed(int rgb, @NonNull float[] hsl) { - return !isWhiteOrBlack(hsl); + return !isWhiteOrBlack(hsl); } - }; - private boolean mIsLowPriority; - private int backgroundColor; - private int secondaryTextColor; - private int primaryTextColor; - private int actionBarColor; - private Drawable drawable; - private Context context; + }; + private boolean mIsLowPriority; + private int backgroundColor; + private int secondaryTextColor; + private int primaryTextColor; + private int actionBarColor; + private Drawable drawable; + private Context context; - public MediaNotificationProcessor(Context context, Drawable drawable) { - this.context = context; - this.drawable = drawable; - getMediaPalette(); + public MediaNotificationProcessor(Context context, Drawable drawable) { + this.context = context; + this.drawable = drawable; + getMediaPalette(); + } + + public MediaNotificationProcessor(Context context, Bitmap bitmap) { + this.context = context; + this.drawable = new BitmapDrawable(context.getResources(), bitmap); + getMediaPalette(); + } + + public MediaNotificationProcessor(Context context) { + this.context = context; + } + + private static boolean isColorLight(int backgroundColor) { + return calculateLuminance(backgroundColor) > 0.5f; + } + + /** + * Returns the luminance of a color as a float between {@code 0.0} and {@code 1.0}. + * + *

Defined as the Y component in the XYZ representation of {@code color}. + */ + @FloatRange(from = 0.0, to = 1.0) + private static double calculateLuminance(@ColorInt int color) { + final double[] result = getTempDouble3Array(); + colorToXYZ(color, result); + // Luminance is the Y component + return result[1] / 100; + } + + private static double[] getTempDouble3Array() { + double[] result = TEMP_ARRAY.get(); + if (result == null) { + result = new double[3]; + TEMP_ARRAY.set(result); } + return result; + } + private static void colorToXYZ(@ColorInt int color, @NonNull double[] outXyz) { + RGBToXYZ(Color.red(color), Color.green(color), Color.blue(color), outXyz); + } - public MediaNotificationProcessor(Context context, Bitmap bitmap) { - this.context = context; - this.drawable = new BitmapDrawable(context.getResources(), bitmap); - getMediaPalette(); - } - - public MediaNotificationProcessor(Context context) { - this.context = context; - } - - private static boolean isColorLight(int backgroundColor) { - return calculateLuminance(backgroundColor) > 0.5f; - } - - /** - * Returns the luminance of a color as a float between {@code 0.0} and {@code 1.0}. - *

Defined as the Y component in the XYZ representation of {@code color}.

- */ - @FloatRange(from = 0.0, to = 1.0) - private static double calculateLuminance(@ColorInt int color) { - final double[] result = getTempDouble3Array(); - colorToXYZ(color, result); - // Luminance is the Y component - return result[1] / 100; - } - - private static double[] getTempDouble3Array() { - double[] result = TEMP_ARRAY.get(); - if (result == null) { - result = new double[3]; - TEMP_ARRAY.set(result); - } - return result; - } - - private static void colorToXYZ(@ColorInt int color, @NonNull double[] outXyz) { - RGBToXYZ(Color.red(color), Color.green(color), Color.blue(color), outXyz); - } - - public void getPaletteAsync(final OnPaletteLoadedListener onPaletteLoadedListener, Drawable drawable) { - this.drawable = drawable; - final Handler handler = new Handler(); - new Thread(new Runnable() { - @Override - public void run() { + public void getPaletteAsync( + final OnPaletteLoadedListener onPaletteLoadedListener, Drawable drawable) { + this.drawable = drawable; + final Handler handler = new Handler(); + new Thread( + new Runnable() { + @Override + public void run() { getMediaPalette(); - handler.post(new Runnable() { - @Override - public void run() { + handler.post( + new Runnable() { + @Override + public void run() { onPaletteLoadedListener.onPaletteLoaded(MediaNotificationProcessor.this); - } - }); - } - }).start(); - - } - - public void getPaletteAsync(OnPaletteLoadedListener onPaletteLoadedListener, Bitmap bitmap) { - this.drawable = new BitmapDrawable(context.getResources(), bitmap); - getPaletteAsync(onPaletteLoadedListener, this.drawable); - } - - /** - * Processes a drawable and calculates the appropriate colors that should - * be used. - */ - private void getMediaPalette() { - Bitmap bitmap; - if (drawable != null) { - // We're transforming the builder, let's make sure all baked in RemoteViews are - // rebuilt! - - int width = drawable.getIntrinsicWidth(); - int height = drawable.getIntrinsicHeight(); - int area = width * height; - if (area > RESIZE_BITMAP_AREA) { - double factor = Math.sqrt((float) RESIZE_BITMAP_AREA / area); - width = (int) (factor * width); - height = (int) (factor * height); - - bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - drawable.setBounds(0, 0, width, height); - drawable.draw(canvas); - - // for the background we only take the left side of the image to ensure - // a smooth transition - Palette.Builder paletteBuilder = Palette.from(bitmap) - .setRegion(0, 0, bitmap.getWidth() / 2, bitmap.getHeight()) - .clearFilters() // we want all colors, red / white / black ones too! - .resizeBitmapArea(RESIZE_BITMAP_AREA); - Palette palette = paletteBuilder.generate(); - backgroundColor = findBackgroundColorAndFilter(drawable); - // we want most of the full region again, slightly shifted to the right - float textColorStartWidthFraction = 0.4f; - paletteBuilder.setRegion((int) (bitmap.getWidth() * textColorStartWidthFraction), 0, - bitmap.getWidth(), - bitmap.getHeight()); - if (mFilteredBackgroundHsl != null) { - paletteBuilder.addFilter(new Palette.Filter() { - @Override - public boolean isAllowed(int rgb, @NonNull float[] hsl) { - // at least 10 degrees hue difference - float diff = Math.abs(hsl[0] - mFilteredBackgroundHsl[0]); - return diff > 10 && diff < 350; - } + } }); - } - paletteBuilder.addFilter(mBlackWhiteFilter); - palette = paletteBuilder.generate(); - int foregroundColor = selectForegroundColor(backgroundColor, palette); - ensureColors(backgroundColor, foregroundColor); - } - } + } + }) + .start(); + } - } + public void getPaletteAsync(OnPaletteLoadedListener onPaletteLoadedListener, Bitmap bitmap) { + this.drawable = new BitmapDrawable(context.getResources(), bitmap); + getPaletteAsync(onPaletteLoadedListener, this.drawable); + } - private int selectForegroundColor(int backgroundColor, Palette palette) { - if (isColorLight(backgroundColor)) { - return selectForegroundColorForSwatches(palette.getDarkVibrantSwatch(), - palette.getVibrantSwatch(), - palette.getDarkMutedSwatch(), - palette.getMutedSwatch(), - palette.getDominantSwatch(), - Color.BLACK); - } else { - return selectForegroundColorForSwatches(palette.getLightVibrantSwatch(), - palette.getVibrantSwatch(), - palette.getLightMutedSwatch(), - palette.getMutedSwatch(), - palette.getDominantSwatch(), - Color.WHITE); - } - } - - public boolean isLight() { - return isColorLight(backgroundColor); - } - - private int selectForegroundColorForSwatches(Palette.Swatch moreVibrant, - Palette.Swatch vibrant, Palette.Swatch moreMutedSwatch, Palette.Swatch mutedSwatch, - Palette.Swatch dominantSwatch, int fallbackColor) { - Palette.Swatch coloredCandidate = selectVibrantCandidate(moreVibrant, vibrant); - if (coloredCandidate == null) { - coloredCandidate = selectMutedCandidate(mutedSwatch, moreMutedSwatch); - } - if (coloredCandidate != null) { - if (dominantSwatch == coloredCandidate) { - return coloredCandidate.getRgb(); - } else if ((float) coloredCandidate.getPopulation() / dominantSwatch.getPopulation() - < POPULATION_FRACTION_FOR_DOMINANT - && dominantSwatch.getHsl()[1] > MIN_SATURATION_WHEN_DECIDING) { - return dominantSwatch.getRgb(); - } else { - return coloredCandidate.getRgb(); - } - } else if (hasEnoughPopulation(dominantSwatch)) { - return dominantSwatch.getRgb(); - } else { - return fallbackColor; - } - } - - private Palette.Swatch selectMutedCandidate(Palette.Swatch first, - Palette.Swatch second) { - boolean firstValid = hasEnoughPopulation(first); - boolean secondValid = hasEnoughPopulation(second); - if (firstValid && secondValid) { - float firstSaturation = first.getHsl()[1]; - float secondSaturation = second.getHsl()[1]; - float populationFraction = first.getPopulation() / (float) second.getPopulation(); - if (firstSaturation * populationFraction > secondSaturation) { - return first; - } else { - return second; - } - } else if (firstValid) { - return first; - } else if (secondValid) { - return second; - } - return null; - } - - private Palette.Swatch selectVibrantCandidate(Palette.Swatch first, Palette.Swatch second) { - boolean firstValid = hasEnoughPopulation(first); - boolean secondValid = hasEnoughPopulation(second); - if (firstValid && secondValid) { - int firstPopulation = first.getPopulation(); - int secondPopulation = second.getPopulation(); - if (firstPopulation / (float) secondPopulation - < POPULATION_FRACTION_FOR_MORE_VIBRANT) { - return second; - } else { - return first; - } - } else if (firstValid) { - return first; - } else if (secondValid) { - return second; - } - return null; - } - - private boolean hasEnoughPopulation(Palette.Swatch swatch) { - // We want a fraction that is at least 1% of the image - return swatch != null - && (swatch.getPopulation() / (float) RESIZE_BITMAP_AREA > MINIMUM_IMAGE_FRACTION); - } - - public int findBackgroundColorAndFilter(Drawable drawable) { - int width = drawable.getIntrinsicWidth(); - int height = drawable.getIntrinsicHeight(); - int area = width * height; + /** Processes a drawable and calculates the appropriate colors that should be used. */ + private void getMediaPalette() { + Bitmap bitmap; + if (drawable != null) { + // We're transforming the builder, let's make sure all baked in RemoteViews are + // rebuilt! + int width = drawable.getIntrinsicWidth(); + int height = drawable.getIntrinsicHeight(); + int area = width * height; + if (area > RESIZE_BITMAP_AREA) { double factor = Math.sqrt((float) RESIZE_BITMAP_AREA / area); width = (int) (factor * width); height = (int) (factor * height); - Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); drawable.setBounds(0, 0, width, height); drawable.draw(canvas); // for the background we only take the left side of the image to ensure // a smooth transition - Palette.Builder paletteBuilder = Palette.from(bitmap) + Palette.Builder paletteBuilder = + Palette.from(bitmap) .setRegion(0, 0, bitmap.getWidth() / 2, bitmap.getHeight()) .clearFilters() // we want all colors, red / white / black ones too! .resizeBitmapArea(RESIZE_BITMAP_AREA); Palette palette = paletteBuilder.generate(); - // by default we use the dominant palette - Palette.Swatch dominantSwatch = palette.getDominantSwatch(); - if (dominantSwatch == null) { - // We're not filtering on white or black - mFilteredBackgroundHsl = null; - return Color.WHITE; + backgroundColor = findBackgroundColorAndFilter(drawable); + // we want most of the full region again, slightly shifted to the right + float textColorStartWidthFraction = 0.4f; + paletteBuilder.setRegion( + (int) (bitmap.getWidth() * textColorStartWidthFraction), + 0, + bitmap.getWidth(), + bitmap.getHeight()); + if (mFilteredBackgroundHsl != null) { + paletteBuilder.addFilter( + new Palette.Filter() { + @Override + public boolean isAllowed(int rgb, @NonNull float[] hsl) { + // at least 10 degrees hue difference + float diff = Math.abs(hsl[0] - mFilteredBackgroundHsl[0]); + return diff > 10 && diff < 350; + } + }); } + paletteBuilder.addFilter(mBlackWhiteFilter); + palette = paletteBuilder.generate(); + int foregroundColor = selectForegroundColor(backgroundColor, palette); + ensureColors(backgroundColor, foregroundColor); + } + } + } - if (!isWhiteOrBlack(dominantSwatch.getHsl())) { - mFilteredBackgroundHsl = dominantSwatch.getHsl(); - return dominantSwatch.getRgb(); - } - // Oh well, we selected black or white. Lets look at the second color! - List swatches = palette.getSwatches(); - float highestNonWhitePopulation = -1; - Palette.Swatch second = null; - for (Palette.Swatch swatch : swatches) { - if (swatch != dominantSwatch - && swatch.getPopulation() > highestNonWhitePopulation - && !isWhiteOrBlack(swatch.getHsl())) { - second = swatch; - highestNonWhitePopulation = swatch.getPopulation(); - } - } - if (second == null) { - // We're not filtering on white or black - mFilteredBackgroundHsl = null; - return dominantSwatch.getRgb(); - } - if (dominantSwatch.getPopulation() / highestNonWhitePopulation - > POPULATION_FRACTION_FOR_WHITE_OR_BLACK) { - // The dominant swatch is very dominant, lets take it! - // We're not filtering on white or black - mFilteredBackgroundHsl = null; - return dominantSwatch.getRgb(); + private int selectForegroundColor(int backgroundColor, Palette palette) { + if (isColorLight(backgroundColor)) { + return selectForegroundColorForSwatches( + palette.getDarkVibrantSwatch(), + palette.getVibrantSwatch(), + palette.getDarkMutedSwatch(), + palette.getMutedSwatch(), + palette.getDominantSwatch(), + Color.BLACK); + } else { + return selectForegroundColorForSwatches( + palette.getLightVibrantSwatch(), + palette.getVibrantSwatch(), + palette.getLightMutedSwatch(), + palette.getMutedSwatch(), + palette.getDominantSwatch(), + Color.WHITE); + } + } + + public boolean isLight() { + return isColorLight(backgroundColor); + } + + private int selectForegroundColorForSwatches( + Palette.Swatch moreVibrant, + Palette.Swatch vibrant, + Palette.Swatch moreMutedSwatch, + Palette.Swatch mutedSwatch, + Palette.Swatch dominantSwatch, + int fallbackColor) { + Palette.Swatch coloredCandidate = selectVibrantCandidate(moreVibrant, vibrant); + if (coloredCandidate == null) { + coloredCandidate = selectMutedCandidate(mutedSwatch, moreMutedSwatch); + } + if (coloredCandidate != null) { + if (dominantSwatch == coloredCandidate) { + return coloredCandidate.getRgb(); + } else if ((float) coloredCandidate.getPopulation() / dominantSwatch.getPopulation() + < POPULATION_FRACTION_FOR_DOMINANT + && dominantSwatch.getHsl()[1] > MIN_SATURATION_WHEN_DECIDING) { + return dominantSwatch.getRgb(); + } else { + return coloredCandidate.getRgb(); + } + } else if (hasEnoughPopulation(dominantSwatch)) { + return dominantSwatch.getRgb(); + } else { + return fallbackColor; + } + } + + private Palette.Swatch selectMutedCandidate(Palette.Swatch first, Palette.Swatch second) { + boolean firstValid = hasEnoughPopulation(first); + boolean secondValid = hasEnoughPopulation(second); + if (firstValid && secondValid) { + float firstSaturation = first.getHsl()[1]; + float secondSaturation = second.getHsl()[1]; + float populationFraction = first.getPopulation() / (float) second.getPopulation(); + if (firstSaturation * populationFraction > secondSaturation) { + return first; + } else { + return second; + } + } else if (firstValid) { + return first; + } else if (secondValid) { + return second; + } + return null; + } + + private Palette.Swatch selectVibrantCandidate(Palette.Swatch first, Palette.Swatch second) { + boolean firstValid = hasEnoughPopulation(first); + boolean secondValid = hasEnoughPopulation(second); + if (firstValid && secondValid) { + int firstPopulation = first.getPopulation(); + int secondPopulation = second.getPopulation(); + if (firstPopulation / (float) secondPopulation < POPULATION_FRACTION_FOR_MORE_VIBRANT) { + return second; + } else { + return first; + } + } else if (firstValid) { + return first; + } else if (secondValid) { + return second; + } + return null; + } + + private boolean hasEnoughPopulation(Palette.Swatch swatch) { + // We want a fraction that is at least 1% of the image + return swatch != null + && (swatch.getPopulation() / (float) RESIZE_BITMAP_AREA > MINIMUM_IMAGE_FRACTION); + } + + public int findBackgroundColorAndFilter(Drawable drawable) { + int width = drawable.getIntrinsicWidth(); + int height = drawable.getIntrinsicHeight(); + int area = width * height; + + double factor = Math.sqrt((float) RESIZE_BITMAP_AREA / area); + width = (int) (factor * width); + height = (int) (factor * height); + + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, width, height); + drawable.draw(canvas); + + // for the background we only take the left side of the image to ensure + // a smooth transition + Palette.Builder paletteBuilder = + Palette.from(bitmap) + .setRegion(0, 0, bitmap.getWidth() / 2, bitmap.getHeight()) + .clearFilters() // we want all colors, red / white / black ones too! + .resizeBitmapArea(RESIZE_BITMAP_AREA); + Palette palette = paletteBuilder.generate(); + // by default we use the dominant palette + Palette.Swatch dominantSwatch = palette.getDominantSwatch(); + if (dominantSwatch == null) { + // We're not filtering on white or black + mFilteredBackgroundHsl = null; + return Color.WHITE; + } + + if (!isWhiteOrBlack(dominantSwatch.getHsl())) { + mFilteredBackgroundHsl = dominantSwatch.getHsl(); + return dominantSwatch.getRgb(); + } + // Oh well, we selected black or white. Lets look at the second color! + List swatches = palette.getSwatches(); + float highestNonWhitePopulation = -1; + Palette.Swatch second = null; + for (Palette.Swatch swatch : swatches) { + if (swatch != dominantSwatch + && swatch.getPopulation() > highestNonWhitePopulation + && !isWhiteOrBlack(swatch.getHsl())) { + second = swatch; + highestNonWhitePopulation = swatch.getPopulation(); + } + } + if (second == null) { + // We're not filtering on white or black + mFilteredBackgroundHsl = null; + return dominantSwatch.getRgb(); + } + if (dominantSwatch.getPopulation() / highestNonWhitePopulation + > POPULATION_FRACTION_FOR_WHITE_OR_BLACK) { + // The dominant swatch is very dominant, lets take it! + // We're not filtering on white or black + mFilteredBackgroundHsl = null; + return dominantSwatch.getRgb(); + } else { + mFilteredBackgroundHsl = second.getHsl(); + return second.getRgb(); + } + } + + private boolean isWhiteOrBlack(float[] hsl) { + return isBlack(hsl) || isWhite(hsl); + } + + /** @return true if the color represents a color which is close to black. */ + private boolean isBlack(float[] hslColor) { + return hslColor[2] <= BLACK_MAX_LIGHTNESS; + } + + /** @return true if the color represents a color which is close to white. */ + private boolean isWhite(float[] hslColor) { + return hslColor[2] >= WHITE_MIN_LIGHTNESS; + } + + public void setIsLowPriority(boolean isLowPriority) { + mIsLowPriority = isLowPriority; + } + + private void ensureColors(int backgroundColor, int mForegroundColor) { + { + double backLum = NotificationColorUtil.calculateLuminance(backgroundColor); + double textLum = NotificationColorUtil.calculateLuminance(mForegroundColor); + double contrast = NotificationColorUtil.calculateContrast(mForegroundColor, backgroundColor); + // We only respect the given colors if worst case Black or White still has + // contrast + boolean backgroundLight = + backLum > textLum + && NotificationColorUtil.satisfiesTextContrast(backgroundColor, Color.BLACK) + || backLum <= textLum + && !NotificationColorUtil.satisfiesTextContrast(backgroundColor, Color.WHITE); + if (contrast < 4.5f) { + if (backgroundLight) { + secondaryTextColor = + NotificationColorUtil.findContrastColor( + mForegroundColor, backgroundColor, true /* findFG */, 4.5f); + primaryTextColor = + NotificationColorUtil.changeColorLightness( + secondaryTextColor, -LIGHTNESS_TEXT_DIFFERENCE_LIGHT); } else { - mFilteredBackgroundHsl = second.getHsl(); - return second.getRgb(); + secondaryTextColor = + NotificationColorUtil.findContrastColorAgainstDark( + mForegroundColor, backgroundColor, true /* findFG */, 4.5f); + primaryTextColor = + NotificationColorUtil.changeColorLightness( + secondaryTextColor, -LIGHTNESS_TEXT_DIFFERENCE_DARK); } - } - - private boolean isWhiteOrBlack(float[] hsl) { - return isBlack(hsl) || isWhite(hsl); - } - - /** - * @return true if the color represents a color which is close to black. - */ - private boolean isBlack(float[] hslColor) { - return hslColor[2] <= BLACK_MAX_LIGHTNESS; - } - - /** - * @return true if the color represents a color which is close to white. - */ - private boolean isWhite(float[] hslColor) { - return hslColor[2] >= WHITE_MIN_LIGHTNESS; - } - - public void setIsLowPriority(boolean isLowPriority) { - mIsLowPriority = isLowPriority; - } - - private void ensureColors(int backgroundColor, int mForegroundColor) { - { - double backLum = NotificationColorUtil.calculateLuminance(backgroundColor); - double textLum = NotificationColorUtil.calculateLuminance(mForegroundColor); - double contrast = NotificationColorUtil.calculateContrast(mForegroundColor, - backgroundColor); - // We only respect the given colors if worst case Black or White still has - // contrast - boolean backgroundLight = backLum > textLum - && NotificationColorUtil.satisfiesTextContrast(backgroundColor, Color.BLACK) - || backLum <= textLum - && !NotificationColorUtil.satisfiesTextContrast(backgroundColor, Color.WHITE); - if (contrast < 4.5f) { - if (backgroundLight) { - secondaryTextColor = NotificationColorUtil.findContrastColor( - mForegroundColor, - backgroundColor, - true /* findFG */, - 4.5f); - primaryTextColor = NotificationColorUtil.changeColorLightness( - secondaryTextColor, -LIGHTNESS_TEXT_DIFFERENCE_LIGHT); - } else { - secondaryTextColor = - NotificationColorUtil.findContrastColorAgainstDark( - mForegroundColor, - backgroundColor, - true /* findFG */, - 4.5f); - primaryTextColor = NotificationColorUtil.changeColorLightness( - secondaryTextColor, -LIGHTNESS_TEXT_DIFFERENCE_DARK); - } - } else { - primaryTextColor = mForegroundColor; - secondaryTextColor = NotificationColorUtil.changeColorLightness( - primaryTextColor, backgroundLight ? LIGHTNESS_TEXT_DIFFERENCE_LIGHT - : LIGHTNESS_TEXT_DIFFERENCE_DARK); - if (NotificationColorUtil.calculateContrast(secondaryTextColor, - backgroundColor) < 4.5f) { - // oh well the secondary is not good enough - if (backgroundLight) { - secondaryTextColor = NotificationColorUtil.findContrastColor( - secondaryTextColor, - backgroundColor, - true /* findFG */, - 4.5f); - } else { - secondaryTextColor - = NotificationColorUtil.findContrastColorAgainstDark( - secondaryTextColor, - backgroundColor, - true /* findFG */, - 4.5f); - } - primaryTextColor = NotificationColorUtil.changeColorLightness( - secondaryTextColor, backgroundLight - ? -LIGHTNESS_TEXT_DIFFERENCE_LIGHT - : -LIGHTNESS_TEXT_DIFFERENCE_DARK); - } - } + } else { + primaryTextColor = mForegroundColor; + secondaryTextColor = + NotificationColorUtil.changeColorLightness( + primaryTextColor, + backgroundLight ? LIGHTNESS_TEXT_DIFFERENCE_LIGHT : LIGHTNESS_TEXT_DIFFERENCE_DARK); + if (NotificationColorUtil.calculateContrast(secondaryTextColor, backgroundColor) < 4.5f) { + // oh well the secondary is not good enough + if (backgroundLight) { + secondaryTextColor = + NotificationColorUtil.findContrastColor( + secondaryTextColor, backgroundColor, true /* findFG */, 4.5f); + } else { + secondaryTextColor = + NotificationColorUtil.findContrastColorAgainstDark( + secondaryTextColor, backgroundColor, true /* findFG */, 4.5f); + } + primaryTextColor = + NotificationColorUtil.changeColorLightness( + secondaryTextColor, + backgroundLight + ? -LIGHTNESS_TEXT_DIFFERENCE_LIGHT + : -LIGHTNESS_TEXT_DIFFERENCE_DARK); } - actionBarColor = NotificationColorUtil.resolveActionBarColor(context, - backgroundColor); + } } + actionBarColor = NotificationColorUtil.resolveActionBarColor(context, backgroundColor); + } - public int getPrimaryTextColor() { + public int getPrimaryTextColor() { + return primaryTextColor; + } + + public int getSecondaryTextColor() { + return secondaryTextColor; + } + + public int getActionBarColor() { + return actionBarColor; + } + + public int getBackgroundColor() { + return backgroundColor; + } + + boolean isWhiteColor(int color) { + return calculateLuminance(color) > 0.6f; + } + + public int getMightyColor() { + boolean isDarkBg = + ColorUtil.INSTANCE.isColorLight( + ATHUtil.INSTANCE.resolveColor(context, R.attr.colorSurface)); + if (isDarkBg) { + if (isColorLight(backgroundColor)) { return primaryTextColor; - } - - public int getSecondaryTextColor() { - return secondaryTextColor; - } - - public int getActionBarColor() { - return actionBarColor; - } - - public int getBackgroundColor() { + } else { return backgroundColor; + } + } else { + if (isColorLight(backgroundColor)) { + return backgroundColor; + } else { + return primaryTextColor; + } } + } - boolean isWhiteColor(int color) { - return calculateLuminance(color) > 0.6f; - } - - public int getMightyColor() { - boolean isDarkBg = ColorUtil.INSTANCE.isColorLight(ATHUtil.INSTANCE.resolveColor(context, R.attr.colorSurface)); - if (isDarkBg) { - if (isColorLight(backgroundColor)) { - return primaryTextColor; - } else { - return backgroundColor; - } - } else { - if (isColorLight(backgroundColor)) { - return backgroundColor; - } else { - return primaryTextColor; - } - } - } - - public interface OnPaletteLoadedListener { - void onPaletteLoaded(MediaNotificationProcessor mediaNotificationProcessor); - } -} \ No newline at end of file + public interface OnPaletteLoadedListener { + void onPaletteLoaded(MediaNotificationProcessor mediaNotificationProcessor); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/util/color/NotificationColorUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/color/NotificationColorUtil.java index 4d607f82..fe8e4346 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/color/NotificationColorUtil.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/color/NotificationColorUtil.java @@ -28,962 +28,966 @@ import android.text.style.ForegroundColorSpan; import android.text.style.TextAppearanceSpan; import android.util.Log; import android.util.Pair; - import androidx.annotation.ColorInt; import androidx.annotation.FloatRange; import androidx.annotation.IntRange; import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; - +import code.name.monkey.retromusic.R; import java.util.WeakHashMap; -import code.name.monkey.retromusic.R; - /** - * Helper class to process legacy (Holo) notifications to make them look like material notifications. + * Helper class to process legacy (Holo) notifications to make them look like material + * notifications. * * @hide */ public class NotificationColorUtil { - private static final String TAG = "NotificationColorUtil"; - private static final boolean DEBUG = false; + private static final String TAG = "NotificationColorUtil"; + private static final boolean DEBUG = false; - private static final Object sLock = new Object(); - private static NotificationColorUtil sInstance; + private static final Object sLock = new Object(); + private static NotificationColorUtil sInstance; - private final ImageUtils mImageUtils = new ImageUtils(); - private final WeakHashMap> mGrayscaleBitmapCache = - new WeakHashMap>(); + private final ImageUtils mImageUtils = new ImageUtils(); + private final WeakHashMap> mGrayscaleBitmapCache = + new WeakHashMap>(); - private final int mGrayscaleIconMaxSize; // @dimen/notification_large_icon_width (64dp) + private final int mGrayscaleIconMaxSize; // @dimen/notification_large_icon_width (64dp) - private NotificationColorUtil(Context context) { - mGrayscaleIconMaxSize = context.getResources().getDimensionPixelSize( - R.dimen.notification_large_icon_width); + private NotificationColorUtil(Context context) { + mGrayscaleIconMaxSize = + context.getResources().getDimensionPixelSize(R.dimen.notification_large_icon_width); + } + + public static NotificationColorUtil getInstance(Context context) { + synchronized (sLock) { + if (sInstance == null) { + sInstance = new NotificationColorUtil(context); + } + return sInstance; } + } - public static NotificationColorUtil getInstance(Context context) { - synchronized (sLock) { - if (sInstance == null) { - sInstance = new NotificationColorUtil(context); - } - return sInstance; + /** + * Clears all color spans of a text + * + * @param charSequence the input text + * @return the same text but without color spans + */ + public static CharSequence clearColorSpans(CharSequence charSequence) { + if (charSequence instanceof Spanned) { + Spanned ss = (Spanned) charSequence; + Object[] spans = ss.getSpans(0, ss.length(), Object.class); + SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString()); + for (Object span : spans) { + Object resultSpan = span; + if (resultSpan instanceof CharacterStyle) { + resultSpan = ((CharacterStyle) span).getUnderlying(); } - } - - /** - * Clears all color spans of a text - * - * @param charSequence the input text - * @return the same text but without color spans - */ - public static CharSequence clearColorSpans(CharSequence charSequence) { - if (charSequence instanceof Spanned) { - Spanned ss = (Spanned) charSequence; - Object[] spans = ss.getSpans(0, ss.length(), Object.class); - SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString()); - for (Object span : spans) { - Object resultSpan = span; - if (resultSpan instanceof CharacterStyle) { - resultSpan = ((CharacterStyle) span).getUnderlying(); - } - if (resultSpan instanceof TextAppearanceSpan) { - TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan; - if (originalSpan.getTextColor() != null) { - resultSpan = new TextAppearanceSpan( - originalSpan.getFamily(), - originalSpan.getTextStyle(), - originalSpan.getTextSize(), - null, - originalSpan.getLinkTextColor()); - } - } else if (resultSpan instanceof ForegroundColorSpan - || (resultSpan instanceof BackgroundColorSpan)) { - continue; - } else { - resultSpan = span; - } - builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span), - ss.getSpanFlags(span)); - } - return builder; - } - return charSequence; - } - - -// /** -// * Inverts all the grayscale colors set by {@link android.text.style.TextAppearanceSpan}s on -// * the text. -// * -// * @param charSequence The text to process. -// * @return The color inverted text. -// */ -// public CharSequence invertCharSequenceColors(CharSequence charSequence) { -// if (charSequence instanceof Spanned) { -// Spanned ss = (Spanned) charSequence; -// Object[] spans = ss.getSpans(0, ss.length(), Object.class); -// SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString()); -// for (Object span : spans) { -// Object resultSpan = span; -// if (resultSpan instanceof CharacterStyle) { -// resultSpan = ((CharacterStyle) span).getUnderlying(); -// } -// if (resultSpan instanceof TextAppearanceSpan) { -// TextAppearanceSpan processedSpan = processTextAppearanceSpan( -// (TextAppearanceSpan) span); -// if (processedSpan != resultSpan) { -// resultSpan = processedSpan; -// } else { -// // we need to still take the orgininal for wrapped spans -// resultSpan = span; -// } -// } else if (resultSpan instanceof ForegroundColorSpan) { -// ForegroundColorSpan originalSpan = (ForegroundColorSpan) resultSpan; -// int foregroundColor = originalSpan.getForegroundColor(); -// resultSpan = new ForegroundColorSpan(processColor(foregroundColor)); -// } else { -// resultSpan = span; -// } -// builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span), -// ss.getSpanFlags(span)); -// } -// return builder; -// } -// return charSequence; -// } - -// private TextAppearanceSpan processTextAppearanceSpan(TextAppearanceSpan span) { -// ColorStateList colorStateList = span.getTextColor(); -// if (colorStateList != null) { -// int[] colors = colorStateList.getColors(); -// boolean changed = false; -// for (int i = 0; i < colors.length; i++) { -// if (ImageUtils.isGrayscale(colors[i])) { -// -// // Allocate a new array so we don't change the colors in the old color state -// // list. -// if (!changed) { -// colors = Arrays.copyOf(colors, colors.length); -// } -// colors[i] = processColor(colors[i]); -// changed = true; -// } -// } -// if (changed) { -// return new TextAppearanceSpan( -// span.getFamily(), span.getTextStyle(), span.getTextSize(), -// new ColorStateList(colorStateList.getStates(), colors), -// span.getLinkTextColor()); -// } -// } -// return span; -// } - - /** - * Finds a suitable color such that there's enough contrast. - * - * @param color the color to start searching from. - * @param other the color to ensure contrast against. Assumed to be lighter than {@param color} - * @param findFg if true, we assume {@param color} is a foreground, otherwise a background. - * @param minRatio the minimum contrast ratio required. - * @return a color with the same hue as {@param color}, potentially darkened to meet the - * contrast ratio. - */ - public static int findContrastColor(int color, int other, boolean findFg, double minRatio) { - int fg = findFg ? color : other; - int bg = findFg ? other : color; - if (ColorUtilsFromCompat.calculateContrast(fg, bg) >= minRatio) { - return color; - } - - double[] lab = new double[3]; - ColorUtilsFromCompat.colorToLAB(findFg ? fg : bg, lab); - - double low = 0, high = lab[0]; - final double a = lab[1], b = lab[2]; - for (int i = 0; i < 15 && high - low > 0.00001; i++) { - final double l = (low + high) / 2; - if (findFg) { - fg = ColorUtilsFromCompat.LABToColor(l, a, b); - } else { - bg = ColorUtilsFromCompat.LABToColor(l, a, b); - } - if (ColorUtilsFromCompat.calculateContrast(fg, bg) > minRatio) { - low = l; - } else { - high = l; - } - } - return ColorUtilsFromCompat.LABToColor(low, a, b); - } - - /** - * Finds a suitable alpha such that there's enough contrast. - * - * @param color the color to start searching from. - * @param backgroundColor the color to ensure contrast against. - * @param minRatio the minimum contrast ratio required. - * @return the same color as {@param color} with potentially modified alpha to meet contrast - */ - public static int findAlphaToMeetContrast(int color, int backgroundColor, double minRatio) { - int fg = color; - int bg = backgroundColor; - if (ColorUtilsFromCompat.calculateContrast(fg, bg) >= minRatio) { - return color; - } - int startAlpha = Color.alpha(color); - int r = Color.red(color); - int g = Color.green(color); - int b = Color.blue(color); - - int low = startAlpha, high = 255; - for (int i = 0; i < 15 && high - low > 0; i++) { - final int alpha = (low + high) / 2; - fg = Color.argb(alpha, r, g, b); - if (ColorUtilsFromCompat.calculateContrast(fg, bg) > minRatio) { - high = alpha; - } else { - low = alpha; - } - } - return Color.argb(high, r, g, b); - } - - /** - * Finds a suitable color such that there's enough contrast. - * - * @param color the color to start searching from. - * @param other the color to ensure contrast against. Assumed to be darker than {@param color} - * @param findFg if true, we assume {@param color} is a foreground, otherwise a background. - * @param minRatio the minimum contrast ratio required. - * @return a color with the same hue as {@param color}, potentially darkened to meet the - * contrast ratio. - */ - public static int findContrastColorAgainstDark(int color, int other, boolean findFg, - double minRatio) { - int fg = findFg ? color : other; - int bg = findFg ? other : color; - if (ColorUtilsFromCompat.calculateContrast(fg, bg) >= minRatio) { - return color; - } - - float[] hsl = new float[3]; - ColorUtilsFromCompat.colorToHSL(findFg ? fg : bg, hsl); - - float low = hsl[2], high = 1; - for (int i = 0; i < 15 && high - low > 0.00001; i++) { - final float l = (low + high) / 2; - hsl[2] = l; - if (findFg) { - fg = ColorUtilsFromCompat.HSLToColor(hsl); - } else { - bg = ColorUtilsFromCompat.HSLToColor(hsl); - } - if (ColorUtilsFromCompat.calculateContrast(fg, bg) > minRatio) { - high = l; - } else { - low = l; - } - } - return findFg ? fg : bg; - } - - public static int ensureTextContrastOnBlack(int color) { - return findContrastColorAgainstDark(color, Color.BLACK, true /* fg */, 12); - } - - /** - * Finds a large text color with sufficient contrast over bg that has the same or darker hue as - * the original color, depending on the value of {@code isBgDarker}. - * - * @param isBgDarker {@code true} if {@code bg} is darker than {@code color}. - */ - public static int ensureLargeTextContrast(int color, int bg, boolean isBgDarker) { - return isBgDarker - ? findContrastColorAgainstDark(color, bg, true, 3) - : findContrastColor(color, bg, true, 3); - } - - /** - * Finds a text color with sufficient contrast over bg that has the same or darker hue as the - * original color, depending on the value of {@code isBgDarker}. - * - * @param isBgDarker {@code true} if {@code bg} is darker than {@code color}. - */ - private static int ensureTextContrast(int color, int bg, boolean isBgDarker) { - return isBgDarker - ? findContrastColorAgainstDark(color, bg, true, 4.5) - : findContrastColor(color, bg, true, 4.5); - } - - /** - * Finds a background color for a text view with given text color and hint text color, that - * has the same hue as the original color. - */ - public static int ensureTextBackgroundColor(int color, int textColor, int hintColor) { - color = findContrastColor(color, hintColor, false, 3.0); - return findContrastColor(color, textColor, false, 4.5); - } - - private static String contrastChange(int colorOld, int colorNew, int bg) { - return String.format("from %.2f:1 to %.2f:1", - ColorUtilsFromCompat.calculateContrast(colorOld, bg), - ColorUtilsFromCompat.calculateContrast(colorNew, bg)); - } - - /** - * Change a color by a specified value - * - * @param baseColor the base color to lighten - * @param amount the amount to lighten the color from 0 to 100. This corresponds to the L - * increase in the LAB color space. A negative value will darken the color and - * a positive will lighten it. - * @return the changed color - */ - public static int changeColorLightness(int baseColor, int amount) { - final double[] result = ColorUtilsFromCompat.getTempDouble3Array(); - ColorUtilsFromCompat.colorToLAB(baseColor, result); - result[0] = Math.max(Math.min(100, result[0] + amount), 0); - return ColorUtilsFromCompat.LABToColor(result[0], result[1], result[2]); - } - - public static int resolvePrimaryColor(Context context, int backgroundColor) { - boolean useDark = shouldUseDark(backgroundColor); - if (useDark) { - return ContextCompat.getColor(context, android.R.color.primary_text_light); + if (resultSpan instanceof TextAppearanceSpan) { + TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan; + if (originalSpan.getTextColor() != null) { + resultSpan = + new TextAppearanceSpan( + originalSpan.getFamily(), + originalSpan.getTextStyle(), + originalSpan.getTextSize(), + null, + originalSpan.getLinkTextColor()); + } + } else if (resultSpan instanceof ForegroundColorSpan + || (resultSpan instanceof BackgroundColorSpan)) { + continue; } else { - return ContextCompat.getColor(context, android.R.color.primary_text_light); + resultSpan = span; } + builder.setSpan( + resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span), ss.getSpanFlags(span)); + } + return builder; + } + return charSequence; + } + + // /** + // * Inverts all the grayscale colors set by {@link android.text.style.TextAppearanceSpan}s on + // * the text. + // * + // * @param charSequence The text to process. + // * @return The color inverted text. + // */ + // public CharSequence invertCharSequenceColors(CharSequence charSequence) { + // if (charSequence instanceof Spanned) { + // Spanned ss = (Spanned) charSequence; + // Object[] spans = ss.getSpans(0, ss.length(), Object.class); + // SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString()); + // for (Object span : spans) { + // Object resultSpan = span; + // if (resultSpan instanceof CharacterStyle) { + // resultSpan = ((CharacterStyle) span).getUnderlying(); + // } + // if (resultSpan instanceof TextAppearanceSpan) { + // TextAppearanceSpan processedSpan = processTextAppearanceSpan( + // (TextAppearanceSpan) span); + // if (processedSpan != resultSpan) { + // resultSpan = processedSpan; + // } else { + // // we need to still take the orgininal for wrapped spans + // resultSpan = span; + // } + // } else if (resultSpan instanceof ForegroundColorSpan) { + // ForegroundColorSpan originalSpan = (ForegroundColorSpan) resultSpan; + // int foregroundColor = originalSpan.getForegroundColor(); + // resultSpan = new ForegroundColorSpan(processColor(foregroundColor)); + // } else { + // resultSpan = span; + // } + // builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span), + // ss.getSpanFlags(span)); + // } + // return builder; + // } + // return charSequence; + // } + + // private TextAppearanceSpan processTextAppearanceSpan(TextAppearanceSpan span) { + // ColorStateList colorStateList = span.getTextColor(); + // if (colorStateList != null) { + // int[] colors = colorStateList.getColors(); + // boolean changed = false; + // for (int i = 0; i < colors.length; i++) { + // if (ImageUtils.isGrayscale(colors[i])) { + // + // // Allocate a new array so we don't change the colors in the old color state + // // list. + // if (!changed) { + // colors = Arrays.copyOf(colors, colors.length); + // } + // colors[i] = processColor(colors[i]); + // changed = true; + // } + // } + // if (changed) { + // return new TextAppearanceSpan( + // span.getFamily(), span.getTextStyle(), span.getTextSize(), + // new ColorStateList(colorStateList.getStates(), colors), + // span.getLinkTextColor()); + // } + // } + // return span; + // } + + /** + * Finds a suitable color such that there's enough contrast. + * + * @param color the color to start searching from. + * @param other the color to ensure contrast against. Assumed to be lighter than {@param color} + * @param findFg if true, we assume {@param color} is a foreground, otherwise a background. + * @param minRatio the minimum contrast ratio required. + * @return a color with the same hue as {@param color}, potentially darkened to meet the contrast + * ratio. + */ + public static int findContrastColor(int color, int other, boolean findFg, double minRatio) { + int fg = findFg ? color : other; + int bg = findFg ? other : color; + if (ColorUtilsFromCompat.calculateContrast(fg, bg) >= minRatio) { + return color; } + double[] lab = new double[3]; + ColorUtilsFromCompat.colorToLAB(findFg ? fg : bg, lab); - public static int resolveSecondaryColor(Context context, int backgroundColor) { - boolean useDark = shouldUseDark(backgroundColor); - if (useDark) { - return ContextCompat.getColor(context, - android.R.color.secondary_text_light); - } else { - return ContextCompat.getColor(context, android.R.color.secondary_text_dark); - } + double low = 0, high = lab[0]; + final double a = lab[1], b = lab[2]; + for (int i = 0; i < 15 && high - low > 0.00001; i++) { + final double l = (low + high) / 2; + if (findFg) { + fg = ColorUtilsFromCompat.LABToColor(l, a, b); + } else { + bg = ColorUtilsFromCompat.LABToColor(l, a, b); + } + if (ColorUtilsFromCompat.calculateContrast(fg, bg) > minRatio) { + low = l; + } else { + high = l; + } + } + return ColorUtilsFromCompat.LABToColor(low, a, b); + } + + /** + * Finds a suitable alpha such that there's enough contrast. + * + * @param color the color to start searching from. + * @param backgroundColor the color to ensure contrast against. + * @param minRatio the minimum contrast ratio required. + * @return the same color as {@param color} with potentially modified alpha to meet contrast + */ + public static int findAlphaToMeetContrast(int color, int backgroundColor, double minRatio) { + int fg = color; + int bg = backgroundColor; + if (ColorUtilsFromCompat.calculateContrast(fg, bg) >= minRatio) { + return color; + } + int startAlpha = Color.alpha(color); + int r = Color.red(color); + int g = Color.green(color); + int b = Color.blue(color); + + int low = startAlpha, high = 255; + for (int i = 0; i < 15 && high - low > 0; i++) { + final int alpha = (low + high) / 2; + fg = Color.argb(alpha, r, g, b); + if (ColorUtilsFromCompat.calculateContrast(fg, bg) > minRatio) { + high = alpha; + } else { + low = alpha; + } + } + return Color.argb(high, r, g, b); + } + + /** + * Finds a suitable color such that there's enough contrast. + * + * @param color the color to start searching from. + * @param other the color to ensure contrast against. Assumed to be darker than {@param color} + * @param findFg if true, we assume {@param color} is a foreground, otherwise a background. + * @param minRatio the minimum contrast ratio required. + * @return a color with the same hue as {@param color}, potentially darkened to meet the contrast + * ratio. + */ + public static int findContrastColorAgainstDark( + int color, int other, boolean findFg, double minRatio) { + int fg = findFg ? color : other; + int bg = findFg ? other : color; + if (ColorUtilsFromCompat.calculateContrast(fg, bg) >= minRatio) { + return color; } - public static int resolveActionBarColor(Context context, int backgroundColor) { - if (backgroundColor == Notification.COLOR_DEFAULT) { - return Color.BLACK; + float[] hsl = new float[3]; + ColorUtilsFromCompat.colorToHSL(findFg ? fg : bg, hsl); + + float low = hsl[2], high = 1; + for (int i = 0; i < 15 && high - low > 0.00001; i++) { + final float l = (low + high) / 2; + hsl[2] = l; + if (findFg) { + fg = ColorUtilsFromCompat.HSLToColor(hsl); + } else { + bg = ColorUtilsFromCompat.HSLToColor(hsl); + } + if (ColorUtilsFromCompat.calculateContrast(fg, bg) > minRatio) { + high = l; + } else { + low = l; + } + } + return findFg ? fg : bg; + } + + public static int ensureTextContrastOnBlack(int color) { + return findContrastColorAgainstDark(color, Color.BLACK, true /* fg */, 12); + } + + /** + * Finds a large text color with sufficient contrast over bg that has the same or darker hue as + * the original color, depending on the value of {@code isBgDarker}. + * + * @param isBgDarker {@code true} if {@code bg} is darker than {@code color}. + */ + public static int ensureLargeTextContrast(int color, int bg, boolean isBgDarker) { + return isBgDarker + ? findContrastColorAgainstDark(color, bg, true, 3) + : findContrastColor(color, bg, true, 3); + } + + /** + * Finds a text color with sufficient contrast over bg that has the same or darker hue as the + * original color, depending on the value of {@code isBgDarker}. + * + * @param isBgDarker {@code true} if {@code bg} is darker than {@code color}. + */ + private static int ensureTextContrast(int color, int bg, boolean isBgDarker) { + return isBgDarker + ? findContrastColorAgainstDark(color, bg, true, 4.5) + : findContrastColor(color, bg, true, 4.5); + } + + /** + * Finds a background color for a text view with given text color and hint text color, that has + * the same hue as the original color. + */ + public static int ensureTextBackgroundColor(int color, int textColor, int hintColor) { + color = findContrastColor(color, hintColor, false, 3.0); + return findContrastColor(color, textColor, false, 4.5); + } + + private static String contrastChange(int colorOld, int colorNew, int bg) { + return String.format( + "from %.2f:1 to %.2f:1", + ColorUtilsFromCompat.calculateContrast(colorOld, bg), + ColorUtilsFromCompat.calculateContrast(colorNew, bg)); + } + + /** + * Change a color by a specified value + * + * @param baseColor the base color to lighten + * @param amount the amount to lighten the color from 0 to 100. This corresponds to the L increase + * in the LAB color space. A negative value will darken the color and a positive will lighten + * it. + * @return the changed color + */ + public static int changeColorLightness(int baseColor, int amount) { + final double[] result = ColorUtilsFromCompat.getTempDouble3Array(); + ColorUtilsFromCompat.colorToLAB(baseColor, result); + result[0] = Math.max(Math.min(100, result[0] + amount), 0); + return ColorUtilsFromCompat.LABToColor(result[0], result[1], result[2]); + } + + public static int resolvePrimaryColor(Context context, int backgroundColor) { + boolean useDark = shouldUseDark(backgroundColor); + if (useDark) { + return ContextCompat.getColor(context, android.R.color.primary_text_light); + } else { + return ContextCompat.getColor(context, android.R.color.primary_text_light); + } + } + + public static int resolveSecondaryColor(Context context, int backgroundColor) { + boolean useDark = shouldUseDark(backgroundColor); + if (useDark) { + return ContextCompat.getColor(context, android.R.color.secondary_text_light); + } else { + return ContextCompat.getColor(context, android.R.color.secondary_text_dark); + } + } + + public static int resolveActionBarColor(Context context, int backgroundColor) { + if (backgroundColor == Notification.COLOR_DEFAULT) { + return Color.BLACK; + } + return getShiftedColor(backgroundColor, 7); + } + + /** Resolves {@param color} to an actual color if it is {@link Notification#COLOR_DEFAULT} */ + public static int resolveColor(Context context, int color) { + if (color == Notification.COLOR_DEFAULT) { + return ContextCompat.getColor(context, android.R.color.background_dark); + } + return color; + } + + // + // public static int resolveContrastColor(Context context, int notificationColor, + // int backgroundColor) { + // return NotificationColorUtil.resolveContrastColor(context, notificationColor, + // backgroundColor, false /* isDark */); + // } + + // /** + // * Resolves a Notification's color such that it has enough contrast to be used as the + // * color for the Notification's action and header text. + // * + // * @param notificationColor the color of the notification or {@link + // Notification#COLOR_DEFAULT} + // * @param backgroundColor the background color to ensure the contrast against. + // * @param isDark whether or not the {@code notificationColor} will be placed on a background + // * that is darker than the color itself + // * @return a color of the same hue with enough contrast against the backgrounds. + // */ + // public static int resolveContrastColor(Context context, int notificationColor, + // int backgroundColor, boolean isDark) { + // final int resolvedColor = resolveColor(context, notificationColor); + // + // final int actionBg = context.getColor( + // com.android.internal.R.color.notification_action_list); + // + // int color = resolvedColor; + // color = NotificationColorUtil.ensureLargeTextContrast(color, actionBg, isDark); + // color = NotificationColorUtil.ensureTextContrast(color, backgroundColor, isDark); + // + // if (color != resolvedColor) { + // if (DEBUG){ + // Log.w(TAG, String.format( + // "Enhanced contrast of notification for %s %s (over action)" + // + " and %s (over background) by changing #%s to %s", + // context.getPackageName(), + // NotificationColorUtil.contrastChange(resolvedColor, color, actionBg), + // NotificationColorUtil.contrastChange(resolvedColor, color, + // backgroundColor), + // Integer.toHexString(resolvedColor), Integer.toHexString(color))); + // } + // } + // return color; + // } + + /** + * Get a color that stays in the same tint, but darkens or lightens it by a certain amount. This + * also looks at the lightness of the provided color and shifts it appropriately. + * + * @param color the base color to use + * @param amount the amount from 1 to 100 how much to modify the color + * @return the now color that was modified + */ + public static int getShiftedColor(int color, int amount) { + final double[] result = ColorUtilsFromCompat.getTempDouble3Array(); + ColorUtilsFromCompat.colorToLAB(color, result); + if (result[0] >= 4) { + result[0] = Math.max(0, result[0] - amount); + } else { + result[0] = Math.min(100, result[0] + amount); + } + return ColorUtilsFromCompat.LABToColor(result[0], result[1], result[2]); + } + + public static int resolveAmbientColor(Context context, int notificationColor) { + final int resolvedColor = resolveColor(context, notificationColor); + + int color = resolvedColor; + color = NotificationColorUtil.ensureTextContrastOnBlack(color); + + if (color != resolvedColor) { + if (DEBUG) { + Log.w( + TAG, + String.format( + "Ambient contrast of notification for %s is %s (over black)" + + " by changing #%s to #%s", + context.getPackageName(), + NotificationColorUtil.contrastChange(resolvedColor, color, Color.BLACK), + Integer.toHexString(resolvedColor), + Integer.toHexString(color))); + } + } + return color; + } + + private static boolean shouldUseDark(int backgroundColor) { + boolean useDark = backgroundColor == Notification.COLOR_DEFAULT; + if (!useDark) { + useDark = ColorUtilsFromCompat.calculateLuminance(backgroundColor) > 0.5; + } + return useDark; + } + + public static double calculateLuminance(int backgroundColor) { + return ColorUtilsFromCompat.calculateLuminance(backgroundColor); + } + + public static double calculateContrast(int foregroundColor, int backgroundColor) { + return ColorUtilsFromCompat.calculateContrast(foregroundColor, backgroundColor); + } + + public static boolean satisfiesTextContrast(int backgroundColor, int foregroundColor) { + return NotificationColorUtil.calculateContrast(foregroundColor, backgroundColor) >= 4.5; + } + + /** Composite two potentially translucent colors over each other and returns the result. */ + public static int compositeColors(int foreground, int background) { + return ColorUtilsFromCompat.compositeColors(foreground, background); + } + + public static boolean isColorLight(int backgroundColor) { + return calculateLuminance(backgroundColor) > 0.5f; + } + + /** + * Checks whether a Bitmap is a small grayscale icon. Grayscale here means "very close to a + * perfect gray"; icon means "no larger than 64dp". + * + * @param bitmap The bitmap to test. + * @return True if the bitmap is grayscale; false if it is color or too large to examine. + */ + public boolean isGrayscaleIcon(Bitmap bitmap) { + // quick test: reject large bitmaps + if (bitmap.getWidth() > mGrayscaleIconMaxSize || bitmap.getHeight() > mGrayscaleIconMaxSize) { + return false; + } + + synchronized (sLock) { + Pair cached = mGrayscaleBitmapCache.get(bitmap); + if (cached != null) { + if (cached.second == bitmap.getGenerationId()) { + return cached.first; } - return getShiftedColor(backgroundColor, 7); + } + } + boolean result; + int generationId; + synchronized (mImageUtils) { + result = mImageUtils.isGrayscale(bitmap); + + // generationId and the check whether the Bitmap is grayscale can't be read atomically + // here. However, since the thread is in the process of posting the notification, we can + // assume that it doesn't modify the bitmap while we are checking the pixels. + generationId = bitmap.getGenerationId(); + } + synchronized (sLock) { + mGrayscaleBitmapCache.put(bitmap, Pair.create(result, generationId)); + } + return result; + } + + private int processColor(int color) { + return Color.argb( + Color.alpha(color), + 255 - Color.red(color), + 255 - Color.green(color), + 255 - Color.blue(color)); + } + + /** Framework copy of functions needed from android.support.v4.graphics.ColorUtils. */ + private static class ColorUtilsFromCompat { + private static final double XYZ_WHITE_REFERENCE_X = 95.047; + private static final double XYZ_WHITE_REFERENCE_Y = 100; + private static final double XYZ_WHITE_REFERENCE_Z = 108.883; + private static final double XYZ_EPSILON = 0.008856; + private static final double XYZ_KAPPA = 903.3; + + private static final int MIN_ALPHA_SEARCH_MAX_ITERATIONS = 10; + private static final int MIN_ALPHA_SEARCH_PRECISION = 1; + + private static final ThreadLocal TEMP_ARRAY = new ThreadLocal<>(); + + private ColorUtilsFromCompat() {} + + /** Composite two potentially translucent colors over each other and returns the result. */ + public static int compositeColors(@ColorInt int foreground, @ColorInt int background) { + int bgAlpha = Color.alpha(background); + int fgAlpha = Color.alpha(foreground); + int a = compositeAlpha(fgAlpha, bgAlpha); + + int r = compositeComponent(Color.red(foreground), fgAlpha, Color.red(background), bgAlpha, a); + int g = + compositeComponent(Color.green(foreground), fgAlpha, Color.green(background), bgAlpha, a); + int b = + compositeComponent(Color.blue(foreground), fgAlpha, Color.blue(background), bgAlpha, a); + + return Color.argb(a, r, g, b); + } + + private static int compositeAlpha(int foregroundAlpha, int backgroundAlpha) { + return 0xFF - (((0xFF - backgroundAlpha) * (0xFF - foregroundAlpha)) / 0xFF); + } + + private static int compositeComponent(int fgC, int fgA, int bgC, int bgA, int a) { + if (a == 0) return 0; + return ((0xFF * fgC * fgA) + (bgC * bgA * (0xFF - fgA))) / (a * 0xFF); } /** - * Resolves {@param color} to an actual color if it is {@link Notification#COLOR_DEFAULT} - */ - public static int resolveColor(Context context, int color) { - if (color == Notification.COLOR_DEFAULT) { - return ContextCompat.getColor(context, android.R.color.background_dark); - } - return color; - } - -// -// public static int resolveContrastColor(Context context, int notificationColor, -// int backgroundColor) { -// return NotificationColorUtil.resolveContrastColor(context, notificationColor, -// backgroundColor, false /* isDark */); -// } - -// /** -// * Resolves a Notification's color such that it has enough contrast to be used as the -// * color for the Notification's action and header text. -// * -// * @param notificationColor the color of the notification or {@link Notification#COLOR_DEFAULT} -// * @param backgroundColor the background color to ensure the contrast against. -// * @param isDark whether or not the {@code notificationColor} will be placed on a background -// * that is darker than the color itself -// * @return a color of the same hue with enough contrast against the backgrounds. -// */ -// public static int resolveContrastColor(Context context, int notificationColor, -// int backgroundColor, boolean isDark) { -// final int resolvedColor = resolveColor(context, notificationColor); -// -// final int actionBg = context.getColor( -// com.android.internal.R.color.notification_action_list); -// -// int color = resolvedColor; -// color = NotificationColorUtil.ensureLargeTextContrast(color, actionBg, isDark); -// color = NotificationColorUtil.ensureTextContrast(color, backgroundColor, isDark); -// -// if (color != resolvedColor) { -// if (DEBUG){ -// Log.w(TAG, String.format( -// "Enhanced contrast of notification for %s %s (over action)" -// + " and %s (over background) by changing #%s to %s", -// context.getPackageName(), -// NotificationColorUtil.contrastChange(resolvedColor, color, actionBg), -// NotificationColorUtil.contrastChange(resolvedColor, color, backgroundColor), -// Integer.toHexString(resolvedColor), Integer.toHexString(color))); -// } -// } -// return color; -// } - - /** - * Get a color that stays in the same tint, but darkens or lightens it by a certain - * amount. - * This also looks at the lightness of the provided color and shifts it appropriately. + * Returns the luminance of a color as a float between {@code 0.0} and {@code 1.0}. * - * @param color the base color to use - * @param amount the amount from 1 to 100 how much to modify the color - * @return the now color that was modified + *

Defined as the Y component in the XYZ representation of {@code color}. */ - public static int getShiftedColor(int color, int amount) { - final double[] result = ColorUtilsFromCompat.getTempDouble3Array(); - ColorUtilsFromCompat.colorToLAB(color, result); - if (result[0] >= 4) { - result[0] = Math.max(0, result[0] - amount); - } else { - result[0] = Math.min(100, result[0] + amount); - } - return ColorUtilsFromCompat.LABToColor(result[0], result[1], result[2]); - } - - public static int resolveAmbientColor(Context context, int notificationColor) { - final int resolvedColor = resolveColor(context, notificationColor); - - int color = resolvedColor; - color = NotificationColorUtil.ensureTextContrastOnBlack(color); - - if (color != resolvedColor) { - if (DEBUG) { - Log.w(TAG, String.format( - "Ambient contrast of notification for %s is %s (over black)" - + " by changing #%s to #%s", - context.getPackageName(), - NotificationColorUtil.contrastChange(resolvedColor, color, Color.BLACK), - Integer.toHexString(resolvedColor), Integer.toHexString(color))); - } - } - return color; - } - - private static boolean shouldUseDark(int backgroundColor) { - boolean useDark = backgroundColor == Notification.COLOR_DEFAULT; - if (!useDark) { - useDark = ColorUtilsFromCompat.calculateLuminance(backgroundColor) > 0.5; - } - return useDark; - } - - public static double calculateLuminance(int backgroundColor) { - return ColorUtilsFromCompat.calculateLuminance(backgroundColor); - } - - public static double calculateContrast(int foregroundColor, int backgroundColor) { - return ColorUtilsFromCompat.calculateContrast(foregroundColor, backgroundColor); - } - - public static boolean satisfiesTextContrast(int backgroundColor, int foregroundColor) { - return NotificationColorUtil.calculateContrast(foregroundColor, backgroundColor) >= 4.5; + @FloatRange(from = 0.0, to = 1.0) + public static double calculateLuminance(@ColorInt int color) { + final double[] result = getTempDouble3Array(); + colorToXYZ(color, result); + // Luminance is the Y component + return result[1] / 100; } /** - * Composite two potentially translucent colors over each other and returns the result. - */ - public static int compositeColors(int foreground, int background) { - return ColorUtilsFromCompat.compositeColors(foreground, background); - } - - public static boolean isColorLight(int backgroundColor) { - return calculateLuminance(backgroundColor) > 0.5f; - } - - /** - * Checks whether a Bitmap is a small grayscale icon. - * Grayscale here means "very close to a perfect gray"; icon means "no larger than 64dp". + * Returns the contrast ratio between {@code foreground} and {@code background}. {@code + * background} must be opaque. * - * @param bitmap The bitmap to test. - * @return True if the bitmap is grayscale; false if it is color or too large to examine. + *

Formula defined here. */ - public boolean isGrayscaleIcon(Bitmap bitmap) { - // quick test: reject large bitmaps - if (bitmap.getWidth() > mGrayscaleIconMaxSize - || bitmap.getHeight() > mGrayscaleIconMaxSize) { - return false; - } + public static double calculateContrast(@ColorInt int foreground, @ColorInt int background) { + if (Color.alpha(background) != 255) { + Log.wtf(TAG, "background can not be translucent: #" + Integer.toHexString(background)); + } + if (Color.alpha(foreground) < 255) { + // If the foreground is translucent, composite the foreground over the background + foreground = compositeColors(foreground, background); + } - synchronized (sLock) { - Pair cached = mGrayscaleBitmapCache.get(bitmap); - if (cached != null) { - if (cached.second == bitmap.getGenerationId()) { - return cached.first; - } - } - } - boolean result; - int generationId; - synchronized (mImageUtils) { - result = mImageUtils.isGrayscale(bitmap); + final double luminance1 = calculateLuminance(foreground) + 0.05; + final double luminance2 = calculateLuminance(background) + 0.05; - // generationId and the check whether the Bitmap is grayscale can't be read atomically - // here. However, since the thread is in the process of posting the notification, we can - // assume that it doesn't modify the bitmap while we are checking the pixels. - generationId = bitmap.getGenerationId(); - } - synchronized (sLock) { - mGrayscaleBitmapCache.put(bitmap, Pair.create(result, generationId)); - } - return result; - } - - private int processColor(int color) { - return Color.argb(Color.alpha(color), - 255 - Color.red(color), - 255 - Color.green(color), - 255 - Color.blue(color)); + // Now return the lighter luminance divided by the darker luminance + return Math.max(luminance1, luminance2) / Math.min(luminance1, luminance2); } /** - * Framework copy of functions needed from android.support.v4.graphics.ColorUtils. + * Convert the ARGB color to its CIE Lab representative components. + * + * @param color the ARGB color to convert. The alpha component is ignored + * @param outLab 3-element array which holds the resulting LAB components */ - private static class ColorUtilsFromCompat { - private static final double XYZ_WHITE_REFERENCE_X = 95.047; - private static final double XYZ_WHITE_REFERENCE_Y = 100; - private static final double XYZ_WHITE_REFERENCE_Z = 108.883; - private static final double XYZ_EPSILON = 0.008856; - private static final double XYZ_KAPPA = 903.3; - - private static final int MIN_ALPHA_SEARCH_MAX_ITERATIONS = 10; - private static final int MIN_ALPHA_SEARCH_PRECISION = 1; - - private static final ThreadLocal TEMP_ARRAY = new ThreadLocal<>(); - - private ColorUtilsFromCompat() { - } - - /** - * Composite two potentially translucent colors over each other and returns the result. - */ - public static int compositeColors(@ColorInt int foreground, @ColorInt int background) { - int bgAlpha = Color.alpha(background); - int fgAlpha = Color.alpha(foreground); - int a = compositeAlpha(fgAlpha, bgAlpha); - - int r = compositeComponent(Color.red(foreground), fgAlpha, - Color.red(background), bgAlpha, a); - int g = compositeComponent(Color.green(foreground), fgAlpha, - Color.green(background), bgAlpha, a); - int b = compositeComponent(Color.blue(foreground), fgAlpha, - Color.blue(background), bgAlpha, a); - - return Color.argb(a, r, g, b); - } - - private static int compositeAlpha(int foregroundAlpha, int backgroundAlpha) { - return 0xFF - (((0xFF - backgroundAlpha) * (0xFF - foregroundAlpha)) / 0xFF); - } - - private static int compositeComponent(int fgC, int fgA, int bgC, int bgA, int a) { - if (a == 0) return 0; - return ((0xFF * fgC * fgA) + (bgC * bgA * (0xFF - fgA))) / (a * 0xFF); - } - - /** - * Returns the luminance of a color as a float between {@code 0.0} and {@code 1.0}. - *

Defined as the Y component in the XYZ representation of {@code color}.

- */ - @FloatRange(from = 0.0, to = 1.0) - public static double calculateLuminance(@ColorInt int color) { - final double[] result = getTempDouble3Array(); - colorToXYZ(color, result); - // Luminance is the Y component - return result[1] / 100; - } - - /** - * Returns the contrast ratio between {@code foreground} and {@code background}. - * {@code background} must be opaque. - *

- * Formula defined - * here. - */ - public static double calculateContrast(@ColorInt int foreground, @ColorInt int background) { - if (Color.alpha(background) != 255) { - Log.wtf(TAG, "background can not be translucent: #" - + Integer.toHexString(background)); - } - if (Color.alpha(foreground) < 255) { - // If the foreground is translucent, composite the foreground over the background - foreground = compositeColors(foreground, background); - } - - final double luminance1 = calculateLuminance(foreground) + 0.05; - final double luminance2 = calculateLuminance(background) + 0.05; - - // Now return the lighter luminance divided by the darker luminance - return Math.max(luminance1, luminance2) / Math.min(luminance1, luminance2); - } - - /** - * Convert the ARGB color to its CIE Lab representative components. - * - * @param color the ARGB color to convert. The alpha component is ignored - * @param outLab 3-element array which holds the resulting LAB components - */ - public static void colorToLAB(@ColorInt int color, @NonNull double[] outLab) { - RGBToLAB(Color.red(color), Color.green(color), Color.blue(color), outLab); - } - - /** - * Convert RGB components to its CIE Lab representative components. - * - *

    - *
  • outLab[0] is L [0 ...100)
  • - *
  • outLab[1] is a [-128...127)
  • - *
  • outLab[2] is b [-128...127)
  • - *
- * - * @param r red component value [0..255] - * @param g green component value [0..255] - * @param b blue component value [0..255] - * @param outLab 3-element array which holds the resulting LAB components - */ - public static void RGBToLAB(@IntRange(from = 0x0, to = 0xFF) int r, - @IntRange(from = 0x0, to = 0xFF) int g, @IntRange(from = 0x0, to = 0xFF) int b, - @NonNull double[] outLab) { - // First we convert RGB to XYZ - RGBToXYZ(r, g, b, outLab); - // outLab now contains XYZ - XYZToLAB(outLab[0], outLab[1], outLab[2], outLab); - // outLab now contains LAB representation - } - - /** - * Convert the ARGB color to it's CIE XYZ representative components. - * - *

The resulting XYZ representation will use the D65 illuminant and the CIE - * 2° Standard Observer (1931).

- * - *
    - *
  • outXyz[0] is X [0 ...95.047)
  • - *
  • outXyz[1] is Y [0...100)
  • - *
  • outXyz[2] is Z [0...108.883)
  • - *
- * - * @param color the ARGB color to convert. The alpha component is ignored - * @param outXyz 3-element array which holds the resulting LAB components - */ - public static void colorToXYZ(@ColorInt int color, @NonNull double[] outXyz) { - RGBToXYZ(Color.red(color), Color.green(color), Color.blue(color), outXyz); - } - - /** - * Convert RGB components to it's CIE XYZ representative components. - * - *

The resulting XYZ representation will use the D65 illuminant and the CIE - * 2° Standard Observer (1931).

- * - *
    - *
  • outXyz[0] is X [0 ...95.047)
  • - *
  • outXyz[1] is Y [0...100)
  • - *
  • outXyz[2] is Z [0...108.883)
  • - *
- * - * @param r red component value [0..255] - * @param g green component value [0..255] - * @param b blue component value [0..255] - * @param outXyz 3-element array which holds the resulting XYZ components - */ - public static void RGBToXYZ(@IntRange(from = 0x0, to = 0xFF) int r, - @IntRange(from = 0x0, to = 0xFF) int g, @IntRange(from = 0x0, to = 0xFF) int b, - @NonNull double[] outXyz) { - if (outXyz.length != 3) { - throw new IllegalArgumentException("outXyz must have a length of 3."); - } - - double sr = r / 255.0; - sr = sr < 0.04045 ? sr / 12.92 : Math.pow((sr + 0.055) / 1.055, 2.4); - double sg = g / 255.0; - sg = sg < 0.04045 ? sg / 12.92 : Math.pow((sg + 0.055) / 1.055, 2.4); - double sb = b / 255.0; - sb = sb < 0.04045 ? sb / 12.92 : Math.pow((sb + 0.055) / 1.055, 2.4); - - outXyz[0] = 100 * (sr * 0.4124 + sg * 0.3576 + sb * 0.1805); - outXyz[1] = 100 * (sr * 0.2126 + sg * 0.7152 + sb * 0.0722); - outXyz[2] = 100 * (sr * 0.0193 + sg * 0.1192 + sb * 0.9505); - } - - /** - * Converts a color from CIE XYZ to CIE Lab representation. - * - *

This method expects the XYZ representation to use the D65 illuminant and the CIE - * 2° Standard Observer (1931).

- * - *
    - *
  • outLab[0] is L [0 ...100)
  • - *
  • outLab[1] is a [-128...127)
  • - *
  • outLab[2] is b [-128...127)
  • - *
- * - * @param x X component value [0...95.047) - * @param y Y component value [0...100) - * @param z Z component value [0...108.883) - * @param outLab 3-element array which holds the resulting Lab components - */ - public static void XYZToLAB(@FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_X) double x, - @FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_Y) double y, - @FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_Z) double z, - @NonNull double[] outLab) { - if (outLab.length != 3) { - throw new IllegalArgumentException("outLab must have a length of 3."); - } - x = pivotXyzComponent(x / XYZ_WHITE_REFERENCE_X); - y = pivotXyzComponent(y / XYZ_WHITE_REFERENCE_Y); - z = pivotXyzComponent(z / XYZ_WHITE_REFERENCE_Z); - outLab[0] = Math.max(0, 116 * y - 16); - outLab[1] = 500 * (x - y); - outLab[2] = 200 * (y - z); - } - - /** - * Converts a color from CIE Lab to CIE XYZ representation. - * - *

The resulting XYZ representation will use the D65 illuminant and the CIE - * 2° Standard Observer (1931).

- * - *
    - *
  • outXyz[0] is X [0 ...95.047)
  • - *
  • outXyz[1] is Y [0...100)
  • - *
  • outXyz[2] is Z [0...108.883)
  • - *
- * - * @param l L component value [0...100) - * @param a A component value [-128...127) - * @param b B component value [-128...127) - * @param outXyz 3-element array which holds the resulting XYZ components - */ - public static void LABToXYZ(@FloatRange(from = 0f, to = 100) final double l, - @FloatRange(from = -128, to = 127) final double a, - @FloatRange(from = -128, to = 127) final double b, - @NonNull double[] outXyz) { - final double fy = (l + 16) / 116; - final double fx = a / 500 + fy; - final double fz = fy - b / 200; - - double tmp = Math.pow(fx, 3); - final double xr = tmp > XYZ_EPSILON ? tmp : (116 * fx - 16) / XYZ_KAPPA; - final double yr = l > XYZ_KAPPA * XYZ_EPSILON ? Math.pow(fy, 3) : l / XYZ_KAPPA; - - tmp = Math.pow(fz, 3); - final double zr = tmp > XYZ_EPSILON ? tmp : (116 * fz - 16) / XYZ_KAPPA; - - outXyz[0] = xr * XYZ_WHITE_REFERENCE_X; - outXyz[1] = yr * XYZ_WHITE_REFERENCE_Y; - outXyz[2] = zr * XYZ_WHITE_REFERENCE_Z; - } - - /** - * Converts a color from CIE XYZ to its RGB representation. - * - *

This method expects the XYZ representation to use the D65 illuminant and the CIE - * 2° Standard Observer (1931).

- * - * @param x X component value [0...95.047) - * @param y Y component value [0...100) - * @param z Z component value [0...108.883) - * @return int containing the RGB representation - */ - @ColorInt - public static int XYZToColor(@FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_X) double x, - @FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_Y) double y, - @FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_Z) double z) { - double r = (x * 3.2406 + y * -1.5372 + z * -0.4986) / 100; - double g = (x * -0.9689 + y * 1.8758 + z * 0.0415) / 100; - double b = (x * 0.0557 + y * -0.2040 + z * 1.0570) / 100; - - r = r > 0.0031308 ? 1.055 * Math.pow(r, 1 / 2.4) - 0.055 : 12.92 * r; - g = g > 0.0031308 ? 1.055 * Math.pow(g, 1 / 2.4) - 0.055 : 12.92 * g; - b = b > 0.0031308 ? 1.055 * Math.pow(b, 1 / 2.4) - 0.055 : 12.92 * b; - - return Color.rgb( - constrain((int) Math.round(r * 255), 0, 255), - constrain((int) Math.round(g * 255), 0, 255), - constrain((int) Math.round(b * 255), 0, 255)); - } - - /** - * Converts a color from CIE Lab to its RGB representation. - * - * @param l L component value [0...100] - * @param a A component value [-128...127] - * @param b B component value [-128...127] - * @return int containing the RGB representation - */ - @ColorInt - public static int LABToColor(@FloatRange(from = 0f, to = 100) final double l, - @FloatRange(from = -128, to = 127) final double a, - @FloatRange(from = -128, to = 127) final double b) { - final double[] result = getTempDouble3Array(); - LABToXYZ(l, a, b, result); - return XYZToColor(result[0], result[1], result[2]); - } - - private static int constrain(int amount, int low, int high) { - return amount < low ? low : (amount > high ? high : amount); - } - - private static float constrain(float amount, float low, float high) { - return amount < low ? low : (amount > high ? high : amount); - } - - private static double pivotXyzComponent(double component) { - return component > XYZ_EPSILON - ? Math.pow(component, 1 / 3.0) - : (XYZ_KAPPA * component + 16) / 116; - } - - public static double[] getTempDouble3Array() { - double[] result = TEMP_ARRAY.get(); - if (result == null) { - result = new double[3]; - TEMP_ARRAY.set(result); - } - return result; - } - - /** - * Convert HSL (hue-saturation-lightness) components to a RGB color. - *
    - *
  • hsl[0] is Hue [0 .. 360)
  • - *
  • hsl[1] is Saturation [0...1]
  • - *
  • hsl[2] is Lightness [0...1]
  • - *
- * If hsv values are out of range, they are pinned. - * - * @param hsl 3-element array which holds the input HSL components - * @return the resulting RGB color - */ - @ColorInt - public static int HSLToColor(@NonNull float[] hsl) { - final float h = hsl[0]; - final float s = hsl[1]; - final float l = hsl[2]; - - final float c = (1f - Math.abs(2 * l - 1f)) * s; - final float m = l - 0.5f * c; - final float x = c * (1f - Math.abs((h / 60f % 2f) - 1f)); - - final int hueSegment = (int) h / 60; - - int r = 0, g = 0, b = 0; - - switch (hueSegment) { - case 0: - r = Math.round(255 * (c + m)); - g = Math.round(255 * (x + m)); - b = Math.round(255 * m); - break; - case 1: - r = Math.round(255 * (x + m)); - g = Math.round(255 * (c + m)); - b = Math.round(255 * m); - break; - case 2: - r = Math.round(255 * m); - g = Math.round(255 * (c + m)); - b = Math.round(255 * (x + m)); - break; - case 3: - r = Math.round(255 * m); - g = Math.round(255 * (x + m)); - b = Math.round(255 * (c + m)); - break; - case 4: - r = Math.round(255 * (x + m)); - g = Math.round(255 * m); - b = Math.round(255 * (c + m)); - break; - case 5: - case 6: - r = Math.round(255 * (c + m)); - g = Math.round(255 * m); - b = Math.round(255 * (x + m)); - break; - } - - r = constrain(r, 0, 255); - g = constrain(g, 0, 255); - b = constrain(b, 0, 255); - - return Color.rgb(r, g, b); - } - - /** - * Convert the ARGB color to its HSL (hue-saturation-lightness) components. - *
    - *
  • outHsl[0] is Hue [0 .. 360)
  • - *
  • outHsl[1] is Saturation [0...1]
  • - *
  • outHsl[2] is Lightness [0...1]
  • - *
- * - * @param color the ARGB color to convert. The alpha component is ignored - * @param outHsl 3-element array which holds the resulting HSL components - */ - public static void colorToHSL(@ColorInt int color, @NonNull float[] outHsl) { - RGBToHSL(Color.red(color), Color.green(color), Color.blue(color), outHsl); - } - - /** - * Convert RGB components to HSL (hue-saturation-lightness). - *
    - *
  • outHsl[0] is Hue [0 .. 360)
  • - *
  • outHsl[1] is Saturation [0...1]
  • - *
  • outHsl[2] is Lightness [0...1]
  • - *
- * - * @param r red component value [0..255] - * @param g green component value [0..255] - * @param b blue component value [0..255] - * @param outHsl 3-element array which holds the resulting HSL components - */ - public static void RGBToHSL(@IntRange(from = 0x0, to = 0xFF) int r, - @IntRange(from = 0x0, to = 0xFF) int g, @IntRange(from = 0x0, to = 0xFF) int b, - @NonNull float[] outHsl) { - final float rf = r / 255f; - final float gf = g / 255f; - final float bf = b / 255f; - - final float max = Math.max(rf, Math.max(gf, bf)); - final float min = Math.min(rf, Math.min(gf, bf)); - final float deltaMaxMin = max - min; - - float h, s; - float l = (max + min) / 2f; - - if (max == min) { - // Monochromatic - h = s = 0f; - } else { - if (max == rf) { - h = ((gf - bf) / deltaMaxMin) % 6f; - } else if (max == gf) { - h = ((bf - rf) / deltaMaxMin) + 2f; - } else { - h = ((rf - gf) / deltaMaxMin) + 4f; - } - - s = deltaMaxMin / (1f - Math.abs(2f * l - 1f)); - } - - h = (h * 60f) % 360f; - if (h < 0) { - h += 360f; - } - - outHsl[0] = constrain(h, 0f, 360f); - outHsl[1] = constrain(s, 0f, 1f); - outHsl[2] = constrain(l, 0f, 1f); - } - + public static void colorToLAB(@ColorInt int color, @NonNull double[] outLab) { + RGBToLAB(Color.red(color), Color.green(color), Color.blue(color), outLab); } -} \ No newline at end of file + + /** + * Convert RGB components to its CIE Lab representative components. + * + *
    + *
  • outLab[0] is L [0 ...100) + *
  • outLab[1] is a [-128...127) + *
  • outLab[2] is b [-128...127) + *
+ * + * @param r red component value [0..255] + * @param g green component value [0..255] + * @param b blue component value [0..255] + * @param outLab 3-element array which holds the resulting LAB components + */ + public static void RGBToLAB( + @IntRange(from = 0x0, to = 0xFF) int r, + @IntRange(from = 0x0, to = 0xFF) int g, + @IntRange(from = 0x0, to = 0xFF) int b, + @NonNull double[] outLab) { + // First we convert RGB to XYZ + RGBToXYZ(r, g, b, outLab); + // outLab now contains XYZ + XYZToLAB(outLab[0], outLab[1], outLab[2], outLab); + // outLab now contains LAB representation + } + + /** + * Convert the ARGB color to it's CIE XYZ representative components. + * + *

The resulting XYZ representation will use the D65 illuminant and the CIE 2° Standard + * Observer (1931). + * + *

    + *
  • outXyz[0] is X [0 ...95.047) + *
  • outXyz[1] is Y [0...100) + *
  • outXyz[2] is Z [0...108.883) + *
+ * + * @param color the ARGB color to convert. The alpha component is ignored + * @param outXyz 3-element array which holds the resulting LAB components + */ + public static void colorToXYZ(@ColorInt int color, @NonNull double[] outXyz) { + RGBToXYZ(Color.red(color), Color.green(color), Color.blue(color), outXyz); + } + + /** + * Convert RGB components to it's CIE XYZ representative components. + * + *

The resulting XYZ representation will use the D65 illuminant and the CIE 2° Standard + * Observer (1931). + * + *

    + *
  • outXyz[0] is X [0 ...95.047) + *
  • outXyz[1] is Y [0...100) + *
  • outXyz[2] is Z [0...108.883) + *
+ * + * @param r red component value [0..255] + * @param g green component value [0..255] + * @param b blue component value [0..255] + * @param outXyz 3-element array which holds the resulting XYZ components + */ + public static void RGBToXYZ( + @IntRange(from = 0x0, to = 0xFF) int r, + @IntRange(from = 0x0, to = 0xFF) int g, + @IntRange(from = 0x0, to = 0xFF) int b, + @NonNull double[] outXyz) { + if (outXyz.length != 3) { + throw new IllegalArgumentException("outXyz must have a length of 3."); + } + + double sr = r / 255.0; + sr = sr < 0.04045 ? sr / 12.92 : Math.pow((sr + 0.055) / 1.055, 2.4); + double sg = g / 255.0; + sg = sg < 0.04045 ? sg / 12.92 : Math.pow((sg + 0.055) / 1.055, 2.4); + double sb = b / 255.0; + sb = sb < 0.04045 ? sb / 12.92 : Math.pow((sb + 0.055) / 1.055, 2.4); + + outXyz[0] = 100 * (sr * 0.4124 + sg * 0.3576 + sb * 0.1805); + outXyz[1] = 100 * (sr * 0.2126 + sg * 0.7152 + sb * 0.0722); + outXyz[2] = 100 * (sr * 0.0193 + sg * 0.1192 + sb * 0.9505); + } + + /** + * Converts a color from CIE XYZ to CIE Lab representation. + * + *

This method expects the XYZ representation to use the D65 illuminant and the CIE 2° + * Standard Observer (1931). + * + *

    + *
  • outLab[0] is L [0 ...100) + *
  • outLab[1] is a [-128...127) + *
  • outLab[2] is b [-128...127) + *
+ * + * @param x X component value [0...95.047) + * @param y Y component value [0...100) + * @param z Z component value [0...108.883) + * @param outLab 3-element array which holds the resulting Lab components + */ + public static void XYZToLAB( + @FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_X) double x, + @FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_Y) double y, + @FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_Z) double z, + @NonNull double[] outLab) { + if (outLab.length != 3) { + throw new IllegalArgumentException("outLab must have a length of 3."); + } + x = pivotXyzComponent(x / XYZ_WHITE_REFERENCE_X); + y = pivotXyzComponent(y / XYZ_WHITE_REFERENCE_Y); + z = pivotXyzComponent(z / XYZ_WHITE_REFERENCE_Z); + outLab[0] = Math.max(0, 116 * y - 16); + outLab[1] = 500 * (x - y); + outLab[2] = 200 * (y - z); + } + + /** + * Converts a color from CIE Lab to CIE XYZ representation. + * + *

The resulting XYZ representation will use the D65 illuminant and the CIE 2° Standard + * Observer (1931). + * + *

    + *
  • outXyz[0] is X [0 ...95.047) + *
  • outXyz[1] is Y [0...100) + *
  • outXyz[2] is Z [0...108.883) + *
+ * + * @param l L component value [0...100) + * @param a A component value [-128...127) + * @param b B component value [-128...127) + * @param outXyz 3-element array which holds the resulting XYZ components + */ + public static void LABToXYZ( + @FloatRange(from = 0f, to = 100) final double l, + @FloatRange(from = -128, to = 127) final double a, + @FloatRange(from = -128, to = 127) final double b, + @NonNull double[] outXyz) { + final double fy = (l + 16) / 116; + final double fx = a / 500 + fy; + final double fz = fy - b / 200; + + double tmp = Math.pow(fx, 3); + final double xr = tmp > XYZ_EPSILON ? tmp : (116 * fx - 16) / XYZ_KAPPA; + final double yr = l > XYZ_KAPPA * XYZ_EPSILON ? Math.pow(fy, 3) : l / XYZ_KAPPA; + + tmp = Math.pow(fz, 3); + final double zr = tmp > XYZ_EPSILON ? tmp : (116 * fz - 16) / XYZ_KAPPA; + + outXyz[0] = xr * XYZ_WHITE_REFERENCE_X; + outXyz[1] = yr * XYZ_WHITE_REFERENCE_Y; + outXyz[2] = zr * XYZ_WHITE_REFERENCE_Z; + } + + /** + * Converts a color from CIE XYZ to its RGB representation. + * + *

This method expects the XYZ representation to use the D65 illuminant and the CIE 2° + * Standard Observer (1931). + * + * @param x X component value [0...95.047) + * @param y Y component value [0...100) + * @param z Z component value [0...108.883) + * @return int containing the RGB representation + */ + @ColorInt + public static int XYZToColor( + @FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_X) double x, + @FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_Y) double y, + @FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_Z) double z) { + double r = (x * 3.2406 + y * -1.5372 + z * -0.4986) / 100; + double g = (x * -0.9689 + y * 1.8758 + z * 0.0415) / 100; + double b = (x * 0.0557 + y * -0.2040 + z * 1.0570) / 100; + + r = r > 0.0031308 ? 1.055 * Math.pow(r, 1 / 2.4) - 0.055 : 12.92 * r; + g = g > 0.0031308 ? 1.055 * Math.pow(g, 1 / 2.4) - 0.055 : 12.92 * g; + b = b > 0.0031308 ? 1.055 * Math.pow(b, 1 / 2.4) - 0.055 : 12.92 * b; + + return Color.rgb( + constrain((int) Math.round(r * 255), 0, 255), + constrain((int) Math.round(g * 255), 0, 255), + constrain((int) Math.round(b * 255), 0, 255)); + } + + /** + * Converts a color from CIE Lab to its RGB representation. + * + * @param l L component value [0...100] + * @param a A component value [-128...127] + * @param b B component value [-128...127] + * @return int containing the RGB representation + */ + @ColorInt + public static int LABToColor( + @FloatRange(from = 0f, to = 100) final double l, + @FloatRange(from = -128, to = 127) final double a, + @FloatRange(from = -128, to = 127) final double b) { + final double[] result = getTempDouble3Array(); + LABToXYZ(l, a, b, result); + return XYZToColor(result[0], result[1], result[2]); + } + + private static int constrain(int amount, int low, int high) { + return amount < low ? low : (amount > high ? high : amount); + } + + private static float constrain(float amount, float low, float high) { + return amount < low ? low : (amount > high ? high : amount); + } + + private static double pivotXyzComponent(double component) { + return component > XYZ_EPSILON + ? Math.pow(component, 1 / 3.0) + : (XYZ_KAPPA * component + 16) / 116; + } + + public static double[] getTempDouble3Array() { + double[] result = TEMP_ARRAY.get(); + if (result == null) { + result = new double[3]; + TEMP_ARRAY.set(result); + } + return result; + } + + /** + * Convert HSL (hue-saturation-lightness) components to a RGB color. + * + *

    + *
  • hsl[0] is Hue [0 .. 360) + *
  • hsl[1] is Saturation [0...1] + *
  • hsl[2] is Lightness [0...1] + *
+ * + * If hsv values are out of range, they are pinned. + * + * @param hsl 3-element array which holds the input HSL components + * @return the resulting RGB color + */ + @ColorInt + public static int HSLToColor(@NonNull float[] hsl) { + final float h = hsl[0]; + final float s = hsl[1]; + final float l = hsl[2]; + + final float c = (1f - Math.abs(2 * l - 1f)) * s; + final float m = l - 0.5f * c; + final float x = c * (1f - Math.abs((h / 60f % 2f) - 1f)); + + final int hueSegment = (int) h / 60; + + int r = 0, g = 0, b = 0; + + switch (hueSegment) { + case 0: + r = Math.round(255 * (c + m)); + g = Math.round(255 * (x + m)); + b = Math.round(255 * m); + break; + case 1: + r = Math.round(255 * (x + m)); + g = Math.round(255 * (c + m)); + b = Math.round(255 * m); + break; + case 2: + r = Math.round(255 * m); + g = Math.round(255 * (c + m)); + b = Math.round(255 * (x + m)); + break; + case 3: + r = Math.round(255 * m); + g = Math.round(255 * (x + m)); + b = Math.round(255 * (c + m)); + break; + case 4: + r = Math.round(255 * (x + m)); + g = Math.round(255 * m); + b = Math.round(255 * (c + m)); + break; + case 5: + case 6: + r = Math.round(255 * (c + m)); + g = Math.round(255 * m); + b = Math.round(255 * (x + m)); + break; + } + + r = constrain(r, 0, 255); + g = constrain(g, 0, 255); + b = constrain(b, 0, 255); + + return Color.rgb(r, g, b); + } + + /** + * Convert the ARGB color to its HSL (hue-saturation-lightness) components. + * + *
    + *
  • outHsl[0] is Hue [0 .. 360) + *
  • outHsl[1] is Saturation [0...1] + *
  • outHsl[2] is Lightness [0...1] + *
+ * + * @param color the ARGB color to convert. The alpha component is ignored + * @param outHsl 3-element array which holds the resulting HSL components + */ + public static void colorToHSL(@ColorInt int color, @NonNull float[] outHsl) { + RGBToHSL(Color.red(color), Color.green(color), Color.blue(color), outHsl); + } + + /** + * Convert RGB components to HSL (hue-saturation-lightness). + * + *
    + *
  • outHsl[0] is Hue [0 .. 360) + *
  • outHsl[1] is Saturation [0...1] + *
  • outHsl[2] is Lightness [0...1] + *
+ * + * @param r red component value [0..255] + * @param g green component value [0..255] + * @param b blue component value [0..255] + * @param outHsl 3-element array which holds the resulting HSL components + */ + public static void RGBToHSL( + @IntRange(from = 0x0, to = 0xFF) int r, + @IntRange(from = 0x0, to = 0xFF) int g, + @IntRange(from = 0x0, to = 0xFF) int b, + @NonNull float[] outHsl) { + final float rf = r / 255f; + final float gf = g / 255f; + final float bf = b / 255f; + + final float max = Math.max(rf, Math.max(gf, bf)); + final float min = Math.min(rf, Math.min(gf, bf)); + final float deltaMaxMin = max - min; + + float h, s; + float l = (max + min) / 2f; + + if (max == min) { + // Monochromatic + h = s = 0f; + } else { + if (max == rf) { + h = ((gf - bf) / deltaMaxMin) % 6f; + } else if (max == gf) { + h = ((bf - rf) / deltaMaxMin) + 2f; + } else { + h = ((rf - gf) / deltaMaxMin) + 4f; + } + + s = deltaMaxMin / (1f - Math.abs(2f * l - 1f)); + } + + h = (h * 60f) % 360f; + if (h < 0) { + h += 360f; + } + + outHsl[0] = constrain(h, 0f, 360f); + outHsl[1] = constrain(s, 0f, 1f); + outHsl[2] = constrain(l, 0f, 1f); + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/views/BaselineGridTextView.java b/app/src/main/java/code/name/monkey/retromusic/views/BaselineGridTextView.java index 8cce44b1..711094db 100644 --- a/app/src/main/java/code/name/monkey/retromusic/views/BaselineGridTextView.java +++ b/app/src/main/java/code/name/monkey/retromusic/views/BaselineGridTextView.java @@ -19,184 +19,173 @@ import android.content.res.TypedArray; import android.graphics.Paint; import android.util.AttributeSet; import android.util.TypedValue; - import androidx.annotation.FontRes; - -import com.google.android.material.textview.MaterialTextView; - import code.name.monkey.retromusic.R; +import com.google.android.material.textview.MaterialTextView; public class BaselineGridTextView extends MaterialTextView { - private final float FOUR_DIP; + private final float FOUR_DIP; - private int extraBottomPadding = 0; + private int extraBottomPadding = 0; - private int extraTopPadding = 0; + private int extraTopPadding = 0; - private @FontRes - int fontResId = 0; + private @FontRes int fontResId = 0; - private float lineHeightHint = 0f; + private float lineHeightHint = 0f; - private float lineHeightMultiplierHint = 1f; + private float lineHeightMultiplierHint = 1f; - private boolean maxLinesByHeight = false; + private boolean maxLinesByHeight = false; - public BaselineGridTextView(Context context) { - this(context, null); + public BaselineGridTextView(Context context) { + this(context, null); + } + + public BaselineGridTextView(Context context, AttributeSet attrs) { + this(context, attrs, android.R.attr.textViewStyle); + } + + public BaselineGridTextView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + final TypedArray a = + context.obtainStyledAttributes(attrs, R.styleable.BaselineGridTextView, defStyleAttr, 0); + + // first check TextAppearance for line height & font attributes + if (a.hasValue(R.styleable.BaselineGridTextView_android_textAppearance)) { + int textAppearanceId = + a.getResourceId( + R.styleable.BaselineGridTextView_android_textAppearance, + android.R.style.TextAppearance); + TypedArray ta = + context.obtainStyledAttributes(textAppearanceId, R.styleable.BaselineGridTextView); + parseTextAttrs(ta); + ta.recycle(); } - public BaselineGridTextView(Context context, AttributeSet attrs) { - this(context, attrs, android.R.attr.textViewStyle); + // then check view attrs + parseTextAttrs(a); + maxLinesByHeight = a.getBoolean(R.styleable.BaselineGridTextView_maxLinesByHeight, false); + a.recycle(); + + FOUR_DIP = + TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, 4, getResources().getDisplayMetrics()); + computeLineHeight(); + } + + @Override + public int getCompoundPaddingBottom() { + // include extra padding to make the height a multiple of 4dp + return super.getCompoundPaddingBottom() + extraBottomPadding; + } + + @Override + public int getCompoundPaddingTop() { + // include extra padding to place the first line's baseline on the grid + return super.getCompoundPaddingTop() + extraTopPadding; + } + + public @FontRes int getFontResId() { + return fontResId; + } + + public float getLineHeightHint() { + return lineHeightHint; + } + + public void setLineHeightHint(float lineHeightHint) { + this.lineHeightHint = lineHeightHint; + computeLineHeight(); + } + + public float getLineHeightMultiplierHint() { + return lineHeightMultiplierHint; + } + + public void setLineHeightMultiplierHint(float lineHeightMultiplierHint) { + this.lineHeightMultiplierHint = lineHeightMultiplierHint; + computeLineHeight(); + } + + public boolean getMaxLinesByHeight() { + return maxLinesByHeight; + } + + public void setMaxLinesByHeight(boolean maxLinesByHeight) { + this.maxLinesByHeight = maxLinesByHeight; + requestLayout(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + extraTopPadding = 0; + extraBottomPadding = 0; + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + int height = getMeasuredHeight(); + height += ensureBaselineOnGrid(); + height += ensureHeightGridAligned(height); + setMeasuredDimension(getMeasuredWidth(), height); + checkMaxLines(height, MeasureSpec.getMode(heightMeasureSpec)); + } + + /** + * When measured with an exact height, text can be vertically clipped mid-line. Prevent this by + * setting the {@code maxLines} property based on the available space. + */ + private void checkMaxLines(int height, int heightMode) { + if (!maxLinesByHeight || heightMode != MeasureSpec.EXACTLY) { + return; } - public BaselineGridTextView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); + int textHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom(); + int completeLines = (int) Math.floor(textHeight / getLineHeight()); + setMaxLines(completeLines); + } - final TypedArray a = context.obtainStyledAttributes( - attrs, R.styleable.BaselineGridTextView, defStyleAttr, 0); + /** Ensures line height is a multiple of 4dp. */ + private void computeLineHeight() { + final Paint.FontMetrics fm = getPaint().getFontMetrics(); + final float fontHeight = Math.abs(fm.ascent - fm.descent) + fm.leading; + final float desiredLineHeight = + (lineHeightHint > 0) ? lineHeightHint : lineHeightMultiplierHint * fontHeight; - // first check TextAppearance for line height & font attributes - if (a.hasValue(R.styleable.BaselineGridTextView_android_textAppearance)) { - int textAppearanceId = - a.getResourceId(R.styleable.BaselineGridTextView_android_textAppearance, - android.R.style.TextAppearance); - TypedArray ta = context.obtainStyledAttributes( - textAppearanceId, R.styleable.BaselineGridTextView); - parseTextAttrs(ta); - ta.recycle(); - } + final int baselineAlignedLineHeight = + (int) ((FOUR_DIP * (float) Math.ceil(desiredLineHeight / FOUR_DIP)) + 0.5f); + setLineSpacing(baselineAlignedLineHeight - fontHeight, 1f); + } - // then check view attrs - parseTextAttrs(a); - maxLinesByHeight = a.getBoolean(R.styleable.BaselineGridTextView_maxLinesByHeight, false); - a.recycle(); - - FOUR_DIP = TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, 4, getResources().getDisplayMetrics()); - computeLineHeight(); + /** Ensure that the first line of text sits on the 4dp grid. */ + private int ensureBaselineOnGrid() { + float baseline = getBaseline(); + float gridAlign = baseline % FOUR_DIP; + if (gridAlign != 0) { + extraTopPadding = (int) (FOUR_DIP - Math.ceil(gridAlign)); } + return extraTopPadding; + } - @Override - public int getCompoundPaddingBottom() { - // include extra padding to make the height a multiple of 4dp - return super.getCompoundPaddingBottom() + extraBottomPadding; + /** Ensure that height is a multiple of 4dp. */ + private int ensureHeightGridAligned(int height) { + float gridOverhang = height % FOUR_DIP; + if (gridOverhang != 0) { + extraBottomPadding = (int) (FOUR_DIP - Math.ceil(gridOverhang)); } + return extraBottomPadding; + } - @Override - public int getCompoundPaddingTop() { - // include extra padding to place the first line's baseline on the grid - return super.getCompoundPaddingTop() + extraTopPadding; + private void parseTextAttrs(TypedArray a) { + if (a.hasValue(R.styleable.BaselineGridTextView_lineHeightMultiplierHint)) { + lineHeightMultiplierHint = + a.getFloat(R.styleable.BaselineGridTextView_lineHeightMultiplierHint, 1f); } - - public @FontRes - int getFontResId() { - return fontResId; + if (a.hasValue(R.styleable.BaselineGridTextView_lineHeightHint)) { + lineHeightHint = a.getDimensionPixelSize(R.styleable.BaselineGridTextView_lineHeightHint, 0); } - - public float getLineHeightHint() { - return lineHeightHint; + if (a.hasValue(R.styleable.BaselineGridTextView_android_fontFamily)) { + fontResId = a.getResourceId(R.styleable.BaselineGridTextView_android_fontFamily, 0); } - - public void setLineHeightHint(float lineHeightHint) { - this.lineHeightHint = lineHeightHint; - computeLineHeight(); - } - - public float getLineHeightMultiplierHint() { - return lineHeightMultiplierHint; - } - - public void setLineHeightMultiplierHint(float lineHeightMultiplierHint) { - this.lineHeightMultiplierHint = lineHeightMultiplierHint; - computeLineHeight(); - } - - public boolean getMaxLinesByHeight() { - return maxLinesByHeight; - } - - public void setMaxLinesByHeight(boolean maxLinesByHeight) { - this.maxLinesByHeight = maxLinesByHeight; - requestLayout(); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - extraTopPadding = 0; - extraBottomPadding = 0; - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - int height = getMeasuredHeight(); - height += ensureBaselineOnGrid(); - height += ensureHeightGridAligned(height); - setMeasuredDimension(getMeasuredWidth(), height); - checkMaxLines(height, MeasureSpec.getMode(heightMeasureSpec)); - } - - /** - * When measured with an exact height, text can be vertically clipped mid-line. Prevent - * this by setting the {@code maxLines} property based on the available space. - */ - private void checkMaxLines(int height, int heightMode) { - if (!maxLinesByHeight || heightMode != MeasureSpec.EXACTLY) { - return; - } - - int textHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom(); - int completeLines = (int) Math.floor(textHeight / getLineHeight()); - setMaxLines(completeLines); - } - - /** - * Ensures line height is a multiple of 4dp. - */ - private void computeLineHeight() { - final Paint.FontMetrics fm = getPaint().getFontMetrics(); - final float fontHeight = Math.abs(fm.ascent - fm.descent) + fm.leading; - final float desiredLineHeight = (lineHeightHint > 0) - ? lineHeightHint - : lineHeightMultiplierHint * fontHeight; - - final int baselineAlignedLineHeight = - (int) ((FOUR_DIP * (float) Math.ceil(desiredLineHeight / FOUR_DIP)) + 0.5f); - setLineSpacing(baselineAlignedLineHeight - fontHeight, 1f); - } - - /** - * Ensure that the first line of text sits on the 4dp grid. - */ - private int ensureBaselineOnGrid() { - float baseline = getBaseline(); - float gridAlign = baseline % FOUR_DIP; - if (gridAlign != 0) { - extraTopPadding = (int) (FOUR_DIP - Math.ceil(gridAlign)); - } - return extraTopPadding; - } - - /** - * Ensure that height is a multiple of 4dp. - */ - private int ensureHeightGridAligned(int height) { - float gridOverhang = height % FOUR_DIP; - if (gridOverhang != 0) { - extraBottomPadding = (int) (FOUR_DIP - Math.ceil(gridOverhang)); - } - return extraBottomPadding; - } - - private void parseTextAttrs(TypedArray a) { - if (a.hasValue(R.styleable.BaselineGridTextView_lineHeightMultiplierHint)) { - lineHeightMultiplierHint = - a.getFloat(R.styleable.BaselineGridTextView_lineHeightMultiplierHint, 1f); - } - if (a.hasValue(R.styleable.BaselineGridTextView_lineHeightHint)) { - lineHeightHint = a.getDimensionPixelSize( - R.styleable.BaselineGridTextView_lineHeightHint, 0); - } - if (a.hasValue(R.styleable.BaselineGridTextView_android_fontFamily)) { - fontResId = a.getResourceId(R.styleable.BaselineGridTextView_android_fontFamily, 0); - } - } -} \ No newline at end of file + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/views/BreadCrumbLayout.java b/app/src/main/java/code/name/monkey/retromusic/views/BreadCrumbLayout.java index 944ca9c1..c13802b1 100644 --- a/app/src/main/java/code/name/monkey/retromusic/views/BreadCrumbLayout.java +++ b/app/src/main/java/code/name/monkey/retromusic/views/BreadCrumbLayout.java @@ -26,422 +26,429 @@ import android.widget.HorizontalScrollView; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; - import androidx.annotation.ColorInt; import androidx.annotation.NonNull; - +import code.name.monkey.appthemehelper.util.ATHUtil; +import code.name.monkey.retromusic.R; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; -import code.name.monkey.appthemehelper.util.ATHUtil; -import code.name.monkey.retromusic.R; - -/** - * @author Aidan Follestad (afollestad), modified for Phonograph by Karim Abou Zeid (kabouzeid) - */ +/** @author Aidan Follestad (afollestad), modified for Phonograph by Karim Abou Zeid (kabouzeid) */ public class BreadCrumbLayout extends HorizontalScrollView implements View.OnClickListener { - @ColorInt - private int contentColorActivated; - @ColorInt - private int contentColorDeactivated; - private int mActive; - private SelectionCallback mCallback; - private LinearLayout mChildFrame; - // Stores currently visible crumbs - private List mCrumbs; - // Stores user's navigation history, like a fragment back stack - private List mHistory; - // Used in setActiveOrAdd() between clearing crumbs and adding the new set, nullified afterwards - private List mOldCrumbs; + @ColorInt private int contentColorActivated; + @ColorInt private int contentColorDeactivated; + private int mActive; + private SelectionCallback mCallback; + private LinearLayout mChildFrame; + // Stores currently visible crumbs + private List mCrumbs; + // Stores user's navigation history, like a fragment back stack + private List mHistory; + // Used in setActiveOrAdd() between clearing crumbs and adding the new set, nullified afterwards + private List mOldCrumbs; - public BreadCrumbLayout(Context context) { - super(context); - init(); + public BreadCrumbLayout(Context context) { + super(context); + init(); + } + + public BreadCrumbLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public BreadCrumbLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + public void addCrumb(@NonNull Crumb crumb, boolean refreshLayout) { + LinearLayout view = + (LinearLayout) LayoutInflater.from(getContext()).inflate(R.layout.bread_crumb, this, false); + view.setTag(mCrumbs.size()); + view.setOnClickListener(this); + + ImageView iv = (ImageView) view.getChildAt(1); + if (iv.getDrawable() != null) { + iv.getDrawable().setAutoMirrored(true); } + iv.setVisibility(View.GONE); - public BreadCrumbLayout(Context context, AttributeSet attrs) { - super(context, attrs); - init(); + mChildFrame.addView( + view, + new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + mCrumbs.add(crumb); + if (refreshLayout) { + mActive = mCrumbs.size() - 1; + requestLayout(); } + invalidateActivatedAll(); + } - public BreadCrumbLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(); + public void addHistory(Crumb crumb) { + mHistory.add(crumb); + } + + public void clearCrumbs() { + try { + mOldCrumbs = new ArrayList<>(mCrumbs); + mCrumbs.clear(); + mChildFrame.removeAllViews(); + } catch (IllegalStateException e) { + e.printStackTrace(); } + } - public void addCrumb(@NonNull Crumb crumb, boolean refreshLayout) { - LinearLayout view = (LinearLayout) LayoutInflater.from(getContext()) - .inflate(R.layout.bread_crumb, this, false); - view.setTag(mCrumbs.size()); - view.setOnClickListener(this); + public void clearHistory() { + mHistory.clear(); + } - ImageView iv = (ImageView) view.getChildAt(1); - if (iv.getDrawable() != null) { - iv.getDrawable().setAutoMirrored(true); - } - iv.setVisibility(View.GONE); - - mChildFrame.addView(view, new ViewGroup.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - mCrumbs.add(crumb); - if (refreshLayout) { - mActive = mCrumbs.size() - 1; - requestLayout(); - } - invalidateActivatedAll(); + public Crumb findCrumb(@NonNull File forDir) { + for (int i = 0; i < mCrumbs.size(); i++) { + if (mCrumbs.get(i).getFile().equals(forDir)) { + return mCrumbs.get(i); + } } + return null; + } - public void addHistory(Crumb crumb) { - mHistory.add(crumb); + public int getActiveIndex() { + return mActive; + } + + public Crumb getCrumb(int index) { + return mCrumbs.get(index); + } + + public SavedStateWrapper getStateWrapper() { + return new SavedStateWrapper(this); + } + + public int historySize() { + return mHistory.size(); + } + + public Crumb lastHistory() { + if (mHistory.size() == 0) { + return null; } + return mHistory.get(mHistory.size() - 1); + } - public void clearCrumbs() { - try { - mOldCrumbs = new ArrayList<>(mCrumbs); - mCrumbs.clear(); - mChildFrame.removeAllViews(); - } catch (IllegalStateException e) { - e.printStackTrace(); - } + @Override + public void onClick(View v) { + if (mCallback != null) { + int index = (Integer) v.getTag(); + mCallback.onCrumbSelection(mCrumbs.get(index), index); } + } - public void clearHistory() { - mHistory.clear(); + public boolean popHistory() { + if (mHistory.size() == 0) { + return false; } + mHistory.remove(mHistory.size() - 1); + return mHistory.size() != 0; + } - public Crumb findCrumb(@NonNull File forDir) { - for (int i = 0; i < mCrumbs.size(); i++) { - if (mCrumbs.get(i).getFile().equals(forDir)) { - return mCrumbs.get(i); + public void restoreFromStateWrapper(SavedStateWrapper mSavedState) { + if (mSavedState != null) { + mActive = mSavedState.mActive; + for (Crumb c : mSavedState.mCrumbs) { + addCrumb(c, false); + } + requestLayout(); + setVisibility(mSavedState.mVisibility); + } + } + + public void reverseHistory() { + Collections.reverse(mHistory); + } + + public void setActivatedContentColor(@ColorInt int contentColorActivated) { + this.contentColorActivated = contentColorActivated; + } + + public void setActiveOrAdd(@NonNull Crumb crumb, boolean forceRecreate) { + if (forceRecreate || !setActive(crumb)) { + clearCrumbs(); + final List newPathSet = new ArrayList<>(); + + newPathSet.add(0, crumb.getFile()); + + File p = crumb.getFile(); + while ((p = p.getParentFile()) != null) { + newPathSet.add(0, p); + } + + for (int index = 0; index < newPathSet.size(); index++) { + final File fi = newPathSet.get(index); + crumb = new Crumb(fi); + + // Restore scroll positions saved before clearing + if (mOldCrumbs != null) { + for (Iterator iterator = mOldCrumbs.iterator(); iterator.hasNext(); ) { + Crumb old = iterator.next(); + if (old.equals(crumb)) { + crumb.setScrollPosition(old.getScrollPosition()); + iterator.remove(); // minimize number of linear passes by removing un-used crumbs from + // history + break; } + } } - return null; + + addCrumb(crumb, true); + } + + // History no longer needed + mOldCrumbs = null; + } + } + + public void setCallback(SelectionCallback callback) { + mCallback = callback; + } + + public void setDeactivatedContentColor(@ColorInt int contentColorDeactivated) { + this.contentColorDeactivated = contentColorDeactivated; + } + + public int size() { + return mCrumbs.size(); + } + + public boolean trim(String path, boolean dir) { + if (!dir) { + return false; + } + int index = -1; + for (int i = mCrumbs.size() - 1; i >= 0; i--) { + File fi = mCrumbs.get(i).getFile(); + if (fi.getPath().equals(path)) { + index = i; + break; + } } - public int getActiveIndex() { - return mActive; + boolean removedActive = index >= mActive; + if (index > -1) { + while (index <= mCrumbs.size() - 1) { + removeCrumbAt(index); + } + if (mChildFrame.getChildCount() > 0) { + int lastIndex = mCrumbs.size() - 1; + invalidateActivated(mChildFrame.getChildAt(lastIndex), mActive == lastIndex, false, false); + } + } + return removedActive || mCrumbs.size() == 0; + } + + public boolean trim(File file) { + return trim(file.getPath(), file.isDirectory()); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + // RTL works fine like this + View child = mChildFrame.getChildAt(mActive); + if (child != null) { + smoothScrollTo(child.getLeft(), 0); + } + } + + void invalidateActivatedAll() { + for (int i = 0; i < mCrumbs.size(); i++) { + Crumb crumb = mCrumbs.get(i); + invalidateActivated( + mChildFrame.getChildAt(i), + mActive == mCrumbs.indexOf(crumb), + false, + i < mCrumbs.size() - 1) + .setText(crumb.getTitle()); + } + } + + void removeCrumbAt(int index) { + mCrumbs.remove(index); + mChildFrame.removeViewAt(index); + } + + void updateIndices() { + for (int i = 0; i < mChildFrame.getChildCount(); i++) { + mChildFrame.getChildAt(i).setTag(i); + } + } + + private void init() { + contentColorActivated = + ATHUtil.INSTANCE.resolveColor(getContext(), android.R.attr.textColorPrimary); + contentColorDeactivated = + ATHUtil.INSTANCE.resolveColor(getContext(), android.R.attr.textColorSecondary); + setMinimumHeight((int) getResources().getDimension(R.dimen.tab_height)); + setClipToPadding(false); + setHorizontalScrollBarEnabled(false); + mCrumbs = new ArrayList<>(); + mHistory = new ArrayList<>(); + mChildFrame = new LinearLayout(getContext()); + addView( + mChildFrame, + new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)); + } + + private TextView invalidateActivated( + View view, + final boolean isActive, + final boolean noArrowIfAlone, + final boolean allowArrowVisible) { + int contentColor = isActive ? contentColorActivated : contentColorDeactivated; + LinearLayout child = (LinearLayout) view; + TextView tv = (TextView) child.getChildAt(0); + tv.setTextColor(contentColor); + ImageView iv = (ImageView) child.getChildAt(1); + iv.setColorFilter(contentColor, PorterDuff.Mode.SRC_IN); + if (noArrowIfAlone && getChildCount() == 1) { + iv.setVisibility(View.GONE); + } else if (allowArrowVisible) { + iv.setVisibility(View.VISIBLE); + } else { + iv.setVisibility(View.GONE); + } + return tv; + } + + private boolean setActive(Crumb newActive) { + mActive = mCrumbs.indexOf(newActive); + invalidateActivatedAll(); + boolean success = mActive > -1; + if (success) { + requestLayout(); + } + return success; + } + + public interface SelectionCallback { + + void onCrumbSelection(Crumb crumb, int index); + } + + public static class Crumb implements Parcelable { + + public static final Creator CREATOR = + new Creator() { + @Override + public Crumb createFromParcel(Parcel source) { + return new Crumb(source); + } + + @Override + public Crumb[] newArray(int size) { + return new Crumb[size]; + } + }; + + private final File file; + + private int scrollPos; + + public Crumb(File file) { + this.file = file; } - public Crumb getCrumb(int index) { - return mCrumbs.get(index); - } - - public SavedStateWrapper getStateWrapper() { - return new SavedStateWrapper(this); - } - - public int historySize() { - return mHistory.size(); - } - - public Crumb lastHistory() { - if (mHistory.size() == 0) { - return null; - } - return mHistory.get(mHistory.size() - 1); + protected Crumb(Parcel in) { + this.file = (File) in.readSerializable(); + this.scrollPos = in.readInt(); } @Override - public void onClick(View v) { - if (mCallback != null) { - int index = (Integer) v.getTag(); - mCallback.onCrumbSelection(mCrumbs.get(index), index); - } - } - - public boolean popHistory() { - if (mHistory.size() == 0) { - return false; - } - mHistory.remove(mHistory.size() - 1); - return mHistory.size() != 0; - } - - public void restoreFromStateWrapper(SavedStateWrapper mSavedState) { - if (mSavedState != null) { - mActive = mSavedState.mActive; - for (Crumb c : mSavedState.mCrumbs) { - addCrumb(c, false); - } - requestLayout(); - setVisibility(mSavedState.mVisibility); - } - } - - public void reverseHistory() { - Collections.reverse(mHistory); - } - - public void setActivatedContentColor(@ColorInt int contentColorActivated) { - this.contentColorActivated = contentColorActivated; - } - - public void setActiveOrAdd(@NonNull Crumb crumb, boolean forceRecreate) { - if (forceRecreate || !setActive(crumb)) { - clearCrumbs(); - final List newPathSet = new ArrayList<>(); - - newPathSet.add(0, crumb.getFile()); - - File p = crumb.getFile(); - while ((p = p.getParentFile()) != null) { - newPathSet.add(0, p); - } - - for (int index = 0; index < newPathSet.size(); index++) { - final File fi = newPathSet.get(index); - crumb = new Crumb(fi); - - // Restore scroll positions saved before clearing - if (mOldCrumbs != null) { - for (Iterator iterator = mOldCrumbs.iterator(); iterator.hasNext(); ) { - Crumb old = iterator.next(); - if (old.equals(crumb)) { - crumb.setScrollPosition(old.getScrollPosition()); - iterator.remove(); // minimize number of linear passes by removing un-used crumbs from history - break; - } - } - } - - addCrumb(crumb, true); - } - - // History no longer needed - mOldCrumbs = null; - } - } - - public void setCallback(SelectionCallback callback) { - mCallback = callback; - } - - public void setDeactivatedContentColor(@ColorInt int contentColorDeactivated) { - this.contentColorDeactivated = contentColorDeactivated; - } - - public int size() { - return mCrumbs.size(); - } - - public boolean trim(String path, boolean dir) { - if (!dir) { - return false; - } - int index = -1; - for (int i = mCrumbs.size() - 1; i >= 0; i--) { - File fi = mCrumbs.get(i).getFile(); - if (fi.getPath().equals(path)) { - index = i; - break; - } - } - - boolean removedActive = index >= mActive; - if (index > -1) { - while (index <= mCrumbs.size() - 1) { - removeCrumbAt(index); - } - if (mChildFrame.getChildCount() > 0) { - int lastIndex = mCrumbs.size() - 1; - invalidateActivated(mChildFrame.getChildAt(lastIndex), mActive == lastIndex, false, false); - } - } - return removedActive || mCrumbs.size() == 0; - } - - public boolean trim(File file) { - return trim(file.getPath(), file.isDirectory()); + public int describeContents() { + return 0; } @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - super.onLayout(changed, l, t, r, b); - //RTL works fine like this - View child = mChildFrame.getChildAt(mActive); - if (child != null) { - smoothScrollTo(child.getLeft(), 0); - } + public boolean equals(Object o) { + return (o instanceof Crumb) + && ((Crumb) o).getFile() != null + && ((Crumb) o).getFile().equals(getFile()); } - void invalidateActivatedAll() { - for (int i = 0; i < mCrumbs.size(); i++) { - Crumb crumb = mCrumbs.get(i); - invalidateActivated(mChildFrame.getChildAt(i), mActive == mCrumbs.indexOf(crumb), false, - i < mCrumbs.size() - 1).setText(crumb.getTitle()); - } + public File getFile() { + return file; } - void removeCrumbAt(int index) { - mCrumbs.remove(index); - mChildFrame.removeViewAt(index); + public int getScrollPosition() { + return scrollPos; } - void updateIndices() { - for (int i = 0; i < mChildFrame.getChildCount(); i++) { - mChildFrame.getChildAt(i).setTag(i); - } + public void setScrollPosition(int scrollY) { + this.scrollPos = scrollY; } - private void init() { - contentColorActivated = ATHUtil.INSTANCE.resolveColor(getContext(), android.R.attr.textColorPrimary); - contentColorDeactivated = ATHUtil.INSTANCE.resolveColor(getContext(), android.R.attr.textColorSecondary); - setMinimumHeight((int) getResources().getDimension(R.dimen.tab_height)); - setClipToPadding(false); - setHorizontalScrollBarEnabled(false); - mCrumbs = new ArrayList<>(); - mHistory = new ArrayList<>(); - mChildFrame = new LinearLayout(getContext()); - addView(mChildFrame, new ViewGroup.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)); + public String getTitle() { + return file.getPath().equals("/") ? "root" : file.getName(); } - private TextView invalidateActivated(View view, final boolean isActive, final boolean noArrowIfAlone, - final boolean allowArrowVisible) { - int contentColor = isActive ? contentColorActivated : contentColorDeactivated; - LinearLayout child = (LinearLayout) view; - TextView tv = (TextView) child.getChildAt(0); - tv.setTextColor(contentColor); - ImageView iv = (ImageView) child.getChildAt(1); - iv.setColorFilter(contentColor, PorterDuff.Mode.SRC_IN); - if (noArrowIfAlone && getChildCount() == 1) { - iv.setVisibility(View.GONE); - } else if (allowArrowVisible) { - iv.setVisibility(View.VISIBLE); - } else { - iv.setVisibility(View.GONE); - } - return tv; + @Override + public String toString() { + return "Crumb{" + "file=" + file + ", scrollPos=" + scrollPos + '}'; } - private boolean setActive(Crumb newActive) { - mActive = mCrumbs.indexOf(newActive); - invalidateActivatedAll(); - boolean success = mActive > -1; - if (success) { - requestLayout(); - } - return success; + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeSerializable(this.file); + dest.writeInt(this.scrollPos); } + } - public interface SelectionCallback { + public static class SavedStateWrapper implements Parcelable { - void onCrumbSelection(Crumb crumb, int index); - } + public static final Creator CREATOR = + new Creator() { + public SavedStateWrapper createFromParcel(Parcel source) { + return new SavedStateWrapper(source); + } - public static class Crumb implements Parcelable { - - public static final Creator CREATOR = new Creator() { - @Override - public Crumb createFromParcel(Parcel source) { - return new Crumb(source); - } - - @Override - public Crumb[] newArray(int size) { - return new Crumb[size]; - } + public SavedStateWrapper[] newArray(int size) { + return new SavedStateWrapper[size]; + } }; - private final File file; + public final int mActive; - private int scrollPos; + public final List mCrumbs; - public Crumb(File file) { - this.file = file; - } + public final int mVisibility; - protected Crumb(Parcel in) { - this.file = (File) in.readSerializable(); - this.scrollPos = in.readInt(); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public boolean equals(Object o) { - return (o instanceof Crumb) && ((Crumb) o).getFile() != null && - ((Crumb) o).getFile().equals(getFile()); - } - - public File getFile() { - return file; - } - - public int getScrollPosition() { - return scrollPos; - } - - public void setScrollPosition(int scrollY) { - this.scrollPos = scrollY; - } - - public String getTitle() { - return file.getPath().equals("/") ? "root" : file.getName(); - } - - @Override - public String toString() { - return "Crumb{" + - "file=" + file + - ", scrollPos=" + scrollPos + - '}'; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeSerializable(this.file); - dest.writeInt(this.scrollPos); - } + public SavedStateWrapper(BreadCrumbLayout view) { + mActive = view.mActive; + mCrumbs = view.mCrumbs; + mVisibility = view.getVisibility(); } - public static class SavedStateWrapper implements Parcelable { - - public static final Creator CREATOR = new Creator() { - public SavedStateWrapper createFromParcel(Parcel source) { - return new SavedStateWrapper(source); - } - - public SavedStateWrapper[] newArray(int size) { - return new SavedStateWrapper[size]; - } - }; - - public final int mActive; - - public final List mCrumbs; - - public final int mVisibility; - - public SavedStateWrapper(BreadCrumbLayout view) { - mActive = view.mActive; - mCrumbs = view.mCrumbs; - mVisibility = view.getVisibility(); - } - - protected SavedStateWrapper(Parcel in) { - this.mActive = in.readInt(); - this.mCrumbs = in.createTypedArrayList(Crumb.CREATOR); - this.mVisibility = in.readInt(); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(this.mActive); - dest.writeTypedList(mCrumbs); - dest.writeInt(this.mVisibility); - } + protected SavedStateWrapper(Parcel in) { + this.mActive = in.readInt(); + this.mCrumbs = in.createTypedArrayList(Crumb.CREATOR); + this.mVisibility = in.readInt(); } -} \ No newline at end of file + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(this.mActive); + dest.writeTypedList(mCrumbs); + dest.writeInt(this.mVisibility); + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/views/CircularImageView.java b/app/src/main/java/code/name/monkey/retromusic/views/CircularImageView.java index 3b555cf2..2c0dccad 100644 --- a/app/src/main/java/code/name/monkey/retromusic/views/CircularImageView.java +++ b/app/src/main/java/code/name/monkey/retromusic/views/CircularImageView.java @@ -27,299 +27,311 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.util.Log; - import androidx.appcompat.widget.AppCompatImageView; - import code.name.monkey.retromusic.R; public class CircularImageView extends AppCompatImageView { - private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP; + private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP; - // Default Values - private static final float DEFAULT_BORDER_WIDTH = 4; - private static final float DEFAULT_SHADOW_RADIUS = 8.0f; + // Default Values + private static final float DEFAULT_BORDER_WIDTH = 4; + private static final float DEFAULT_SHADOW_RADIUS = 8.0f; - // Properties - private float borderWidth; - private int canvasSize; - private float shadowRadius; - private int shadowColor = Color.BLACK; + // Properties + private float borderWidth; + private int canvasSize; + private float shadowRadius; + private int shadowColor = Color.BLACK; - // Object used to draw - private Bitmap image; - private Drawable drawable; - private Paint paint; - private Paint paintBorder; + // Object used to draw + private Bitmap image; + private Drawable drawable; + private Paint paint; + private Paint paintBorder; - //region Constructor & Init Method - public CircularImageView(final Context context) { - this(context, null); + // region Constructor & Init Method + public CircularImageView(final Context context) { + this(context, null); + } + + public CircularImageView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public CircularImageView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs, defStyleAttr); + } + + private void init(Context context, AttributeSet attrs, int defStyleAttr) { + // Init paint + paint = new Paint(); + paint.setAntiAlias(true); + + paintBorder = new Paint(); + paintBorder.setAntiAlias(true); + + // Load the styled attributes and set their properties + TypedArray attributes = + context.obtainStyledAttributes(attrs, R.styleable.CircularImageView, defStyleAttr, 0); + + // Init Border + if (attributes.getBoolean(R.styleable.CircularImageView_civ_border, true)) { + float defaultBorderSize = + DEFAULT_BORDER_WIDTH * getContext().getResources().getDisplayMetrics().density; + setBorderWidth( + attributes.getDimension( + R.styleable.CircularImageView_civ_border_width, defaultBorderSize)); + setBorderColor( + attributes.getColor(R.styleable.CircularImageView_civ_border_color, Color.WHITE)); } - public CircularImageView(Context context, AttributeSet attrs) { - this(context, attrs, 0); + // Init Shadow + if (attributes.getBoolean(R.styleable.CircularImageView_civ_shadow, false)) { + shadowRadius = DEFAULT_SHADOW_RADIUS; + drawShadow( + attributes.getFloat(R.styleable.CircularImageView_civ_shadow_radius, shadowRadius), + attributes.getColor(R.styleable.CircularImageView_civ_shadow_color, shadowColor)); + } + attributes.recycle(); + } + // endregion + + // region Set Attr Method + public void setBorderWidth(float borderWidth) { + this.borderWidth = borderWidth; + requestLayout(); + invalidate(); + } + + public void setBorderColor(int borderColor) { + if (paintBorder != null) { + paintBorder.setColor(borderColor); + } + invalidate(); + } + + public void addShadow() { + if (shadowRadius == 0) { + shadowRadius = DEFAULT_SHADOW_RADIUS; + } + drawShadow(shadowRadius, shadowColor); + invalidate(); + } + + public void setShadowRadius(float shadowRadius) { + drawShadow(shadowRadius, shadowColor); + invalidate(); + } + + public void setShadowColor(int shadowColor) { + drawShadow(shadowRadius, shadowColor); + invalidate(); + } + + @Override + public ScaleType getScaleType() { + return SCALE_TYPE; + } + + @Override + public void setScaleType(ScaleType scaleType) { + if (scaleType != SCALE_TYPE) { + throw new IllegalArgumentException( + String.format( + "ScaleType %s not supported. ScaleType.CENTER_CROP is used by default. So you don't need to use ScaleType.", + scaleType)); + } + } + // endregion + + // region Draw Method + @Override + public void onDraw(Canvas canvas) { + // Load the bitmap + loadBitmap(); + + // Check if image isn't null + if (image == null) { + return; } - public CircularImageView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(context, attrs, defStyleAttr); + if (!isInEditMode()) { + canvasSize = canvas.getWidth(); + if (canvas.getHeight() < canvasSize) { + canvasSize = canvas.getHeight(); + } } - private void init(Context context, AttributeSet attrs, int defStyleAttr) { - // Init paint - paint = new Paint(); - paint.setAntiAlias(true); + // circleCenter is the x or y of the view's center + // radius is the radius in pixels of the cirle to be drawn + // paint contains the shader that will texture the shape + int circleCenter = (int) (canvasSize - (borderWidth * 2)) / 2; + // Draw Border + canvas.drawCircle( + circleCenter + borderWidth, + circleCenter + borderWidth, + circleCenter + borderWidth - (shadowRadius + shadowRadius / 2), + paintBorder); + // Draw CircularImageView + canvas.drawCircle( + circleCenter + borderWidth, + circleCenter + borderWidth, + circleCenter - (shadowRadius + shadowRadius / 2), + paint); + } - paintBorder = new Paint(); - paintBorder.setAntiAlias(true); - - // Load the styled attributes and set their properties - TypedArray attributes = context - .obtainStyledAttributes(attrs, R.styleable.CircularImageView, defStyleAttr, 0); - - // Init Border - if (attributes.getBoolean(R.styleable.CircularImageView_civ_border, true)) { - float defaultBorderSize = - DEFAULT_BORDER_WIDTH * getContext().getResources().getDisplayMetrics().density; - setBorderWidth(attributes - .getDimension(R.styleable.CircularImageView_civ_border_width, defaultBorderSize)); - setBorderColor( - attributes.getColor(R.styleable.CircularImageView_civ_border_color, Color.WHITE)); - } - - // Init Shadow - if (attributes.getBoolean(R.styleable.CircularImageView_civ_shadow, false)) { - shadowRadius = DEFAULT_SHADOW_RADIUS; - drawShadow(attributes.getFloat(R.styleable.CircularImageView_civ_shadow_radius, shadowRadius), - attributes.getColor(R.styleable.CircularImageView_civ_shadow_color, shadowColor)); - } - attributes.recycle(); - } - //endregion - - //region Set Attr Method - public void setBorderWidth(float borderWidth) { - this.borderWidth = borderWidth; - requestLayout(); - invalidate(); + private void loadBitmap() { + if (this.drawable == getDrawable()) { + return; } - public void setBorderColor(int borderColor) { - if (paintBorder != null) { - paintBorder.setColor(borderColor); - } - invalidate(); + this.drawable = getDrawable(); + this.image = drawableToBitmap(this.drawable); + updateShader(); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + canvasSize = w; + if (h < canvasSize) { + canvasSize = h; + } + if (image != null) { + updateShader(); + } + } + + private void drawShadow(float shadowRadius, int shadowColor) { + this.shadowRadius = shadowRadius; + this.shadowColor = shadowColor; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) { + setLayerType(LAYER_TYPE_SOFTWARE, paintBorder); + } + paintBorder.setShadowLayer(shadowRadius, 0.0f, shadowRadius / 2, shadowColor); + } + + private void updateShader() { + if (image == null) { + return; } - public void addShadow() { - if (shadowRadius == 0) { - shadowRadius = DEFAULT_SHADOW_RADIUS; - } - drawShadow(shadowRadius, shadowColor); - invalidate(); + // Crop Center Image + image = cropBitmap(image); + + // Create Shader + BitmapShader shader = new BitmapShader(image, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); + + // Center Image in Shader + Matrix matrix = new Matrix(); + matrix.setScale( + (float) canvasSize / (float) image.getWidth(), + (float) canvasSize / (float) image.getHeight()); + shader.setLocalMatrix(matrix); + + // Set Shader in Paint + paint.setShader(shader); + } + + private Bitmap cropBitmap(Bitmap bitmap) { + Bitmap bmp; + if (bitmap.getWidth() >= bitmap.getHeight()) { + bmp = + Bitmap.createBitmap( + bitmap, + bitmap.getWidth() / 2 - bitmap.getHeight() / 2, + 0, + bitmap.getHeight(), + bitmap.getHeight()); + } else { + bmp = + Bitmap.createBitmap( + bitmap, + 0, + bitmap.getHeight() / 2 - bitmap.getWidth() / 2, + bitmap.getWidth(), + bitmap.getWidth()); + } + return bmp; + } + + private Bitmap drawableToBitmap(Drawable drawable) { + if (drawable == null) { + return null; + } else if (drawable instanceof BitmapDrawable) { + return ((BitmapDrawable) drawable).getBitmap(); } - public void setShadowRadius(float shadowRadius) { - drawShadow(shadowRadius, shadowColor); - invalidate(); + int intrinsicWidth = drawable.getIntrinsicWidth(); + int intrinsicHeight = drawable.getIntrinsicHeight(); + + if (!(intrinsicWidth > 0 && intrinsicHeight > 0)) { + return null; } - public void setShadowColor(int shadowColor) { - drawShadow(shadowRadius, shadowColor); - invalidate(); + try { + // Create Bitmap object out of the drawable + Bitmap bitmap = Bitmap.createBitmap(intrinsicWidth, intrinsicHeight, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + return bitmap; + } catch (OutOfMemoryError e) { + // Simply return null of failed bitmap creations + Log.e(getClass().toString(), "Encountered OutOfMemoryError while generating bitmap!"); + return null; + } + } + // endregion + + // region Mesure Method + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = measureWidth(widthMeasureSpec); + int height = measureHeight(heightMeasureSpec); + /*int imageSize = (width < height) ? width : height; + setMeasuredDimension(imageSize, imageSize);*/ + setMeasuredDimension(width, height); + } + + private int measureWidth(int measureSpec) { + int result; + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + + if (specMode == MeasureSpec.EXACTLY) { + // The parent has determined an exact size for the child. + result = specSize; + } else if (specMode == MeasureSpec.AT_MOST) { + // The child can be as large as it wants up to the specified size. + result = specSize; + } else { + // The parent has not imposed any constraint on the child. + result = canvasSize; } - @Override - public ScaleType getScaleType() { - return SCALE_TYPE; + return result; + } + + private int measureHeight(int measureSpecHeight) { + int result; + int specMode = MeasureSpec.getMode(measureSpecHeight); + int specSize = MeasureSpec.getSize(measureSpecHeight); + + if (specMode == MeasureSpec.EXACTLY) { + // We were told how big to be + result = specSize; + } else if (specMode == MeasureSpec.AT_MOST) { + // The child can be as large as it wants up to the specified size. + result = specSize; + } else { + // Measure the text (beware: ascent is a negative number) + result = canvasSize; } - @Override - public void setScaleType(ScaleType scaleType) { - if (scaleType != SCALE_TYPE) { - throw new IllegalArgumentException(String.format( - "ScaleType %s not supported. ScaleType.CENTER_CROP is used by default. So you don't need to use ScaleType.", - scaleType)); - } - } - //endregion - - //region Draw Method - @Override - public void onDraw(Canvas canvas) { - // Load the bitmap - loadBitmap(); - - // Check if image isn't null - if (image == null) { - return; - } - - if (!isInEditMode()) { - canvasSize = canvas.getWidth(); - if (canvas.getHeight() < canvasSize) { - canvasSize = canvas.getHeight(); - } - } - - // circleCenter is the x or y of the view's center - // radius is the radius in pixels of the cirle to be drawn - // paint contains the shader that will texture the shape - int circleCenter = (int) (canvasSize - (borderWidth * 2)) / 2; - // Draw Border - canvas.drawCircle(circleCenter + borderWidth, circleCenter + borderWidth, - circleCenter + borderWidth - (shadowRadius + shadowRadius / 2), paintBorder); - // Draw CircularImageView - canvas.drawCircle(circleCenter + borderWidth, circleCenter + borderWidth, - circleCenter - (shadowRadius + shadowRadius / 2), paint); - } - - private void loadBitmap() { - if (this.drawable == getDrawable()) { - return; - } - - this.drawable = getDrawable(); - this.image = drawableToBitmap(this.drawable); - updateShader(); - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - canvasSize = w; - if (h < canvasSize) { - canvasSize = h; - } - if (image != null) { - updateShader(); - } - } - - private void drawShadow(float shadowRadius, int shadowColor) { - this.shadowRadius = shadowRadius; - this.shadowColor = shadowColor; - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) { - setLayerType(LAYER_TYPE_SOFTWARE, paintBorder); - } - paintBorder.setShadowLayer(shadowRadius, 0.0f, shadowRadius / 2, shadowColor); - } - - private void updateShader() { - if (image == null) { - return; - } - - // Crop Center Image - image = cropBitmap(image); - - // Create Shader - BitmapShader shader = new BitmapShader(image, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); - - // Center Image in Shader - Matrix matrix = new Matrix(); - matrix.setScale((float) canvasSize / (float) image.getWidth(), - (float) canvasSize / (float) image.getHeight()); - shader.setLocalMatrix(matrix); - - // Set Shader in Paint - paint.setShader(shader); - } - - private Bitmap cropBitmap(Bitmap bitmap) { - Bitmap bmp; - if (bitmap.getWidth() >= bitmap.getHeight()) { - bmp = Bitmap.createBitmap( - bitmap, - bitmap.getWidth() / 2 - bitmap.getHeight() / 2, - 0, - bitmap.getHeight(), bitmap.getHeight()); - } else { - bmp = Bitmap.createBitmap( - bitmap, - 0, - bitmap.getHeight() / 2 - bitmap.getWidth() / 2, - bitmap.getWidth(), bitmap.getWidth()); - } - return bmp; - } - - private Bitmap drawableToBitmap(Drawable drawable) { - if (drawable == null) { - return null; - } else if (drawable instanceof BitmapDrawable) { - return ((BitmapDrawable) drawable).getBitmap(); - } - - int intrinsicWidth = drawable.getIntrinsicWidth(); - int intrinsicHeight = drawable.getIntrinsicHeight(); - - if (!(intrinsicWidth > 0 && intrinsicHeight > 0)) { - return null; - } - - try { - // Create Bitmap object out of the drawable - Bitmap bitmap = Bitmap.createBitmap(intrinsicWidth, intrinsicHeight, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); - drawable.draw(canvas); - return bitmap; - } catch (OutOfMemoryError e) { - // Simply return null of failed bitmap creations - Log.e(getClass().toString(), "Encountered OutOfMemoryError while generating bitmap!"); - return null; - } - } - //endregion - - //region Mesure Method - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int width = measureWidth(widthMeasureSpec); - int height = measureHeight(heightMeasureSpec); - /*int imageSize = (width < height) ? width : height; - setMeasuredDimension(imageSize, imageSize);*/ - setMeasuredDimension(width, height); - } - - private int measureWidth(int measureSpec) { - int result; - int specMode = MeasureSpec.getMode(measureSpec); - int specSize = MeasureSpec.getSize(measureSpec); - - if (specMode == MeasureSpec.EXACTLY) { - // The parent has determined an exact size for the child. - result = specSize; - } else if (specMode == MeasureSpec.AT_MOST) { - // The child can be as large as it wants up to the specified size. - result = specSize; - } else { - // The parent has not imposed any constraint on the child. - result = canvasSize; - } - - return result; - } - - private int measureHeight(int measureSpecHeight) { - int result; - int specMode = MeasureSpec.getMode(measureSpecHeight); - int specSize = MeasureSpec.getSize(measureSpecHeight); - - if (specMode == MeasureSpec.EXACTLY) { - // We were told how big to be - result = specSize; - } else if (specMode == MeasureSpec.AT_MOST) { - // The child can be as large as it wants up to the specified size. - result = specSize; - } else { - // Measure the text (beware: ascent is a negative number) - result = canvasSize; - } - - return (result + 2); - } - //endregion -} \ No newline at end of file + return (result + 2); + } + // endregion +} diff --git a/app/src/main/java/code/name/monkey/retromusic/views/ContributorsView.java b/app/src/main/java/code/name/monkey/retromusic/views/ContributorsView.java index f5e25904..1ce50b0a 100644 --- a/app/src/main/java/code/name/monkey/retromusic/views/ContributorsView.java +++ b/app/src/main/java/code/name/monkey/retromusic/views/ContributorsView.java @@ -14,6 +14,8 @@ package code.name.monkey.retromusic.views; +import static code.name.monkey.retromusic.util.RetroUtil.openUrl; + import android.app.Activity; import android.content.Context; import android.content.res.TypedArray; @@ -22,55 +24,54 @@ import android.view.LayoutInflater; import android.view.View; import android.widget.FrameLayout; import android.widget.TextView; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import code.name.monkey.retromusic.R; -import static code.name.monkey.retromusic.util.RetroUtil.openUrl; - public class ContributorsView extends FrameLayout { - public ContributorsView(@NonNull Context context) { - super(context); - init(context, null); - } - - public ContributorsView(@NonNull Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - init(context, attrs); - } - - public ContributorsView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(context, attrs); - } - - private void init(Context context, AttributeSet attributeSet) { - final TypedArray attributes = context.obtainStyledAttributes(attributeSet, R.styleable.ContributorsView, 0, 0); - if (attributes != null) { - final View layout = LayoutInflater.from(context).inflate(R.layout.item_contributor, this); - - NetworkImageView networkImageView = layout.findViewById(R.id.image); - String url = attributes.getString(R.styleable.ContributorsView_profile_url); - networkImageView.setImageUrl(url); - - String name = attributes.getString(R.styleable.ContributorsView_profile_name); - TextView title = layout.findViewById(R.id.title); - title.setText(name); - - String summary = attributes.getString(R.styleable.ContributorsView_profile_summary); - TextView text = layout.findViewById(R.id.text); - text.setText(summary); - - String link = attributes.getString(R.styleable.ContributorsView_profile_link); - layout.setOnClickListener(v -> { - if (link == null) { - return; - } - openUrl((Activity) getContext(), link); - }); - attributes.recycle(); - } + public ContributorsView(@NonNull Context context) { + super(context); + init(context, null); + } + + public ContributorsView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(context, attrs); + } + + public ContributorsView( + @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs); + } + + private void init(Context context, AttributeSet attributeSet) { + final TypedArray attributes = + context.obtainStyledAttributes(attributeSet, R.styleable.ContributorsView, 0, 0); + if (attributes != null) { + final View layout = LayoutInflater.from(context).inflate(R.layout.item_contributor, this); + + NetworkImageView networkImageView = layout.findViewById(R.id.image); + String url = attributes.getString(R.styleable.ContributorsView_profile_url); + networkImageView.setImageUrl(url); + + String name = attributes.getString(R.styleable.ContributorsView_profile_name); + TextView title = layout.findViewById(R.id.title); + title.setText(name); + + String summary = attributes.getString(R.styleable.ContributorsView_profile_summary); + TextView text = layout.findViewById(R.id.text); + text.setText(summary); + + String link = attributes.getString(R.styleable.ContributorsView_profile_link); + layout.setOnClickListener( + v -> { + if (link == null) { + return; + } + openUrl((Activity) getContext(), link); + }); + attributes.recycle(); } + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/views/DrawableGradient.java b/app/src/main/java/code/name/monkey/retromusic/views/DrawableGradient.java index aad7cdbd..e2a5daa5 100644 --- a/app/src/main/java/code/name/monkey/retromusic/views/DrawableGradient.java +++ b/app/src/main/java/code/name/monkey/retromusic/views/DrawableGradient.java @@ -17,20 +17,19 @@ package code.name.monkey.retromusic.views; import android.graphics.drawable.GradientDrawable; public class DrawableGradient extends GradientDrawable { - public DrawableGradient(Orientation orientations, int[] colors, int shape) { - super(orientations, colors); - try { - setShape(shape); - setGradientType(GradientDrawable.LINEAR_GRADIENT); - setCornerRadius(0); - } catch (Exception e) { - e.printStackTrace(); - } - } - - public DrawableGradient SetTransparency(int transparencyPercent) { - this.setAlpha(255 - ((255 * transparencyPercent) / 100)); - return this; + public DrawableGradient(Orientation orientations, int[] colors, int shape) { + super(orientations, colors); + try { + setShape(shape); + setGradientType(GradientDrawable.LINEAR_GRADIENT); + setCornerRadius(0); + } catch (Exception e) { + e.printStackTrace(); } + } + public DrawableGradient SetTransparency(int transparencyPercent) { + this.setAlpha(255 - ((255 * transparencyPercent) / 100)); + return this; + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/views/HeightFitSquareLayout.java b/app/src/main/java/code/name/monkey/retromusic/views/HeightFitSquareLayout.java index 9aebf19e..8ed2a9e6 100755 --- a/app/src/main/java/code/name/monkey/retromusic/views/HeightFitSquareLayout.java +++ b/app/src/main/java/code/name/monkey/retromusic/views/HeightFitSquareLayout.java @@ -20,34 +20,34 @@ import android.util.AttributeSet; import android.widget.FrameLayout; public class HeightFitSquareLayout extends FrameLayout { - private boolean forceSquare = true; + private boolean forceSquare = true; - public HeightFitSquareLayout(Context context) { - super(context); - } + public HeightFitSquareLayout(Context context) { + super(context); + } - public HeightFitSquareLayout(Context context, AttributeSet attributeSet) { - super(context, attributeSet); - } + public HeightFitSquareLayout(Context context, AttributeSet attributeSet) { + super(context, attributeSet); + } - public HeightFitSquareLayout(Context context, AttributeSet attributeSet, int i) { - super(context, attributeSet, i); - } + public HeightFitSquareLayout(Context context, AttributeSet attributeSet, int i) { + super(context, attributeSet, i); + } - @TargetApi(21) - public HeightFitSquareLayout(Context context, AttributeSet attributeSet, int i, int i2) { - super(context, attributeSet, i, i2); - } + @TargetApi(21) + public HeightFitSquareLayout(Context context, AttributeSet attributeSet, int i, int i2) { + super(context, attributeSet, i, i2); + } - public void forceSquare(boolean z) { - this.forceSquare = z; - requestLayout(); - } + public void forceSquare(boolean z) { + this.forceSquare = z; + requestLayout(); + } - protected void onMeasure(int i, int i2) { - if (this.forceSquare) { - i = i2; - } - super.onMeasure(i, i2); + protected void onMeasure(int i, int i2) { + if (this.forceSquare) { + i = i2; } + super.onMeasure(i, i2); + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/views/LollipopFixedWebView.java b/app/src/main/java/code/name/monkey/retromusic/views/LollipopFixedWebView.java index b599977f..66284340 100644 --- a/app/src/main/java/code/name/monkey/retromusic/views/LollipopFixedWebView.java +++ b/app/src/main/java/code/name/monkey/retromusic/views/LollipopFixedWebView.java @@ -22,28 +22,30 @@ import android.util.AttributeSet; import android.webkit.WebView; public class LollipopFixedWebView extends WebView { - public LollipopFixedWebView(Context context) { - super(getFixedContext(context)); - } + public LollipopFixedWebView(Context context) { + super(getFixedContext(context)); + } - public LollipopFixedWebView(Context context, AttributeSet attrs) { - super(getFixedContext(context), attrs); - } + public LollipopFixedWebView(Context context, AttributeSet attrs) { + super(getFixedContext(context), attrs); + } - public LollipopFixedWebView(Context context, AttributeSet attrs, int defStyleAttr) { - super(getFixedContext(context), attrs, defStyleAttr); - } + public LollipopFixedWebView(Context context, AttributeSet attrs, int defStyleAttr) { + super(getFixedContext(context), attrs, defStyleAttr); + } - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - public LollipopFixedWebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(getFixedContext(context), attrs, defStyleAttr, defStyleRes); - } + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public LollipopFixedWebView( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(getFixedContext(context), attrs, defStyleAttr, defStyleRes); + } - public LollipopFixedWebView(Context context, AttributeSet attrs, int defStyleAttr, boolean privateBrowsing) { - super(getFixedContext(context), attrs, defStyleAttr, privateBrowsing); - } + public LollipopFixedWebView( + Context context, AttributeSet attrs, int defStyleAttr, boolean privateBrowsing) { + super(getFixedContext(context), attrs, defStyleAttr, privateBrowsing); + } - public static Context getFixedContext(Context context) { - return context.createConfigurationContext(new Configuration()); - } -} \ No newline at end of file + public static Context getFixedContext(Context context) { + return context.createConfigurationContext(new Configuration()); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/views/NetworkImageView.java b/app/src/main/java/code/name/monkey/retromusic/views/NetworkImageView.java index 378550bd..f115ff94 100644 --- a/app/src/main/java/code/name/monkey/retromusic/views/NetworkImageView.java +++ b/app/src/main/java/code/name/monkey/retromusic/views/NetworkImageView.java @@ -17,50 +17,47 @@ package code.name.monkey.retromusic.views; import android.content.Context; import android.content.res.TypedArray; import android.util.AttributeSet; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - +import code.name.monkey.retromusic.R; import com.bumptech.glide.Glide; -import code.name.monkey.retromusic.R; - -/** - * @author Hemanth S (h4h13). - */ +/** @author Hemanth S (h4h13). */ public class NetworkImageView extends CircularImageView { - public NetworkImageView(@NonNull Context context) { - super(context); - init(context, null); - } + public NetworkImageView(@NonNull Context context) { + super(context); + init(context, null); + } - public NetworkImageView(@NonNull Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - init(context, attrs); - } + public NetworkImageView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(context, attrs); + } - public NetworkImageView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(context, attrs); - } + public NetworkImageView( + @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs); + } - public void setImageUrl(@NonNull String imageUrl) { - setImageUrl(getContext(), imageUrl); - } + public void setImageUrl(@NonNull String imageUrl) { + setImageUrl(getContext(), imageUrl); + } - public void setImageUrl(@NonNull Context context, @NonNull String imageUrl) { - Glide.with(context) - .load(imageUrl) - .error(R.drawable.ic_account) - .placeholder(R.drawable.ic_account) - .into(this); - } + public void setImageUrl(@NonNull Context context, @NonNull String imageUrl) { + Glide.with(context) + .load(imageUrl) + .error(R.drawable.ic_account) + .placeholder(R.drawable.ic_account) + .into(this); + } - private void init(Context context, AttributeSet attributeSet) { - TypedArray attributes = context.obtainStyledAttributes(attributeSet, R.styleable.NetworkImageView, 0, 0); - String url = attributes.getString(R.styleable.NetworkImageView_url_link); - setImageUrl(context, url); - attributes.recycle(); - } + private void init(Context context, AttributeSet attributeSet) { + TypedArray attributes = + context.obtainStyledAttributes(attributeSet, R.styleable.NetworkImageView, 0, 0); + String url = attributes.getString(R.styleable.NetworkImageView_url_link); + setImageUrl(context, url); + attributes.recycle(); + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/views/PopupBackground.java b/app/src/main/java/code/name/monkey/retromusic/views/PopupBackground.java index 88b930c7..685c0089 100644 --- a/app/src/main/java/code/name/monkey/retromusic/views/PopupBackground.java +++ b/app/src/main/java/code/name/monkey/retromusic/views/PopupBackground.java @@ -27,134 +27,135 @@ import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Build; import android.view.View; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.graphics.drawable.DrawableCompat; - import code.name.monkey.appthemehelper.ThemeStore; import code.name.monkey.retromusic.R; public class PopupBackground extends Drawable { - private final int mPaddingEnd; + private final int mPaddingEnd; - private final int mPaddingStart; + private final int mPaddingStart; - @NonNull - private final Paint mPaint; + @NonNull private final Paint mPaint; - @NonNull - private final Path mPath = new Path(); + @NonNull private final Path mPath = new Path(); - @NonNull - private final Matrix mTempMatrix = new Matrix(); + @NonNull private final Matrix mTempMatrix = new Matrix(); - public PopupBackground(@NonNull Context context) { - mPaint = new Paint(); - mPaint.setAntiAlias(true); - mPaint.setColor(ThemeStore.Companion.accentColor(context)); - mPaint.setStyle(Paint.Style.FILL); - Resources resources = context.getResources(); - mPaddingStart = resources.getDimensionPixelOffset(R.dimen.afs_md2_popup_padding_start); - mPaddingEnd = resources.getDimensionPixelOffset(R.dimen.afs_md2_popup_padding_end); + public PopupBackground(@NonNull Context context) { + mPaint = new Paint(); + mPaint.setAntiAlias(true); + mPaint.setColor(ThemeStore.Companion.accentColor(context)); + mPaint.setStyle(Paint.Style.FILL); + Resources resources = context.getResources(); + mPaddingStart = resources.getDimensionPixelOffset(R.dimen.afs_md2_popup_padding_start); + mPaddingEnd = resources.getDimensionPixelOffset(R.dimen.afs_md2_popup_padding_end); + } + + private static void pathArcTo( + @NonNull Path path, + float centerX, + float centerY, + float radius, + float startAngle, + float sweepAngle) { + path.arcTo( + centerX - radius, + centerY - radius, + centerX + radius, + centerY + radius, + startAngle, + sweepAngle, + false); + } + + @Override + public void draw(@NonNull Canvas canvas) { + canvas.drawPath(mPath, mPaint); + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } + + @Override + public void getOutline(@NonNull Outline outline) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q && !mPath.isConvex()) { + // The outline path must be convex before Q, but we may run into floating point error + // caused by calculation involving sqrt(2) or OEM implementation difference, so in this + // case we just omit the shadow instead of crashing. + super.getOutline(outline); + return; } + outline.setConvexPath(mPath); + } - private static void pathArcTo(@NonNull Path path, float centerX, float centerY, float radius, - float startAngle, float sweepAngle) { - path.arcTo(centerX - radius, centerY - radius, centerX + radius, centerY + radius, - startAngle, sweepAngle, false); + @Override + public void setAlpha(int alpha) {} + + @Override + public void setColorFilter(@Nullable ColorFilter colorFilter) {} + + @Override + public boolean getPadding(@NonNull Rect padding) { + if (needMirroring()) { + padding.set(mPaddingEnd, 0, mPaddingStart, 0); + } else { + padding.set(mPaddingStart, 0, mPaddingEnd, 0); } + return true; + } - @Override - public void draw(@NonNull Canvas canvas) { - canvas.drawPath(mPath, mPaint); + @Override + public boolean isAutoMirrored() { + return true; + } + + @Override + public boolean onLayoutDirectionChanged(int layoutDirection) { + updatePath(); + return true; + } + + @Override + protected void onBoundsChange(@NonNull Rect bounds) { + updatePath(); + } + + private boolean needMirroring() { + return DrawableCompat.getLayoutDirection(this) == View.LAYOUT_DIRECTION_RTL; + } + + private void updatePath() { + + mPath.reset(); + + Rect bounds = getBounds(); + float width = bounds.width(); + float height = bounds.height(); + float r = height / 2; + float sqrt2 = (float) Math.sqrt(2); + // Ensure we are convex. + width = Math.max(r + sqrt2 * r, width); + pathArcTo(mPath, r, r, r, 90, 180); + float o1X = width - sqrt2 * r; + pathArcTo(mPath, o1X, r, r, -90, 45f); + float r2 = r / 5; + float o2X = width - sqrt2 * r2; + pathArcTo(mPath, o2X, r, r2, -45, 90); + pathArcTo(mPath, o1X, r, r, 45f, 45f); + mPath.close(); + + if (needMirroring()) { + mTempMatrix.setScale(-1, 1, width / 2, 0); + } else { + mTempMatrix.reset(); } - - @Override - public int getOpacity() { - return PixelFormat.TRANSLUCENT; - } - - @Override - public void getOutline(@NonNull Outline outline) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q && !mPath.isConvex()) { - // The outline path must be convex before Q, but we may run into floating point error - // caused by calculation involving sqrt(2) or OEM implementation difference, so in this - // case we just omit the shadow instead of crashing. - super.getOutline(outline); - return; - } - outline.setConvexPath(mPath); - } - - @Override - public void setAlpha(int alpha) { - - } - - @Override - public void setColorFilter(@Nullable ColorFilter colorFilter) { - - } - - @Override - public boolean getPadding(@NonNull Rect padding) { - if (needMirroring()) { - padding.set(mPaddingEnd, 0, mPaddingStart, 0); - } else { - padding.set(mPaddingStart, 0, mPaddingEnd, 0); - } - return true; - } - - @Override - public boolean isAutoMirrored() { - return true; - } - - @Override - public boolean onLayoutDirectionChanged(int layoutDirection) { - updatePath(); - return true; - } - - - @Override - protected void onBoundsChange(@NonNull Rect bounds) { - updatePath(); - } - - private boolean needMirroring() { - return DrawableCompat.getLayoutDirection(this) == View.LAYOUT_DIRECTION_RTL; - } - - private void updatePath() { - - mPath.reset(); - - Rect bounds = getBounds(); - float width = bounds.width(); - float height = bounds.height(); - float r = height / 2; - float sqrt2 = (float) Math.sqrt(2); - // Ensure we are convex. - width = Math.max(r + sqrt2 * r, width); - pathArcTo(mPath, r, r, r, 90, 180); - float o1X = width - sqrt2 * r; - pathArcTo(mPath, o1X, r, r, -90, 45f); - float r2 = r / 5; - float o2X = width - sqrt2 * r2; - pathArcTo(mPath, o2X, r, r2, -45, 90); - pathArcTo(mPath, o1X, r, r, 45f, 45f); - mPath.close(); - - if (needMirroring()) { - mTempMatrix.setScale(-1, 1, width / 2, 0); - } else { - mTempMatrix.reset(); - } - mTempMatrix.postTranslate(bounds.left, bounds.top); - mPath.transform(mTempMatrix); - } -} \ No newline at end of file + mTempMatrix.postTranslate(bounds.left, bounds.top); + mPath.transform(mTempMatrix); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/views/ScrollingViewOnApplyWindowInsetsListener.java b/app/src/main/java/code/name/monkey/retromusic/views/ScrollingViewOnApplyWindowInsetsListener.java index 1abe0bac..7f2d049a 100644 --- a/app/src/main/java/code/name/monkey/retromusic/views/ScrollingViewOnApplyWindowInsetsListener.java +++ b/app/src/main/java/code/name/monkey/retromusic/views/ScrollingViewOnApplyWindowInsetsListener.java @@ -17,42 +17,46 @@ package code.name.monkey.retromusic.views; import android.graphics.Rect; import android.view.View; import android.view.WindowInsets; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import me.zhanghai.android.fastscroll.FastScroller; public class ScrollingViewOnApplyWindowInsetsListener implements View.OnApplyWindowInsetsListener { - @NonNull - private final Rect mPadding = new Rect(); - @Nullable - private final FastScroller mFastScroller; + @NonNull private final Rect mPadding = new Rect(); + @Nullable private final FastScroller mFastScroller; - public ScrollingViewOnApplyWindowInsetsListener(@Nullable View view, - @Nullable FastScroller fastScroller) { - if (view != null) { - mPadding.set(view.getPaddingLeft(), view.getPaddingTop(), view.getPaddingRight(), - view.getPaddingBottom()); - } - mFastScroller = fastScroller; + public ScrollingViewOnApplyWindowInsetsListener( + @Nullable View view, @Nullable FastScroller fastScroller) { + if (view != null) { + mPadding.set( + view.getPaddingLeft(), + view.getPaddingTop(), + view.getPaddingRight(), + view.getPaddingBottom()); } + mFastScroller = fastScroller; + } - public ScrollingViewOnApplyWindowInsetsListener() { - this(null, null); - } + public ScrollingViewOnApplyWindowInsetsListener() { + this(null, null); + } - @NonNull - @Override - public WindowInsets onApplyWindowInsets(@NonNull View view, @NonNull WindowInsets insets) { - view.setPadding(mPadding.left + insets.getSystemWindowInsetLeft(), mPadding.top, - mPadding.right + insets.getSystemWindowInsetRight(), - mPadding.bottom + insets.getSystemWindowInsetBottom()); - if (mFastScroller != null) { - mFastScroller.setPadding(insets.getSystemWindowInsetLeft(), 0, - insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()); - } - return insets; + @NonNull + @Override + public WindowInsets onApplyWindowInsets(@NonNull View view, @NonNull WindowInsets insets) { + view.setPadding( + mPadding.left + insets.getSystemWindowInsetLeft(), + mPadding.top, + mPadding.right + insets.getSystemWindowInsetRight(), + mPadding.bottom + insets.getSystemWindowInsetBottom()); + if (mFastScroller != null) { + mFastScroller.setPadding( + insets.getSystemWindowInsetLeft(), + 0, + insets.getSystemWindowInsetRight(), + insets.getSystemWindowInsetBottom()); } -} \ No newline at end of file + return insets; + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/views/SeekArc.java b/app/src/main/java/code/name/monkey/retromusic/views/SeekArc.java index 684410fc..82b6b546 100644 --- a/app/src/main/java/code/name/monkey/retromusic/views/SeekArc.java +++ b/app/src/main/java/code/name/monkey/retromusic/views/SeekArc.java @@ -24,529 +24,488 @@ import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; - import code.name.monkey.retromusic.R; /** * SeekArc.java - *

- * This is a class that functions much like a SeekBar but - * follows a circle path instead of a straight line. + * + *

This is a class that functions much like a SeekBar but follows a circle path instead of a + * straight line. * * @author Neil Davies */ public class SeekArc extends View { - private static final String TAG = SeekArc.class.getSimpleName(); - private static int INVALID_PROGRESS_VALUE = -1; - // The initial rotational offset -90 means we start at 12 o'clock - private final int mAngleOffset = -90; - private Paint mArcPaint; - // Internal variables - private int mArcRadius = 0; - private RectF mArcRect = new RectF(); - /** - * The Width of the background arc for the SeekArc - */ - private int mArcWidth = 2; - /** - * Will the progress increase clockwise or anti-clockwise - */ - private boolean mClockwise = true; - /** - * is the control enabled/touchable - */ - private boolean mEnabled = true; - /** - * The Maximum value that this SeekArc can be set to - */ - private int mMax = 100; - private OnSeekArcChangeListener mOnSeekArcChangeListener; - /** - * The Current value that the SeekArc is set to - */ - private int mProgress = 0; - private Paint mProgressPaint; - private float mProgressSweep = 0; - /** - * The width of the progress line for this SeekArc - */ - private int mProgressWidth = 4; - /** - * The rotation of the SeekArc- 0 is twelve o'clock - */ - private int mRotation = 0; - /** - * Give the SeekArc rounded edges - */ - private boolean mRoundedEdges = false; - /** - * The Angle to start drawing this Arc from - */ - private int mStartAngle = 0; - /** - * The Angle through which to draw the arc (Max is 360) - */ - private int mSweepAngle = 360; - /** - * The Drawable for the seek arc thumbnail - */ - private Drawable mThumb; - private int mThumbXPos; - private int mThumbYPos; - private double mTouchAngle; - private float mTouchIgnoreRadius; - /** - * Enable touch inside the SeekArc - */ - private boolean mTouchInside = true; - private int mTranslateX; - private int mTranslateY; + private static final String TAG = SeekArc.class.getSimpleName(); + private static int INVALID_PROGRESS_VALUE = -1; + // The initial rotational offset -90 means we start at 12 o'clock + private final int mAngleOffset = -90; + private Paint mArcPaint; + // Internal variables + private int mArcRadius = 0; + private RectF mArcRect = new RectF(); + /** The Width of the background arc for the SeekArc */ + private int mArcWidth = 2; + /** Will the progress increase clockwise or anti-clockwise */ + private boolean mClockwise = true; + /** is the control enabled/touchable */ + private boolean mEnabled = true; + /** The Maximum value that this SeekArc can be set to */ + private int mMax = 100; - public SeekArc(Context context) { - super(context); - init(context, null, 0); + private OnSeekArcChangeListener mOnSeekArcChangeListener; + /** The Current value that the SeekArc is set to */ + private int mProgress = 0; + + private Paint mProgressPaint; + private float mProgressSweep = 0; + /** The width of the progress line for this SeekArc */ + private int mProgressWidth = 4; + /** The rotation of the SeekArc- 0 is twelve o'clock */ + private int mRotation = 0; + /** Give the SeekArc rounded edges */ + private boolean mRoundedEdges = false; + /** The Angle to start drawing this Arc from */ + private int mStartAngle = 0; + /** The Angle through which to draw the arc (Max is 360) */ + private int mSweepAngle = 360; + /** The Drawable for the seek arc thumbnail */ + private Drawable mThumb; + + private int mThumbXPos; + private int mThumbYPos; + private double mTouchAngle; + private float mTouchIgnoreRadius; + /** Enable touch inside the SeekArc */ + private boolean mTouchInside = true; + + private int mTranslateX; + private int mTranslateY; + + public SeekArc(Context context) { + super(context); + init(context, null, 0); + } + + public SeekArc(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs, R.attr.seekArcStyle); + } + + public SeekArc(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(context, attrs, defStyle); + } + + public int getArcColor() { + return mArcPaint.getColor(); + } + + public void setArcColor(int color) { + mArcPaint.setColor(color); + invalidate(); + } + + public int getArcRotation() { + return mRotation; + } + + public void setArcRotation(int mRotation) { + this.mRotation = mRotation; + updateThumbPosition(); + } + + public int getArcWidth() { + return mArcWidth; + } + + public void setArcWidth(int mArcWidth) { + this.mArcWidth = mArcWidth; + mArcPaint.setStrokeWidth(mArcWidth); + } + + public int getMax() { + return mMax; + } + + public void setMax(int mMax) { + this.mMax = mMax; + } + + public int getProgress() { + return mProgress; + } + + public void setProgress(int progress) { + updateProgress(progress, false); + } + + public int getProgressColor() { + return mProgressPaint.getColor(); + } + + public void setProgressColor(int color) { + mProgressPaint.setColor(color); + invalidate(); + } + + public int getProgressWidth() { + return mProgressWidth; + } + + public void setProgressWidth(int mProgressWidth) { + this.mProgressWidth = mProgressWidth; + mProgressPaint.setStrokeWidth(mProgressWidth); + } + + public int getStartAngle() { + return mStartAngle; + } + + public void setStartAngle(int mStartAngle) { + this.mStartAngle = mStartAngle; + updateThumbPosition(); + } + + public int getSweepAngle() { + return mSweepAngle; + } + + public void setSweepAngle(int mSweepAngle) { + this.mSweepAngle = mSweepAngle; + updateThumbPosition(); + } + + public boolean isClockwise() { + return mClockwise; + } + + public void setClockwise(boolean isClockwise) { + mClockwise = isClockwise; + } + + public boolean isEnabled() { + return mEnabled; + } + + public void setEnabled(boolean enabled) { + this.mEnabled = enabled; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (mEnabled) { + this.getParent().requestDisallowInterceptTouchEvent(true); + + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + onStartTrackingTouch(); + updateOnTouch(event); + break; + case MotionEvent.ACTION_MOVE: + updateOnTouch(event); + break; + case MotionEvent.ACTION_UP: + onStopTrackingTouch(); + setPressed(false); + this.getParent().requestDisallowInterceptTouchEvent(false); + break; + case MotionEvent.ACTION_CANCEL: + onStopTrackingTouch(); + setPressed(false); + this.getParent().requestDisallowInterceptTouchEvent(false); + break; + } + return true; + } + return false; + } + + /** + * Sets a listener to receive notifications of changes to the SeekArc's progress level. Also + * provides notifications of when the user starts and stops a touch gesture within the SeekArc. + * + * @param l The seek bar notification listener + * @see SeekArc.OnSeekBarChangeListener + */ + public void setOnSeekArcChangeListener(OnSeekArcChangeListener l) { + mOnSeekArcChangeListener = l; + } + + public void setRoundedEdges(boolean isEnabled) { + mRoundedEdges = isEnabled; + if (mRoundedEdges) { + mArcPaint.setStrokeCap(Paint.Cap.ROUND); + mProgressPaint.setStrokeCap(Paint.Cap.ROUND); + } else { + mArcPaint.setStrokeCap(Paint.Cap.SQUARE); + mProgressPaint.setStrokeCap(Paint.Cap.SQUARE); + } + } + + public void setTouchInSide(boolean isEnabled) { + int thumbHalfheight = (int) mThumb.getIntrinsicHeight() / 2; + int thumbHalfWidth = (int) mThumb.getIntrinsicWidth() / 2; + mTouchInside = isEnabled; + if (mTouchInside) { + mTouchIgnoreRadius = (float) mArcRadius / 4; + } else { + // Don't use the exact radius makes interaction too tricky + mTouchIgnoreRadius = mArcRadius - Math.min(thumbHalfWidth, thumbHalfheight); + } + } + + @Override + protected void drawableStateChanged() { + super.drawableStateChanged(); + if (mThumb != null && mThumb.isStateful()) { + int[] state = getDrawableState(); + mThumb.setState(state); + } + invalidate(); + } + + @Override + protected void onDraw(Canvas canvas) { + if (!mClockwise) { + canvas.scale(-1, 1, mArcRect.centerX(), mArcRect.centerY()); } - public SeekArc(Context context, AttributeSet attrs) { - super(context, attrs); - init(context, attrs, R.attr.seekArcStyle); + // Draw the arcs + final int arcStart = mStartAngle + mAngleOffset + mRotation; + final int arcSweep = mSweepAngle; + canvas.drawArc(mArcRect, arcStart, arcSweep, false, mArcPaint); + canvas.drawArc(mArcRect, arcStart, mProgressSweep, false, mProgressPaint); + + if (mEnabled) { + // Draw the thumb nail + canvas.translate(mTranslateX - mThumbXPos, mTranslateY - mThumbYPos); + mThumb.draw(canvas); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + + final int height = getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec); + final int width = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec); + final int min = Math.min(width, height); + float top = 0; + float left = 0; + int arcDiameter = 0; + + mTranslateX = (int) (width * 0.5f); + mTranslateY = (int) (height * 0.5f); + + arcDiameter = min - getPaddingLeft(); + mArcRadius = arcDiameter / 2; + top = height / 2 - (arcDiameter / 2); + left = width / 2 - (arcDiameter / 2); + mArcRect.set(left, top, left + arcDiameter, top + arcDiameter); + + int arcStart = (int) mProgressSweep + mStartAngle + mRotation + 90; + mThumbXPos = (int) (mArcRadius * Math.cos(Math.toRadians(arcStart))); + mThumbYPos = (int) (mArcRadius * Math.sin(Math.toRadians(arcStart))); + + setTouchInSide(mTouchInside); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + private int getProgressForAngle(double angle) { + int touchProgress = (int) Math.round(valuePerDegree() * angle); + + touchProgress = (touchProgress < 0) ? INVALID_PROGRESS_VALUE : touchProgress; + touchProgress = (touchProgress > mMax) ? INVALID_PROGRESS_VALUE : touchProgress; + return touchProgress; + } + + private double getTouchDegrees(float xPos, float yPos) { + float x = xPos - mTranslateX; + float y = yPos - mTranslateY; + // invert the x-coord if we are rotating anti-clockwise + x = (mClockwise) ? x : -x; + // convert to arc Angle + double angle = Math.toDegrees(Math.atan2(y, x) + (Math.PI / 2) - Math.toRadians(mRotation)); + if (angle < 0) { + angle = 360 + angle; + } + angle -= mStartAngle; + return angle; + } + + private boolean ignoreTouch(float xPos, float yPos) { + boolean ignore = false; + float x = xPos - mTranslateX; + float y = yPos - mTranslateY; + + float touchRadius = (float) Math.sqrt(((x * x) + (y * y))); + if (touchRadius < mTouchIgnoreRadius) { + ignore = true; + } + return ignore; + } + + private void init(Context context, AttributeSet attrs, int defStyle) { + + Log.d(TAG, "Initialising SeekArc"); + final Resources res = getResources(); + float density = context.getResources().getDisplayMetrics().density; + + // Defaults, may need to link this into theme settings + int arcColor = res.getColor(R.color.progress_gray); + int progressColor = res.getColor(R.color.default_blue_light); + int thumbHalfheight = 0; + int thumbHalfWidth = 0; + mThumb = res.getDrawable(R.drawable.switch_thumb_material); + // Convert progress width to pixels for current density + mProgressWidth = (int) (mProgressWidth * density); + + if (attrs != null) { + // Attribute initialization + final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SeekArc, defStyle, 0); + + Drawable thumb = a.getDrawable(R.styleable.SeekArc_thumb); + if (thumb != null) { + mThumb = thumb; + } + + thumbHalfheight = (int) mThumb.getIntrinsicHeight() / 2; + thumbHalfWidth = (int) mThumb.getIntrinsicWidth() / 2; + mThumb.setBounds(-thumbHalfWidth, -thumbHalfheight, thumbHalfWidth, thumbHalfheight); + + mMax = a.getInteger(R.styleable.SeekArc_max, mMax); + mProgress = a.getInteger(R.styleable.SeekArc_seekProgress, mProgress); + mProgressWidth = (int) a.getDimension(R.styleable.SeekArc_progressWidth, mProgressWidth); + mArcWidth = (int) a.getDimension(R.styleable.SeekArc_arcWidth, mArcWidth); + mStartAngle = a.getInt(R.styleable.SeekArc_startAngle, mStartAngle); + mSweepAngle = a.getInt(R.styleable.SeekArc_sweepAngle, mSweepAngle); + mRotation = a.getInt(R.styleable.SeekArc_rotation, mRotation); + mRoundedEdges = a.getBoolean(R.styleable.SeekArc_roundEdges, mRoundedEdges); + mTouchInside = a.getBoolean(R.styleable.SeekArc_touchInside, mTouchInside); + mClockwise = a.getBoolean(R.styleable.SeekArc_clockwise, mClockwise); + mEnabled = a.getBoolean(R.styleable.SeekArc_enabled, mEnabled); + + arcColor = a.getColor(R.styleable.SeekArc_arcColor, arcColor); + progressColor = a.getColor(R.styleable.SeekArc_progressColor, progressColor); + + a.recycle(); } - public SeekArc(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - init(context, attrs, defStyle); + mProgress = (mProgress > mMax) ? mMax : mProgress; + mProgress = (mProgress < 0) ? 0 : mProgress; + + mSweepAngle = (mSweepAngle > 360) ? 360 : mSweepAngle; + mSweepAngle = (mSweepAngle < 0) ? 0 : mSweepAngle; + + mProgressSweep = (float) mProgress / mMax * mSweepAngle; + + mStartAngle = (mStartAngle > 360) ? 0 : mStartAngle; + mStartAngle = (mStartAngle < 0) ? 0 : mStartAngle; + + mArcPaint = new Paint(); + mArcPaint.setColor(arcColor); + mArcPaint.setAntiAlias(true); + mArcPaint.setStyle(Paint.Style.STROKE); + mArcPaint.setStrokeWidth(mArcWidth); + // mArcPaint.setAlpha(45); + + mProgressPaint = new Paint(); + mProgressPaint.setColor(progressColor); + mProgressPaint.setAntiAlias(true); + mProgressPaint.setStyle(Paint.Style.STROKE); + mProgressPaint.setStrokeWidth(mProgressWidth); + + if (mRoundedEdges) { + mArcPaint.setStrokeCap(Paint.Cap.ROUND); + mProgressPaint.setStrokeCap(Paint.Cap.ROUND); + } + } + + private void onProgressRefresh(int progress, boolean fromUser) { + updateProgress(progress, fromUser); + } + + private void onStartTrackingTouch() { + if (mOnSeekArcChangeListener != null) { + mOnSeekArcChangeListener.onStartTrackingTouch(this); + } + } + + private void onStopTrackingTouch() { + if (mOnSeekArcChangeListener != null) { + mOnSeekArcChangeListener.onStopTrackingTouch(this); + } + } + + private void updateOnTouch(MotionEvent event) { + boolean ignoreTouch = ignoreTouch(event.getX(), event.getY()); + if (ignoreTouch) { + return; + } + setPressed(true); + mTouchAngle = getTouchDegrees(event.getX(), event.getY()); + int progress = getProgressForAngle(mTouchAngle); + onProgressRefresh(progress, true); + } + + private void updateProgress(int progress, boolean fromUser) { + + if (progress == INVALID_PROGRESS_VALUE) { + return; } - public int getArcColor() { - return mArcPaint.getColor(); + progress = (progress > mMax) ? mMax : progress; + progress = (progress < 0) ? 0 : progress; + mProgress = progress; + + if (mOnSeekArcChangeListener != null) { + mOnSeekArcChangeListener.onProgressChanged(this, progress, fromUser); } - public void setArcColor(int color) { - mArcPaint.setColor(color); - invalidate(); - } + mProgressSweep = (float) progress / mMax * mSweepAngle; - public int getArcRotation() { - return mRotation; - } + updateThumbPosition(); - public void setArcRotation(int mRotation) { - this.mRotation = mRotation; - updateThumbPosition(); - } + invalidate(); + } - public int getArcWidth() { - return mArcWidth; - } + private void updateThumbPosition() { + int thumbAngle = (int) (mStartAngle + mProgressSweep + mRotation + 90); + mThumbXPos = (int) (mArcRadius * Math.cos(Math.toRadians(thumbAngle))); + mThumbYPos = (int) (mArcRadius * Math.sin(Math.toRadians(thumbAngle))); + } - public void setArcWidth(int mArcWidth) { - this.mArcWidth = mArcWidth; - mArcPaint.setStrokeWidth(mArcWidth); - } + private float valuePerDegree() { + return (float) mMax / mSweepAngle; + } - public int getMax() { - return mMax; - } - - public void setMax(int mMax) { - this.mMax = mMax; - } - - public int getProgress() { - return mProgress; - } - - public void setProgress(int progress) { - updateProgress(progress, false); - } - - public int getProgressColor() { - return mProgressPaint.getColor(); - } - - public void setProgressColor(int color) { - mProgressPaint.setColor(color); - invalidate(); - } - - public int getProgressWidth() { - return mProgressWidth; - } - - public void setProgressWidth(int mProgressWidth) { - this.mProgressWidth = mProgressWidth; - mProgressPaint.setStrokeWidth(mProgressWidth); - } - - public int getStartAngle() { - return mStartAngle; - } - - public void setStartAngle(int mStartAngle) { - this.mStartAngle = mStartAngle; - updateThumbPosition(); - } - - public int getSweepAngle() { - return mSweepAngle; - } - - public void setSweepAngle(int mSweepAngle) { - this.mSweepAngle = mSweepAngle; - updateThumbPosition(); - } - - public boolean isClockwise() { - return mClockwise; - } - - public void setClockwise(boolean isClockwise) { - mClockwise = isClockwise; - } - - public boolean isEnabled() { - return mEnabled; - } - - public void setEnabled(boolean enabled) { - this.mEnabled = enabled; - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - if (mEnabled) { - this.getParent().requestDisallowInterceptTouchEvent(true); - - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - onStartTrackingTouch(); - updateOnTouch(event); - break; - case MotionEvent.ACTION_MOVE: - updateOnTouch(event); - break; - case MotionEvent.ACTION_UP: - onStopTrackingTouch(); - setPressed(false); - this.getParent().requestDisallowInterceptTouchEvent(false); - break; - case MotionEvent.ACTION_CANCEL: - onStopTrackingTouch(); - setPressed(false); - this.getParent().requestDisallowInterceptTouchEvent(false); - break; - } - return true; - } - return false; - } + public interface OnSeekArcChangeListener { /** - * Sets a listener to receive notifications of changes to the SeekArc's - * progress level. Also provides notifications of when the user starts and - * stops a touch gesture within the SeekArc. + * Notification that the progress level has changed. Clients can use the fromUser parameter to + * distinguish user-initiated changes from those that occurred programmatically. * - * @param l The seek bar notification listener - * @see SeekArc.OnSeekBarChangeListener + * @param seekArc The SeekArc whose progress has changed + * @param progress The current progress level. This will be in the range 0..max where max was + * set by {@link ProgressArc#setMax(int)}. (The default value for max is 100.) + * @param fromUser True if the progress change was initiated by the user. */ - public void setOnSeekArcChangeListener(OnSeekArcChangeListener l) { - mOnSeekArcChangeListener = l; - } + void onProgressChanged(SeekArc seekArc, int progress, boolean fromUser); - public void setRoundedEdges(boolean isEnabled) { - mRoundedEdges = isEnabled; - if (mRoundedEdges) { - mArcPaint.setStrokeCap(Paint.Cap.ROUND); - mProgressPaint.setStrokeCap(Paint.Cap.ROUND); - } else { - mArcPaint.setStrokeCap(Paint.Cap.SQUARE); - mProgressPaint.setStrokeCap(Paint.Cap.SQUARE); - } - } + /** + * Notification that the user has started a touch gesture. Clients may want to use this to + * disable advancing the seekbar. + * + * @param seekArc The SeekArc in which the touch gesture began + */ + void onStartTrackingTouch(SeekArc seekArc); - public void setTouchInSide(boolean isEnabled) { - int thumbHalfheight = (int) mThumb.getIntrinsicHeight() / 2; - int thumbHalfWidth = (int) mThumb.getIntrinsicWidth() / 2; - mTouchInside = isEnabled; - if (mTouchInside) { - mTouchIgnoreRadius = (float) mArcRadius / 4; - } else { - // Don't use the exact radius makes interaction too tricky - mTouchIgnoreRadius = mArcRadius - - Math.min(thumbHalfWidth, thumbHalfheight); - } - } - - @Override - protected void drawableStateChanged() { - super.drawableStateChanged(); - if (mThumb != null && mThumb.isStateful()) { - int[] state = getDrawableState(); - mThumb.setState(state); - } - invalidate(); - } - - @Override - protected void onDraw(Canvas canvas) { - if (!mClockwise) { - canvas.scale(-1, 1, mArcRect.centerX(), mArcRect.centerY()); - } - - // Draw the arcs - final int arcStart = mStartAngle + mAngleOffset + mRotation; - final int arcSweep = mSweepAngle; - canvas.drawArc(mArcRect, arcStart, arcSweep, false, mArcPaint); - canvas.drawArc(mArcRect, arcStart, mProgressSweep, false, - mProgressPaint); - - if (mEnabled) { - // Draw the thumb nail - canvas.translate(mTranslateX - mThumbXPos, mTranslateY - mThumbYPos); - mThumb.draw(canvas); - } - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - - final int height = getDefaultSize(getSuggestedMinimumHeight(), - heightMeasureSpec); - final int width = getDefaultSize(getSuggestedMinimumWidth(), - widthMeasureSpec); - final int min = Math.min(width, height); - float top = 0; - float left = 0; - int arcDiameter = 0; - - mTranslateX = (int) (width * 0.5f); - mTranslateY = (int) (height * 0.5f); - - arcDiameter = min - getPaddingLeft(); - mArcRadius = arcDiameter / 2; - top = height / 2 - (arcDiameter / 2); - left = width / 2 - (arcDiameter / 2); - mArcRect.set(left, top, left + arcDiameter, top + arcDiameter); - - int arcStart = (int) mProgressSweep + mStartAngle + mRotation + 90; - mThumbXPos = (int) (mArcRadius * Math.cos(Math.toRadians(arcStart))); - mThumbYPos = (int) (mArcRadius * Math.sin(Math.toRadians(arcStart))); - - setTouchInSide(mTouchInside); - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - - private int getProgressForAngle(double angle) { - int touchProgress = (int) Math.round(valuePerDegree() * angle); - - touchProgress = (touchProgress < 0) ? INVALID_PROGRESS_VALUE - : touchProgress; - touchProgress = (touchProgress > mMax) ? INVALID_PROGRESS_VALUE - : touchProgress; - return touchProgress; - } - - private double getTouchDegrees(float xPos, float yPos) { - float x = xPos - mTranslateX; - float y = yPos - mTranslateY; - //invert the x-coord if we are rotating anti-clockwise - x = (mClockwise) ? x : -x; - // convert to arc Angle - double angle = Math.toDegrees(Math.atan2(y, x) + (Math.PI / 2) - - Math.toRadians(mRotation)); - if (angle < 0) { - angle = 360 + angle; - } - angle -= mStartAngle; - return angle; - } - - private boolean ignoreTouch(float xPos, float yPos) { - boolean ignore = false; - float x = xPos - mTranslateX; - float y = yPos - mTranslateY; - - float touchRadius = (float) Math.sqrt(((x * x) + (y * y))); - if (touchRadius < mTouchIgnoreRadius) { - ignore = true; - } - return ignore; - } - - private void init(Context context, AttributeSet attrs, int defStyle) { - - Log.d(TAG, "Initialising SeekArc"); - final Resources res = getResources(); - float density = context.getResources().getDisplayMetrics().density; - - // Defaults, may need to link this into theme settings - int arcColor = res.getColor(R.color.progress_gray); - int progressColor = res.getColor(R.color.default_blue_light); - int thumbHalfheight = 0; - int thumbHalfWidth = 0; - mThumb = res.getDrawable(R.drawable.switch_thumb_material); - // Convert progress width to pixels for current density - mProgressWidth = (int) (mProgressWidth * density); - - if (attrs != null) { - // Attribute initialization - final TypedArray a = context.obtainStyledAttributes(attrs, - R.styleable.SeekArc, defStyle, 0); - - Drawable thumb = a.getDrawable(R.styleable.SeekArc_thumb); - if (thumb != null) { - mThumb = thumb; - } - - thumbHalfheight = (int) mThumb.getIntrinsicHeight() / 2; - thumbHalfWidth = (int) mThumb.getIntrinsicWidth() / 2; - mThumb.setBounds(-thumbHalfWidth, -thumbHalfheight, thumbHalfWidth, - thumbHalfheight); - - mMax = a.getInteger(R.styleable.SeekArc_max, mMax); - mProgress = a.getInteger(R.styleable.SeekArc_seekProgress, mProgress); - mProgressWidth = (int) a.getDimension( - R.styleable.SeekArc_progressWidth, mProgressWidth); - mArcWidth = (int) a.getDimension(R.styleable.SeekArc_arcWidth, - mArcWidth); - mStartAngle = a.getInt(R.styleable.SeekArc_startAngle, mStartAngle); - mSweepAngle = a.getInt(R.styleable.SeekArc_sweepAngle, mSweepAngle); - mRotation = a.getInt(R.styleable.SeekArc_rotation, mRotation); - mRoundedEdges = a.getBoolean(R.styleable.SeekArc_roundEdges, - mRoundedEdges); - mTouchInside = a.getBoolean(R.styleable.SeekArc_touchInside, - mTouchInside); - mClockwise = a.getBoolean(R.styleable.SeekArc_clockwise, - mClockwise); - mEnabled = a.getBoolean(R.styleable.SeekArc_enabled, mEnabled); - - arcColor = a.getColor(R.styleable.SeekArc_arcColor, arcColor); - progressColor = a.getColor(R.styleable.SeekArc_progressColor, - progressColor); - - a.recycle(); - } - - mProgress = (mProgress > mMax) ? mMax : mProgress; - mProgress = (mProgress < 0) ? 0 : mProgress; - - mSweepAngle = (mSweepAngle > 360) ? 360 : mSweepAngle; - mSweepAngle = (mSweepAngle < 0) ? 0 : mSweepAngle; - - mProgressSweep = (float) mProgress / mMax * mSweepAngle; - - mStartAngle = (mStartAngle > 360) ? 0 : mStartAngle; - mStartAngle = (mStartAngle < 0) ? 0 : mStartAngle; - - mArcPaint = new Paint(); - mArcPaint.setColor(arcColor); - mArcPaint.setAntiAlias(true); - mArcPaint.setStyle(Paint.Style.STROKE); - mArcPaint.setStrokeWidth(mArcWidth); - //mArcPaint.setAlpha(45); - - mProgressPaint = new Paint(); - mProgressPaint.setColor(progressColor); - mProgressPaint.setAntiAlias(true); - mProgressPaint.setStyle(Paint.Style.STROKE); - mProgressPaint.setStrokeWidth(mProgressWidth); - - if (mRoundedEdges) { - mArcPaint.setStrokeCap(Paint.Cap.ROUND); - mProgressPaint.setStrokeCap(Paint.Cap.ROUND); - } - } - - private void onProgressRefresh(int progress, boolean fromUser) { - updateProgress(progress, fromUser); - } - - private void onStartTrackingTouch() { - if (mOnSeekArcChangeListener != null) { - mOnSeekArcChangeListener.onStartTrackingTouch(this); - } - } - - private void onStopTrackingTouch() { - if (mOnSeekArcChangeListener != null) { - mOnSeekArcChangeListener.onStopTrackingTouch(this); - } - } - - private void updateOnTouch(MotionEvent event) { - boolean ignoreTouch = ignoreTouch(event.getX(), event.getY()); - if (ignoreTouch) { - return; - } - setPressed(true); - mTouchAngle = getTouchDegrees(event.getX(), event.getY()); - int progress = getProgressForAngle(mTouchAngle); - onProgressRefresh(progress, true); - } - - private void updateProgress(int progress, boolean fromUser) { - - if (progress == INVALID_PROGRESS_VALUE) { - return; - } - - progress = (progress > mMax) ? mMax : progress; - progress = (progress < 0) ? 0 : progress; - mProgress = progress; - - if (mOnSeekArcChangeListener != null) { - mOnSeekArcChangeListener - .onProgressChanged(this, progress, fromUser); - } - - mProgressSweep = (float) progress / mMax * mSweepAngle; - - updateThumbPosition(); - - invalidate(); - } - - private void updateThumbPosition() { - int thumbAngle = (int) (mStartAngle + mProgressSweep + mRotation + 90); - mThumbXPos = (int) (mArcRadius * Math.cos(Math.toRadians(thumbAngle))); - mThumbYPos = (int) (mArcRadius * Math.sin(Math.toRadians(thumbAngle))); - } - - private float valuePerDegree() { - return (float) mMax / mSweepAngle; - } - - public interface OnSeekArcChangeListener { - - /** - * Notification that the progress level has changed. Clients can use the - * fromUser parameter to distinguish user-initiated changes from those - * that occurred programmatically. - * - * @param seekArc The SeekArc whose progress has changed - * @param progress The current progress level. This will be in the range - * 0..max where max was set by - * {@link ProgressArc#setMax(int)}. (The default value for - * max is 100.) - * @param fromUser True if the progress change was initiated by the user. - */ - void onProgressChanged(SeekArc seekArc, int progress, boolean fromUser); - - /** - * Notification that the user has started a touch gesture. Clients may - * want to use this to disable advancing the seekbar. - * - * @param seekArc The SeekArc in which the touch gesture began - */ - void onStartTrackingTouch(SeekArc seekArc); - - /** - * Notification that the user has finished a touch gesture. Clients may - * want to use this to re-enable advancing the seekarc. - * - * @param seekArc The SeekArc in which the touch gesture began - */ - void onStopTrackingTouch(SeekArc seekArc); - } -} \ No newline at end of file + /** + * Notification that the user has finished a touch gesture. Clients may want to use this to + * re-enable advancing the seekarc. + * + * @param seekArc The SeekArc in which the touch gesture began + */ + void onStopTrackingTouch(SeekArc seekArc); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/views/StatusBarMarginFrameLayout.java b/app/src/main/java/code/name/monkey/retromusic/views/StatusBarMarginFrameLayout.java index e9dfc386..f4bc8f8f 100644 --- a/app/src/main/java/code/name/monkey/retromusic/views/StatusBarMarginFrameLayout.java +++ b/app/src/main/java/code/name/monkey/retromusic/views/StatusBarMarginFrameLayout.java @@ -19,33 +19,32 @@ import android.os.Build; import android.util.AttributeSet; import android.view.WindowInsets; import android.widget.FrameLayout; - import androidx.annotation.NonNull; public class StatusBarMarginFrameLayout extends FrameLayout { + public StatusBarMarginFrameLayout(@NonNull Context context) { + super(context); + } - public StatusBarMarginFrameLayout(@NonNull Context context) { - super(context); - } + public StatusBarMarginFrameLayout(@NonNull Context context, @NonNull AttributeSet attrs) { + super(context, attrs); + } - public StatusBarMarginFrameLayout(@NonNull Context context, @NonNull AttributeSet attrs) { - super(context, attrs); - } + public StatusBarMarginFrameLayout( + @NonNull Context context, @NonNull AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } - public StatusBarMarginFrameLayout(@NonNull Context context, @NonNull AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - @NonNull - @Override - public WindowInsets onApplyWindowInsets(@NonNull WindowInsets insets) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams(); - lp.topMargin = insets.getSystemWindowInsetTop(); - lp.bottomMargin = insets.getSystemWindowInsetBottom(); - setLayoutParams(lp); - } - return super.onApplyWindowInsets(insets); + @NonNull + @Override + public WindowInsets onApplyWindowInsets(@NonNull WindowInsets insets) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams(); + lp.topMargin = insets.getSystemWindowInsetTop(); + lp.bottomMargin = insets.getSystemWindowInsetBottom(); + setLayoutParams(lp); } + return super.onApplyWindowInsets(insets); + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/views/StatusBarView.java b/app/src/main/java/code/name/monkey/retromusic/views/StatusBarView.java index 8eaf325d..c149cc3d 100644 --- a/app/src/main/java/code/name/monkey/retromusic/views/StatusBarView.java +++ b/app/src/main/java/code/name/monkey/retromusic/views/StatusBarView.java @@ -18,42 +18,38 @@ import android.content.Context; import android.content.res.Resources; import android.util.AttributeSet; import android.view.View; - import androidx.annotation.NonNull; public class StatusBarView extends View { + public StatusBarView(@NonNull Context context) { + super(context); + init(context); + } - public StatusBarView(@NonNull Context context) { - super(context); - init(context); + public StatusBarView(@NonNull Context context, @NonNull AttributeSet attrs) { + super(context, attrs); + init(context); + } + + public StatusBarView(@NonNull Context context, @NonNull AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context); + } + + public static int getStatusBarHeight(@NonNull Resources r) { + int result = 0; + int resourceId = r.getIdentifier("status_bar_height", "dimen", "android"); + if (resourceId > 0) { + result = r.getDimensionPixelSize(resourceId); } + return result; + } - public StatusBarView(@NonNull Context context, @NonNull AttributeSet attrs) { - super(context, attrs); - init(context); - } + private void init(Context context) {} - public StatusBarView(@NonNull Context context, @NonNull AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(context); - } - - public static int getStatusBarHeight(@NonNull Resources r) { - int result = 0; - int resourceId = r.getIdentifier("status_bar_height", "dimen", "android"); - if (resourceId > 0) { - result = r.getDimensionPixelSize(resourceId); - } - return result; - } - - private void init(Context context) { - - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), getStatusBarHeight(getResources())); - } -} \ No newline at end of file + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), getStatusBarHeight(getResources())); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/views/VerticalTextView.java b/app/src/main/java/code/name/monkey/retromusic/views/VerticalTextView.java index 5e79c096..1172bb6a 100644 --- a/app/src/main/java/code/name/monkey/retromusic/views/VerticalTextView.java +++ b/app/src/main/java/code/name/monkey/retromusic/views/VerticalTextView.java @@ -19,50 +19,46 @@ import android.graphics.Canvas; import android.text.TextPaint; import android.util.AttributeSet; import android.view.Gravity; - import androidx.appcompat.widget.AppCompatTextView; - public class VerticalTextView extends AppCompatTextView { - final boolean topDown; + final boolean topDown; - public VerticalTextView(Context context, AttributeSet attrs) { - super(context, attrs); - final int gravity = getGravity(); - if (Gravity.isVertical(gravity) && (gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { - setGravity((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) | Gravity.TOP); - topDown = false; - } else - topDown = true; + public VerticalTextView(Context context, AttributeSet attrs) { + super(context, attrs); + final int gravity = getGravity(); + if (Gravity.isVertical(gravity) + && (gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { + setGravity((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) | Gravity.TOP); + topDown = false; + } else topDown = true; + } + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(heightMeasureSpec, widthMeasureSpec); + setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth()); + } + + @Override + protected void onDraw(Canvas canvas) { + TextPaint textPaint = getPaint(); + textPaint.setColor(getCurrentTextColor()); + textPaint.drawableState = getDrawableState(); + + canvas.save(); + + if (topDown) { + canvas.translate(getWidth(), 0); + canvas.rotate(90); + } else { + canvas.translate(0, getHeight()); + canvas.rotate(-90); } - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(heightMeasureSpec, widthMeasureSpec); - setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth()); - } + canvas.translate(getCompoundPaddingLeft(), getExtendedPaddingTop()); - @Override - protected void onDraw(Canvas canvas) { - TextPaint textPaint = getPaint(); - textPaint.setColor(getCurrentTextColor()); - textPaint.drawableState = getDrawableState(); - - canvas.save(); - - if (topDown) { - canvas.translate(getWidth(), 0); - canvas.rotate(90); - } else { - canvas.translate(0, getHeight()); - canvas.rotate(-90); - } - - - canvas.translate(getCompoundPaddingLeft(), getExtendedPaddingTop()); - - getLayout().draw(canvas); - canvas.restore(); - } -} \ No newline at end of file + getLayout().draw(canvas); + canvas.restore(); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/volume/AudioVolumeContentObserver.java b/app/src/main/java/code/name/monkey/retromusic/volume/AudioVolumeContentObserver.java index 7e1ec7c3..cea4a85e 100644 --- a/app/src/main/java/code/name/monkey/retromusic/volume/AudioVolumeContentObserver.java +++ b/app/src/main/java/code/name/monkey/retromusic/volume/AudioVolumeContentObserver.java @@ -18,47 +18,46 @@ import android.database.ContentObserver; import android.media.AudioManager; import android.net.Uri; import android.os.Handler; - import androidx.annotation.NonNull; public class AudioVolumeContentObserver extends ContentObserver { - private final OnAudioVolumeChangedListener mListener; + private final OnAudioVolumeChangedListener mListener; - private final AudioManager mAudioManager; + private final AudioManager mAudioManager; - private final int mAudioStreamType; + private final int mAudioStreamType; - private float mLastVolume; + private float mLastVolume; - AudioVolumeContentObserver(@NonNull Handler handler, @NonNull AudioManager audioManager, - int audioStreamType, - @NonNull OnAudioVolumeChangedListener listener) { + AudioVolumeContentObserver( + @NonNull Handler handler, + @NonNull AudioManager audioManager, + int audioStreamType, + @NonNull OnAudioVolumeChangedListener listener) { - super(handler); - mAudioManager = audioManager; - mAudioStreamType = audioStreamType; - mListener = listener; - mLastVolume = audioManager.getStreamVolume(mAudioStreamType); + super(handler); + mAudioManager = audioManager; + mAudioStreamType = audioStreamType; + mListener = listener; + mLastVolume = audioManager.getStreamVolume(mAudioStreamType); + } + + /** Depending on the handler this method may be executed on the UI thread */ + @Override + public void onChange(boolean selfChange, Uri uri) { + if (mAudioManager != null && mListener != null) { + int maxVolume = mAudioManager.getStreamMaxVolume(mAudioStreamType); + int currentVolume = mAudioManager.getStreamVolume(mAudioStreamType); + if (currentVolume != mLastVolume) { + mLastVolume = currentVolume; + mListener.onAudioVolumeChanged(currentVolume, maxVolume); + } } + } - /** - * Depending on the handler this method may be executed on the UI thread - */ - @Override - public void onChange(boolean selfChange, Uri uri) { - if (mAudioManager != null && mListener != null) { - int maxVolume = mAudioManager.getStreamMaxVolume(mAudioStreamType); - int currentVolume = mAudioManager.getStreamVolume(mAudioStreamType); - if (currentVolume != mLastVolume) { - mLastVolume = currentVolume; - mListener.onAudioVolumeChanged(currentVolume, maxVolume); - } - } - } - - @Override - public boolean deliverSelfNotifications() { - return super.deliverSelfNotifications(); - } -} \ No newline at end of file + @Override + public boolean deliverSelfNotifications() { + return super.deliverSelfNotifications(); + } +} diff --git a/app/src/main/res/navigation/main_graph.xml b/app/src/main/res/navigation/main_graph.xml index ad6626bf..80614a59 100644 --- a/app/src/main/res/navigation/main_graph.xml +++ b/app/src/main/res/navigation/main_graph.xml @@ -15,7 +15,7 @@ app:argType="code.name.monkey.retromusic.model.Genre" /> - - + -->