diff --git a/README.md b/README.md index b2ed69f13..fa41beaa6 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ Dark and Just Black for AMOLED displays. Select your favorite accent color from a color palette. ### 🏠 Home -Where you can have your recently/top played artists, albums and +Where you can view your recently/top played artists, albums and favorite songs. No other music player has this feature. ### 📦 Included Features @@ -49,33 +49,32 @@ favorite songs. No other music player has this feature. - Driving Mode - Headset/Bluetooth support - Music duration filter -- Folder support - Play song by folder +- Folder support - Play songs by folder - Gapless playback - Volume controls -- Carousel effect for an album cover +- Carousel effect for album covers - Home screen widgets - Lock screen playback controls - Lyrics screen (download and sync with music) -- Sleep Timer +- Sleep timer - Easy drag to sort playlist & play queue - Tag editor - Create, edit and import playlists - Playing queue with reorder - User profile -- 30 Languages support +- 30+ languages support - Browse and play your music by songs, albums, artists, playlists and genre - Smart Auto Playlists - Recently played, most played and history -- Build your own playlist on the go +- Build your playlist on the go -We are trying our best to bring you the best user experience. The app is regulary being updated for bug fixes and new features. +We are trying our best to bring you the best user experience. The app is regularly being updated for bug fixes and new features. ### 🗂️ License Metro is released under the GNU General Public License v3.0 -(GPLv3), which can be found here: [License](LICENSE.md) +(GPLv3), which can be found [here](LICENSE.md) ->Please note: Retro Music player is an offline music player app. It ->doesn't support music downloading or online music streaming. +> Please note: Metro is an offline music player app. It doesn't support music downloading or online music streaming. diff --git a/app/build.gradle b/app/build.gradle index 668334712..e4f15c8bf 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,7 +6,7 @@ apply plugin: "androidx.navigation.safeargs.kotlin" android { compileSdkVersion 29 - buildToolsVersion = '30.0.1' + buildToolsVersion = '30.0.2' defaultConfig { minSdkVersion 21 @@ -16,8 +16,8 @@ android { vectorDrawables.useSupportLibrary = true applicationId 'io.github.muntashirakon.Music' - versionCode 10445 - versionName '3.5.11' + versionCode 10503 + versionName '4.0.010' multiDexEnabled true } @@ -134,4 +134,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' diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index a7f474df0..c48afcd1b 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -26,13 +26,11 @@ -dontwarn java.lang.invoke.* -dontwarn **$$Lambda$* +-dontwarn javax.annotation.** # RetroFit -dontwarn retrofit.** -keep class retrofit.** { *; } --keepattributes Signature --keepattributes Exceptions --dontwarn javax.annotation.** # Glide -keep public class * implements com.bumptech.glide.module.GlideModule @@ -41,14 +39,24 @@ public *; } +# OkHttp +-keepattributes Signature +-keepattributes *Annotation* +-keep interface com.squareup.okhttp3.** { *; } +-dontwarn com.squareup.okhttp3.** --dontwarn --ignorewarnings +#-dontwarn +#-ignorewarnings +-dontshrink +-dontobfuscate -#-dontwarn android.support.v8.renderscript.* -#-keepclassmembers class android.support.v8.renderscript.RenderScript { -# native *** rsn*(...); -# native *** n*(...); -#} +-dontwarn org.jaudiotagger.** +-keep class org.jaudiotagger.** { *; } -#-keep class org.jaudiotagger.** { *; } \ No newline at end of file +-keepclassmembers enum * { *; } +-keepattributes *Annotation*, Signature, Exception +-keepnames class androidx.navigation.fragment.NavHostFragment +-keepnames class code.name.monkey.retromusic.model.Home +-keep class * extends androidx.fragment.app.Fragment{} +-keepnames class * extends android.os.Parcelable +-keepnames class * extends java.io.Serializable \ No newline at end of file diff --git a/app/src/debug/res/values/styles.xml b/app/src/debug/res/values/styles.xml index 81154a395..dcee15a66 100644 --- a/app/src/debug/res/values/styles.xml +++ b/app/src/debug/res/values/styles.xml @@ -88,4 +88,9 @@ 0.0125 ?android:attr/textColorPrimary + + \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2b318ee18..d2bbd995d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -5,6 +5,7 @@ android:installLocation="auto"> + @@ -116,11 +117,7 @@ - - + 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/io/github/muntashirakon/music/activities/DriveModeActivity.kt b/app/src/main/java/io/github/muntashirakon/music/activities/DriveModeActivity.kt index 6435aec1c..375eb189f 100644 --- a/app/src/main/java/io/github/muntashirakon/music/activities/DriveModeActivity.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/activities/LicenseActivity.java b/app/src/main/java/io/github/muntashirakon/music/activities/LicenseActivity.java index ff00ba00a..555e8980a 100644 --- a/app/src/main/java/io/github/muntashirakon/music/activities/LicenseActivity.java +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.R; import io.github.muntashirakon.music.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/io/github/muntashirakon/music/activities/LockScreenActivity.kt b/app/src/main/java/io/github/muntashirakon/music/activities/LockScreenActivity.kt index 872fb3469..0e7f498d8 100644 --- a/app/src/main/java/io/github/muntashirakon/music/activities/LockScreenActivity.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.activities import android.app.KeyguardManager @@ -9,7 +23,8 @@ import android.view.WindowManager import androidx.core.view.ViewCompat import io.github.muntashirakon.music.R import io.github.muntashirakon.music.activities.base.AbsMusicServiceActivity -import io.github.muntashirakon.music.fragments.player.lockscreen.LockScreenPlayerControlsFragment +import io.github.muntashirakon.music.extensions.whichFragment +import io.github.muntashirakon.music.fragments.player.lockscreen.LockScreenControlsFragment import io.github.muntashirakon.music.glide.RetroMusicColoredTarget import io.github.muntashirakon.music.glide.SongGlideRequest import io.github.muntashirakon.music.helper.MusicPlayerRemote @@ -22,21 +37,12 @@ import com.r0adkll.slidr.model.SlidrPosition import kotlinx.android.synthetic.main.activity_lock_screen.* class LockScreenActivity : AbsMusicServiceActivity() { - private var fragment: LockScreenPlayerControlsFragment? = null + private var fragment: LockScreenControlsFragment? = null override fun onCreate(savedInstanceState: Bundle?) { setDrawUnderStatusBar() super.onCreate(savedInstanceState) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { - setShowWhenLocked(true) - setTurnScreenOn(true) - } else { - this.window.addFlags( - WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or - WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or - WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON - ) - } + lockScreenInit() setContentView(R.layout.activity_lock_screen) hideStatusBar() setStatusbarColorAuto() @@ -67,8 +73,7 @@ class LockScreenActivity : AbsMusicServiceActivity() { Slidr.attach(this, config) - fragment = - supportFragmentManager.findFragmentById(R.id.playback_controls_fragment) as LockScreenPlayerControlsFragment? + fragment = whichFragment(R.id.playback_controls_fragment) findViewById(R.id.slide).apply { translationY = 100f @@ -77,6 +82,19 @@ class LockScreenActivity : AbsMusicServiceActivity() { } } + private fun lockScreenInit() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { + setShowWhenLocked(true) + val keyguardManager: KeyguardManager = getSystemService(KeyguardManager::class.java) + keyguardManager.requestDismissKeyguard(this, null) + } else { + this.window.addFlags( + WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or + WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED + ) + } + } + override fun onPlayingMetaChanged() { super.onPlayingMetaChanged() updateSongs() @@ -97,4 +115,4 @@ class LockScreenActivity : AbsMusicServiceActivity() { } }) } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/muntashirakon/music/activities/LyricsActivity.kt b/app/src/main/java/io/github/muntashirakon/music/activities/LyricsActivity.kt index 4ca69a924..2a715395b 100644 --- a/app/src/main/java/io/github/muntashirakon/music/activities/LyricsActivity.kt +++ b/app/src/main/java/io/github/muntashirakon/music/activities/LyricsActivity.kt @@ -1,9 +1,25 @@ +/* + * 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 io.github.muntashirakon.music.activities import android.os.Bundle import android.view.Menu import android.view.MenuItem import android.view.WindowManager +import androidx.core.view.ViewCompat +import androidx.interpolator.view.animation.FastOutSlowInInterpolator import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import io.github.muntashirakon.music.R @@ -15,6 +31,10 @@ import io.github.muntashirakon.music.lyrics.LrcView import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.util.LyricUtil import io.github.muntashirakon.music.util.RetroUtil +import com.google.android.material.color.MaterialColors +import com.google.android.material.transition.platform.MaterialArcMotion +import com.google.android.material.transition.platform.MaterialContainerTransform +import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback import kotlinx.android.synthetic.main.activity_lyrics.* class LyricsActivity : AbsMusicServiceActivity(), MusicProgressViewUpdateHelper.Callback { @@ -31,9 +51,20 @@ class LyricsActivity : AbsMusicServiceActivity(), MusicProgressViewUpdateHelper. return baseUrl } + private fun buildContainerTransform(): MaterialContainerTransform { + val transform = MaterialContainerTransform() + transform.setAllContainerColors( + MaterialColors.getColor(findViewById(R.id.container), R.attr.colorSurface) + ) + transform.addTarget(R.id.container) + transform.duration = 300 + return transform + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_lyrics) + ViewCompat.setTransitionName(container, "lyrics") setStatusbarColorAuto() setTaskDescriptionColorAuto() setNavigationbarColorAuto() @@ -122,4 +153,4 @@ class LyricsActivity : AbsMusicServiceActivity(), MusicProgressViewUpdateHelper. } return super.onOptionsItemSelected(item) } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/muntashirakon/music/activities/MainActivity.kt b/app/src/main/java/io/github/muntashirakon/music/activities/MainActivity.kt index 61076560d..56a289c1f 100644 --- a/app/src/main/java/io/github/muntashirakon/music/activities/MainActivity.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.activities import android.content.Intent @@ -8,17 +22,45 @@ import android.os.Bundle import android.provider.MediaStore import android.view.View import androidx.lifecycle.lifecycleScope -import io.github.muntashirakon.music.* +import androidx.navigation.ui.NavigationUI +import io.github.muntashirakon.music.ADAPTIVE_COLOR_APP +import io.github.muntashirakon.music.ALBUM_COVER_STYLE +import io.github.muntashirakon.music.ALBUM_COVER_TRANSFORM +import io.github.muntashirakon.music.BANNER_IMAGE_PATH +import io.github.muntashirakon.music.BLACK_THEME +import io.github.muntashirakon.music.CAROUSEL_EFFECT +import io.github.muntashirakon.music.CIRCULAR_ALBUM_ART +import io.github.muntashirakon.music.DESATURATED_COLOR +import io.github.muntashirakon.music.EXTRA_SONG_INFO +import io.github.muntashirakon.music.GENERAL_THEME +import io.github.muntashirakon.music.HOME_ARTIST_GRID_STYLE +import io.github.muntashirakon.music.KEEP_SCREEN_ON +import io.github.muntashirakon.music.LANGUAGE_NAME +import io.github.muntashirakon.music.LIBRARY_CATEGORIES +import io.github.muntashirakon.music.NOW_PLAYING_SCREEN_ID +import io.github.muntashirakon.music.PROFILE_IMAGE_PATH +import io.github.muntashirakon.music.R +import io.github.muntashirakon.music.ROUND_CORNERS +import io.github.muntashirakon.music.TAB_TEXT_MODE +import io.github.muntashirakon.music.TOGGLE_ADD_CONTROLS +import io.github.muntashirakon.music.TOGGLE_FULL_SCREEN +import io.github.muntashirakon.music.TOGGLE_GENRE +import io.github.muntashirakon.music.TOGGLE_HOME_BANNER +import io.github.muntashirakon.music.TOGGLE_SEPARATE_LINE +import io.github.muntashirakon.music.TOGGLE_VOLUME +import io.github.muntashirakon.music.USER_NAME import io.github.muntashirakon.music.activities.base.AbsSlidingMusicPanelActivity import io.github.muntashirakon.music.extensions.findNavController import io.github.muntashirakon.music.helper.MusicPlayerRemote import io.github.muntashirakon.music.helper.SearchQueryHelper.getSongs +import io.github.muntashirakon.music.model.CategoryInfo import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.repository.PlaylistSongsLoader import io.github.muntashirakon.music.service.MusicService import io.github.muntashirakon.music.util.PreferenceUtil import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.launch +import org.koin.android.ext.android.get class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeListener { companion object { @@ -28,21 +70,43 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis } override fun createContentView(): View { - return wrapSlidingMusicPanel(R.layout.activity_main_content) + return wrapSlidingMusicPanel() } override fun onCreate(savedInstanceState: Bundle?) { setDrawUnderStatusBar() super.onCreate(savedInstanceState) - if (!hasPermissions()) { - findNavController(R.id.fragment_container).navigate(R.id.permissionFragment) - } setStatusbarColorAuto() setNavigationbarColorAuto() setLightNavigationBar(true) setTaskDescriptionColorAuto() hideStatusBar() updateTabs() + + // NavigationUI.setupWithNavController(getBottomNavigationView(), findNavController(R.id.fragment_container)) + setupNavigationController() + if (!hasPermissions()) { + findNavController(R.id.fragment_container).navigate(R.id.permissionFragment) + } + + showPromotionalDialog() + } + + private fun showPromotionalDialog() { + + } + + private fun setupNavigationController() { + val navController = findNavController(R.id.fragment_container) + val navInflater = navController.navInflater + val navGraph = navInflater.inflate(R.navigation.main_graph) + + val categoryInfo: CategoryInfo = PreferenceUtil.libraryCategory.first { it.visible } + if (categoryInfo.visible) { + navGraph.startDestination = categoryInfo.category.id + } + navController.graph = navGraph + NavigationUI.setupWithNavController(getBottomNavigationView(), navController) } override fun onSupportNavigateUp(): Boolean = @@ -98,8 +162,7 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis val id = parseLongFromIntent(intent, "playlistId", "playlist") if (id >= 0L) { val position: Int = intent.getIntExtra("position", 0) - val songs: List = - PlaylistSongsLoader.getPlaylistSongList(this@MainActivity, id) + val songs: List = PlaylistSongsLoader.getPlaylistSongList(get(), id) MusicPlayerRemote.openQueue(songs, position, true) handled = true } @@ -107,8 +170,9 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis val id = parseLongFromIntent(intent, "albumId", "album") if (id >= 0L) { val position: Int = intent.getIntExtra("position", 0) + val songs = libraryViewModel.albumById(id).songs MusicPlayerRemote.openQueue( - libraryViewModel.albumById(id).songs, + songs, position, true ) @@ -118,8 +182,9 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis val id = parseLongFromIntent(intent, "artistId", "artist") if (id >= 0L) { val position: Int = intent.getIntExtra("position", 0) + val songs: List = libraryViewModel.artistById(id).songs MusicPlayerRemote.openQueue( - libraryViewModel.artistById(id).songs, + songs, position, true ) @@ -130,11 +195,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) @@ -150,4 +215,4 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis } return id } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/muntashirakon/music/activities/PermissionActivity.kt b/app/src/main/java/io/github/muntashirakon/music/activities/PermissionActivity.kt index 0e4d7d2c4..a8f4bf819 100644 --- a/app/src/main/java/io/github/muntashirakon/music/activities/PermissionActivity.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.activities import android.content.Intent @@ -13,7 +27,6 @@ import io.github.muntashirakon.music.extensions.show import io.github.muntashirakon.music.util.RingtoneManager import kotlinx.android.synthetic.main.activity_permission.* - class PermissionActivity : AbsMusicServiceActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -37,7 +50,7 @@ class PermissionActivity : AbsMusicServiceActivity() { } finish.accentBackgroundColor() finish.setOnClickListener { - if (hasPermissions() && !RingtoneManager.requiresDialog(this)) { + if (hasPermissions()) { startActivity( Intent(this, MainActivity::class.java).addFlags( Intent.FLAG_ACTIVITY_NEW_TASK or @@ -56,4 +69,4 @@ class PermissionActivity : AbsMusicServiceActivity() { ) appNameText.text = appName } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/muntashirakon/music/activities/PlayingQueueActivity.kt b/app/src/main/java/io/github/muntashirakon/music/activities/PlayingQueueActivity.kt index 40bdff127..9a280bbb1 100644 --- a/app/src/main/java/io/github/muntashirakon/music/activities/PlayingQueueActivity.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.activities import android.content.res.ColorStateList diff --git a/app/src/main/java/io/github/muntashirakon/music/activities/SettingsActivity.kt b/app/src/main/java/io/github/muntashirakon/music/activities/SettingsActivity.kt index a82d834a7..c8d293200 100644 --- a/app/src/main/java/io/github/muntashirakon/music/activities/SettingsActivity.kt +++ b/app/src/main/java/io/github/muntashirakon/music/activities/SettingsActivity.kt @@ -1,8 +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 io.github.muntashirakon.music.activities import android.os.Bundle import android.view.MenuItem import androidx.navigation.NavController +import androidx.navigation.NavDestination import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.util.VersionUtils import com.afollestad.materialdialogs.color.ColorChooserDialog @@ -29,10 +44,26 @@ class SettingsActivity : AbsBaseActivity(), ColorChooserDialog.ColorCallback { applyToolbar(toolbar) val navController: NavController = findNavController(R.id.contentFrame) navController.addOnDestinationChangedListener { _, _, _ -> - toolbar.title = navController.currentDestination?.label + toolbar.title = navController.currentDestination?.let { getStringFromDestination(it) } } } + private fun getStringFromDestination(currentDestination: NavDestination): String { + val idRes = when (currentDestination.id) { + R.id.mainSettingsFragment -> R.string.action_settings + R.id.audioSettings -> R.string.pref_header_audio + R.id.imageSettingFragment -> R.string.pref_header_images + R.id.notificationSettingsFragment -> R.string.notification + R.id.nowPlayingSettingsFragment -> R.string.now_playing + R.id.otherSettingsFragment -> R.string.others + R.id.personalizeSettingsFragment -> R.string.personalize + R.id.themeSettingsFragment -> R.string.general_settings_title + R.id.aboutActivity -> R.string.action_about + else -> R.id.action_settings + } + return getString(idRes) + } + override fun onSupportNavigateUp(): Boolean { return findNavController(R.id.contentFrame).navigateUp() || super.onSupportNavigateUp() } @@ -49,7 +80,6 @@ class SettingsActivity : AbsBaseActivity(), ColorChooserDialog.ColorCallback { } override fun onColorChooserDismissed(dialog: ColorChooserDialog) { - } override fun onOptionsItemSelected(item: MenuItem): Boolean { @@ -58,4 +88,4 @@ class SettingsActivity : AbsBaseActivity(), ColorChooserDialog.ColorCallback { } return super.onOptionsItemSelected(item) } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/muntashirakon/music/activities/ShareInstagramStory.kt b/app/src/main/java/io/github/muntashirakon/music/activities/ShareInstagramStory.kt index bb83db570..a93603ad1 100644 --- a/app/src/main/java/io/github/muntashirakon/music/activities/ShareInstagramStory.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.activities import android.content.res.ColorStateList diff --git a/app/src/main/java/io/github/muntashirakon/music/activities/UserInfoActivity.kt b/app/src/main/java/io/github/muntashirakon/music/activities/UserInfoActivity.kt index 4f40dff43..2496d1242 100644 --- a/app/src/main/java/io/github/muntashirakon/music/activities/UserInfoActivity.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.activities import android.app.Activity @@ -11,12 +25,6 @@ import android.view.MenuItem import android.widget.Toast import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.MaterialValueHelper -import com.bumptech.glide.Glide -import com.bumptech.glide.load.engine.DiskCacheStrategy -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 io.github.muntashirakon.music.Constants.USER_BANNER import io.github.muntashirakon.music.Constants.USER_PROFILE import io.github.muntashirakon.music.R @@ -27,15 +35,21 @@ import io.github.muntashirakon.music.glide.ProfileBannerGlideRequest import io.github.muntashirakon.music.glide.UserProfileGlideRequest import io.github.muntashirakon.music.util.ImageUtil import io.github.muntashirakon.music.util.PreferenceUtil +import com.bumptech.glide.Glide +import com.bumptech.glide.load.engine.DiskCacheStrategy +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() { @@ -55,7 +69,7 @@ class UserInfoActivity : AbsBaseActivity() { pickNewPhoto() } - bannerSelect.setOnClickListener { + bannerImage.setOnClickListener { selectBannerImage() } @@ -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/io/github/muntashirakon/music/activities/WhatsNewActivity.java b/app/src/main/java/io/github/muntashirakon/music/activities/WhatsNewActivity.java index c45e797b8..595c1d53c 100644 --- a/app/src/main/java/io/github/muntashirakon/music/activities/WhatsNewActivity.java +++ b/app/src/main/java/io/github/muntashirakon/music/activities/WhatsNewActivity.java @@ -1,23 +1,14 @@ package io.github.muntashirakon.music.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 io.github.muntashirakon.music.R; import io.github.muntashirakon.music.activities.base.AbsBaseActivity; import io.github.muntashirakon.music.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/io/github/muntashirakon/music/activities/base/AbsBaseActivity.kt b/app/src/main/java/io/github/muntashirakon/music/activities/base/AbsBaseActivity.kt index a797a2809..1f84a568c 100644 --- a/app/src/main/java/io/github/muntashirakon/music/activities/base/AbsBaseActivity.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.activities.base import android.Manifest @@ -12,8 +26,8 @@ import android.view.KeyEvent import android.view.View import androidx.core.app.ActivityCompat import code.name.monkey.appthemehelper.ThemeStore -import com.google.android.material.snackbar.Snackbar import io.github.muntashirakon.music.R +import com.google.android.material.snackbar.Snackbar abstract class AbsBaseActivity : AbsThemeActivity() { private var hadPermissions: Boolean = false @@ -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/io/github/muntashirakon/music/activities/base/AbsMusicServiceActivity.kt b/app/src/main/java/io/github/muntashirakon/music/activities/base/AbsMusicServiceActivity.kt index 9d10348df..9d8d3faf2 100644 --- a/app/src/main/java/io/github/muntashirakon/music/activities/base/AbsMusicServiceActivity.kt +++ b/app/src/main/java/io/github/muntashirakon/music/activities/base/AbsMusicServiceActivity.kt @@ -1,25 +1,44 @@ +/* + * 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 io.github.muntashirakon.music.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 import io.github.muntashirakon.music.R import io.github.muntashirakon.music.db.toPlayCount import io.github.muntashirakon.music.helper.MusicPlayerRemote -import io.github.muntashirakon.music.interfaces.MusicServiceEventListener +import io.github.muntashirakon.music.interfaces.IMusicServiceEventListener import io.github.muntashirakon.music.repository.RealRepository import io.github.muntashirakon.music.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(), MusicServiceEventListener { +abstract class AbsMusicServiceActivity : AbsBaseActivity(), IMusicServiceEventListener { - private val mMusicServiceEventListeners = ArrayList() + private val mMusicServiceEventListeners = ArrayList() private val repository: RealRepository by inject() private var serviceToken: MusicPlayerRemote.ServiceToken? = null private var musicStateReceiver: MusicStateReceiver? = null @@ -49,15 +68,15 @@ abstract class AbsMusicServiceActivity : AbsBaseActivity(), MusicServiceEventLis } } - fun addMusicServiceEventListener(listener: MusicServiceEventListener?) { - if (listener != null) { - mMusicServiceEventListeners.add(listener) + fun addMusicServiceEventListener(listenerI: IMusicServiceEventListener?) { + if (listenerI != null) { + mMusicServiceEventListeners.add(listenerI) } } - fun removeMusicServiceEventListener(listener: MusicServiceEventListener?) { - if (listener != null) { - mMusicServiceEventListeners.remove(listener) + fun removeMusicServiceEventListener(listenerI: IMusicServiceEventListener?) { + if (listenerI != null) { + mMusicServiceEventListeners.remove(listenerI) } } diff --git a/app/src/main/java/io/github/muntashirakon/music/activities/base/AbsSlidingMusicPanelActivity.kt b/app/src/main/java/io/github/muntashirakon/music/activities/base/AbsSlidingMusicPanelActivity.kt index c06f0a7c4..ae594a210 100644 --- a/app/src/main/java/io/github/muntashirakon/music/activities/base/AbsSlidingMusicPanelActivity.kt +++ b/app/src/main/java/io/github/muntashirakon/music/activities/base/AbsSlidingMusicPanelActivity.kt @@ -1,30 +1,63 @@ +/* + * 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 io.github.muntashirakon.music.activities.base +import android.annotation.SuppressLint import android.graphics.Color import android.os.Bundle import android.view.View import android.view.ViewGroup import android.view.ViewTreeObserver import android.widget.FrameLayout -import androidx.annotation.LayoutRes import androidx.core.view.ViewCompat import androidx.core.view.isVisible -import androidx.lifecycle.Observer +import androidx.fragment.app.Fragment +import androidx.fragment.app.commit import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ColorUtil import io.github.muntashirakon.music.R import io.github.muntashirakon.music.RetroBottomSheetBehavior -import io.github.muntashirakon.music.extensions.hide -import io.github.muntashirakon.music.extensions.whichFragment +import io.github.muntashirakon.music.extensions.* import io.github.muntashirakon.music.fragments.LibraryViewModel import io.github.muntashirakon.music.fragments.MiniPlayerFragment import io.github.muntashirakon.music.fragments.NowPlayingScreen import io.github.muntashirakon.music.fragments.NowPlayingScreen.* +import io.github.muntashirakon.music.fragments.base.AbsPlayerFragment +import io.github.muntashirakon.music.fragments.player.adaptive.AdaptiveFragment +import io.github.muntashirakon.music.fragments.player.blur.BlurPlayerFragment +import io.github.muntashirakon.music.fragments.player.card.CardFragment +import io.github.muntashirakon.music.fragments.player.cardblur.CardBlurFragment +import io.github.muntashirakon.music.fragments.player.circle.CirclePlayerFragment +import io.github.muntashirakon.music.fragments.player.classic.ClassicPlayerFragment +import io.github.muntashirakon.music.fragments.player.color.ColorFragment +import io.github.muntashirakon.music.fragments.player.fit.FitFragment +import io.github.muntashirakon.music.fragments.player.flat.FlatPlayerFragment +import io.github.muntashirakon.music.fragments.player.full.FullPlayerFragment +import io.github.muntashirakon.music.fragments.player.gradient.GradientPlayerFragment +import io.github.muntashirakon.music.fragments.player.material.MaterialFragment +import io.github.muntashirakon.music.fragments.player.normal.PlayerFragment +import io.github.muntashirakon.music.fragments.player.peak.PeakPlayerFragment +import io.github.muntashirakon.music.fragments.player.plain.PlainPlayerFragment +import io.github.muntashirakon.music.fragments.player.simple.SimplePlayerFragment +import io.github.muntashirakon.music.fragments.player.tiny.TinyPlayerFragment import io.github.muntashirakon.music.helper.MusicPlayerRemote import io.github.muntashirakon.music.model.CategoryInfo import io.github.muntashirakon.music.util.PreferenceUtil import io.github.muntashirakon.music.views.BottomNavigationBarTinted import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetBehavior.* import kotlinx.android.synthetic.main.sliding_music_panel_layout.* import org.koin.androidx.viewmodel.ext.android.viewModel @@ -34,9 +67,10 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() { } protected val libraryViewModel by viewModel() - private lateinit var behavior: RetroBottomSheetBehavior + private lateinit var bottomSheetBehavior: RetroBottomSheetBehavior + private var playerFragment: AbsPlayerFragment? = null private var miniPlayerFragment: MiniPlayerFragment? = null - private var cps: NowPlayingScreen? = null + private var nowPlayingScreen: NowPlayingScreen? = null private var navigationBarColor: Int = 0 private var taskColor: Int = 0 private var lightStatusBar: Boolean = false @@ -44,88 +78,90 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() { private var paletteColor: Int = Color.WHITE protected abstract fun createContentView(): View private val panelState: Int - get() = behavior.state + get() = bottomSheetBehavior.state - private val bottomSheetCallbackList = object : BottomSheetBehavior.BottomSheetCallback() { + private val bottomSheetCallbackList = object : BottomSheetCallback() { override fun onSlide(bottomSheet: View, slideOffset: Float) { setMiniPlayerAlphaProgress(slideOffset) + dimBackground.show() + dimBackground.alpha = slideOffset } override fun onStateChanged(bottomSheet: View, newState: Int) { when (newState) { - BottomSheetBehavior.STATE_EXPANDED -> { + STATE_EXPANDED -> { onPanelExpanded() } - BottomSheetBehavior.STATE_COLLAPSED -> { + STATE_COLLAPSED -> { onPanelCollapsed() + dimBackground.hide() } else -> { - + println("Do something") } } } } + fun getBottomSheetBehavior() = bottomSheetBehavior override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(createContentView()) chooseFragmentForTheme() setupSlidingUpPanel() - setupBottomSheet() + updateColor() - libraryViewModel.paletteColorLiveData.observe(this, Observer { - this.paletteColor = it - onPaletteColorChanged() - }) + val themeColor = ATHUtil.resolveColor(this, android.R.attr.windowBackground, Color.GRAY) + dimBackground.setBackgroundColor(ColorUtil.withAlpha(themeColor, 0.5f)) + dimBackground.setOnClickListener { + println("dimBackground") + + } } - fun getBottomSheetBehavior() = behavior - private fun setupBottomSheet() { - behavior = BottomSheetBehavior.from(slidingPanel) as RetroBottomSheetBehavior - behavior.addBottomSheetCallback(bottomSheetCallbackList) - - if (behavior.state == BottomSheetBehavior.STATE_EXPANDED) { - setMiniPlayerAlphaProgress(1f) - } + bottomSheetBehavior = from(slidingPanel) as RetroBottomSheetBehavior + bottomSheetBehavior.addBottomSheetCallback(bottomSheetCallbackList) } override fun onResume() { super.onResume() - if (cps != PreferenceUtil.nowPlayingScreen) { + if (nowPlayingScreen != PreferenceUtil.nowPlayingScreen) { postRecreate() } + if (bottomSheetBehavior.state == BottomSheetBehavior.STATE_EXPANDED) { + setMiniPlayerAlphaProgress(1f) + } } override fun onDestroy() { super.onDestroy() - behavior.removeBottomSheetCallback(bottomSheetCallbackList) + bottomSheetBehavior.removeBottomSheetCallback(bottomSheetCallbackList) } - protected fun wrapSlidingMusicPanel(@LayoutRes resId: Int): View { + @SuppressLint("InflateParams") + protected fun wrapSlidingMusicPanel(): View { val slidingMusicPanelLayout = layoutInflater.inflate(R.layout.sliding_music_panel_layout, null) val contentContainer: ViewGroup = slidingMusicPanelLayout.findViewById(R.id.mainContentFrame) - layoutInflater.inflate(resId, contentContainer) + layoutInflater.inflate(R.layout.activity_main_content, contentContainer) return slidingMusicPanelLayout } fun collapsePanel() { - behavior.state = BottomSheetBehavior.STATE_COLLAPSED - setMiniPlayerAlphaProgress(0f) + bottomSheetBehavior.state = STATE_COLLAPSED } fun expandPanel() { - behavior.state = BottomSheetBehavior.STATE_EXPANDED + bottomSheetBehavior.state = STATE_EXPANDED setMiniPlayerAlphaProgress(1f) } private fun setMiniPlayerAlphaProgress(progress: Float) { - if (miniPlayerFragment?.view == null) return val alpha = 1 - progress miniPlayerFragment?.view?.alpha = alpha miniPlayerFragment?.view?.visibility = if (alpha == 0f) View.GONE else View.VISIBLE @@ -150,11 +186,16 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() { ViewTreeObserver.OnGlobalLayoutListener { override fun onGlobalLayout() { slidingPanel.viewTreeObserver.removeOnGlobalLayoutListener(this) + if (nowPlayingScreen != Peak) { + val params = slidingPanel.layoutParams as ViewGroup.LayoutParams + params.height = ViewGroup.LayoutParams.MATCH_PARENT + slidingPanel.layoutParams = params + } when (panelState) { - BottomSheetBehavior.STATE_EXPANDED -> onPanelExpanded() - BottomSheetBehavior.STATE_COLLAPSED -> onPanelCollapsed() + STATE_EXPANDED -> onPanelExpanded() + STATE_COLLAPSED -> onPanelCollapsed() else -> { - //playerFragment!!.onHide() + // playerFragment!!.onHide() } } } @@ -165,35 +206,6 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() { return bottomNavigationView } - fun hideBottomBarVisibility(visible: Boolean) { - bottomNavigationView.isVisible = visible - hideBottomBar(MusicPlayerRemote.playingQueue.isEmpty()) - } - - private fun hideBottomBar(hide: Boolean) { - val heightOfBar = bottomNavigationView.height - val isBottomBarVisible = bottomNavigationView.isVisible - - if (hide) { - behavior.isHideable = true - behavior.peekHeight = 0 - collapsePanel() - ViewCompat.setElevation(slidingPanel, 0f) - ViewCompat.setElevation(bottomNavigationView, 10f) - } else { - ViewCompat.setElevation(bottomNavigationView, 10f) - ViewCompat.setElevation(slidingPanel, 10f) - behavior.isHideable = false - behavior.peekHeight = (if (isBottomBarVisible) heightOfBar * 2 else heightOfBar) - 24 - } - } - - private fun chooseFragmentForTheme() { - cps = PreferenceUtil.nowPlayingScreen - miniPlayerFragment = whichFragment(R.id.miniPlayerFragment) - miniPlayerFragment?.view?.setOnClickListener { expandPanel() } - } - override fun onServiceConnected() { super.onServiceConnected() if (MusicPlayerRemote.playingQueue.isNotEmpty()) { @@ -216,36 +228,38 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() { if (!handleBackPress()) super.onBackPressed() } - open fun handleBackPress(): Boolean { - if (panelState == BottomSheetBehavior.STATE_EXPANDED) { + private fun handleBackPress(): Boolean { + if (bottomSheetBehavior.peekHeight != 0 && playerFragment!!.onBackPressed()) return true + if (panelState == STATE_EXPANDED) { collapsePanel() return true } + return false } private fun onPaletteColorChanged() { - if (panelState == BottomSheetBehavior.STATE_EXPANDED) { + if (panelState == STATE_EXPANDED) { super.setTaskDescriptionColor(paletteColor) val isColorLight = ColorUtil.isColorLight(paletteColor) - if (PreferenceUtil.isAdaptiveColor && (cps == Normal || cps == Flat)) { + if (PreferenceUtil.isAdaptiveColor && (nowPlayingScreen == Normal || nowPlayingScreen == Flat)) { super.setLightNavigationBar(true) super.setLightStatusbar(isColorLight) - } else if (cps == Card || cps == Blur || cps == BlurCard) { + } else if (nowPlayingScreen == Card || nowPlayingScreen == Blur || nowPlayingScreen == BlurCard) { super.setLightStatusbar(false) super.setLightNavigationBar(true) super.setNavigationbarColor(Color.BLACK) - } else if (cps == Color || cps == Tiny || cps == Gradient) { + } else if (nowPlayingScreen == Color || nowPlayingScreen == Tiny || nowPlayingScreen == Gradient) { super.setNavigationbarColor(paletteColor) super.setLightNavigationBar(isColorLight) super.setLightStatusbar(isColorLight) - } else if (cps == Full) { + } else if (nowPlayingScreen == Full) { super.setNavigationbarColor(paletteColor) super.setLightNavigationBar(isColorLight) super.setLightStatusbar(false) - } else if (cps == Classic) { + } else if (nowPlayingScreen == Classic) { super.setLightStatusbar(false) - } else if (cps == Fit) { + } else if (nowPlayingScreen == Fit) { super.setLightStatusbar(false) } else { super.setLightStatusbar( @@ -263,38 +277,32 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() { override fun setLightStatusbar(enabled: Boolean) { lightStatusBar = enabled - if (panelState == BottomSheetBehavior.STATE_COLLAPSED) { + if (panelState == STATE_COLLAPSED) { super.setLightStatusbar(enabled) } } override fun setLightNavigationBar(enabled: Boolean) { lightNavigationBar = enabled - if (panelState == BottomSheetBehavior.STATE_COLLAPSED) { + if (panelState == STATE_COLLAPSED) { super.setLightNavigationBar(enabled) } } override fun setNavigationbarColor(color: Int) { navigationBarColor = color - if (panelState == BottomSheetBehavior.STATE_COLLAPSED) { + if (panelState == STATE_COLLAPSED) { super.setNavigationbarColor(color) } } override fun setTaskDescriptionColor(color: Int) { taskColor = color - if (panelState == BottomSheetBehavior.STATE_COLLAPSED) { + if (panelState == STATE_COLLAPSED) { super.setTaskDescriptionColor(color) } } - fun hideBottomNavigation() { - behavior.isHideable = true - behavior.peekHeight = 0 - hideBottomBarVisibility(false) - } - fun updateTabs() { bottomNavigationView.menu.clear() val currentTabs: List = PreferenceUtil.libraryCategory @@ -308,4 +316,74 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() { bottomNavigationView.hide() } } -} \ No newline at end of file + + private fun updateColor() { + libraryViewModel.paletteColor.observe(this, { color -> + this.paletteColor = color + onPaletteColorChanged() + }) + } + + fun setBottomBarVisibility(visible: Int) { + bottomNavigationView.visibility = visible + hideBottomBar(MusicPlayerRemote.playingQueue.isEmpty()) + } + + private fun hideBottomBar(hide: Boolean) { + val heightOfBar = dip(R.dimen.mini_player_height) + val heightOfBarWithTabs = dip(R.dimen.mini_player_height_expanded) + val isVisible = bottomNavigationView.isVisible + if (hide) { + bottomSheetBehavior.isHideable = true + bottomSheetBehavior.peekHeight = 0 + ViewCompat.setElevation(slidingPanel, 0f) + ViewCompat.setElevation(bottomNavigationView, 10f) + collapsePanel() + } else { + if (MusicPlayerRemote.playingQueue.isNotEmpty()) { + bottomSheetBehavior.isHideable = false + ViewCompat.setElevation(slidingPanel, 10f) + ViewCompat.setElevation(bottomNavigationView, 10f) + if (isVisible) { + bottomSheetBehavior.peekHeight = heightOfBarWithTabs + bottomNavigationView.translateYAnimate(0f) + } else { + bottomNavigationView.translateYAnimate(150f) + bottomSheetBehavior.peekHeight = heightOfBar + } + } + } + } + + private fun chooseFragmentForTheme() { + nowPlayingScreen = PreferenceUtil.nowPlayingScreen + + val fragment: Fragment = when (nowPlayingScreen) { + Blur -> BlurPlayerFragment() + Adaptive -> AdaptiveFragment() + Normal -> PlayerFragment() + Card -> CardFragment() + BlurCard -> CardBlurFragment() + Fit -> FitFragment() + Flat -> FlatPlayerFragment() + Full -> FullPlayerFragment() + Plain -> PlainPlayerFragment() + Simple -> SimplePlayerFragment() + Material -> MaterialFragment() + Color -> ColorFragment() + Gradient -> GradientPlayerFragment() + Tiny -> TinyPlayerFragment() + Peak -> PeakPlayerFragment() + Circle -> CirclePlayerFragment() + Classic -> ClassicPlayerFragment() + else -> PlayerFragment() + } // must implement AbsPlayerFragment + supportFragmentManager.commit { + replace(R.id.playerFragmentContainer, fragment) + } + supportFragmentManager.executePendingTransactions() + playerFragment = whichFragment(R.id.playerFragmentContainer) + miniPlayerFragment = whichFragment(R.id.miniPlayerFragment) + miniPlayerFragment?.view?.setOnClickListener { expandPanel() } + } +} diff --git a/app/src/main/java/io/github/muntashirakon/music/activities/base/AbsThemeActivity.kt b/app/src/main/java/io/github/muntashirakon/music/activities/base/AbsThemeActivity.kt index ba3ef39c2..c9f0e2b19 100644 --- a/app/src/main/java/io/github/muntashirakon/music/activities/base/AbsThemeActivity.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/activities/bugreport/BugReportActivity.kt b/app/src/main/java/io/github/muntashirakon/music/activities/bugreport/BugReportActivity.kt index 1c208803e..4e5753630 100644 --- a/app/src/main/java/io/github/muntashirakon/music/activities/bugreport/BugReportActivity.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.activities.bugreport import android.app.Activity @@ -31,6 +45,7 @@ import io.github.muntashirakon.music.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/io/github/muntashirakon/music/activities/bugreport/model/DeviceInfo.java b/app/src/main/java/io/github/muntashirakon/music/activities/bugreport/model/DeviceInfo.java index e1f12ddee..b30ab7bce 100644 --- a/app/src/main/java/io/github/muntashirakon/music/activities/bugreport/model/DeviceInfo.java +++ b/app/src/main/java/io/github/muntashirakon/music/activities/bugreport/model/DeviceInfo.java @@ -5,129 +5,197 @@ import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Build; - import androidx.annotation.IntRange; - -import org.jetbrains.annotations.NotNull; - +import code.name.monkey.retromusic.util.PreferenceUtil; import java.util.Arrays; import java.util.Locale; -import io.github.muntashirakon.music.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(); + } - @NotNull + 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"; + } + + @NotNull @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 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/io/github/muntashirakon/music/activities/bugreport/model/Report.java b/app/src/main/java/io/github/muntashirakon/music/activities/bugreport/model/Report.java index ee02b3685..38ad348f5 100644 --- a/app/src/main/java/io/github/muntashirakon/music/activities/bugreport/model/Report.java +++ b/app/src/main/java/io/github/muntashirakon/music/activities/bugreport/model/Report.java @@ -1,33 +1,34 @@ package io.github.muntashirakon.music.activities.bugreport.model; - import io.github.muntashirakon.music.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/io/github/muntashirakon/music/activities/bugreport/model/github/ExtraInfo.java b/app/src/main/java/io/github/muntashirakon/music/activities/bugreport/model/github/ExtraInfo.java index 88947a0a6..4dd70db8b 100644 --- a/app/src/main/java/io/github/muntashirakon/music/activities/bugreport/model/github/ExtraInfo.java +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/activities/bugreport/model/github/GithubLogin.java b/app/src/main/java/io/github/muntashirakon/music/activities/bugreport/model/github/GithubLogin.java index 5af73d375..4c5dff82a 100644 --- a/app/src/main/java/io/github/muntashirakon/music/activities/bugreport/model/github/GithubLogin.java +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/activities/bugreport/model/github/GithubTarget.java b/app/src/main/java/io/github/muntashirakon/music/activities/bugreport/model/github/GithubTarget.java index 48b285c94..e8247303f 100644 --- a/app/src/main/java/io/github/muntashirakon/music/activities/bugreport/model/github/GithubTarget.java +++ b/app/src/main/java/io/github/muntashirakon/music/activities/bugreport/model/github/GithubTarget.java @@ -2,20 +2,20 @@ package io.github.muntashirakon.music.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/io/github/muntashirakon/music/activities/saf/SAFGuideActivity.java b/app/src/main/java/io/github/muntashirakon/music/activities/saf/SAFGuideActivity.java index 6f4a99319..188f107a1 100644 --- a/app/src/main/java/io/github/muntashirakon/music/activities/saf/SAFGuideActivity.java +++ b/app/src/main/java/io/github/muntashirakon/music/activities/saf/SAFGuideActivity.java @@ -16,57 +16,58 @@ package io.github.muntashirakon.music.activities.saf; import android.os.Build; import android.os.Bundle; - import androidx.annotation.Nullable; - +import io.github.muntashirakon.music.R; import com.heinrichreimersoftware.materialintro.app.IntroActivity; import com.heinrichreimersoftware.materialintro.slide.SimpleSlide; -import io.github.muntashirakon.music.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/io/github/muntashirakon/music/activities/tageditor/AbsTagEditorActivity.kt b/app/src/main/java/io/github/muntashirakon/music/activities/tageditor/AbsTagEditorActivity.kt index 7a13acf8d..39eec815f 100755 --- a/app/src/main/java/io/github/muntashirakon/music/activities/tageditor/AbsTagEditorActivity.kt +++ b/app/src/main/java/io/github/muntashirakon/music/activities/tageditor/AbsTagEditorActivity.kt @@ -1,13 +1,25 @@ +/* + * 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 io.github.muntashirakon.music.activities.tageditor import android.app.Activity import android.app.SearchManager import android.content.Intent -import android.content.res.ColorStateList import android.graphics.Bitmap import android.graphics.BitmapFactory import android.net.Uri -import android.os.Build import android.os.Bundle import android.util.Log import android.view.MenuItem @@ -16,18 +28,19 @@ import android.view.animation.OvershootInterpolator import androidx.appcompat.app.AlertDialog 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.MaterialValueHelper import code.name.monkey.appthemehelper.util.TintHelper -import com.google.android.material.button.MaterialButton -import com.google.android.material.dialog.MaterialAlertDialogBuilder import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R.drawable import io.github.muntashirakon.music.activities.base.AbsBaseActivity import io.github.muntashirakon.music.activities.saf.SAFGuideActivity +import io.github.muntashirakon.music.extensions.accentColor +import io.github.muntashirakon.music.model.ArtworkInfo +import io.github.muntashirakon.music.model.LoadingInfo import io.github.muntashirakon.music.repository.Repository import io.github.muntashirakon.music.util.RetroUtil import io.github.muntashirakon.music.util.SAFUtil +import com.google.android.material.button.MaterialButton +import com.google.android.material.dialog.MaterialAlertDialogBuilder import kotlinx.android.synthetic.main.activity_album_tag_editor.* import org.jaudiotagger.audio.AudioFile import org.jaudiotagger.audio.AudioFileIO @@ -49,6 +62,8 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() { private val currentSongPath: String? = null private var savedTags: Map? = null private var savedArtworkInfo: ArtworkInfo? = null + protected abstract val contentViewLayout: Int + protected abstract fun loadImageFromFile(selectedFile: Uri?) protected val show: AlertDialog get() = @@ -62,7 +77,6 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() { } } .show() - protected abstract val contentViewLayout: Int internal val albumArtist: String? get() { @@ -182,6 +196,7 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() { getIntentExtras() songPaths = getSongPaths() + println(songPaths?.size) if (songPaths!!.isEmpty()) { finish() } @@ -198,9 +213,9 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() { private fun setUpImageView() { loadCurrentImage() items = listOf( - getString(io.github.muntashirakon.music.R.string.pick_from_local_storage), - getString(io.github.muntashirakon.music.R.string.web_search), - getString(io.github.muntashirakon.music.R.string.remove_cover) + getString(R.string.pick_from_local_storage), + getString(R.string.web_search), + getString(R.string.remove_cover) ) editorImage?.setOnClickListener { show } } @@ -211,7 +226,7 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() { startActivityForResult( Intent.createChooser( intent, - getString(io.github.muntashirakon.music.R.string.pick_from_local_storage) + getString(R.string.pick_from_local_storage) ), REQUEST_CODE_SELECT_IMAGE ) } @@ -223,20 +238,7 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() { protected abstract fun deleteImage() private fun setUpFab() { - saveFab.backgroundTintList = ColorStateList.valueOf(ThemeStore.accentColor(this)) - ColorStateList.valueOf( - MaterialValueHelper.getPrimaryTextColor( - this, - ColorUtil.isColorLight( - ThemeStore.accentColor( - this - ) - ) - ) - ).apply { - saveFab.setTextColor(this) - saveFab.iconTint = this - } + saveFab.accentColor() saveFab.apply { scaleX = 0f scaleY = 0f @@ -324,35 +326,25 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() { } protected fun writeValuesToFiles( - fieldKeyValueMap: Map, artworkInfo: ArtworkInfo? + fieldKeyValueMap: Map, + artworkInfo: ArtworkInfo? ) { RetroUtil.hideSoftKeyboard(this) hideFab() - - savedSongPaths = songPaths - savedTags = fieldKeyValueMap - savedArtworkInfo = artworkInfo - - if (!SAFUtil.isSAFRequired(savedSongPaths)) { - writeTags(savedSongPaths) - } else { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - if (SAFUtil.isSDCardAccessGranted(this)) { - writeTags(savedSongPaths) - } else { - startActivityForResult( - Intent(this, SAFGuideActivity::class.java), - SAFGuideActivity.REQUEST_CODE_SAF_GUIDE - ) - } - } - } + println(fieldKeyValueMap) + WriteTagsAsyncTask(this).execute( + LoadingInfo( + songPaths, + fieldKeyValueMap, + artworkInfo + ) + ) } private fun writeTags(paths: List?) { WriteTagsAsyncTask(this).execute( - WriteTagsAsyncTask.LoadingInfo( + LoadingInfo( paths, savedTags, savedArtworkInfo @@ -360,6 +352,7 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() { ) } + override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) { super.onActivityResult(requestCode, resultCode, intent) when (requestCode) { @@ -385,7 +378,6 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() { } } - protected abstract fun loadImageFromFile(selectedFile: Uri?) private fun getAudioFile(path: String): AudioFile { return try { @@ -396,7 +388,6 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() { } } - class ArtworkInfo constructor(val albumId: Long, val artwork: Bitmap?) companion object { @@ -405,5 +396,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/io/github/muntashirakon/music/activities/tageditor/AlbumTagEditorActivity.kt b/app/src/main/java/io/github/muntashirakon/music/activities/tageditor/AlbumTagEditorActivity.kt index a4d2c3814..3aa74ca86 100755 --- a/app/src/main/java/io/github/muntashirakon/music/activities/tageditor/AlbumTagEditorActivity.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.activities.tageditor import android.app.Activity @@ -18,6 +32,7 @@ import io.github.muntashirakon.music.R import io.github.muntashirakon.music.extensions.appHandleColor import io.github.muntashirakon.music.glide.palette.BitmapPaletteTranscoder import io.github.muntashirakon.music.glide.palette.BitmapPaletteWrapper +import io.github.muntashirakon.music.model.ArtworkInfo import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.util.ImageUtil import io.github.muntashirakon.music.util.RetroColorUtil.generatePalette @@ -26,9 +41,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 +170,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() @@ -163,8 +178,11 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher { writeValuesToFiles( fieldKeyValueMap, - if (deleteAlbumArt) ArtworkInfo(id, null) - else if (albumArtBitmap == null) null else ArtworkInfo(id, albumArtBitmap!!) + when { + deleteAlbumArt -> ArtworkInfo(id, null) + albumArtBitmap == null -> null + else -> ArtworkInfo(id, albumArtBitmap!!) + } ) } diff --git a/app/src/main/java/io/github/muntashirakon/music/activities/tageditor/SongTagEditorActivity.kt b/app/src/main/java/io/github/muntashirakon/music/activities/tageditor/SongTagEditorActivity.kt index baf9f580f..55c068a3a 100755 --- a/app/src/main/java/io/github/muntashirakon/music/activities/tageditor/SongTagEditorActivity.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.activities.tageditor import android.net.Uri @@ -88,11 +102,7 @@ class SongTagEditorActivity : AbsTagEditorActivity(), TextWatcher { writeValuesToFiles(fieldKeyValueMap, null) } - override fun getSongPaths(): List { - val paths = ArrayList(1) - paths.add(songRepository.song(id).data) - return paths - } + override fun getSongPaths(): List = listOf(songRepository.song(id).data) override fun loadImageFromFile(selectedFile: Uri?) { } @@ -111,5 +121,3 @@ class SongTagEditorActivity : AbsTagEditorActivity(), TextWatcher { val TAG: String = SongTagEditorActivity::class.java.simpleName } } - - diff --git a/app/src/main/java/io/github/muntashirakon/music/activities/tageditor/WriteTagsAsyncTask.java b/app/src/main/java/io/github/muntashirakon/music/activities/tageditor/WriteTagsAsyncTask.java index 07aaf76b8..7c6851aa1 100644 --- a/app/src/main/java/io/github/muntashirakon/music/activities/tageditor/WriteTagsAsyncTask.java +++ b/app/src/main/java/io/github/muntashirakon/music/activities/tageditor/WriteTagsAsyncTask.java @@ -5,67 +5,54 @@ import android.app.Dialog; import android.content.Context; import android.graphics.Bitmap; import android.media.MediaScannerConnection; -import android.net.Uri; -import android.os.Build; +import android.util.Log; +import android.widget.Toast; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.afollestad.materialdialogs.MaterialDialog; import org.jaudiotagger.audio.AudioFile; import org.jaudiotagger.audio.AudioFileIO; +import org.jaudiotagger.audio.exceptions.CannotReadException; +import org.jaudiotagger.audio.exceptions.CannotWriteException; +import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException; +import org.jaudiotagger.audio.exceptions.ReadOnlyFileException; import org.jaudiotagger.tag.FieldKey; import org.jaudiotagger.tag.Tag; +import org.jaudiotagger.tag.TagException; import org.jaudiotagger.tag.images.Artwork; import org.jaudiotagger.tag.images.ArtworkFactory; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Collection; +import java.util.List; import java.util.Map; import io.github.muntashirakon.music.R; import io.github.muntashirakon.music.misc.DialogAsyncTask; import io.github.muntashirakon.music.misc.UpdateToastMediaScannerCompletionListener; +import io.github.muntashirakon.music.model.LoadingInfo; import io.github.muntashirakon.music.util.MusicUtil; -import io.github.muntashirakon.music.util.SAFUtil; -public class WriteTagsAsyncTask extends DialogAsyncTask { +public class WriteTagsAsyncTask extends DialogAsyncTask> { - private WeakReference activity; - - public WriteTagsAsyncTask(@NonNull Activity activity) { - super(activity); - this.activity = new WeakReference<>(activity); - } - - @NonNull - @Override - protected Dialog createDialog(@NonNull Context context) { - - return new MaterialAlertDialogBuilder(context) - .setTitle(R.string.saving_changes) - .setCancelable(false) - .setView(R.layout.loading) - .create(); + public WriteTagsAsyncTask(Context context) { + super(context); } @Override - protected String[] doInBackground(LoadingInfo... params) { + protected List doInBackground(LoadingInfo... params) { try { LoadingInfo info = params[0]; Artwork artwork = null; File albumArtFile = null; - if (info.artworkInfo != null && info.artworkInfo.getArtwork() != null) { + if (info.getArtworkInfo() != null && info.getArtworkInfo().getArtwork() != null) { try { albumArtFile = MusicUtil.INSTANCE.createAlbumArtFile().getCanonicalFile(); - info.artworkInfo.getArtwork() - .compress(Bitmap.CompressFormat.PNG, 0, new FileOutputStream(albumArtFile)); + info.getArtworkInfo().getArtwork().compress(Bitmap.CompressFormat.PNG, 0, new FileOutputStream(albumArtFile)); artwork = ArtworkFactory.createArtworkFromFile(albumArtFile); } catch (IOException e) { e.printStackTrace(); @@ -75,21 +62,14 @@ public class WriteTagsAsyncTask extends DialogAsyncTask entry : info.fieldKeyValueMap.entrySet()) { + if (info.getFieldKeyValueMap() != null) { + for (Map.Entry entry : info.getFieldKeyValueMap().entrySet()) { try { tag.setField(entry.getKey(), entry.getValue()); } catch (Exception e) { @@ -98,8 +78,8 @@ public class WriteTagsAsyncTask extends DialogAsyncTask 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()]); + return info.getFilePaths(); } catch (Exception e) { e.printStackTrace(); return null; @@ -145,48 +112,40 @@ public class WriteTagsAsyncTask extends DialogAsyncTask toBeScanned) { super.onPostExecute(toBeScanned); scan(toBeScanned); } + @Override + protected void onCancelled(List toBeScanned) { + super.onCancelled(toBeScanned); + scan(toBeScanned); + } + + private void scan(List toBeScanned) { + Context context = getContext(); + if (toBeScanned == null || toBeScanned.isEmpty()) { + Log.i("scan", "scan: Empty"); + Toast.makeText(context, "Scan file from folder", Toast.LENGTH_SHORT).show(); + return; + } + MediaScannerConnection.scanFile(context, toBeScanned.toArray(new String[0]), null, context instanceof Activity ? new UpdateToastMediaScannerCompletionListener((Activity) context, toBeScanned) : null); + } + + @Override + protected Dialog createDialog(@NonNull Context context) { + return new MaterialDialog.Builder(context) + .title(R.string.saving_changes) + .cancelable(false) + .progress(false, 0) + .build(); + } + @Override protected void onProgressUpdate(@NonNull Dialog dialog, Integer... values) { super.onProgressUpdate(dialog, values); - //((MaterialDialog) dialog).setMaxProgress(values[1]); - //((MaterialDialog) dialog).setProgress(values[0]); + ((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; - } - } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/muntashirakon/music/adapter/CategoryInfoAdapter.java b/app/src/main/java/io/github/muntashirakon/music/adapter/CategoryInfoAdapter.java index 55132839c..41ba9eece 100644 --- a/app/src/main/java/io/github/muntashirakon/music/adapter/CategoryInfoAdapter.java +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.R; import io.github.muntashirakon.music.model.CategoryInfo; import io.github.muntashirakon.music.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/io/github/muntashirakon/music/adapter/ContributorAdapter.kt b/app/src/main/java/io/github/muntashirakon/music/adapter/ContributorAdapter.kt index 7b781a1d0..7cef1c843 100644 --- a/app/src/main/java/io/github/muntashirakon/music/adapter/ContributorAdapter.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.adapter import android.app.Activity @@ -59,6 +73,11 @@ class ContributorAdapter( return contributors.size } + fun swapData(it: List) { + contributors = it + notifyDataSetChanged() + } + inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val title: TextView = itemView.findViewById(R.id.title) val text: TextView = itemView.findViewById(R.id.text) @@ -68,7 +87,7 @@ class ContributorAdapter( title.text = contributor.name text.text = contributor.summary Glide.with(image.context) - .load(contributor.profileImage) + .load(contributor.image) .error(R.drawable.ic_account) .placeholder(R.drawable.ic_account) .dontAnimate() diff --git a/app/src/main/java/io/github/muntashirakon/music/adapter/GenreAdapter.kt b/app/src/main/java/io/github/muntashirakon/music/adapter/GenreAdapter.kt index 307272b33..f597a195f 100644 --- a/app/src/main/java/io/github/muntashirakon/music/adapter/GenreAdapter.kt +++ b/app/src/main/java/io/github/muntashirakon/music/adapter/GenreAdapter.kt @@ -1,6 +1,19 @@ +/* + * 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 io.github.muntashirakon.music.adapter -import android.graphics.Color import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -23,14 +36,12 @@ class GenreAdapter( var dataSet: List, private val mItemLayoutRes: Int ) : RecyclerView.Adapter() { - val colors = listOf(Color.RED, Color.BLUE) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { return ViewHolder(LayoutInflater.from(activity).inflate(mItemLayoutRes, parent, false)) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { val genre = dataSet[position] - holder.title?.text = genre.name holder.text?.text = String.format( Locale.getDefault(), diff --git a/app/src/main/java/io/github/muntashirakon/music/adapter/HomeAdapter.kt b/app/src/main/java/io/github/muntashirakon/music/adapter/HomeAdapter.kt index 0d9d57955..6401d0a9c 100644 --- a/app/src/main/java/io/github/muntashirakon/music/adapter/HomeAdapter.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.adapter import android.view.LayoutInflater @@ -15,23 +29,23 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.util.ColorUtil -import com.bumptech.glide.Glide -import com.google.android.material.card.MaterialCardView import io.github.muntashirakon.music.* import io.github.muntashirakon.music.adapter.album.AlbumAdapter import io.github.muntashirakon.music.adapter.artist.ArtistAdapter import io.github.muntashirakon.music.adapter.song.SongAdapter import io.github.muntashirakon.music.extensions.hide -import io.github.muntashirakon.music.fragments.albums.AlbumClickListener -import io.github.muntashirakon.music.fragments.artists.ArtistClickListener import io.github.muntashirakon.music.glide.SongGlideRequest import io.github.muntashirakon.music.helper.MusicPlayerRemote +import io.github.muntashirakon.music.interfaces.IAlbumClickListener +import io.github.muntashirakon.music.interfaces.IArtistClickListener import io.github.muntashirakon.music.model.* import io.github.muntashirakon.music.util.PreferenceUtil +import com.bumptech.glide.Glide +import com.google.android.material.card.MaterialCardView class HomeAdapter( private val activity: AppCompatActivity -) : RecyclerView.Adapter(), ArtistClickListener, AlbumClickListener { +) : RecyclerView.Adapter(), IArtistClickListener, IAlbumClickListener { private var list = listOf() @@ -121,7 +135,6 @@ class HomeAdapter( viewHolder.bind(home) } PLAYLISTS -> { - } } } @@ -181,7 +194,6 @@ class HomeAdapter( .asBitmap() .build() .into(itemView.findViewById(id)) - } } } @@ -225,21 +237,22 @@ class HomeAdapter( } fun artistsAdapter(artists: List) = - ArtistAdapter(activity, artists, PreferenceUtil.homeGridStyle, null, this) + ArtistAdapter(activity, artists, PreferenceUtil.homeArtistGridStyle, null, this) fun albumAdapter(albums: List) = - AlbumAdapter(activity, albums, R.layout.item_image, null, this) + AlbumAdapter(activity, albums, PreferenceUtil.homeAlbumGridStyle, null, this) fun gridLayoutManager() = GridLayoutManager(activity, 1, GridLayoutManager.HORIZONTAL, false) + fun linearLayoutManager() = LinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false) - override fun onArtist(artistId: Long, imageView: ImageView) { + override fun onArtist(artistId: Long, view: View) { activity.findNavController(R.id.fragment_container).navigate( R.id.artistDetailsFragment, bundleOf(EXTRA_ARTIST_ID to artistId), null, FragmentNavigatorExtras( - imageView to activity.getString(R.string.transition_album_art) + view to "artist" ) ) } @@ -250,7 +263,7 @@ class HomeAdapter( bundleOf(EXTRA_ALBUM_ID to albumId), null, FragmentNavigatorExtras( - view to activity.getString(R.string.transition_album_art) + view to "album" ) ) } diff --git a/app/src/main/java/io/github/muntashirakon/music/adapter/SearchAdapter.kt b/app/src/main/java/io/github/muntashirakon/music/adapter/SearchAdapter.kt index 8726619d8..277ca0b59 100644 --- a/app/src/main/java/io/github/muntashirakon/music/adapter/SearchAdapter.kt +++ b/app/src/main/java/io/github/muntashirakon/music/adapter/SearchAdapter.kt @@ -1,16 +1,32 @@ +/* + * 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 io.github.muntashirakon.music.adapter import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.os.bundleOf +import androidx.core.view.isInvisible +import androidx.core.view.isVisible import androidx.fragment.app.FragmentActivity import androidx.navigation.findNavController import androidx.recyclerview.widget.RecyclerView import code.name.monkey.appthemehelper.ThemeStore -import com.bumptech.glide.Glide import io.github.muntashirakon.music.* import io.github.muntashirakon.music.adapter.base.MediaEntryViewHolder +import io.github.muntashirakon.music.db.PlaylistWithSongs import io.github.muntashirakon.music.glide.AlbumGlideRequest import io.github.muntashirakon.music.glide.ArtistGlideRequest import io.github.muntashirakon.music.helper.MusicPlayerRemote @@ -19,13 +35,15 @@ import io.github.muntashirakon.music.model.* import io.github.muntashirakon.music.model.smartplaylist.AbsSmartPlaylist import io.github.muntashirakon.music.repository.PlaylistSongsLoader import io.github.muntashirakon.music.util.MusicUtil +import com.bumptech.glide.Glide +import java.util.* class SearchAdapter( private val activity: FragmentActivity, private var dataSet: List ) : RecyclerView.Adapter() { - fun swapDataSet(dataSet: MutableList) { + fun swapDataSet(dataSet: List) { this.dataSet = dataSet notifyDataSetChanged() } @@ -34,7 +52,7 @@ class SearchAdapter( if (dataSet[position] is Album) return ALBUM if (dataSet[position] is Artist) return ARTIST if (dataSet[position] is Genre) return GENRE - if (dataSet[position] is Playlist) return PLAYLIST + if (dataSet[position] is PlaylistWithSongs) return PLAYLIST return if (dataSet[position] is Song) SONG else HEADER } @@ -56,42 +74,52 @@ class SearchAdapter( override fun onBindViewHolder(holder: ViewHolder, position: Int) { when (getItemViewType(position)) { ALBUM -> { - val album = dataSet.get(position) as Album + holder.imageTextContainer?.isVisible = true + val album = dataSet[position] as Album holder.title?.text = album.title holder.text?.text = album.artistName AlbumGlideRequest.Builder.from(Glide.with(activity), album.safeGetFirstSong()) .checkIgnoreMediaStore().build().into(holder.image) } ARTIST -> { - val artist = dataSet.get(position) as Artist + holder.imageTextContainer?.isVisible = true + val artist = dataSet[position] as Artist holder.title?.text = artist.name holder.text?.text = MusicUtil.getArtistInfoString(activity, artist) ArtistGlideRequest.Builder.from(Glide.with(activity), artist).build() .into(holder.image) } SONG -> { - val song = dataSet.get(position) as Song + val song = dataSet[position] as Song holder.title?.text = song.title holder.text?.text = song.albumName } GENRE -> { - val genre = dataSet.get(position) as Genre + val genre = dataSet[position] as Genre holder.title?.text = genre.name + holder.text?.text = String.format( + Locale.getDefault(), + "%d %s", + genre.songCount, + if (genre.songCount > 1) activity.getString(R.string.songs) else activity.getString( + R.string.song + ) + ) } PLAYLIST -> { - val playlist = dataSet.get(position) as Playlist - holder.title?.text = playlist.name - holder.text?.text = MusicUtil.getPlaylistInfoString(activity, getSongs(playlist)) + val playlist = dataSet[position] as PlaylistWithSongs + holder.title?.text = playlist.playlistEntity.playlistName + holder.text?.text = MusicUtil.playlistInfoString(activity, playlist.songs) } else -> { - holder.title?.text = dataSet.get(position).toString() + holder.title?.text = dataSet[position].toString() holder.title?.setTextColor(ThemeStore.accentColor(activity)) } } } - private fun getSongs(playlist: Playlist): java.util.ArrayList { - val songs = java.util.ArrayList() + private fun getSongs(playlist: Playlist): List { + val songs = mutableListOf() if (playlist is AbsSmartPlaylist) { songs.addAll(playlist.getSongs()) } else { @@ -107,7 +135,7 @@ class SearchAdapter( inner class ViewHolder(itemView: View, itemViewType: Int) : MediaEntryViewHolder(itemView) { init { itemView.setOnLongClickListener(null) - + imageTextContainer?.isInvisible = true if (itemViewType == SONG) { menu?.visibility = View.VISIBLE menu?.setOnClickListener(object : SongMenuHelper.OnClickSongMenu(activity) { @@ -151,12 +179,12 @@ class SearchAdapter( } PLAYLIST -> { activity.findNavController(R.id.fragment_container).navigate( - R.id.artistDetailsFragment, - bundleOf(EXTRA_PLAYLIST to (item as Playlist)) + R.id.playlistDetailsFragment, + bundleOf(EXTRA_PLAYLIST to (item as PlaylistWithSongs)) ) } SONG -> { - val playList = ArrayList() + val playList = mutableListOf() playList.add(item as Song) MusicPlayerRemote.openQueue(playList, 0, true) } diff --git a/app/src/main/java/io/github/muntashirakon/music/adapter/SongFileAdapter.kt b/app/src/main/java/io/github/muntashirakon/music/adapter/SongFileAdapter.kt index b6c8503c4..8523164e8 100644 --- a/app/src/main/java/io/github/muntashirakon/music/adapter/SongFileAdapter.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.adapter @@ -22,31 +21,31 @@ import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import code.name.monkey.appthemehelper.util.ATHUtil -import com.bumptech.glide.Glide -import com.bumptech.glide.load.engine.DiskCacheStrategy -import com.bumptech.glide.signature.MediaStoreSignature import io.github.muntashirakon.music.R import io.github.muntashirakon.music.adapter.base.AbsMultiSelectAdapter import io.github.muntashirakon.music.adapter.base.MediaEntryViewHolder import io.github.muntashirakon.music.glide.audiocover.AudioFileCover -import io.github.muntashirakon.music.interfaces.CabHolder -import io.github.muntashirakon.music.interfaces.Callbacks +import io.github.muntashirakon.music.interfaces.ICabHolder +import io.github.muntashirakon.music.interfaces.ICallbacks import io.github.muntashirakon.music.util.MusicUtil import io.github.muntashirakon.music.util.RetroUtil -import me.zhanghai.android.fastscroll.PopupTextProvider +import com.bumptech.glide.Glide +import com.bumptech.glide.load.engine.DiskCacheStrategy +import com.bumptech.glide.signature.MediaStoreSignature 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, private var dataSet: List, private val itemLayoutRes: Int, - private val callbacks: Callbacks?, - cabHolder: CabHolder? + private val ICallbacks: ICallbacks?, + ICabHolder: ICabHolder? ) : AbsMultiSelectAdapter( - activity, cabHolder, R.menu.menu_media_selection + activity, ICabHolder, R.menu.menu_media_selection ), PopupTextProvider { init { @@ -136,8 +135,8 @@ class SongFileAdapter( } override fun onMultipleItemAction(menuItem: MenuItem, selection: List) { - if (callbacks == null) return - callbacks.onMultipleItemAction(menuItem, selection as ArrayList) + if (ICallbacks == null) return + ICallbacks.onMultipleItemAction(menuItem, selection as ArrayList) } override fun getPopupText(position: Int): String { @@ -148,15 +147,14 @@ class SongFileAdapter( return MusicUtil.getSectionName(dataSet[position].name) } - inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) { init { - if (menu != null && callbacks != null) { + if (menu != null && ICallbacks != null) { menu?.setOnClickListener { v -> val position = layoutPosition if (isPositionInRange(position)) { - callbacks.onFileMenuClicked(dataSet[position], v) + ICallbacks.onFileMenuClicked(dataSet[position], v) } } } @@ -171,7 +169,7 @@ class SongFileAdapter( if (isInQuickSelectMode) { toggleChecked(position) } else { - callbacks?.onFileSelected(dataSet[position]) + ICallbacks?.onFileSelected(dataSet[position]) } } } @@ -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/io/github/muntashirakon/music/adapter/TranslatorsAdapter.kt b/app/src/main/java/io/github/muntashirakon/music/adapter/TranslatorsAdapter.kt index be98c547d..e945007c4 100644 --- a/app/src/main/java/io/github/muntashirakon/music/adapter/TranslatorsAdapter.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/adapter/album/AlbumAdapter.kt b/app/src/main/java/io/github/muntashirakon/music/adapter/album/AlbumAdapter.kt index 0ad1834e4..663889468 100644 --- a/app/src/main/java/io/github/muntashirakon/music/adapter/album/AlbumAdapter.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.adapter.album import android.content.res.ColorStateList @@ -6,7 +20,23 @@ import android.view.LayoutInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup +import androidx.core.view.ViewCompat import androidx.fragment.app.FragmentActivity +import io.github.muntashirakon.music.R +import io.github.muntashirakon.music.adapter.base.AbsMultiSelectAdapter +import io.github.muntashirakon.music.adapter.base.MediaEntryViewHolder +import io.github.muntashirakon.music.glide.AlbumGlideRequest +import io.github.muntashirakon.music.glide.RetroMusicColoredTarget +import io.github.muntashirakon.music.helper.MusicPlayerRemote +import io.github.muntashirakon.music.helper.SortOrder +import io.github.muntashirakon.music.helper.menu.SongsMenuHelper +import io.github.muntashirakon.music.interfaces.IAlbumClickListener +import io.github.muntashirakon.music.interfaces.ICabHolder +import io.github.muntashirakon.music.model.Album +import io.github.muntashirakon.music.model.Song +import io.github.muntashirakon.music.util.MusicUtil +import io.github.muntashirakon.music.util.PreferenceUtil +import io.github.muntashirakon.music.util.color.MediaNotificationProcessor import com.bumptech.glide.Glide import io.github.muntashirakon.music.R import io.github.muntashirakon.music.adapter.base.AbsMultiSelectAdapter @@ -29,11 +59,11 @@ open class AlbumAdapter( protected val activity: FragmentActivity, var dataSet: List, protected var itemLayoutRes: Int, - cabHolder: CabHolder?, - private val albumClickListener: AlbumClickListener? + ICabHolder: ICabHolder?, + private val albumClickListener: IAlbumClickListener? ) : AbsMultiSelectAdapter( activity, - cabHolder, + ICabHolder, R.menu.menu_media_selection ), PopupTextProvider { @@ -75,7 +105,7 @@ open class AlbumAdapter( holder.title?.text = getAlbumTitle(album) holder.text?.text = getAlbumText(album) holder.playSongs?.setOnClickListener { - album.songs?.let { songs -> + album.songs.let { songs -> MusicPlayerRemote.openQueue( songs, 0, @@ -116,7 +146,7 @@ open class AlbumAdapter( } override fun getItemId(position: Int): Long { - return dataSet[position].id.toLong() + return dataSet[position].id } override fun getIdentifier(position: Int): Album? { @@ -128,7 +158,8 @@ open class AlbumAdapter( } override fun onMultipleItemAction( - menuItem: MenuItem, selection: List + menuItem: MenuItem, + selection: List ) { SongsMenuHelper.handleMenuClick(activity, getSongList(selection), menuItem.itemId) } @@ -161,7 +192,7 @@ open class AlbumAdapter( inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) { init { - setImageTransitionName(activity.getString(R.string.transition_album_art)) + setImageTransitionName("Album") menu?.visibility = View.GONE } @@ -170,6 +201,7 @@ open class AlbumAdapter( if (isInQuickSelectMode) { toggleChecked(layoutPosition) } else { + ViewCompat.setTransitionName(itemView, "album") albumClickListener?.onAlbumClick(dataSet[layoutPosition].id, itemView) } } diff --git a/app/src/main/java/io/github/muntashirakon/music/adapter/album/AlbumCoverPagerAdapter.kt b/app/src/main/java/io/github/muntashirakon/music/adapter/album/AlbumCoverPagerAdapter.kt index 64d1ca381..e3cbf2491 100644 --- a/app/src/main/java/io/github/muntashirakon/music/adapter/album/AlbumCoverPagerAdapter.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.adapter.album import android.os.Bundle @@ -5,6 +19,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageView +import androidx.core.view.ViewCompat import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.lifecycle.lifecycleScope @@ -88,9 +103,9 @@ class AlbumCoverPagerAdapter( savedInstanceState: Bundle? ): View? { val view = inflater.inflate(getLayoutWithPlayerTheme(), container, false) + ViewCompat.setTransitionName(view, "lyrics") albumCover = view.findViewById(R.id.player_image) - albumCover.setOnClickListener { - //LyricsDialog().show(childFragmentManager, "LyricsDialog") + view.setOnClickListener { showLyricsDialog() } return view @@ -98,14 +113,14 @@ class AlbumCoverPagerAdapter( private fun showLyricsDialog() { lifecycleScope.launch(Dispatchers.IO) { - val data: String = MusicUtil.getLyrics(song) ?: "No lyrics found" + val data: String? = MusicUtil.getLyrics(song) withContext(Dispatchers.Main) { MaterialAlertDialogBuilder( requireContext(), R.style.ThemeOverlay_MaterialComponents_Dialog_Alert ).apply { setTitle(song.title) - setMessage(data) + setMessage(if (data.isNullOrEmpty()) "No lyrics found" else data) setNegativeButton(R.string.synced_lyrics) { _, _ -> NavigationUtil.goToLyrics(requireActivity()) } @@ -197,4 +212,3 @@ class AlbumCoverPagerAdapter( val TAG: String = AlbumCoverPagerAdapter::class.java.simpleName } } - diff --git a/app/src/main/java/io/github/muntashirakon/music/adapter/album/HorizontalAlbumAdapter.kt b/app/src/main/java/io/github/muntashirakon/music/adapter/album/HorizontalAlbumAdapter.kt index 9d53628f5..fb86f9645 100644 --- a/app/src/main/java/io/github/muntashirakon/music/adapter/album/HorizontalAlbumAdapter.kt +++ b/app/src/main/java/io/github/muntashirakon/music/adapter/album/HorizontalAlbumAdapter.kt @@ -1,13 +1,27 @@ +/* + * 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 io.github.muntashirakon.music.adapter.album import android.view.View import android.view.ViewGroup import androidx.fragment.app.FragmentActivity -import io.github.muntashirakon.music.fragments.albums.AlbumClickListener import io.github.muntashirakon.music.glide.AlbumGlideRequest import io.github.muntashirakon.music.glide.RetroMusicColoredTarget import io.github.muntashirakon.music.helper.HorizontalAdapterHelper -import io.github.muntashirakon.music.interfaces.CabHolder +import io.github.muntashirakon.music.interfaces.IAlbumClickListener +import io.github.muntashirakon.music.interfaces.ICabHolder import io.github.muntashirakon.music.model.Album import io.github.muntashirakon.music.util.MusicUtil import io.github.muntashirakon.music.util.color.MediaNotificationProcessor @@ -16,10 +30,10 @@ import com.bumptech.glide.Glide class HorizontalAlbumAdapter( activity: FragmentActivity, dataSet: List, - cabHolder: CabHolder?, - albumClickListener: AlbumClickListener + ICabHolder: ICabHolder?, + albumClickListener: IAlbumClickListener ) : AlbumAdapter( - activity, dataSet, HorizontalAdapterHelper.LAYOUT_RES, cabHolder, albumClickListener + activity, dataSet, HorizontalAdapterHelper.LAYOUT_RES, ICabHolder, albumClickListener ) { override fun createViewHolder(view: View, viewType: Int): ViewHolder { @@ -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) { @@ -51,7 +65,7 @@ class HorizontalAlbumAdapter( } override fun getItemViewType(position: Int): Int { - return HorizontalAdapterHelper.getItemViewtype(position, itemCount) + return HorizontalAdapterHelper.getItemViewType(position, itemCount) } override fun getItemCount(): Int { diff --git a/app/src/main/java/io/github/muntashirakon/music/adapter/artist/ArtistAdapter.kt b/app/src/main/java/io/github/muntashirakon/music/adapter/artist/ArtistAdapter.kt index 54fd14e16..8e2d3821b 100644 --- a/app/src/main/java/io/github/muntashirakon/music/adapter/artist/ArtistAdapter.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.adapter.artist import android.content.res.ColorStateList @@ -8,31 +22,31 @@ import android.view.View import android.view.ViewGroup import androidx.core.view.ViewCompat import androidx.fragment.app.FragmentActivity -import com.bumptech.glide.Glide import io.github.muntashirakon.music.R import io.github.muntashirakon.music.adapter.base.AbsMultiSelectAdapter import io.github.muntashirakon.music.adapter.base.MediaEntryViewHolder import io.github.muntashirakon.music.extensions.hide -import io.github.muntashirakon.music.fragments.artists.ArtistClickListener import io.github.muntashirakon.music.glide.ArtistGlideRequest import io.github.muntashirakon.music.glide.RetroMusicColoredTarget import io.github.muntashirakon.music.helper.menu.SongsMenuHelper -import io.github.muntashirakon.music.interfaces.CabHolder +import io.github.muntashirakon.music.interfaces.IArtistClickListener +import io.github.muntashirakon.music.interfaces.ICabHolder import io.github.muntashirakon.music.model.Artist import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.util.MusicUtil import io.github.muntashirakon.music.util.color.MediaNotificationProcessor -import me.zhanghai.android.fastscroll.PopupTextProvider +import com.bumptech.glide.Glide import java.util.* +import me.zhanghai.android.fastscroll.PopupTextProvider class ArtistAdapter( val activity: FragmentActivity, var dataSet: List, var itemLayoutRes: Int, - cabHolder: CabHolder?, - private val artistClickListener: ArtistClickListener + val ICabHolder: ICabHolder?, + val IArtistClickListener: IArtistClickListener ) : AbsMultiSelectAdapter( - activity, cabHolder, R.menu.menu_media_selection + activity, ICabHolder, R.menu.menu_media_selection ), PopupTextProvider { init { @@ -45,7 +59,7 @@ class ArtistAdapter( } override fun getItemId(position: Int): Long { - return dataSet[position].id.toLong() + return dataSet[position].id } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { @@ -107,7 +121,8 @@ class ArtistAdapter( } override fun onMultipleItemAction( - menuItem: MenuItem, selection: List + menuItem: MenuItem, + selection: List ) { SongsMenuHelper.handleMenuClick(activity, getSongList(selection), menuItem.itemId) } @@ -140,11 +155,8 @@ class ArtistAdapter( toggleChecked(layoutPosition) } else { image?.let { - ViewCompat.setTransitionName( - it, - activity.getString(R.string.transition_artist_image) - ) - artistClickListener.onArtist(dataSet[layoutPosition].id, it) + ViewCompat.setTransitionName(itemView, "album") + IArtistClickListener.onArtist(dataSet[layoutPosition].id, itemView) } } } diff --git a/app/src/main/java/io/github/muntashirakon/music/adapter/base/AbsMultiSelectAdapter.java b/app/src/main/java/io/github/muntashirakon/music/adapter/base/AbsMultiSelectAdapter.java index 1cdb9a531..b07b76503 100644 --- a/app/src/main/java/io/github/muntashirakon/music/adapter/base/AbsMultiSelectAdapter.java +++ b/app/src/main/java/io/github/muntashirakon/music/adapter/base/AbsMultiSelectAdapter.java @@ -3,132 +3,127 @@ package io.github.muntashirakon.music.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 io.github.muntashirakon.music.R; +import io.github.muntashirakon.music.interfaces.ICabHolder; import com.afollestad.materialcab.MaterialCab; - import java.util.ArrayList; import java.util.List; -import io.github.muntashirakon.music.R; -import io.github.muntashirakon.music.interfaces.CabHolder; +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 CabHolder cabHolder; - 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 CabHolder cabHolder, @MenuRes int menuRes) { - this.cabHolder = cabHolder; - 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 (cabHolder != 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 (cabHolder != 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 (cabHolder != null) { - if (cab == null || !cab.isActive()) { - cab = cabHolder.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/io/github/muntashirakon/music/adapter/base/MediaEntryViewHolder.java b/app/src/main/java/io/github/muntashirakon/music/adapter/base/MediaEntryViewHolder.java index 8895d1e7d..6e978d4cd 100644 --- a/app/src/main/java/io/github/muntashirakon/music/adapter/base/MediaEntryViewHolder.java +++ b/app/src/main/java/io/github/muntashirakon/music/adapter/base/MediaEntryViewHolder.java @@ -16,126 +16,104 @@ package io.github.muntashirakon.music.adapter.base; import android.graphics.Color; import android.view.View; -import android.view.ViewGroup; 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 io.github.muntashirakon.music.R; import com.google.android.material.card.MaterialCardView; import com.h6ah4i.android.widget.advrecyclerview.utils.AbstractDraggableSwipeableItemViewHolder; -import io.github.muntashirakon.music.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 artistImage; + @Nullable public ImageView image; - @Nullable - public ImageView playerImage; + @Nullable public ImageView artistImage; - @Nullable - public ViewGroup imageContainer; + @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 time; + @Nullable public TextView text2; - @Nullable - public TextView title; + @Nullable public TextView time; - public MediaEntryViewHolder(@NonNull View itemView) { - super(itemView); - title = itemView.findViewById(R.id.title); - text = itemView.findViewById(R.id.text); + @Nullable public TextView title; - 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); + 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); - imageText = itemView.findViewById(R.id.imageText); - imageContainer = itemView.findViewById(R.id.imageContainer); - imageTextContainer = itemView.findViewById(R.id.imageTextContainer); - imageContainerCard = itemView.findViewById(R.id.imageContainerCard); + 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); - 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); + imageText = itemView.findViewById(R.id.imageText); + imageTextContainer = itemView.findViewById(R.id.imageTextContainer); + imageContainerCard = itemView.findViewById(R.id.imageContainerCard); - if (imageContainerCard != null) { - imageContainerCard.setCardBackgroundColor(Color.TRANSPARENT); - } - itemView.setOnClickListener(this); - itemView.setOnLongClickListener(this); + 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); + } - @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/io/github/muntashirakon/music/adapter/playlist/LegacyPlaylistAdapter.kt b/app/src/main/java/io/github/muntashirakon/music/adapter/playlist/LegacyPlaylistAdapter.kt index e6e04488d..ed416f038 100644 --- a/app/src/main/java/io/github/muntashirakon/music/adapter/playlist/LegacyPlaylistAdapter.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/adapter/playlist/PlaylistAdapter.kt b/app/src/main/java/io/github/muntashirakon/music/adapter/playlist/PlaylistAdapter.kt index 70f0ef9af..e7b23528c 100755 --- a/app/src/main/java/io/github/muntashirakon/music/adapter/playlist/PlaylistAdapter.kt +++ b/app/src/main/java/io/github/muntashirakon/music/adapter/playlist/PlaylistAdapter.kt @@ -1,21 +1,32 @@ +/* + * 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 io.github.muntashirakon.music.adapter.playlist -import android.graphics.Bitmap import android.graphics.Color import android.graphics.drawable.Drawable -import android.os.AsyncTask import android.text.TextUtils import android.view.LayoutInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup import androidx.appcompat.widget.PopupMenu -import androidx.core.os.bundleOf +import androidx.core.view.ViewCompat import androidx.fragment.app.FragmentActivity -import androidx.navigation.findNavController +import androidx.lifecycle.lifecycleScope import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.TintHelper -import io.github.muntashirakon.music.EXTRA_PLAYLIST import io.github.muntashirakon.music.R import io.github.muntashirakon.music.adapter.base.AbsMultiSelectAdapter import io.github.muntashirakon.music.adapter.base.MediaEntryViewHolder @@ -27,22 +38,25 @@ import io.github.muntashirakon.music.extensions.hide import io.github.muntashirakon.music.extensions.show import io.github.muntashirakon.music.helper.menu.PlaylistMenuHelper import io.github.muntashirakon.music.helper.menu.SongsMenuHelper -import io.github.muntashirakon.music.interfaces.CabHolder -import io.github.muntashirakon.music.model.Playlist +import io.github.muntashirakon.music.interfaces.ICabHolder +import io.github.muntashirakon.music.interfaces.IPlaylistClickListener import io.github.muntashirakon.music.model.Song -import io.github.muntashirakon.music.repository.PlaylistSongsLoader import io.github.muntashirakon.music.util.AutoGeneratedPlaylistBitmap import io.github.muntashirakon.music.util.MusicUtil -import io.github.muntashirakon.music.util.RetroColorUtil +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.Dispatchers.Main +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext class PlaylistAdapter( private val activity: FragmentActivity, - var dataSet: List, + private var dataSet: List, private var itemLayoutRes: Int, - cabHolder: CabHolder? + ICabHolder: ICabHolder?, + private val listener: IPlaylistClickListener ) : AbsMultiSelectAdapter( activity, - cabHolder, + ICabHolder, R.menu.menu_playlists_selection ) { @@ -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(activity, holder, playlist) } private fun getIconRes(): Drawable = TintHelper.createTintedDrawable( @@ -158,10 +172,8 @@ class PlaylistAdapter( if (isInQuickSelectMode) { toggleChecked(layoutPosition) } else { - activity.findNavController(R.id.fragment_container).navigate( - R.id.playlistDetailsFragment, - bundleOf(EXTRA_PLAYLIST to dataSet[layoutPosition]) - ) + ViewCompat.setTransitionName(itemView, "playlist") + listener.onPlaylistClick(dataSet[layoutPosition], itemView) } } @@ -171,28 +183,35 @@ class PlaylistAdapter( } } - class PlaylistBitmapLoader( - private var adapter: PlaylistAdapter, - private var viewHolder: ViewHolder, - private var playlist: Playlist - ) : AsyncTask() { + private fun playlistBitmapLoader( + activity: FragmentActivity, + viewHolder: ViewHolder, + playlist: PlaylistWithSongs + ) { - override fun doInBackground(vararg params: Void?): Bitmap { - val songs = PlaylistSongsLoader.getPlaylistSongList(adapter.activity, playlist) - return AutoGeneratedPlaylistBitmap.getBitmap(adapter.activity, songs, false, false) + activity.lifecycleScope.launch(IO) { + val songs = playlist.songs.toSongs() + val bitmap = AutoGeneratedPlaylistBitmap.getBitmap(activity, songs, false, false) + withContext(Main) { viewHolder.image?.setImageBitmap(bitmap) } } - override fun onPostExecute(result: Bitmap?) { - super.onPostExecute(result) - viewHolder.image?.setImageBitmap(result) - val color = RetroColorUtil.getColor( - RetroColorUtil.generatePalette( - result - ), - ATHUtil.resolveColor(adapter.activity, R.attr.colorSurface) - ) - viewHolder.paletteColorContainer?.setBackgroundColor(color) - } + /* + override fun doInBackground(vararg params: Void?): Bitmap { + val songs = playlist.songs.toSongs() + return AutoGeneratedPlaylistBitmap.getBitmap(activity, songs, false, false) + } + + override fun onPostExecute(result: Bitmap?) { + super.onPostExecute(result) + viewHolder.image?.setImageBitmap(result) + val color = RetroColorUtil.getColor( + RetroColorUtil.generatePalette( + result + ), + ATHUtil.resolveColor(activity, R.attr.colorSurface) + ) + viewHolder.paletteColorContainer?.setBackgroundColor(color) + }*/ } companion object { diff --git a/app/src/main/java/io/github/muntashirakon/music/adapter/song/AbsOffsetSongAdapter.kt b/app/src/main/java/io/github/muntashirakon/music/adapter/song/AbsOffsetSongAdapter.kt index 45193c4ba..3bb610aee 100644 --- a/app/src/main/java/io/github/muntashirakon/music/adapter/song/AbsOffsetSongAdapter.kt +++ b/app/src/main/java/io/github/muntashirakon/music/adapter/song/AbsOffsetSongAdapter.kt @@ -1,21 +1,35 @@ +/* + * 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 io.github.muntashirakon.music.adapter.song import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.annotation.LayoutRes -import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.FragmentActivity import io.github.muntashirakon.music.R import io.github.muntashirakon.music.helper.MusicPlayerRemote -import io.github.muntashirakon.music.interfaces.CabHolder +import io.github.muntashirakon.music.interfaces.ICabHolder import io.github.muntashirakon.music.model.Song abstract class AbsOffsetSongAdapter( - activity: AppCompatActivity, + activity: FragmentActivity, dataSet: MutableList, @LayoutRes itemLayoutRes: Int, - cabHolder: CabHolder? -) : SongAdapter(activity, dataSet, itemLayoutRes, cabHolder) { + ICabHolder: ICabHolder? +) : SongAdapter(activity, dataSet, itemLayoutRes, ICabHolder) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SongAdapter.ViewHolder { if (viewType == OFFSET_ITEM) { @@ -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/io/github/muntashirakon/music/adapter/song/OrderablePlaylistSongAdapter.kt b/app/src/main/java/io/github/muntashirakon/music/adapter/song/OrderablePlaylistSongAdapter.kt index 8876309f6..1b03e72ad 100644 --- a/app/src/main/java/io/github/muntashirakon/music/adapter/song/OrderablePlaylistSongAdapter.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.adapter.song import android.view.MenuItem @@ -9,7 +23,7 @@ import io.github.muntashirakon.music.db.PlaylistEntity import io.github.muntashirakon.music.db.toSongEntity import io.github.muntashirakon.music.db.toSongs import io.github.muntashirakon.music.dialogs.RemoveSongFromPlaylistDialog -import io.github.muntashirakon.music.interfaces.CabHolder +import io.github.muntashirakon.music.interfaces.ICabHolder import io.github.muntashirakon.music.model.PlaylistSong import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.util.ViewUtil @@ -23,13 +37,13 @@ class OrderablePlaylistSongAdapter( activity: FragmentActivity, dataSet: ArrayList, itemLayoutRes: Int, - cabHolder: CabHolder?, + ICabHolder: ICabHolder?, private val onMoveItemListener: OnMoveItemListener? ) : SongAdapter( activity, dataSet, itemLayoutRes, - cabHolder + ICabHolder ), DraggableItemAdapter { init { diff --git a/app/src/main/java/io/github/muntashirakon/music/adapter/song/PlayingQueueAdapter.kt b/app/src/main/java/io/github/muntashirakon/music/adapter/song/PlayingQueueAdapter.kt index 96a939710..adb8ed5ff 100644 --- a/app/src/main/java/io/github/muntashirakon/music/adapter/song/PlayingQueueAdapter.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.adapter.song import android.view.MenuItem @@ -5,6 +19,8 @@ import android.view.View import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.FragmentActivity import io.github.muntashirakon.music.R +import io.github.muntashirakon.music.glide.RetroMusicColoredTarget +import io.github.muntashirakon.music.glide.SongGlideRequest import io.github.muntashirakon.music.helper.MusicPlayerRemote import io.github.muntashirakon.music.helper.MusicPlayerRemote.isPlaying import io.github.muntashirakon.music.helper.MusicPlayerRemote.playNextSong @@ -12,6 +28,8 @@ import io.github.muntashirakon.music.helper.MusicPlayerRemote.removeFromQueue import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.util.MusicUtil import io.github.muntashirakon.music.util.ViewUtil +import io.github.muntashirakon.music.util.color.MediaNotificationProcessor +import com.bumptech.glide.Glide import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange import com.h6ah4i.android.widget.advrecyclerview.draggable.annotation.DraggableItemStateFlags @@ -41,8 +59,8 @@ class PlayingQueueAdapter( override fun onBindViewHolder(holder: SongAdapter.ViewHolder, position: Int) { super.onBindViewHolder(holder, position) - holder.imageText?.text = (position - current).toString() - holder.time?.text = MusicUtil.getReadableDurationString(dataSet[position].duration) + val song = dataSet[position] + holder.time?.text = MusicUtil.getReadableDurationString(song.duration) if (holder.itemViewType == HISTORY || holder.itemViewType == CURRENT) { setAlpha(holder, 0.5f) } @@ -58,7 +76,17 @@ class PlayingQueueAdapter( } override fun loadAlbumCover(song: Song, holder: SongAdapter.ViewHolder) { - // We don't want to load it in this adapter + if (holder.image == null) { + return + } + SongGlideRequest.Builder.from(Glide.with(activity), song) + .checkIgnoreMediaStore(activity) + .generatePalette(activity).build() + .into(object : RetroMusicColoredTarget(holder.image!!) { + override fun onColorReady(colors: MediaNotificationProcessor) { + //setColors(colors, holder) + } + }) } fun swapDataSet(dataSet: List, position: Int) { @@ -76,7 +104,6 @@ class PlayingQueueAdapter( holder.image?.alpha = alpha holder.title?.alpha = alpha holder.text?.alpha = alpha - holder.imageText?.alpha = alpha holder.paletteColorContainer?.alpha = alpha holder.dragView?.alpha = alpha holder.menu?.alpha = alpha @@ -129,7 +156,6 @@ class PlayingQueueAdapter( } init { - imageText?.visibility = View.VISIBLE dragView?.visibility = View.VISIBLE } @@ -173,7 +199,7 @@ class PlayingQueueAdapter( } override fun onGetSwipeReactionType(holder: ViewHolder, position: Int, x: Int, y: Int): Int { - return if (onCheckCanStartDrag(holder!!, position, x, y)) { + return if (onCheckCanStartDrag(holder, position, x, y)) { SwipeableItemConstants.REACTION_CAN_NOT_SWIPE_BOTH_H } else { SwipeableItemConstants.REACTION_CAN_SWIPE_BOTH_H @@ -196,17 +222,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/io/github/muntashirakon/music/adapter/song/PlaylistSongAdapter.kt b/app/src/main/java/io/github/muntashirakon/music/adapter/song/PlaylistSongAdapter.kt index 4a73c7441..05f941dbd 100644 --- a/app/src/main/java/io/github/muntashirakon/music/adapter/song/PlaylistSongAdapter.kt +++ b/app/src/main/java/io/github/muntashirakon/music/adapter/song/PlaylistSongAdapter.kt @@ -1,23 +1,37 @@ +/* + * 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 io.github.muntashirakon.music.adapter.song import android.view.MenuItem import android.view.View -import androidx.appcompat.app.AppCompatActivity -import androidx.core.os.bundleOf -import androidx.navigation.findNavController -import com.google.android.material.button.MaterialButton -import io.github.muntashirakon.music.EXTRA_ALBUM_ID +import androidx.fragment.app.FragmentActivity import io.github.muntashirakon.music.R -import io.github.muntashirakon.music.helper.MusicPlayerRemote -import io.github.muntashirakon.music.interfaces.CabHolder +import com.google.android.material.button.MaterialButton +import io.github.muntashirakon.music.db.PlaylistEntity +import io.github.muntashirakon.music.db.toSongEntity +import io.github.muntashirakon.music.dialogs.RemoveSongFromPlaylistDialog +import io.github.muntashirakon.music.interfaces.ICabHolder import io.github.muntashirakon.music.model.Song open class PlaylistSongAdapter( - activity: AppCompatActivity, + private val playlist: PlaylistEntity, + activity: FragmentActivity, dataSet: MutableList, itemLayoutRes: Int, - cabHolder: CabHolder? -) : AbsOffsetSongAdapter(activity, dataSet, itemLayoutRes, cabHolder) { + ICabHolder: ICabHolder? +) : SongAdapter(activity, dataSet, itemLayoutRes, ICabHolder) { init { this.setMultiSelectMenuRes(R.menu.menu_cannot_delete_single_songs_playlist_songs_selection) @@ -27,45 +41,23 @@ open class PlaylistSongAdapter( return ViewHolder(view) } - override fun onBindViewHolder(holder: SongAdapter.ViewHolder, position: Int) { - if (holder.itemViewType == OFFSET_ITEM) { - val viewHolder = holder as ViewHolder - viewHolder.playAction?.let { - it.setOnClickListener { - MusicPlayerRemote.openQueue(dataSet, 0, true) - } - } - viewHolder.shuffleAction?.let { - it.setOnClickListener { - MusicPlayerRemote.openAndShuffleQueue(dataSet, true) - } - } - } else { - super.onBindViewHolder(holder, position - 1) - } - } - - open inner class ViewHolder(itemView: View) : AbsOffsetSongAdapter.ViewHolder(itemView) { - - val playAction: MaterialButton? = itemView.findViewById(R.id.playAction) - val shuffleAction: MaterialButton? = itemView.findViewById(R.id.shuffleAction) + open inner class ViewHolder(itemView: View) : SongAdapter.ViewHolder(itemView) { override var songMenuRes: Int - get() = R.menu.menu_item_cannot_delete_single_songs_playlist_song + get() = R.menu.menu_item_playlist_song set(value) { super.songMenuRes = value } override fun onSongMenuItemClick(item: MenuItem): Boolean { - if (item.itemId == R.id.action_go_to_album) { - activity.findNavController(R.id.fragment_container) - .navigate( - R.id.albumDetailsFragment, - bundleOf(EXTRA_ALBUM_ID to song.albumId) - ) - return true + when (item.itemId) { + R.id.action_remove_from_playlist -> { + RemoveSongFromPlaylistDialog.create(song.toSongEntity(playlist.playListId)) + .show(activity.supportFragmentManager, "REMOVE_FROM_PLAYLIST") + return true + } } return super.onSongMenuItemClick(item) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/muntashirakon/music/adapter/song/ShuffleButtonSongAdapter.kt b/app/src/main/java/io/github/muntashirakon/music/adapter/song/ShuffleButtonSongAdapter.kt index a346fdcf5..3dcdfcffc 100644 --- a/app/src/main/java/io/github/muntashirakon/music/adapter/song/ShuffleButtonSongAdapter.kt +++ b/app/src/main/java/io/github/muntashirakon/music/adapter/song/ShuffleButtonSongAdapter.kt @@ -1,10 +1,24 @@ +/* + * 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 io.github.muntashirakon.music.adapter.song import android.view.View import androidx.appcompat.app.AppCompatActivity import io.github.muntashirakon.music.R import io.github.muntashirakon.music.helper.MusicPlayerRemote -import io.github.muntashirakon.music.interfaces.CabHolder +import io.github.muntashirakon.music.interfaces.ICabHolder import io.github.muntashirakon.music.model.Song import com.google.android.material.button.MaterialButton @@ -12,8 +26,8 @@ class ShuffleButtonSongAdapter( activity: AppCompatActivity, dataSet: MutableList, itemLayoutRes: Int, - cabHolder: CabHolder? -) : AbsOffsetSongAdapter(activity, dataSet, itemLayoutRes, cabHolder) { + ICabHolder: ICabHolder? +) : AbsOffsetSongAdapter(activity, dataSet, itemLayoutRes, ICabHolder) { override fun createViewHolder(view: View): SongAdapter.ViewHolder { return ViewHolder(view) @@ -49,4 +63,4 @@ class ShuffleButtonSongAdapter( super.onClick(v) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/muntashirakon/music/adapter/song/SimpleSongAdapter.kt b/app/src/main/java/io/github/muntashirakon/music/adapter/song/SimpleSongAdapter.kt index 7c7a8cbd5..bf566dacd 100755 --- a/app/src/main/java/io/github/muntashirakon/music/adapter/song/SimpleSongAdapter.kt +++ b/app/src/main/java/io/github/muntashirakon/music/adapter/song/SimpleSongAdapter.kt @@ -1,9 +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 io.github.muntashirakon.music.adapter.song import android.view.LayoutInflater import android.view.ViewGroup import androidx.fragment.app.FragmentActivity -import io.github.muntashirakon.music.interfaces.CabHolder +import io.github.muntashirakon.music.interfaces.ICabHolder import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.util.MusicUtil import java.util.* @@ -12,8 +26,8 @@ class SimpleSongAdapter( context: FragmentActivity, songs: ArrayList, layoutRes: Int, - cabHolder: CabHolder? -) : SongAdapter(context, songs, layoutRes, cabHolder) { + ICabHolder: ICabHolder? +) : SongAdapter(context, songs, layoutRes, ICabHolder) { override fun swapDataSet(dataSet: List) { this.dataSet = dataSet.toMutableList() diff --git a/app/src/main/java/io/github/muntashirakon/music/adapter/song/SongAdapter.kt b/app/src/main/java/io/github/muntashirakon/music/adapter/song/SongAdapter.kt index 4af8e3f85..57ede69f2 100644 --- a/app/src/main/java/io/github/muntashirakon/music/adapter/song/SongAdapter.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.adapter.song import android.content.res.ColorStateList @@ -7,10 +21,9 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import androidx.core.os.bundleOf +import androidx.core.view.isVisible import androidx.fragment.app.FragmentActivity import androidx.navigation.findNavController -import com.afollestad.materialcab.MaterialCab -import com.bumptech.glide.Glide import io.github.muntashirakon.music.EXTRA_ALBUM_ID import io.github.muntashirakon.music.R import io.github.muntashirakon.music.adapter.base.AbsMultiSelectAdapter @@ -23,11 +36,13 @@ import io.github.muntashirakon.music.helper.MusicPlayerRemote import io.github.muntashirakon.music.helper.SortOrder import io.github.muntashirakon.music.helper.menu.SongMenuHelper import io.github.muntashirakon.music.helper.menu.SongsMenuHelper -import io.github.muntashirakon.music.interfaces.CabHolder +import io.github.muntashirakon.music.interfaces.ICabHolder import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.util.MusicUtil import io.github.muntashirakon.music.util.PreferenceUtil import io.github.muntashirakon.music.util.color.MediaNotificationProcessor +import com.afollestad.materialcab.MaterialCab +import com.bumptech.glide.Glide import me.zhanghai.android.fastscroll.PopupTextProvider /** @@ -38,11 +53,11 @@ open class SongAdapter( protected val activity: FragmentActivity, var dataSet: MutableList, protected var itemLayoutRes: Int, - cabHolder: CabHolder?, + ICabHolder: ICabHolder?, showSectionName: Boolean = true ) : AbsMultiSelectAdapter( activity, - cabHolder, + ICabHolder, R.menu.menu_media_selection ), MaterialCab.Callback, PopupTextProvider { @@ -59,7 +74,7 @@ open class SongAdapter( } override fun getItemId(position: Int): Long { - return dataSet[position].id.toLong() + return dataSet[position].id } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { @@ -87,6 +102,7 @@ open class SongAdapter( } holder.title?.text = getSongTitle(song) holder.text?.text = getSongText(song) + holder.text2?.text = getSongText(song) loadAlbumCover(song, holder) } @@ -121,6 +137,10 @@ open class SongAdapter( return song.artistName } + private fun getSongText2(song: Song): String? { + return song.albumName + } + override fun getItemCount(): Int { return dataSet.size } @@ -157,7 +177,6 @@ open class SongAdapter( get() = dataSet[layoutPosition] init { - setImageTransitionName(activity.getString(R.string.transition_album_art)) menu?.setOnClickListener(object : SongMenuHelper.OnClickSongMenu(activity) { override val song: Song get() = this@ViewHolder.song @@ -172,7 +191,7 @@ open class SongAdapter( } protected open fun onSongMenuItemClick(item: MenuItem): Boolean { - if (image != null && image!!.visibility == View.VISIBLE) { + if (image != null && image!!.isVisible) { when (item.itemId) { R.id.action_go_to_album -> { activity.findNavController(R.id.fragment_container) diff --git a/app/src/main/java/io/github/muntashirakon/music/appshortcuts/AppShortcutIconGenerator.kt b/app/src/main/java/io/github/muntashirakon/music/appshortcuts/AppShortcutIconGenerator.kt index 106d37337..1628aca97 100644 --- a/app/src/main/java/io/github/muntashirakon/music/appshortcuts/AppShortcutIconGenerator.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/appshortcuts/AppShortcutLauncherActivity.kt b/app/src/main/java/io/github/muntashirakon/music/appshortcuts/AppShortcutLauncherActivity.kt index 185db958b..a569b6b25 100644 --- a/app/src/main/java/io/github/muntashirakon/music/appshortcuts/AppShortcutLauncherActivity.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.appshortcuts import android.app.Activity @@ -20,6 +20,7 @@ import android.os.Bundle import io.github.muntashirakon.music.appshortcuts.shortcuttype.LastAddedShortcutType import io.github.muntashirakon.music.appshortcuts.shortcuttype.ShuffleAllShortcutType import io.github.muntashirakon.music.appshortcuts.shortcuttype.TopTracksShortcutType +import io.github.muntashirakon.music.extensions.extraNotNull import io.github.muntashirakon.music.model.Playlist import io.github.muntashirakon.music.model.smartplaylist.LastAddedPlaylist import io.github.muntashirakon.music.model.smartplaylist.ShuffleAllPlaylist @@ -31,16 +32,7 @@ class AppShortcutLauncherActivity : Activity() { public override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - - var shortcutType = SHORTCUT_TYPE_NONE - - // Set shortcutType from the intent extras - val extras = intent.extras - if (extras != null) { - shortcutType = extras.getInt(KEY_SHORTCUT_TYPE, SHORTCUT_TYPE_NONE) - } - - when (shortcutType) { + when (extraNotNull(KEY_SHORTCUT_TYPE, SHORTCUT_TYPE_NONE).value) { SHORTCUT_TYPE_SHUFFLE_ALL -> { startServiceWithPlaylist( SHUFFLE_MODE_SHUFFLE, ShuffleAllPlaylist() @@ -78,9 +70,9 @@ class AppShortcutLauncherActivity : Activity() { companion object { const val KEY_SHORTCUT_TYPE = "io.github.muntashirakon.Music.appshortcuts.ShortcutType" - const val SHORTCUT_TYPE_SHUFFLE_ALL = 0 - const val SHORTCUT_TYPE_TOP_TRACKS = 1 - const val SHORTCUT_TYPE_LAST_ADDED = 2 - const val SHORTCUT_TYPE_NONE = 4 + const val SHORTCUT_TYPE_SHUFFLE_ALL = 0L + const val SHORTCUT_TYPE_TOP_TRACKS = 1L + const val SHORTCUT_TYPE_LAST_ADDED = 2L + const val SHORTCUT_TYPE_NONE = 4L } } diff --git a/app/src/main/java/io/github/muntashirakon/music/appshortcuts/DynamicShortcutManager.kt b/app/src/main/java/io/github/muntashirakon/music/appshortcuts/DynamicShortcutManager.kt index 5c7b7b571..fd2fbe5b3 100644 --- a/app/src/main/java/io/github/muntashirakon/music/appshortcuts/DynamicShortcutManager.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.appshortcuts import android.annotation.TargetApi @@ -32,16 +32,16 @@ class DynamicShortcutManager(private val context: Context) { this.context.getSystemService(ShortcutManager::class.java) private val defaultShortcuts: List - get() = Arrays.asList( + get() = listOf( ShuffleAllShortcutType(context).shortcutInfo, TopTracksShortcutType(context).shortcutInfo, LastAddedShortcutType(context).shortcutInfo ) fun initDynamicShortcuts() { - //if (shortcutManager.dynamicShortcuts.size == 0) { + // if (shortcutManager.dynamicShortcuts.size == 0) { shortcutManager.dynamicShortcuts = defaultShortcuts - //} + // } } fun updateDynamicShortcuts() { @@ -58,8 +58,12 @@ class DynamicShortcutManager(private val context: Context) { icon: Icon, intent: Intent ): ShortcutInfo { - return ShortcutInfo.Builder(context, id).setShortLabel(shortLabel) - .setLongLabel(longLabel).setIcon(icon).setIntent(intent).build() + return ShortcutInfo.Builder(context, id) + .setShortLabel(shortLabel) + .setLongLabel(longLabel) + .setIcon(icon) + .setIntent(intent) + .build() } fun reportShortcutUsed(context: Context, shortcutId: String) { diff --git a/app/src/main/java/io/github/muntashirakon/music/appshortcuts/shortcuttype/BaseShortcutType.kt b/app/src/main/java/io/github/muntashirakon/music/appshortcuts/shortcuttype/BaseShortcutType.kt index 5bf3d0ff7..99fecc088 100644 --- a/app/src/main/java/io/github/muntashirakon/music/appshortcuts/shortcuttype/BaseShortcutType.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.appshortcuts.shortcuttype import android.annotation.TargetApi @@ -33,11 +33,11 @@ abstract class BaseShortcutType(internal var context: Context) { * @param shortcutType Describes the type of shortcut to create (ShuffleAll, TopTracks, custom playlist, etc.) * @return */ - internal fun getPlaySongsIntent(shortcutType: Int): Intent { + internal fun getPlaySongsIntent(shortcutType: Long): Intent { val intent = Intent(context, AppShortcutLauncherActivity::class.java) intent.action = Intent.ACTION_VIEW val b = Bundle() - b.putInt(AppShortcutLauncherActivity.KEY_SHORTCUT_TYPE, shortcutType) + b.putLong(AppShortcutLauncherActivity.KEY_SHORTCUT_TYPE, shortcutType) intent.putExtras(b) return intent } diff --git a/app/src/main/java/io/github/muntashirakon/music/appshortcuts/shortcuttype/LastAddedShortcutType.kt b/app/src/main/java/io/github/muntashirakon/music/appshortcuts/shortcuttype/LastAddedShortcutType.kt index 8ebfd1a8e..6f21171e0 100644 --- a/app/src/main/java/io/github/muntashirakon/music/appshortcuts/shortcuttype/LastAddedShortcutType.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.appshortcuts.shortcuttype import android.annotation.TargetApi diff --git a/app/src/main/java/io/github/muntashirakon/music/appshortcuts/shortcuttype/ShuffleAllShortcutType.kt b/app/src/main/java/io/github/muntashirakon/music/appshortcuts/shortcuttype/ShuffleAllShortcutType.kt index 640c9959f..9aad0b655 100644 --- a/app/src/main/java/io/github/muntashirakon/music/appshortcuts/shortcuttype/ShuffleAllShortcutType.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.appshortcuts.shortcuttype import android.annotation.TargetApi @@ -26,20 +26,16 @@ import io.github.muntashirakon.music.appshortcuts.AppShortcutLauncherActivity class ShuffleAllShortcutType(context: Context) : BaseShortcutType(context) { override val shortcutInfo: ShortcutInfo - get() = ShortcutInfo.Builder( - context, id - ).setShortLabel(context.getString(R.string.app_shortcut_shuffle_all_short)).setLongLabel( - context.getString(R.string.app_shortcut_shuffle_all_long) - ).setIcon( - AppShortcutIconGenerator.generateThemedIcon( - context, R.drawable.ic_app_shortcut_shuffle_all - ) - ).setIntent(getPlaySongsIntent(AppShortcutLauncherActivity.SHORTCUT_TYPE_SHUFFLE_ALL)) + get() = ShortcutInfo.Builder(context, id) + .setShortLabel(context.getString(R.string.app_shortcut_shuffle_all_short)) + .setLongLabel(context.getString(R.string.app_shortcut_shuffle_all_long)) + .setIcon(AppShortcutIconGenerator.generateThemedIcon(context, R.drawable.ic_app_shortcut_shuffle_all)) + .setIntent(getPlaySongsIntent(AppShortcutLauncherActivity.SHORTCUT_TYPE_SHUFFLE_ALL)) .build() companion object { val id: String - get() = BaseShortcutType.ID_PREFIX + "shuffle_all" + get() = ID_PREFIX + "shuffle_all" } } diff --git a/app/src/main/java/io/github/muntashirakon/music/appshortcuts/shortcuttype/TopTracksShortcutType.kt b/app/src/main/java/io/github/muntashirakon/music/appshortcuts/shortcuttype/TopTracksShortcutType.kt index 78ee8c352..47d5e1811 100644 --- a/app/src/main/java/io/github/muntashirakon/music/appshortcuts/shortcuttype/TopTracksShortcutType.kt +++ b/app/src/main/java/io/github/muntashirakon/music/appshortcuts/shortcuttype/TopTracksShortcutType.kt @@ -1,17 +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 io.github.muntashirakon.music.appshortcuts.shortcuttype import android.annotation.TargetApi diff --git a/app/src/main/java/io/github/muntashirakon/music/appwidgets/AppWidgetBig.kt b/app/src/main/java/io/github/muntashirakon/music/appwidgets/AppWidgetBig.kt index 0d2995b4f..a7c2bf7ab 100644 --- a/app/src/main/java/io/github/muntashirakon/music/appwidgets/AppWidgetBig.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.appwidgets import android.app.PendingIntent @@ -30,6 +30,7 @@ import io.github.muntashirakon.music.appwidgets.base.BaseAppWidget import io.github.muntashirakon.music.glide.SongGlideRequest import io.github.muntashirakon.music.service.MusicService import io.github.muntashirakon.music.service.MusicService.* +import io.github.muntashirakon.music.util.PreferenceUtil import io.github.muntashirakon.music.util.RetroUtil import com.bumptech.glide.Glide import com.bumptech.glide.request.animation.GlideAnimation @@ -193,8 +194,11 @@ class AppWidgetBig : BaseAppWidget() { * Link up various button actions using [PendingIntent]. */ private fun linkButtons(context: Context, views: RemoteViews) { - val action = - Intent(context, MainActivity::class.java).putExtra(MainActivity.EXPAND_PANEL, true) + val action = Intent(context, MainActivity::class.java) + .putExtra( + MainActivity.EXPAND_PANEL, + PreferenceUtil.isExpandPanel + ) var pendingIntent: PendingIntent val serviceName = ComponentName(context, MusicService::class.java) @@ -229,6 +233,5 @@ class AppWidgetBig : BaseAppWidget() { } return mInstance!! } - } } diff --git a/app/src/main/java/io/github/muntashirakon/music/appwidgets/AppWidgetCard.kt b/app/src/main/java/io/github/muntashirakon/music/appwidgets/AppWidgetCard.kt index 056da1874..f1995e50b 100644 --- a/app/src/main/java/io/github/muntashirakon/music/appwidgets/AppWidgetCard.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.appwidgets import android.app.PendingIntent @@ -32,6 +32,7 @@ import io.github.muntashirakon.music.glide.palette.BitmapPaletteWrapper import io.github.muntashirakon.music.service.MusicService import io.github.muntashirakon.music.service.MusicService.* import io.github.muntashirakon.music.util.ImageUtil +import io.github.muntashirakon.music.util.PreferenceUtil import io.github.muntashirakon.music.util.RetroUtil import com.bumptech.glide.Glide import com.bumptech.glide.request.animation.GlideAnimation @@ -217,7 +218,11 @@ class AppWidgetCard : BaseAppWidget() { * Link up various button actions using [PendingIntent]. */ private fun linkButtons(context: Context, views: RemoteViews) { - val action: Intent = Intent(context, MainActivity::class.java).putExtra("expand", true) + val action = Intent(context, MainActivity::class.java) + .putExtra( + MainActivity.EXPAND_PANEL, + PreferenceUtil.isExpandPanel + ) var pendingIntent: PendingIntent val serviceName = ComponentName(context, MusicService::class.java) diff --git a/app/src/main/java/io/github/muntashirakon/music/appwidgets/AppWidgetClassic.kt b/app/src/main/java/io/github/muntashirakon/music/appwidgets/AppWidgetClassic.kt index 9012c7be9..b04587ef3 100644 --- a/app/src/main/java/io/github/muntashirakon/music/appwidgets/AppWidgetClassic.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.appwidgets import android.app.PendingIntent @@ -33,6 +33,7 @@ import io.github.muntashirakon.music.glide.palette.BitmapPaletteWrapper import io.github.muntashirakon.music.service.MusicService import io.github.muntashirakon.music.service.MusicService.* import io.github.muntashirakon.music.util.ImageUtil +import io.github.muntashirakon.music.util.PreferenceUtil import io.github.muntashirakon.music.util.RetroUtil import com.bumptech.glide.Glide import com.bumptech.glide.request.animation.GlideAnimation @@ -49,7 +50,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( @@ -208,7 +208,11 @@ class AppWidgetClassic : BaseAppWidget() { * Link up various button actions using [PendingIntent]. */ private fun linkButtons(context: Context, views: RemoteViews) { - val action = Intent(context, MainActivity::class.java).putExtra("expand", true) + val action = Intent(context, MainActivity::class.java) + .putExtra( + MainActivity.EXPAND_PANEL, + PreferenceUtil.isExpandPanel + ) var pendingIntent: PendingIntent val serviceName = ComponentName(context, MusicService::class.java) diff --git a/app/src/main/java/io/github/muntashirakon/music/appwidgets/AppWidgetSmall.kt b/app/src/main/java/io/github/muntashirakon/music/appwidgets/AppWidgetSmall.kt index 848d3cbdc..28bb9b228 100644 --- a/app/src/main/java/io/github/muntashirakon/music/appwidgets/AppWidgetSmall.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.appwidgets import android.app.PendingIntent @@ -31,6 +31,7 @@ import io.github.muntashirakon.music.glide.SongGlideRequest import io.github.muntashirakon.music.glide.palette.BitmapPaletteWrapper import io.github.muntashirakon.music.service.MusicService import io.github.muntashirakon.music.service.MusicService.* +import io.github.muntashirakon.music.util.PreferenceUtil import io.github.muntashirakon.music.util.RetroUtil import com.bumptech.glide.Glide import com.bumptech.glide.request.animation.GlideAnimation @@ -192,7 +193,11 @@ class AppWidgetSmall : BaseAppWidget() { * Link up various button actions using [PendingIntent]. */ private fun linkButtons(context: Context, views: RemoteViews) { - val action = Intent(context, MainActivity::class.java).putExtra("expand", true) + val action = Intent(context, MainActivity::class.java) + .putExtra( + MainActivity.EXPAND_PANEL, + PreferenceUtil.isExpandPanel + ) var pendingIntent: PendingIntent val serviceName = ComponentName(context, MusicService::class.java) diff --git a/app/src/main/java/io/github/muntashirakon/music/appwidgets/AppWidgetText.kt b/app/src/main/java/io/github/muntashirakon/music/appwidgets/AppWidgetText.kt index db28445e8..cdd5b130b 100644 --- a/app/src/main/java/io/github/muntashirakon/music/appwidgets/AppWidgetText.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.appwidgets import android.app.PendingIntent @@ -28,6 +28,7 @@ import io.github.muntashirakon.music.activities.MainActivity import io.github.muntashirakon.music.appwidgets.base.BaseAppWidget import io.github.muntashirakon.music.service.MusicService import io.github.muntashirakon.music.service.MusicService.* +import io.github.muntashirakon.music.util.PreferenceUtil import io.github.muntashirakon.music.util.RetroUtil class AppWidgetText : BaseAppWidget() { @@ -77,7 +78,11 @@ class AppWidgetText : BaseAppWidget() { * Link up various button actions using [PendingIntent]. */ private fun linkButtons(context: Context, views: RemoteViews) { - val action = Intent(context, MainActivity::class.java).putExtra("expand", true) + val action = Intent(context, MainActivity::class.java) + .putExtra( + MainActivity.EXPAND_PANEL, + PreferenceUtil.isExpandPanel + ) var pendingIntent: PendingIntent val serviceName = ComponentName(context, MusicService::class.java) @@ -153,10 +158,7 @@ class AppWidgetText : BaseAppWidget() { ) ) - - pushUpdate(service.applicationContext, appWidgetIds, appWidgetView) - } companion object { diff --git a/app/src/main/java/io/github/muntashirakon/music/appwidgets/BootReceiver.kt b/app/src/main/java/io/github/muntashirakon/music/appwidgets/BootReceiver.kt index bac3e580c..adae340c8 100644 --- a/app/src/main/java/io/github/muntashirakon/music/appwidgets/BootReceiver.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.appwidgets import android.appwidget.AppWidgetManager diff --git a/app/src/main/java/io/github/muntashirakon/music/appwidgets/base/BaseAppWidget.kt b/app/src/main/java/io/github/muntashirakon/music/appwidgets/base/BaseAppWidget.kt index a3bf6ff78..fd2bc533e 100644 --- a/app/src/main/java/io/github/muntashirakon/music/appwidgets/base/BaseAppWidget.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/db/BlackListStoreDao.kt b/app/src/main/java/io/github/muntashirakon/music/db/BlackListStoreDao.kt index b0efb794f..ac72f0e76 100644 --- a/app/src/main/java/io/github/muntashirakon/music/db/BlackListStoreDao.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/db/BlackListStoreEntity.kt b/app/src/main/java/io/github/muntashirakon/music/db/BlackListStoreEntity.kt index ef745e5ce..4e7cf10bc 100644 --- a/app/src/main/java/io/github/muntashirakon/music/db/BlackListStoreEntity.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/db/HistoryDao.kt b/app/src/main/java/io/github/muntashirakon/music/db/HistoryDao.kt index f472f14b1..94dbe77ba 100644 --- a/app/src/main/java/io/github/muntashirakon/music/db/HistoryDao.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/db/HistoryEntity.kt b/app/src/main/java/io/github/muntashirakon/music/db/HistoryEntity.kt index 8f1b0ec96..4458577b7 100644 --- a/app/src/main/java/io/github/muntashirakon/music/db/HistoryEntity.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/db/LyricsDao.kt b/app/src/main/java/io/github/muntashirakon/music/db/LyricsDao.kt index ac922fe46..cd50b2e77 100644 --- a/app/src/main/java/io/github/muntashirakon/music/db/LyricsDao.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/db/LyricsEntity.kt b/app/src/main/java/io/github/muntashirakon/music/db/LyricsEntity.kt index e244dbdb5..8eab4d65a 100644 --- a/app/src/main/java/io/github/muntashirakon/music/db/LyricsEntity.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/db/PlayCountDao.kt b/app/src/main/java/io/github/muntashirakon/music/db/PlayCountDao.kt index facd5fa29..de621adb7 100644 --- a/app/src/main/java/io/github/muntashirakon/music/db/PlayCountDao.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/db/PlayCountEntity.kt b/app/src/main/java/io/github/muntashirakon/music/db/PlayCountEntity.kt index 13768902a..fb552311b 100644 --- a/app/src/main/java/io/github/muntashirakon/music/db/PlayCountEntity.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/db/PlaylistDao.kt b/app/src/main/java/io/github/muntashirakon/music/db/PlaylistDao.kt index a90ba8664..6b24a95ee 100644 --- a/app/src/main/java/io/github/muntashirakon/music/db/PlaylistDao.kt +++ b/app/src/main/java/io/github/muntashirakon/music/db/PlaylistDao.kt @@ -1,10 +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 io.github.muntashirakon.music.db import androidx.lifecycle.LiveData import androidx.room.* -import io.github.muntashirakon.music.db.PlaylistEntity -import io.github.muntashirakon.music.db.PlaylistWithSongs -import io.github.muntashirakon.music.db.SongEntity @Dao interface PlaylistDao { @@ -48,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/io/github/muntashirakon/music/db/PlaylistEntity.kt b/app/src/main/java/io/github/muntashirakon/music/db/PlaylistEntity.kt index bb5954973..2ef3e4e77 100644 --- a/app/src/main/java/io/github/muntashirakon/music/db/PlaylistEntity.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/db/PlaylistWithSongs.kt b/app/src/main/java/io/github/muntashirakon/music/db/PlaylistWithSongs.kt index dff53e10c..4a27afe01 100644 --- a/app/src/main/java/io/github/muntashirakon/music/db/PlaylistWithSongs.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.db import android.os.Parcelable @@ -14,4 +28,3 @@ data class PlaylistWithSongs( ) val songs: List ) : Parcelable - diff --git a/app/src/main/java/io/github/muntashirakon/music/db/RetroDatabase.kt b/app/src/main/java/io/github/muntashirakon/music/db/RetroDatabase.kt index 3ec860ea1..aece1301f 100644 --- a/app/src/main/java/io/github/muntashirakon/music/db/RetroDatabase.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/db/SongEntity.kt b/app/src/main/java/io/github/muntashirakon/music/db/SongEntity.kt index d55237830..bd7f49a64 100644 --- a/app/src/main/java/io/github/muntashirakon/music/db/SongEntity.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/db/SongExtension.kt b/app/src/main/java/io/github/muntashirakon/music/db/SongExtension.kt index c59990470..2f97c92da 100644 --- a/app/src/main/java/io/github/muntashirakon/music/db/SongExtension.kt +++ b/app/src/main/java/io/github/muntashirakon/music/db/SongExtension.kt @@ -1,7 +1,27 @@ +/* + * 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 io.github.muntashirakon.music.db import io.github.muntashirakon.music.model.Song +fun List.fromHistoryToSongs(): List { + return map { + it.toSong() + } +} + fun List.toSongs(): List { return map { it.toSong() diff --git a/app/src/main/java/io/github/muntashirakon/music/dialogs/AddToPlaylistDialog.kt b/app/src/main/java/io/github/muntashirakon/music/dialogs/AddToPlaylistDialog.kt index b76d4b186..cff05c8a6 100644 --- a/app/src/main/java/io/github/muntashirakon/music/dialogs/AddToPlaylistDialog.kt +++ b/app/src/main/java/io/github/muntashirakon/music/dialogs/AddToPlaylistDialog.kt @@ -1,7 +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 io.github.muntashirakon.music.dialogs import android.app.Dialog import android.os.Bundle +import android.widget.ArrayAdapter import androidx.core.os.bundleOf import androidx.fragment.app.DialogFragment import androidx.lifecycle.lifecycleScope @@ -9,7 +24,6 @@ import io.github.muntashirakon.music.EXTRA_PLAYLISTS import io.github.muntashirakon.music.EXTRA_SONG import io.github.muntashirakon.music.R import io.github.muntashirakon.music.db.PlaylistEntity -import io.github.muntashirakon.music.db.SongEntity import io.github.muntashirakon.music.db.toSongsEntity import io.github.muntashirakon.music.extensions.colorButtons import io.github.muntashirakon.music.extensions.extraNotNull @@ -41,30 +55,39 @@ class AddToPlaylistDialog : DialogFragment() { } } + private fun playlistAdapter(playlists: List): ArrayAdapter { + val adapter = ArrayAdapter(requireContext(), R.layout.item_simple_text, R.id.title) + adapter.addAll(playlists) + return adapter + } + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val playlistEntities: List = - extraNotNull>(EXTRA_PLAYLISTS).value - val songs: List = extraNotNull>(EXTRA_SONG).value - val playlistNames: MutableList = mutableListOf() + val playlistEntities = extraNotNull>(EXTRA_PLAYLISTS).value + val songs = extraNotNull>(EXTRA_SONG).value + val playlistNames = mutableListOf() playlistNames.add(requireContext().resources.getString(R.string.action_new_playlist)) for (entity: PlaylistEntity in playlistEntities) { playlistNames.add(entity.playlistName) } return materialDialog(R.string.add_playlist_title) - .setItems(playlistNames.toTypedArray()) { _, which -> + .setAdapter( + playlistAdapter(playlistNames) + ) { dialog, which -> if (which == 0) { - CreatePlaylistDialog.create(songs) - .show(requireActivity().supportFragmentManager, "Dialog") + showCreateDialog(songs) } else { lifecycleScope.launch(Dispatchers.IO) { - val songEntities: List = - songs.toSongsEntity(playlistEntities[which - 1]) + val songEntities = songs.toSongsEntity(playlistEntities[which - 1]) libraryViewModel.insertSongs(songEntities) libraryViewModel.forceReload(Playlists) } } - dismiss() + dialog.dismiss() } .create().colorButtons() } -} \ No newline at end of file + + private fun showCreateDialog(songs: List) { + CreatePlaylistDialog.create(songs).show(requireActivity().supportFragmentManager, "Dialog") + } +} diff --git a/app/src/main/java/io/github/muntashirakon/music/dialogs/BlacklistFolderChooserDialog.java b/app/src/main/java/io/github/muntashirakon/music/dialogs/BlacklistFolderChooserDialog.java index 18e7cfbb3..2ab4a092e 100644 --- a/app/src/main/java/io/github/muntashirakon/music/dialogs/BlacklistFolderChooserDialog.java +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/dialogs/CreatePlaylistDialog.kt b/app/src/main/java/io/github/muntashirakon/music/dialogs/CreatePlaylistDialog.kt index 35787a8ab..2d7b27580 100644 --- a/app/src/main/java/io/github/muntashirakon/music/dialogs/CreatePlaylistDialog.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.dialogs import android.app.Dialog @@ -26,7 +40,7 @@ import kotlinx.coroutines.launch import org.koin.androidx.viewmodel.ext.android.sharedViewModel class CreatePlaylistDialog : DialogFragment() { - private val libraryViewModel by sharedViewModel() + private val io.github.muntashirakon.music by sharedViewModel() companion object { fun create(song: Song): CreatePlaylistDialog { @@ -54,17 +68,8 @@ class CreatePlaylistDialog : DialogFragment() { ) { _, _ -> val playlistName = playlistView.text.toString() if (!TextUtils.isEmpty(playlistName)) { - lifecycleScope.launch(Dispatchers.IO) { - if (libraryViewModel.checkPlaylistExists(playlistName).isEmpty()) { - val playlistId: Long = - libraryViewModel.createPlaylist(PlaylistEntity(playlistName = playlistName)) - libraryViewModel.insertSongs(songs.map { it.toSongEntity(playlistId) }) - libraryViewModel.forceReload(Playlists) - } else { - Toast.makeText(requireContext(), "Playlist exists", Toast.LENGTH_SHORT) - .show() - } - } + io.github.muntashirakon.music.addToPlaylist(playlistName, songs) + } else { playlistContainer.error = "Playlist is can't be empty" } @@ -72,4 +77,4 @@ class CreatePlaylistDialog : DialogFragment() { .create() .colorButtons() } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/muntashirakon/music/dialogs/DeletePlaylistDialog.kt b/app/src/main/java/io/github/muntashirakon/music/dialogs/DeletePlaylistDialog.kt index 8d4359788..c306b8dea 100644 --- a/app/src/main/java/io/github/muntashirakon/music/dialogs/DeletePlaylistDialog.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/dialogs/DeleteSongsDialog.kt b/app/src/main/java/io/github/muntashirakon/music/dialogs/DeleteSongsDialog.kt index ae441e4c4..2274ee881 100644 --- a/app/src/main/java/io/github/muntashirakon/music/dialogs/DeleteSongsDialog.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/dialogs/ImportPlaylistDialog.kt b/app/src/main/java/io/github/muntashirakon/music/dialogs/ImportPlaylistDialog.kt index e40ca35fe..af69de107 100644 --- a/app/src/main/java/io/github/muntashirakon/music/dialogs/ImportPlaylistDialog.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/dialogs/LyricsDialog.kt b/app/src/main/java/io/github/muntashirakon/music/dialogs/LyricsDialog.kt deleted file mode 100644 index 94942f2f4..000000000 --- a/app/src/main/java/io/github/muntashirakon/music/dialogs/LyricsDialog.kt +++ /dev/null @@ -1,60 +0,0 @@ -package io.github.muntashirakon.music.dialogs - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.DialogFragment -import androidx.lifecycle.lifecycleScope -import io.github.muntashirakon.music.R -import io.github.muntashirakon.music.extensions.accentTextColor -import io.github.muntashirakon.music.extensions.hide -import io.github.muntashirakon.music.helper.MusicPlayerRemote -import io.github.muntashirakon.music.network.Result -import io.github.muntashirakon.music.repository.Repository -import kotlinx.android.synthetic.main.lyrics_dialog.* -import kotlinx.coroutines.Dispatchers.IO -import kotlinx.coroutines.Dispatchers.Main -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import org.koin.android.ext.android.inject - -class LyricsDialog : DialogFragment() { - override fun getTheme(): Int { - return R.style.MaterialAlertDialogTheme - } - - private val repository by inject() - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - return inflater.inflate(R.layout.lyrics_dialog, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - val song = MusicPlayerRemote.currentSong - dialogTitle.text = song.title - syncedLyrics.accentTextColor() - lifecycleScope.launch(IO) { - val result: Result = repository.lyrics( - song.artistName, - song.title - ) - withContext(Main) { - - when (result) { - is Result.Error -> progressBar.hide() - is Result.Loading -> println("Loading") - is Result.Success -> { - progressBar.hide() - lyricsText.text = result.data - } - } - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/io/github/muntashirakon/music/dialogs/RemoveSongFromPlaylistDialog.kt b/app/src/main/java/io/github/muntashirakon/music/dialogs/RemoveSongFromPlaylistDialog.kt index ca364883f..be554210c 100644 --- a/app/src/main/java/io/github/muntashirakon/music/dialogs/RemoveSongFromPlaylistDialog.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.dialogs import android.app.Dialog @@ -12,7 +26,6 @@ import io.github.muntashirakon.music.extensions.colorButtons import io.github.muntashirakon.music.extensions.extraNotNull import io.github.muntashirakon.music.extensions.materialDialog import io.github.muntashirakon.music.fragments.LibraryViewModel -import io.github.muntashirakon.music.fragments.ReloadType.Playlists import org.koin.androidx.viewmodel.ext.android.sharedViewModel class RemoveSongFromPlaylistDialog : DialogFragment() { @@ -60,10 +73,9 @@ class RemoveSongFromPlaylistDialog : DialogFragment() { .setMessage(pair.second) .setPositiveButton(R.string.remove_action) { _, _ -> libraryViewModel.deleteSongsInPlaylist(songs) - libraryViewModel.forceReload(Playlists) } .setNegativeButton(android.R.string.cancel, null) .create() .colorButtons() } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/muntashirakon/music/dialogs/RenamePlaylistDialog.kt b/app/src/main/java/io/github/muntashirakon/music/dialogs/RenamePlaylistDialog.kt index ea6738145..c26ad24d7 100644 --- a/app/src/main/java/io/github/muntashirakon/music/dialogs/RenamePlaylistDialog.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/dialogs/SavePlaylistDialog.kt b/app/src/main/java/io/github/muntashirakon/music/dialogs/SavePlaylistDialog.kt index a873c7c22..ceffa774b 100644 --- a/app/src/main/java/io/github/muntashirakon/music/dialogs/SavePlaylistDialog.kt +++ b/app/src/main/java/io/github/muntashirakon/music/dialogs/SavePlaylistDialog.kt @@ -1,6 +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 io.github.muntashirakon.music.dialogs import android.app.Dialog +import android.media.MediaScannerConnection import android.os.Bundle import android.widget.Toast import androidx.core.os.bundleOf @@ -18,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 { @@ -33,9 +47,14 @@ class SavePlaylistDialog : DialogFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) lifecycleScope.launch(Dispatchers.IO) { - val playlistWithSongs: PlaylistWithSongs = - extraNotNull(EXTRA_PLAYLIST).value - val file = PlaylistsUtil.savePlaylistWithSongs(requireContext(), playlistWithSongs) + val playlistWithSongs = extraNotNull(EXTRA_PLAYLIST).value + val file = PlaylistsUtil.savePlaylistWithSongs(playlistWithSongs) + MediaScannerConnection.scanFile( + requireActivity(), + arrayOf(file.path), + null + ) { _, _ -> + } withContext(Dispatchers.Main) { Toast.makeText( requireContext(), @@ -52,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/io/github/muntashirakon/music/dialogs/SleepTimerDialog.kt b/app/src/main/java/io/github/muntashirakon/music/dialogs/SleepTimerDialog.kt index fc88b8489..2d95c0373 100755 --- a/app/src/main/java/io/github/muntashirakon/music/dialogs/SleepTimerDialog.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/dialogs/SongDetailDialog.kt b/app/src/main/java/io/github/muntashirakon/music/dialogs/SongDetailDialog.kt index a6235e8d1..58cf58d25 100644 --- a/app/src/main/java/io/github/muntashirakon/music/dialogs/SongDetailDialog.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.dialogs import android.annotation.SuppressLint @@ -31,13 +31,13 @@ import io.github.muntashirakon.music.extensions.colorButtons import io.github.muntashirakon.music.extensions.materialDialog import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.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/io/github/muntashirakon/music/dialogs/SongShareDialog.kt b/app/src/main/java/io/github/muntashirakon/music/dialogs/SongShareDialog.kt index 20e063f7d..9236b1648 100644 --- a/app/src/main/java/io/github/muntashirakon/music/dialogs/SongShareDialog.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.dialogs import android.app.Dialog diff --git a/app/src/main/java/io/github/muntashirakon/music/extensions/ActivityEx.kt b/app/src/main/java/io/github/muntashirakon/music/extensions/ActivityEx.kt index e4a1dacb9..c3ec4cf51 100644 --- a/app/src/main/java/io/github/muntashirakon/music/extensions/ActivityEx.kt +++ b/app/src/main/java/io/github/muntashirakon/music/extensions/ActivityEx.kt @@ -1,20 +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 io.github.muntashirakon.music.extensions import android.app.Activity +import androidx.annotation.DimenRes import androidx.appcompat.app.AppCompatActivity import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import com.google.android.material.appbar.MaterialToolbar @@ -33,4 +34,8 @@ 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 } +} + +fun Activity.dip(@DimenRes id: Int): Int { + return resources.getDimensionPixelSize(id) } \ No newline at end of file diff --git a/app/src/main/java/io/github/muntashirakon/music/extensions/ColorExt.kt b/app/src/main/java/io/github/muntashirakon/music/extensions/ColorExt.kt index bf069359a..236620e1e 100644 --- a/app/src/main/java/io/github/muntashirakon/music/extensions/ColorExt.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.extensions import android.app.Dialog @@ -154,6 +154,10 @@ fun MaterialButton.applyColor(color: Int) { iconTint = textColorColorStateList } +fun MaterialButton.accentColor(){ + this.applyColor(ThemeStore.accentColor(context)) +} + fun MaterialButton.applyOutlineColor(color: Int) { val textColorColorStateList = ColorStateList.valueOf(color) setTextColor(textColorColorStateList) @@ -180,7 +184,6 @@ fun ProgressIndicator.applyColor(color: Int) { } fun TextInputEditText.accentColor() { - } fun AppCompatImageView.accentColor(): Int { @@ -203,4 +206,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/io/github/muntashirakon/music/extensions/CursorExtensions.kt b/app/src/main/java/io/github/muntashirakon/music/extensions/CursorExtensions.kt index 310fe1e0c..2f1f9fe41 100644 --- a/app/src/main/java/io/github/muntashirakon/music/extensions/CursorExtensions.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/extensions/DialogExtension.kt b/app/src/main/java/io/github/muntashirakon/music/extensions/DialogExtension.kt index 5f1252ca5..a78373025 100644 --- a/app/src/main/java/io/github/muntashirakon/music/extensions/DialogExtension.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/extensions/DimenExtension.kt b/app/src/main/java/io/github/muntashirakon/music/extensions/DimenExtension.kt index 836f2e47a..e9933694c 100644 --- a/app/src/main/java/io/github/muntashirakon/music/extensions/DimenExtension.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/extensions/DrawableExt.kt b/app/src/main/java/io/github/muntashirakon/music/extensions/DrawableExt.kt index eecd88dca..d425411a9 100644 --- a/app/src/main/java/io/github/muntashirakon/music/extensions/DrawableExt.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/extensions/FragmentExt.kt b/app/src/main/java/io/github/muntashirakon/music/extensions/FragmentExt.kt index d9244bde0..99bba7615 100644 --- a/app/src/main/java/io/github/muntashirakon/music/extensions/FragmentExt.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/extensions/NavigationExtensions.kt b/app/src/main/java/io/github/muntashirakon/music/extensions/NavigationExtensions.kt index 7b2d0473f..0955e4c78 100644 --- a/app/src/main/java/io/github/muntashirakon/music/extensions/NavigationExtensions.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/extensions/PaletteEX.kt b/app/src/main/java/io/github/muntashirakon/music/extensions/PaletteEX.kt index 15be9ad07..075563355 100644 --- a/app/src/main/java/io/github/muntashirakon/music/extensions/PaletteEX.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/extensions/Preference.kt b/app/src/main/java/io/github/muntashirakon/music/extensions/Preference.kt index 3d61a7f9c..0de0c5e33 100644 --- a/app/src/main/java/io/github/muntashirakon/music/extensions/Preference.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.extensions import android.content.SharedPreferences diff --git a/app/src/main/java/io/github/muntashirakon/music/extensions/ViewExtensions.kt b/app/src/main/java/io/github/muntashirakon/music/extensions/ViewExtensions.kt index bc75abf41..3034ab13f 100644 --- a/app/src/main/java/io/github/muntashirakon/music/extensions/ViewExtensions.kt +++ b/app/src/main/java/io/github/muntashirakon/music/extensions/ViewExtensions.kt @@ -1,26 +1,29 @@ /* - * 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 io.github.muntashirakon.music.extensions +import android.animation.ObjectAnimator import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.EditText import androidx.annotation.LayoutRes +import androidx.core.animation.doOnEnd +import androidx.core.animation.doOnStart import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.util.TintHelper +import com.google.android.material.bottomsheet.BottomSheetBehavior @Suppress("UNCHECKED_CAST") fun ViewGroup.inflate(@LayoutRes layout: Int): T { @@ -45,3 +48,29 @@ fun EditText.appHandleColor(): EditText { TintHelper.colorHandles(this, ThemeStore.accentColor(context)) return this } + +fun View.translateYAnimate(value: Float) { + ObjectAnimator.ofFloat(this, "translationY", value) + .apply { + duration = 300 + doOnStart { + if (value == 0f) { + show() + } + } + doOnEnd { + if (value != 0f) { + hide() + } + } + start() + } +} + +fun BottomSheetBehavior<*>.peekHeightAnimate(value: Int) { + ObjectAnimator.ofInt(this, "peekHeight", value) + .apply { + duration = 300 + start() + } +} diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/AlbumCoverStyle.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/AlbumCoverStyle.kt index 082136950..efb255a81 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/AlbumCoverStyle.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.fragments import androidx.annotation.DrawableRes import androidx.annotation.StringRes import io.github.muntashirakon.music.R - enum class AlbumCoverStyle( @StringRes val titleRes: Int, @DrawableRes val drawableResId: Int, diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/CoroutineViewModel.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/CoroutineViewModel.kt index 75d82f411..ac696776d 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/CoroutineViewModel.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/fragments/DetailListFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/DetailListFragment.kt index 9aeda938b..314c3ee67 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/DetailListFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/DetailListFragment.kt @@ -1,43 +1,51 @@ +/* + * 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 io.github.muntashirakon.music.fragments import android.os.Bundle import android.view.View -import android.widget.ImageView import androidx.core.os.bundleOf -import androidx.lifecycle.Observer -import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.FragmentNavigatorExtras import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver import io.github.muntashirakon.music.* import io.github.muntashirakon.music.adapter.album.AlbumAdapter import io.github.muntashirakon.music.adapter.artist.ArtistAdapter import io.github.muntashirakon.music.adapter.song.SongAdapter import io.github.muntashirakon.music.db.toSong -import io.github.muntashirakon.music.fragments.albums.AlbumClickListener -import io.github.muntashirakon.music.fragments.artists.ArtistClickListener +import io.github.muntashirakon.music.extensions.dipToPix import io.github.muntashirakon.music.fragments.base.AbsMainActivityFragment +import io.github.muntashirakon.music.interfaces.IAlbumClickListener +import io.github.muntashirakon.music.interfaces.IArtistClickListener import io.github.muntashirakon.music.model.Album import io.github.muntashirakon.music.model.Artist -import io.github.muntashirakon.music.repository.RealRepository +import io.github.muntashirakon.music.state.NowPlayingPanelState +import io.github.muntashirakon.music.util.RetroUtil import kotlinx.android.synthetic.main.fragment_playlist_detail.* -import kotlinx.coroutines.Dispatchers.IO -import kotlinx.coroutines.Dispatchers.Main -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import org.koin.android.ext.android.inject class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_detail), - ArtistClickListener, AlbumClickListener { + IArtistClickListener, IAlbumClickListener { private val args by navArgs() - private val repository by inject() override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) + mainActivity.setBottomBarVisibility(View.GONE) mainActivity.setSupportActionBar(toolbar) - mainActivity.hideBottomBarVisibility(false) progressIndicator.hide() when (args.type) { TOP_ARTISTS -> { @@ -57,6 +65,14 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de LAST_ADDED_PLAYLIST -> lastAddedSongs() TOP_PLAYED_PLAYLIST -> topPlayed() } + + recyclerView.adapter?.registerAdapterDataObserver(object : AdapterDataObserver() { + override fun onChanged() { + super.onChanged() + val height = dipToPix(52f) + recyclerView.setPadding(0, 0, 0, height.toInt()) + } + }) } private fun lastAddedSongs() { @@ -70,10 +86,9 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de adapter = songAdapter layoutManager = linearLayoutManager() } - lifecycleScope.launch(IO) { - val songs = repository.recentSongs() - withContext(Main) { songAdapter.swapDataSet(songs) } - } + libraryViewModel.recentSongs().observe(viewLifecycleOwner, { songs -> + songAdapter.swapDataSet(songs) + }) } private fun topPlayed() { @@ -87,12 +102,9 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de adapter = songAdapter layoutManager = linearLayoutManager() } - lifecycleScope.launch(IO) { - val songs = repository.playCountSongs().map { - it.toSong() - } - withContext(Main) { songAdapter.swapDataSet(songs) } - } + libraryViewModel.playCountSongs().observe(viewLifecycleOwner, { songs -> + songAdapter.swapDataSet(songs) + }) } private fun loadHistory() { @@ -107,9 +119,8 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de adapter = songAdapter layoutManager = linearLayoutManager() } - repository.observableHistorySongs().observe(viewLifecycleOwner, Observer { - val songs = it.map { historyEntity -> historyEntity.toSong() } - songAdapter.swapDataSet(songs) + libraryViewModel.observableHistorySongs().observe(viewLifecycleOwner, { + songAdapter.swapDataSet(it) }) } @@ -124,40 +135,30 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de adapter = songAdapter layoutManager = linearLayoutManager() } - repository.favorites().observe(viewLifecycleOwner, Observer { - println(it.size) - val songs = it.map { songEntity -> songEntity.toSong() } + libraryViewModel.favorites().observe(viewLifecycleOwner, { songEntities -> + val songs = songEntities.map { songEntity -> songEntity.toSong() } songAdapter.swapDataSet(songs) }) } private fun loadArtists(title: Int, type: Int) { toolbar.setTitle(title) - lifecycleScope.launch(IO) { - val artists = - if (type == TOP_ARTISTS) repository.topArtists() else repository.recentArtists() - withContext(Main) { - recyclerView.apply { - adapter = artistAdapter(artists) - layoutManager = gridLayoutManager() - } + libraryViewModel.artists(type).observe(viewLifecycleOwner, { artists -> + recyclerView.apply { + adapter = artistAdapter(artists) + layoutManager = gridLayoutManager() } - } + }) } private fun loadAlbums(title: Int, type: Int) { toolbar.setTitle(title) - lifecycleScope.launch(IO) { - val albums = - if (type == TOP_ALBUMS) repository.topAlbums() else repository.recentAlbums() - withContext(Main) { - recyclerView.apply { - adapter = albumAdapter(albums) - layoutManager = gridLayoutManager() - - } + libraryViewModel.albums(type).observe(viewLifecycleOwner, { albums -> + recyclerView.apply { + adapter = albumAdapter(albums) + layoutManager = gridLayoutManager() } - } + }) } private fun artistAdapter(artists: List): ArtistAdapter = ArtistAdapter( @@ -178,15 +179,21 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false) private fun gridLayoutManager(): GridLayoutManager = - GridLayoutManager(requireContext(), 2, GridLayoutManager.VERTICAL, false) + GridLayoutManager(requireContext(), gridCount(), GridLayoutManager.VERTICAL, false) + private fun gridCount(): Int { + if (RetroUtil.isTablet()) { + return if (RetroUtil.isLandscape()) 6 else 4 + } + return 2 + } - override fun onArtist(artistId: Long, imageView: ImageView) { + override fun onArtist(artistId: Long, view: View) { findNavController().navigate( R.id.artistDetailsFragment, bundleOf(EXTRA_ARTIST_ID to artistId), null, - FragmentNavigatorExtras(imageView to getString(R.string.transition_artist_image)) + FragmentNavigatorExtras(view to "artist") ) } @@ -195,7 +202,9 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de R.id.albumDetailsFragment, bundleOf(EXTRA_ALBUM_ID to albumId), null, - FragmentNavigatorExtras(view to getString(R.string.transition_album_art)) + FragmentNavigatorExtras( + view to "album" + ) ) } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/LibraryViewModel.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/LibraryViewModel.kt index d1848bd15..9ddae31ab 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/LibraryViewModel.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/LibraryViewModel.kt @@ -1,38 +1,68 @@ +/* + * 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 io.github.muntashirakon.music.fragments +import android.widget.Toast import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.liveData import androidx.lifecycle.viewModelScope +import io.github.muntashirakon.music.App +import io.github.muntashirakon.music.RECENT_ALBUMS +import io.github.muntashirakon.music.RECENT_ARTISTS +import io.github.muntashirakon.music.TOP_ALBUMS +import io.github.muntashirakon.music.TOP_ARTISTS import io.github.muntashirakon.music.db.PlaylistEntity import io.github.muntashirakon.music.db.PlaylistWithSongs import io.github.muntashirakon.music.db.SongEntity +import io.github.muntashirakon.music.db.toSong import io.github.muntashirakon.music.db.toSongEntity import io.github.muntashirakon.music.fragments.ReloadType.* import io.github.muntashirakon.music.helper.MusicPlayerRemote -import io.github.muntashirakon.music.interfaces.MusicServiceEventListener -import io.github.muntashirakon.music.model.* +import io.github.muntashirakon.music.interfaces.IMusicServiceEventListener +import io.github.muntashirakon.music.model.Album +import io.github.muntashirakon.music.model.Artist +import io.github.muntashirakon.music.model.Contributor +import io.github.muntashirakon.music.model.Genre +import io.github.muntashirakon.music.model.Home +import io.github.muntashirakon.music.model.Playlist +import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.repository.RealRepository +import io.github.muntashirakon.music.state.NowPlayingPanelState +import io.github.muntashirakon.music.util.PreferenceUtil import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.launch class LibraryViewModel( private val repository: RealRepository -) : ViewModel(), MusicServiceEventListener { +) : ViewModel(), IMusicServiceEventListener { - private val paletteColor = MutableLiveData() + private val _paletteColor = MutableLiveData() + private val home = MutableLiveData>() private val albums = MutableLiveData>() private val songs = MutableLiveData>() private val artists = MutableLiveData>() private val playlists = MutableLiveData>() private val legacyPlaylists = MutableLiveData>() private val genres = MutableLiveData>() - private val home = MutableLiveData>() - - val paletteColorLiveData: LiveData = paletteColor + private val searchResults = MutableLiveData>() + val paletteColor: LiveData = _paletteColor init { - fetchHomeSections() + loadLibraryContent() } private fun loadLibraryContent() = viewModelScope.launch(IO) { @@ -44,33 +74,29 @@ class LibraryViewModel( fetchPlaylists() } + fun getSearchResult(): LiveData> = searchResults + fun getSongs(): LiveData> { - fetchSongs() return songs } fun getAlbums(): LiveData> { - fetchAlbums() return albums } fun getArtists(): LiveData> { - fetchArtists() return artists } fun getPlaylists(): LiveData> { - fetchPlaylists() return playlists } fun getLegacyPlaylist(): LiveData> { - fetchLegacyPlaylist() return legacyPlaylists } fun getGenre(): LiveData> { - fetchGenres() return genres } @@ -91,8 +117,14 @@ class LibraryViewModel( } private fun fetchArtists() { - viewModelScope.launch(IO) { - artists.postValue(repository.fetchArtists()) + if (PreferenceUtil.albumArtistsOnly) { + viewModelScope.launch(IO) { + artists.postValue(repository.albumArtists()) + } + } else { + viewModelScope.launch(IO) { + artists.postValue(repository.fetchArtists()) + } } } @@ -120,6 +152,11 @@ class LibraryViewModel( } } + fun search(query: String?) = viewModelScope.launch(IO) { + val result = repository.search(query) + searchResults.postValue(result) + } + fun forceReload(reloadType: ReloadType) = viewModelScope.launch { when (reloadType) { Songs -> fetchSongs() @@ -132,7 +169,7 @@ class LibraryViewModel( } fun updateColor(newColor: Int) { - paletteColor.postValue(newColor) + _paletteColor.postValue(newColor) } override fun onMediaStoreChanged() { @@ -154,7 +191,6 @@ class LibraryViewModel( override fun onPlayingMetaChanged() { println("onPlayingMetaChanged") - } override fun onPlayStateChanged() { @@ -181,8 +217,11 @@ class LibraryViewModel( repository.renameRoomPlaylist(playListId, name) } - fun deleteSongsInPlaylist(songs: List) = viewModelScope.launch(IO) { - repository.deleteSongsInPlaylist(songs) + fun deleteSongsInPlaylist(songs: List) { + viewModelScope.launch(IO) { + repository.deleteSongsInPlaylist(songs) + forceReload(Playlists) + } } fun deleteSongsFromPlaylist(playlists: List) = viewModelScope.launch(IO) { @@ -201,10 +240,10 @@ class LibraryViewModel( suspend fun removeSongFromPlaylist(songEntity: SongEntity) = repository.removeSongFromPlaylist(songEntity) - suspend fun checkPlaylistExists(playlistName: String): List = + private suspend fun checkPlaylistExists(playlistName: String): List = repository.checkPlaylistExists(playlistName) - suspend fun createPlaylist(playlistEntity: PlaylistEntity): Long = + private suspend fun createPlaylist(playlistEntity: PlaylistEntity): Long = repository.createPlaylist(playlistEntity) fun importPlaylists() = viewModelScope.launch(IO) { @@ -232,6 +271,75 @@ class LibraryViewModel( fetchPlaylists() loadLibraryContent() } + + fun recentSongs(): LiveData> = liveData { + emit(repository.recentSongs()) + } + + fun playCountSongs(): LiveData> = liveData { + emit(repository.playCountSongs().map { + it.toSong() + }) + } + + fun artists(type: Int): LiveData> = liveData { + when (type) { + TOP_ARTISTS -> emit(repository.topArtists()) + RECENT_ARTISTS -> { + emit(repository.recentArtists()) + } + } + } + + fun albums(type: Int): LiveData> = liveData { + when (type) { + TOP_ALBUMS -> emit(repository.topAlbums()) + RECENT_ALBUMS -> { + emit(repository.recentAlbums()) + } + } + } + + fun artist(artistId: Long): LiveData = liveData { + emit(repository.artistById(artistId)) + } + + fun fetchContributors(): LiveData> = liveData { + emit(repository.contributor()) + } + + fun observableHistorySongs() = repository.observableHistorySongs() + + fun favorites() = repository.favorites() + + fun clearSearchResult() { + viewModelScope.launch { + searchResults.postValue(emptyList()) + } + } + + fun addToPlaylist(playlistName: String, songs: List) { + viewModelScope.launch(IO) { + val playlists = checkPlaylistExists(playlistName) + if (playlists.isEmpty()) { + val playlistId: Long = createPlaylist(PlaylistEntity(playlistName = playlistName)) + insertSongs(songs.map { it.toSongEntity(playlistId) }) + forceReload(Playlists) + } else { + val playlist = playlists.firstOrNull() + if (playlist != null) { + insertSongs(songs.map { + it.toSongEntity(playListId = playlist.playListId) + }) + } + Toast.makeText( + App.getContext(), + "Adding songs to $playlistName", + Toast.LENGTH_SHORT + ).show() + } + } + } } enum class ReloadType { @@ -241,4 +349,4 @@ enum class ReloadType { HomeSections, Playlists, Genres, -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/MiniPlayerFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/MiniPlayerFragment.kt index adaa793f8..183e993df 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/MiniPlayerFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.fragments import android.animation.ObjectAnimator @@ -12,15 +26,19 @@ import android.view.MotionEvent import android.view.View import android.view.animation.DecelerateInterpolator import io.github.muntashirakon.music.R -import io.github.muntashirakon.music.extensions.* +import io.github.muntashirakon.music.extensions.accentColor +import io.github.muntashirakon.music.extensions.applyColor +import io.github.muntashirakon.music.extensions.show +import io.github.muntashirakon.music.extensions.textColorPrimary +import io.github.muntashirakon.music.extensions.textColorSecondary import io.github.muntashirakon.music.fragments.base.AbsMusicServiceFragment import io.github.muntashirakon.music.helper.MusicPlayerRemote import io.github.muntashirakon.music.helper.MusicProgressViewUpdateHelper import io.github.muntashirakon.music.helper.PlayPauseButtonOnClickHandler import io.github.muntashirakon.music.util.PreferenceUtil import io.github.muntashirakon.music.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/io/github/muntashirakon/music/fragments/NowPlayingScreen.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/NowPlayingScreen.kt index 79972c57b..e150dd1de 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/NowPlayingScreen.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.fragments import androidx.annotation.DrawableRes @@ -24,7 +38,7 @@ enum class NowPlayingScreen constructor( Gradient(R.string.gradient, R.drawable.np_gradient, 17), Material(R.string.material, R.drawable.np_material, 11), Normal(R.string.normal, R.drawable.np_normal, 0), - //Peak(R.string.peak, R.drawable.np_peak, 14), + Peak(R.string.peak, R.drawable.np_peak, 14), Plain(R.string.plain, R.drawable.np_plain, 3), Simple(R.string.simple, R.drawable.np_simple, 8), Tiny(R.string.tiny, R.drawable.np_tiny, 7), diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/VolumeFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/VolumeFragment.kt index 4677186b4..64f5d20cc 100755 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/VolumeFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/fragments/about/AboutFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/about/AboutFragment.kt index b76f8cb32..609c6794e 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/about/AboutFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.fragments.about import android.content.Intent @@ -9,21 +23,21 @@ import androidx.core.app.ShareCompat import androidx.fragment.app.Fragment import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.LinearLayoutManager -import com.google.gson.Gson -import com.google.gson.reflect.TypeToken +import io.github.muntashirakon.music.App import io.github.muntashirakon.music.Constants import io.github.muntashirakon.music.R import io.github.muntashirakon.music.adapter.ContributorAdapter -import io.github.muntashirakon.music.model.Contributor +import io.github.muntashirakon.music.fragments.LibraryViewModel import io.github.muntashirakon.music.util.NavigationUtil import kotlinx.android.synthetic.main.card_credit.* import kotlinx.android.synthetic.main.card_other.* import kotlinx.android.synthetic.main.card_retro_info.* import kotlinx.android.synthetic.main.card_social.* -import java.io.IOException -import java.nio.charset.StandardCharsets +import org.koin.androidx.viewmodel.ext.android.sharedViewModel class AboutFragment : Fragment(R.layout.fragment_about), View.OnClickListener { + private val libraryViewModel by sharedViewModel() + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) version.setSummary(getAppVersion()) @@ -31,24 +45,6 @@ class AboutFragment : Fragment(R.layout.fragment_about), View.OnClickListener { loadContributors() } - private val contributorsJson: String? - get() { - val json: String - try { - val inputStream = requireActivity().assets.open("contributors.json") - val size = inputStream.available() - val buffer = ByteArray(size) - inputStream.read(buffer) - inputStream.close() - json = String(buffer, StandardCharsets.UTF_8) - } catch (ex: IOException) { - ex.printStackTrace() - return null - } - return json - } - - private fun openUrl(url: String) { val i = Intent(Intent.ACTION_VIEW) i.data = Uri.parse(url) @@ -109,16 +105,14 @@ class AboutFragment : Fragment(R.layout.fragment_about), View.OnClickListener { } private fun loadContributors() { - val type = object : TypeToken>() { - - }.type - val contributors = Gson().fromJson>(contributorsJson, type) - - val contributorAdapter = ContributorAdapter(contributors) + val contributorAdapter = ContributorAdapter(emptyList()) recyclerView.apply { layoutManager = LinearLayoutManager(requireContext()) itemAnimator = DefaultItemAnimator() adapter = contributorAdapter } + libraryViewModel.fetchContributors().observe(viewLifecycleOwner, { contributors -> + contributorAdapter.swapData(contributors) + }) } } diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/albums/AlbumDetailsFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/albums/AlbumDetailsFragment.kt index b61af2bbb..dadf49b68 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/albums/AlbumDetailsFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/albums/AlbumDetailsFragment.kt @@ -1,15 +1,33 @@ +/* + * 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 io.github.muntashirakon.music.fragments.albums import android.app.ActivityOptions import android.content.Intent import android.os.Bundle -import android.view.* +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.SubMenu +import android.view.View import androidx.appcompat.app.AppCompatActivity import androidx.core.os.bundleOf import androidx.core.text.HtmlCompat +import androidx.core.view.ViewCompat import androidx.lifecycle.Observer import androidx.lifecycle.lifecycleScope -import androidx.navigation.findNavController import androidx.navigation.fragment.FragmentNavigatorExtras import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs @@ -17,6 +35,7 @@ import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager import code.name.monkey.appthemehelper.common.ATHToolbarActivity.getToolbarBackgroundColor +import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import io.github.muntashirakon.music.EXTRA_ALBUM_ID import io.github.muntashirakon.music.EXTRA_ARTIST_ID @@ -29,6 +48,7 @@ import io.github.muntashirakon.music.dialogs.AddToPlaylistDialog import io.github.muntashirakon.music.dialogs.DeleteSongsDialog import io.github.muntashirakon.music.extensions.applyColor import io.github.muntashirakon.music.extensions.applyOutlineColor +import io.github.muntashirakon.music.extensions.findActivityNavController import io.github.muntashirakon.music.extensions.show import io.github.muntashirakon.music.fragments.base.AbsMainActivityFragment import io.github.muntashirakon.music.glide.AlbumGlideRequest @@ -37,6 +57,7 @@ import io.github.muntashirakon.music.glide.RetroMusicColoredTarget import io.github.muntashirakon.music.glide.SingleColorTarget import io.github.muntashirakon.music.helper.MusicPlayerRemote import io.github.muntashirakon.music.helper.SortOrder +import io.github.muntashirakon.music.interfaces.IAlbumClickListener import io.github.muntashirakon.music.model.Album import io.github.muntashirakon.music.model.Artist import io.github.muntashirakon.music.network.Result @@ -47,8 +68,7 @@ import io.github.muntashirakon.music.util.PreferenceUtil import io.github.muntashirakon.music.util.RetroUtil import io.github.muntashirakon.music.util.color.MediaNotificationProcessor import com.bumptech.glide.Glide -import com.google.android.material.transition.platform.MaterialArcMotion -import com.google.android.material.transition.platform.MaterialContainerTransform +import com.google.android.material.transition.MaterialContainerTransform import kotlinx.android.synthetic.main.fragment_album_content.* import kotlinx.android.synthetic.main.fragment_album_details.* import kotlinx.coroutines.Dispatchers @@ -60,7 +80,7 @@ import org.koin.core.parameter.parametersOf import java.util.* class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_details), - AlbumClickListener { + IAlbumClickListener { private val arguments by navArgs() private val detailsViewModel by viewModel { @@ -73,21 +93,25 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det private val savedSortOrder: String get() = PreferenceUtil.albumDetailSongSortOrder + private fun setUpTransitions() { + val transform = MaterialContainerTransform() + transform.setAllContainerColors(ATHUtil.resolveColor(requireContext(), R.attr.colorSurface)) + sharedElementEnterTransition = transform + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - sharedElementEnterTransition = MaterialContainerTransform().apply { - duration = 1000L - pathMotion = MaterialArcMotion() - } + setUpTransitions() } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setHasOptionsMenu(true) - mainActivity.hideBottomBarVisibility(false) + mainActivity.setBottomBarVisibility(View.GONE) mainActivity.addMusicServiceEventListener(detailsViewModel) mainActivity.setSupportActionBar(toolbar) toolbar.title = " " + ViewCompat.setTransitionName(container, "album") postponeEnterTransition() detailsViewModel.getAlbum().observe(viewLifecycleOwner, Observer { startPostponedEnterTransition() @@ -95,11 +119,14 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det }) setupRecyclerView() - artistImage.setOnClickListener { - requireActivity().findNavController(R.id.fragment_container) + artistImage.setOnClickListener { artistView -> + ViewCompat.setTransitionName(artistView, "artist") + findActivityNavController(R.id.fragment_container) .navigate( R.id.artistDetailsFragment, - bundleOf(EXTRA_ARTIST_ID to album.artistId) + bundleOf(EXTRA_ARTIST_ID to album.artistId), + null, + FragmentNavigatorExtras(artistView to "artist") ) } playAction.setOnClickListener { MusicPlayerRemote.openQueue(album.songs, 0, true) } @@ -125,10 +152,9 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det override fun onDestroy() { super.onDestroy() - playerActivity?.removeMusicServiceEventListener(detailsViewModel) + serviceActivity?.removeMusicServiceEventListener(detailsViewModel) } - private fun setupRecyclerView() { simpleSongAdapter = SimpleSongAdapter( requireActivity() as AppCompatActivity, @@ -270,7 +296,9 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det R.id.albumDetailsFragment, bundleOf(EXTRA_ALBUM_ID to albumId), null, - FragmentNavigatorExtras(view to getString(R.string.transition_album_art)) + FragmentNavigatorExtras( + view to "album" + ) ) } @@ -392,4 +420,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/io/github/muntashirakon/music/fragments/albums/AlbumDetailsViewModel.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/albums/AlbumDetailsViewModel.kt index e50d7f69c..85c522d02 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/albums/AlbumDetailsViewModel.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/albums/AlbumDetailsViewModel.kt @@ -1,25 +1,51 @@ +/* + * 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 io.github.muntashirakon.music.fragments.albums import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.liveData -import io.github.muntashirakon.music.interfaces.MusicServiceEventListener +import androidx.lifecycle.viewModelScope +import io.github.muntashirakon.music.interfaces.IMusicServiceEventListener import io.github.muntashirakon.music.model.Album import io.github.muntashirakon.music.model.Artist import io.github.muntashirakon.music.network.Result import io.github.muntashirakon.music.network.model.LastFmAlbum import io.github.muntashirakon.music.repository.RealRepository import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.launch class AlbumDetailsViewModel( private val repository: RealRepository, private val albumId: Long -) : ViewModel(), MusicServiceEventListener { +) : ViewModel(), IMusicServiceEventListener { + private val albumDetails = MutableLiveData() - fun getAlbum(): LiveData = liveData(IO) { - emit(repository.albumByIdAsync(albumId)) + init { + fetchAlbum() } + private fun fetchAlbum() { + viewModelScope.launch(IO) { + albumDetails.postValue(repository.albumByIdAsync(albumId)) + } + } + + fun getAlbum(): LiveData = albumDetails + fun getArtist(artistId: Long): LiveData = liveData(IO) { val artist = repository.artistById(artistId) emit(artist) @@ -37,7 +63,7 @@ class AlbumDetailsViewModel( } override fun onMediaStoreChanged() { - + fetchAlbum() } override fun onServiceConnected() {} @@ -47,4 +73,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/io/github/muntashirakon/music/fragments/albums/AlbumsFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/albums/AlbumsFragment.kt index b4492d089..b0572460b 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/albums/AlbumsFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.fragments.albums import android.os.Bundle @@ -5,27 +19,25 @@ import android.view.* import androidx.core.os.bundleOf import androidx.lifecycle.Observer import androidx.navigation.fragment.FragmentNavigatorExtras +import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.GridLayoutManager import io.github.muntashirakon.music.EXTRA_ALBUM_ID import io.github.muntashirakon.music.R import io.github.muntashirakon.music.adapter.album.AlbumAdapter -import io.github.muntashirakon.music.extensions.findActivityNavController +import io.github.muntashirakon.music.extensions.surfaceColor import io.github.muntashirakon.music.fragments.ReloadType import io.github.muntashirakon.music.fragments.base.AbsRecyclerViewCustomGridSizeFragment import io.github.muntashirakon.music.helper.SortOrder import io.github.muntashirakon.music.helper.SortOrder.AlbumSortOrder +import io.github.muntashirakon.music.interfaces.IAlbumClickListener +import io.github.muntashirakon.music.interfaces.ICabHolder import io.github.muntashirakon.music.util.PreferenceUtil +import io.github.muntashirakon.music.util.RetroColorUtil import io.github.muntashirakon.music.util.RetroUtil -import com.google.android.material.transition.platform.MaterialFadeThrough - +import com.afollestad.materialcab.MaterialCab class AlbumsFragment : AbsRecyclerViewCustomGridSizeFragment(), - AlbumClickListener { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - enterTransition = MaterialFadeThrough() - } + IAlbumClickListener, ICabHolder { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -50,7 +62,7 @@ class AlbumsFragment : AbsRecyclerViewCustomGridSizeFragment sortOrder = AlbumSortOrder.ALBUM_A_Z - R.id.action_album_sort_order_desc -> sortOrder = AlbumSortOrder.ALBUM_Z_A - R.id.action_album_sort_order_artist -> sortOrder = AlbumSortOrder.ALBUM_ARTIST - R.id.action_album_sort_order_year -> sortOrder = AlbumSortOrder.ALBUM_YEAR + val sortOrder: String = when (item.itemId) { + R.id.action_album_sort_order_asc -> AlbumSortOrder.ALBUM_A_Z + R.id.action_album_sort_order_desc -> AlbumSortOrder.ALBUM_Z_A + R.id.action_album_sort_order_artist -> AlbumSortOrder.ALBUM_ARTIST + R.id.action_album_sort_order_year -> AlbumSortOrder.ALBUM_YEAR + else -> PreferenceUtil.albumSortOrder } - if (sortOrder != null) { + if (sortOrder != PreferenceUtil.albumSortOrder) { item.isChecked = true setAndSaveSortOrder(sortOrder) return true @@ -249,16 +259,16 @@ class AlbumsFragment : AbsRecyclerViewCustomGridSizeFragment layoutRes = R.layout.item_grid - R.id.action_layout_card -> layoutRes = R.layout.item_card - R.id.action_layout_colored_card -> layoutRes = R.layout.item_card_color - R.id.action_layout_circular -> layoutRes = R.layout.item_grid_circle - R.id.action_layout_image -> layoutRes = R.layout.image - R.id.action_layout_gradient_image -> layoutRes = R.layout.item_image_gradient + val layoutRes = when (item.itemId) { + R.id.action_layout_normal -> R.layout.item_grid + R.id.action_layout_card -> R.layout.item_card + R.id.action_layout_colored_card -> R.layout.item_card_color + R.id.action_layout_circular -> R.layout.item_grid_circle + R.id.action_layout_image -> R.layout.image + R.id.action_layout_gradient_image -> R.layout.item_image_gradient + else -> PreferenceUtil.albumGridStyle } - if (layoutRes != -1) { + if (layoutRes != PreferenceUtil.albumGridStyle) { item.isChecked = true setAndSaveLayoutRes(layoutRes) return true @@ -269,16 +279,16 @@ class AlbumsFragment : AbsRecyclerViewCustomGridSizeFragment gridSize = 1 - R.id.action_grid_size_2 -> gridSize = 2 - R.id.action_grid_size_3 -> gridSize = 3 - R.id.action_grid_size_4 -> gridSize = 4 - R.id.action_grid_size_5 -> gridSize = 5 - R.id.action_grid_size_6 -> gridSize = 6 - R.id.action_grid_size_7 -> gridSize = 7 - R.id.action_grid_size_8 -> gridSize = 8 + val gridSize = when (item.itemId) { + R.id.action_grid_size_1 -> 1 + R.id.action_grid_size_2 -> 2 + R.id.action_grid_size_3 -> 3 + R.id.action_grid_size_4 -> 4 + R.id.action_grid_size_5 -> 5 + R.id.action_grid_size_6 -> 6 + R.id.action_grid_size_7 -> 7 + R.id.action_grid_size_8 -> 8 + else -> 0 } if (gridSize > 0) { item.isChecked = true @@ -288,8 +298,30 @@ class AlbumsFragment : AbsRecyclerViewCustomGridSizeFragment() private val detailsViewModel: ArtistDetailsViewModel by viewModel { parametersOf(arguments.extraArtistId) } - private lateinit var artist: Artist private lateinit var songAdapter: SimpleSongAdapter private lateinit var albumAdapter: HorizontalAlbumAdapter @@ -64,20 +80,31 @@ class ArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_artist_d private var lang: String? = null private var biography: Spanned? = null + private fun setUpTransitions() { + val transform = MaterialContainerTransform() + transform.setAllContainerColors(ATHUtil.resolveColor(requireContext(), R.attr.colorSurface)) + sharedElementEnterTransition = transform + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setUpTransitions() + } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) setHasOptionsMenu(true) + mainActivity.setBottomBarVisibility(View.GONE) + mainActivity.addMusicServiceEventListener(detailsViewModel) mainActivity.setSupportActionBar(toolbar) - mainActivity.hideBottomBarVisibility(false) toolbar.title = null - setupRecyclerView() + ViewCompat.setTransitionName(container, "artist") postponeEnterTransition() detailsViewModel.getArtist().observe(viewLifecycleOwner, Observer { - showArtist(it) startPostponedEnterTransition() + showArtist(it) }) - + setupRecyclerView() playAction.apply { setOnClickListener { MusicPlayerRemote.openQueue(artist.songs, 0, true) } @@ -138,7 +165,6 @@ class ArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_artist_d albumTitle.text = albumText songAdapter.swapDataSet(artist.songs.sortedBy { it.trackNumber }) artist.albums?.let { albumAdapter.swapDataSet(it) } - } private fun loadBiography( @@ -184,7 +210,6 @@ class ArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_artist_d } } - private fun loadArtistImage(artist: Artist) { ArtistGlideRequest.Builder.from(Glide.with(requireContext()), artist) .generatePalette(requireContext()).build() @@ -201,14 +226,13 @@ class ArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_artist_d playAction.applyOutlineColor(color) } - override fun onAlbumClick(albumId: Long, view: View) { findNavController().navigate( R.id.albumDetailsFragment, bundleOf(EXTRA_ALBUM_ID to albumId), null, FragmentNavigatorExtras( - view to getString(R.string.transition_album_art) + view to "album" ) ) } @@ -266,4 +290,4 @@ class ArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_artist_d companion object { const val REQUEST_CODE_SELECT_IMAGE = 9002 } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/artists/ArtistDetailsViewModel.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/artists/ArtistDetailsViewModel.kt index 0bdd3b140..ec7c90449 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/artists/ArtistDetailsViewModel.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/artists/ArtistDetailsViewModel.kt @@ -1,25 +1,50 @@ +/* + * 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 io.github.muntashirakon.music.fragments.artists import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.liveData -import io.github.muntashirakon.music.interfaces.MusicServiceEventListener +import androidx.lifecycle.viewModelScope +import io.github.muntashirakon.music.interfaces.IMusicServiceEventListener import io.github.muntashirakon.music.model.Artist import io.github.muntashirakon.music.network.Result import io.github.muntashirakon.music.network.model.LastFmArtist import io.github.muntashirakon.music.repository.RealRepository import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.launch class ArtistDetailsViewModel( private val realRepository: RealRepository, private val artistId: Long -) : ViewModel(), MusicServiceEventListener { +) : ViewModel(), IMusicServiceEventListener { + private val artistDetails = MutableLiveData() - fun getArtist(): LiveData = liveData(IO) { - val artist = realRepository.artistById(artistId) - emit(artist) + init { + fetchArtist() } + private fun fetchArtist() { + viewModelScope.launch(IO) { + artistDetails.postValue(realRepository.artistById(artistId)) + } + } + + fun getArtist(): LiveData = artistDetails + fun getArtistInfo( name: String, lang: String?, @@ -31,7 +56,7 @@ class ArtistDetailsViewModel( } override fun onMediaStoreChanged() { - getArtist() + fetchArtist() } override fun onServiceConnected() {} @@ -41,4 +66,4 @@ class ArtistDetailsViewModel( override fun onPlayStateChanged() {} override fun onRepeatModeChanged() {} override fun onShuffleModeChanged() {} -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/artists/ArtistsFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/artists/ArtistsFragment.kt index 7cc396b54..4c44d0ad4 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/artists/ArtistsFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/artists/ArtistsFragment.kt @@ -1,30 +1,42 @@ +/* + * 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 io.github.muntashirakon.music.fragments.artists import android.os.Bundle import android.view.* -import android.widget.ImageView import androidx.core.os.bundleOf import androidx.lifecycle.Observer +import androidx.navigation.fragment.FragmentNavigatorExtras +import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.GridLayoutManager import io.github.muntashirakon.music.EXTRA_ARTIST_ID import io.github.muntashirakon.music.R import io.github.muntashirakon.music.adapter.artist.ArtistAdapter -import io.github.muntashirakon.music.extensions.findActivityNavController +import io.github.muntashirakon.music.extensions.surfaceColor import io.github.muntashirakon.music.fragments.ReloadType import io.github.muntashirakon.music.fragments.base.AbsRecyclerViewCustomGridSizeFragment import io.github.muntashirakon.music.helper.SortOrder.ArtistSortOrder +import io.github.muntashirakon.music.interfaces.IArtistClickListener +import io.github.muntashirakon.music.interfaces.ICabHolder import io.github.muntashirakon.music.util.PreferenceUtil +import io.github.muntashirakon.music.util.RetroColorUtil import io.github.muntashirakon.music.util.RetroUtil -import com.google.android.material.transition.platform.MaterialFadeThrough - +import com.afollestad.materialcab.MaterialCab class ArtistsFragment : AbsRecyclerViewCustomGridSizeFragment(), - ArtistClickListener { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - enterTransition = MaterialFadeThrough() - } - + IArtistClickListener, ICabHolder { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) libraryViewModel.getArtists().observe(viewLifecycleOwner, Observer { @@ -52,7 +64,7 @@ class ArtistsFragment : AbsRecyclerViewCustomGridSizeFragment sortOrder = ArtistSortOrder.ARTIST_A_Z - R.id.action_artist_sort_order_desc -> sortOrder = ArtistSortOrder.ARTIST_Z_A + val sortOrder: String = when (item.itemId) { + R.id.action_artist_sort_order_asc -> ArtistSortOrder.ARTIST_A_Z + R.id.action_artist_sort_order_desc -> ArtistSortOrder.ARTIST_Z_A + else -> PreferenceUtil.artistSortOrder } - if (sortOrder != null) { + if (sortOrder != PreferenceUtil.artistSortOrder) { item.isChecked = true setAndSaveSortOrder(sortOrder) return true @@ -222,16 +237,16 @@ class ArtistsFragment : AbsRecyclerViewCustomGridSizeFragment layoutRes = R.layout.item_grid - R.id.action_layout_card -> layoutRes = R.layout.item_card - R.id.action_layout_colored_card -> layoutRes = R.layout.item_card_color - R.id.action_layout_circular -> layoutRes = R.layout.item_grid_circle - R.id.action_layout_image -> layoutRes = R.layout.image - R.id.action_layout_gradient_image -> layoutRes = R.layout.item_image_gradient + val layoutRes = when (item.itemId) { + R.id.action_layout_normal -> R.layout.item_grid + R.id.action_layout_card -> R.layout.item_card + R.id.action_layout_colored_card -> R.layout.item_card_color + R.id.action_layout_circular -> R.layout.item_grid_circle + R.id.action_layout_image -> R.layout.image + R.id.action_layout_gradient_image -> R.layout.item_image_gradient + else -> PreferenceUtil.artistGridStyle } - if (layoutRes != -1) { + if (layoutRes != PreferenceUtil.artistGridStyle) { item.isChecked = true setAndSaveLayoutRes(layoutRes) return true @@ -242,16 +257,16 @@ class ArtistsFragment : AbsRecyclerViewCustomGridSizeFragment gridSize = 1 - R.id.action_grid_size_2 -> gridSize = 2 - R.id.action_grid_size_3 -> gridSize = 3 - R.id.action_grid_size_4 -> gridSize = 4 - R.id.action_grid_size_5 -> gridSize = 5 - R.id.action_grid_size_6 -> gridSize = 6 - R.id.action_grid_size_7 -> gridSize = 7 - R.id.action_grid_size_8 -> gridSize = 8 + val gridSize = when (item.itemId) { + R.id.action_grid_size_1 -> 1 + R.id.action_grid_size_2 -> 2 + R.id.action_grid_size_3 -> 3 + R.id.action_grid_size_4 -> 4 + R.id.action_grid_size_5 -> 5 + R.id.action_grid_size_6 -> 6 + R.id.action_grid_size_7 -> 7 + R.id.action_grid_size_8 -> 8 + else -> 0 } if (gridSize > 0) { item.isChecked = true @@ -260,8 +275,30 @@ class ArtistsFragment : AbsRecyclerViewCustomGridSizeFragment() override fun onMenuItemClick( item: MenuItem @@ -162,7 +173,6 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme return false } - abstract fun playerToolbar(): Toolbar? abstract fun onShow() diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/base/AbsRecyclerViewCustomGridSizeFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/base/AbsRecyclerViewCustomGridSizeFragment.kt index 7460ec424..0d6f9fc00 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/base/AbsRecyclerViewCustomGridSizeFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/base/AbsRecyclerViewCustomGridSizeFragment.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 io.github.muntashirakon.music.fragments.base import android.os.Bundle @@ -29,7 +43,6 @@ abstract class 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,9 +71,9 @@ abstract class AbsRecyclerViewCustomGridSizeFragment return sortOrder } - fun setAndSaveSortOrder(sortOrder: String) { this.sortOrder = sortOrder + println(sortOrder) saveSortOrder(sortOrder) setSortOrder(sortOrder) } diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/base/AbsRecyclerViewFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/base/AbsRecyclerViewFragment.kt index ba9270647..597ba13f1 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/base/AbsRecyclerViewFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/base/AbsRecyclerViewFragment.kt @@ -1,30 +1,47 @@ +/* + * 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 io.github.muntashirakon.music.fragments.base import android.os.Bundle -import android.view.View -import android.view.ViewGroup +import android.view.* import androidx.annotation.NonNull import androidx.annotation.StringRes +import androidx.core.text.HtmlCompat +import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.RecyclerView -import com.google.android.material.appbar.AppBarLayout +import code.name.monkey.appthemehelper.ThemeStore +import code.name.monkey.appthemehelper.common.ATHToolbarActivity +import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import io.github.muntashirakon.music.R -import io.github.muntashirakon.music.fragments.LibraryViewModel +import io.github.muntashirakon.music.dialogs.CreatePlaylistDialog +import io.github.muntashirakon.music.dialogs.ImportPlaylistDialog import io.github.muntashirakon.music.helper.MusicPlayerRemote +import io.github.muntashirakon.music.state.NowPlayingPanelState import io.github.muntashirakon.music.util.DensityUtil import io.github.muntashirakon.music.util.ThemedFastScroller.create import io.github.muntashirakon.music.views.ScrollingViewOnApplyWindowInsetsListener -import kotlinx.android.synthetic.main.fragment_main_activity_recycler_view.* +import com.google.android.material.appbar.AppBarLayout +import com.google.android.material.transition.Hold +import kotlinx.android.synthetic.main.fragment_main_recycler.* import me.zhanghai.android.fastscroll.FastScroller import me.zhanghai.android.fastscroll.FastScrollerBuilder -import org.koin.androidx.viewmodel.ext.android.sharedViewModel - abstract class AbsRecyclerViewFragment, LM : RecyclerView.LayoutManager> : - AbsMusicServiceFragment(R.layout.fragment_main_activity_recycler_view), + AbsMainActivityFragment(R.layout.fragment_main_recycler), AppBarLayout.OnOffsetChangedListener { - val libraryViewModel: LibraryViewModel by sharedViewModel() - override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) setHasOptionsMenu(true) @@ -33,12 +50,41 @@ abstract class AbsRecyclerViewFragment, LM : Recycle protected var adapter: A? = null protected var layoutManager: LM? = null + private fun setUpTransitions() { + exitTransition = Hold() + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setUpTransitions() + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + mainActivity.setBottomBarVisibility(View.VISIBLE) + mainActivity.setSupportActionBar(toolbar) + mainActivity.supportActionBar?.title = null initLayoutManager() initAdapter() setUpRecyclerView() + setupTitle() + } + + private fun setupTitle() { + toolbar.setNavigationOnClickListener { + findNavController().navigate( + R.id.searchFragment, + null, + navOptions + ) + } + val color = ThemeStore.accentColor(requireContext()) + val hexColor = String.format("#%06X", 0xFFFFFF and color) + val appName = HtmlCompat.fromHtml( + "Retro Music", + HtmlCompat.FROM_HTML_MODE_COMPACT + ) + appNameText.text = appName } private fun setUpRecyclerView() { @@ -78,7 +124,7 @@ abstract class AbsRecyclerViewFragment, LM : Recycle return String(Character.toChars(unicode)) } - private fun checkIsEmpty() { + private fun checkIsEmpty() { emptyText.setText(emptyMessage) empty.visibility = if (adapter!!.itemCount == 0) View.VISIBLE else View.GONE } @@ -95,7 +141,6 @@ abstract class AbsRecyclerViewFragment, LM : Recycle } } - private fun initLayoutManager() { layoutManager = createLayoutManager() } @@ -138,4 +183,39 @@ abstract class AbsRecyclerViewFragment, LM : Recycle fun recyclerView(): RecyclerView { return recyclerView } -} \ No newline at end of file + + override fun onPrepareOptionsMenu(menu: Menu) { + super.onPrepareOptionsMenu(menu) + ToolbarContentTintHelper.handleOnPrepareOptionsMenu(requireActivity(), toolbar) + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + super.onCreateOptionsMenu(menu, inflater) + inflater.inflate(R.menu.menu_main, menu) + ToolbarContentTintHelper.handleOnCreateOptionsMenu( + requireContext(), + toolbar, + menu, + ATHToolbarActivity.getToolbarBackgroundColor(toolbar) + ) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.action_settings -> findNavController().navigate( + R.id.settingsActivity, + null, + navOptions + ) + R.id.action_import_playlist -> ImportPlaylistDialog().show( + childFragmentManager, + "ImportPlaylist" + ) + R.id.action_add_to_playlist -> CreatePlaylistDialog.create(emptyList()).show( + childFragmentManager, + "ShowCreatePlaylistDialog" + ) + } + return super.onOptionsItemSelected(item) + } +} diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/folder/FoldersFragment.java b/app/src/main/java/io/github/muntashirakon/music/fragments/folder/FoldersFragment.java index b2415a279..bb8b047b1 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/folder/FoldersFragment.java +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/folder/FoldersFragment.java @@ -20,7 +20,10 @@ import android.media.MediaScannerConnection; import android.os.Bundle; import android.os.Environment; import android.text.Html; +import android.text.Spanned; import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; @@ -32,8 +35,11 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.appcompat.widget.Toolbar; +import androidx.core.text.HtmlCompat; import androidx.loader.app.LoaderManager; import androidx.loader.content.Loader; +import androidx.navigation.Navigation; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -41,11 +47,14 @@ 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.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.LinkedList; @@ -53,15 +62,16 @@ import java.util.List; import code.name.monkey.appthemehelper.ThemeStore; import code.name.monkey.appthemehelper.util.ATHUtil; +import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper; import io.github.muntashirakon.music.R; import io.github.muntashirakon.music.adapter.SongFileAdapter; import io.github.muntashirakon.music.fragments.base.AbsMainActivityFragment; import io.github.muntashirakon.music.helper.MusicPlayerRemote; import io.github.muntashirakon.music.helper.menu.SongMenuHelper; import io.github.muntashirakon.music.helper.menu.SongsMenuHelper; -import io.github.muntashirakon.music.interfaces.CabHolder; -import io.github.muntashirakon.music.interfaces.Callbacks; -import io.github.muntashirakon.music.interfaces.MainActivityFragmentCallbacks; +import io.github.muntashirakon.music.interfaces.ICabHolder; +import io.github.muntashirakon.music.interfaces.ICallbacks; +import io.github.muntashirakon.music.interfaces.IMainActivityFragmentCallbacks; import io.github.muntashirakon.music.misc.DialogAsyncTask; import io.github.muntashirakon.music.misc.UpdateToastMediaScannerCompletionListener; import io.github.muntashirakon.music.misc.WrappedAsyncTaskLoader; @@ -75,37 +85,44 @@ import io.github.muntashirakon.music.views.BreadCrumbLayout; import io.github.muntashirakon.music.views.ScrollingViewOnApplyWindowInsetsListener; import me.zhanghai.android.fastscroll.FastScroller; -public class FoldersFragment extends AbsMainActivityFragment implements - MainActivityFragmentCallbacks, - CabHolder, +import static code.name.monkey.appthemehelper.common.ATHToolbarActivity.getToolbarBackgroundColor; + +public class FoldersFragment extends AbsMainActivityFragment + implements IMainActivityFragmentCallbacks, + ICabHolder, BreadCrumbLayout.SelectionCallback, - Callbacks, + 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 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) -> { - if (lhs.isDirectory() && !rhs.isDirectory()) { - return -1; - } else if (!lhs.isDirectory() && rhs.isDirectory()) { - return 1; - } else { - return lhs.getName().compareToIgnoreCase - (rhs.getName()); - } - }; + private Comparator fileComparator = + (lhs, rhs) -> { + if (lhs.isDirectory() && !rhs.isDirectory()) { + return -1; + } else if (!lhs.isDirectory() && rhs.isDirectory()) { + return 1; + } else { + return lhs.getName().compareToIgnoreCase(rhs.getName()); + } + }; private RecyclerView recyclerView; public FoldersFragment() { @@ -139,9 +156,8 @@ public class FoldersFragment extends AbsMainActivityFragment implements @NonNull @Override - public View onCreateView(@NonNull LayoutInflater inflater, - ViewGroup container, - Bundle savedInstanceState) { + public View onCreateView( + @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_folder, container, false); initViews(view); return view; @@ -149,23 +165,42 @@ public class FoldersFragment extends AbsMainActivityFragment implements @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + getMainActivity().addMusicServiceEventListener(getLibraryViewModel()); + getMainActivity().setBottomBarVisibility(View.VISIBLE); + 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) { - //noinspection ConstantConditions - setCrumb(new BreadCrumbLayout.Crumb(FileUtil.safeGetCanonicalFile(PreferenceUtil.INSTANCE.getStartDirectory())), true); + setCrumb( + new BreadCrumbLayout.Crumb( + FileUtil.safeGetCanonicalFile(PreferenceUtil.INSTANCE.getStartDirectory())), + true); } else { breadCrumbs.restoreFromStateWrapper(savedInstanceState.getParcelable(CRUMBS)); - getLoaderManager().initLoader(LOADER_ID, null, this); + LoaderManager.getInstance(this).initLoader(LOADER_ID, null, this); } } @@ -181,7 +216,6 @@ public class FoldersFragment extends AbsMainActivityFragment implements if (breadCrumbs != null) { outState.putParcelable(CRUMBS, breadCrumbs.getStateWrapper()); } - } @Override @@ -209,102 +243,129 @@ public class FoldersFragment extends AbsMainActivityFragment implements } @Override - public void onFileMenuClicked(final File file, View view) { + 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(getActivity(), 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; - }); + 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(getActivity(), - 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.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(File file) { + 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 (startIndex > -1) { - 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(); - } - }).execute(new ListSongsAsyncTask.LoadingInfo(toList(file.getParentFile()), fileFilter, - getFileComparator())); + 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 (startIndex > -1) { + 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(); + } + }) + .execute( + new ListSongsAsyncTask.LoadingInfo( + toList(file.getParentFile()), fileFilter, getFileComparator())); } } @@ -319,27 +380,51 @@ public class FoldersFragment extends AbsMainActivityFragment implements } @Override - public void onMultipleItemAction(MenuItem item, ArrayList files) { + public void onMultipleItemAction(MenuItem item, @NotNull ArrayList files) { final int itemId = item.getItemId(); - new ListSongsAsyncTask(getActivity(), null, - (songs, extra) -> SongsMenuHelper.INSTANCE.handleMenuClick(getActivity(), songs, itemId)) + 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 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); + 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)); + .execute(new ListPathsAsyncTask.LoadingInfo(crumb.getFile(), AUDIO_FILE_FILTER)); } return true; } @@ -360,37 +445,43 @@ public class FoldersFragment extends AbsMainActivityFragment implements @NonNull @Override - public MaterialCab openCab(int menuRes, MaterialCab.Callback callback) { + 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); + 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); + 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); + 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; + return breadCrumbs != null && breadCrumbs.size() > 0 + ? breadCrumbs.getCrumb(breadCrumbs.getActiveIndex()) + : null; } private String getEmojiByUnicode(int unicode) { @@ -407,6 +498,8 @@ public class FoldersFragment extends AbsMainActivityFragment implements 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() { @@ -424,8 +517,11 @@ public class FoldersFragment extends AbsMainActivityFragment implements 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)); + MediaScannerConnection.scanFile( + getActivity().getApplicationContext(), + toBeScanned, + null, + new UpdateToastMediaScannerCompletionListener(getActivity(), Arrays.asList(toBeScanned))); } } @@ -438,20 +534,21 @@ public class FoldersFragment extends AbsMainActivityFragment implements if (addToHistory) { breadCrumbs.addHistory(crumb); } - getLoaderManager().restartLoader(LOADER_ID, null, this); + 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(); - } - }); + 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(); } @@ -489,8 +586,8 @@ public class FoldersFragment extends AbsMainActivityFragment implements } } - public static class ListPathsAsyncTask extends - ListingFilesDialogAsyncTask { + public static class ListPathsAsyncTask + extends ListingFilesDialogAsyncTask { private WeakReference onPathsListedCallbackWeakReference; @@ -629,8 +726,7 @@ public class FoldersFragment extends AbsMainActivityFragment implements LoadingInfo info = params[0]; List files = FileUtil.listFilesDeep(info.files, info.fileFilter); - if (isCancelled() || checkContextReference() == null - || checkCallbackReference() == null) { + if (isCancelled() || checkContextReference() == null || checkCallbackReference() == null) { return null; } @@ -694,8 +790,10 @@ public class FoldersFragment extends AbsMainActivityFragment implements final List files; - LoadingInfo(@NonNull List files, @NonNull FileFilter fileFilter, - @NonNull Comparator fileComparator) { + LoadingInfo( + @NonNull List files, + @NonNull FileFilter fileFilter, + @NonNull Comparator fileComparator) { this.fileComparator = fileComparator; this.fileFilter = fileFilter; this.files = files; @@ -703,8 +801,8 @@ public class FoldersFragment extends AbsMainActivityFragment implements } } - private static abstract class ListingFilesDialogAsyncTask extends - DialogAsyncTask { + private abstract static class ListingFilesDialogAsyncTask + extends DialogAsyncTask { ListingFilesDialogAsyncTask(Context context) { super(context); diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/genres/GenreDetailsFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/genres/GenreDetailsFragment.kt index 99febc1e6..7302d7eca 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/genres/GenreDetailsFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.fragments.genres import android.os.Bundle @@ -16,26 +30,26 @@ import io.github.muntashirakon.music.fragments.base.AbsMainActivityFragment import io.github.muntashirakon.music.helper.menu.GenreMenuHelper import io.github.muntashirakon.music.model.Genre import io.github.muntashirakon.music.model.Song +import io.github.muntashirakon.music.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() private val detailsViewModel: GenreDetailsViewModel by viewModel { parametersOf(arguments.extraGenre) } - private lateinit var genre: Genre private lateinit var songAdapter: SongAdapter override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) setHasOptionsMenu(true) + mainActivity.setBottomBarVisibility(View.GONE) mainActivity.addMusicServiceEventListener(detailsViewModel) mainActivity.setSupportActionBar(toolbar) - mainActivity.hideBottomBarVisibility(false) progressIndicator.hide() setupRecyclerView() detailsViewModel.getSongs().observe(viewLifecycleOwner, androidx.lifecycle.Observer { @@ -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/io/github/muntashirakon/music/fragments/genres/GenreDetailsViewModel.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/genres/GenreDetailsViewModel.kt index 1b1331f62..1a33d2534 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/genres/GenreDetailsViewModel.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/genres/GenreDetailsViewModel.kt @@ -1,10 +1,24 @@ +/* + * 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 io.github.muntashirakon.music.fragments.genres import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import io.github.muntashirakon.music.interfaces.MusicServiceEventListener +import io.github.muntashirakon.music.interfaces.IMusicServiceEventListener import io.github.muntashirakon.music.model.Genre import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.repository.RealRepository @@ -15,7 +29,7 @@ import kotlinx.coroutines.withContext class GenreDetailsViewModel( private val realRepository: RealRepository, private val genre: Genre -) : ViewModel(), MusicServiceEventListener { +) : ViewModel(), IMusicServiceEventListener { private val _playListSongs = MutableLiveData>() private val _genre = MutableLiveData().apply { @@ -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/io/github/muntashirakon/music/fragments/genres/GenresFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/genres/GenresFragment.kt index ad4c8a37b..3f9f8ebdd 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/genres/GenresFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.fragments.genres import android.os.Bundle @@ -21,14 +21,8 @@ import androidx.recyclerview.widget.LinearLayoutManager import io.github.muntashirakon.music.R import io.github.muntashirakon.music.adapter.GenreAdapter import io.github.muntashirakon.music.fragments.base.AbsRecyclerViewFragment -import com.google.android.material.transition.platform.MaterialFadeThrough class GenresFragment : AbsRecyclerViewFragment() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - enterTransition = MaterialFadeThrough() - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) libraryViewModel.getGenre().observe(viewLifecycleOwner, Observer { @@ -39,7 +33,6 @@ class GenresFragment : AbsRecyclerViewFragmentMusic", + HtmlCompat.FROM_HTML_MODE_COMPACT + ) + appNameText.text = appName } private fun loadProfile() { @@ -126,10 +146,17 @@ class HomeFragment : override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { super.onCreateOptionsMenu(menu, inflater) + inflater.inflate(R.menu.menu_main, menu) menu.removeItem(R.id.action_grid_size) menu.removeItem(R.id.action_layout_type) menu.removeItem(R.id.action_sort_order) menu.findItem(R.id.action_settings).setShowAsAction(SHOW_AS_ACTION_IF_ROOM) + ToolbarContentTintHelper.handleOnCreateOptionsMenu( + requireContext(), + toolbar, + menu, + ATHToolbarActivity.getToolbarBackgroundColor(toolbar) + ) } companion object { @@ -141,4 +168,28 @@ class HomeFragment : return HomeFragment() } } -} \ No newline at end of file + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.action_settings -> findNavController().navigate( + R.id.settingsActivity, + null, + navOptions + ) + R.id.action_import_playlist -> ImportPlaylistDialog().show( + childFragmentManager, + "ImportPlaylist" + ) + R.id.action_add_to_playlist -> CreatePlaylistDialog.create(emptyList()).show( + childFragmentManager, + "ShowCreatePlaylistDialog" + ) + } + return super.onOptionsItemSelected(item) + } + + override fun onPrepareOptionsMenu(menu: Menu) { + super.onPrepareOptionsMenu(menu) + ToolbarContentTintHelper.handleOnPrepareOptionsMenu(requireActivity(), toolbar) + } +} diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/library/LibraryFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/library/LibraryFragment.kt index 3569bf7aa..7dc89354a 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/library/LibraryFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/library/LibraryFragment.kt @@ -1,18 +1,39 @@ +/* + * 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 io.github.muntashirakon.music.fragments.library import android.os.Bundle import android.view.Menu import android.view.MenuInflater import android.view.MenuItem +import android.view.View +import androidx.core.text.HtmlCompat +import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.findNavController import androidx.navigation.ui.NavigationUI +import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.common.ATHToolbarActivity.getToolbarBackgroundColor import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import io.github.muntashirakon.music.R import io.github.muntashirakon.music.dialogs.CreatePlaylistDialog import io.github.muntashirakon.music.dialogs.ImportPlaylistDialog -import io.github.muntashirakon.music.extensions.findNavController +import io.github.muntashirakon.music.extensions.whichFragment import io.github.muntashirakon.music.fragments.base.AbsMainActivityFragment +import io.github.muntashirakon.music.model.CategoryInfo +import io.github.muntashirakon.music.state.NowPlayingPanelState +import io.github.muntashirakon.music.util.PreferenceUtil import kotlinx.android.synthetic.main.fragment_library.* class LibraryFragment : AbsMainActivityFragment(R.layout.fragment_library) { @@ -20,8 +41,7 @@ class LibraryFragment : AbsMainActivityFragment(R.layout.fragment_library) { override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) setHasOptionsMenu(true) - retainInstance = true - mainActivity.hideBottomBarVisibility(true) + mainActivity.setBottomBarVisibility(View.VISIBLE) mainActivity.setSupportActionBar(toolbar) mainActivity.supportActionBar?.title = null toolbar.setNavigationOnClickListener { @@ -40,8 +60,20 @@ class LibraryFragment : AbsMainActivityFragment(R.layout.fragment_library) { } private fun setupNavigationController() { - val navController = findNavController(R.id.fragment_container) + val navHostFragment = whichFragment(R.id.fragment_container) + val navController = navHostFragment.navController + val navInflater = navController.navInflater + val navGraph = navInflater.inflate(R.navigation.library_graph) + + val categoryInfo: CategoryInfo = PreferenceUtil.libraryCategory.first { it.visible } + if (categoryInfo.visible) { + navGraph.startDestination = categoryInfo.category.id + } + navController.graph = navGraph NavigationUI.setupWithNavController(mainActivity.getBottomNavigationView(), navController) + navController.addOnDestinationChangedListener { _, _, _ -> + appBarLayout.setExpanded(true, true) + } } override fun onPrepareOptionsMenu(menu: Menu) { @@ -78,4 +110,4 @@ class LibraryFragment : AbsMainActivityFragment(R.layout.fragment_library) { } return super.onOptionsItemSelected(item) } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/player/NowPlayingPlayerFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/player/NowPlayingPlayerFragment.kt deleted file mode 100644 index 0f7a6c1a7..000000000 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/player/NowPlayingPlayerFragment.kt +++ /dev/null @@ -1,42 +0,0 @@ -package io.github.muntashirakon.music.fragments.player - -import android.os.Bundle -import androidx.fragment.app.Fragment -import androidx.navigation.NavController -import io.github.muntashirakon.music.R -import io.github.muntashirakon.music.extensions.findNavController -import io.github.muntashirakon.music.fragments.NowPlayingScreen.* -import io.github.muntashirakon.music.util.PreferenceUtil - -class NowPlayingPlayerFragment : Fragment(R.layout.fragment_now_playing_player) { - companion object { - const val TAG = "NowPlaying" - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - val navController = findNavController(R.id.playerFragmentContainer) - updateNowPlaying(navController) - } - - private fun updateNowPlaying(navController: NavController) { - when (PreferenceUtil.nowPlayingScreen) { - Adaptive -> navController.navigate(R.id.adaptiveFragment) - Blur -> navController.navigate(R.id.blurPlayerFragment) - BlurCard -> navController.navigate(R.id.cardBlurFragment) - Card -> navController.navigate(R.id.cardFragment) - Circle -> navController.navigate(R.id.circlePlayerFragment) - Classic -> navController.navigate(R.id.classicPlayerFragment) - Color -> navController.navigate(R.id.colorFragment) - Fit -> navController.navigate(R.id.fitFragment) - Flat -> navController.navigate(R.id.flatPlayerFragment) - Full -> navController.navigate(R.id.fullPlayerFragment) - Gradient -> navController.navigate(R.id.gradientPlayerFragment) - Material -> navController.navigate(R.id.materialFragment) - Plain -> navController.navigate(R.id.plainPlayerFragment) - Simple -> navController.navigate(R.id.simplePlayerFragment) - Tiny -> navController.navigate(R.id.tinyPlayerFragment) - else -> navController.navigate(R.id.playerFragment) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/player/PlayerAlbumCoverFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/player/PlayerAlbumCoverFragment.kt index 07869bdbb..17dee446e 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/player/PlayerAlbumCoverFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/player/PlayerAlbumCoverFragment.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 io.github.muntashirakon.music.fragments.player import android.os.Bundle @@ -15,7 +29,6 @@ import io.github.muntashirakon.music.util.PreferenceUtil import io.github.muntashirakon.music.util.color.MediaNotificationProcessor import kotlinx.android.synthetic.main.fragment_player_album_cover.* - class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_player_album_cover), ViewPager.OnPageChangeListener { @@ -114,7 +127,6 @@ class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_playe callbacks = listener } - interface Callbacks { fun onColorChanged(color: MediaNotificationProcessor) diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/player/adaptive/AdaptiveFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/player/adaptive/AdaptiveFragment.kt index 7ed24ad3c..b88eb59f1 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/player/adaptive/AdaptiveFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/player/adaptive/AdaptiveFragment.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 io.github.muntashirakon.music.fragments.player.adaptive import android.os.Bundle @@ -111,4 +125,4 @@ class AdaptiveFragment : AbsPlayerFragment(R.layout.fragment_adaptive_player) { override val paletteColor: Int get() = lastColor -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/player/adaptive/AdaptivePlaybackControlsFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/player/adaptive/AdaptivePlaybackControlsFragment.kt index afa701c64..e18eb2949 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/player/adaptive/AdaptivePlaybackControlsFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/player/adaptive/AdaptivePlaybackControlsFragment.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 io.github.muntashirakon.music.fragments.player.adaptive import android.animation.ObjectAnimator @@ -25,7 +39,6 @@ import io.github.muntashirakon.music.helper.PlayPauseButtonOnClickHandler import io.github.muntashirakon.music.misc.SimpleOnSeekbarChangeListener import io.github.muntashirakon.music.service.MusicService import io.github.muntashirakon.music.util.MusicUtil - import io.github.muntashirakon.music.util.PreferenceUtil import io.github.muntashirakon.music.util.color.MediaNotificationProcessor import kotlinx.android.synthetic.main.fragment_adaptive_player_playback_controls.* @@ -43,7 +56,8 @@ class AdaptivePlaybackControlsFragment : } override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, + inflater: LayoutInflater, + container: ViewGroup?, savedInstanceState: Bundle? ): View? { return inflater.inflate( @@ -149,7 +163,7 @@ class AdaptivePlaybackControlsFragment : } private fun updatePlayPauseColor() { - //playPauseButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + // playPauseButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); } private fun setUpPlayPauseFab() { @@ -188,11 +202,9 @@ class AdaptivePlaybackControlsFragment : } override fun show() { - } override fun hide() { - } override fun updateShuffleState() { diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/player/blur/BlurPlaybackControlsFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/player/blur/BlurPlaybackControlsFragment.kt index dad6c75a8..d52de93d9 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/player/blur/BlurPlaybackControlsFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/player/blur/BlurPlaybackControlsFragment.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 io.github.muntashirakon.music.fragments.player.blur import android.animation.ObjectAnimator diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/player/blur/BlurPlayerFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/player/blur/BlurPlayerFragment.kt index eb351fcea..0e9647190 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/player/blur/BlurPlayerFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/player/blur/BlurPlayerFragment.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 io.github.muntashirakon.music.fragments.player.blur import android.content.SharedPreferences @@ -31,7 +45,6 @@ class BlurPlayerFragment : AbsPlayerFragment(R.layout.fragment_blur), private var lastColor: Int = 0 - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setUpSubFragments() @@ -139,4 +152,3 @@ class BlurPlayerFragment : AbsPlayerFragment(R.layout.fragment_blur), } } } - diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/player/card/CardFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/player/card/CardFragment.kt index 09af83b2c..bb3d1c3dc 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/player/card/CardFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/player/card/CardFragment.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 io.github.muntashirakon.music.fragments.player.card import android.graphics.Color diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/player/card/CardPlaybackControlsFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/player/card/CardPlaybackControlsFragment.kt index 6db37d625..8166db238 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/player/card/CardPlaybackControlsFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/player/card/CardPlaybackControlsFragment.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 io.github.muntashirakon.music.fragments.player.card import android.animation.ObjectAnimator @@ -39,7 +53,6 @@ class CardPlaybackControlsFragment : progressViewUpdateHelper = MusicProgressViewUpdateHelper(this) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setUpMusicControllers() @@ -138,7 +151,7 @@ class CardPlaybackControlsFragment : } private fun updatePlayPauseColor() { - //playPauseButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + // playPauseButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); } private fun setUpPlayPauseFab() { @@ -232,11 +245,11 @@ class CardPlaybackControlsFragment : } public override fun show() { - //Ignore + // Ignore } public override fun hide() { - //Ignore + // Ignore } override fun setUpProgressSlider() { diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/player/cardblur/CardBlurFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/player/cardblur/CardBlurFragment.kt index 92b802a4d..8ef50eb16 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/player/cardblur/CardBlurFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/player/cardblur/CardBlurFragment.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 io.github.muntashirakon.music.fragments.player.cardblur import android.content.SharedPreferences @@ -70,7 +84,6 @@ class CardBlurFragment : AbsPlayerFragment(R.layout.fragment_card_blur_player), toggleFavorite(MusicPlayerRemote.currentSong) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setUpSubFragments() diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/player/cardblur/CardBlurPlaybackControlsFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/player/cardblur/CardBlurPlaybackControlsFragment.kt index 8f515b360..e41824dd9 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/player/cardblur/CardBlurPlaybackControlsFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/player/cardblur/CardBlurPlaybackControlsFragment.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 io.github.muntashirakon.music.fragments.player.cardblur import android.animation.ObjectAnimator diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/player/circle/CirclePlayerFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/player/circle/CirclePlayerFragment.kt index 244a5b183..d69a63af7 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/player/circle/CirclePlayerFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/player/circle/CirclePlayerFragment.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 io.github.muntashirakon.music.fragments.player.circle import android.animation.ObjectAnimator @@ -45,7 +45,6 @@ import io.github.muntashirakon.music.helper.PlayPauseButtonOnClickHandler import io.github.muntashirakon.music.misc.SimpleOnSeekbarChangeListener import io.github.muntashirakon.music.util.MusicUtil import io.github.muntashirakon.music.util.PreferenceUtil - import io.github.muntashirakon.music.util.ViewUtil import io.github.muntashirakon.music.util.color.MediaNotificationProcessor import io.github.muntashirakon.music.views.SeekArc @@ -171,7 +170,6 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player), get() = Color.BLACK override fun onColorChanged(color: MediaNotificationProcessor) { - } override fun onFavoriteToggled() { @@ -264,4 +262,4 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player), 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/io/github/muntashirakon/music/fragments/player/classic/ClassicPlayerFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/player/classic/ClassicPlayerFragment.kt index 6721be988..23c9b348c 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/player/classic/ClassicPlayerFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/player/classic/ClassicPlayerFragment.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 io.github.muntashirakon.music.fragments.player.classic import android.animation.ObjectAnimator @@ -189,7 +203,6 @@ class ClassicPlayerFragment : AbsPlayerFragment(R.layout.fragment_classic_player recyclerViewDragDropManager?.cancelDrag() super.onPause() progressViewUpdateHelper.stop() - } override fun onServiceConnected() { @@ -201,7 +214,6 @@ class ClassicPlayerFragment : AbsPlayerFragment(R.layout.fragment_classic_player override fun onPlayStateChanged() { updatePlayPauseDrawableState() - } override fun onRepeatModeChanged() { @@ -218,23 +230,19 @@ class ClassicPlayerFragment : AbsPlayerFragment(R.layout.fragment_classic_player updateQueuePosition() } - override fun onQueueChanged() { super.onQueueChanged() updateQueue() } - override fun playerToolbar(): Toolbar? { return playerToolbar } override fun onShow() { - } override fun onHide() { - } override fun onBackPressed(): Boolean { @@ -351,7 +359,6 @@ class ClassicPlayerFragment : AbsPlayerFragment(R.layout.fragment_classic_player ) } - private fun setupRecyclerView() { playingQueueAdapter = PlayingQueueAdapter( requireActivity() as AppCompatActivity, @@ -414,7 +421,6 @@ class ClassicPlayerFragment : AbsPlayerFragment(R.layout.fragment_classic_player setUpProgressSlider() } - private fun setUpPrevNext() { updatePrevNextColor() nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } @@ -468,7 +474,6 @@ class ClassicPlayerFragment : AbsPlayerFragment(R.layout.fragment_classic_player } } - override fun onLayoutChange( v: View?, left: Int, @@ -486,4 +491,4 @@ class ClassicPlayerFragment : AbsPlayerFragment(R.layout.fragment_classic_player val panel = getQueuePanel() panel.peekHeight = finalHeight } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/player/color/ColorFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/player/color/ColorFragment.kt index 77e5cd83f..63644ab23 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/player/color/ColorFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/player/color/ColorFragment.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 io.github.muntashirakon.music.fragments.player.color import android.animation.ValueAnimator @@ -37,7 +51,7 @@ class ColorFragment : AbsPlayerFragment(R.layout.fragment_color_player) { navigationColor = color.backgroundColor colorGradientBackground?.setBackgroundColor(color.backgroundColor) - playerActivity?.setLightNavigationBar(ColorUtil.isColorLight(color.backgroundColor)) + serviceActivity?.setLightNavigationBar(ColorUtil.isColorLight(color.backgroundColor)) Handler().post { ToolbarContentTintHelper.colorizeToolbar( playerToolbar, diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/player/color/ColorPlaybackControlsFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/player/color/ColorPlaybackControlsFragment.kt index 8c430fda1..d740ae400 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/player/color/ColorPlaybackControlsFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/player/color/ColorPlaybackControlsFragment.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 io.github.muntashirakon.music.fragments.player.color import android.animation.ObjectAnimator @@ -24,7 +38,6 @@ import io.github.muntashirakon.music.misc.SimpleOnSeekbarChangeListener import io.github.muntashirakon.music.service.MusicService import io.github.muntashirakon.music.util.MusicUtil import io.github.muntashirakon.music.util.PreferenceUtil - import io.github.muntashirakon.music.util.color.MediaNotificationProcessor import kotlinx.android.synthetic.main.fragment_color_player_playback_controls.* diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/player/fit/FitFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/player/fit/FitFragment.kt index 197c31c37..74a16b86f 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/player/fit/FitFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/player/fit/FitFragment.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 io.github.muntashirakon.music.fragments.player.fit import android.os.Bundle diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/player/fit/FitPlaybackControlsFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/player/fit/FitPlaybackControlsFragment.kt index 83a3781b1..e21b28947 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/player/fit/FitPlaybackControlsFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/player/fit/FitPlaybackControlsFragment.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 io.github.muntashirakon.music.fragments.player.fit import android.animation.ObjectAnimator @@ -31,7 +45,6 @@ import kotlinx.android.synthetic.main.fragment_fit_playback_controls.* class FitPlaybackControlsFragment : AbsPlayerControlsFragment(R.layout.fragment_fit_playback_controls) { - private var lastPlaybackControlsColor: Int = 0 private var lastDisabledPlaybackControlsColor: Int = 0 private lateinit var progressViewUpdateHelper: MusicProgressViewUpdateHelper @@ -41,7 +54,6 @@ class FitPlaybackControlsFragment : progressViewUpdateHelper = MusicProgressViewUpdateHelper(this) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setUpMusicControllers() @@ -232,7 +244,6 @@ class FitPlaybackControlsFragment : } } - private fun showBonceAnimation() { playPauseButton.apply { clearAnimation() diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/player/flat/FlatPlaybackControlsFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/player/flat/FlatPlaybackControlsFragment.kt index 68f803195..6054f131b 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/player/flat/FlatPlaybackControlsFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/player/flat/FlatPlaybackControlsFragment.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 io.github.muntashirakon.music.fragments.player.flat import android.animation.ObjectAnimator diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/player/flat/FlatPlayerFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/player/flat/FlatPlayerFragment.kt index 2c346b163..e42408c53 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/player/flat/FlatPlayerFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/player/flat/FlatPlayerFragment.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 io.github.muntashirakon.music.fragments.player.flat import android.animation.ArgbEvaluator @@ -63,7 +77,6 @@ class FlatPlayerFragment : AbsPlayerFragment(R.layout.fragment_flat_player) { intArrayOf(animation.animatedValue as Int, android.R.color.transparent), 0 ) colorGradientBackground?.background = drawable - } valueAnimator?.setDuration(ViewUtil.RETRO_MUSIC_ANIM_TIME.toLong())?.start() } diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/player/full/FullPlaybackControlsFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/player/full/FullPlaybackControlsFragment.kt index dab3eec39..f6df65df5 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/player/full/FullPlaybackControlsFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/player/full/FullPlaybackControlsFragment.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 io.github.muntashirakon.music.fragments.player.full import android.animation.ObjectAnimator @@ -67,7 +81,6 @@ class FullPlaybackControlsFragment : progressViewUpdateHelper.stop() } - public override fun show() { playPauseButton!!.animate() .scaleX(1f) @@ -313,6 +326,4 @@ class FullPlaybackControlsFragment : fun onFavoriteToggled() { toggleFavorite(MusicPlayerRemote.currentSong) } - - } diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/player/full/FullPlayerFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/player/full/FullPlayerFragment.kt index 35085a08a..a490dcfa5 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/player/full/FullPlayerFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/player/full/FullPlayerFragment.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 io.github.muntashirakon.music.fragments.player.full import android.content.res.ColorStateList @@ -8,12 +22,10 @@ import android.widget.FrameLayout import android.widget.TextView import androidx.appcompat.widget.Toolbar import androidx.core.os.bundleOf -import androidx.lifecycle.lifecycleScope +import androidx.navigation.fragment.findNavController import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper -import com.bumptech.glide.Glide import io.github.muntashirakon.music.EXTRA_ARTIST_ID import io.github.muntashirakon.music.R -import io.github.muntashirakon.music.extensions.findActivityNavController import io.github.muntashirakon.music.extensions.hide import io.github.muntashirakon.music.extensions.show import io.github.muntashirakon.music.extensions.whichFragment @@ -26,17 +38,12 @@ import io.github.muntashirakon.music.helper.MusicProgressViewUpdateHelper import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.model.lyrics.AbsSynchronizedLyrics import io.github.muntashirakon.music.model.lyrics.Lyrics -import io.github.muntashirakon.music.repository.ArtistRepository import io.github.muntashirakon.music.util.color.MediaNotificationProcessor +import com.bumptech.glide.Glide import kotlinx.android.synthetic.main.fragment_full.* -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import org.koin.android.ext.android.inject class FullPlayerFragment : AbsPlayerFragment(R.layout.fragment_full), MusicProgressViewUpdateHelper.Callback { - private val artistRepository by inject() private lateinit var lyricsLayout: FrameLayout private lateinit var lyricsLine1: TextView private lateinit var lyricsLine2: TextView @@ -155,11 +162,10 @@ class FullPlayerFragment : AbsPlayerFragment(R.layout.fragment_full), private fun setupArtist() { artistImage.setOnClickListener { mainActivity.collapsePanel() - findActivityNavController(R.id.fragment_container) - .navigate( - R.id.artistDetailsFragment, - bundleOf(EXTRA_ARTIST_ID to MusicPlayerRemote.currentSong.artistId) - ) + findNavController().navigate( + R.id.artistDetailsFragment, + bundleOf(EXTRA_ARTIST_ID to MusicPlayerRemote.currentSong.artistId), + ) } } @@ -222,19 +228,16 @@ class FullPlayerFragment : AbsPlayerFragment(R.layout.fragment_full), } private fun updateArtistImage() { - lifecycleScope.launch { - val artist = artistRepository.artist(MusicPlayerRemote.currentSong.artistId) - withContext(Dispatchers.Main) { + libraryViewModel.artist(MusicPlayerRemote.currentSong.artistId) + .observe(viewLifecycleOwner, { artist -> ArtistGlideRequest.Builder.from(Glide.with(requireContext()), artist) .generatePalette(requireContext()) .build() .into(object : RetroMusicColoredTarget(artistImage) { override fun onColorReady(colors: MediaNotificationProcessor) { - } }) - } - } + }) } override fun onQueueChanged() { diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/player/gradient/GradientPlayerFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/player/gradient/GradientPlayerFragment.kt index 31f068ce1..e8276b49b 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/player/gradient/GradientPlayerFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/player/gradient/GradientPlayerFragment.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 io.github.muntashirakon.music.fragments.player.gradient import android.animation.ObjectAnimator @@ -123,7 +137,6 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play progressViewUpdateHelper = MusicProgressViewUpdateHelper(this) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) hideVolumeIfAvailable() @@ -155,8 +168,8 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play } override fun onPause() { - recyclerViewDragDropManager?.cancelDrag() super.onPause() + recyclerViewDragDropManager?.cancelDrag() progressViewUpdateHelper.stop() } @@ -165,11 +178,9 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play } override fun onShow() { - } override fun onHide() { - } override fun onBackPressed(): Boolean { @@ -213,7 +224,7 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play songInfo.setTextColor(lastDisabledPlaybackControlsColor) volumeFragment?.setTintableColor(lastPlaybackControlsColor.ripAlpha()) - ViewUtil.setProgressDrawable(progressSlider, color.primaryTextColor.ripAlpha(), true) + ViewUtil.setProgressDrawable(progressSlider, lastPlaybackControlsColor.ripAlpha(), true) updateRepeatState() updateShuffleState() @@ -324,7 +335,6 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play } } - private fun setUpPlayPauseFab() { playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) } @@ -490,4 +500,4 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play 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/io/github/muntashirakon/music/fragments/player/home/HomePlayerFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/player/home/HomePlayerFragment.kt index bdf2e41ca..9ef545ec6 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/player/home/HomePlayerFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/player/home/HomePlayerFragment.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 io.github.muntashirakon.music.fragments.player.home import android.graphics.Color @@ -15,7 +29,6 @@ import io.github.muntashirakon.music.util.MusicUtil import io.github.muntashirakon.music.util.color.MediaNotificationProcessor import kotlinx.android.synthetic.main.fragment_home_player.* - class HomePlayerFragment : AbsPlayerFragment(R.layout.fragment_home_player), MusicProgressViewUpdateHelper.Callback { private var lastColor: Int = 0 @@ -29,7 +42,6 @@ class HomePlayerFragment : AbsPlayerFragment(R.layout.fragment_home_player), override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setUpPlayerToolbar() - } override fun onResume() { @@ -40,7 +52,6 @@ class HomePlayerFragment : AbsPlayerFragment(R.layout.fragment_home_player), override fun onPause() { super.onPause() progressViewUpdateHelper.stop() - } override fun playerToolbar(): Toolbar? { @@ -48,11 +59,9 @@ class HomePlayerFragment : AbsPlayerFragment(R.layout.fragment_home_player), } override fun onShow() { - } override fun onHide() { - } override fun onServiceConnected() { @@ -118,4 +127,4 @@ class HomePlayerFragment : AbsPlayerFragment(R.layout.fragment_home_player), requireActivity() ) } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/player/lockscreen/LockScreenPlayerControlsFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/player/lockscreen/LockScreenControlsFragment.kt similarity index 97% rename from app/src/main/java/io/github/muntashirakon/music/fragments/player/lockscreen/LockScreenPlayerControlsFragment.kt rename to app/src/main/java/io/github/muntashirakon/music/fragments/player/lockscreen/LockScreenControlsFragment.kt index 60527fcb6..07b4cffc4 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/player/lockscreen/LockScreenPlayerControlsFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/player/lockscreen/LockScreenControlsFragment.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 io.github.muntashirakon.music.fragments.player.lockscreen import android.animation.ObjectAnimator @@ -43,7 +43,7 @@ import kotlinx.android.synthetic.main.fragment_lock_screen_playback_controls.* /** * @author Hemanth S (h4h13). */ -class LockScreenPlayerControlsFragment : +class LockScreenControlsFragment : AbsPlayerControlsFragment(R.layout.fragment_lock_screen_playback_controls) { private var progressViewUpdateHelper: MusicProgressViewUpdateHelper? = null @@ -256,4 +256,4 @@ class LockScreenPlayerControlsFragment : 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/io/github/muntashirakon/music/fragments/player/material/MaterialControlsFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/player/material/MaterialControlsFragment.kt index 4fc21e545..bdb213d94 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/player/material/MaterialControlsFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/player/material/MaterialControlsFragment.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 io.github.muntashirakon.music.fragments.player.material import android.animation.ObjectAnimator diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/player/material/MaterialFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/player/material/MaterialFragment.kt index 97cf9f4cd..370d24aab 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/player/material/MaterialFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/player/material/MaterialFragment.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 io.github.muntashirakon.music.fragments.player.material import android.os.Bundle diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/player/normal/PlayerFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/player/normal/PlayerFragment.kt index 2af9a911e..da016ee61 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/player/normal/PlayerFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/player/normal/PlayerFragment.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 io.github.muntashirakon.music.fragments.player.normal import android.animation.ArgbEvaluator @@ -19,7 +33,6 @@ import io.github.muntashirakon.music.util.color.MediaNotificationProcessor import io.github.muntashirakon.music.views.DrawableGradient import kotlinx.android.synthetic.main.fragment_player.* - class PlayerFragment : AbsPlayerFragment(R.layout.fragment_player) { private var lastColor: Int = 0 @@ -29,7 +42,6 @@ class PlayerFragment : AbsPlayerFragment(R.layout.fragment_player) { private lateinit var controlsFragment: PlayerPlaybackControlsFragment private var valueAnimator: ValueAnimator? = null - private fun colorize(i: Int) { if (valueAnimator != null) { valueAnimator?.cancel() @@ -99,14 +111,12 @@ class PlayerFragment : AbsPlayerFragment(R.layout.fragment_player) { toggleFavorite(MusicPlayerRemote.currentSong) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setUpSubFragments() setUpPlayerToolbar() } - private fun setUpSubFragments() { controlsFragment = childFragmentManager.findFragmentById(R.id.playbackControlsFragment) as PlayerPlaybackControlsFragment @@ -146,4 +156,3 @@ class PlayerFragment : AbsPlayerFragment(R.layout.fragment_player) { } } } - diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/player/normal/PlayerPlaybackControlsFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/player/normal/PlayerPlaybackControlsFragment.kt index d1f68f4fd..0d784b1d3 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/player/normal/PlayerPlaybackControlsFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/player/normal/PlayerPlaybackControlsFragment.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 io.github.muntashirakon.music.fragments.player.normal import android.animation.ObjectAnimator diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/player/peak/PeakPlayerControlFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/player/peak/PeakPlayerControlFragment.kt index 6f82eb533..0c936b41f 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/player/peak/PeakPlayerControlFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/player/peak/PeakPlayerControlFragment.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 io.github.muntashirakon.music.fragments.player.peak import android.animation.ObjectAnimator @@ -226,6 +226,4 @@ class PeakPlayerControlFragment : AbsPlayerControlsFragment(R.layout.fragment_pe override fun onShuffleModeChanged() { updateShuffleState() } - - -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/player/peak/PeakPlayerFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/player/peak/PeakPlayerFragment.kt index 08c07dde2..1bdbbf5e6 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/player/peak/PeakPlayerFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/player/peak/PeakPlayerFragment.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 io.github.muntashirakon.music.fragments.player.peak import android.os.Bundle @@ -43,7 +43,6 @@ class PeakPlayerFragment : AbsPlayerFragment(R.layout.fragment_peak_player) { setUpPlayerToolbar() setUpSubFragments() title.isSelected = true - } private fun setUpSubFragments() { @@ -120,4 +119,4 @@ class PeakPlayerFragment : AbsPlayerFragment(R.layout.fragment_peak_player) { super.onPlayingMetaChanged() updateSong() } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/player/plain/PlainPlaybackControlsFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/player/plain/PlainPlaybackControlsFragment.kt index 5d7f461c7..48b950ecb 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/player/plain/PlainPlaybackControlsFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/player/plain/PlainPlaybackControlsFragment.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 io.github.muntashirakon.music.fragments.player.plain import android.animation.ObjectAnimator @@ -85,7 +99,6 @@ class PlainPlaybackControlsFragment : progressViewUpdateHelper = MusicProgressViewUpdateHelper(this) } - override fun onResume() { super.onResume() progressViewUpdateHelper.start() diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/player/plain/PlainPlayerFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/player/plain/PlainPlayerFragment.kt index c3744ced9..f302e911d 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/player/plain/PlainPlayerFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/player/plain/PlainPlayerFragment.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 io.github.muntashirakon.music.fragments.player.plain import android.os.Bundle diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/player/simple/SimplePlaybackControlsFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/player/simple/SimplePlaybackControlsFragment.kt index 0faac0bb7..c13b72823 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/player/simple/SimplePlaybackControlsFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/player/simple/SimplePlaybackControlsFragment.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 io.github.muntashirakon.music.fragments.player.simple import android.graphics.PorterDuff diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/player/simple/SimplePlayerFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/player/simple/SimplePlayerFragment.kt index 5649cd1bb..57625c23b 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/player/simple/SimplePlayerFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/player/simple/SimplePlayerFragment.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 io.github.muntashirakon.music.fragments.player.simple import android.os.Bundle @@ -29,7 +43,6 @@ class SimplePlayerFragment : AbsPlayerFragment(R.layout.fragment_simple_player) private lateinit var controlsFragment: SimplePlaybackControlsFragment - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setUpSubFragments() diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/player/tiny/TinyPlaybackControlsFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/player/tiny/TinyPlaybackControlsFragment.kt index abe1e09cd..af096df90 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/player/tiny/TinyPlaybackControlsFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/player/tiny/TinyPlaybackControlsFragment.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 io.github.muntashirakon.music.fragments.player.tiny import android.graphics.PorterDuff @@ -37,7 +51,6 @@ class TinyPlaybackControlsFragment : private var lastPlaybackControlsColor: Int = 0 private var lastDisabledPlaybackControlsColor: Int = 0 - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setUpMusicControllers() @@ -102,5 +115,4 @@ class TinyPlaybackControlsFragment : override fun onShuffleModeChanged() { updateShuffleState() } - -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/player/tiny/TinyPlayerFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/player/tiny/TinyPlayerFragment.kt index 7a563de73..4db639c8a 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/player/tiny/TinyPlayerFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/player/tiny/TinyPlayerFragment.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 io.github.muntashirakon.music.fragments.player.tiny import android.animation.AnimatorSet @@ -29,7 +43,6 @@ class TinyPlayerFragment : AbsPlayerFragment(R.layout.fragment_tiny_player), private var lastColor: Int = 0 private var toolbarColor: Int = 0 - override fun playerToolbar(): Toolbar { return playerToolbar } @@ -48,11 +61,9 @@ class TinyPlayerFragment : AbsPlayerFragment(R.layout.fragment_tiny_player), return toolbarColor } - override val paletteColor: Int get() = lastColor - override fun onColorChanged(color: MediaNotificationProcessor) { lastColor = color.backgroundColor libraryViewModel.updateColor(color.backgroundColor) @@ -109,7 +120,6 @@ class TinyPlayerFragment : AbsPlayerFragment(R.layout.fragment_tiny_player), } } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) title.isSelected = true @@ -170,4 +180,4 @@ class TinyPlayerFragment : AbsPlayerFragment(R.layout.fragment_tiny_player), MusicUtil.getReadableDurationString(progress.toLong()) ) } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/playlists/PlaylistDetailsFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/playlists/PlaylistDetailsFragment.kt index 08f1a2318..9e59ac5a9 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/playlists/PlaylistDetailsFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/playlists/PlaylistDetailsFragment.kt @@ -5,89 +5,75 @@ import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View +import androidx.core.view.ViewCompat +import androidx.core.view.isVisible import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import code.name.monkey.appthemehelper.util.ATHUtil import io.github.muntashirakon.music.R -import io.github.muntashirakon.music.adapter.song.OrderablePlaylistSongAdapter -import io.github.muntashirakon.music.adapter.song.SongAdapter +import io.github.muntashirakon.music.adapter.song.PlaylistSongAdapter import io.github.muntashirakon.music.db.PlaylistWithSongs import io.github.muntashirakon.music.db.toSongs import io.github.muntashirakon.music.extensions.dipToPix import io.github.muntashirakon.music.fragments.base.AbsMainActivityFragment import io.github.muntashirakon.music.helper.menu.PlaylistMenuHelper import io.github.muntashirakon.music.model.Song -import io.github.muntashirakon.music.util.PlaylistsUtil -import com.h6ah4i.android.widget.advrecyclerview.animator.RefactoredDefaultItemAnimator -import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager -import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils +import io.github.muntashirakon.music.state.NowPlayingPanelState +import com.google.android.material.transition.MaterialContainerTransform import kotlinx.android.synthetic.main.fragment_playlist_detail.* import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playlist_detail) { private val arguments by navArgs() - private val viewModel: PlaylistDetailsViewModel by viewModel { + private val viewModel by viewModel { parametersOf(arguments.extraPlaylist) } private lateinit var playlist: PlaylistWithSongs - private lateinit var adapter: SongAdapter + private lateinit var playlistSongAdapter: PlaylistSongAdapter - private var wrappedAdapter: RecyclerView.Adapter<*>? = null - private var recyclerViewDragDropManager: RecyclerViewDragDropManager? = null + private fun setUpTransitions() { + val transform = MaterialContainerTransform() + transform.setAllContainerColors(ATHUtil.resolveColor(requireContext(), R.attr.colorSurface)) + sharedElementEnterTransition = transform + } - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setUpTransitions() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) setHasOptionsMenu(true) + mainActivity.setBottomBarVisibility(View.GONE) mainActivity.addMusicServiceEventListener(viewModel) mainActivity.setSupportActionBar(toolbar) - mainActivity.hideBottomBarVisibility(false) - + ViewCompat.setTransitionName(container, "playlist") playlist = arguments.extraPlaylist toolbar.title = playlist.playlistEntity.playlistName - setUpRecyclerView() - viewModel.getSongs().observe(viewLifecycleOwner, { songs(it.toSongs()) }) } private fun setUpRecyclerView() { - recyclerView.layoutManager = LinearLayoutManager(requireContext()) - recyclerViewDragDropManager = RecyclerViewDragDropManager() - val animator = RefactoredDefaultItemAnimator() - adapter = - OrderablePlaylistSongAdapter( - playlist.playlistEntity, - requireActivity(), - ArrayList(), - R.layout.item_list, - null, - object : OrderablePlaylistSongAdapter.OnMoveItemListener { - override fun onMoveItem(fromPosition: Int, toPosition: Int) { - if (PlaylistsUtil.moveItem( - requireContext(), - playlist.playlistEntity.playListId, - fromPosition, - toPosition - ) - ) { - val song = adapter.dataSet.removeAt(fromPosition) - adapter.dataSet.add(toPosition, song) - adapter.notifyItemMoved(fromPosition, toPosition) - } - } - }) - wrappedAdapter = recyclerViewDragDropManager!!.createWrappedAdapter(adapter) - - recyclerView.adapter = wrappedAdapter - recyclerView.itemAnimator = animator - - recyclerViewDragDropManager?.attachRecyclerView(recyclerView) - - adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { + playlistSongAdapter = PlaylistSongAdapter( + playlist.playlistEntity, + requireActivity(), + ArrayList(), + R.layout.item_list, + null, + ) + recyclerView.apply { + layoutManager = LinearLayoutManager(requireContext()) + adapter = playlistSongAdapter + } + playlistSongAdapter.registerAdapterDataObserver(object : + RecyclerView.AdapterDataObserver() { override fun onChanged() { super.onChanged() checkIsEmpty() @@ -97,10 +83,7 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { super.onCreateOptionsMenu(menu, inflater) - val menuRes =/* if (playlist is AbsCustomPlaylist) - R.menu.menu_smart_playlist_detail - else*/ R.menu.menu_playlist_detail - inflater.inflate(menuRes, menu) + inflater.inflate(R.menu.menu_playlist_detail, menu) } override fun onOptionsItemSelected(item: MenuItem): Boolean { @@ -114,32 +97,13 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli private fun checkIsEmpty() { checkForPadding() - empty.visibility = if (adapter.itemCount == 0) View.VISIBLE else View.GONE - emptyText.visibility = if (adapter.itemCount == 0) View.VISIBLE else View.GONE - } - - override fun onPause() { - if (recyclerViewDragDropManager != null) { - recyclerViewDragDropManager!!.cancelDrag() - } - super.onPause() + empty.isVisible = playlistSongAdapter.itemCount == 0 + emptyText.isVisible = playlistSongAdapter.itemCount == 0 } override fun onDestroy() { - if (recyclerViewDragDropManager != null) { - recyclerViewDragDropManager!!.release() - recyclerViewDragDropManager = null - } - - if (recyclerView != null) { - recyclerView!!.itemAnimator = null - recyclerView!!.adapter = null - } - - if (wrappedAdapter != null) { - WrapperAdapterUtils.releaseAll(wrappedAdapter) - wrappedAdapter = null - } + recyclerView?.itemAnimator = null + recyclerView?.adapter = null super.onDestroy() } @@ -151,7 +115,7 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli fun songs(songs: List) { progressIndicator.hide() if (songs.isNotEmpty()) { - adapter.swapDataSet(songs) + playlistSongAdapter.swapDataSet(songs) } else { showEmptyView() } diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/playlists/PlaylistDetailsViewModel.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/playlists/PlaylistDetailsViewModel.kt index 56a98ae2a..7602c206d 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/playlists/PlaylistDetailsViewModel.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/playlists/PlaylistDetailsViewModel.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 io.github.muntashirakon.music.fragments.playlists import androidx.lifecycle.LiveData @@ -5,45 +19,21 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import io.github.muntashirakon.music.db.PlaylistWithSongs import io.github.muntashirakon.music.db.SongEntity -import io.github.muntashirakon.music.interfaces.MusicServiceEventListener +import io.github.muntashirakon.music.interfaces.IMusicServiceEventListener import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.repository.RealRepository class PlaylistDetailsViewModel( private val realRepository: RealRepository, private var playlist: PlaylistWithSongs -) : ViewModel(), MusicServiceEventListener { +) : ViewModel(), IMusicServiceEventListener { - private val _playListSongs = MutableLiveData>() - private val _playlist = MutableLiveData().apply { - postValue(playlist) - } + private val playListSongs = MutableLiveData>() - fun getPlaylist(): LiveData = _playlist - - fun getSongs(): LiveData> = realRepository.playlistSongs(playlist.playlistEntity) - - - override fun onMediaStoreChanged() { - /*if (playlist !is AbsCustomPlaylist) { - // Playlist deleted - if (!PlaylistsUtil.doesPlaylistExist(App.getContext(), playlist.id)) { - //TODO Finish the page - return - } - // Playlist renamed - val playlistName = - PlaylistsUtil.getNameForPlaylist(App.getContext(), playlist.id.toLong()) - if (playlistName != playlist.name) { - viewModelScope.launch { - playlist = realRepository.playlist(playlist.id) - _playlist.postValue(playlist) - } - } - } - loadPlaylistSongs(playlist)*/ - } + fun getSongs(): LiveData> = + realRepository.playlistSongs(playlist.playlistEntity.playListId) + override fun onMediaStoreChanged() {} override fun onServiceConnected() {} override fun onServiceDisconnected() {} override fun onQueueChanged() {} diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/playlists/PlaylistsFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/playlists/PlaylistsFragment.kt index 18d119a78..db627ee18 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/playlists/PlaylistsFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/playlists/PlaylistsFragment.kt @@ -1,28 +1,48 @@ +/* + * 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 io.github.muntashirakon.music.fragments.playlists import android.os.Bundle import android.view.Menu import android.view.MenuInflater import android.view.MenuItem +import android.view.SubMenu import android.view.View -import androidx.lifecycle.Observer -import androidx.recyclerview.widget.LinearLayoutManager +import androidx.core.os.bundleOf +import androidx.core.view.MenuCompat +import androidx.navigation.fragment.FragmentNavigatorExtras +import androidx.navigation.fragment.findNavController +import androidx.recyclerview.widget.GridLayoutManager import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper +import io.github.muntashirakon.music.EXTRA_PLAYLIST import io.github.muntashirakon.music.R import io.github.muntashirakon.music.adapter.playlist.PlaylistAdapter -import io.github.muntashirakon.music.fragments.base.AbsRecyclerViewFragment -import com.google.android.material.transition.platform.MaterialFadeThrough +import io.github.muntashirakon.music.db.PlaylistWithSongs +import io.github.muntashirakon.music.fragments.ReloadType +import io.github.muntashirakon.music.fragments.base.AbsRecyclerViewCustomGridSizeFragment +import io.github.muntashirakon.music.helper.SortOrder.PlaylistSortOrder +import io.github.muntashirakon.music.interfaces.IPlaylistClickListener +import io.github.muntashirakon.music.util.PreferenceUtil import kotlinx.android.synthetic.main.fragment_library.* -class PlaylistsFragment : AbsRecyclerViewFragment() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - enterTransition = MaterialFadeThrough() - } - +class PlaylistsFragment : + AbsRecyclerViewCustomGridSizeFragment(), + IPlaylistClickListener { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - libraryViewModel.getPlaylists().observe(viewLifecycleOwner, Observer { + libraryViewModel.getPlaylists().observe(viewLifecycleOwner, { if (it.isNotEmpty()) adapter?.swapDataSet(it) else @@ -33,16 +53,17 @@ class PlaylistsFragment : AbsRecyclerViewFragment PlaylistSortOrder.PLAYLIST_A_Z + R.id.action_song_sort_order_desc -> PlaylistSortOrder.PLAYLIST_Z_A + R.id.action_playlist_sort_order -> PlaylistSortOrder.PLAYLIST_SONG_COUNT + R.id.action_playlist_sort_order_desc -> PlaylistSortOrder.PLAYLIST_SONG_COUNT_DESC + else -> PreferenceUtil.playlistSortOrder + } + if (sortOrder != PreferenceUtil.playlistSortOrder) { + item.isChecked = true + setAndSaveSortOrder(sortOrder) + return true + } + return false + } + + private fun createId(menu: SubMenu, id: Int, title: Int, checked: Boolean) { + menu.add(0, id, 0, title).isChecked = checked + } + + + override fun setGridSize(gridSize: Int) { + TODO("Not yet implemented") + } + + override fun setSortOrder(sortOrder: String) { + libraryViewModel.forceReload(ReloadType.Playlists) + } + + override fun loadSortOrder(): String { + return PreferenceUtil.playlistSortOrder + } + + override fun saveSortOrder(sortOrder: String) { + PreferenceUtil.playlistSortOrder = sortOrder + } + + override fun loadGridSize(): Int { + return 1 + } + + override fun saveGridSize(gridColumns: Int) { + //Add grid save + } + + override fun loadGridSizeLand(): Int { + return 2 + } + + override fun saveGridSizeLand(gridColumns: Int) { + //Add land grid save + } + + override fun loadLayoutRes(): Int { + return R.layout.item_list + } + + override fun saveLayoutRes(layoutRes: Int) { + //Save layout + } + + override fun onPlaylistClick(playlistWithSongs: PlaylistWithSongs, view: View) { + findNavController().navigate( + R.id.playlistDetailsFragment, + bundleOf(EXTRA_PLAYLIST to playlistWithSongs), + null, + FragmentNavigatorExtras(view to "playlist") + ) } } diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/queue/PlayingQueueFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/queue/PlayingQueueFragment.kt index ae9538cb1..9794d53ab 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/queue/PlayingQueueFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.fragments.queue @@ -139,4 +140,4 @@ class PlayingQueueFragment : AbsRecyclerViewFragment( + keyboardPopup.apply { + accentColor() + setOnClickListener { + val inputManager = getSystemService( requireContext(), InputMethodManager::class.java ) - inputManager?.showSoftInput(searchView, InputMethodManager.SHOW_IMPLICIT) + inputManager?.showSoftInput(searchView, InputMethodManager.SHOW_IMPLICIT) + } } - if (savedInstanceState != null) { query = savedInstanceState.getString(QUERY) } - - viewModel.getSearchResult().observe(viewLifecycleOwner, Observer { + libraryViewModel.getSearchResult().observe(viewLifecycleOwner, { showData(it) }) } - private fun showData(data: MutableList) { + private fun showData(data: List) { if (data.isNotEmpty()) { searchAdapter.swapDataSet(data) } else { @@ -73,13 +83,14 @@ class SearchFragment : AbsMainActivityFragment(R.layout.fragment_search), TextWa } } - private fun setupRecyclerView() { - searchAdapter = SearchAdapter(requireActivity() as AppCompatActivity, emptyList()) + searchAdapter = SearchAdapter(requireActivity(), emptyList()) searchAdapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { override fun onChanged() { super.onChanged() empty.isVisible = searchAdapter.itemCount < 1 + val height = dipToPix(52f) + recyclerView.setPadding(0, 0, 0, height.toInt()) } }) recyclerView.apply { @@ -103,11 +114,9 @@ class SearchFragment : AbsMainActivityFragment(R.layout.fragment_search), TextWa } override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { - } override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { - } private fun search(query: String) { @@ -115,7 +124,7 @@ class SearchFragment : AbsMainActivityFragment(R.layout.fragment_search), TextWa TransitionManager.beginDelayedTransition(appBarLayout) voiceSearch.isGone = query.isNotEmpty() clearText.isVisible = query.isNotEmpty() - viewModel.search(query) + libraryViewModel.search(query) } private fun startMicSearch() { @@ -140,4 +149,4 @@ class SearchFragment : AbsMainActivityFragment(R.layout.fragment_search), TextWa fun TextInputEditText.clearText() { text = null -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/search/SearchViewModel.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/search/SearchViewModel.kt deleted file mode 100644 index 0ec0b5997..000000000 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/search/SearchViewModel.kt +++ /dev/null @@ -1,20 +0,0 @@ -package io.github.muntashirakon.music.fragments.search - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import io.github.muntashirakon.music.repository.RealRepository -import kotlinx.coroutines.Dispatchers.IO -import kotlinx.coroutines.launch - -class SearchViewModel(private val realRepository: RealRepository) : ViewModel() { - private val results = MutableLiveData>() - - fun getSearchResult(): LiveData> = results - - fun search(query: String?) = viewModelScope.launch(IO) { - val result = realRepository.search(query) - results.postValue(result) - } -} \ No newline at end of file diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/settings/AbsSettingsFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/settings/AbsSettingsFragment.kt index 84e34ee6a..fa1504e4c 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/settings/AbsSettingsFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/settings/AbsSettingsFragment.kt @@ -1,28 +1,29 @@ /* - * 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 io.github.muntashirakon.music.fragments.settings import android.graphics.Color import android.graphics.drawable.ColorDrawable import android.os.Bundle import android.view.View +import android.widget.Toast import androidx.preference.ListPreference import androidx.preference.Preference import androidx.preference.PreferenceManager import code.name.monkey.appthemehelper.common.prefs.supportv7.ATEPreferenceFragmentCompat import io.github.muntashirakon.music.preferences.* +import io.github.muntashirakon.music.util.NavigationUtil /** * @author Hemanth S (h4h13). @@ -55,7 +56,6 @@ abstract class AbsSettingsFragment : ATEPreferenceFragmentCompat() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setDivider(ColorDrawable(Color.TRANSPARENT)) - //listView.setBackgroundColor(ATHUtil.resolveColor(requireContext(), R.attr.colorSurface)) listView.overScrollMode = View.OVER_SCROLL_NEVER listView.setPadding(0, 0, 0, 0) listView.setPaddingRelative(0, 0, 0, 0) diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/settings/AudioSettings.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/settings/AudioSettings.kt index 09977f0d1..bbb27518f 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/settings/AudioSettings.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/settings/AudioSettings.kt @@ -1,41 +1,41 @@ /* - * 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 io.github.muntashirakon.music.fragments.settings import android.content.Intent import android.media.audiofx.AudioEffect import android.os.Bundle import androidx.preference.Preference +import io.github.muntashirakon.music.EQUALIZER import io.github.muntashirakon.music.R import io.github.muntashirakon.music.util.NavigationUtil - /** * @author Hemanth S (h4h13). */ class AudioSettings : AbsSettingsFragment() { override fun invalidateSettings() { - val findPreference: Preference = findPreference("equalizer")!! + val findPreference: Preference? = findPreference(EQUALIZER) if (!hasEqualizer()) { - findPreference.isEnabled = false - findPreference.summary = resources.getString(R.string.no_equalizer) + findPreference?.isEnabled = false + findPreference?.summary = resources.getString(R.string.no_equalizer) } else { - findPreference.isEnabled = true + findPreference?.isEnabled = true } - findPreference.setOnPreferenceClickListener { + findPreference?.setOnPreferenceClickListener { NavigationUtil.openEqualizer(requireActivity()) true } diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/settings/ImageSettingFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/settings/ImageSettingFragment.kt index 4a916b4e8..12c59fbb9 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/settings/ImageSettingFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/settings/ImageSettingFragment.kt @@ -1,22 +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 io.github.muntashirakon.music.fragments.settings import android.os.Bundle import android.view.View import androidx.preference.Preference +import io.github.muntashirakon.music.AUTO_DOWNLOAD_IMAGES_POLICY import io.github.muntashirakon.music.R /** @@ -25,7 +26,7 @@ import io.github.muntashirakon.music.R class ImageSettingFragment : AbsSettingsFragment() { override fun invalidateSettings() { - val autoDownloadImagesPolicy: Preference = findPreference("auto_download_images_policy")!! + val autoDownloadImagesPolicy: Preference = findPreference(AUTO_DOWNLOAD_IMAGES_POLICY)!! setSummary(autoDownloadImagesPolicy) autoDownloadImagesPolicy.setOnPreferenceChangeListener { _, o -> setSummary(autoDownloadImagesPolicy, o) @@ -39,7 +40,7 @@ class ImageSettingFragment : AbsSettingsFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val preference: Preference? = findPreference("auto_download_images_policy") + val preference: Preference? = findPreference(AUTO_DOWNLOAD_IMAGES_POLICY) preference?.let { setSummary(it) } } } diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/settings/MainSettingsFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/settings/MainSettingsFragment.kt index 3e8cb1c74..0bdd8c2e1 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/settings/MainSettingsFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/settings/MainSettingsFragment.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 io.github.muntashirakon.music.fragments.settings import android.content.res.ColorStateList @@ -44,7 +44,8 @@ class MainSettingsFragment : Fragment(), View.OnClickListener { } override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, + inflater: LayoutInflater, + container: ViewGroup?, savedInstanceState: Bundle? ): View? { return inflater.inflate(R.layout.fragment_main_settings, container, false) @@ -62,4 +63,4 @@ class MainSettingsFragment : Fragment(), View.OnClickListener { otherSettings.setOnClickListener(this) aboutSettings.setOnClickListener(this) } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/settings/NotificationSettingsFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/settings/NotificationSettingsFragment.kt index 12233884b..c29aa220d 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/settings/NotificationSettingsFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/settings/NotificationSettingsFragment.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 io.github.muntashirakon.music.fragments.settings import android.content.SharedPreferences @@ -21,10 +21,10 @@ import android.os.Bundle import androidx.preference.Preference import androidx.preference.TwoStatePreference import io.github.muntashirakon.music.CLASSIC_NOTIFICATION +import io.github.muntashirakon.music.COLORED_NOTIFICATION import io.github.muntashirakon.music.R import io.github.muntashirakon.music.util.PreferenceUtil - /** * @author Hemanth S (h4h13). */ @@ -34,7 +34,7 @@ class NotificationSettingsFragment : AbsSettingsFragment(), override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { if (key == CLASSIC_NOTIFICATION) { if (VERSION.SDK_INT >= VERSION_CODES.O) { - findPreference("colored_notification")?.isEnabled = + findPreference(COLORED_NOTIFICATION)?.isEnabled = sharedPreferences?.getBoolean(key, false)!! } } @@ -42,7 +42,7 @@ class NotificationSettingsFragment : AbsSettingsFragment(), override fun invalidateSettings() { - val classicNotification: TwoStatePreference? = findPreference("classic_notification") + val classicNotification: TwoStatePreference? = findPreference(CLASSIC_NOTIFICATION) if (VERSION.SDK_INT < VERSION_CODES.N) { classicNotification?.isVisible = false } else { @@ -57,7 +57,7 @@ class NotificationSettingsFragment : AbsSettingsFragment(), } } - val coloredNotification: TwoStatePreference? = findPreference("colored_notification") + val coloredNotification: TwoStatePreference? = findPreference(COLORED_NOTIFICATION) if (VERSION.SDK_INT >= VERSION_CODES.O) { coloredNotification?.isEnabled = PreferenceUtil.isClassicNotification } else { diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/settings/NowPlayingSettingsFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/settings/NowPlayingSettingsFragment.kt index b0a73eaf3..8f28c2d55 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/settings/NowPlayingSettingsFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/settings/NowPlayingSettingsFragment.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 io.github.muntashirakon.music.fragments.settings import android.content.SharedPreferences @@ -19,12 +19,8 @@ import android.os.Bundle import android.view.View import androidx.preference.Preference import androidx.preference.TwoStatePreference -import io.github.muntashirakon.music.R -import io.github.muntashirakon.music.ALBUM_COVER_STYLE -import io.github.muntashirakon.music.CAROUSEL_EFFECT -import io.github.muntashirakon.music.CIRCULAR_ALBUM_ART -import io.github.muntashirakon.music.NOW_PLAYING_SCREEN_ID -import io.github.muntashirakon.music.util.PreferenceUtil +import code.name.monkey.retromusic.* +import code.name.monkey.retromusic.util.PreferenceUtil /** * @author Hemanth S (h4h13). @@ -37,8 +33,8 @@ class NowPlayingSettingsFragment : AbsSettingsFragment(), updateNowPlayingScreenSummary() updateAlbumCoverStyleSummary() - val carouselEffect: TwoStatePreference = findPreference("carousel_effect")!! - carouselEffect.setOnPreferenceChangeListener { _, _ -> + val carouselEffect: TwoStatePreference? = findPreference(CAROUSEL_EFFECT) + carouselEffect?.setOnPreferenceChangeListener { _, _ -> return@setOnPreferenceChangeListener true } } @@ -60,7 +56,7 @@ class NowPlayingSettingsFragment : AbsSettingsFragment(), override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) PreferenceUtil.registerOnSharedPreferenceChangedListener(this) - val preference: Preference? = findPreference("album_cover_transform") + val preference: Preference? = findPreference(ALBUM_COVER_TRANSFORM) preference?.setOnPreferenceChangeListener { albumPrefs, newValue -> setSummary(albumPrefs, newValue) true diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/settings/OtherSettingsFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/settings/OtherSettingsFragment.kt index 417457c54..ba17605be 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/settings/OtherSettingsFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/settings/OtherSettingsFragment.kt @@ -1,23 +1,25 @@ /* - * 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 io.github.muntashirakon.music.fragments.settings import android.os.Bundle import android.view.View import androidx.preference.Preference import code.name.monkey.appthemehelper.common.prefs.supportv7.ATEListPreference +import io.github.muntashirakon.music.LANGUAGE_NAME +import io.github.muntashirakon.music.LAST_ADDED_CUTOFF import io.github.muntashirakon.music.R import io.github.muntashirakon.music.fragments.LibraryViewModel import io.github.muntashirakon.music.fragments.ReloadType.HomeSections @@ -29,8 +31,9 @@ import org.koin.androidx.viewmodel.ext.android.sharedViewModel class OtherSettingsFragment : AbsSettingsFragment() { private val libraryViewModel by sharedViewModel() + override fun invalidateSettings() { - val languagePreference: ATEListPreference? = findPreference("language_name") + val languagePreference: ATEListPreference? = findPreference(LANGUAGE_NAME) languagePreference?.setOnPreferenceChangeListener { _, _ -> requireActivity().recreate() return@setOnPreferenceChangeListener true @@ -43,13 +46,13 @@ class OtherSettingsFragment : AbsSettingsFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val preference: Preference? = findPreference("last_added_interval") + val preference: Preference? = findPreference(LAST_ADDED_CUTOFF) preference?.setOnPreferenceChangeListener { lastAdded, newValue -> setSummary(lastAdded, newValue) libraryViewModel.forceReload(HomeSections) true } - val languagePreference: Preference? = findPreference("language_name") + val languagePreference: Preference? = findPreference(LANGUAGE_NAME) languagePreference?.setOnPreferenceChangeListener { prefs, newValue -> setSummary(prefs, newValue) true diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/settings/PersonalizeSettingsFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/settings/PersonalizeSettingsFragment.kt index 08b2ef149..46788fd4d 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/settings/PersonalizeSettingsFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/settings/PersonalizeSettingsFragment.kt @@ -1,30 +1,30 @@ /* - * 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 io.github.muntashirakon.music.fragments.settings import android.os.Bundle import android.view.View import androidx.preference.TwoStatePreference import code.name.monkey.appthemehelper.common.prefs.supportv7.ATEListPreference -import io.github.muntashirakon.music.R +import io.github.muntashirakon.music.* class PersonalizeSettingsFragment : AbsSettingsFragment() { override fun invalidateSettings() { - val toggleFullScreen: TwoStatePreference = findPreference("toggle_full_screen")!! - toggleFullScreen.setOnPreferenceChangeListener { _, _ -> + val toggleFullScreen: TwoStatePreference? = findPreference(TOGGLE_FULL_SCREEN) + toggleFullScreen?.setOnPreferenceChangeListener { _, _ -> requireActivity().recreate() true } @@ -36,12 +36,17 @@ class PersonalizeSettingsFragment : AbsSettingsFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val homeArtistStyle: ATEListPreference? = findPreference("home_artist_grid_style") + val homeArtistStyle: ATEListPreference? = findPreference(HOME_ARTIST_GRID_STYLE) homeArtistStyle?.setOnPreferenceChangeListener { preference, newValue -> setSummary(preference, newValue) true } - val tabTextMode: ATEListPreference? = findPreference("tab_text_mode") + val homeAlbumStyle: ATEListPreference? = findPreference(HOME_ALBUM_GRID_STYLE) + homeAlbumStyle?.setOnPreferenceChangeListener { preference, newValue -> + setSummary(preference, newValue) + true + } + val tabTextMode: ATEListPreference? = findPreference(TAB_TEXT_MODE) tabTextMode?.setOnPreferenceChangeListener { prefs, newValue -> setSummary(prefs, newValue) true diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/settings/ThemeSettingsFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/settings/ThemeSettingsFragment.kt index 73d3cfffd..77b81ce8d 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/settings/ThemeSettingsFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/settings/ThemeSettingsFragment.kt @@ -1,17 +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 io.github.muntashirakon.music.fragments.settings import android.os.Build @@ -23,8 +22,7 @@ import code.name.monkey.appthemehelper.common.prefs.supportv7.ATEColorPreference import code.name.monkey.appthemehelper.common.prefs.supportv7.ATESwitchPreference import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.VersionUtils -import io.github.muntashirakon.music.DESATURATED_COLOR -import io.github.muntashirakon.music.R +import io.github.muntashirakon.music.* import io.github.muntashirakon.music.appshortcuts.DynamicShortcutManager import io.github.muntashirakon.music.util.PreferenceUtil import com.afollestad.materialdialogs.color.ColorChooserDialog @@ -35,7 +33,7 @@ import com.afollestad.materialdialogs.color.ColorChooserDialog class ThemeSettingsFragment : AbsSettingsFragment() { override fun invalidateSettings() { - val generalTheme: Preference? = findPreference("general_theme") + val generalTheme: Preference? = findPreference(GENERAL_THEME) generalTheme?.let { setSummary(it) it.setOnPreferenceChangeListener { _, newValue -> @@ -52,11 +50,10 @@ class ThemeSettingsFragment : AbsSettingsFragment() { } } - val accentColorPref: ATEColorPreference = findPreference("accent_color")!! + val accentColorPref: ATEColorPreference? = findPreference(ACCENT_COLOR) val accentColor = ThemeStore.accentColor(requireContext()) - accentColorPref.setColor(accentColor, ColorUtil.darkenColor(accentColor)) - - accentColorPref.setOnPreferenceClickListener { + accentColorPref?.setColor(accentColor, ColorUtil.darkenColor(accentColor)) + accentColorPref?.setOnPreferenceClickListener { ColorChooserDialog.Builder(requireContext(), R.string.accent_color) .accentMode(true) .allowUserColorInput(true) @@ -65,7 +62,7 @@ class ThemeSettingsFragment : AbsSettingsFragment() { .show(requireActivity()) return@setOnPreferenceClickListener true } - val blackTheme: ATESwitchPreference? = findPreference("black_theme") + val blackTheme: ATESwitchPreference? = findPreference(BLACK_THEME) blackTheme?.setOnPreferenceChangeListener { _, _ -> ThemeStore.markChanged(requireContext()) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { @@ -88,13 +85,12 @@ class ThemeSettingsFragment : AbsSettingsFragment() { true } - - val colorAppShortcuts: TwoStatePreference = findPreference("should_color_app_shortcuts")!! + val colorAppShortcuts: TwoStatePreference? = findPreference(SHOULD_COLOR_APP_SHORTCUTS) if (!VersionUtils.hasNougatMR()) { - colorAppShortcuts.isVisible = false + colorAppShortcuts?.isVisible = false } else { - colorAppShortcuts.isChecked = PreferenceUtil.isColoredAppShortcuts - colorAppShortcuts.setOnPreferenceChangeListener { _, newValue -> + colorAppShortcuts?.isChecked = PreferenceUtil.isColoredAppShortcuts + colorAppShortcuts?.setOnPreferenceChangeListener { _, newValue -> PreferenceUtil.isColoredAppShortcuts = newValue as Boolean DynamicShortcutManager(requireContext()).updateDynamicShortcuts() true diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/songs/SongsFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/songs/SongsFragment.kt index 351fb9add..f03a9ddc8 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/songs/SongsFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/songs/SongsFragment.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 io.github.muntashirakon.music.fragments.songs import android.os.Bundle @@ -7,20 +21,18 @@ import androidx.lifecycle.Observer import androidx.recyclerview.widget.GridLayoutManager import io.github.muntashirakon.music.R import io.github.muntashirakon.music.adapter.song.SongAdapter +import io.github.muntashirakon.music.extensions.surfaceColor import io.github.muntashirakon.music.fragments.ReloadType import io.github.muntashirakon.music.fragments.base.AbsRecyclerViewCustomGridSizeFragment import io.github.muntashirakon.music.helper.SortOrder.SongSortOrder +import io.github.muntashirakon.music.interfaces.ICabHolder import io.github.muntashirakon.music.util.PreferenceUtil +import io.github.muntashirakon.music.util.RetroColorUtil import io.github.muntashirakon.music.util.RetroUtil -import com.google.android.material.transition.platform.MaterialFadeThrough - - -class SongsFragment : AbsRecyclerViewCustomGridSizeFragment() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - enterTransition = MaterialFadeThrough() - } +import com.afollestad.materialcab.MaterialCab +class SongsFragment : AbsRecyclerViewCustomGridSizeFragment(), + ICabHolder { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) libraryViewModel.getSongs().observe(viewLifecycleOwner, Observer { @@ -44,7 +56,7 @@ class SongsFragment : AbsRecyclerViewCustomGridSizeFragment sortOrder = SongSortOrder.SONG_A_Z - R.id.action_song_sort_order_desc -> sortOrder = SongSortOrder.SONG_Z_A - R.id.action_song_sort_order_artist -> sortOrder = SongSortOrder.SONG_ARTIST - R.id.action_song_sort_order_album -> sortOrder = SongSortOrder.SONG_ALBUM - R.id.action_song_sort_order_year -> sortOrder = SongSortOrder.SONG_YEAR - R.id.action_song_sort_order_date -> sortOrder = SongSortOrder.SONG_DATE - R.id.action_song_sort_order_composer -> sortOrder = SongSortOrder.COMPOSER - R.id.action_song_sort_order_date_modified -> sortOrder = - SongSortOrder.SONG_DATE_MODIFIED + val sortOrder: String = when (item.itemId) { + R.id.action_song_sort_order_asc -> SongSortOrder.SONG_A_Z + R.id.action_song_sort_order_desc -> SongSortOrder.SONG_Z_A + R.id.action_song_sort_order_artist -> SongSortOrder.SONG_ARTIST + R.id.action_song_sort_order_album -> SongSortOrder.SONG_ALBUM + R.id.action_song_sort_order_year -> SongSortOrder.SONG_YEAR + R.id.action_song_sort_order_date -> SongSortOrder.SONG_DATE + R.id.action_song_sort_order_composer -> SongSortOrder.COMPOSER + R.id.action_song_sort_order_date_modified -> SongSortOrder.SONG_DATE_MODIFIED + else -> PreferenceUtil.songSortOrder } - if (sortOrder != null) { + if (sortOrder != PreferenceUtil.songSortOrder) { item.isChecked = true setAndSaveSortOrder(sortOrder) return true @@ -257,16 +267,16 @@ class SongsFragment : AbsRecyclerViewCustomGridSizeFragment layoutRes = R.layout.item_grid - R.id.action_layout_card -> layoutRes = R.layout.item_card - R.id.action_layout_colored_card -> layoutRes = R.layout.item_card_color - R.id.action_layout_circular -> layoutRes = R.layout.item_grid_circle - R.id.action_layout_image -> layoutRes = R.layout.image - R.id.action_layout_gradient_image -> layoutRes = R.layout.item_image_gradient + val layoutRes = when (item.itemId) { + R.id.action_layout_normal -> R.layout.item_grid + R.id.action_layout_card -> R.layout.item_card + R.id.action_layout_colored_card -> R.layout.item_card_color + R.id.action_layout_circular -> R.layout.item_grid_circle + R.id.action_layout_image -> R.layout.image + R.id.action_layout_gradient_image -> R.layout.item_image_gradient + else -> PreferenceUtil.songGridStyle } - if (layoutRes != -1) { + if (layoutRes != PreferenceUtil.songGridStyle) { item.isChecked = true setAndSaveLayoutRes(layoutRes) return true @@ -277,16 +287,16 @@ class SongsFragment : AbsRecyclerViewCustomGridSizeFragment gridSize = 1 - R.id.action_grid_size_2 -> gridSize = 2 - R.id.action_grid_size_3 -> gridSize = 3 - R.id.action_grid_size_4 -> gridSize = 4 - R.id.action_grid_size_5 -> gridSize = 5 - R.id.action_grid_size_6 -> gridSize = 6 - R.id.action_grid_size_7 -> gridSize = 7 - R.id.action_grid_size_8 -> gridSize = 8 + val gridSize = when (item.itemId) { + R.id.action_grid_size_1 -> 1 + R.id.action_grid_size_2 -> 2 + R.id.action_grid_size_3 -> 3 + R.id.action_grid_size_4 -> 4 + R.id.action_grid_size_5 -> 5 + R.id.action_grid_size_6 -> 6 + R.id.action_grid_size_7 -> 7 + R.id.action_grid_size_8 -> 8 + else -> 0 } if (gridSize > 0) { item.isChecked = true @@ -305,4 +315,31 @@ class SongsFragment : AbsRecyclerViewCustomGridSizeFragment 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/io/github/muntashirakon/music/glide/ArtistGlideRequest.java b/app/src/main/java/io/github/muntashirakon/music/glide/ArtistGlideRequest.java index f8e193a24..5d725d2d1 100644 --- a/app/src/main/java/io/github/muntashirakon/music/glide/ArtistGlideRequest.java +++ b/app/src/main/java/io/github/muntashirakon/music/glide/ArtistGlideRequest.java @@ -17,20 +17,8 @@ package io.github.muntashirakon.music.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 io.github.muntashirakon.music.App; @@ -41,128 +29,143 @@ import io.github.muntashirakon.music.glide.palette.BitmapPaletteWrapper; import io.github.muntashirakon.music.model.Artist; import io.github.muntashirakon.music.util.ArtistSignatureUtil; import io.github.muntashirakon.music.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.getName())); - } 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/io/github/muntashirakon/music/glide/BlurTransformation.kt b/app/src/main/java/io/github/muntashirakon/music/glide/BlurTransformation.kt index d0484eacd..77b417d57 100644 --- a/app/src/main/java/io/github/muntashirakon/music/glide/BlurTransformation.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.glide import android.content.Context @@ -27,7 +27,6 @@ import io.github.muntashirakon.music.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/io/github/muntashirakon/music/glide/ProfileBannerGlideRequest.java b/app/src/main/java/io/github/muntashirakon/music/glide/ProfileBannerGlideRequest.java index 3eb9eb754..b48bac7e5 100644 --- a/app/src/main/java/io/github/muntashirakon/music/glide/ProfileBannerGlideRequest.java +++ b/app/src/main/java/io/github/muntashirakon/music/glide/ProfileBannerGlideRequest.java @@ -1,79 +1,76 @@ package io.github.muntashirakon.music.glide; +import static io.github.muntashirakon.music.Constants.USER_BANNER; + import android.graphics.Bitmap; - import androidx.annotation.NonNull; - +import io.github.muntashirakon.music.App; +import io.github.muntashirakon.music.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 io.github.muntashirakon.music.App; -import io.github.muntashirakon.music.R; - -import static io.github.muntashirakon.music.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/io/github/muntashirakon/music/glide/RetroMusicColoredTarget.kt b/app/src/main/java/io/github/muntashirakon/music/glide/RetroMusicColoredTarget.kt index 6bc9c0225..11f97b441 100644 --- a/app/src/main/java/io/github/muntashirakon/music/glide/RetroMusicColoredTarget.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.glide import android.graphics.drawable.Drawable @@ -24,7 +24,6 @@ import io.github.muntashirakon.music.glide.palette.BitmapPaletteWrapper import io.github.muntashirakon.music.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/io/github/muntashirakon/music/glide/RetroMusicGlideModule.kt b/app/src/main/java/io/github/muntashirakon/music/glide/RetroMusicGlideModule.kt index c82231396..99df16035 100644 --- a/app/src/main/java/io/github/muntashirakon/music/glide/RetroMusicGlideModule.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.glide import android.content.Context diff --git a/app/src/main/java/io/github/muntashirakon/music/glide/SingleColorTarget.kt b/app/src/main/java/io/github/muntashirakon/music/glide/SingleColorTarget.kt index a8cb77e8a..b44dc193d 100644 --- a/app/src/main/java/io/github/muntashirakon/music/glide/SingleColorTarget.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.glide import android.graphics.drawable.Drawable @@ -9,7 +23,6 @@ import io.github.muntashirakon.music.glide.palette.BitmapPaletteWrapper import io.github.muntashirakon.music.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/io/github/muntashirakon/music/glide/SongGlideRequest.java b/app/src/main/java/io/github/muntashirakon/music/glide/SongGlideRequest.java index e8285284a..e541ac5ca 100644 --- a/app/src/main/java/io/github/muntashirakon/music/glide/SongGlideRequest.java +++ b/app/src/main/java/io/github/muntashirakon/music/glide/SongGlideRequest.java @@ -16,9 +16,14 @@ package io.github.muntashirakon.music.glide; import android.content.Context; import android.graphics.Bitmap; - import androidx.annotation.NonNull; - +import io.github.muntashirakon.music.R; +import io.github.muntashirakon.music.glide.audiocover.AudioFileCover; +import io.github.muntashirakon.music.glide.palette.BitmapPaletteTranscoder; +import io.github.muntashirakon.music.glide.palette.BitmapPaletteWrapper; +import io.github.muntashirakon.music.model.Song; +import io.github.muntashirakon.music.util.MusicUtil; +import io.github.muntashirakon.music.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 io.github.muntashirakon.music.R; -import io.github.muntashirakon.music.glide.audiocover.AudioFileCover; -import io.github.muntashirakon.music.glide.palette.BitmapPaletteTranscoder; -import io.github.muntashirakon.music.glide.palette.BitmapPaletteWrapper; -import io.github.muntashirakon.music.model.Song; -import io.github.muntashirakon.music.util.MusicUtil; -import io.github.muntashirakon.music.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/io/github/muntashirakon/music/glide/UserProfileGlideRequest.java b/app/src/main/java/io/github/muntashirakon/music/glide/UserProfileGlideRequest.java index f9ea7a113..da9ee6229 100644 --- a/app/src/main/java/io/github/muntashirakon/music/glide/UserProfileGlideRequest.java +++ b/app/src/main/java/io/github/muntashirakon/music/glide/UserProfileGlideRequest.java @@ -1,82 +1,83 @@ package io.github.muntashirakon.music.glide; +import static io.github.muntashirakon.music.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 io.github.muntashirakon.music.App; +import io.github.muntashirakon.music.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 io.github.muntashirakon.music.App; -import io.github.muntashirakon.music.R; - -import static io.github.muntashirakon.music.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/io/github/muntashirakon/music/glide/artistimage/ArtistImageLoader.kt b/app/src/main/java/io/github/muntashirakon/music/glide/artistimage/ArtistImageLoader.kt index 2b31c6f18..03e68abd3 100644 --- a/app/src/main/java/io/github/muntashirakon/music/glide/artistimage/ArtistImageLoader.kt +++ b/app/src/main/java/io/github/muntashirakon/music/glide/artistimage/ArtistImageLoader.kt @@ -1,20 +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 io.github.muntashirakon.music.glide.artistimage import android.content.Context +import io.github.muntashirakon.music.model.Artist import io.github.muntashirakon.music.model.Data import io.github.muntashirakon.music.network.DeezerService import io.github.muntashirakon.music.util.MusicUtil @@ -27,14 +28,14 @@ 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 artistName: String) +class ArtistImage(val artist: Artist) class ArtistImageFetcher( private val context: Context, @@ -53,7 +54,7 @@ class ArtistImageFetcher( } override fun getId(): String { - return model.artistName + return model.artist.name } override fun cancel() { @@ -62,14 +63,14 @@ class ArtistImageFetcher( } override fun loadData(priority: Priority?): InputStream? { - if (!MusicUtil.isArtistNameUnknown(model.artistName) && + if (!MusicUtil.isArtistNameUnknown(model.artist.name) && PreferenceUtil.isAllowedToDownloadMetadata() ) { - val artists = model.artistName.split(",") + val artists = model.artist.name.split(",") 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 @@ -85,13 +86,20 @@ class ArtistImageFetcher( val glideUrl = GlideUrl(imageUrl) urlFetcher = urlLoader.getResourceFetcher(glideUrl, width, height) urlFetcher?.loadData(priority) - } else null + } else { + getFallbackAlbumImage() + } } catch (e: Exception) { - null + getFallbackAlbumImage() } } else return null } + private fun getFallbackAlbumImage(): InputStream? { + val imageUri = MusicUtil.getMediaStoreAlbumCoverUri(model.artist.safeGetFirstAlbum().id) + 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/io/github/muntashirakon/music/glide/audiocover/AudioFileCover.java b/app/src/main/java/io/github/muntashirakon/music/glide/audiocover/AudioFileCover.java index 3b094dd4e..78e08a27b 100644 --- a/app/src/main/java/io/github/muntashirakon/music/glide/audiocover/AudioFileCover.java +++ b/app/src/main/java/io/github/muntashirakon/music/glide/audiocover/AudioFileCover.java @@ -14,13 +14,11 @@ package io.github.muntashirakon.music.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/io/github/muntashirakon/music/glide/audiocover/AudioFileCoverFetcher.java b/app/src/main/java/io/github/muntashirakon/music/glide/audiocover/AudioFileCoverFetcher.java index 701373a03..7ccf0ca50 100644 --- a/app/src/main/java/io/github/muntashirakon/music/glide/audiocover/AudioFileCoverFetcher.java +++ b/app/src/main/java/io/github/muntashirakon/music/glide/audiocover/AudioFileCoverFetcher.java @@ -15,64 +15,61 @@ package io.github.muntashirakon.music.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/io/github/muntashirakon/music/glide/audiocover/AudioFileCoverLoader.java b/app/src/main/java/io/github/muntashirakon/music/glide/audiocover/AudioFileCoverLoader.java index d22117c3c..e062bb507 100644 --- a/app/src/main/java/io/github/muntashirakon/music/glide/audiocover/AudioFileCoverLoader.java +++ b/app/src/main/java/io/github/muntashirakon/music/glide/audiocover/AudioFileCoverLoader.java @@ -15,32 +15,28 @@ package io.github.muntashirakon.music.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/io/github/muntashirakon/music/glide/audiocover/AudioFileCoverUtils.java b/app/src/main/java/io/github/muntashirakon/music/glide/audiocover/AudioFileCoverUtils.java index 226ef9cd0..9d220cbc1 100644 --- a/app/src/main/java/io/github/muntashirakon/music/glide/audiocover/AudioFileCoverUtils.java +++ b/app/src/main/java/io/github/muntashirakon/music/glide/audiocover/AudioFileCoverUtils.java @@ -14,50 +14,50 @@ package io.github.muntashirakon.music.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/io/github/muntashirakon/music/glide/palette/BitmapPaletteResource.java b/app/src/main/java/io/github/muntashirakon/music/glide/palette/BitmapPaletteResource.java index 1bab1f464..eaba3524a 100644 --- a/app/src/main/java/io/github/muntashirakon/music/glide/palette/BitmapPaletteResource.java +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/glide/palette/BitmapPaletteTarget.java b/app/src/main/java/io/github/muntashirakon/music/glide/palette/BitmapPaletteTarget.java index e69c0a501..66bd160bf 100644 --- a/app/src/main/java/io/github/muntashirakon/music/glide/palette/BitmapPaletteTarget.java +++ b/app/src/main/java/io/github/muntashirakon/music/glide/palette/BitmapPaletteTarget.java @@ -15,16 +15,15 @@ package io.github.muntashirakon.music.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/io/github/muntashirakon/music/glide/palette/BitmapPaletteTranscoder.java b/app/src/main/java/io/github/muntashirakon/music/glide/palette/BitmapPaletteTranscoder.java index 51cd5da64..4096fa423 100644 --- a/app/src/main/java/io/github/muntashirakon/music/glide/palette/BitmapPaletteTranscoder.java +++ b/app/src/main/java/io/github/muntashirakon/music/glide/palette/BitmapPaletteTranscoder.java @@ -16,34 +16,33 @@ package io.github.muntashirakon.music.glide.palette; import android.content.Context; import android.graphics.Bitmap; - +import io.github.muntashirakon.music.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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/glide/palette/BitmapPaletteWrapper.java b/app/src/main/java/io/github/muntashirakon/music/glide/palette/BitmapPaletteWrapper.java index b4fd054bf..d1125b3d4 100644 --- a/app/src/main/java/io/github/muntashirakon/music/glide/palette/BitmapPaletteWrapper.java +++ b/app/src/main/java/io/github/muntashirakon/music/glide/palette/BitmapPaletteWrapper.java @@ -15,23 +15,22 @@ package io.github.muntashirakon.music.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/io/github/muntashirakon/music/helper/HorizontalAdapterHelper.kt b/app/src/main/java/io/github/muntashirakon/music/helper/HorizontalAdapterHelper.kt index 63f03b084..162262be8 100644 --- a/app/src/main/java/io/github/muntashirakon/music/helper/HorizontalAdapterHelper.kt +++ b/app/src/main/java/io/github/muntashirakon/music/helper/HorizontalAdapterHelper.kt @@ -1,27 +1,26 @@ /* - * 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 io.github.muntashirakon.music.helper import android.content.Context import android.view.ViewGroup import io.github.muntashirakon.music.R - object HorizontalAdapterHelper { - const val LAYOUT_RES = R.layout.item_album_card + const val LAYOUT_RES = R.layout.item_image private const val TYPE_FIRST = 1 private const val TYPE_MIDDLE = 2 @@ -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) @@ -40,7 +40,7 @@ object HorizontalAdapterHelper { } } - fun getItemViewtype(position: Int, itemCount: Int): Int { + fun getItemViewType(position: Int, itemCount: Int): Int { return when (position) { 0 -> TYPE_FIRST itemCount - 1 -> TYPE_LAST diff --git a/app/src/main/java/io/github/muntashirakon/music/helper/M3UConstants.java b/app/src/main/java/io/github/muntashirakon/music/helper/M3UConstants.java index 934921521..1d9ea5c5d 100644 --- a/app/src/main/java/io/github/muntashirakon/music/helper/M3UConstants.java +++ b/app/src/main/java/io/github/muntashirakon/music/helper/M3UConstants.java @@ -15,8 +15,8 @@ package io.github.muntashirakon.music.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/io/github/muntashirakon/music/helper/M3UWriter.kt b/app/src/main/java/io/github/muntashirakon/music/helper/M3UWriter.kt index 7541e4867..adcdb70cd 100644 --- a/app/src/main/java/io/github/muntashirakon/music/helper/M3UWriter.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.helper @@ -66,4 +67,4 @@ object M3UWriter : M3UConstants { } return file } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/muntashirakon/music/helper/MusicPlayerRemote.kt b/app/src/main/java/io/github/muntashirakon/music/helper/MusicPlayerRemote.kt index 7cc9e175f..78d494c57 100644 --- a/app/src/main/java/io/github/muntashirakon/music/helper/MusicPlayerRemote.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.helper import android.annotation.TargetApi @@ -25,15 +25,14 @@ import android.os.IBinder import android.provider.DocumentsContract import android.widget.Toast import androidx.core.content.ContextCompat -import io.github.muntashirakon.music.R import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.repository.SongRepository import io.github.muntashirakon.music.service.MusicService import io.github.muntashirakon.music.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 @@ -42,7 +41,6 @@ object MusicPlayerRemote : KoinComponent { private val songRepository by inject() - @JvmStatic val isPlaying: Boolean get() = musicService != null && musicService!!.isPlaying @@ -306,7 +304,7 @@ object MusicPlayerRemote : KoinComponent { } Toast.makeText( musicService, - musicService!!.resources.getString(R.string.added_title_to_playing_queue), + musicService!!.resources.getString(io.github.muntashirakon.music.R.string.added_title_to_playing_queue), Toast.LENGTH_SHORT ).show() return true @@ -322,8 +320,8 @@ object MusicPlayerRemote : KoinComponent { openQueue(songs, 0, false) } val toast = - if (songs.size == 1) musicService!!.resources.getString(R.string.added_title_to_playing_queue) else musicService!!.resources.getString( - R.string.added_x_titles_to_playing_queue, + if (songs.size == 1) musicService!!.resources.getString(io.github.muntashirakon.music.R.string.added_title_to_playing_queue) else musicService!!.resources.getString( + io.github.muntashirakon.music.R.string.added_x_titles_to_playing_queue, songs.size ) Toast.makeText(musicService, toast, Toast.LENGTH_SHORT).show() @@ -343,7 +341,7 @@ object MusicPlayerRemote : KoinComponent { } Toast.makeText( musicService, - musicService!!.resources.getString(R.string.added_title_to_playing_queue), + musicService!!.resources.getString(io.github.muntashirakon.music.R.string.added_title_to_playing_queue), Toast.LENGTH_SHORT ).show() return true @@ -359,8 +357,8 @@ object MusicPlayerRemote : KoinComponent { openQueue(songs, 0, false) } val toast = - if (songs.size == 1) musicService!!.resources.getString(R.string.added_title_to_playing_queue) else musicService!!.resources.getString( - R.string.added_x_titles_to_playing_queue, + if (songs.size == 1) musicService!!.resources.getString(io.github.muntashirakon.music.R.string.added_title_to_playing_queue) else musicService!!.resources.getString( + io.github.muntashirakon.music.R.string.added_x_titles_to_playing_queue, songs.size ) Toast.makeText(musicService, toast, Toast.LENGTH_SHORT).show() @@ -443,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/io/github/muntashirakon/music/helper/MusicProgressViewUpdateHelper.kt b/app/src/main/java/io/github/muntashirakon/music/helper/MusicProgressViewUpdateHelper.kt index 4b7b9bf29..314b4a7a1 100644 --- a/app/src/main/java/io/github/muntashirakon/music/helper/MusicProgressViewUpdateHelper.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.helper import android.os.Handler diff --git a/app/src/main/java/io/github/muntashirakon/music/helper/PlayPauseButtonOnClickHandler.kt b/app/src/main/java/io/github/muntashirakon/music/helper/PlayPauseButtonOnClickHandler.kt index 4fb8eb1f2..9d4180a56 100644 --- a/app/src/main/java/io/github/muntashirakon/music/helper/PlayPauseButtonOnClickHandler.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.helper import android.view.View - class PlayPauseButtonOnClickHandler : View.OnClickListener { override fun onClick(v: View) { if (MusicPlayerRemote.isPlaying) { diff --git a/app/src/main/java/io/github/muntashirakon/music/helper/SearchQueryHelper.kt b/app/src/main/java/io/github/muntashirakon/music/helper/SearchQueryHelper.kt index 677bc7824..534e3a640 100644 --- a/app/src/main/java/io/github/muntashirakon/music/helper/SearchQueryHelper.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.helper import android.app.SearchManager @@ -19,9 +19,9 @@ import android.os.Bundle import android.provider.MediaStore import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.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/io/github/muntashirakon/music/helper/ShuffleHelper.kt b/app/src/main/java/io/github/muntashirakon/music/helper/ShuffleHelper.kt index c828aa3c5..18c401ced 100644 --- a/app/src/main/java/io/github/muntashirakon/music/helper/ShuffleHelper.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.helper import io.github.muntashirakon.music.model.Song - object ShuffleHelper { fun makeShuffleList(listToShuffle: MutableList, current: Int) { diff --git a/app/src/main/java/io/github/muntashirakon/music/helper/SortOrder.kt b/app/src/main/java/io/github/muntashirakon/music/helper/SortOrder.kt index 57323f388..ea2da7286 100644 --- a/app/src/main/java/io/github/muntashirakon/music/helper/SortOrder.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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,25 @@ class SortOrder { const val ALBUM_Z_A = "$GENRE_A_Z DESC" } } -} \ No newline at end of file + + /** + * Playlist sort order entries. + */ + interface PlaylistSortOrder { + + companion object { + + /* Playlist sort order A-Z */ + const val PLAYLIST_A_Z = MediaStore.Audio.Playlists.DEFAULT_SORT_ORDER + + /* Playlist sort order Z-A */ + const val PLAYLIST_Z_A = "$PLAYLIST_A_Z DESC" + + /* Playlist sort order number of songs */ + const val PLAYLIST_SONG_COUNT = "playlist_song_count" + + /* Playlist sort order number of songs */ + const val PLAYLIST_SONG_COUNT_DESC = "$PLAYLIST_SONG_COUNT DESC" + } + } +} diff --git a/app/src/main/java/io/github/muntashirakon/music/helper/StackBlur.java b/app/src/main/java/io/github/muntashirakon/music/helper/StackBlur.java index c23efcd89..ab96f8665 100644 --- a/app/src/main/java/io/github/muntashirakon/music/helper/StackBlur.java +++ b/app/src/main/java/io/github/muntashirakon/music/helper/StackBlur.java @@ -1,7 +1,6 @@ package io.github.muntashirakon.music.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/io/github/muntashirakon/music/helper/StopWatch.kt b/app/src/main/java/io/github/muntashirakon/music/helper/StopWatch.kt index 4b11d1125..3d78d77ad 100644 --- a/app/src/main/java/io/github/muntashirakon/music/helper/StopWatch.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.helper /** diff --git a/app/src/main/java/io/github/muntashirakon/music/helper/menu/GenreMenuHelper.kt b/app/src/main/java/io/github/muntashirakon/music/helper/menu/GenreMenuHelper.kt index f6dab2766..44a2cfab9 100644 --- a/app/src/main/java/io/github/muntashirakon/music/helper/menu/GenreMenuHelper.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.helper.menu import android.view.MenuItem diff --git a/app/src/main/java/io/github/muntashirakon/music/helper/menu/PlaylistMenuHelper.kt b/app/src/main/java/io/github/muntashirakon/music/helper/menu/PlaylistMenuHelper.kt index 0ab167636..96af6877c 100644 --- a/app/src/main/java/io/github/muntashirakon/music/helper/menu/PlaylistMenuHelper.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.helper.menu - import android.view.MenuItem import androidx.fragment.app.FragmentActivity import io.github.muntashirakon.music.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/io/github/muntashirakon/music/helper/menu/SongMenuHelper.kt b/app/src/main/java/io/github/muntashirakon/music/helper/menu/SongMenuHelper.kt index 6f92178b9..3b1403478 100644 --- a/app/src/main/java/io/github/muntashirakon/music/helper/menu/SongMenuHelper.kt +++ b/app/src/main/java/io/github/muntashirakon/music/helper/menu/SongMenuHelper.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 io.github.muntashirakon.music.helper.menu @@ -30,7 +31,7 @@ import io.github.muntashirakon.music.dialogs.AddToPlaylistDialog import io.github.muntashirakon.music.dialogs.DeleteSongsDialog import io.github.muntashirakon.music.dialogs.SongDetailDialog import io.github.muntashirakon.music.helper.MusicPlayerRemote -import io.github.muntashirakon.music.interfaces.PaletteColorHolder +import io.github.muntashirakon.music.interfaces.IPaletteColorHolder import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.repository.RealRepository import io.github.muntashirakon.music.util.MusicUtil @@ -90,10 +91,10 @@ object SongMenuHelper : KoinComponent { R.id.action_tag_editor -> { val tagEditorIntent = Intent(activity, SongTagEditorActivity::class.java) tagEditorIntent.putExtra(AbsTagEditorActivity.EXTRA_ID, song.id) - if (activity is PaletteColorHolder) + if (activity is IPaletteColorHolder) tagEditorIntent.putExtra( AbsTagEditorActivity.EXTRA_PALETTE, - (activity as PaletteColorHolder).paletteColor + (activity as IPaletteColorHolder).paletteColor ) activity.startActivity(tagEditorIntent) return true diff --git a/app/src/main/java/io/github/muntashirakon/music/helper/menu/SongsMenuHelper.kt b/app/src/main/java/io/github/muntashirakon/music/helper/menu/SongsMenuHelper.kt index 7ecd89a21..e825685ab 100644 --- a/app/src/main/java/io/github/muntashirakon/music/helper/menu/SongsMenuHelper.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.helper.menu import androidx.fragment.app.FragmentActivity diff --git a/app/src/main/java/io/github/muntashirakon/music/interfaces/Callbacks.kt b/app/src/main/java/io/github/muntashirakon/music/interfaces/Callbacks.kt deleted file mode 100644 index 17924914e..000000000 --- a/app/src/main/java/io/github/muntashirakon/music/interfaces/Callbacks.kt +++ /dev/null @@ -1,13 +0,0 @@ -package io.github.muntashirakon.music.interfaces - -import android.view.MenuItem -import android.view.View -import java.io.File - -interface Callbacks { - fun onFileSelected(file: File) - - 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/io/github/muntashirakon/music/interfaces/IAlbumClickListener.kt b/app/src/main/java/io/github/muntashirakon/music/interfaces/IAlbumClickListener.kt new file mode 100644 index 000000000..c5412d0f2 --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/interfaces/IAlbumClickListener.kt @@ -0,0 +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) +} diff --git a/app/src/main/java/io/github/muntashirakon/music/interfaces/IArtistClickListener.kt b/app/src/main/java/io/github/muntashirakon/music/interfaces/IArtistClickListener.kt new file mode 100644 index 000000000..f1c1f7450 --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/interfaces/IArtistClickListener.kt @@ -0,0 +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) +} diff --git a/app/src/main/java/io/github/muntashirakon/music/interfaces/CabHolder.kt b/app/src/main/java/io/github/muntashirakon/music/interfaces/ICabHolder.kt similarity index 74% rename from app/src/main/java/io/github/muntashirakon/music/interfaces/CabHolder.kt rename to app/src/main/java/io/github/muntashirakon/music/interfaces/ICabHolder.kt index 3b7d641a9..eb5b787f5 100644 --- a/app/src/main/java/io/github/muntashirakon/music/interfaces/CabHolder.kt +++ b/app/src/main/java/io/github/muntashirakon/music/interfaces/ICabHolder.kt @@ -1,23 +1,22 @@ /* - * 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 io.github.muntashirakon.music.interfaces import com.afollestad.materialcab.MaterialCab - -interface CabHolder { +interface ICabHolder { fun openCab(menuRes: Int, callback: MaterialCab.Callback): MaterialCab } diff --git a/app/src/main/java/io/github/muntashirakon/music/interfaces/ICallbacks.kt b/app/src/main/java/io/github/muntashirakon/music/interfaces/ICallbacks.kt new file mode 100644 index 000000000..b44a01f0e --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/interfaces/ICallbacks.kt @@ -0,0 +1,27 @@ +/* + * 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 +import android.view.View +import java.io.File + +interface ICallbacks { + fun onFileSelected(file: File) + + fun onFileMenuClicked(file: File, view: View) + + fun onMultipleItemAction(item: MenuItem, files: ArrayList) +} diff --git a/app/src/main/java/io/github/muntashirakon/music/interfaces/MainActivityFragmentCallbacks.kt b/app/src/main/java/io/github/muntashirakon/music/interfaces/IMainActivityFragmentCallbacks.kt similarity index 70% rename from app/src/main/java/io/github/muntashirakon/music/interfaces/MainActivityFragmentCallbacks.kt rename to app/src/main/java/io/github/muntashirakon/music/interfaces/IMainActivityFragmentCallbacks.kt index 8acae39da..954b8cc6c 100644 --- a/app/src/main/java/io/github/muntashirakon/music/interfaces/MainActivityFragmentCallbacks.kt +++ b/app/src/main/java/io/github/muntashirakon/music/interfaces/IMainActivityFragmentCallbacks.kt @@ -1,21 +1,22 @@ /* - * 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 io.github.muntashirakon.music.interfaces /** * Created by hemanths on 14/08/17. */ -internal interface MainActivityFragmentCallbacks { +interface IMainActivityFragmentCallbacks { fun handleBackPress(): Boolean -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/muntashirakon/music/interfaces/MusicServiceEventListener.kt b/app/src/main/java/io/github/muntashirakon/music/interfaces/IMusicServiceEventListener.kt similarity index 76% rename from app/src/main/java/io/github/muntashirakon/music/interfaces/MusicServiceEventListener.kt rename to app/src/main/java/io/github/muntashirakon/music/interfaces/IMusicServiceEventListener.kt index d75bf2f47..0364277a4 100644 --- a/app/src/main/java/io/github/muntashirakon/music/interfaces/MusicServiceEventListener.kt +++ b/app/src/main/java/io/github/muntashirakon/music/interfaces/IMusicServiceEventListener.kt @@ -1,21 +1,20 @@ /* - * 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 io.github.muntashirakon.music.interfaces - -interface MusicServiceEventListener { +interface IMusicServiceEventListener { fun onServiceConnected() fun onServiceDisconnected() diff --git a/app/src/main/java/io/github/muntashirakon/music/interfaces/PaletteColorHolder.kt b/app/src/main/java/io/github/muntashirakon/music/interfaces/IPaletteColorHolder.kt similarity index 72% rename from app/src/main/java/io/github/muntashirakon/music/interfaces/PaletteColorHolder.kt rename to app/src/main/java/io/github/muntashirakon/music/interfaces/IPaletteColorHolder.kt index 487673019..078fc8224 100644 --- a/app/src/main/java/io/github/muntashirakon/music/interfaces/PaletteColorHolder.kt +++ b/app/src/main/java/io/github/muntashirakon/music/interfaces/IPaletteColorHolder.kt @@ -1,22 +1,22 @@ /* - * 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 io.github.muntashirakon.music.interfaces /** * @author Aidan Follestad (afollestad) */ -interface PaletteColorHolder { +interface IPaletteColorHolder { val paletteColor: Int } diff --git a/app/src/main/java/io/github/muntashirakon/music/interfaces/IPlaylistClickListener.kt b/app/src/main/java/io/github/muntashirakon/music/interfaces/IPlaylistClickListener.kt new file mode 100644 index 000000000..3b0eb141d --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/interfaces/IPlaylistClickListener.kt @@ -0,0 +1,8 @@ +package code.name.monkey.retromusic.interfaces + +import android.view.View +import code.name.monkey.retromusic.db.PlaylistWithSongs + +interface IPlaylistClickListener { + fun onPlaylistClick(playlistWithSongs: PlaylistWithSongs, view: View) +} \ No newline at end of file diff --git a/app/src/main/java/io/github/muntashirakon/music/lyrics/Lrc.java b/app/src/main/java/io/github/muntashirakon/music/lyrics/Lrc.java index b460b3260..493fac9cd 100644 --- a/app/src/main/java/io/github/muntashirakon/music/lyrics/Lrc.java +++ b/app/src/main/java/io/github/muntashirakon/music/lyrics/Lrc.java @@ -1,30 +1,26 @@ package io.github.muntashirakon.music.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/io/github/muntashirakon/music/lyrics/LrcEntry.java b/app/src/main/java/io/github/muntashirakon/music/lyrics/LrcEntry.java index 8e742a304..3c546e057 100644 --- a/app/src/main/java/io/github/muntashirakon/music/lyrics/LrcEntry.java +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/lyrics/LrcHelper.java b/app/src/main/java/io/github/muntashirakon/music/lyrics/LrcHelper.java index bf07e655c..10746d98a 100644 --- a/app/src/main/java/io/github/muntashirakon/music/lyrics/LrcHelper.java +++ b/app/src/main/java/io/github/muntashirakon/music/lyrics/LrcHelper.java @@ -1,7 +1,6 @@ package io.github.muntashirakon.music.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/io/github/muntashirakon/music/lyrics/LrcUtils.java b/app/src/main/java/io/github/muntashirakon/music/lyrics/LrcUtils.java index 56704b8e2..71250ed38 100644 --- a/app/src/main/java/io/github/muntashirakon/music/lyrics/LrcUtils.java +++ b/app/src/main/java/io/github/muntashirakon/music/lyrics/LrcUtils.java @@ -17,7 +17,6 @@ package io.github.muntashirakon.music.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/io/github/muntashirakon/music/lyrics/LrcView.java b/app/src/main/java/io/github/muntashirakon/music/lyrics/LrcView.java index 327eb1df1..2533176e6 100644 --- a/app/src/main/java/io/github/muntashirakon/music/lyrics/LrcView.java +++ b/app/src/main/java/io/github/muntashirakon/music/lyrics/LrcView.java @@ -34,719 +34,739 @@ import android.view.MotionEvent; import android.view.View; import android.view.animation.LinearInterpolator; import android.widget.Scroller; - -import androidx.core.content.res.ResourcesCompat; - +import io.github.muntashirakon.music.BuildConfig; +import io.github.muntashirakon.music.R; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import io.github.muntashirakon.music.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; - } - 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/io/github/muntashirakon/music/misc/CustomFragmentStatePagerAdapter.java b/app/src/main/java/io/github/muntashirakon/music/misc/CustomFragmentStatePagerAdapter.java index 2edaccc3b..8540532da 100644 --- a/app/src/main/java/io/github/muntashirakon/music/misc/CustomFragmentStatePagerAdapter.java +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/misc/DialogAsyncTask.java b/app/src/main/java/io/github/muntashirakon/music/misc/DialogAsyncTask.java index e4421bd87..9c6c26c45 100644 --- a/app/src/main/java/io/github/muntashirakon/music/misc/DialogAsyncTask.java +++ b/app/src/main/java/io/github/muntashirakon/music/misc/DialogAsyncTask.java @@ -17,90 +17,86 @@ package io.github.muntashirakon.music.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/io/github/muntashirakon/music/misc/GenericFileProvider.java b/app/src/main/java/io/github/muntashirakon/music/misc/GenericFileProvider.java index a1facf85b..18ad0d9cf 100644 --- a/app/src/main/java/io/github/muntashirakon/music/misc/GenericFileProvider.java +++ b/app/src/main/java/io/github/muntashirakon/music/misc/GenericFileProvider.java @@ -16,5 +16,4 @@ package io.github.muntashirakon.music.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/io/github/muntashirakon/music/misc/LagTracker.java b/app/src/main/java/io/github/muntashirakon/music/misc/LagTracker.java index dd637007f..73a286d0f 100755 --- a/app/src/main/java/io/github/muntashirakon/music/misc/LagTracker.java +++ b/app/src/main/java/io/github/muntashirakon/music/misc/LagTracker.java @@ -15,62 +15,71 @@ package io.github.muntashirakon.music.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/io/github/muntashirakon/music/misc/UpdateToastMediaScannerCompletionListener.java b/app/src/main/java/io/github/muntashirakon/music/misc/UpdateToastMediaScannerCompletionListener.java index 9f281d1f8..dcfb5507b 100644 --- a/app/src/main/java/io/github/muntashirakon/music/misc/UpdateToastMediaScannerCompletionListener.java +++ b/app/src/main/java/io/github/muntashirakon/music/misc/UpdateToastMediaScannerCompletionListener.java @@ -21,25 +21,27 @@ import android.net.Uri; import android.widget.Toast; import java.lang.ref.WeakReference; +import java.util.List; import io.github.muntashirakon.music.R; /** * @author Karim Abou Zeid (kabouzeid) */ -public class UpdateToastMediaScannerCompletionListener implements MediaScannerConnection.OnScanCompletedListener { +public class UpdateToastMediaScannerCompletionListener + implements MediaScannerConnection.OnScanCompletedListener { private final WeakReference activityWeakReference; private final String couldNotScanFiles; private final String scannedFiles; - private final String[] toBeScanned; + private final List toBeScanned; private int failed = 0; private int scanned = 0; private Toast toast; @SuppressLint("ShowToast") - public UpdateToastMediaScannerCompletionListener(Activity activity, String[] toBeScanned) { + public UpdateToastMediaScannerCompletionListener(Activity activity, List toBeScanned) { this.toBeScanned = toBeScanned; scannedFiles = activity.getString(R.string.scanned_files); couldNotScanFiles = activity.getString(R.string.could_not_scan_files); @@ -51,17 +53,20 @@ public class UpdateToastMediaScannerCompletionListener implements MediaScannerCo 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(); - }); + activity.runOnUiThread( + () -> { + if (uri == null) { + failed++; + } else { + scanned++; + } + String text = + " " + + String.format(scannedFiles, scanned, toBeScanned.size()) + + (failed > 0 ? " " + String.format(couldNotScanFiles, failed) : ""); + toast.setText(text); + toast.show(); + }); } } } diff --git a/app/src/main/java/io/github/muntashirakon/music/model/Artist.kt b/app/src/main/java/io/github/muntashirakon/music/model/Artist.kt index 4b951202b..656c61db7 100644 --- a/app/src/main/java/io/github/muntashirakon/music/model/Artist.kt +++ b/app/src/main/java/io/github/muntashirakon/music/model/Artist.kt @@ -15,6 +15,7 @@ package io.github.muntashirakon.music.model import io.github.muntashirakon.music.util.MusicUtil +import code.name.monkey.retromusic.util.PreferenceUtil import java.util.* data class Artist( @@ -25,6 +26,9 @@ data class Artist( val name: String get() { val name = safeGetFirstAlbum().safeGetFirstSong().albumArtist + if (PreferenceUtil.albumArtistsOnly && MusicUtil.isVariousArtists(name)) { + return VARIOUS_ARTISTS_DISPLAY_NAME + } return if (MusicUtil.isArtistNameUnknown(name)) { UNKNOWN_ARTIST_DISPLAY_NAME } else safeGetFirstAlbum().safeGetFirstSong().artistName @@ -51,6 +55,8 @@ data class Artist( companion object { const val UNKNOWN_ARTIST_DISPLAY_NAME = "Unknown Artist" + const val VARIOUS_ARTISTS_DISPLAY_NAME = "Various Artists" + const val VARIOUS_ARTISTS_ID : Long = -2 val empty = Artist(-1, emptyList()) } diff --git a/app/src/main/java/io/github/muntashirakon/music/model/ArtworkInfo.kt b/app/src/main/java/io/github/muntashirakon/music/model/ArtworkInfo.kt new file mode 100644 index 000000000..a0bfead2b --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/model/ArtworkInfo.kt @@ -0,0 +1,5 @@ +package code.name.monkey.retromusic.model + +import android.graphics.Bitmap + +class ArtworkInfo constructor(val albumId: Long, val artwork: Bitmap?) diff --git a/app/src/main/java/io/github/muntashirakon/music/model/CategoryInfo.kt b/app/src/main/java/io/github/muntashirakon/music/model/CategoryInfo.kt index d80afa051..5707d2b6f 100644 --- a/app/src/main/java/io/github/muntashirakon/music/model/CategoryInfo.kt +++ b/app/src/main/java/io/github/muntashirakon/music/model/CategoryInfo.kt @@ -39,5 +39,4 @@ data class CategoryInfo( Genres(R.id.action_genre, R.string.genres, R.drawable.ic_guitar), Folder(R.id.action_folder, R.string.folders, R.drawable.ic_folder); } - } \ No newline at end of file diff --git a/app/src/main/java/io/github/muntashirakon/music/model/Contributor.kt b/app/src/main/java/io/github/muntashirakon/music/model/Contributor.kt index 21adf5175..bea35dbf8 100644 --- a/app/src/main/java/io/github/muntashirakon/music/model/Contributor.kt +++ b/app/src/main/java/io/github/muntashirakon/music/model/Contributor.kt @@ -14,12 +14,14 @@ package io.github.muntashirakon.music.model +import android.os.Parcelable import com.google.gson.annotations.SerializedName +import kotlinx.android.parcel.Parcelize -data class Contributor( - val name: String, - val summary: String, - val link: String, - @SerializedName("profile_image") - val profileImage: String -) \ No newline at end of file +@Parcelize +class Contributor( + @SerializedName("name") val name: String = "", + @SerializedName("summary") val summary: String = "", + @SerializedName("link") val link: String = "", + @SerializedName("image") val image: String = "" +) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/io/github/muntashirakon/music/model/DeezerResponse.kt b/app/src/main/java/io/github/muntashirakon/music/model/DeezerResponse.kt index 9c3034061..2a853fc10 100644 --- a/app/src/main/java/io/github/muntashirakon/music/model/DeezerResponse.kt +++ b/app/src/main/java/io/github/muntashirakon/music/model/DeezerResponse.kt @@ -3,13 +3,17 @@ package io.github.muntashirakon.music.model import com.google.gson.annotations.SerializedName data class Data( + @SerializedName("id") val id: String, + @SerializedName("link") val link: String, + @SerializedName("name") val name: String, @SerializedName("nb_album") val nbAlbum: Int, @SerializedName("nb_fan") val nbFan: Int, + @SerializedName("picture") val picture: String, @SerializedName("picture_big") val pictureBig: String, @@ -19,13 +23,19 @@ data class Data( val pictureSmall: String, @SerializedName("picture_xl") val pictureXl: String, + @SerializedName("radio") val radio: Boolean, + @SerializedName("tracklist") val tracklist: String, + @SerializedName("type") val type: String ) data class DeezerResponse( + @SerializedName("data") val data: List, + @SerializedName("next") val next: String, + @SerializedName("total") val total: Int ) \ No newline at end of file diff --git a/app/src/main/java/io/github/muntashirakon/music/model/LoadingInfo.kt b/app/src/main/java/io/github/muntashirakon/music/model/LoadingInfo.kt new file mode 100644 index 000000000..bfb9751f3 --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/model/LoadingInfo.kt @@ -0,0 +1,9 @@ +package code.name.monkey.retromusic.model + +import org.jaudiotagger.tag.FieldKey + +class LoadingInfo( + val filePaths: List?, + val fieldKeyValueMap: Map?, + val artworkInfo: ArtworkInfo? +) \ No newline at end of file diff --git a/app/src/main/java/io/github/muntashirakon/music/model/PlaylistSong.kt b/app/src/main/java/io/github/muntashirakon/music/model/PlaylistSong.kt index c2d1b8ad0..43798a684 100644 --- a/app/src/main/java/io/github/muntashirakon/music/model/PlaylistSong.kt +++ b/app/src/main/java/io/github/muntashirakon/music/model/PlaylistSong.kt @@ -33,7 +33,7 @@ class PlaylistSong( override val artistName: String, val playlistId: Long, val idInPlayList: Long, - override val composer: String, + override val composer: String?, override val albumArtist: String? ) : Song( id = id, diff --git a/app/src/main/java/io/github/muntashirakon/music/model/lyrics/AbsSynchronizedLyrics.java b/app/src/main/java/io/github/muntashirakon/music/model/lyrics/AbsSynchronizedLyrics.java index c2e94d896..306b4c675 100644 --- a/app/src/main/java/io/github/muntashirakon/music/model/lyrics/AbsSynchronizedLyrics.java +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/model/lyrics/Lyrics.java b/app/src/main/java/io/github/muntashirakon/music/model/lyrics/Lyrics.java index 37d76f4d5..dc7bb6660 100644 --- a/app/src/main/java/io/github/muntashirakon/music/model/lyrics/Lyrics.java +++ b/app/src/main/java/io/github/muntashirakon/music/model/lyrics/Lyrics.java @@ -14,74 +14,72 @@ package io.github.muntashirakon.music.model.lyrics; - -import java.util.ArrayList; - import io.github.muntashirakon.music.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/io/github/muntashirakon/music/model/lyrics/SynchronizedLyricsLRC.java b/app/src/main/java/io/github/muntashirakon/music/model/lyrics/SynchronizedLyricsLRC.java index 062efe9e0..ae1854fff 100644 --- a/app/src/main/java/io/github/muntashirakon/music/model/lyrics/SynchronizedLyricsLRC.java +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/model/smartplaylist/PlaylistIdGenerator.kt b/app/src/main/java/io/github/muntashirakon/music/model/smartplaylist/PlaylistIdGenerator.kt index baf6867ce..c22510f4a 100644 --- a/app/src/main/java/io/github/muntashirakon/music/model/smartplaylist/PlaylistIdGenerator.kt +++ b/app/src/main/java/io/github/muntashirakon/music/model/smartplaylist/PlaylistIdGenerator.kt @@ -6,7 +6,7 @@ import kotlin.math.abs object PlaylistIdGenerator { operator fun invoke(name: String, @DrawableRes iconRes: Int): Long { - return -abs(31L * name.hashCode() + iconRes * name.hashCode() * 31L * 31L) + return abs(31L * name.hashCode() + iconRes * name.hashCode() * 31L * 31L) } } \ No newline at end of file diff --git a/app/src/main/java/io/github/muntashirakon/music/network/model/LastFmAlbum.java b/app/src/main/java/io/github/muntashirakon/music/network/model/LastFmAlbum.java index 99488972b..69048773e 100644 --- a/app/src/main/java/io/github/muntashirakon/music/network/model/LastFmAlbum.java +++ b/app/src/main/java/io/github/muntashirakon/music/network/model/LastFmAlbum.java @@ -16,158 +16,144 @@ package io.github.muntashirakon.music.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/io/github/muntashirakon/music/network/model/LastFmArtist.java b/app/src/main/java/io/github/muntashirakon/music/network/model/LastFmArtist.java index 40c261031..36b97c244 100644 --- a/app/src/main/java/io/github/muntashirakon/music/network/model/LastFmArtist.java +++ b/app/src/main/java/io/github/muntashirakon/music/network/model/LastFmArtist.java @@ -16,111 +16,102 @@ package io.github.muntashirakon.music.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/io/github/muntashirakon/music/network/model/LastFmTrack.java b/app/src/main/java/io/github/muntashirakon/music/network/model/LastFmTrack.java index 4b018df21..7ee350ef9 100644 --- a/app/src/main/java/io/github/muntashirakon/music/network/model/LastFmTrack.java +++ b/app/src/main/java/io/github/muntashirakon/music/network/model/LastFmTrack.java @@ -16,173 +16,157 @@ package io.github.muntashirakon.music.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/io/github/muntashirakon/music/providers/BlacklistStore.java b/app/src/main/java/io/github/muntashirakon/music/providers/BlacklistStore.java index 2ad8e957b..01e219a28 100644 --- a/app/src/main/java/io/github/muntashirakon/music/providers/BlacklistStore.java +++ b/app/src/main/java/io/github/muntashirakon/music/providers/BlacklistStore.java @@ -14,6 +14,8 @@ package io.github.muntashirakon.music.providers; +import static io.github.muntashirakon.music.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 io.github.muntashirakon.music.util.FileUtil; +import io.github.muntashirakon.music.util.PreferenceUtil; import java.io.File; import java.util.ArrayList; -import io.github.muntashirakon.music.util.FileUtil; -import io.github.muntashirakon.music.util.PreferenceUtil; - -import static io.github.muntashirakon.music.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/io/github/muntashirakon/music/providers/HistoryStore.java b/app/src/main/java/io/github/muntashirakon/music/providers/HistoryStore.java index f4502fac2..2557e5c85 100644 --- a/app/src/main/java/io/github/muntashirakon/music/providers/HistoryStore.java +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/providers/MusicPlaybackQueueStore.java b/app/src/main/java/io/github/muntashirakon/music/providers/MusicPlaybackQueueStore.java index fb8b119f4..17d4359c3 100644 --- a/app/src/main/java/io/github/muntashirakon/music/providers/MusicPlaybackQueueStore.java +++ b/app/src/main/java/io/github/muntashirakon/music/providers/MusicPlaybackQueueStore.java @@ -20,7 +20,6 @@ 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; @@ -29,186 +28,188 @@ import java.util.List; import io.github.muntashirakon.music.App; import io.github.muntashirakon.music.model.Song; import io.github.muntashirakon.music.repository.RealSongRepository; +import io.github.muntashirakon.music.App; +import io.github.muntashirakon.music.model.Song; +import io.github.muntashirakon.music.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/io/github/muntashirakon/music/providers/SongPlayCountStore.java b/app/src/main/java/io/github/muntashirakon/music/providers/SongPlayCountStore.java index 66a878390..40d0053ae 100644 --- a/app/src/main/java/io/github/muntashirakon/music/providers/SongPlayCountStore.java +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/repository/ArtistRepository.kt b/app/src/main/java/io/github/muntashirakon/music/repository/ArtistRepository.kt index 8ea9bc694..5785c4a76 100644 --- a/app/src/main/java/io/github/muntashirakon/music/repository/ArtistRepository.kt +++ b/app/src/main/java/io/github/muntashirakon/music/repository/ArtistRepository.kt @@ -40,6 +40,19 @@ class RealArtistRepository( PreferenceUtil.artistSongSortOrder } override fun artist(artistId: Long): Artist { + if (artistId == Artist.VARIOUS_ARTISTS_ID) { + // Get Various Artists + val songs = songRepository.songs( + songRepository.makeSongCursor( + null, + null, + getSongLoaderSortOrder() + ) + ) + val albums = albumRepository.splitIntoAlbums(songs).filter { it.albumArtist == Artist.VARIOUS_ARTISTS_DISPLAY_NAME } + return Artist(Artist.VARIOUS_ARTISTS_ID, albums) + } + val songs = songRepository.songs( songRepository.makeSongCursor( AudioColumns.ARTIST_ID + "=?", @@ -67,6 +80,7 @@ class RealArtistRepository( getSongLoaderSortOrder() ) ) + return splitIntoAlbumArtists(albumRepository.splitIntoAlbums(songs)) } @@ -86,8 +100,12 @@ class RealArtistRepository( return albums.groupBy { it.albumArtist } .map { val currentAlbums = it.value - if (albums.isNotEmpty()) { - Artist(currentAlbums[0].id, currentAlbums) + if (currentAlbums.isNotEmpty()) { + if (currentAlbums[0].albumArtist == Artist.VARIOUS_ARTISTS_DISPLAY_NAME) { + Artist(Artist.VARIOUS_ARTISTS_ID, currentAlbums) + } else { + Artist(currentAlbums[0].artistId, currentAlbums) + } } else { Artist.empty } @@ -100,4 +118,4 @@ class RealArtistRepository( return albums.groupBy { it.artistId } .map { Artist(it.key, it.value) } } -} +} \ No newline at end of file diff --git a/app/src/main/java/io/github/muntashirakon/music/repository/GenreRepository.kt b/app/src/main/java/io/github/muntashirakon/music/repository/GenreRepository.kt index 7be444faa..41543341d 100644 --- a/app/src/main/java/io/github/muntashirakon/music/repository/GenreRepository.kt +++ b/app/src/main/java/io/github/muntashirakon/music/repository/GenreRepository.kt @@ -24,6 +24,7 @@ import io.github.muntashirakon.music.Constants.IS_MUSIC import io.github.muntashirakon.music.Constants.baseProjection import io.github.muntashirakon.music.extensions.getLong import io.github.muntashirakon.music.extensions.getString +import io.github.muntashirakon.music.extensions.getStringOrNull import io.github.muntashirakon.music.model.Genre import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.util.PreferenceUtil @@ -53,9 +54,9 @@ class RealGenreRepository( private fun getGenreFromCursor(cursor: Cursor): Genre { val id = cursor.getLong(Genres._ID) - val name = cursor.getString(Genres.NAME) + val name = cursor.getStringOrNull(Genres.NAME) val songCount = songs(id).size - return Genre(id, name, songCount) + return Genre(id, name ?: "", songCount) } @@ -94,13 +95,17 @@ class RealGenreRepository( } private fun makeGenreSongCursor(genreId: Long): Cursor? { - return contentResolver.query( - Genres.Members.getContentUri("external", genreId), - baseProjection, - IS_MUSIC, - null, - PreferenceUtil.songSortOrder - ) + return try { + contentResolver.query( + Genres.Members.getContentUri("external", genreId), + baseProjection, + IS_MUSIC, + null, + PreferenceUtil.songSortOrder + ) + } catch (e: SecurityException) { + return null + } } private fun getGenresFromCursor(cursor: Cursor?): ArrayList { @@ -142,17 +147,18 @@ class RealGenreRepository( return genres } - private fun makeGenreCursor(): Cursor? { val projection = arrayOf(Genres._ID, Genres.NAME) - return contentResolver.query( - Genres.EXTERNAL_CONTENT_URI, - projection, - null, - null, - PreferenceUtil.genreSortOrder - ) + return try { + contentResolver.query( + Genres.EXTERNAL_CONTENT_URI, + projection, + null, + null, + PreferenceUtil.genreSortOrder + ) + } catch (e: SecurityException) { + return null + } } - - } diff --git a/app/src/main/java/io/github/muntashirakon/music/repository/LocalDataRepository.kt b/app/src/main/java/io/github/muntashirakon/music/repository/LocalDataRepository.kt new file mode 100644 index 000000000..a40633051 --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/repository/LocalDataRepository.kt @@ -0,0 +1,25 @@ +package code.name.monkey.retromusic.repository + +import android.content.Context +import code.name.monkey.retromusic.model.Contributor +import com.google.gson.GsonBuilder +import com.google.gson.reflect.TypeToken + +interface LocalDataRepository { + fun contributors(): List +} + +class RealLocalDataRepository( + private val context: Context +) : LocalDataRepository { + + override fun contributors(): List { + val jsonString = context.assets.open("contributors.json") + .bufferedReader().use { it.readText() } + + val gsonBuilder = GsonBuilder() + val gson = gsonBuilder.create() + val listContributorType = object : TypeToken>() {}.type + return gson.fromJson(jsonString, listContributorType) + } +} \ No newline at end of file diff --git a/app/src/main/java/io/github/muntashirakon/music/repository/PlaylistSongsLoader.kt b/app/src/main/java/io/github/muntashirakon/music/repository/PlaylistSongsLoader.kt index e0354a979..b71c8cae1 100644 --- a/app/src/main/java/io/github/muntashirakon/music/repository/PlaylistSongsLoader.kt +++ b/app/src/main/java/io/github/muntashirakon/music/repository/PlaylistSongsLoader.kt @@ -16,9 +16,8 @@ package io.github.muntashirakon.music.repository import android.content.Context import android.database.Cursor -import android.provider.MediaStore import android.provider.MediaStore.Audio.AudioColumns -import android.provider.MediaStore.Audio.Playlists.* +import android.provider.MediaStore.Audio.Playlists.Members import io.github.muntashirakon.music.Constants.IS_MUSIC import io.github.muntashirakon.music.extensions.getInt import io.github.muntashirakon.music.extensions.getLong @@ -83,7 +82,7 @@ object PlaylistSongsLoader { val artistId = cursor.getLong(AudioColumns.ARTIST_ID) val artistName = cursor.getString(AudioColumns.ARTIST) val idInPlaylist = cursor.getLong(Members._ID) - val composer = cursor.getString(AudioColumns.COMPOSER) + val composer = cursor.getStringOrNull(AudioColumns.COMPOSER) val albumArtist = cursor.getStringOrNull("album_artist") return PlaylistSong( id, diff --git a/app/src/main/java/io/github/muntashirakon/music/repository/Repository.kt b/app/src/main/java/io/github/muntashirakon/music/repository/Repository.kt index 041770e9c..518cff007 100644 --- a/app/src/main/java/io/github/muntashirakon/music/repository/Repository.kt +++ b/app/src/main/java/io/github/muntashirakon/music/repository/Repository.kt @@ -16,12 +16,12 @@ package io.github.muntashirakon.music.repository import android.content.Context import androidx.lifecycle.LiveData +import androidx.lifecycle.Transformations import io.github.muntashirakon.music.* import io.github.muntashirakon.music.db.* import io.github.muntashirakon.music.model.* import io.github.muntashirakon.music.model.smartplaylist.NotPlayedPlaylist import io.github.muntashirakon.music.network.LastFMService -import io.github.muntashirakon.music.network.LyricsRestService import io.github.muntashirakon.music.network.Result import io.github.muntashirakon.music.network.Result.* import io.github.muntashirakon.music.network.model.LastFmAlbum @@ -40,9 +40,9 @@ interface Repository { fun genresFlow(): Flow>> fun historySong(): List fun favorites(): LiveData> - fun observableHistorySongs(): LiveData> + fun observableHistorySongs(): LiveData> fun albumById(albumId: Long): Album - fun playlistSongs(playlistEntity: PlaylistEntity): LiveData> + fun playlistSongs(playListId: Long): LiveData> suspend fun fetchAlbums(): List suspend fun albumByIdAsync(albumId: Long): Album suspend fun allSongs(): List @@ -96,8 +96,11 @@ interface Repository { suspend fun checkSongExistInPlayCount(songId: Long): List suspend fun playCountSongs(): List suspend fun blackListPaths(): List - suspend fun lyrics(artist: String, title: String): Result suspend fun deleteSongs(songs: List) + suspend fun contributor(): List + suspend fun searchArtists(query: String): List + suspend fun searchSongs(query: String): List + suspend fun searchAlbums(query: String): List } class RealRepository( @@ -112,18 +115,21 @@ class RealRepository( private val searchRepository: RealSearchRepository, private val topPlayedRepository: TopPlayedRepository, private val roomRepository: RoomRepository, - private val lyricsRestService: LyricsRestService + private val localDataRepository: LocalDataRepository ) : Repository { - override suspend fun lyrics(artist: String, title: String): Result = try { - Success(lyricsRestService.getLyrics(artist, title)) - } catch (e: Exception) { - println(e) - Error(e) - } override suspend fun deleteSongs(songs: List) = roomRepository.deleteSongs(songs) + override suspend fun contributor(): List = localDataRepository.contributors() + + override suspend fun searchSongs(query: String): List = songRepository.songs(query) + + override suspend fun searchAlbums(query: String): List = albumRepository.albums(query) + + override suspend fun searchArtists(query: String): List = + artistRepository.artists(query) + override suspend fun fetchAlbums(): List = albumRepository.albums() override suspend fun albumByIdAsync(albumId: Long): Album = albumRepository.album(albumId) @@ -246,8 +252,8 @@ class RealRepository( it.toSong() } - override fun playlistSongs(playlistEntity: PlaylistEntity): LiveData> = - roomRepository.getSongs(playlistEntity) + override fun playlistSongs(playListId: Long): LiveData> = + roomRepository.getSongs(playListId) override suspend fun insertSongs(songs: List) = roomRepository.insertSongs(songs) @@ -315,8 +321,10 @@ class RealRepository( override suspend fun blackListPaths(): List = roomRepository.blackListPaths() - override fun observableHistorySongs(): LiveData> = - roomRepository.observableHistorySongs() + override fun observableHistorySongs(): LiveData> = + Transformations.map(roomRepository.observableHistorySongs()) { + it.fromHistoryToSongs() + } override fun historySong(): List = roomRepository.historySongs() diff --git a/app/src/main/java/io/github/muntashirakon/music/repository/RoomRepository.kt b/app/src/main/java/io/github/muntashirakon/music/repository/RoomRepository.kt index e42bff6b5..f410080c4 100644 --- a/app/src/main/java/io/github/muntashirakon/music/repository/RoomRepository.kt +++ b/app/src/main/java/io/github/muntashirakon/music/repository/RoomRepository.kt @@ -2,9 +2,24 @@ package io.github.muntashirakon.music.repository import androidx.annotation.WorkerThread import androidx.lifecycle.LiveData -import io.github.muntashirakon.music.db.* +import io.github.muntashirakon.music.db.BlackListStoreDao +import io.github.muntashirakon.music.db.BlackListStoreEntity +import io.github.muntashirakon.music.db.HistoryDao +import io.github.muntashirakon.music.db.HistoryEntity +import io.github.muntashirakon.music.db.LyricsDao +import io.github.muntashirakon.music.db.PlayCountDao +import io.github.muntashirakon.music.db.PlayCountEntity +import io.github.muntashirakon.music.db.PlaylistDao +import io.github.muntashirakon.music.db.PlaylistEntity +import io.github.muntashirakon.music.db.PlaylistWithSongs +import io.github.muntashirakon.music.db.SongEntity +import io.github.muntashirakon.music.db.toHistoryEntity +import io.github.muntashirakon.music.helper.SortOrder.PlaylistSortOrder.Companion.PLAYLIST_A_Z +import io.github.muntashirakon.music.helper.SortOrder.PlaylistSortOrder.Companion.PLAYLIST_SONG_COUNT +import io.github.muntashirakon.music.helper.SortOrder.PlaylistSortOrder.Companion.PLAYLIST_SONG_COUNT_DESC +import io.github.muntashirakon.music.helper.SortOrder.PlaylistSortOrder.Companion.PLAYLIST_Z_A import io.github.muntashirakon.music.model.Song -import io.github.muntashirakon.music.db.* +import io.github.muntashirakon.music.util.PreferenceUtil interface RoomRepository { @@ -12,7 +27,7 @@ interface RoomRepository { fun favoritePlaylistLiveData(favorite: String): LiveData> fun insertBlacklistPath(blackListStoreEntity: BlackListStoreEntity) fun observableHistorySongs(): LiveData> - fun getSongs(playlistEntity: PlaylistEntity): LiveData> + fun getSongs(playListId: Long): LiveData> suspend fun createPlaylist(playlistEntity: PlaylistEntity): Long suspend fun checkPlaylistExists(playlistName: String): List suspend fun playlists(): List @@ -62,7 +77,22 @@ class RealRoomRepository( @WorkerThread override suspend fun playlistWithSongs(): List = - playlistDao.playlistsWithSongs() + when (PreferenceUtil.playlistSortOrder) { + PLAYLIST_A_Z -> + playlistDao.playlistsWithSongs().sortedBy { + it.playlistEntity.playlistName + } + PLAYLIST_Z_A -> playlistDao.playlistsWithSongs() + .sortedByDescending { + it.playlistEntity.playlistName + } + PLAYLIST_SONG_COUNT -> playlistDao.playlistsWithSongs().sortedBy { it.songs.size } + PLAYLIST_SONG_COUNT_DESC -> playlistDao.playlistsWithSongs() + .sortedByDescending { it.songs.size } + else -> playlistDao.playlistsWithSongs().sortedBy { + it.playlistEntity.playlistName + } + } @WorkerThread override suspend fun insertSongs(songs: List) { @@ -71,8 +101,8 @@ class RealRoomRepository( } - override fun getSongs(playlistEntity: PlaylistEntity): LiveData> = - playlistDao.songsFromPlaylist(playlistEntity.playListId) + override fun getSongs(playListId: Long): LiveData> = + playlistDao.songsFromPlaylist(playListId) override suspend fun deletePlaylistEntities(playlistEntities: List) = playlistDao.deletePlaylists(playlistEntities) diff --git a/app/src/main/java/io/github/muntashirakon/music/repository/SearchRepository.kt b/app/src/main/java/io/github/muntashirakon/music/repository/SearchRepository.kt index 40b0eb23c..946aff516 100644 --- a/app/src/main/java/io/github/muntashirakon/music/repository/SearchRepository.kt +++ b/app/src/main/java/io/github/muntashirakon/music/repository/SearchRepository.kt @@ -22,11 +22,11 @@ import java.util.* class RealSearchRepository( private val songRepository: SongRepository, private val albumRepository: AlbumRepository, - private val artistRepository: RealArtistRepository, + private val artistRepository: ArtistRepository, + private val roomRepository: RoomRepository, private val genreRepository: GenreRepository, - private val playlistRepository: PlaylistRepository ) { - fun searchAll(context: Context, query: String?): MutableList { + suspend fun searchAll(context: Context, query: String?): MutableList { val results = mutableListOf() query?.let { searchString -> val songs = songRepository.songs(searchString) @@ -53,8 +53,8 @@ class RealSearchRepository( results.add(context.resources.getString(R.string.genres)) results.addAll(genres) } - val playlist = playlistRepository.playlists().filter { playlist -> - playlist.name.toLowerCase(Locale.getDefault()) + val playlist = roomRepository.playlistWithSongs().filter { playlist -> + playlist.playlistEntity.playlistName.toLowerCase(Locale.getDefault()) .contains(searchString.toLowerCase(Locale.getDefault())) } if (playlist.isNotEmpty()) { diff --git a/app/src/main/java/io/github/muntashirakon/music/repository/SongRepository.kt b/app/src/main/java/io/github/muntashirakon/music/repository/SongRepository.kt index 46cae7a45..c0e2cccf3 100644 --- a/app/src/main/java/io/github/muntashirakon/music/repository/SongRepository.kt +++ b/app/src/main/java/io/github/muntashirakon/music/repository/SongRepository.kt @@ -149,13 +149,22 @@ class RealSongRepository(private val context: Context) : SongRepository { selectionFinal = selectionFinal + " AND " + Media.DURATION + ">= " + (PreferenceUtil.filterLength * 1000) - return context.contentResolver.query( - Media.getContentUri(MediaStore.VOLUME_EXTERNAL), - baseProjection, - selectionFinal, - selectionValuesFinal, - sortOrder - ) + val uri = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) { + Media.getContentUri(MediaStore.VOLUME_EXTERNAL) + } else { + Media.EXTERNAL_CONTENT_URI + } + return try { + context.contentResolver.query( + uri, + baseProjection, + selectionFinal, + selectionValuesFinal, + sortOrder + ) + } catch (ex: SecurityException) { + return null + } } private fun generateBlacklistSelection( diff --git a/app/src/main/java/io/github/muntashirakon/music/repository/SortedCursor.java b/app/src/main/java/io/github/muntashirakon/music/repository/SortedCursor.java index c036edbea..8e9f6a4ae 100644 --- a/app/src/main/java/io/github/muntashirakon/music/repository/SortedCursor.java +++ b/app/src/main/java/io/github/muntashirakon/music/repository/SortedCursor.java @@ -15,154 +15,150 @@ package io.github.muntashirakon.music.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/io/github/muntashirakon/music/repository/SortedLongCursor.java b/app/src/main/java/io/github/muntashirakon/music/repository/SortedLongCursor.java index 575f69df0..49aafb6a2 100644 --- a/app/src/main/java/io/github/muntashirakon/music/repository/SortedLongCursor.java +++ b/app/src/main/java/io/github/muntashirakon/music/repository/SortedLongCursor.java @@ -15,154 +15,149 @@ package io.github.muntashirakon.music.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/io/github/muntashirakon/music/service/MultiPlayer.java b/app/src/main/java/io/github/muntashirakon/music/service/MultiPlayer.java index 441f3ef46..269237c89 100644 --- a/app/src/main/java/io/github/muntashirakon/music/service/MultiPlayer.java +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.R; import io.github.muntashirakon.music.service.playback.Playback; import io.github.muntashirakon.music.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/io/github/muntashirakon/music/service/MusicService.java b/app/src/main/java/io/github/muntashirakon/music/service/MusicService.java index bdaa42579..2ddb6326f 100644 --- a/app/src/main/java/io/github/muntashirakon/music/service/MusicService.java +++ b/app/src/main/java/io/github/muntashirakon/music/service/MusicService.java @@ -63,6 +63,7 @@ import java.util.Objects; import java.util.Random; import io.github.muntashirakon.music.R; +import io.github.muntashirakon.music.activities.LockScreenActivity; import io.github.muntashirakon.music.appwidgets.AppWidgetBig; import io.github.muntashirakon.music.appwidgets.AppWidgetCard; import io.github.muntashirakon.music.appwidgets.AppWidgetClassic; @@ -71,8 +72,10 @@ import io.github.muntashirakon.music.appwidgets.AppWidgetText; import io.github.muntashirakon.music.glide.BlurTransformation; import io.github.muntashirakon.music.glide.SongGlideRequest; import io.github.muntashirakon.music.helper.ShuffleHelper; +import io.github.muntashirakon.music.model.AbsCustomPlaylist; import io.github.muntashirakon.music.model.Playlist; import io.github.muntashirakon.music.model.Song; +import io.github.muntashirakon.music.model.smartplaylist.AbsSmartPlaylist; import io.github.muntashirakon.music.providers.HistoryStore; import io.github.muntashirakon.music.providers.MusicPlaybackQueueStore; import io.github.muntashirakon.music.providers.SongPlayCountStore; @@ -94,8 +97,8 @@ import static io.github.muntashirakon.music.ConstantsKt.TOGGLE_HEADSET; /** * @author Karim Abou Zeid (kabouzeid), Andrew Neal */ -public class MusicService extends Service implements - SharedPreferences.OnSharedPreferenceChangeListener, Playback.PlaybackCallbacks { +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 = "io.github.muntashirakon.music"; @@ -109,17 +112,22 @@ public class MusicService extends Service implements 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 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) + // 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 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 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"; @@ -144,13 +152,14 @@ public class MusicService extends Service implements 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 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; @@ -171,43 +180,44 @@ public class MusicService extends Service implements private AppWidgetText appWidgetText = AppWidgetText.Companion.getInstance(); - 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; + 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; + } + } } } - } - - } - }; + }; private AudioManager audioManager; - private IntentFilter becomingNoisyReceiverIntentFilter = new IntentFilter( - AudioManager.ACTION_AUDIO_BECOMING_NOISY); + private IntentFilter becomingNoisyReceiverIntentFilter = + new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY); private boolean becomingNoisyReceiverRegistered; - private IntentFilter bluetoothConnectedIntentFilter = new IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED); + 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; @@ -219,96 +229,112 @@ public class MusicService extends Service implements private List playingQueue = new ArrayList<>(); private boolean pausedByTransientLossOfFocus; - 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(); - } - } - }; + 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(); + } + } + }; private PlaybackHandler playerHandler; - private final AudioManager.OnAudioFocusChangeListener audioFocusListener - = new AudioManager.OnAudioFocusChangeListener() { - @Override - public void onAudioFocusChange(final int focusChange) { - playerHandler.obtainMessage(FOCUS_CHANGE, focusChange, 0).sendToTarget(); - } - }; + private final AudioManager.OnAudioFocusChangeListener audioFocusListener = + new AudioManager.OnAudioFocusChangeListener() { + @Override + public void onAudioFocusChange(final int focusChange) { + playerHandler.obtainMessage(FOCUS_CHANGE, focusChange, 0).sendToTarget(); + } + }; private PlayingNotification playingNotification; - private final BroadcastReceiver updateFavoriteReceiver = new BroadcastReceiver() { - @Override - public void onReceive(final Context context, final Intent intent) { - updateNotification(); - } - }; - + private final BroadcastReceiver updateFavoriteReceiver = + new BroadcastReceiver() { + @Override + public void onReceive(final Context context, final Intent intent) { + updateNotification(); + } + }; + 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); + } + } + }; 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(); + 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(); + } + } } } } - } - } - }; - 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); - } - }; - 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); + }; + private PhoneStateListener phoneStateListener = + new PhoneStateListener() { + @Override + public void onCallStateChanged(int state, String incomingNumber) { switch (state) { - case 0: + 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; - case 1: - play(); - break; + default: + } + super.onCallStateChanged(state, incomingNumber); + } + }; + 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; + } + } } } - } - } - }; + }; private ThrottledSeekHandler throttledSeekHandler; private Handler uiThreadHandler; private PowerManager.WakeLock wakeLock; @@ -333,7 +359,8 @@ public class MusicService extends Service implements @Override public void onCreate() { super.onCreate(); - final TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE); + final TelephonyManager telephonyManager = + (TelephonyManager) getSystemService(TELEPHONY_SERVICE); if (telephonyManager != null) { telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE); } @@ -353,8 +380,10 @@ public class MusicService extends Service implements 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); + // 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()); @@ -362,38 +391,49 @@ public class MusicService extends Service implements 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); + .registerContentObserver( + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, true, mediaStoreObserver); getContentResolver() - .registerContentObserver(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, true, mediaStoreObserver); + .registerContentObserver( + MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, true, mediaStoreObserver); getContentResolver() - .registerContentObserver(MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, true, mediaStoreObserver); + .registerContentObserver( + MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, true, mediaStoreObserver); getContentResolver() - .registerContentObserver(MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI, true, mediaStoreObserver); + .registerContentObserver( + MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI, true, mediaStoreObserver); getContentResolver() - .registerContentObserver(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, true, mediaStoreObserver); + .registerContentObserver( + MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, true, mediaStoreObserver); getContentResolver() - .registerContentObserver(MediaStore.Audio.Media.INTERNAL_CONTENT_URI, true, mediaStoreObserver); + .registerContentObserver( + MediaStore.Audio.Media.INTERNAL_CONTENT_URI, true, mediaStoreObserver); getContentResolver() - .registerContentObserver(MediaStore.Audio.Albums.INTERNAL_CONTENT_URI, true, mediaStoreObserver); + .registerContentObserver( + MediaStore.Audio.Albums.INTERNAL_CONTENT_URI, true, mediaStoreObserver); getContentResolver() - .registerContentObserver(MediaStore.Audio.Artists.INTERNAL_CONTENT_URI, true, mediaStoreObserver); + .registerContentObserver( + MediaStore.Audio.Artists.INTERNAL_CONTENT_URI, true, mediaStoreObserver); getContentResolver() - .registerContentObserver(MediaStore.Audio.Genres.INTERNAL_CONTENT_URI, true, mediaStoreObserver); + .registerContentObserver( + MediaStore.Audio.Genres.INTERNAL_CONTENT_URI, true, mediaStoreObserver); getContentResolver() - .registerContentObserver(MediaStore.Audio.Playlists.INTERNAL_CONTENT_URI, true, mediaStoreObserver); + .registerContentObserver( + MediaStore.Audio.Playlists.INTERNAL_CONTENT_URI, true, mediaStoreObserver); PreferenceUtil.INSTANCE.registerOnSharedPreferenceChangedListener(this); restoreState(); - sendBroadcast(new Intent(RETRO_MUSIC_PACKAGE_NAME + ".RETRO_MUSIC_SERVICE_CREATED")); + sendBroadcast(new Intent("io.github.muntashirakon.music.RETRO_MUSIC_SERVICE_CREATED")); registerHeadsetEvents(); registerBluetoothConnected(); @@ -403,6 +443,7 @@ public class MusicService extends Service implements public void onDestroy() { unregisterReceiver(widgetIntentReceiver); unregisterReceiver(updateFavoriteReceiver); + unregisterReceiver(lockScreenReceiver); if (becomingNoisyReceiverRegistered) { unregisterReceiver(becomingNoisyReceiver); becomingNoisyReceiverRegistered = false; @@ -422,7 +463,7 @@ public class MusicService extends Service implements PreferenceUtil.INSTANCE.unregisterOnSharedPreferenceChangedListener(this); wakeLock.release(); - sendBroadcast(new Intent(RETRO_MUSIC_PACKAGE_NAME + ".RETRO_MUSIC_SERVICE_DESTROYED")); + sendBroadcast(new Intent("io.github.muntashirakon.music.RETRO_MUSIC_SERVICE_DESTROYED")); } public void acquireWakeLock(long milli) { @@ -591,7 +632,8 @@ public class MusicService extends Service implements case REPEAT_MODE_ALL: case REPEAT_MODE_THIS: this.repeatMode = repeatMode; - PreferenceManager.getDefaultSharedPreferences(this).edit() + PreferenceManager.getDefaultSharedPreferences(this) + .edit() .putInt(SAVED_REPEAT_MODE, repeatMode) .apply(); prepareNext(); @@ -605,7 +647,8 @@ public class MusicService extends Service implements } public void setShuffleMode(final int shuffleMode) { - PreferenceManager.getDefaultSharedPreferences(this).edit() + PreferenceManager.getDefaultSharedPreferences(this) + .edit() .putInt(SAVED_SHUFFLE_MODE, shuffleMode) .apply(); switch (shuffleMode) { @@ -664,8 +707,8 @@ public class MusicService extends Service implements } public void initNotification() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && - !PreferenceUtil.INSTANCE.isClassicNotification()) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N + && !PreferenceUtil.INSTANCE.isClassicNotification()) { playingNotification = new PlayingNotificationImpl(); } else { playingNotification = new PlayingNotificationOreo(); @@ -725,7 +768,8 @@ public class MusicService extends Service implements } @Override - public void onSharedPreferenceChanged(@NonNull SharedPreferences sharedPreferences, @NonNull String key) { + public void onSharedPreferenceChanged( + @NonNull SharedPreferences sharedPreferences, @NonNull String key) { switch (key) { case GAP_LESS_PLAYBACK: if (sharedPreferences.getBoolean(key, false)) { @@ -817,10 +861,14 @@ public class MusicService extends Service implements 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()) { + 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); @@ -878,13 +926,15 @@ public class MusicService extends Service implements } notifyChange(PLAY_STATE_CHANGED); - // fixes a bug where the volume would stay ducked because the AudioManager.AUDIOFOCUS_GAIN event is not sent + // 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) + Toast.makeText( + this, getResources().getString(R.string.audio_focus_denied), Toast.LENGTH_SHORT) .show(); } } @@ -908,7 +958,8 @@ public class MusicService extends Service implements if (openTrackAndPrepareNextAt(position)) { play(); } else { - Toast.makeText(this, getResources().getString(R.string.unplayable_file), Toast.LENGTH_SHORT).show(); + Toast.makeText(this, getResources().getString(R.string.unplayable_file), Toast.LENGTH_SHORT) + .show(); } } @@ -988,12 +1039,15 @@ public class MusicService extends Service implements 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); + 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() + if (restoredQueue.size() > 0 + && restoredQueue.size() == restoredOriginalQueue.size() && restoredPosition != -1) { this.originalPlayingQueue = restoredOriginalQueue; this.playingQueue = restoredQueue; @@ -1019,8 +1073,10 @@ public class MusicService extends Service implements } public void savePositionInTrack() { - PreferenceManager.getDefaultSharedPreferences(this).edit() - .putInt(SAVED_POSITION_IN_TRACK, getSongProgressMillis()).apply(); + PreferenceManager.getDefaultSharedPreferences(this) + .edit() + .putInt(SAVED_POSITION_IN_TRACK, getSongProgressMillis()) + .apply(); } public void saveQueuesImpl() { @@ -1076,10 +1132,13 @@ public class MusicService extends Service implements } public void updateMediaSessionPlaybackState() { - PlaybackStateCompat.Builder stateBuilder = new PlaybackStateCompat.Builder() - .setActions(MEDIA_SESSION_ACTIONS) - .setState(isPlaying() ? PlaybackStateCompat.STATE_PLAYING : PlaybackStateCompat.STATE_PAUSED, - getSongProgressMillis(), 1); + PlaybackStateCompat.Builder stateBuilder = + new PlaybackStateCompat.Builder() + .setActions(MEDIA_SESSION_ACTIONS) + .setState( + isPlaying() ? PlaybackStateCompat.STATE_PLAYING : PlaybackStateCompat.STATE_PAUSED, + getSongProgressMillis(), + 1); setCustomAction(stateBuilder); @@ -1092,7 +1151,8 @@ public class MusicService extends Service implements } } - void updateMediaSessionMetaData() { + public void updateMediaSessionMetaData() { + Log.i(TAG, "onResourceReady: "); final Song song = getCurrentSong(); if (song.getId() == -1) { @@ -1108,13 +1168,15 @@ public class MusicService extends Service implements .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()); + .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, null); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + metaData.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) + final BitmapRequestBuilder request = SongGlideRequest.Builder.from(Glide.with(MusicService.this), song) .checkIgnoreMediaStore(MusicService.this) .asBitmap().build(); if (PreferenceUtil.INSTANCE.isBlurredAlbumArt()) { @@ -1132,6 +1194,7 @@ public class MusicService extends Service implements @Override public void onResourceReady(Bitmap resource, GlideAnimation glideAnimation) { + metaData.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, copy(resource)); mediaSession.setMetadata(metaData.build()); } @@ -1144,7 +1207,8 @@ public class MusicService extends Service implements } private void closeAudioEffectSession() { - final Intent audioEffectsIntent = new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION); + final Intent audioEffectsIntent = + new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION); if (playback != null) { audioEffectsIntent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, playback.getAudioSessionId()); } @@ -1177,15 +1241,11 @@ public class MusicService extends Service implements savePosition(); savePositionInTrack(); final Song currentSong = getCurrentSong(); - if (currentSong != null) { - HistoryStore.getInstance(this).addSongId(currentSong.getId()); - } + HistoryStore.getInstance(this).addSongId(currentSong.getId()); if (songPlayCountHelper.shouldBumpPlayCount()) { SongPlayCountStore.getInstance(this).bumpPlayCount(songPlayCountHelper.getSong().getId()); } - if (currentSong != null) { - songPlayCountHelper.notifySongChanged(currentSong); - } + songPlayCountHelper.notifySongChanged(currentSong); break; case QUEUE_CHANGED: updateMediaSessionMetaData(); // because playing queue size might have changed @@ -1213,10 +1273,10 @@ public class MusicService extends Service implements } private void playFromPlaylist(Intent intent) { - Playlist playlist = intent.getParcelableExtra(INTENT_EXTRA_PLAYLIST); + AbsSmartPlaylist playlist = intent.getParcelableExtra(INTENT_EXTRA_PLAYLIST); int shuffleMode = intent.getIntExtra(INTENT_EXTRA_SHUFFLE_MODE, getShuffleMode()); if (playlist != null) { - List playlistSongs = playlist.getSongs(); + List playlistSongs = playlist.songs(); if (!playlistSongs.isEmpty()) { if (shuffleMode == SHUFFLE_MODE_SHUFFLE) { int startPosition = new Random().nextInt(playlistSongs.size()); @@ -1226,7 +1286,8 @@ public class MusicService extends Service implements openQueue(playlistSongs, 0, true); } } else { - Toast.makeText(getApplicationContext(), R.string.playlist_is_empty, Toast.LENGTH_LONG).show(); + 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(); @@ -1280,7 +1341,8 @@ public class MusicService extends Service implements private boolean requestFocus() { return (getAudioManager() - .requestAudioFocus(audioFocusListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN) + .requestAudioFocus( + audioFocusListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED); } @@ -1295,7 +1357,10 @@ public class MusicService extends Service implements } private void savePosition() { - PreferenceManager.getDefaultSharedPreferences(this).edit().putInt(SAVED_POSITION, getPosition()).apply(); + PreferenceManager.getDefaultSharedPreferences(this) + .edit() + .putInt(SAVED_POSITION, getPosition()) + .apply(); } private void saveQueues() { @@ -1313,56 +1378,60 @@ public class MusicService extends Service implements } private void setCustomAction(PlaybackStateCompat.Builder stateBuilder) { - int repeatIcon = R.drawable.ic_repeat; // REPEAT_MODE_NONE + 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( - CYCLE_REPEAT, getString(R.string.action_cycle_repeat), repeatIcon) - .build()); + stateBuilder.addCustomAction( + new PlaybackStateCompat.CustomAction.Builder( + CYCLE_REPEAT, getString(R.string.action_cycle_repeat), repeatIcon) + .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( - TOGGLE_SHUFFLE, getString(R.string.action_toggle_shuffle), shuffleIcon) - .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( + TOGGLE_SHUFFLE, getString(R.string.action_toggle_shuffle), shuffleIcon) + .build()); - 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()); + 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()); } private void setupMediaSession() { - ComponentName mediaButtonReceiverComponentName = new ComponentName( - getApplicationContext(), - MediaButtonIntentReceiver.class); + 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); + PendingIntent mediaButtonReceiverPendingIntent = + PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0); - mediaSession = new MediaSessionCompat(this, - "Metro", - mediaButtonReceiverComponentName, - mediaButtonReceiverPendingIntent); - MediaSessionCallback mediasessionCallback = new MediaSessionCallback( - getApplicationContext(), this); - mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS - | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS - ); + 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 { @@ -1372,4 +1441,4 @@ public class MusicService extends Service implements return MusicService.this; } } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/muntashirakon/music/service/PlaybackHandler.java b/app/src/main/java/io/github/muntashirakon/music/service/PlaybackHandler.java index 5579462fe..ad4ccd929 100644 --- a/app/src/main/java/io/github/muntashirakon/music/service/PlaybackHandler.java +++ b/app/src/main/java/io/github/muntashirakon/music/service/PlaybackHandler.java @@ -14,17 +14,6 @@ package io.github.muntashirakon.music.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 io.github.muntashirakon.music.util.PreferenceUtil; - import static io.github.muntashirakon.music.service.MusicService.DUCK; import static io.github.muntashirakon.music.service.MusicService.META_CHANGED; import static io.github.muntashirakon.music.service.MusicService.PLAY_STATE_CHANGED; @@ -32,140 +21,148 @@ import static io.github.muntashirakon.music.service.MusicService.REPEAT_MODE_NON import static io.github.muntashirakon.music.service.MusicService.TRACK_ENDED; import static io.github.muntashirakon.music.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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/service/notification/PlayingNotificationImpl.kt b/app/src/main/java/io/github/muntashirakon/music/service/notification/PlayingNotificationImpl.kt index 71013ac1e..14ebf62f5 100644 --- a/app/src/main/java/io/github/muntashirakon/music/service/notification/PlayingNotificationImpl.kt +++ b/app/src/main/java/io/github/muntashirakon/music/service/notification/PlayingNotificationImpl.kt @@ -55,7 +55,7 @@ class PlayingNotificationImpl : PlayingNotification() { if (isFavorite) R.drawable.ic_favorite else R.drawable.ic_favorite_border val action = Intent(service, MainActivity::class.java) - action.putExtra(MainActivity.EXPAND_PANEL, true) + action.putExtra(MainActivity.EXPAND_PANEL, PreferenceUtil.isExpandPanel) action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP val clickIntent = PendingIntent.getActivity(service, 0, action, PendingIntent.FLAG_UPDATE_CURRENT) diff --git a/app/src/main/java/io/github/muntashirakon/music/service/notification/PlayingNotificationOreo.kt b/app/src/main/java/io/github/muntashirakon/music/service/notification/PlayingNotificationOreo.kt index e3ced6e20..ff63696d7 100644 --- a/app/src/main/java/io/github/muntashirakon/music/service/notification/PlayingNotificationOreo.kt +++ b/app/src/main/java/io/github/muntashirakon/music/service/notification/PlayingNotificationOreo.kt @@ -73,7 +73,7 @@ class PlayingNotificationOreo : PlayingNotification() { val notificationLayoutBig = getCombinedRemoteViews(false, song) val action = Intent(service, MainActivity::class.java) - action.putExtra(MainActivity.EXPAND_PANEL, true) + action.putExtra(MainActivity.EXPAND_PANEL, PreferenceUtil.isExpandPanel) action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP val clickIntent = PendingIntent diff --git a/app/src/main/java/io/github/muntashirakon/music/state/NowPlayingPanelState.kt b/app/src/main/java/io/github/muntashirakon/music/state/NowPlayingPanelState.kt new file mode 100644 index 000000000..b142f1436 --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/state/NowPlayingPanelState.kt @@ -0,0 +1,8 @@ +package code.name.monkey.retromusic.state + +enum class NowPlayingPanelState { + EXPAND, + COLLAPSED_WITH, + COLLAPSED_WITHOUT, + HIDE, +} \ No newline at end of file diff --git a/app/src/main/java/io/github/muntashirakon/music/util/ArtistSignatureUtil.java b/app/src/main/java/io/github/muntashirakon/music/util/ArtistSignatureUtil.java index c099bafd8..27a180304 100644 --- a/app/src/main/java/io/github/muntashirakon/music/util/ArtistSignatureUtil.java +++ b/app/src/main/java/io/github/muntashirakon/music/util/ArtistSignatureUtil.java @@ -17,42 +17,38 @@ package io.github.muntashirakon.music.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/io/github/muntashirakon/music/util/AutoGeneratedPlaylistBitmap.java b/app/src/main/java/io/github/muntashirakon/music/util/AutoGeneratedPlaylistBitmap.java index 49d9e5a4f..1491c3963 100644 --- a/app/src/main/java/io/github/muntashirakon/music/util/AutoGeneratedPlaylistBitmap.java +++ b/app/src/main/java/io/github/muntashirakon/music/util/AutoGeneratedPlaylistBitmap.java @@ -19,7 +19,6 @@ import java.util.List; import io.github.muntashirakon.music.R; import io.github.muntashirakon.music.model.Song; - public class AutoGeneratedPlaylistBitmap { private static final String TAG = "AutoGeneratedPB"; @@ -30,8 +29,9 @@ public class AutoGeneratedPlaylistBitmap { 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; + public static Bitmap getBitmap( + Context context, List songPlaylist, boolean round, boolean blur) { + if (songPlaylist == null || songPlaylist.isEmpty()) return null; long start = System.currentTimeMillis(); // lấy toàn bộ album id, loại bỏ trùng nhau List albumID = new ArrayList<>(); @@ -49,31 +49,31 @@ public class AutoGeneratedPlaylistBitmap { if (art.size() == 6) break; } return MergedImageUtils.INSTANCE.joinImages(art); - /* + /* - 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); - } - 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 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); + } + 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;*/ + Log.d(TAG, "getBitmap: time = " + (System.currentTimeMillis() - start) + ", start2 = " + start2 + ", start3 = " + start3); + return ret;*/ } private static Bitmap getBitmapCollection(ArrayList art, boolean round) { @@ -91,21 +91,28 @@ public class AutoGeneratedPlaylistBitmap { 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.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.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.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; @@ -159,12 +166,9 @@ public class AutoGeneratedPlaylistBitmap { } canvas.restore(); } - - } Log.d(TAG, "getBitmapCollection: smalltime = " + (System.currentTimeMillis() - start)); - if (round) - return BitmapEditor.getRoundedCornerBitmap(bitmap, bitmap.getWidth() / 40); + if (round) return BitmapEditor.getRoundedCornerBitmap(bitmap, bitmap.getWidth() / 40); else return bitmap; } @@ -185,5 +189,4 @@ public class AutoGeneratedPlaylistBitmap { return BitmapFactory.decodeResource(context.getResources(), R.drawable.default_album_art); return BitmapFactory.decodeResource(context.getResources(), R.drawable.default_album_art); } - -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/muntashirakon/music/util/BitmapEditor.java b/app/src/main/java/io/github/muntashirakon/music/util/BitmapEditor.java index e52b70633..dd1b94c29 100644 --- a/app/src/main/java/io/github/muntashirakon/music/util/BitmapEditor.java +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/util/CalendarUtil.java b/app/src/main/java/io/github/muntashirakon/music/util/CalendarUtil.java index d0569b5a8..4ddf67e75 100644 --- a/app/src/main/java/io/github/muntashirakon/music/util/CalendarUtil.java +++ b/app/src/main/java/io/github/muntashirakon/music/util/CalendarUtil.java @@ -17,126 +17,124 @@ package io.github.muntashirakon.music.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/io/github/muntashirakon/music/util/ColorUtil.java b/app/src/main/java/io/github/muntashirakon/music/util/ColorUtil.java index 030913100..2ff2b6802 100644 --- a/app/src/main/java/io/github/muntashirakon/music/util/ColorUtil.java +++ b/app/src/main/java/io/github/muntashirakon/music/util/ColorUtil.java @@ -1,58 +1,55 @@ package io.github.muntashirakon.music.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/io/github/muntashirakon/music/util/Compressor.java b/app/src/main/java/io/github/muntashirakon/music/util/Compressor.java index e3da5a44e..e9256a33a 100644 --- a/app/src/main/java/io/github/muntashirakon/music/util/Compressor.java +++ b/app/src/main/java/io/github/muntashirakon/music/util/Compressor.java @@ -16,63 +16,64 @@ package io.github.muntashirakon.music.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/io/github/muntashirakon/music/util/FileUtil.java b/app/src/main/java/io/github/muntashirakon/music/util/FileUtil.java index c12296f9b..9e5f88724 100644 --- a/app/src/main/java/io/github/muntashirakon/music/util/FileUtil.java +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.model.Song; +import io.github.muntashirakon.music.repository.RealSongRepository; +import io.github.muntashirakon.music.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 io.github.muntashirakon.music.model.Song; -import io.github.muntashirakon.music.repository.RealSongRepository; -import io.github.muntashirakon.music.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/io/github/muntashirakon/music/util/ImageUtil.java b/app/src/main/java/io/github/muntashirakon/music/util/ImageUtil.java index 9843146fc..f7758a609 100644 --- a/app/src/main/java/io/github/muntashirakon/music/util/ImageUtil.java +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/util/LyricUtil.java b/app/src/main/java/io/github/muntashirakon/music/util/LyricUtil.java index 2436df0d7..3852a9299 100644 --- a/app/src/main/java/io/github/muntashirakon/music/util/LyricUtil.java +++ b/app/src/main/java/io/github/muntashirakon/music/util/LyricUtil.java @@ -15,10 +15,8 @@ package io.github.muntashirakon.music.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/io/github/muntashirakon/music/util/MusicUtil.kt b/app/src/main/java/io/github/muntashirakon/music/util/MusicUtil.kt index 352110f4c..0cef45107 100644 --- a/app/src/main/java/io/github/muntashirakon/music/util/MusicUtil.kt +++ b/app/src/main/java/io/github/muntashirakon/music/util/MusicUtil.kt @@ -299,6 +299,16 @@ object MusicUtil : KoinComponent { return tempName == "unknown" || tempName == "" } + fun isVariousArtists(artistName: String?): Boolean { + if (TextUtils.isEmpty(artistName)) { + return false + } + if (artistName == Artist.VARIOUS_ARTISTS_DISPLAY_NAME) { + return true + } + return false + } + fun isFavorite(context: Context, song: Song): Boolean { return PlaylistsUtil .doPlaylistContains(context, getFavoritesPlaylist(context).id, song.id) diff --git a/app/src/main/java/io/github/muntashirakon/music/util/NavigationUtil.java b/app/src/main/java/io/github/muntashirakon/music/util/NavigationUtil.java index 59f645fcc..9dbc9a5aa 100755 --- a/app/src/main/java/io/github/muntashirakon/music/util/NavigationUtil.java +++ b/app/src/main/java/io/github/muntashirakon/music/util/NavigationUtil.java @@ -17,81 +17,82 @@ package io.github.muntashirakon.music.util; import android.app.Activity; import android.app.ActivityOptions; import android.content.ActivityNotFoundException; +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 io.github.muntashirakon.music.R; import io.github.muntashirakon.music.activities.DriveModeActivity; import io.github.muntashirakon.music.activities.LicenseActivity; import io.github.muntashirakon.music.activities.LyricsActivity; import io.github.muntashirakon.music.activities.PlayingQueueActivity; +import io.github.muntashirakon.music.activities.PurchaseActivity; +import io.github.muntashirakon.music.activities.SupportDevelopmentActivity; import io.github.muntashirakon.music.activities.UserInfoActivity; import io.github.muntashirakon.music.activities.WhatsNewActivity; import io.github.muntashirakon.music.activities.bugreport.BugReportActivity; import io.github.muntashirakon.music.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 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 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/io/github/muntashirakon/music/util/PlaylistsUtil.java b/app/src/main/java/io/github/muntashirakon/music/util/PlaylistsUtil.java index 0293bb228..4c83d06ae 100644 --- a/app/src/main/java/io/github/muntashirakon/music/util/PlaylistsUtil.java +++ b/app/src/main/java/io/github/muntashirakon/music/util/PlaylistsUtil.java @@ -14,6 +14,8 @@ package io.github.muntashirakon.music.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 io.github.muntashirakon.music.R; import io.github.muntashirakon.music.db.PlaylistWithSongs; import io.github.muntashirakon.music.helper.M3UWriter; import io.github.muntashirakon.music.model.Playlist; import io.github.muntashirakon.music.model.PlaylistSong; import io.github.muntashirakon.music.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(Context context, 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/io/github/muntashirakon/music/util/PreferenceUtil.kt b/app/src/main/java/io/github/muntashirakon/music/util/PreferenceUtil.kt index ff2e4fed3..6399c0c24 100644 --- a/app/src/main/java/io/github/muntashirakon/music/util/PreferenceUtil.kt +++ b/app/src/main/java/io/github/muntashirakon/music/util/PreferenceUtil.kt @@ -8,7 +8,75 @@ import androidx.core.content.ContextCompat import androidx.core.content.edit import androidx.preference.PreferenceManager import androidx.viewpager.widget.ViewPager -import io.github.muntashirakon.music.* +import io.github.muntashirakon.music.ADAPTIVE_COLOR_APP +import io.github.muntashirakon.music.ALBUM_ARTISTS_ONLY +import io.github.muntashirakon.music.ALBUM_ART_ON_LOCK_SCREEN +import io.github.muntashirakon.music.ALBUM_COVER_STYLE +import io.github.muntashirakon.music.ALBUM_COVER_TRANSFORM +import io.github.muntashirakon.music.ALBUM_DETAIL_SONG_SORT_ORDER +import io.github.muntashirakon.music.ALBUM_GRID_SIZE +import io.github.muntashirakon.music.ALBUM_GRID_SIZE_LAND +import io.github.muntashirakon.music.ALBUM_GRID_STYLE +import io.github.muntashirakon.music.ALBUM_SONG_SORT_ORDER +import io.github.muntashirakon.music.ALBUM_SORT_ORDER +import io.github.muntashirakon.music.ARTIST_ALBUM_SORT_ORDER +import io.github.muntashirakon.music.ARTIST_GRID_SIZE +import io.github.muntashirakon.music.ARTIST_GRID_SIZE_LAND +import io.github.muntashirakon.music.ARTIST_GRID_STYLE +import io.github.muntashirakon.music.ARTIST_SONG_SORT_ORDER +import io.github.muntashirakon.music.ARTIST_SORT_ORDER +import io.github.muntashirakon.music.AUDIO_DUCKING +import io.github.muntashirakon.music.AUTO_DOWNLOAD_IMAGES_POLICY +import io.github.muntashirakon.music.App +import io.github.muntashirakon.music.BLACK_THEME +import io.github.muntashirakon.music.BLUETOOTH_PLAYBACK +import io.github.muntashirakon.music.BLURRED_ALBUM_ART +import io.github.muntashirakon.music.CAROUSEL_EFFECT +import io.github.muntashirakon.music.CHOOSE_EQUALIZER +import io.github.muntashirakon.music.CLASSIC_NOTIFICATION +import io.github.muntashirakon.music.COLORED_APP_SHORTCUTS +import io.github.muntashirakon.music.COLORED_NOTIFICATION +import io.github.muntashirakon.music.DESATURATED_COLOR +import io.github.muntashirakon.music.EXPAND_NOW_PLAYING_PANEL +import io.github.muntashirakon.music.EXTRA_SONG_INFO +import io.github.muntashirakon.music.FILTER_SONG +import io.github.muntashirakon.music.GAP_LESS_PLAYBACK +import io.github.muntashirakon.music.GENERAL_THEME +import io.github.muntashirakon.music.GENRE_SORT_ORDER +import io.github.muntashirakon.music.HOME_ALBUM_GRID_STYLE +import io.github.muntashirakon.music.HOME_ARTIST_GRID_STYLE +import io.github.muntashirakon.music.IGNORE_MEDIA_STORE_ARTWORK +import io.github.muntashirakon.music.INITIALIZED_BLACKLIST +import io.github.muntashirakon.music.KEEP_SCREEN_ON +import io.github.muntashirakon.music.LANGUAGE_NAME +import io.github.muntashirakon.music.LAST_ADDED_CUTOFF +import io.github.muntashirakon.music.LAST_CHANGELOG_VERSION +import io.github.muntashirakon.music.LAST_PAGE +import io.github.muntashirakon.music.LAST_SLEEP_TIMER_VALUE +import io.github.muntashirakon.music.LIBRARY_CATEGORIES +import io.github.muntashirakon.music.LOCK_SCREEN +import io.github.muntashirakon.music.LYRICS_OPTIONS +import io.github.muntashirakon.music.NEXT_SLEEP_TIMER_ELAPSED_REALTIME +import io.github.muntashirakon.music.NOW_PLAYING_SCREEN_ID +import io.github.muntashirakon.music.PAUSE_ON_ZERO_VOLUME +import io.github.muntashirakon.music.PLAYLIST_SORT_ORDER +import io.github.muntashirakon.music.R +import io.github.muntashirakon.music.RECENTLY_PLAYED_CUTOFF +import io.github.muntashirakon.music.SAF_SDCARD_URI +import io.github.muntashirakon.music.SLEEP_TIMER_FINISH_SONG +import io.github.muntashirakon.music.SONG_GRID_SIZE +import io.github.muntashirakon.music.SONG_GRID_SIZE_LAND +import io.github.muntashirakon.music.SONG_GRID_STYLE +import io.github.muntashirakon.music.SONG_SORT_ORDER +import io.github.muntashirakon.music.START_DIRECTORY +import io.github.muntashirakon.music.TAB_TEXT_MODE +import io.github.muntashirakon.music.TOGGLE_ADD_CONTROLS +import io.github.muntashirakon.music.TOGGLE_FULL_SCREEN +import io.github.muntashirakon.music.TOGGLE_HEADSET +import io.github.muntashirakon.music.TOGGLE_HOME_BANNER +import io.github.muntashirakon.music.TOGGLE_SHUFFLE +import io.github.muntashirakon.music.TOGGLE_VOLUME +import io.github.muntashirakon.music.USER_NAME import io.github.muntashirakon.music.extensions.getIntRes import io.github.muntashirakon.music.extensions.getStringOrDefault import io.github.muntashirakon.music.fragments.AlbumCoverStyle @@ -16,7 +84,13 @@ import io.github.muntashirakon.music.fragments.NowPlayingScreen import io.github.muntashirakon.music.fragments.folder.FoldersFragment import io.github.muntashirakon.music.helper.SortOrder.* import io.github.muntashirakon.music.model.CategoryInfo -import io.github.muntashirakon.music.transform.* +import io.github.muntashirakon.music.transform.CascadingPageTransformer +import io.github.muntashirakon.music.transform.DepthTransformation +import io.github.muntashirakon.music.transform.HingeTransformation +import io.github.muntashirakon.music.transform.HorizontalFlipTransformation +import io.github.muntashirakon.music.transform.NormalPageTransformer +import io.github.muntashirakon.music.transform.VerticalFlipTransformation +import io.github.muntashirakon.music.transform.VerticalStackTransformer import io.github.muntashirakon.music.util.theme.ThemeMode import com.google.android.material.bottomnavigation.LabelVisibilityMode import com.google.gson.Gson @@ -122,6 +196,13 @@ object PreferenceUtil { "only_wifi" ) + var albumArtistsOnly + get() = sharedPreferences.getBoolean( + ALBUM_ARTISTS_ONLY, + false + ) + set(value) = sharedPreferences.edit { putBoolean(ALBUM_ARTISTS_ONLY, value) } + var albumDetailSongSortOrder get() = sharedPreferences.getStringOrDefault( ALBUM_DETAIL_SONG_SORT_ORDER, @@ -147,10 +228,11 @@ object PreferenceUtil { putString(ALBUM_SORT_ORDER, value) } + var artistSortOrder get() = sharedPreferences.getStringOrDefault( ARTIST_SORT_ORDER, - AlbumSortOrder.ALBUM_A_Z + ArtistSortOrder.ARTIST_A_Z ) set(value) = sharedPreferences.edit { putString(ARTIST_SORT_ORDER, value) @@ -174,6 +256,15 @@ object PreferenceUtil { ArtistAlbumSortOrder.ALBUM_A_Z ) + var playlistSortOrder + get() = sharedPreferences.getStringOrDefault( + PLAYLIST_SORT_ORDER, + PlaylistSortOrder.PLAYLIST_A_Z + ) + set(value) = sharedPreferences.edit { + putString(PLAYLIST_SORT_ORDER, value) + } + val genreSortOrder get() = sharedPreferences.getStringOrDefault( GENRE_SORT_ORDER, @@ -390,12 +481,23 @@ object PreferenceUtil { } } - val homeGridStyle: Int + val homeArtistGridStyle: Int get() { - val position = - sharedPreferences.getStringOrDefault( - HOME_ARTIST_GRID_STYLE, "0" - ).toInt() + val position = sharedPreferences.getStringOrDefault( + HOME_ARTIST_GRID_STYLE, "0" + ).toInt() + val typedArray = + App.getContext().resources.obtainTypedArray(R.array.pref_home_grid_style_layout) + val layoutRes = typedArray.getResourceId(position, 0) + typedArray.recycle() + return if (layoutRes == 0) { + R.layout.item_artist + } else layoutRes + } + + val homeAlbumGridStyle: Int + get() { + val position = sharedPreferences.getStringOrDefault(HOME_ALBUM_GRID_STYLE, "4").toInt() val typedArray = App.getContext().resources.obtainTypedArray(R.array.pref_home_grid_style_layout) val layoutRes = typedArray.getResourceId(position, 0) @@ -408,7 +510,7 @@ object PreferenceUtil { val tabTitleMode: Int get() { return when (sharedPreferences.getStringOrDefault( - TAB_TEXT_MODE, "1" + TAB_TEXT_MODE, "0" ).toInt()) { 1 -> LabelVisibilityMode.LABEL_VISIBILITY_LABELED 0 -> LabelVisibilityMode.LABEL_VISIBILITY_AUTO diff --git a/app/src/main/java/io/github/muntashirakon/music/util/RetroColorUtil.java b/app/src/main/java/io/github/muntashirakon/music/util/RetroColorUtil.java index fc8644ef7..feb91d7e8 100644 --- a/app/src/main/java/io/github/muntashirakon/music/util/RetroColorUtil.java +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/util/RetroUtil.java b/app/src/main/java/io/github/muntashirakon/music/util/RetroUtil.java index 6f7317364..54dae9518 100755 --- a/app/src/main/java/io/github/muntashirakon/music/util/RetroUtil.java +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/util/RippleUtils.java b/app/src/main/java/io/github/muntashirakon/music/util/RippleUtils.java index 30f5aba4b..17703ccdb 100644 --- a/app/src/main/java/io/github/muntashirakon/music/util/RippleUtils.java +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/util/SAFUtil.java b/app/src/main/java/io/github/muntashirakon/music/util/SAFUtil.java index ceab52ea7..f885e1311 100644 --- a/app/src/main/java/io/github/muntashirakon/music/util/SAFUtil.java +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.R; +import io.github.muntashirakon.music.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 io.github.muntashirakon.music.R; -import io.github.muntashirakon.music.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/io/github/muntashirakon/music/util/SwipeAndDragHelper.java b/app/src/main/java/io/github/muntashirakon/music/util/SwipeAndDragHelper.java index d87330b00..86e4254da 100644 --- a/app/src/main/java/io/github/muntashirakon/music/util/SwipeAndDragHelper.java +++ b/app/src/main/java/io/github/muntashirakon/music/util/SwipeAndDragHelper.java @@ -15,58 +15,58 @@ package io.github.muntashirakon.music.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/io/github/muntashirakon/music/util/TempUtils.java b/app/src/main/java/io/github/muntashirakon/music/util/TempUtils.java index 5c4ab1be3..582eb176a 100644 --- a/app/src/main/java/io/github/muntashirakon/music/util/TempUtils.java +++ b/app/src/main/java/io/github/muntashirakon/music/util/TempUtils.java @@ -14,69 +14,67 @@ package io.github.muntashirakon.music.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/io/github/muntashirakon/music/util/color/ImageUtils.java b/app/src/main/java/io/github/muntashirakon/music/util/color/ImageUtils.java index 3820a8526..a2595cf8a 100644 --- a/app/src/main/java/io/github/muntashirakon/music/util/color/ImageUtils.java +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/util/color/MediaNotificationProcessor.java b/app/src/main/java/io/github/muntashirakon/music/util/color/MediaNotificationProcessor.java index d6fc54b0a..53ed9c1d2 100644 --- a/app/src/main/java/io/github/muntashirakon/music/util/color/MediaNotificationProcessor.java +++ b/app/src/main/java/io/github/muntashirakon/music/util/color/MediaNotificationProcessor.java @@ -16,6 +16,8 @@ package io.github.muntashirakon.music.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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/util/color/NotificationColorUtil.java b/app/src/main/java/io/github/muntashirakon/music/util/color/NotificationColorUtil.java index 946a1764b..3f984d808 100644 --- a/app/src/main/java/io/github/muntashirakon/music/util/color/NotificationColorUtil.java +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.R; import java.util.WeakHashMap; -import io.github.muntashirakon.music.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/io/github/muntashirakon/music/views/BaselineGridTextView.java b/app/src/main/java/io/github/muntashirakon/music/views/BaselineGridTextView.java index 3147c2708..dde75d9b3 100644 --- a/app/src/main/java/io/github/muntashirakon/music/views/BaselineGridTextView.java +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/views/BreadCrumbLayout.java b/app/src/main/java/io/github/muntashirakon/music/views/BreadCrumbLayout.java index 494cd51a8..52c3c1d76 100644 --- a/app/src/main/java/io/github/muntashirakon/music/views/BreadCrumbLayout.java +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/views/CircularImageView.java b/app/src/main/java/io/github/muntashirakon/music/views/CircularImageView.java index e4d747dbc..42530a9bd 100644 --- a/app/src/main/java/io/github/muntashirakon/music/views/CircularImageView.java +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/views/ContributorsView.java b/app/src/main/java/io/github/muntashirakon/music/views/ContributorsView.java index 85a132ee1..a890a4c64 100644 --- a/app/src/main/java/io/github/muntashirakon/music/views/ContributorsView.java +++ b/app/src/main/java/io/github/muntashirakon/music/views/ContributorsView.java @@ -14,6 +14,8 @@ package io.github.muntashirakon.music.views; +import static io.github.muntashirakon.music.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 io.github.muntashirakon.music.R; -import static io.github.muntashirakon.music.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/io/github/muntashirakon/music/views/DrawableGradient.java b/app/src/main/java/io/github/muntashirakon/music/views/DrawableGradient.java index 90f00f9c8..a7c1bd840 100644 --- a/app/src/main/java/io/github/muntashirakon/music/views/DrawableGradient.java +++ b/app/src/main/java/io/github/muntashirakon/music/views/DrawableGradient.java @@ -17,20 +17,19 @@ package io.github.muntashirakon.music.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/io/github/muntashirakon/music/views/HeightFitSquareLayout.java b/app/src/main/java/io/github/muntashirakon/music/views/HeightFitSquareLayout.java index 54238b46a..d7679bcd0 100755 --- a/app/src/main/java/io/github/muntashirakon/music/views/HeightFitSquareLayout.java +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/views/LollipopFixedWebView.java b/app/src/main/java/io/github/muntashirakon/music/views/LollipopFixedWebView.java index d68b72ae4..86df558c2 100644 --- a/app/src/main/java/io/github/muntashirakon/music/views/LollipopFixedWebView.java +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/views/NetworkImageView.java b/app/src/main/java/io/github/muntashirakon/music/views/NetworkImageView.java index 1389b9819..3cfa60d3f 100644 --- a/app/src/main/java/io/github/muntashirakon/music/views/NetworkImageView.java +++ b/app/src/main/java/io/github/muntashirakon/music/views/NetworkImageView.java @@ -17,50 +17,47 @@ package io.github.muntashirakon.music.views; import android.content.Context; import android.content.res.TypedArray; import android.util.AttributeSet; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - +import io.github.muntashirakon.music.R; import com.bumptech.glide.Glide; -import io.github.muntashirakon.music.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/io/github/muntashirakon/music/views/PopupBackground.java b/app/src/main/java/io/github/muntashirakon/music/views/PopupBackground.java index 6d4db2c7a..9886aeda7 100644 --- a/app/src/main/java/io/github/muntashirakon/music/views/PopupBackground.java +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/views/ScrollingViewOnApplyWindowInsetsListener.java b/app/src/main/java/io/github/muntashirakon/music/views/ScrollingViewOnApplyWindowInsetsListener.java index d462c407d..a3721b526 100644 --- a/app/src/main/java/io/github/muntashirakon/music/views/ScrollingViewOnApplyWindowInsetsListener.java +++ b/app/src/main/java/io/github/muntashirakon/music/views/ScrollingViewOnApplyWindowInsetsListener.java @@ -17,42 +17,46 @@ package io.github.muntashirakon.music.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/io/github/muntashirakon/music/views/SeekArc.java b/app/src/main/java/io/github/muntashirakon/music/views/SeekArc.java index cd3ac1a5a..b281e8f96 100644 --- a/app/src/main/java/io/github/muntashirakon/music/views/SeekArc.java +++ b/app/src/main/java/io/github/muntashirakon/music/views/SeekArc.java @@ -24,529 +24,488 @@ import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; - import io.github.muntashirakon.music.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/io/github/muntashirakon/music/views/StatusBarMarginFrameLayout.java b/app/src/main/java/io/github/muntashirakon/music/views/StatusBarMarginFrameLayout.java index 2ad019fb4..4a839a63c 100644 --- a/app/src/main/java/io/github/muntashirakon/music/views/StatusBarMarginFrameLayout.java +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/views/StatusBarView.java b/app/src/main/java/io/github/muntashirakon/music/views/StatusBarView.java index fca294d5f..7aaf69e60 100644 --- a/app/src/main/java/io/github/muntashirakon/music/views/StatusBarView.java +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/views/VerticalTextView.java b/app/src/main/java/io/github/muntashirakon/music/views/VerticalTextView.java index 2a08f7c4e..a33f77962 100644 --- a/app/src/main/java/io/github/muntashirakon/music/views/VerticalTextView.java +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/volume/AudioVolumeContentObserver.java b/app/src/main/java/io/github/muntashirakon/music/volume/AudioVolumeContentObserver.java index 7bbbb1e7b..7d088816e 100644 --- a/app/src/main/java/io/github/muntashirakon/music/volume/AudioVolumeContentObserver.java +++ b/app/src/main/java/io/github/muntashirakon/music/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/drawable-night/ic_launcher_background.xml b/app/src/main/res/drawable-night/ic_launcher_background.xml new file mode 100644 index 000000000..c760e2636 --- /dev/null +++ b/app/src/main/res/drawable-night/ic_launcher_background.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml index da4d42d65..2417b12d2 100644 --- a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -2,70 +2,71 @@ xmlns:aapt="http://schemas.android.com/aapt" android:width="108dp" android:height="108dp" - android:viewportWidth="921.0526" - android:viewportHeight="921.0526"> - - - - - - - - - - - - - - - - - - - - - - - - - - + android:viewportWidth="108" + android:viewportHeight="108"> + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable-xxxhdpi/ic_splash.png b/app/src/main/res/drawable-xxxhdpi/ic_splash.png index 07a310c4c..727bc25f2 100644 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_splash.png and b/app/src/main/res/drawable-xxxhdpi/ic_splash.png differ diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml index b97f2835e..d651a2a61 100644 --- a/app/src/main/res/drawable/ic_launcher_background.xml +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -5,6 +5,6 @@ android:viewportWidth="108" android:viewportHeight="108"> diff --git a/app/src/main/res/drawable/ic_retro_music_icon.xml b/app/src/main/res/drawable/ic_retro_music_icon.xml deleted file mode 100644 index 4c3025dfc..000000000 --- a/app/src/main/res/drawable/ic_retro_music_icon.xml +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout-land/fragment_album_details.xml b/app/src/main/res/layout-land/fragment_album_details.xml index a941c6859..df2f76714 100644 --- a/app/src/main/res/layout-land/fragment_album_details.xml +++ b/app/src/main/res/layout-land/fragment_album_details.xml @@ -2,6 +2,7 @@ - - - - - + app:layout_constraintTop_toTopOf="parent"> - @@ -63,12 +51,13 @@ - - - - - - + app:layout_constraintTop_toTopOf="parent"> @@ -63,12 +52,13 @@ - - - + + + + + + + + + + + + + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/app/src/main/res/layout-land/fragment_home.xml b/app/src/main/res/layout-land/fragment_home.xml index ccb4f9d34..be073b86a 100644 --- a/app/src/main/res/layout-land/fragment_home.xml +++ b/app/src/main/res/layout-land/fragment_home.xml @@ -11,66 +11,109 @@ ~ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ~ See the GNU General Public License for more details. --> - - + android:layout_height="match_parent"> - + android:layout_height="wrap_content" + app:liftOnScroll="true"> - - - + app:layout_scrollFlags="scroll|enterAlways"> - + - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-sw600dp/activity_playing_queue.xml b/app/src/main/res/layout-sw600dp/activity_playing_queue.xml new file mode 100644 index 000000000..0eddd4025 --- /dev/null +++ b/app/src/main/res/layout-sw600dp/activity_playing_queue.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-sw600dp/item_list.xml b/app/src/main/res/layout-sw600dp/item_list.xml new file mode 100644 index 000000000..7a5f59b12 --- /dev/null +++ b/app/src/main/res/layout-sw600dp/item_list.xml @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-sw600dp/item_list_no_image.xml b/app/src/main/res/layout-sw600dp/item_list_no_image.xml new file mode 100644 index 000000000..dfcbafa8b --- /dev/null +++ b/app/src/main/res/layout-sw600dp/item_list_no_image.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-sw600dp/item_queue.xml b/app/src/main/res/layout-sw600dp/item_queue.xml new file mode 100644 index 000000000..7917c4ea8 --- /dev/null +++ b/app/src/main/res/layout-sw600dp/item_queue.xml @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-xlarge-land/fragment_blur.xml b/app/src/main/res/layout-xlarge-land/fragment_blur.xml index b604a069a..fec223c30 100644 --- a/app/src/main/res/layout-xlarge-land/fragment_blur.xml +++ b/app/src/main/res/layout-xlarge-land/fragment_blur.xml @@ -16,54 +16,57 @@ android:scaleType="centerCrop" app:srcCompat="@color/black_color" /> - - - + + - - + - + tools:layout="@layout/fragment_album_cover" /> + - - - + + + + diff --git a/app/src/main/res/layout/activity_lyrics.xml b/app/src/main/res/layout/activity_lyrics.xml index 56edc85df..0bc6d3fc5 100644 --- a/app/src/main/res/layout/activity_lyrics.xml +++ b/app/src/main/res/layout/activity_lyrics.xml @@ -3,6 +3,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" + android:id="@+id/container" android:layout_height="match_parent" android:transitionName="@string/transition_lyrics"> diff --git a/app/src/main/res/layout/activity_main_content.xml b/app/src/main/res/layout/activity_main_content.xml index aee8d3b0d..cb62073d6 100644 --- a/app/src/main/res/layout/activity_main_content.xml +++ b/app/src/main/res/layout/activity_main_content.xml @@ -23,6 +23,5 @@ android:layout_width="match_parent" android:layout_height="match_parent" app:defaultNavHost="true" - app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" - app:navGraph="@navigation/main_graph" /> + app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" /> diff --git a/app/src/main/res/layout/activity_permission.xml b/app/src/main/res/layout/activity_permission.xml index 8ed55740f..49af2daa7 100644 --- a/app/src/main/res/layout/activity_permission.xml +++ b/app/src/main/res/layout/activity_permission.xml @@ -37,9 +37,9 @@ app:layout_constraintTop_toBottomOf="@id/divider" app:permissionButtonTitle="Grant access" app:permissionIcon="@drawable/ic_sd_storage" - app:permissionTitle="Storage Access" + app:permissionTitle="@string/permission_title" app:permissionTitleNumber="1" - app:permissionTitleSubTitle="The app needs permission to access your device storage for music files playing music" /> + app:permissionTitleSubTitle="@string/permission_summary" /> + app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" + tools:listitem="@layout/item_queue" /> - + app:layout_constraintTop_toTopOf="parent" + app:shapeAppearanceOverlay="@style/circleImageView" + app:srcCompat="@drawable/material_design_default" /> - - - - - - - - - - - - - - - - - + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/bannerImage" + app:retroCornerSize="36dp" + app:srcCompat="@drawable/ic_person_flat" /> + app:layout_constraintStart_toEndOf="@id/userImage" + app:layout_constraintTop_toTopOf="@id/userImage"> + tools:srcCompat="@tools:sample/backgrounds/scenic" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_album_details.xml b/app/src/main/res/layout/fragment_album_details.xml index 3e7c72cee..a5a58a375 100644 --- a/app/src/main/res/layout/fragment_album_details.xml +++ b/app/src/main/res/layout/fragment_album_details.xml @@ -3,8 +3,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="match_parent" - android:transitionName="@string/transition_album_art"> + android:layout_height="match_parent"> diff --git a/app/src/main/res/layout/fragment_artist_details.xml b/app/src/main/res/layout/fragment_artist_details.xml index fe8a3ca21..0adba0e62 100644 --- a/app/src/main/res/layout/fragment_artist_details.xml +++ b/app/src/main/res/layout/fragment_artist_details.xml @@ -33,6 +33,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:overScrollMode="never" + android:descendantFocusability="beforeDescendants" + android:focusableInTouchMode="true" app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"> - + android:layout_height="match_parent"> - + app:liftOnScroll="true"> - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + app:layout_scrollFlags="scroll|enterAlways"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_classic_player.xml b/app/src/main/res/layout/fragment_classic_player.xml index 7064a2445..e2fed7c55 100644 --- a/app/src/main/res/layout/fragment_classic_player.xml +++ b/app/src/main/res/layout/fragment_classic_player.xml @@ -12,15 +12,22 @@ android:layout_height="match_parent" android:orientation="vertical"> - + + + + + app:layout_constraintTop_toBottomOf="@id/albumCoverContainer" /> + app:cardElevation="24dp" + app:layout_behavior="io.github.muntashirakon.music.RetroBottomSheetBehavior" + tools:peekHeight="0dp"> @@ -133,4 +142,4 @@ - + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_folder.xml b/app/src/main/res/layout/fragment_folder.xml index d8c9ba4e2..6c6706e59 100644 --- a/app/src/main/res/layout/fragment_folder.xml +++ b/app/src/main/res/layout/fragment_folder.xml @@ -1,63 +1,106 @@ - + android:layout_height="match_parent"> - + app:liftOnScroll="true"> - + app:layout_scrollFlags="scroll|enterAlways"> - + + + + + + + + + + + - + android:gravity="center" + android:orientation="vertical" + android:visibility="gone" + tools:visibility="visible"> - + - + + - + android:orientation="vertical"> - - \ No newline at end of file + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_gradient_player.xml b/app/src/main/res/layout/fragment_gradient_player.xml index 113bfa083..24275ff33 100644 --- a/app/src/main/res/layout/fragment_gradient_player.xml +++ b/app/src/main/res/layout/fragment_gradient_player.xml @@ -108,6 +108,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="?attr/roundSelector" + android:clickable="true" + android:focusable="true" android:padding="14dp" android:scaleType="fitCenter" app:layout_constraintBottom_toBottomOf="@+id/previousButton" @@ -124,6 +126,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="?attr/roundSelector" + android:clickable="true" + android:focusable="true" android:padding="14dp" android:scaleType="fitCenter" app:layout_constraintBottom_toBottomOf="@+id/nextButton" diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index bb3a39c72..973c2fe70 100755 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -11,64 +11,108 @@ ~ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ~ See the GNU General Public License for more details. --> - + android:layout_height="match_parent"> - + android:layout_height="wrap_content" + app:liftOnScroll="true"> - - - + app:layout_scrollFlags="scroll|enterAlways"> - + - - + + - \ No newline at end of file + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_library.xml b/app/src/main/res/layout/fragment_library.xml index 59a58832b..67663cbe3 100644 --- a/app/src/main/res/layout/fragment_library.xml +++ b/app/src/main/res/layout/fragment_library.xml @@ -28,10 +28,10 @@ tools:ignore="UnusedAttribute"> @@ -48,7 +48,5 @@ android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="match_parent" android:layout_height="match_parent" - app:defaultNavHost="true" - app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" - app:navGraph="@navigation/library_graph" /> + app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_main_recycler.xml b/app/src/main/res/layout/fragment_main_recycler.xml new file mode 100644 index 000000000..5a6758477 --- /dev/null +++ b/app/src/main/res/layout/fragment_main_recycler.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_now_playing_player.xml b/app/src/main/res/layout/fragment_now_playing_player.xml deleted file mode 100644 index 2b7ec4529..000000000 --- a/app/src/main/res/layout/fragment_now_playing_player.xml +++ /dev/null @@ -1,13 +0,0 @@ - - \ No newline at end of file diff --git a/app/src/main/res/layout/item_image.xml b/app/src/main/res/layout/item_image.xml index 1723039c2..2a3f434a0 100644 --- a/app/src/main/res/layout/item_image.xml +++ b/app/src/main/res/layout/item_image.xml @@ -7,14 +7,15 @@ android:background="?rectSelector" android:orientation="vertical"> - @@ -26,7 +27,7 @@ android:scaleType="centerCrop" tools:srcCompat="@tools:sample/backgrounds/scenic[16]" /> - + - + tools:ignore="ContentDescription" + tools:visibility="visible" /> - - - - - - - - - - - + android:layout_marginStart="16dp" + app:cardCornerRadius="6dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toEndOf="@id/drag_view" + app:layout_constraintTop_toTopOf="parent"> + + + android:minHeight="40dp" + android:textAppearance="@style/TextViewSubtitle2" + android:visibility="gone" + tools:text="100" + tools:visibility="visible" /> + - - + + + + - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/item_list_no_image.xml b/app/src/main/res/layout/item_list_no_image.xml index bf6ffc263..6331125d8 100644 --- a/app/src/main/res/layout/item_list_no_image.xml +++ b/app/src/main/res/layout/item_list_no_image.xml @@ -32,7 +32,7 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" - tools:text="@tools:sample/first_names" /> + tools:text="@tools:sample/full_names" /> - + tools:text="@tools:sample/full_names" /> \ No newline at end of file diff --git a/app/src/main/res/layout/item_queue.xml b/app/src/main/res/layout/item_queue.xml index 1ac4b428e..0df8c24b6 100644 --- a/app/src/main/res/layout/item_queue.xml +++ b/app/src/main/res/layout/item_queue.xml @@ -13,11 +13,14 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - @@ -26,71 +29,95 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="center_vertical|start" - android:padding="8dp" android:visibility="gone" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" app:srcCompat="@drawable/ic_drag_vertical" app:tint="?attr/colorControlNormal" tools:ignore="ContentDescription" tools:visibility="visible" /> + + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_marginStart="16dp" + app:cardCornerRadius="6dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toEndOf="@id/drag_view" + app:layout_constraintTop_toTopOf="parent"> + + - - + android:ellipsize="end" + android:maxLines="1" + android:paddingHorizontal="16dp" + android:textAppearance="@style/TextViewSubtitle1" + android:textColor="?android:attr/textColorPrimary" + app:layout_constraintBottom_toTopOf="@+id/text" + app:layout_constraintEnd_toStartOf="@id/menu" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toEndOf="@id/imageContainer" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_chainStyle="packed" + tools:text="@tools:sample/full_names" /> - - - - + - + \ No newline at end of file diff --git a/app/src/main/res/layout/item_simple_text.xml b/app/src/main/res/layout/item_simple_text.xml new file mode 100644 index 000000000..daf36368d --- /dev/null +++ b/app/src/main/res/layout/item_simple_text.xml @@ -0,0 +1,9 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_suggestions.xml b/app/src/main/res/layout/item_suggestions.xml index 70f5a24c3..046d4dfb0 100644 --- a/app/src/main/res/layout/item_suggestions.xml +++ b/app/src/main/res/layout/item_suggestions.xml @@ -145,8 +145,9 @@ android:text="New music mix" android:textAppearance="@style/TextViewNormal" android:textStyle="bold" + android:textSize="32sp" app:autoSizeMaxTextSize="32sp" - app:autoSizeMinTextSize="18sp" + app:autoSizeMinTextSize="24sp" app:autoSizeStepGranularity="1sp" /> diff --git a/app/src/main/res/layout/sliding_music_panel_layout.xml b/app/src/main/res/layout/sliding_music_panel_layout.xml index 70026aede..c7db7b4fc 100644 --- a/app/src/main/res/layout/sliding_music_panel_layout.xml +++ b/app/src/main/res/layout/sliding_music_panel_layout.xml @@ -11,19 +11,25 @@ android:layout_width="match_parent" android:layout_height="match_parent" /> + - @@ -41,6 +47,7 @@ android:layout_width="match_parent" android:layout_height="56dp" android:layout_gravity="bottom" + android:background="?attr/colorSurface" android:elevation="0dp" app:itemHorizontalTranslationEnabled="false" app:itemIconTint="@drawable/bottom_navigation_item_colors" diff --git a/app/src/main/res/layout/sub_header.xml b/app/src/main/res/layout/sub_header.xml index 5a638f255..5b066f07f 100644 --- a/app/src/main/res/layout/sub_header.xml +++ b/app/src/main/res/layout/sub_header.xml @@ -6,7 +6,8 @@ android:layout_height="wrap_content" android:layout_gravity="start" android:gravity="start" - android:paddingHorizontal="16dp" + android:layout_marginStart="@dimen/toolbar_margin_horizontal" + android:layout_marginEnd="@dimen/toolbar_margin_horizontal" android:paddingVertical="12dp" android:textAppearance="@style/TextViewOverline" android:textStyle="bold" diff --git a/app/src/main/res/master/values-af-rZA/strings.xml b/app/src/main/res/master/values-af-rZA/strings.xml index 610335234..e028f27cb 100644 --- a/app/src/main/res/master/values-af-rZA/strings.xml +++ b/app/src/main/res/master/values-af-rZA/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/master/values-bn-rIN/strings.xml b/app/src/main/res/master/values-bn-rIN/strings.xml index 610335234..e028f27cb 100644 --- a/app/src/main/res/master/values-bn-rIN/strings.xml +++ b/app/src/main/res/master/values-bn-rIN/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/master/values-ca-rES/strings.xml b/app/src/main/res/master/values-ca-rES/strings.xml index 610335234..e028f27cb 100644 --- a/app/src/main/res/master/values-ca-rES/strings.xml +++ b/app/src/main/res/master/values-ca-rES/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/master/values-cs-rCZ/strings.xml b/app/src/main/res/master/values-cs-rCZ/strings.xml index 7d8a1c7ec..0586557fb 100644 --- a/app/src/main/res/master/values-cs-rCZ/strings.xml +++ b/app/src/main/res/master/values-cs-rCZ/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Může způsobit problémy s přehráváním u některých zařízení." Toggle genre tab - Toggle home banner style + Show or hide the home banner Může zvýšit kvalitu obalu alba, ale způsobí pomalejší načítání snímků. Tuto možnost povolte pouze v případě potíží s uměleckými díly s nízkým rozlišením. Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Přehrávání bez mezery Hlavní téma Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignorovat obaly v zařízení Last added playlist interval Fullscreen controls diff --git a/app/src/main/res/master/values-da-rDK/strings.xml b/app/src/main/res/master/values-da-rDK/strings.xml index 610335234..e028f27cb 100644 --- a/app/src/main/res/master/values-da-rDK/strings.xml +++ b/app/src/main/res/master/values-da-rDK/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/master/values-el-rGR/strings.xml b/app/src/main/res/master/values-el-rGR/strings.xml index 207851a8a..b68f5bc70 100644 --- a/app/src/main/res/master/values-el-rGR/strings.xml +++ b/app/src/main/res/master/values-el-rGR/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Μπορεί να προκαλέσει προβλήματα με την αναπαραγωγή σε κάποιες συσκευές." Toggle genre tab - Toggle home banner style + Show or hide the home banner Μπορεί να αυξήσει την ποιότητα των εξωφύλλων άλμπουμ, αλλά προκαλεί αργή φόρτωση εικόνων. Ενεργοποιήστε αυτή την επίλογη μόνο εαν αντιμετωπίζετε προβλήματα με εξώφυλλα χαμηλής ανάλυσης. Configure visibility and order of library categories. Ενεργοποίηση διακοπτών ρύθμισης στην οθόνη κλειδώματος. @@ -345,8 +345,8 @@ Αναπαραγωγή χωρίς κενά Γενικό θέμα Show genre tab - Home artist grid - Home banner + Artist grid + Banner Παράληψη Media Store για εξώφυλλα Χρονικό διάστημα playlist \"Προστέθηκε τελευταία\" Full screen Ρυθμίσεις diff --git a/app/src/main/res/master/values-en-rUS/strings.xml b/app/src/main/res/master/values-en-rUS/strings.xml index 610335234..e028f27cb 100644 --- a/app/src/main/res/master/values-en-rUS/strings.xml +++ b/app/src/main/res/master/values-en-rUS/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/master/values-fa-rIR/strings.xml b/app/src/main/res/master/values-fa-rIR/strings.xml index 610335234..e028f27cb 100644 --- a/app/src/main/res/master/values-fa-rIR/strings.xml +++ b/app/src/main/res/master/values-fa-rIR/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/master/values-fi-rFI/strings.xml b/app/src/main/res/master/values-fi-rFI/strings.xml index 610335234..e028f27cb 100644 --- a/app/src/main/res/master/values-fi-rFI/strings.xml +++ b/app/src/main/res/master/values-fi-rFI/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/master/values-hi-rIN/strings.xml b/app/src/main/res/master/values-hi-rIN/strings.xml index 9e11f38ee..d9822f7ae 100644 --- a/app/src/main/res/master/values-hi-rIN/strings.xml +++ b/app/src/main/res/master/values-hi-rIN/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/master/values-it-rIT/strings.xml b/app/src/main/res/master/values-it-rIT/strings.xml index 2d844245b..3ab5f6486 100644 --- a/app/src/main/res/master/values-it-rIT/strings.xml +++ b/app/src/main/res/master/values-it-rIT/strings.xml @@ -347,7 +347,7 @@ https://play.google.com/store/apps/details?id=%s Tema generale Mostra scheda Genere Griglia schermata artista - Home banner + Banner Ignora le copertine del Media Store Intervallo playlist ultimi aggiunti Controlli a schermo intero diff --git a/app/src/main/res/master/values-iw-rIL/strings.xml b/app/src/main/res/master/values-iw-rIL/strings.xml index 610335234..e028f27cb 100644 --- a/app/src/main/res/master/values-iw-rIL/strings.xml +++ b/app/src/main/res/master/values-iw-rIL/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/master/values-kn-rIN/strings.xml b/app/src/main/res/master/values-kn-rIN/strings.xml index 610335234..e028f27cb 100644 --- a/app/src/main/res/master/values-kn-rIN/strings.xml +++ b/app/src/main/res/master/values-kn-rIN/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/master/values-ko-rKR/strings.xml b/app/src/main/res/master/values-ko-rKR/strings.xml index 8d916b7c0..10c27f2e8 100644 --- a/app/src/main/res/master/values-ko-rKR/strings.xml +++ b/app/src/main/res/master/values-ko-rKR/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "몇몇 기기에서 재생 문제를 유발할 수 있습니다." Toggle genre tab - Toggle home banner style + Show or hide the home banner 앨범 커버의 품질을 향상시킬 수 있지만 이미지를 불러오는 시간이 늘어납니다. 저해상도 이미지를 불러오는 데 문제가 있는 경우에만 사용하십시오. Configure visibility and order of library categories. Retro music에서 제공하는 자체 잠금 화면 사용 @@ -345,8 +345,8 @@ 지연없이 재생하기 기본 테마 Show genre tab - Home artist grid - Home banner + Artist grid + Banner 미디어 저장소 커버 무시 최근 추가된 음악 간격 지정 전체 화면 컨트롤 diff --git a/app/src/main/res/master/values-ml-rIN/strings.xml b/app/src/main/res/master/values-ml-rIN/strings.xml index 610335234..e028f27cb 100644 --- a/app/src/main/res/master/values-ml-rIN/strings.xml +++ b/app/src/main/res/master/values-ml-rIN/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/master/values-ne-rIN/strings.xml b/app/src/main/res/master/values-ne-rIN/strings.xml index 610335234..e028f27cb 100644 --- a/app/src/main/res/master/values-ne-rIN/strings.xml +++ b/app/src/main/res/master/values-ne-rIN/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/master/values-nl-rNL/strings.xml b/app/src/main/res/master/values-nl-rNL/strings.xml index 1fb659148..710fef05f 100644 --- a/app/src/main/res/master/values-nl-rNL/strings.xml +++ b/app/src/main/res/master/values-nl-rNL/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Kan afspeelproblemen veroorzaken op sommige toestellen" Toggle genre tab - Toggle home banner style + Show or hide the home banner Kan album cover kwaliteit verbeteren, maar veroorzaakt langere laadtijden. Alleen aanzetten als je problemen hebt met lage resolutie artworks Configure visibility and order of library categories. Zet besturing knoppen aan op vergrendelscherm @@ -345,8 +345,8 @@ Afspelen zonder pauzes Basis thema Show genre tab - Home artist grid - Home banner + Artist grid + Banner Negeer media store covers Laatst toegevoegde afspeellijst interval Volledig scherm besturing knoppen diff --git a/app/src/main/res/master/values-no-rNO/strings.xml b/app/src/main/res/master/values-no-rNO/strings.xml index 610335234..e028f27cb 100644 --- a/app/src/main/res/master/values-no-rNO/strings.xml +++ b/app/src/main/res/master/values-no-rNO/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/master/values-or-rIN/strings.xml b/app/src/main/res/master/values-or-rIN/strings.xml index 610335234..e028f27cb 100644 --- a/app/src/main/res/master/values-or-rIN/strings.xml +++ b/app/src/main/res/master/values-or-rIN/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/master/values-pt-rPT/strings.xml b/app/src/main/res/master/values-pt-rPT/strings.xml index 610335234..e028f27cb 100644 --- a/app/src/main/res/master/values-pt-rPT/strings.xml +++ b/app/src/main/res/master/values-pt-rPT/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/master/values-ro-rRO/strings.xml b/app/src/main/res/master/values-ro-rRO/strings.xml index c68d25e96..c971ce2a0 100644 --- a/app/src/main/res/master/values-ro-rRO/strings.xml +++ b/app/src/main/res/master/values-ro-rRO/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Poate cauza probleme de redare pe unele dispozitive." Toggle genre tab - Toggle home banner style + Show or hide the home banner Poate mări calitatea copertei de album, dar cauzează încărcarea mai lentă a imaginilor. Activați această opțiune doar dacă aveți probleme cu coperta de album cu rezoluție mică. Configure visibility and order of library categories. Comenzi pe ecranul de blocare pentru Retro music. @@ -345,8 +345,8 @@ Redare \"Gapless\" Temă Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignoră copertele de pe Magazinul Media Ultimul interval de playlist adăugat Comenzi ecran complet diff --git a/app/src/main/res/master/values-sk-rSK/strings.xml b/app/src/main/res/master/values-sk-rSK/strings.xml index 610335234..e028f27cb 100644 --- a/app/src/main/res/master/values-sk-rSK/strings.xml +++ b/app/src/main/res/master/values-sk-rSK/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/master/values-sr-rSP/strings.xml b/app/src/main/res/master/values-sr-rSP/strings.xml index fcc543df6..a0d473c74 100644 --- a/app/src/main/res/master/values-sr-rSP/strings.xml +++ b/app/src/main/res/master/values-sr-rSP/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Moze prouzrokovati probleme na pojedinim uredjajima." Toggle genre tab - Toggle home banner style + Show or hide the home banner oze poboljsati kvalitet omota albuma ali uzrokuje njegovo sporije ucitavanje. Omoguci ovo samo ukoliko imas problema sa losim kvalitetom slike omota albuma. Configure visibility and order of library categories. Prikazuj kontrole na zakljucanom ekranu @@ -345,8 +345,8 @@ Neuznemiravano reprodukovanje Opsta tema Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignorisi omote sa prodavnice Interval plejliste poslednje dodato Kontrole preko celog ekrana diff --git a/app/src/main/res/master/values-sv-rSE/strings.xml b/app/src/main/res/master/values-sv-rSE/strings.xml index 610335234..e028f27cb 100644 --- a/app/src/main/res/master/values-sv-rSE/strings.xml +++ b/app/src/main/res/master/values-sv-rSE/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/master/values-ta-rIN/strings.xml b/app/src/main/res/master/values-ta-rIN/strings.xml index 610335234..e028f27cb 100644 --- a/app/src/main/res/master/values-ta-rIN/strings.xml +++ b/app/src/main/res/master/values-ta-rIN/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/master/values-ur-rIN/strings.xml b/app/src/main/res/master/values-ur-rIN/strings.xml index 610335234..e028f27cb 100644 --- a/app/src/main/res/master/values-ur-rIN/strings.xml +++ b/app/src/main/res/master/values-ur-rIN/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/master/values-zh-rTW/strings.xml b/app/src/main/res/master/values-zh-rTW/strings.xml index 4fca6fcf7..fa860ba65 100644 --- a/app/src/main/res/master/values-zh-rTW/strings.xml +++ b/app/src/main/res/master/values-zh-rTW/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "可能會在某些裝置上出現播放問題。" Toggle genre tab - Toggle home banner style + Show or hide the home banner 提高專輯封面的成像品質,但會造成較長的讀取時間。建議只有在您對低畫質的專輯封面有問題時才開啟此選項。 Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ 無縫播放 主題 Show genre tab - Home artist grid - Home banner + Artist grid + Banner 忽略音訊檔內嵌的專輯封面 Last added playlist interval Fullscreen controls diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml index 83b9ea939..fc06a2b58 100644 --- a/app/src/main/res/menu/menu_main.xml +++ b/app/src/main/res/menu/menu_main.xml @@ -90,6 +90,7 @@

+ app:startDestination="@id/action_home"> + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/now_playing.xml b/app/src/main/res/navigation/now_playing.xml deleted file mode 100644 index af817beb4..000000000 --- a/app/src/main/res/navigation/now_playing.xml +++ /dev/null @@ -1,165 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/values-af-rZA/strings.xml b/app/src/main/res/values-af-rZA/strings.xml index 12d03622e..c44382282 100644 --- a/app/src/main/res/values-af-rZA/strings.xml +++ b/app/src/main/res/values-af-rZA/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/values-bn-rIN/strings.xml b/app/src/main/res/values-bn-rIN/strings.xml index 12d03622e..c44382282 100644 --- a/app/src/main/res/values-bn-rIN/strings.xml +++ b/app/src/main/res/values-bn-rIN/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/values-ca-rES/strings.xml b/app/src/main/res/values-ca-rES/strings.xml index 12d03622e..c44382282 100644 --- a/app/src/main/res/values-ca-rES/strings.xml +++ b/app/src/main/res/values-ca-rES/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/values-cs-rCZ/strings.xml b/app/src/main/res/values-cs-rCZ/strings.xml index 03342c322..cd2992854 100644 --- a/app/src/main/res/values-cs-rCZ/strings.xml +++ b/app/src/main/res/values-cs-rCZ/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Může způsobit problémy s přehráváním u některých zařízení." Toggle genre tab - Toggle home banner style + Show or hide the home banner Může zvýšit kvalitu obalu alba, ale způsobí pomalejší načítání snímků. Tuto možnost povolte pouze v případě potíží s uměleckými díly s nízkým rozlišením. Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Přehrávání bez mezery Hlavní téma Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignorovat obaly v zařízení Last added playlist interval Fullscreen controls diff --git a/app/src/main/res/values-da-rDK/strings.xml b/app/src/main/res/values-da-rDK/strings.xml index 12d03622e..c44382282 100644 --- a/app/src/main/res/values-da-rDK/strings.xml +++ b/app/src/main/res/values-da-rDK/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/values-el-rGR/strings.xml b/app/src/main/res/values-el-rGR/strings.xml index 41fcf970b..8a4f6068a 100644 --- a/app/src/main/res/values-el-rGR/strings.xml +++ b/app/src/main/res/values-el-rGR/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Μπορεί να προκαλέσει προβλήματα με την αναπαραγωγή σε κάποιες συσκευές." Toggle genre tab - Toggle home banner style + Show or hide the home banner Μπορεί να αυξήσει την ποιότητα των εξωφύλλων άλμπουμ, αλλά προκαλεί αργή φόρτωση εικόνων. Ενεργοποιήστε αυτή την επίλογη μόνο εαν αντιμετωπίζετε προβλήματα με εξώφυλλα χαμηλής ανάλυσης. Configure visibility and order of library categories. Ενεργοποίηση διακοπτών ρύθμισης στην οθόνη κλειδώματος. @@ -345,8 +345,8 @@ Αναπαραγωγή χωρίς κενά Γενικό θέμα Show genre tab - Home artist grid - Home banner + Artist grid + Banner Παράληψη Media Store για εξώφυλλα Χρονικό διάστημα playlist \"Προστέθηκε τελευταία\" Full screen Ρυθμίσεις diff --git a/app/src/main/res/values-en-rHK/strings.xml b/app/src/main/res/values-en-rHK/strings.xml index 610335234..e028f27cb 100644 --- a/app/src/main/res/values-en-rHK/strings.xml +++ b/app/src/main/res/values-en-rHK/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/values-en-rID/strings.xml b/app/src/main/res/values-en-rID/strings.xml index 610335234..e028f27cb 100644 --- a/app/src/main/res/values-en-rID/strings.xml +++ b/app/src/main/res/values-en-rID/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/values-en-rIN/strings.xml b/app/src/main/res/values-en-rIN/strings.xml index 610335234..e028f27cb 100644 --- a/app/src/main/res/values-en-rIN/strings.xml +++ b/app/src/main/res/values-en-rIN/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/values-en-rUS/strings.xml b/app/src/main/res/values-en-rUS/strings.xml index 12d03622e..c44382282 100644 --- a/app/src/main/res/values-en-rUS/strings.xml +++ b/app/src/main/res/values-en-rUS/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index c58a3e9e0..3c96555bd 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -51,7 +51,7 @@ Añadir letra Agregar foto "Agregar a lista de reproducción" - Añadir retardo para letras + Añadir retraso para letras "Se ha agregado 1 canción a la cola de reproducción" %1$d canciones agregadas a la cola de reproducción Álbum @@ -74,7 +74,7 @@ Automático Color base del tema Refuerzo de graves - Biografía + Bio Biografía Negro Lista Negra @@ -82,7 +82,7 @@ Tarjeta con desenfoque Enviando el reporte a GitHub... Token de acceso inválido. Por favor, contacta con el desarrollador de la aplicación - El problema no esta habilitado para el repositorio seleccionado. Por favor, contacta col el desarrollador de la app. + El problema no está habilitado para el repositorio seleccionado. Por favor, contacta con el desarrollador de la app. Se ha producido un error inesperado. Por favor, contacta con el desarrollador de la aplicación Usuario o contraseña incorrectos Problema @@ -183,7 +183,7 @@ 6 7 8 - Cuadricula y estilo + Cuadrícula y estilo Giro Historial Inicio @@ -199,7 +199,7 @@ Nombre del archivo Ruta del archivo Tamaño - Más desde %s + Más de %s Frecuencia de muestreo Duración Etiquetado @@ -207,10 +207,10 @@ Última canción Vamos a tocar un poco de música Biblioteca - Categorías biblioteca + Categorías de la Biblioteca Licencias Blanco claro - Escuchadores + Oyentes Listando archivos Cargando productos... Iniciar Sección @@ -230,9 +230,9 @@ No hay Álbumes No hay Artistas "Primero reproduce una canción, luego intenta de nuevo." - No se encontró ecualizador + No se encontró ningún ecualizador No hay Géneros - No se encontró letra + No se encontró la letra No hay canciones tocando No hay Listas de Reproducción No se encontraron compras. @@ -256,7 +256,7 @@ Contraseña Más de 3 meses Pegar letra aquí - Peak + Pica Permiso de acceso al almacenamiento externo denegado. Permiso denegado. Personalizar @@ -289,7 +289,7 @@ Biblioteca Pantalla de bloqueo Listas de reproducción - Pausar la reproducción cuando se esta en silencio y reproducir cuando se aumenta el volumen. ¡Cuidado! Cuando se aumenta el volumen se empezara la reproducción aunque se este fuera de la app. + Pausar la reproducción cuando se está en silencio y reproducir cuando se aumenta el volumen. ¡Cuidado! Cuando se aumenta el volumen se empezará la reproducción aunque se esté fuera de la app. Pausar en cero Tenga en cuenta que habilitar esta función puede afectar la duración de la batería Mantener la pantalla encendida @@ -306,14 +306,14 @@ El color del fondo y los botones de control cambian de acuerdo a la portada del álbum para la ventana de reproducción Colorea los accesos directos de la aplicación en el color de énfasis. Cada vez que cambie el color, active esta opción Colorea la barra de navegación con el color principal - "Colorea la notificaci\u00f3n con el color vibrante de la portada del \u00e1lbum" + "Colorea la notificación con el color vibrante de la portada del álbum" Según las líneas de la guía Material Design en los colores del modo oscuro deben ser desaturados Se tomará el color dominante de la portada del álbum o imagen del artista Añadir controles extra al mini reproductor - Mostrar información extra de canciones, como el formato de archivo, bitrate y frecuencia + Mostrar información extra de canciones, como el formato de archivo, tasa de bits y frecuencia "Puede causar problemas de reproducción en algunos dispositivos" - Mostrar/Ocultar pestaña de géneros - Mostrar/Ocultar banner en inicio + Mostrar/Ocultar pestaña Géneros + Mostrar/Ocultar banner en Inicio Puede aumentar la calidad de la portada del álbum, pero provoca tiempos de carga de imágenes más lentos. Solo habilite esto si tiene problemas con portadas de baja resolución Configure la visibilidad y el orden de las categorías de la biblioteca. Usar los controles personalizados de Retro Music en la pantalla de bloqueo @@ -321,32 +321,32 @@ Redondear las esquinas de la aplicación Mostrar/Ocultar nombres de las pestañas de navegación Modo inmersivo - Comenzar a reproducir inmediatamente se conecten audífonos + Comenzar a reproducir inmediatamente cuando se conecten audífonos El modo aleatorio se desactivará cuando se reproduzca una nueva lista de canciones Mostrar controles de volumen si hay suficiente espacio disponible. Mostrar/Ocultar portada del álbum Tema de la portada del álbum Estilo de portada del álbum en reproducción - Cuadricula del álbum + Cuadrícula del álbum Accesos directos de la aplicación coloreados - Cuadricula de los artistas - Reducir el volumen en pérdida de enfoque + Cuadrícula de los artistas + Reducir el volumen cuando se pierda el enfoque Descarga automática de imágenes de artistas Lista Negra Reproducción por Bluetooth Desenfocar portada del álbum Elegir ecualizador Diseño de notificación clásico - Color adaptativo + Color Adaptativo Notificación coloreada Color Desaturado Controles extra Información de la canción Reproducción sin pausas Tema de la aplicación - Mostrar pestaña de géneros - Cuadricula de los artistas en inicio - Banner de inicio + Mostrar pestaña Géneros + Cuadrícula de los artistas en inicio + Banner de Inicio Ignorar las portadas de la biblioteca de medios Intervalo de la lista \"Añadidos Recientemente\" Controles en pantalla completa @@ -355,7 +355,7 @@ Licencias de código abierto Bordes de las esquinas Forma de los títulos de las pestañas - Efecto carrusel + Efecto Carrusel Color dominante Aplicación en pantalla completa Títulos de las pestañas @@ -364,7 +364,7 @@ Controles de volumen Información de usuario Color principal - El color principal del tema, por defecto es gris azulado, por ahora funciona con colores oscuros + El color principal del tema, por defecto gris azulado, por ahora funciona con colores oscuros Pro Temas en reproducción, efecto Carrusel, tema de color y más ... Perfil @@ -378,7 +378,7 @@ Eliminar Eliminar foto del banner Eliminar portada - Eliminar de la lista negra + Eliminar de la Lista Negra Eliminar foto de perfil Eliminar canción de la lista %1$s de la lista?]]> @@ -393,8 +393,8 @@ Compra anterior restaurada. Por favor, reinicie la aplicación para hacer uso de todas las funciones. Compras anteriores restauradas. Restaurando compra... - Ecualizador de Reto Music - Reproductor de Música Retro + Ecualizador de Retro Music + Reproductor de Retro Music Retro Music Pro La eliminación del archivo falló @@ -471,12 +471,12 @@ Tablero ¡Buenas Tardes! ¡Buen Día! - ¡Buenas Tardes! - ¡Buenos días! - Buenas noches + ¡Buenas Noches! + ¡Buen Día! + ¡Buenas Noches! ¿Cómo te llamas? Hoy - Álbumes mas reproducidos + Álbumes más reproducidos Artistas más reproducidos "Pista (2 para pista 2 o 3004 para CD3 pista 4)" Número de pista @@ -485,7 +485,7 @@ Twitter Comparte tu diseño con Retro Music Sin etiqueta - No se pudo reproducir esta canci\u00f3n + No se pudo reproducir esta canción A continuación Actualizar imagen Actualizando... @@ -504,37 +504,37 @@ %1$d seleccionados Año Tienes que seleccionar al menos una categoría - Sera redirigido al sitio web para reportar problemas. + Será redirigido al sitio web para reportar problemas. Los datos de tu cuenta sólo se utilizan para la autenticación Cantidad Nota (Opcional) Iniciar pago Mostrar en pantalla de reproducción Al hacer clic en la notificación se mostrará la pantalla de reproducción en lugar de la pantalla de inicio - Tiny card - About %s - Select language - Translators - The people who helped translate this app - Try Retro Music Premium + Tarjeta pequeña + Acerca de %s + Seleccionar lenguaje + Traductores + Las personas que ayudaron a traducir esta aplicación + Pruebe Retro Music Premium - Song - Songs + Canción + Canciones - Album - Albums + Álbum + Álbumes - %d Song - %d Songs + %d Canción + %d Canciones - %d Album - %d Albums + %d Álbum + %d Álbumes - %d Artist - %d Artists + %d Artista + %d Artistas diff --git a/app/src/main/res/values-fa-rIR/strings.xml b/app/src/main/res/values-fa-rIR/strings.xml index 12d03622e..c44382282 100644 --- a/app/src/main/res/values-fa-rIR/strings.xml +++ b/app/src/main/res/values-fa-rIR/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/values-fi-rFI/strings.xml b/app/src/main/res/values-fi-rFI/strings.xml index 12d03622e..c44382282 100644 --- a/app/src/main/res/values-fi-rFI/strings.xml +++ b/app/src/main/res/values-fi-rFI/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/values-hi-rIN/strings.xml b/app/src/main/res/values-hi-rIN/strings.xml index 6913810e8..d55510221 100644 --- a/app/src/main/res/values-hi-rIN/strings.xml +++ b/app/src/main/res/values-hi-rIN/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/values-it-rIT/strings.xml b/app/src/main/res/values-it-rIT/strings.xml index 56287e50d..289384035 100644 --- a/app/src/main/res/values-it-rIT/strings.xml +++ b/app/src/main/res/values-it-rIT/strings.xml @@ -347,7 +347,7 @@ https://play.google.com/store/apps/details?id=%s
Tema generale Mostra scheda Genere Griglia schermata artista - Home banner + Banner Ignora le copertine del Media Store Intervallo playlist ultimi aggiunti Controlli a schermo intero diff --git a/app/src/main/res/values-iw-rIL/strings.xml b/app/src/main/res/values-iw-rIL/strings.xml index d6a358d48..fa0ac4a38 100644 --- a/app/src/main/res/values-iw-rIL/strings.xml +++ b/app/src/main/res/values-iw-rIL/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/values-kn-rIN/strings.xml b/app/src/main/res/values-kn-rIN/strings.xml index 12d03622e..c44382282 100644 --- a/app/src/main/res/values-kn-rIN/strings.xml +++ b/app/src/main/res/values-kn-rIN/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/values-ko-rKR/strings.xml b/app/src/main/res/values-ko-rKR/strings.xml index 84dd888cb..bf3a4d17e 100644 --- a/app/src/main/res/values-ko-rKR/strings.xml +++ b/app/src/main/res/values-ko-rKR/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "몇몇 기기에서 재생 문제를 유발할 수 있습니다." Toggle genre tab - Toggle home banner style + Show or hide the home banner 앨범 커버의 품질을 향상시킬 수 있지만 이미지를 불러오는 시간이 늘어납니다. 저해상도 이미지를 불러오는 데 문제가 있는 경우에만 사용하십시오. Configure visibility and order of library categories. Retro music에서 제공하는 자체 잠금 화면 사용 @@ -345,8 +345,8 @@ 지연없이 재생하기 기본 테마 Show genre tab - Home artist grid - Home banner + Artist grid + Banner 미디어 저장소 커버 무시 최근 추가된 음악 간격 지정 전체 화면 컨트롤 diff --git a/app/src/main/res/values-ml-rIN/strings.xml b/app/src/main/res/values-ml-rIN/strings.xml index 12d03622e..c44382282 100644 --- a/app/src/main/res/values-ml-rIN/strings.xml +++ b/app/src/main/res/values-ml-rIN/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/values-ne-rIN/strings.xml b/app/src/main/res/values-ne-rIN/strings.xml index 12d03622e..c44382282 100644 --- a/app/src/main/res/values-ne-rIN/strings.xml +++ b/app/src/main/res/values-ne-rIN/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/values-nl-rNL/strings.xml b/app/src/main/res/values-nl-rNL/strings.xml index 87013f4dd..de751b869 100644 --- a/app/src/main/res/values-nl-rNL/strings.xml +++ b/app/src/main/res/values-nl-rNL/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Kan afspeelproblemen veroorzaken op sommige toestellen" Toggle genre tab - Toggle home banner style + Show or hide the home banner Kan album cover kwaliteit verbeteren, maar veroorzaakt langere laadtijden. Alleen aanzetten als je problemen hebt met lage resolutie artworks Configure visibility and order of library categories. Zet besturing knoppen aan op vergrendelscherm @@ -345,8 +345,8 @@ Afspelen zonder pauzes Basis thema Show genre tab - Home artist grid - Home banner + Artist grid + Banner Negeer media store covers Laatst toegevoegde afspeellijst interval Volledig scherm besturing knoppen diff --git a/app/src/main/res/values-no-rNO/strings.xml b/app/src/main/res/values-no-rNO/strings.xml index 12d03622e..c44382282 100644 --- a/app/src/main/res/values-no-rNO/strings.xml +++ b/app/src/main/res/values-no-rNO/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/values-or-rIN/strings.xml b/app/src/main/res/values-or-rIN/strings.xml index 12d03622e..c44382282 100644 --- a/app/src/main/res/values-or-rIN/strings.xml +++ b/app/src/main/res/values-or-rIN/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/values-pl-rPL/strings.xml b/app/src/main/res/values-pl-rPL/strings.xml index 46f655c70..0356f3b58 100644 --- a/app/src/main/res/values-pl-rPL/strings.xml +++ b/app/src/main/res/values-pl-rPL/strings.xml @@ -1,180 +1,313 @@ - + Zespół, media społecznościowe + Kolor akcentu - Kolor akcentu motywu, domyślnie morski + Kolor akcentu motywu, domyślnie fioletowy + O aplikacji + Dodaj do ulubionych Dodaj do kolejki Dodaj do playlisty + Wyczyść kolejkę Wyczyść playlistę + Przełącz tryb powtarzania + Usuń Usuń z urządzenia + Szczegóły - Przejdź do albumu - Przejdź do artysty + + Idź do albumu + Idź do artysty Idź do gatunku - Przejdź do katalogu startowego + Idź do katalogu startowego + Przyznaj + Rozmiar siatki Rozmiar siatki (poziomo) + Nowa playlista + Następny + Odtwarzaj Odtwórz wszystko Odtwarzaj następne Odtwarzanie/Pauza + Poprzedni + Usuń z ulubionych Usuń z kolejki odtwarzania Usuń z playlisty + Zmień nazwę + Zapisz kolejkę + Skanuj + Szukaj + Rozpocznij Ustaw jako dzwonek Ustaw jako katalog startowy + "Ustawienia" + Udostępnij - Losowo wszystko + + Wszystko losowo Playlista losowo + Wyłącznik czasowy - Sortowanie + + Sortuj według + + Tylko artyści albumów + Edytor tagów - Przełącz na ulubione + + Przełącz ulubione Przełącz tryb losowy + Adaptacyjny + Dodaj + Dodaj tekst utworu - Dodaj\nzdjęcie + + Dodaj \nzdjęcie + "Dodaj do playlisty" + Dodaj znaczniki czasowe do tekstu - "Dodano 1 tytuł do kolejki odtwarzania" - Dodano %1$d tytułów do kolejki + + "Dodano 1 tytuł do kolejki odtwarzania." + + Dodano %1$d tytułów do kolejki. + Album + Artysta albumu - Pole tytuł lub artysta jest puste. + + Tytuł lub artysta jest pusty. + Albumy + Zawsze + Hej sprawdź ten fajny odtwarzacz muzyki na: https://play.google.com/store/apps/details?id=%s + Losowo Najczęściej odtwarzane utwory - Duży + + Pełne zdjęcie Karta Klasyczny - mały + Mały Tekst + Artysta + Artyści + Odrzucono fokus dźwiękowy. + Dostosuj ustawienia dźwięku i equalizera + Automatyczny + Bazowy kolor motywu + Wzmocnienie Bassu - Biografia + + Bio + Biografia + Po prostu czarny + Czarna lista + Rozmycie + Rozmyta Karta + Nie udało się wysłać raportu Niepoprawny token dostępu. Proszę się skontaktować z twórcą aplikacji. Problemy nie zostały włączone dla wybranego repozytorium. Proszę się skontaktować z twórcą aplikacji. Wystąpił nieoczekiwany błąd. Proszę skontaktować się z twórcą aplikacji. Zła nazwa użytkownika lub hasło - Problem + Błąd Wyślij ręcznie - Proszę opisać problemu + Proszę opisać problem Proszę wpisać poprawne hasło GitHub Proszę wpisać tytuł problemu Proszę wpisać poprawną nazwę użytkownika GitHub - Wystąpił nieoczekiwany błąd. Wyczyść dane podręczne lub - jeśli błąd pojawi się ponownie - wyślij nam maila. - Wrzucanie raportu na GitHub... + Wystąpił nieoczekiwany błąd. Wyczyść dane podręczne lub jeśli błąd pojawi się ponownie - wyślij nam maila + Wrzucanie raportu na GitHub… Wyślij przez konto GitHub + Kup teraz + Anuluj + Karta + Okrągły - Kolorowa Karta + + Kolorowa karta + Karta - Karuzela - Efekt karuzeli na ekranie Teraz odtwarzane + + Kwadratowa karta + + Karozela + + Efekt karuzeli na ekranie \"Teraz odtwarzane\" + Kaskadowy + Strumieniowanie + Lista zmian + Lista zmian zarządzana z aplikacji Telegram + Okręg + Okrągły + Klasyczny + Wyczyść + Wyczyść dane aplikacji + Wyczyść czarną listę + Wyczyść kolejkę + Wyczyść playlistę %1$s? To nie może być cofnięte!]]>
+ Zamknij + Kolor + Kolor + Kolory + Kompozytor - Skopiowano informacje o urządzeniu do schowka - Nie mo\u017cna utworzy\u0107 playlisty. - "Nie mo\u017cna pobra\u0107 dopasowanej ok\u0142adki albumu" - Nie można przywrócić zakupów. + + Skopiowano informacje o urządzeniu do schowka. + + Nie można utworzyć playlisty. + "Nie można pobrać pasującej okładni albumu." + Nie można przywrócić zakupu. Nie można przeskanować %d plików. + Utwórz - Stworzono playlistę %1$s. - Członkowie i współpracownicy + + Utworzono playlistę %1$s. + + Członkowie i współpracownicy + Aktualnie odtwarzane %1$s wykonawcy %2$s. + Dość ciemny - Brak tekstu + + Brak tekstu utworu + Usuń playlistę - %1$s?]]> - Usuń listy odtwarzania + %1$s?]]> + + Usuń playlisty + Usuń utwór %1$s?]]> + Usuń utwory - %1$d ?]]> - %1$d ?]]> + + %1$d playlist?]]> + %1$d utworów?]]> Usunięto %1$d utworów. + Usuwanie utworów + Głębia + Opis + Informacje o urządzeniu + Pozwól Retro Music na modyfikację ustawień dźwięku + Ustaw jako dzwonek + Czy chcesz wyczyścić czarną listę? %1$s z czarnej listy?]]> + Wesprzyj nas + Jeżeli uważasz, że zasługuje na zapłatę za moją pracę możesz zostawić tu drobną sumę + Kup mi: + Pobierz z Last.fm + Tryb samochodowy + Edytuj + Edytuj okładkę + Pusto - Korektor dźwięku + + Equalizer + Błąd - Najczęściej zadawane pytania + + Często zadawane pytania + Ulubione - Dokończ ostatnią piosenkę - Dopasuj + + Dokończ ostatni utwór + + Dopasowany + Płaski + Foldery + Śledź system - Dla Ciebie + + Dla ciebie + Darmowe - Pełne + + Pełny + Pełna karta + Zmień motyw i kolory aplikacji Wygląd i zachowanie interfejsu + Gatunek + Gatunki + Zobacz kod na GitHubie + Dołącz do społeczności Google Plus by uzyskać informacje o aktualizacjach i pomoc + 1 2 3 @@ -183,159 +316,252 @@ 6 7 8 + Styl siatki + Zawias + Historia + Strona główna + Obrót poziomy - Obraz - Obraz gradientowy + + Zdjęcie + + Zdjęcie gradient + Zmień ustawienia pobierania obrazka wykonawcy - Dodano %1$d utworów do listy odtwarzania %2$s. + + Dodano %1$d utworów do playlisty %2$s. + + Instagram Udostępnij swój setup aplikacji Retro Music na Instagramie + Klawiatura - Przepływność - Typ + + Przepustowość + + Format Nazwa pliku - Ścieżka pliku + Ścieżka do pliku Rozmiar - Więcej z %s + + Więcej od %s + Częstotliwość próbkowania + Długość + Wszystkie podpisy + Ostatnio dodane + Ostatni utwór + Odtwórzmy jakąś muzykę + Biblioteka + Kategorie biblioteki + Licencje - Śnieżno biały + + Śnieżnobiały + Słuchacze + Listowanie plików - Ładowanie... + + Ładowanie… + Zaloguj się - Tekst utworu + + Teksty utworów + Stworzone z ❤️ w Indiach + Materialistyczny + Błąd + Błąd uprawnień - Moje imię + + Nazwa + Najczęściej odtwarzane + Nigdy + Nowe zdjęcie banera - Nowa lista odtwarzania + + Nowa playlista + Nowe zdjęcie profilowe - %s jest nowym katalogiem startowym. + + %s jest nowym domyślnym katalogiem. + Następny utwór + Brak albumów + Brak artystów + "Odtwórz jakiś utwór i spróbuj ponownie." - Nie znaleziono korektora dźwięku. + + Nie znaleziono equalizera + Brak gatunków - Nie znaleziono tekstu utworu + + Brak tekstu utworu + Brak odtwarzanych utworów - Brak list odtwarzania - Nie znaleziono zakupów. - Brak wyników. + + Brak playlist + + Nie znaleziono zakupu. + + Brak wyników + Brak utworów - Normalne + + Normalnie + Normalny tekst utworu + Normalny + %s nie znajduje się w magazynie multimediów.]]> + Nic do skanowania. Nic do zobaczenia + Powiadomienie - Zmień wygląd powiadomień + + Zmień wygląd powiadomienia + Teraz odtwarzane - Kolejka teraz odtwarzanych - Dostosuj panel aktualnie odtwarzanych - 9+ motywów Teraz odtwarzane + Kolejka odtwarzania + Dostosuj ekran kolejki odtwarzania + 9+ motywów \"Teraz odtwarzane\" + Tylko przez Wi-Fi + Zaawansowane funkcje eksperymentalne - Inne ustawienia + + Inne + Hasło - Przez 3 miesiące + + Ostatnie 3 miesiące + Wklej tekst utworu tutaj + Szczyt + Odmowa dostępu do pamięci zewnętrznej. + Odmowa dostępu. + Personalizuj + Dostosuj interfejs \"Teraz odtwarzane\" - Wybierz z pamięci lokalnej - Wybierz obraz + + Wybierz z pamięci urządzenia + + Wybierz zdjęcie + Pinterest Śledź stronę Retro Music na Pintrest po więcej inspiracji + Wyraźny + Powiadomienie odtwarzania pokazuje przyciski play/pauza itp. Powiadomienie odtwarzania - Pusta lista odtwarzania - Lista odtwarzania jest pusta - Nazwa listy odtwarzania - Listy odtwarzania - Styl detali albumu + + Wyczyść playlistę + + Playlista jest pusta + + Nazwa playlisty + + Playlisty + + Styl informacji o albumie + Ilość rozmycia dla motywów, mniejsza ilość jest szybsza - Wartość rozmycia + Ilość rozmycia + Dostosuj rogi dolnego okna dialogowego Narożniki okna dialogowego - Filtruj piosenki według długości + + Filtruj utwory według długości Filtruj długość utworów + Zaawansowane - Styl okładki - Dźwięk + Styl albumów + Audio Czarna lista - Przyciski kontrolne + Przełączniki Motyw Obrazki Biblioteka Ekran blokady - Listy odtwarzania + Playlisty + Zatrzymuje odtwarzanie przy zerowym poziomie głośności i wznawia po jego zwiększeniu. Kiedy zwiększysz głośność, odtwarzanie rozpocznie się nawet gdy jesteś poza aplikacją Zatrzymaj przy wyłączonym dźwięku Ta opcja może mieć wpływ na zużycie baterii Pozostaw ekran włączony + Kliknij aby otworzyć z lub przesunąć do przezroczystej nawigacji ekranu teraz odtwarzane Kliknij albo przesuń + Efekt spadającego śniegu + + Pokazuj artystów albumu w kategorii \"Artyści\" Używaj okładki aktualnie odtwarzanego albumu jako tapety ekranu blokady Zmniejsz głośność kiedy dostajesz powiadomienia Zawartość czarnej listy jest ukryta w twojej bibliotece. Rozpocznij odtwarzanie po podłączeniu urządzenia bluetooth Rozmazuj okładkę albumu na ekranie blokady. Może powodować problemy z aplikacjami i widżetami innych firm - Efekt karuzeli na okładce obecnie odtwarzanego albumu. Zwróć uwagę, że motyw karty i rozmycia nie zadziała - Użyj klasycznego wyglądu powiadomień. + Efekt karuzeli na okładce obecnie odtwarzanego albumu. Zwróć uwagę, że motyw karty i rozmycia nie zadziałają + Użyj klasycznego wyglądu powiadomień Tło oraz kolor przycisku sterującego zmieniają się zgodnie z okładką albumu na ekranie odtwarzacza - Koloruje skróty aplikacji w kolorze akcentującym. Za każdym razem, gdy zmieniasz kolor, włącz tę opcję, aby zmiany odniosły skutek. - Koloruje pasek nawigacji kolorem wiodącym. - "Koloruje powiadomienie dominuj\u0105cym kolorem ok\u0142adki albumu." + Koloruje skróty aplikacji w kolorze akcentującym. Za każdym razem, gdy zmieniasz kolor, włącz tę opcję, aby zmiany odniosły skutek + Koloruje pasek nawigacji kolorem wiodącym + "Koloruje powiadomienie dominującym kolorem okładki albumu" Zgodnie z zaleceniami Material Design, w trybie ciemnym kolory powinny być mniej nasycone - Najbardziej dominujący kolor zostanie wybrany z okładki albumu lub wykonawcy. - Dodaj ekstra przyciski do małego odtwarzacza + Najbardziej dominujący kolor zostanie wybrany z okładki albumu lub wykonawcy + Dodaj dodatkowe przyciski do małego odtwarzacza Pokaż dodatkowe informacje o utworze, takie jak format pliku, częstość próbkowania i częstotliwość "Może powodować problemy z odtwarzaniem na niektórych urządzeniach." Przełącz kartę gatunku - Przełącz styl banera strony głównej - Może zwiększyć jakość okładki albumu, ale powoduje spowolnienie ładowania obrazu. Włącz tę opcję tylko jeśli masz problemy z okładkami o niskiej rozdzielczości. + Pokaż lub ukryj baner strony głównej + Może zwiększyć jakość okładki albumu, ale powoduje spowolnienie ładowania obrazu. Włącz tę opcję tylko jeśli masz problemy z okładkami o niskiej rozdzielczości Skonfiguruj widoczność i kolejność kategorii w bibliotece. - Sterowanie Retro na zablokowanym ekranie. + Sterowanie Retro na zablokowanym ekranie Szczegóły licencji oprogramowania open source - Zaokrąglone krawędzie okna, okładek albumów itp. - Włącz/wyłącz tytuły kart u dołu. - Tryb imersji + Zaokrągl rogi w aplikacji + Przełącz tytuły kart u dołu + Tryb immersji Zacznij odtwarzanie po podłączanie zestawu słuchawkowego Odtwarzanie losowe zostanie wyłączone podczas odtwarzania nowej listy utworów - Włącz sterowanie głośnością na ekranie \"teraz odtwarzane\" + Pokaż sterowanie głośnością na ekranie \"teraz odtwarzane\" + + Nawiguj po artystach albumów Pokaż okładkę albumu Wygląd okładki albumu Przesuwanie okładki albumu Siatka albumów Kolorowe skróty aplikacji - Siatka wykonawców + Siatka artystów Zmniejsz głośność przy braku skupienia Automatycznie pobierz obrazki wykonawców Czarna lista Odtwarzanie przez Bluetooth Rozmaż okładkę albumu - Wybierz korektor dźwięku + Wybierz equalizer Klasyczny wygląd powiadomień Kolor adaptacyjny Kolorowe powiadomienia @@ -343,16 +569,17 @@ Dodatkowe sterowanie Informacje o utworze Odtwarzanie bez przerw - Motyw główny + Motyw aplikacji Pokaż kartę gatunku - Siatka wykonawców strony głównej - Baner strony głównej + Siatka artystów + Siatka albumów + Baner Ignoruj okładki z Media Store Ostatnio dodany interwał listy odtwarzania Sterowanie pełno ekranowe Kolorowy pasek nawigacji - Wygląd - Licencje Open Source + Wygląd \"Teraz odtwarzane\" + Licencje open source Narożne krawędzie Wygląd tytułów kart Efekt karuzeli @@ -363,188 +590,310 @@ Tryb losowy Sterowanie głośnością Informacje użytkownika + Kolor podstawowy Główny kolor motywu, domyślnie niebieski, działa teraz z ciemnymi kolorami + Pro - Efekt karuzeli, kolorowe motywy i wiele więcej... + + Efekt karuzeli, kolorowe motywy i wiele więcej.. + Profil + Kup + *Zastanów się przed zakupem, nie proś o zwrot. - Kolejka + + Kolejka odtwarzania + Oceń aplikację - Uwielbiasz tą aplikację? Daj nam znać w sklepie Google Play co o niej sądzisz i co powinniśmy poprawić. + + Uwielbiasz tą aplikację? Daj nam znać w sklepie Google Play co o niej sądzisz i co powinniśmy poprawić + Ostatnie albumy + Ostatni artyści + Usuń + Usuń zdjęcie banera + Usuń okładkę + Usuń z czarnej listy + Usuń zdjęcie profilowe + Usuń utwór z listy odtwarzania - %1$s z listy odtwarzania?]]> + %1$s z playlisty?]]> + Usuń te utwory z listy odtwarzania - %1$d z listy odtwarzania?]]> - Zmień nazwę listy odtwarzania + + %1$d utworów z playlisty?]]> + + Zmień nazwę playlisty + Zgłoś problem + Zgłoś błąd + Zresetuj + Zresetuj obrazek wykonawcy + Przywróć + Przywrócono poprzedni zakup. Zrestartuj aplikacje aby korzystać z wszystkich funkcji. Przywrócono poprzednie zakupy. - Przywracanie zakupu... - Korektor dźwięku Retro + + Przywracanie zakupu… + + Retro Music Equalizer + Retro Music Player Retro Music Pro + Nie udało się usunąć pliku: %s + Nie można uzyskać URI SAF + Otwórz szufladę nawigacji - Włącz \'Pokaż kartę SD\' w menu przeciążenia + Włącz \'Pokaż kartę SD\' w menu %s wymaga dostępu do karty SD Musisz wybrać katalog główny karty SD Wybierz kartę SD w panelu nawigacji Nie otwieraj żadnych podfolderów Dotknij przycisku \'Wybierz\' u dołu ekranu + Nie udało się zapisać do pliku: %s + Zapisz + Zapisz jako plik - Zapisz jako + + Zapisz jako pliki + Zapisz listę odtwarzania do %s. + Zapisywanie zmian + Skanuj media - Zeskanowano %1$d z plików %2$d. + + Zeskanowano %1$d z %2$d plików. + Scrobble - Przeszukaj bibliotekę... + + Przeszukaj bibliotekę… + Zaznacz wszystko + Wybierz zdjęcie banera - Zaznaczone + + Zaznaczono + Zgłoś awarię + Ustaw + Ustaw obrazek wykonawcy + Ustaw zdjęcie profilowe + Udostępnij aplikację - Podziel się z opowieściami + + Udostępnij na Stories + Losowo + Prosty - Wyłącznik czasowy wyłączony. + + Wyłącznik czasowy anulowany. Wyłącznik czasowy ustawiony na %d minut. - Slajd + + Ślizg + Mały album + Społeczność - Udostępnij historię + + Udostępnij na Story + Utwór + Długość utworu + Utwory - Porządek sortowania + + Sortuj według Rosnąco Album Artysta Kompozytor - Dane + Data dodania Data modyfikacji Rok Malejąco + Przepraszamy! Twoje urządzenie nie obsługuje wprowadzania głosowego + Przeszukaj swoją bibliotekę + Stos - Rozpocznij odtwarzanie + + Rozpocznij odtwarzanie. + Sugestie - Po prostu pokaż tylko swoje imię na ekranie głównym + + Po prostu pokażę twoje imię na ekranie głównym + Wspieraj rozwój - Przesuń, aby odblokować + + Przesuń by odblokować + Synchronizowany tekst utworu - Systemowy korektor dźwięku + + Systemowy equalizer + Telegram - Dołącz do grupy Telegram, aby zgłosić błędy, zasugerować zmiany i inne + Dołącz do grupy Telegramie, aby zgłosić błędy, zasugerować zmiany i inne + Dziękuję! - Pliki dźwiękowy + + Plik dźwiękowy + Ten miesiąc + Ten tydzień + Ten rok + Mały + Tytuł + Ekran główny + Miłego popołudnia Dzień dobry Dobry wieczór Dzień dobry Dobry wieczór + Jak masz na imię? - Dzisiaj + + Dziś + Najczęściej odtwarzane albumy + Najczęściej odtwarzani artyści + "Ścieżka (2 dla ścieżki 2 lub 3004 dla ścieżki CD3 4)" + Numer utworu + Przetłumacz + Pomóż nam tłumaczyć aplikację na swój język - Strona na Twitterze + + Twitter Podziel się swoim designem z Retro Music + Niepodpisane - Nie mo\u017cna odtworzy\u0107 utworu. + + Nie można odtworzyć utworu. + Następne + Zaktualizuj obrazek - Aktualizowanie... + + Aktualizowanie… + Nazwa użytkownika + Wersja + Obrót pionowy - Wirtualizacja + + Wirtualizator + Głośność + Przeszukaj sieć + Witaj, + Co chciałbyś udostępnić? + Co nowego + Okno + Zaokrąglone rogi + Ustaw %1$s jako dzwonek. + Wybrano %1$d + Rok - Musisz zaznaczyć przynajmniej jedną kategorię + + Musisz zaznaczyć przynajmniej jedną kategorię. + Będziesz przekierowany do strony ze zgłoszeniami. + Informacje o twoim koncie są używane tylko do autoryzacji. Ilość Notatka(Opcjonalna) Rozpocznij płatność Wyświetl teraz odtwarzane Kliknięcie powiadomienia pokaże teraz odtwarzane zamiast ekranu głównego + Mała karta O %s Wybierz język Tłumacze Osoby, które pomogły przetłumaczyć tę aplikację Wypróbuj Retro Music Premium + Podziel się aplikacją z rodziną i przyjaciółmi + Potrzebujesz pomocy? + Gradient + Nazwa użytkownika + Nie odtwarzane ostatnio + Ostatnie 7 dni + Utwór - Utwory - Utwory Utwory Album - Albumy - Albumy Albumy - %d Utwór - %d Utworu - %d Utworu - %d Utworu + %d utwór + %d utwory %d Album - %d Albumu - %d Albumu - %d Albumu + %d albumy - %d Artysta - %d Artyści - %d Artyści - %d Artyści + %d artysta + %d artyści + Gotowe + Zaimportuj playlisty + Importuje wszystkie playlisty listowane w Media Store z utworami, jeśli playlista istnieje, utwory zostaną połączone. + Import + Liczba utworów + Rosnąco + Song count desc + Aplikacja potrzebuje uprawnienia na dostęp do pamięci urządzenia aby odtwarzać muzykę + Dostęp do pamięci + Dzwonek + Aplikacja potrezbuje uprawnienia na dostęp do ustawień telefonu, aby ustawić utwór jako dzwonek telefonu diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index 12d03622e..c44382282 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/values-ro-rRO/strings.xml b/app/src/main/res/values-ro-rRO/strings.xml index ffefc5eca..6ac56419f 100644 --- a/app/src/main/res/values-ro-rRO/strings.xml +++ b/app/src/main/res/values-ro-rRO/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Poate cauza probleme de redare pe unele dispozitive." Toggle genre tab - Toggle home banner style + Show or hide the home banner Poate mări calitatea copertei de album, dar cauzează încărcarea mai lentă a imaginilor. Activați această opțiune doar dacă aveți probleme cu coperta de album cu rezoluție mică. Configure visibility and order of library categories. Comenzi pe ecranul de blocare pentru Retro music. @@ -345,8 +345,8 @@ Redare \"Gapless\" Temă Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignoră copertele de pe Magazinul Media Ultimul interval de playlist adăugat Comenzi ecran complet diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index ca112efec..dd0fd6c63 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -1,85 +1,144 @@ - + Команда, ссылки на соц. сети + Основной цвет Основной цвет, по умолчанию фиолетовый + О программe + Добавить в избранное - Добавить в очередь проигрывания + Добавить в очередь воспроизведения Добавить в плейлист - Очистить очередь проигрывания + + Очистить очередь воспроизведения Очистить плейлист - Режим повтора цикла + + Режим циклического повтора + Удалить Удалить с устройства + Подробности + Перейти к альбому Перейти к исполнителю Перейти к жанру В начало + Разрешить + Размер сетки Размер сетки (по горизонтали) - Создать плейлист + + Новый плейлист + Далее + Играть Воспроизвести всё Играть далее Воспроизведение/Пауза + Предыдуший + Удалить из избранного Удалить из очереди воспроизведения Удалить из плейлиста + Переименовать + Сохранить очередь воспроизведения + Сканировать + Поиск + Запустить Задать в качества рингтона Установить как стартовый каталог - "Настройки" + + Настройки + Поделиться + Перемешать всё Перемешать плейлист + Таймер сна + Порядок сортировки + + Только альбомы исполнителей + Редактор тегов + Показать избранное Включить перемешивающий режим + Адаптированная + Добавить + Добавить тест песни + Добавить \nфото + "Добавить в плейлист" + Довавить текст песни + "В очередь добавлен 1 трек" + В очередь добавлено %1$d треков. + Альбом + Исполнитель альбома + Трек или альбом отсутствуют. + Альбомы + Всегда - Эй, попробуй этот крутой музыкальный плеер на: https://play.google.com/store/apps/details?id=%s + + Эй, попробуй этот крутой музыкальный плеер на Android: https://play.google.com/store/apps/details?id=%s + Перемешать - Часто прослушиваемые треки - Крупный - Карточка + Лучшие треки + + Полное изображение + Компактный Классический - Маленький - Текст + Маленькое изображение + Минималистичный + Исполнитель + Исполнители + Фокус на аудио отключен. - Отрегулировать настройки звука и эквалайзера + + Измените настройки звука и отрегулируйте настройки эквалайзера + Авто + Основная цветовая тема - Усиление баса + + Усиление басов + Биография + Биография + Чёрная + Черный список + Размытие + Карточка с размытием + Невозможно отправить отчет Недопустимый ключ доступа. Пожалуйста, свяжитесь с разработчиком приложения. Проблемы не активны в выбранном хранилище. Пожалуйста, свяжитесь с разработчиком приложения. @@ -91,91 +150,164 @@ Пожалуйста, введите ваш корректный пароль GitHub Пожалуйста, введите название отчета о проблеме Пожалуйста, введите корректно ваше имя пользователя GitHub - Произошла непредвиденная ошибка. Извините, если это продолжится -то «Очистите данные приложения» + Произошла непредвиденная ошибка. Извините, если это продолжится то \"Очистите данные приложения\" или отправьте сообщение на электронную почту Загрузка отчета на GitHub… Отправить с помощью учетной записи GitHub + Купить сейчас + Отменить + Карточка + Круговой + Цветная карточка + Карточка + + Квадратная Карточка + Карусель + Эффект карусели на экране воспроизведения + Каскадный - Передать на устройство - Список изменений + + Транслировать + + Список изменений + Список изменений находится на канале Telegram + Круг + Круговая - Классический + + Классическая + Очистить + Очистить данные приложения + Очистить черный список + Очистить очередь + Очистить плейлист - %1$s? \u042d\u0442\u043e \u043d\u0435\u043b\u044c\u0437\u044f \u043e\u0442\u043c\u0435\u043d\u0438\u0442\u044c!]]> + %1$s? Это действие отменить невозможно!]]> + Закрыть + Цветная + Цвет + Цвета + Композитор + Скопировать информацию об устройстве в буфер обмена. - \u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043f\u043b\u0435\u0439\u043b\u0438\u0441\u0442. - "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0441\u043a\u0430\u0447\u0430\u0442\u044c \u043f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0443\u044e \u043e\u0431\u043b\u043e\u0436\u043a\u0430 \u0430\u043b\u044c\u0431\u043e\u043c\u0430." + + Не удалось создать плейлист + "Не удалось загрузить подходящую обложку для альбома." Не удалось восстановить покупку. - Не удалось отсканировать %d файлов. + Не удалось просканировать %d файлов. + Создать + Создан плейлист %1$s. + Участники и помощники + Сейчас играет %1$s от %2$s. + Тёмная - Нет текста + + Нет текста песни + Удалить плейлист %1$s?]]> + Удалить плейлисты - Удалить трек - %1$s?]]> - Удалить треки + + Удалить песню + %1$s?]]> + + Удалить песни + %1$d плейлистов?]]> - %1$d треков?]]> - Удалено %1$d треков. + %1$d песен?]]> + Удалено %1$d песен. + Удаление песен + Глубина + Описание + Информация об устройстве + Разрешить Retro Music изменять настройки звука + Выбрать рингтон - Хотите очистить черный список? - %1$s из черного списка?]]> + + Вы хотите очистить черный список? + %1$s из черного списка?]]> + Пожертвовать - Если вы считаете, что я заслуживаю награды за свою работу, можете отправить мне несколько рублей здесь + + Если вы считаете, что я заслуживаю награды за свой труд, можете отправить мне несколько рублей здесь + Купить мне: + Загрузить с Last.fm + Режим вождения + Редактировать + Изменить обложку + Пусто + Эквалайзер + Ошибка + ЧаВО + Избранное - Закончить последнюю песню + + Закончить вопсроизведение последней песни + По размеру + Плоская + Папки + Системная + Для вас - Бесплатная + + Доступно + Заполнение + Заполненная карточка - Изменить тему и цвета приложения + + Измените тему и цвет в приложении Внешний вид интерфейса + Жанр + Жанры + Развивайте проект на GitHub - Присоединяйся к сообществу Google Plus, где ты можешь попросить о помощи или следить за обновлениями Retro Music + + Присоединяйтесь к сообществу GooglePlus, где вы можете попросить о помощи или следить за обновлениями Retro Music + 1 2 3 @@ -184,102 +316,188 @@ 6 7 8 + Стиль сетки + Пластинки + История + Главная + Горизонтальный поворот + Изображение + Градиентное изображение + Изменение настроек загрузки изображения артиста + В плейлист %2$s внесено %1$d песен. + + Instagram Поделитесь своим обзором на приложение Retro Music в Instagram + Клавиатура + Битрейт + Формат Имя файла Расположение файла Размер + Больше от %s + Частота дискретизации + Длина + Показывать всегда + Последние добавленные - Предыдущая песня + + Последняя песня + Давайте послушаем немного музыки + Библиотека + Разделы библиотеки + Лицензии + Белая + Слушатели + Список файлов + Загрузка товаров... + Войти - Текст + + Текст песни + Сделано с ❤️ в Индии + Material + Ошибка + Ошибка разрешения + Моё имя + Любимые треки + Никогда - Новая фото баннера + + Новое фото баннера + Новый плейлист + Новое фото профиля + %s новая стартовая директория. - Следующая песня + + Следующая песня + Альбомы отсутствуют - Исполнители отсутствуют + + Исполнители отсутствую + "Сначала проиграйте песню, затем попробуйте заново." - Эквалайзер не найден. + + Эквалайзер не найден + Жанры отсутствуют + Текст отсутствует + Нет проигрываемых песен + Плейлисты отсутствуют + Покупки отсутствуют. + Нет результатов + Нет песен + Обычная + Обычный текст - Нормальный - %s не найден в media store.]]> - Нечего сканировать. - Нечего сканировать + + Обычный + + %s не найден в хранилище медиа.]]> + + Нет файлов для сканирования. + Нет файлов для сканирования + Уведомления + Настроить стиль уведомлений + Экран воспроизведения Очередь в \"Экране воспроизведения\" - Настроить экран текущего воспроизведения + Настроить экран воспроизведения 9+ тем экрана текущего воспроизведения + Только по Wi-Fi - Расширеные возможности + + Эксперементальные возможности + Другое + Пароль + Последние 3 месяца + Вставьте тест песни сюда + Панель снизу + Разрешение для доступа у внешнему хранилищу не получено. + Разрешения не получены. + Персонализация - Настройте управление экрана текущего воспроизведения и интерфейса - Взять из хранилища + + Настройте управление экрана воспроизведения и интерфейс управления музыкой + + Выбрать из хранилища + Выбрать изображение + Pinterest - Следуйте за страницей Retro Music в Pinterest + Подпишитесь на страницу Retro Music в Pinterest + Гладкая - Уведомление о песне предоставляет действия для воспроизведения / паузы и т.д. + + Уведомление о песне предоставляет действия для воспроизведения/паузы и т.д. Уведомления воспроизведения + Пустой плейлист + Плейлист пуст + Название плейлиста + Плейлисты + Стиль деталей альбома + Степень размытия в соответствующих темах; чем ниже, тем быстрее работает устройство Степень размытия + Регулировка углов нижней панели Диалоговое окно + Фильтровать песни по длине Фильтровать песни по длительности + Расширенные настройки Стиль альбома Звук @@ -290,14 +508,19 @@ Библиотека Экран блокировки Плейлисты - Автоматически ставит музыку на паузу при уменьшении громкости до нуля и играет после увеличения громкости. Предупреждение, когда вы увеличиваете громкость не в приложении, то Retro Music также начнёт воспроизведение + + Ставит музыку на паузу при уменьшении громкости до нуля и играет после увеличения громкости. Предупреждение, когда вы увеличиваете громкость не в приложении, то Retro Music также начнёт воспроизведение Пауза при нулевой громкости Имейте в виду, что включение этой функции может повлиять на заряд батареи Оставить экран включенным + Нажмите, чтобы открыть экран воспроизведения с прозрачной навигации или проведите чтобы открыть без прозрачной навигации Нажмите или Проведите + Эффект снегопада - Использовать обложку альбома текущей песни в качестве обоев на экране блокировки. + + Только альбомы исполнителей + Использовать обложку альбома текущей песни в качестве обоев на экране блокировки. Снизить громкость воспроизведения когда приходит звуковое уведомление Содержимое черного списка скрыто из вашей библиотеки. Начать воспроизведение сразу же после подключения Bluetooth-устройства @@ -307,7 +530,7 @@ Цвет кнопок фона и кнопок управления изменяется в соответствии с обложкой альбома с экрана воспроизведения Окрашивает ярлыки в основной цвет. Каждый раз, когда вы меняете цвет, вкл-выкл эту настройку, чтобы изменение вступило в силу Окрашивает панель навигации в главный цвет - "\u041e\u043a\u0440\u0430\u0448\u0438\u0432\u0430\u0442\u044c \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0435 \u0432 \u044f\u0440\u043a\u0438\u0439 \u0446\u0432\u0435\u0442 \u043e\u0431\u043b\u043e\u0436\u043a\u0438 \u0430\u043b\u044c\u0431\u043e\u043c\u0430" + "Окрашивает уведомление в доминирубщий цвет обложки альбома" Согласно Material Design в темном режиме цвета должны быть немного обесцвечены Наиболее доминирующий цвет будет выбран из обложки альбома или исполнителя Добавить дополнительные элементы управления для мини-плеера @@ -325,7 +548,9 @@ Начать воспроизведение музыки сразу после подключения наушников Режим перемешивания выключится при проигрывании нового списка песен Если доступно достаточно места, показывать регулировку громкости на экране воспроизведения - Показать обложку альбома + + Показывать обложку альбома в разделе исполнители + Показать обложку альбома Тема обложки альбома Стиль смены обложки альбома Сетка альбомов @@ -347,7 +572,8 @@ Тема приложения Показать вкладку жанра Сетка исполнителя на Главной странице - Кнопка Домой + Сетка альбома на Главной странице + Кнопка Домой Игнорировать обложки из хранилища Дата последнего добавления плейлиста Полноэкранное управление @@ -364,42 +590,74 @@ Режим перемешивания Регулировка громкости Информация о пользователе + Основной цвет - Основной цвет темы, по умолчанию - синий, теперь работает с темными цветами + Основной цвет темы, по умолчанию - серо-синий, теперь работает с темными цветами + Pro + Черная тема, Темы экрана воспроизведения, Эффект карусели и многое другое.. + Профиль + Купить - * Подумайте, прежде чем покупать, не просите возврата. + + *Подумайте, прежде чем покупать, не просите возврата. + Очередь + Оценить приложение + Понравилось это приложение? Напишите нам в Google Play Store о том, как мы можем сделать его еще лучше + Последние альбомы + Последние исполнители + Удалить + Удалить фотографию баннера + Удалить обложку + Удалить из черного списка + Удалить фотографию профиля + Удалить песню из плейлиста %1$s из плейлиста?]]> + Удалить песни из плейлиста + + Переименовать плейлист - Сообщить о проблеме + + Сообщить об ошибке + Сообщить об ошибке + Сбросить + Сбросить изображение исполнителя + Восстановить + Предыдущая покупка восстановлена. Перезагрузите приложение, чтобы использовать все функции. Восстановленные предыдущие покупки. + Восстановление покупки ... + Эквалайзер Retro Music + Retro Music Player Retro Music Pro + Ошибка при удалении файла: %s + Не удается получить SAF URI + Открыть навигационное меню Включите «Показать SD-карту» в всплывающем меню @@ -408,38 +666,68 @@ Выберите SD-карту в меню навигации Не открывайте никакие подпапки Нажмите кнопку «выбрать» в нижней части экрана + Ошибка при записи файла: %s + Сохранить + Сохранить как... + Сохранить как... + Сохраненный список воспроизведения в %s. + Сохранение изменений + Сканировать медиа-файлы + Просканировано %1$d из %2$d файлов. + Скробблинг + Найдите свою библиотеку ... + Выбрать все + Выбрать фото баннера + Выбранная кнопка + Отправить лог ошибки + Установить + Установить изображение исполнителя + Выбрать фото профиля + Поделиться приложением + Поделиться в Историях + Перемешать + Простая + Таймер отключения отменен. Таймер сна установлен на %d минут. + Провести + Маленький альбом - Общее + + Социальный + Поделиться историей + Песня + Длительность песни + Песни + Порядок сортировки По возрастанию Альбом @@ -449,103 +737,163 @@ Дата изменения Год По убыванию + Извините! Ваше устройство не поддерживает ввод с помощью речи + Поиск в вашей библиотеке + Стэк + Начать воспроизведение музыки. + Предложения + Просто покажите свое имя на главном экране + Поддержать разработку + Проведите, чтобы разблокировать + Синхронизируемый текст + Системный эквалайзер + Telegram Присоединяйтесь к группе Telegram, чтобы обсуждать ошибки, предлагать улучшения, хвастаться и т.д. + Спасибо ! + Аудиофайл + Этот месяц + Это неделя + Этот год + Маленькая + Название + Панель приборов + Добрый день Добрый день Добрый вечер Доброе утро Доброй ночи + Как тебя зовут + Сегодня + Топ альбомов + Топ исполнителей + "Трек (2 для трека 2 или 3004 для CD3 трека 4)" + Номер трека + Переведите + Помогите нам перевести приложение на ваш язык + Твиттер Поделитесь своим дизайном Retro Music + Не показывать - \u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0441\u0442\u0438 \u044d\u0442\u0443 \u043f\u0435\u0441\u043d\u044e. + + Невозможно проиграть эту песню + Следующие песни + Обновить изображение - Обновленяется ... + + Обновляется… + Имя пользователя + Версия + Вертикальный поворт + Виртуализация + Громкость + Поиск в интернете + Добро пожаловать, + Чем вы хотите поделиться? + Что нового : + Окно + Закругленные углы + Установите %1$s в качестве мелодии звонка. + Выбрано %1$d + Год + Выберите хотя бы одну категорию. + Вы будете перенаправлены на сайт системы отслеживания ошибок. + Данные вашей учетной записи используются только для аутентификации. Количество Примечание (необязательно) Начать оплату Показать экран воспроизведения Нажатие на уведомление будет показывать экран воспроизведения вместо домашнего экрана + Крошечная карточка - О программе %s + Об альбоме %s Выберите язык Переводчики Люди которые помогали переводить это приложение Попробуйте Retro Music Premium - + Поделитесь приложением со своими друзьями и родственниками + Нужна ещё помощь? + Градиент + Имя Пользователя + Сейчас не проигрывается + Последние 7 дней + + Песня - Песни - Песен Песни Альбом - Альбомов - Альбомов Альбомы %d Песня - %d Песен - %d Песен - %d песен + %d Песен %d Альбом - %d Альбомов - %d Альбомов - %d альбомов + %d Альбомов %d Исполнитель - %d Исполнителей - %d Исполнителей %d исполнителей + Готов + Импорт плейлиста + Импортирует все плейлисты, перечисленные в Android Media Store с песнями, если плейлисты уже существуют, песни будут объединены. + Импорт + Колличество песен + По возрастанию + Количество композиций по убыванию + Приложению требуется разрешение на доступ к внутренней памяти вашего устройства для воспроизведения музыки. + Доступ к внутренней памяти + Рингтон + Приложению требуется разрешение на доступ к настройкам вашего устройства, чтобы установить музыку в качестве рингтона. diff --git a/app/src/main/res/values-sk-rSK/strings.xml b/app/src/main/res/values-sk-rSK/strings.xml index 346fa1647..e14f5f04e 100644 --- a/app/src/main/res/values-sk-rSK/strings.xml +++ b/app/src/main/res/values-sk-rSK/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/values-sr-rSP/strings.xml b/app/src/main/res/values-sr-rSP/strings.xml index 62204deab..3fb8f47e4 100644 --- a/app/src/main/res/values-sr-rSP/strings.xml +++ b/app/src/main/res/values-sr-rSP/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Moze prouzrokovati probleme na pojedinim uredjajima." Toggle genre tab - Toggle home banner style + Show or hide the home banner oze poboljsati kvalitet omota albuma ali uzrokuje njegovo sporije ucitavanje. Omoguci ovo samo ukoliko imas problema sa losim kvalitetom slike omota albuma. Configure visibility and order of library categories. Prikazuj kontrole na zakljucanom ekranu @@ -345,8 +345,8 @@ Neuznemiravano reprodukovanje Opsta tema Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignorisi omote sa prodavnice Interval plejliste poslednje dodato Kontrole preko celog ekrana diff --git a/app/src/main/res/values-ta-rIN/strings.xml b/app/src/main/res/values-ta-rIN/strings.xml index 12d03622e..c44382282 100644 --- a/app/src/main/res/values-ta-rIN/strings.xml +++ b/app/src/main/res/values-ta-rIN/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/values-ur-rIN/strings.xml b/app/src/main/res/values-ur-rIN/strings.xml index 12d03622e..c44382282 100644 --- a/app/src/main/res/values-ur-rIN/strings.xml +++ b/app/src/main/res/values-ur-rIN/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -370,7 +370,7 @@ Profile Purchase *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app Love this app? Let us know in the Google Play Store how we can make it even better Recent albums diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 9639c9ecf..a5a736ce3 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -313,7 +313,7 @@ Show extra Song information, such as file format, bitrate and frequency "可能會在某些裝置上出現播放問題。" Toggle genre tab - Toggle home banner style + Show or hide the home banner 提高專輯封面的成像品質,但會造成較長的讀取時間。建議只有在您對低畫質的專輯封面有問題時才開啟此選項。 Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -345,8 +345,8 @@ 無縫播放 主題 Show genre tab - Home artist grid - Home banner + Artist grid + Banner 忽略音訊檔內嵌的專輯封面 Last added playlist interval Fullscreen controls diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml index e2cad59d7..1020e9a06 100644 --- a/app/src/main/res/values/ids.xml +++ b/app/src/main/res/values/ids.xml @@ -16,6 +16,9 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 018bdc9f8..95add41a0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -69,6 +69,8 @@ Sort order + Album artists only + Tag editor Toggle favorite @@ -517,6 +519,7 @@ Snow fall effect + Show Album Artists in the Artist category Use the currently playing song album cover as the lockscreen wallpaper Lower the volume when a system sound is played or a notification is received The content of blacklisted folders is hidden from your library. @@ -534,7 +537,7 @@ Show extra Song information, such as file format, bitrate and frequency "Can cause playback issues on some devices." Toggle genre tab - Toggle home banner style + Show or hide the home banner Can increase the album cover quality, but causes slower image loading times. Only enable this if you have problems with low resolution artworks Configure visibility and order of library categories. Use Retro Music\'s custom lockscreen controls @@ -546,6 +549,7 @@ Shuffle mode will turn off when playing a new list of songs If enough space is available, show volume controls in the now playing screen + Navigate by Album Artist Show album cover Album cover theme Album cover skip @@ -567,8 +571,9 @@ Gapless playback App theme Show genre tab - Home artist grid - Home banner + Artist grid + Album grid + Banner Ignore Media Store covers Last added playlist interval Fullscreen controls @@ -599,7 +604,7 @@ *Think before buying, don\'t ask for refund. - Queue + Playing Queue Rate the app @@ -880,10 +885,15 @@ %d Artist %d Artists - - Hello blank fragment Done Import playlist It imports all playlists listed in the Android Media Store with songs, if the playlists already exists, the songs will get merged. Import + Song count + Ascending + Song count desc + The app needs permission to access your device storage for playing music + Storage Access + Ringtone + The app needs permission to access your device settings in order to set music as Ringtone diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 01529cc06..650e5a2b2 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -6,18 +6,21 @@ @color/window_color_light none 16dp + @style/WindowAnimationTransition + + + + + diff --git a/app/src/main/res/xml/pref_advanced.xml b/app/src/main/res/xml/pref_advanced.xml index 51688b2f5..98af66783 100755 --- a/app/src/main/res/xml/pref_advanced.xml +++ b/app/src/main/res/xml/pref_advanced.xml @@ -59,15 +59,6 @@ android:title="@string/pref_keep_screen_on_title" app:icon="@drawable/ic_settings_brigntness" /> - + + + +