Merge pull request #1168 from prathameshmm02/dev

New Update
main
Prathamesh More 2021-11-21 21:34:36 +05:30 committed by GitHub
commit c7d158f860
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
319 changed files with 5265 additions and 3980 deletions

View File

@ -6,7 +6,6 @@ apply plugin: 'kotlin-parcelize'
android {
compileSdkVersion 31
buildToolsVersion = '29.0.3'
defaultConfig {
minSdkVersion 21
@ -16,7 +15,7 @@ android {
vectorDrawables.useSupportLibrary = true
applicationId "code.name.monkey.retromusic"
versionCode 10520
versionCode 10519
versionName '5.0.0' + "_" + getDate()
buildConfigField("String", "GOOGLE_PLAY_LICENSING_KEY", "\"${getProperty(getProperties('../public.properties'), 'GOOGLE_PLAY_LICENSE_KEY')}\"")
@ -92,21 +91,21 @@ dependencies {
implementation project(':appthemehelper')
implementation "androidx.gridlayout:gridlayout:1.0.0"
implementation "androidx.cardview:cardview:1.0.0"
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'androidx.annotation:annotation:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
implementation 'androidx.appcompat:appcompat:1.4.0'
implementation 'androidx.annotation:annotation:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'androidx.preference:preference-ktx:1.1.1'
implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.palette:palette-ktx:1.0.0'
//Cast Dependencies
implementation 'androidx.mediarouter:mediarouter:1.2.5'
implementation 'com.google.android.gms:play-services-cast-framework:20.0.0'
implementation 'com.google.android.gms:play-services-cast-framework:20.1.0'
//WebServer by NanoHttpd
implementation "org.nanohttpd:nanohttpd:2.3.1"
def nav_version = "2.4.0-alpha07"
def nav_version = '2.4.0-beta02'
implementation "androidx.navigation:navigation-runtime-ktx:$nav_version"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
@ -116,13 +115,13 @@ dependencies {
implementation "androidx.room:room-ktx:$room_version"
kapt "androidx.room:room-compiler:$room_version"
def lifecycle_version = "2.3.1"
def lifecycle_version = "2.4.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
implementation 'com.google.android.play:core-ktx:1.8.1'
implementation 'com.google.android.material:material:1.5.0-alpha03'
implementation 'com.google.android.material:material:1.5.0-beta01'
def retrofit_version = '2.9.0'
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
@ -134,18 +133,18 @@ dependencies {
implementation "com.afollestad.material-dialogs:input:$material_dialog_version"
implementation "com.afollestad.material-dialogs:color:$material_dialog_version"
implementation "com.afollestad.material-dialogs:bottomsheets:$material_dialog_version"
//noinspection GradleDependency
implementation 'com.afollestad:material-cab:0.1.12'
implementation 'com.afollestad:material-cab:2.0.1'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
def kotlin_coroutines_version = "1.5.2-native-mt"
def kotlin_coroutines_version = '1.5.2'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
def koin_version = "2.1.5"
implementation "org.koin:koin-core:$koin_version"
implementation "org.koin:koin-androidx-viewmodel:$koin_version"
def koin_version = '3.1.3'
implementation "io.insert-koin:koin-core:$koin_version"
implementation "io.insert-koin:koin-android:$koin_version"
implementation 'com.github.bumptech.glide:glide:4.12.0'
kapt 'com.github.bumptech.glide:compiler:4.12.0'
@ -153,14 +152,15 @@ dependencies {
implementation 'com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:1.0.0'
implementation 'org.eclipse.mylyn.github:org.eclipse.egit.github.core:3.4.0.201406110918-r'
implementation 'org.bitbucket.ijabz:jaudiotagger:2.2.5'
implementation 'com.anjlab.android.iab.v3:library:1.1.0'
implementation 'org.eclipse.mylyn.github:org.eclipse.egit.github.core:2.1.5'
implementation 'com.github.AdrienPoupa:jaudiotagger:2.2.3'
implementation 'com.anjlab.android.iab.v3:library:2.0.1'
implementation 'com.r0adkll:slidableactivity:2.1.0'
implementation 'com.heinrichreimersoftware:material-intro:2.0.0'
implementation 'com.github.dhaval2404:imagepicker:1.7.1'
implementation 'me.zhanghai.android.fastscroll:library:1.1.7'
debugImplementation 'com.amitshekhar.android:debug-db:1.0.6'
implementation 'cat.ereza:customactivityoncrash:2.3.0'
debugImplementation 'com.github.amitshekhariitbhu:Android-Debug-Database:1.0.6'
}
apply from: '../spotless.gradle'

View File

@ -16,16 +16,13 @@
# public *;
#}
# Uncomment this to preserve the line number information for
# Preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
-keepnames class **
-keepnames class ** { *; }
-keepattributes SourceFile,LineNumberTable
-dontwarn java.lang.invoke.*
-dontwarn **$$Lambda$*
@ -58,11 +55,9 @@
-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
-keep class code.name.monkey.retromusic.network.model.** { *; }
-keep class code.name.monkey.retromusic.model.CategoryInfo { *; }
-keep class com.google.android.material.bottomsheet.** { *; }
-keep class code.name.monkey.retromusic.Constants { *; }
-keep class com.google.android.material.bottomsheet.** { *; }

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="md3_available">true</bool>
</resources>

View File

@ -96,7 +96,7 @@
<style name="circleImageView" parent="">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">16dp</item>
<item name="cornerSize">40dp</item>
</style>
<style name="BottomSheetItemTextAppearance" parent="Widget.MaterialComponents.BottomNavigationView.Colored">

View File

@ -8,7 +8,7 @@
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
@ -107,12 +107,9 @@
<data android:mimeType="vnd.android.cursor.dir/audio" />
</intent-filter>
</activity>
<activity android:name=".activities.PlayingQueueActivity" />
<activity android:name=".activities.SettingsActivity" />
<activity android:name=".activities.tageditor.AlbumTagEditorActivity" />
<activity android:name=".activities.tageditor.SongTagEditorActivity" />
<activity android:name=".activities.LyricsActivity" />
<activity android:name=".activities.UserInfoActivity" />
<activity android:name=".activities.SupportDevelopmentActivity" />
<activity android:name=".activities.LicenseActivity" />
<activity android:name=".activities.PurchaseActivity" />
@ -122,6 +119,19 @@
<activity android:name=".activities.DriveModeActivity" />
<activity android:name=".activities.PermissionActivity" />
<activity android:name=".activities.LockScreenActivity" />
<activity
android:name="code.name.monkey.retromusic.fragments.backup.RestoreActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="file" />
<data android:mimeType="*/*" />
<data android:pathPattern=".*\\.rmbak" />
</intent-filter>
</activity>
<activity
android:name=".appshortcuts.AppShortcutLauncherActivity"
@ -136,14 +146,14 @@
android:name=".cast.ExpandedControlsActivity"
android:label="@string/app_name"
android:launchMode="singleTask"
android:theme="@style/Theme.AppCompat.NoActionBar"
android:screenOrientation="portrait">
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.MainActivity"/>
android:value=".activities.MainActivity" />
</activity>
<provider
@ -156,7 +166,6 @@
android:resource="@xml/provider_paths" />
</provider>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}"
@ -266,16 +275,16 @@
android:value="true" />
<!-- Android Auto -->
<!-- <meta-data
<meta-data
android:name="com.google.android.gms.car.application"
android:resource="@xml/automotive_app_desc"/>
android:resource="@xml/automotive_app_desc" />
<meta-data
android:name="com.google.android.gms.car.application.theme"
android:resource="@style/CarTheme" />
<meta-data
android:name="com.google.android.gms.car.notification.SmallIcon"
android:resource="@drawable/ic_notification"/>
-->
android:resource="@drawable/ic_notification" />
<!-- ChromeCast -->
<meta-data
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"

View File

@ -6,7 +6,7 @@
word-wrap: break-word;
}
div{
div {
margin: 20px 10px;
padding: 10px;
border-radius: 10px;
@ -16,6 +16,8 @@
h2 {
margin-block-end: 0rem;
margin-block-start: 0rem;
display: inline-block;
vertical-align: center;
}
li {
@ -35,7 +37,6 @@
span {
font-size: 0.7rem;
line-height: 0.7rem;
}
h5 {
@ -47,58 +48,124 @@
margin: 10px 0px;
font-size: 1rem;
}
.tag {
border-radius: 5px;
margin-left: 10px;
padding: 5px;
display: inline-block;
vertical-align: top;
}
{style-placeholder}
</style>
</head>
<body>
<h2>
<b>Clear the app if it crashes after updating</b>
</h2>
<div>
<h5>Date</h5>
<h2>v5.1.0<span class="tag"><i>Beta</i></span></h2>
<h3>What's New</h3>
<ul>
<li><b>Material You</b></li>
<li>Going Edge-to-Edge</li>
<li>Added Backup & restore</li>
</ul>
<h3>Improved</h3>
<ul>
<li>Improved Crossfade</li>
</ul>
</div>
<div>
<h5>September 06, 2021</h5>
<h2>v5.0.0</h2>
<h3>What's New</h3>
<ul>
<li>Added Chromecast support</li>
<li>Added animated icons</li>
<li>Added cross-fade (experimental)</li>
<li>Added ability to remember the last tab</li>
<li>Added whitelisting songs</li>
<li>Added support for embedded synced lyrics</li>
<li>Added lyrics editor for normal and synced lyrics</li>
<li>Added playlist ordering</li>
<li>Added search filters</li>
<li>Added audio fade</li>
<li>Added Multi-select in album and artist details</li>
<li>Added SD card from folders tab</li>
<li>Added Synced lyrics in all themes</li>
<li>Added Swipe anywhere to change the song</li>
<li>Added album artist</li>
<li> Albums now show album artists instead of artists of the first song</li>
<li>Added ability to Remember last tab</li>
<li>Added Whitelisting songs</li>
<li>You can now browse SDCard from Folders Tab</li>
</ul>
</div>
<div>
<h5>August 22, 2021</h5>
<h2>v4.4.0<span class="tag"><i>Beta</i></span></h2>
<h3>What's New</h3>
<ul>
<li>Added Crossfade</li>
<li>Multi-select in Album and Artist Details</li>
<li>Albums now show Album Artists instead of artist of first song</li>
</ul>
<h3>Fixed</h3>
<ul>
<li> Fixed playlist preview images</li>
<li> Fixed language switching</li>
<li>Fixed Playlist Preview Images</li>
</ul>
</div>
<div>
<h5>August 11, 2021</h5>
<h2>v4.2.30<span class="tag"><i>Beta</i></span></h2>
<h3>What's New</h3>
<ul>
<li>Revamped Playlist Tab</li>
<li>Revamped Genres Tab</li>
<li>Added support for embedded Synced Lyrics</li>
<li>Added animated icons</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Fixed Language Switching</li>
<li>Fixed some reported bugs</li>
</ul>
</div>
<div>
<h5>July 18, 2021</h5>
<h2>v4.2.020<span class="tag"><i>Beta</i></span></h2>
<h3>What's New</h3>
<ul>
<li>Added ChromeCast Support</li>
<li>Added Lyrics Editor for Normal and Synced Lyrics</li>
<li>Added Ripple Animation for Color Theme</li>
<li>Added Drag to seek in Tiny Theme</li>
<li>Added Playlist Order</li>
<li>Added Search Filters</li>
<li>Added Audio Fade</li>
<li>Synced Lyrics in all Themes</li>
<li>Swipe anywhere to change song</li>
</ul>
<h3>Improved</h3>
<ul>
<li>Improved playlists tab</li>
<li> Improved genres tab</li>
<li>Fixed Navigate by Album Artist</li>
<li>Changed New Music Mix Actions</li>
<li>Improved Animations</li>
<li>And some minor bug fixes and improvements</li>
</ul>
</div>
<!--<h3><span class="colorHeader">Bug fixes</span></h3>
<ul>
<li></li>
</ul>-->
</body>
<div>
<h5>October 12, 2020</h5>
<h2>v4.0.10</h2>
<h3>What's New</h3>
<ul>
<li>Re-built from scratch using MVVM Architecture and JetPack Components</li>
<li>New Material Design icon</li>
<li>Implemented a custom database for playlists</li>
<li>Added new Material Design motions</li>
<li>Bug fixes & performance improvements</li>
<li>Revamped Home tab UI</li>
<li>Android 11 support</li>
<li>And more!</li>
</ul>
</div>
<div>
<h5>April 30, 2020</h5>
<h2>v3.5.110<span class="tag"><i>Beta</i></span></h2>
<h3>What's New</h3>
<ul>
<li>Changed profile form image to icon</li>
<li>New what's new screen</li>
<li>Added In-App language changer, where you can select language</li>
</ul>
<h3>Improved</h3>
<ul>
<li>Improved loading of Songs, Albums, Artists, Genres, Playlists</li>
</ul>
</div>
</body>

View File

@ -21,7 +21,7 @@ import code.name.monkey.appthemehelper.util.VersionUtils
import code.name.monkey.retromusic.Constants.PRO_VERSION_PRODUCT_ID
import code.name.monkey.retromusic.appshortcuts.DynamicShortcutManager
import com.anjlab.android.iab.v3.BillingProcessor
import com.anjlab.android.iab.v3.TransactionDetails
import com.anjlab.android.iab.v3.PurchaseInfo
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin
@ -49,9 +49,10 @@ class App : Application() {
DynamicShortcutManager(this).initDynamicShortcuts()
// automatically restores purchases
billingProcessor = BillingProcessor(this, BuildConfig.GOOGLE_PLAY_LICENSING_KEY,
billingProcessor = BillingProcessor(
this, BuildConfig.GOOGLE_PLAY_LICENSING_KEY,
object : BillingProcessor.IBillingHandler {
override fun onProductPurchased(productId: String, details: TransactionDetails?) {}
override fun onProductPurchased(productId: String, details: PurchaseInfo?) {}
override fun onPurchaseHistoryRestored() {
Toast.makeText(

View File

@ -148,3 +148,4 @@ const val SHOW_LYRICS = "show_lyrics"
const val REMEMBER_LAST_TAB = "remember_last_tab"
const val LAST_USED_TAB = "last_used_tab"
const val WHITELIST_MUSIC = "whitelist_music"
const val MATERIAL_YOU = "material_you"

View File

@ -1,107 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.activities;
import android.graphics.Color;
import android.os.Bundle;
import android.view.MenuItem;
import android.webkit.WebView;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.Toolbar;
import org.jetbrains.annotations.Nullable;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import code.name.monkey.appthemehelper.ThemeStore;
import code.name.monkey.appthemehelper.util.ATHUtil;
import code.name.monkey.appthemehelper.util.ColorUtil;
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.activities.base.AbsBaseActivity;
/** 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();
// 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(
"<h1>Unable to load</h1><p>" + e.getLocalizedMessage() + "</p>", "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);
}
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
}
}

View File

@ -0,0 +1,99 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.activities
import android.graphics.Color
import android.os.Bundle
import android.view.MenuItem
import android.webkit.WebView
import androidx.appcompat.widget.Toolbar
import code.name.monkey.appthemehelper.ThemeStore.Companion.accentColor
import code.name.monkey.appthemehelper.util.ATHUtil.isWindowBackgroundDark
import code.name.monkey.appthemehelper.util.ATHUtil.resolveColor
import code.name.monkey.appthemehelper.util.ColorUtil.lightenColor
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsThemeActivity
import java.io.BufferedReader
import java.io.InputStreamReader
import java.nio.charset.StandardCharsets
/** Created by hemanths on 2019-09-27. */
class LicenseActivity : AbsThemeActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_license)
val toolbar = findViewById<Toolbar>(R.id.toolbar)
setSupportActionBar(toolbar)
ToolbarContentTintHelper.colorBackButton(toolbar)
toolbar.setBackgroundColor(resolveColor(this, R.attr.colorSurface))
val webView = findViewById<WebView>(R.id.license)
try {
val buf = StringBuilder()
val json = assets.open("oldindex.html")
val br = BufferedReader(InputStreamReader(json, StandardCharsets.UTF_8))
var str: String?
while (br.readLine().also { str = it } != null) {
buf.append(str)
}
br.close()
// Inject color values for WebView body background and links
val isDark = isWindowBackgroundDark(this)
val backgroundColor = colorToCSS(
resolveColor(
this,
R.attr.colorSurface,
Color.parseColor(if (isDark) "#424242" else "#ffffff")
)
)
val contentColor = colorToCSS(Color.parseColor(if (isDark) "#ffffff" else "#000000"))
val changeLog = buf.toString()
.replace(
"{style-placeholder}", String.format(
"body { background-color: %s; color: %s; }", backgroundColor, contentColor
)
)
.replace("{link-color}", colorToCSS(accentColor(this)))
.replace(
"{link-color-active}",
colorToCSS(
lightenColor(accentColor(this))
)
)
webView.loadData(changeLog, "text/html", "UTF-8")
} catch (e: Throwable) {
webView.loadData(
"<h1>Unable to load</h1><p>" + e.localizedMessage + "</p>", "text/html", "UTF-8"
)
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
onBackPressed()
return true
}
return super.onOptionsItemSelected(item)
}
private fun colorToCSS(color: Int): String {
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
}
}

View File

@ -48,9 +48,7 @@ class LockScreenActivity : AbsMusicServiceActivity() {
setContentView(binding.root)
hideStatusBar()
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
setLightNavigationBar(true)
val config = SlidrConfig.Builder().listener(object : SlidrListener {
override fun onSlideStateChanged(state: Int) {

View File

@ -21,10 +21,12 @@ import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import androidx.lifecycle.lifecycleScope
import androidx.navigation.contains
import androidx.navigation.ui.setupWithNavController
import code.name.monkey.retromusic.*
import code.name.monkey.retromusic.activities.base.AbsCastActivity
import code.name.monkey.retromusic.databinding.SlidingMusicPanelLayoutBinding
import code.name.monkey.retromusic.extensions.currentFragment
import code.name.monkey.retromusic.extensions.extra
import code.name.monkey.retromusic.extensions.findNavController
import code.name.monkey.retromusic.fragments.base.AbsRecyclerViewFragment
@ -54,9 +56,6 @@ class MainActivity : AbsCastActivity(), OnSharedPreferenceChangeListener {
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setLightNavigationBar(true)
setTaskDescriptionColorAuto()
hideStatusBar()
updateTabs()
@ -75,6 +74,8 @@ class MainActivity : AbsCastActivity(), OnSharedPreferenceChangeListener {
val categoryInfo: CategoryInfo = PreferenceUtil.libraryCategory.first { it.visible }
if (categoryInfo.visible) {
if (!navGraph.contains(PreferenceUtil.lastTab)) PreferenceUtil.lastTab =
categoryInfo.category.id
navGraph.setStartDestination(
if (PreferenceUtil.rememberLastTab) {
PreferenceUtil.lastTab.let {
@ -88,10 +89,10 @@ class MainActivity : AbsCastActivity(), OnSharedPreferenceChangeListener {
)
}
navController.graph = navGraph
getBottomNavigationView().setupWithNavController(navController)
bottomNavigationView.setupWithNavController(navController)
// Scroll Fragment to top
getBottomNavigationView().setOnItemReselectedListener {
supportFragmentManager.findFragmentById(R.id.fragment_container)?.childFragmentManager?.fragments?.get(0)
bottomNavigationView.setOnItemReselectedListener {
currentFragment(R.id.fragment_container)
.also {
if (it is AbsRecyclerViewFragment<*, *>) {
it.scrollToTop()
@ -101,19 +102,25 @@ class MainActivity : AbsCastActivity(), OnSharedPreferenceChangeListener {
}
}
}
// This is more like a work-around as for start destination of navGraph
// enterTransition won't work as expected
navGraph.setStartDestination(R.id.libraryFragment)
navController.addOnDestinationChangedListener { _, destination, _ ->
when (destination.id) {
when (destination.id) {
R.id.action_home, R.id.action_song, R.id.action_album, R.id.action_artist, R.id.action_folder, R.id.action_playlist, R.id.action_genre -> {
// Save the last tab
if (PreferenceUtil.rememberLastTab) {
saveTab(destination.id)
}
// Show Bottom Navigation Bar
setBottomBarVisibility(true)
setBottomNavVisibility(visible = true, animate = true)
}
else -> setBottomBarVisibility(false) // Hide Bottom Navigation Bar
R.id.playing_queue_fragment -> {
setBottomNavVisibility(visible = false)
hideBottomSheet(true)
}
else -> setBottomNavVisibility(visible = false, animate = true) // Hide Bottom Navigation Bar
}
}
}
@ -129,7 +136,7 @@ class MainActivity : AbsCastActivity(), OnSharedPreferenceChangeListener {
PreferenceUtil.registerOnSharedPreferenceChangedListener(this)
val expand = extra<Boolean>(EXPAND_PANEL).value ?: false
if (expand && PreferenceUtil.isExpandPanel) {
setBottomBarVisibility(false)
setBottomNavVisibility(false)
fromNotification = true
expandPanel()
intent.removeExtra(EXPAND_PANEL)
@ -142,7 +149,7 @@ class MainActivity : AbsCastActivity(), OnSharedPreferenceChangeListener {
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
if (key == GENERAL_THEME || key == BLACK_THEME || key == ADAPTIVE_COLOR_APP || key == USER_NAME || key == TOGGLE_FULL_SCREEN || key == TOGGLE_VOLUME || key == ROUND_CORNERS || key == CAROUSEL_EFFECT || key == NOW_PLAYING_SCREEN_ID || key == TOGGLE_GENRE || key == BANNER_IMAGE_PATH || key == PROFILE_IMAGE_PATH || key == CIRCULAR_ALBUM_ART || key == KEEP_SCREEN_ON || key == TOGGLE_SEPARATE_LINE || key == TOGGLE_HOME_BANNER || key == TOGGLE_ADD_CONTROLS || key == ALBUM_COVER_STYLE || key == HOME_ARTIST_GRID_STYLE || key == ALBUM_COVER_TRANSFORM || key == DESATURATED_COLOR || key == EXTRA_SONG_INFO || key == TAB_TEXT_MODE || key == LANGUAGE_NAME || key == LIBRARY_CATEGORIES) {
if (key == GENERAL_THEME || key == MATERIAL_YOU || key == BLACK_THEME || key == ADAPTIVE_COLOR_APP || key == USER_NAME || key == TOGGLE_FULL_SCREEN || key == TOGGLE_VOLUME || key == ROUND_CORNERS || key == CAROUSEL_EFFECT || key == NOW_PLAYING_SCREEN_ID || key == TOGGLE_GENRE || key == BANNER_IMAGE_PATH || key == PROFILE_IMAGE_PATH || key == CIRCULAR_ALBUM_ART || key == KEEP_SCREEN_ON || key == TOGGLE_SEPARATE_LINE || key == TOGGLE_HOME_BANNER || key == TOGGLE_ADD_CONTROLS || key == ALBUM_COVER_STYLE || key == HOME_ARTIST_GRID_STYLE || key == ALBUM_COVER_TRANSFORM || key == DESATURATED_COLOR || key == EXTRA_SONG_INFO || key == TAB_TEXT_MODE || key == LANGUAGE_NAME || key == LIBRARY_CATEGORIES) {
postRecreate()
}
}

View File

@ -41,8 +41,6 @@ class PermissionActivity : AbsMusicServiceActivity() {
binding = ActivityPermissionBinding.inflate(layoutInflater)
setContentView(binding.root)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setLightNavigationBar(true)
setTaskDescriptionColorAuto()
setupTitle()

View File

@ -1,204 +0,0 @@
/*
* Copyright (c) 2020 Hemanth Savarla.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*/
package code.name.monkey.retromusic.activities
import android.content.res.ColorStateList
import android.os.Bundle
import android.view.MenuItem
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity
import code.name.monkey.retromusic.adapter.song.PlayingQueueAdapter
import code.name.monkey.retromusic.databinding.ActivityPlayingQueueBinding
import code.name.monkey.retromusic.extensions.accentColor
import code.name.monkey.retromusic.extensions.surfaceColor
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.ThemedFastScroller
import com.h6ah4i.android.widget.advrecyclerview.animator.DraggableItemAnimator
import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager
import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager
import com.h6ah4i.android.widget.advrecyclerview.touchguard.RecyclerViewTouchActionGuardManager
import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils
open class PlayingQueueActivity : AbsMusicServiceActivity() {
private lateinit var binding: ActivityPlayingQueueBinding
private var wrappedAdapter: RecyclerView.Adapter<*>? = null
private var recyclerViewDragDropManager: RecyclerViewDragDropManager? = null
private var recyclerViewSwipeManager: RecyclerViewSwipeManager? = null
private var recyclerViewTouchActionGuardManager: RecyclerViewTouchActionGuardManager? = null
private var playingQueueAdapter: PlayingQueueAdapter? = null
private lateinit var linearLayoutManager: LinearLayoutManager
private fun getUpNextAndQueueTime(): String {
val duration = MusicPlayerRemote.getQueueDurationMillis(MusicPlayerRemote.position)
return MusicUtil.buildInfoString(
resources.getString(R.string.up_next),
MusicUtil.getReadableDurationString(duration)
)
}
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
binding = ActivityPlayingQueueBinding.inflate(layoutInflater)
setContentView(binding.root)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
setLightNavigationBar(true)
setupToolbar()
setUpRecyclerView()
binding.clearQueue.setOnClickListener {
MusicPlayerRemote.clearQueue()
}
checkForPadding()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
android.R.id.home -> {
onBackPressed()
true
}
else -> super.onOptionsItemSelected(item)
}
}
private fun setUpRecyclerView() {
recyclerViewTouchActionGuardManager = RecyclerViewTouchActionGuardManager()
recyclerViewDragDropManager = RecyclerViewDragDropManager()
recyclerViewSwipeManager = RecyclerViewSwipeManager()
val animator = DraggableItemAnimator()
animator.supportsChangeAnimations = false
playingQueueAdapter = PlayingQueueAdapter(
this,
MusicPlayerRemote.playingQueue.toMutableList(),
MusicPlayerRemote.position,
R.layout.item_queue
)
wrappedAdapter = recyclerViewDragDropManager?.createWrappedAdapter(playingQueueAdapter!!)
wrappedAdapter = wrappedAdapter?.let { recyclerViewSwipeManager?.createWrappedAdapter(it) }
linearLayoutManager = LinearLayoutManager(this)
binding.recyclerView.layoutManager = linearLayoutManager
binding.recyclerView.adapter = wrappedAdapter
binding.recyclerView.itemAnimator = animator
recyclerViewTouchActionGuardManager?.attachRecyclerView(binding.recyclerView)
recyclerViewDragDropManager?.attachRecyclerView(binding.recyclerView)
recyclerViewSwipeManager?.attachRecyclerView(binding.recyclerView)
linearLayoutManager.scrollToPositionWithOffset(MusicPlayerRemote.position + 1, 0)
binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (dy > 0) {
binding.clearQueue.shrink()
} else if (dy < 0) {
binding.clearQueue.extend()
}
}
})
ThemedFastScroller.create(binding.recyclerView)
}
private fun checkForPadding() {
}
override fun onQueueChanged() {
if (MusicPlayerRemote.playingQueue.isEmpty()) {
finish()
return
}
checkForPadding()
updateQueue()
updateCurrentSong()
}
override fun onMediaStoreChanged() {
updateQueue()
updateCurrentSong()
}
private fun updateCurrentSong() {
binding.toolbar.subtitle = getUpNextAndQueueTime()
}
override fun onPlayingMetaChanged() {
updateQueuePosition()
}
private fun updateQueuePosition() {
playingQueueAdapter?.setCurrent(MusicPlayerRemote.position)
resetToCurrentPosition()
binding.toolbar.subtitle = getUpNextAndQueueTime()
}
private fun updateQueue() {
playingQueueAdapter?.swapDataSet(MusicPlayerRemote.playingQueue, MusicPlayerRemote.position)
resetToCurrentPosition()
}
private fun resetToCurrentPosition() {
binding.recyclerView.stopScroll()
linearLayoutManager.scrollToPositionWithOffset(MusicPlayerRemote.position + 1, 0)
}
override fun onPause() {
if (recyclerViewDragDropManager != null) {
recyclerViewDragDropManager!!.cancelDrag()
}
super.onPause()
}
override fun onDestroy() {
if (recyclerViewDragDropManager != null) {
recyclerViewDragDropManager!!.release()
recyclerViewDragDropManager = null
}
if (recyclerViewSwipeManager != null) {
recyclerViewSwipeManager?.release()
recyclerViewSwipeManager = null
}
if (wrappedAdapter != null) {
WrapperAdapterUtils.releaseAll(wrappedAdapter)
wrappedAdapter = null
}
playingQueueAdapter = null
super.onDestroy()
}
private fun setupToolbar() {
binding.toolbar.subtitle = getUpNextAndQueueTime()
binding.toolbar.setBackgroundColor(surfaceColor())
setSupportActionBar(binding.toolbar)
binding.clearQueue.backgroundTintList = ColorStateList.valueOf(accentColor())
ColorStateList.valueOf(
MaterialValueHelper.getPrimaryTextColor(this, ColorUtil.isColorLight(accentColor()))
).apply {
binding.clearQueue.setTextColor(this)
binding.clearQueue.iconTint = this
}
}
}

View File

@ -14,10 +14,8 @@
*/
package code.name.monkey.retromusic.activities
import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.Color
import android.os.AsyncTask
import android.os.Bundle
import android.util.Log
import android.view.MenuItem
@ -31,14 +29,12 @@ import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsBaseActivity
import code.name.monkey.retromusic.databinding.ActivityProVersionBinding
import com.anjlab.android.iab.v3.BillingProcessor
import com.anjlab.android.iab.v3.TransactionDetails
import java.lang.ref.WeakReference
import com.anjlab.android.iab.v3.PurchaseInfo
class PurchaseActivity : AbsBaseActivity(), BillingProcessor.IBillingHandler {
private lateinit var binding: ActivityProVersionBinding
private lateinit var billingProcessor: BillingProcessor
private var restorePurchaseAsyncTask: AsyncTask<*, *, *>? = null
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
@ -47,8 +43,6 @@ class PurchaseActivity : AbsBaseActivity(), BillingProcessor.IBillingHandler {
setContentView(binding.root)
setStatusbarColor(Color.TRANSPARENT)
setLightStatusbar(false)
setNavigationbarColor(Color.BLACK)
setLightNavigationBar(false)
binding.toolbar.navigationIcon?.setTint(Color.WHITE)
binding.toolbar.setNavigationOnClickListener { onBackPressed() }
@ -60,9 +54,7 @@ class PurchaseActivity : AbsBaseActivity(), BillingProcessor.IBillingHandler {
MaterialUtil.setTint(binding.purchaseButton, true)
binding.restoreButton.setOnClickListener {
if (restorePurchaseAsyncTask == null || restorePurchaseAsyncTask!!.status != AsyncTask.Status.RUNNING) {
restorePurchase()
}
restorePurchase()
}
binding.purchaseButton.setOnClickListener {
billingProcessor.purchase(this@PurchaseActivity, PRO_VERSION_PRODUCT_ID)
@ -72,13 +64,25 @@ class PurchaseActivity : AbsBaseActivity(), BillingProcessor.IBillingHandler {
}
private fun restorePurchase() {
if (restorePurchaseAsyncTask != null) {
restorePurchaseAsyncTask!!.cancel(false)
}
restorePurchaseAsyncTask = RestorePurchaseAsyncTask(this).execute()
Toast.makeText(this, R.string.restoring_purchase, Toast.LENGTH_SHORT)
.show()
billingProcessor.loadOwnedPurchasesFromGoogleAsync(object :
BillingProcessor.IPurchasesResponseListener {
override fun onPurchasesSuccess() {
onPurchaseHistoryRestored()
}
override fun onPurchasesError() {
Toast.makeText(
this@PurchaseActivity,
R.string.could_not_restore_purchase,
Toast.LENGTH_SHORT
).show()
}
})
}
override fun onProductPurchased(productId: String, details: TransactionDetails?) {
override fun onProductPurchased(productId: String, details: PurchaseInfo?) {
Toast.makeText(this, R.string.thank_you, Toast.LENGTH_SHORT).show()
setResult(RESULT_OK)
}
@ -105,12 +109,6 @@ class PurchaseActivity : AbsBaseActivity(), BillingProcessor.IBillingHandler {
binding.purchaseButton.isEnabled = true
}
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (!billingProcessor.handleActivityResult(requestCode, resultCode, data)) {
super.onActivityResult(requestCode, resultCode, data)
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> finish()
@ -123,52 +121,6 @@ class PurchaseActivity : AbsBaseActivity(), BillingProcessor.IBillingHandler {
super.onDestroy()
}
private class RestorePurchaseAsyncTask(purchaseActivity: PurchaseActivity) :
AsyncTask<Void, Void, Boolean>() {
private val buyActivityWeakReference: WeakReference<PurchaseActivity> = WeakReference(
purchaseActivity
)
override fun onPreExecute() {
super.onPreExecute()
val purchaseActivity = buyActivityWeakReference.get()
if (purchaseActivity != null) {
Toast.makeText(purchaseActivity, R.string.restoring_purchase, Toast.LENGTH_SHORT)
.show()
} else {
cancel(false)
}
}
override fun doInBackground(vararg params: Void): Boolean? {
val purchaseActivity = buyActivityWeakReference.get()
if (purchaseActivity != null) {
return purchaseActivity.billingProcessor.loadOwnedPurchasesFromGoogle()
}
cancel(false)
return null
}
override fun onPostExecute(b: Boolean?) {
super.onPostExecute(b)
val purchaseActivity = buyActivityWeakReference.get()
if (purchaseActivity == null || b == null) {
return
}
if (b) {
purchaseActivity.onPurchaseHistoryRestored()
} else {
Toast.makeText(
purchaseActivity,
R.string.could_not_restore_purchase,
Toast.LENGTH_SHORT
).show()
}
}
}
companion object {
private const val TAG: String = "PurchaseActivity"
}

View File

@ -14,6 +14,7 @@
*/
package code.name.monkey.retromusic.activities
import android.content.Intent
import android.os.Bundle
import android.view.MenuItem
import androidx.navigation.NavController
@ -21,33 +22,33 @@ import androidx.navigation.NavDestination
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.VersionUtils
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsBaseActivity
import code.name.monkey.retromusic.activities.base.AbsThemeActivity
import code.name.monkey.retromusic.appshortcuts.DynamicShortcutManager
import code.name.monkey.retromusic.databinding.ActivitySettingsBinding
import code.name.monkey.retromusic.extensions.applyToolbar
import code.name.monkey.retromusic.extensions.extra
import code.name.monkey.retromusic.extensions.findNavController
import code.name.monkey.retromusic.extensions.surfaceColor
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.color.ColorCallback
class SettingsActivity : AbsBaseActivity(), ColorCallback {
class SettingsActivity : AbsThemeActivity(), ColorCallback, OnThemeChangedListener {
private lateinit var binding: ActivitySettingsBinding
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
val mSavedInstanceState = extra<Bundle>(TAG).value ?: savedInstanceState
super.onCreate(mSavedInstanceState)
setLightStatusbarAuto(surfaceColor())
binding = ActivitySettingsBinding.inflate(layoutInflater)
setContentView(binding.root)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setLightNavigationBar(true)
setupToolbar()
}
private fun setupToolbar() {
setTitle(R.string.action_settings)
applyToolbar(binding.toolbar)
val navController: NavController = findNavController(R.id.contentFrame)
navController.addOnDestinationChangedListener { _, _, _ ->
binding.toolbar.title =
binding.collapsingToolbarLayout.title =
navController.currentDestination?.let { getStringFromDestination(it) }
}
}
@ -63,6 +64,7 @@ class SettingsActivity : AbsBaseActivity(), ColorCallback {
R.id.personalizeSettingsFragment -> R.string.personalize
R.id.themeSettingsFragment -> R.string.general_settings_title
R.id.aboutActivity -> R.string.action_about
R.id.backup_fragment -> R.string.backup_restore_title
else -> R.id.action_settings
}
return getString(idRes)
@ -84,6 +86,28 @@ class SettingsActivity : AbsBaseActivity(), ColorCallback {
if (VersionUtils.hasNougatMR())
DynamicShortcutManager(this).updateDynamicShortcuts()
recreate()
restart()
}
override fun onThemeValuesChanged() {
restart()
}
private fun restart() {
val savedInstanceState = Bundle().apply {
onSaveInstanceState(this)
}
finish()
val intent = Intent(this, this::class.java).putExtra(TAG, savedInstanceState)
startActivity(intent)
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
}
companion object {
val TAG: String = SettingsActivity::class.java.simpleName
}
}
interface OnThemeChangedListener {
fun onThemeValuesChanged()
}

View File

@ -61,7 +61,6 @@ class ShareInstagramStory : AbsBaseActivity() {
binding = ActivityShareInstagramBinding.inflate(layoutInflater)
setContentView(binding.root)
setStatusbarColor(Color.TRANSPARENT)
setNavigationbarColor(Color.BLACK)
binding.toolbar.setBackgroundColor(Color.TRANSPARENT)
setSupportActionBar(binding.toolbar)

View File

@ -14,9 +14,7 @@
*/
package code.name.monkey.retromusic.activities
import android.content.Intent
import android.graphics.Paint
import android.os.AsyncTask
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
@ -27,6 +25,7 @@ import android.widget.TextView
import android.widget.Toast
import androidx.annotation.LayoutRes
import androidx.appcompat.widget.AppCompatImageView
import androidx.core.view.isVisible
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
@ -41,9 +40,8 @@ import code.name.monkey.retromusic.databinding.ActivityDonationBinding
import code.name.monkey.retromusic.extensions.textColorPrimary
import code.name.monkey.retromusic.extensions.textColorSecondary
import com.anjlab.android.iab.v3.BillingProcessor
import com.anjlab.android.iab.v3.PurchaseInfo
import com.anjlab.android.iab.v3.SkuDetails
import com.anjlab.android.iab.v3.TransactionDetails
import java.lang.ref.WeakReference
import java.util.*
class SupportDevelopmentActivity : AbsBaseActivity(), BillingProcessor.IBillingHandler {
@ -53,11 +51,9 @@ class SupportDevelopmentActivity : AbsBaseActivity(), BillingProcessor.IBillingH
companion object {
val TAG: String = SupportDevelopmentActivity::class.java.simpleName
const val DONATION_PRODUCT_IDS = R.array.donation_ids
private const val TEZ_REQUEST_CODE = 123
}
var billingProcessor: BillingProcessor? = null
private var skuDetailsLoadAsyncTask: AsyncTask<*, *, *>? = null
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
@ -75,12 +71,10 @@ class SupportDevelopmentActivity : AbsBaseActivity(), BillingProcessor.IBillingH
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityDonationBinding.inflate(layoutInflater)
setContentView(R.layout.activity_donation)
setContentView(binding.root)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
setLightNavigationBar(true)
setupToolbar()
@ -101,13 +95,35 @@ class SupportDevelopmentActivity : AbsBaseActivity(), BillingProcessor.IBillingH
}
private fun loadSkuDetails() {
if (skuDetailsLoadAsyncTask != null) {
skuDetailsLoadAsyncTask!!.cancel(false)
}
skuDetailsLoadAsyncTask = SkuDetailsLoadAsyncTask(this).execute()
binding.progressContainer.isVisible = true
binding.recyclerView.isVisible = false
val ids =
resources.getStringArray(DONATION_PRODUCT_IDS)
billingProcessor!!.getPurchaseListingDetailsAsync(
ArrayList(listOf(*ids)),
object : BillingProcessor.ISkuDetailsResponseListener {
override fun onSkuDetailsResponse(skuDetails: MutableList<SkuDetails>?) {
if (skuDetails == null || skuDetails.isEmpty()) {
binding.progressContainer.isVisible = false
return
}
binding.progressContainer.isVisible = false
binding.recyclerView.apply {
itemAnimator = DefaultItemAnimator()
layoutManager = GridLayoutManager(this@SupportDevelopmentActivity, 2)
adapter = SkuDetailsAdapter(this@SupportDevelopmentActivity, skuDetails)
isVisible = true
}
}
override fun onSkuDetailsError(error: String?) {
Log.e(TAG, error.toString())
}
})
}
override fun onProductPurchased(productId: String, details: TransactionDetails?) {
override fun onProductPurchased(productId: String, details: PurchaseInfo?) {
// loadSkuDetails();
Toast.makeText(this, R.string.thank_you, Toast.LENGTH_SHORT).show()
}
@ -121,68 +137,12 @@ class SupportDevelopmentActivity : AbsBaseActivity(), BillingProcessor.IBillingH
Toast.makeText(this, R.string.restored_previous_purchases, Toast.LENGTH_SHORT).show()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (!billingProcessor!!.handleActivityResult(requestCode, resultCode, data)) {
super.onActivityResult(requestCode, resultCode, data)
}
if (requestCode == TEZ_REQUEST_CODE) {
// Process based on the data in response.
// Log.d("result", data!!.getStringExtra("Status"))
}
}
override fun onDestroy() {
billingProcessor?.release()
skuDetailsLoadAsyncTask?.cancel(true)
super.onDestroy()
}
}
private class SkuDetailsLoadAsyncTask(supportDevelopmentActivity: SupportDevelopmentActivity) :
AsyncTask<Void, Void, List<SkuDetails>>() {
private val weakReference: WeakReference<SupportDevelopmentActivity> = WeakReference(
supportDevelopmentActivity
)
override fun onPreExecute() {
super.onPreExecute()
val supportDevelopmentActivity = weakReference.get() ?: return
supportDevelopmentActivity.binding.progressContainer.visibility = View.VISIBLE
supportDevelopmentActivity.binding.recyclerView.visibility = View.GONE
}
override fun doInBackground(vararg params: Void): List<SkuDetails>? {
val dialog = weakReference.get()
if (dialog != null) {
val ids =
dialog.resources.getStringArray(SupportDevelopmentActivity.DONATION_PRODUCT_IDS)
return dialog.billingProcessor!!.getPurchaseListingDetails(ArrayList(Arrays.asList(*ids)))
}
cancel(false)
return null
}
override fun onPostExecute(skuDetails: List<SkuDetails>?) {
super.onPostExecute(skuDetails)
val dialog = weakReference.get() ?: return
if (skuDetails == null || skuDetails.isEmpty()) {
dialog.binding.progressContainer.visibility = View.GONE
return
}
dialog.binding.progressContainer.visibility = View.GONE
dialog.binding.recyclerView.apply {
itemAnimator = DefaultItemAnimator()
layoutManager = GridLayoutManager(dialog, 2)
adapter = SkuDetailsAdapter(dialog, skuDetails)
visibility = View.VISIBLE
}
}
}
class SkuDetailsAdapter(
private var donationsDialog: SupportDevelopmentActivity,
objects: List<SkuDetails>
@ -223,7 +183,7 @@ class SkuDetailsAdapter(
viewHolder.title.text = skuDetails.title.replace("Music Player - MP3 Player - Retro", "")
.trim { it <= ' ' }
viewHolder.text.text = skuDetails.description
viewHolder.text.visibility = View.GONE
viewHolder.text.isVisible = false
viewHolder.price.text = skuDetails.priceText
viewHolder.image.setImageResource(getIcon(i))

View File

@ -1,128 +0,0 @@
package code.name.monkey.retromusic.activities;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.widget.NestedScrollView;
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;
import code.name.monkey.appthemehelper.util.MaterialValueHelper;
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper;
import code.name.monkey.retromusic.Constants;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.activities.base.AbsBaseActivity;
import code.name.monkey.retromusic.databinding.ActivityWhatsNewBinding;
import code.name.monkey.retromusic.extensions.ColorExtKt;
import code.name.monkey.retromusic.util.PreferenceUtil;
import code.name.monkey.retromusic.util.RetroUtil;
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 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);
ActivityWhatsNewBinding binding = ActivityWhatsNewBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setStatusbarColorAuto();
setNavigationbarColorAuto();
setTaskDescriptionColorAuto();
binding.toolbar.setBackgroundColor(ATHUtil.INSTANCE.resolveColor(this, R.attr.colorSurface));
binding.toolbar.setNavigationOnClickListener(v -> onBackPressed());
ToolbarContentTintHelper.colorBackButton(binding.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 cardBackgroundColor = colorToCSS(Color.parseColor(isDark ? "#353535" : "#ffffff"));
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;} h3 {color: %s;} .tag {color: %s;} div{background-color: %s;}",
backgroundColor,
contentColor,
textColor,
accentColorString,
accentColorString,
cardBackgroundColor))
.replace("{link-color}", colorToCSS(ThemeStore.Companion.accentColor(this)))
.replace(
"{link-color-active}",
colorToCSS(
ColorUtil.INSTANCE.lightenColor(ThemeStore.Companion.accentColor(this))));
binding.webView.loadData(changeLog, "text/html", "UTF-8");
} catch (Throwable e) {
binding.webView.loadData(
"<h1>Unable to load</h1><p>" + e.getLocalizedMessage() + "</p>", "text/html", "UTF-8");
}
setChangelogRead(this);
binding.tgFab.setOnClickListener(v -> RetroUtil.openUrl(this, Constants.TELEGRAM_CHANGE_LOG));
ColorExtKt.accentColor(binding.tgFab);
binding.tgFab.shrink();
binding.container.setOnScrollChangeListener((NestedScrollView.OnScrollChangeListener) (v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
int dy = scrollY - oldScrollY;
if (dy > 0) {
binding.tgFab.shrink();
} else if (dy < 0) {
binding.tgFab.extend();
}
});
}
}

View File

@ -0,0 +1,125 @@
package code.name.monkey.retromusic.activities
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.Color
import android.os.Bundle
import androidx.core.widget.NestedScrollView
import code.name.monkey.appthemehelper.ThemeStore.Companion.accentColor
import code.name.monkey.appthemehelper.util.ATHUtil.isWindowBackgroundDark
import code.name.monkey.appthemehelper.util.ATHUtil.resolveColor
import code.name.monkey.appthemehelper.util.ColorUtil.isColorLight
import code.name.monkey.appthemehelper.util.ColorUtil.lightenColor
import code.name.monkey.appthemehelper.util.MaterialValueHelper.getPrimaryTextColor
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.Constants
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsThemeActivity
import code.name.monkey.retromusic.databinding.ActivityWhatsNewBinding
import code.name.monkey.retromusic.extensions.accentColor
import code.name.monkey.retromusic.util.PreferenceUtil.lastVersion
import code.name.monkey.retromusic.util.RetroUtil
import java.io.BufferedReader
import java.io.InputStreamReader
import java.nio.charset.StandardCharsets
import java.util.*
class WhatsNewActivity : AbsThemeActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityWhatsNewBinding.inflate(layoutInflater)
setContentView(binding.root)
setLightStatusbarAuto(resolveColor(this, R.attr.colorSurface))
setTaskDescriptionColorAuto()
binding.toolbar.setNavigationOnClickListener { onBackPressed() }
ToolbarContentTintHelper.colorBackButton(binding.toolbar)
try {
val buf = StringBuilder()
val json = assets.open("retro-changelog.html")
val br = BufferedReader(InputStreamReader(json, StandardCharsets.UTF_8))
var str: String?
while (br.readLine().also { str = it } != null) {
buf.append(str)
}
br.close()
// Inject color values for WebView body background and links
val isDark = isWindowBackgroundDark(this)
val accentColor = accentColor(this)
val backgroundColor = colorToCSS(
resolveColor(
this,
R.attr.colorSurface,
Color.parseColor(if (isDark) "#424242" else "#ffffff")
)
)
val contentColor = colorToCSS(Color.parseColor(if (isDark) "#ffffff" else "#000000"))
val textColor = colorToCSS(Color.parseColor(if (isDark) "#60FFFFFF" else "#80000000"))
val accentColorString = colorToCSS(accentColor(this))
val cardBackgroundColor =
colorToCSS(Color.parseColor(if (isDark) "#353535" else "#ffffff"))
val accentTextColor = colorToCSS(
getPrimaryTextColor(
this, isColorLight(accentColor)
)
)
val changeLog = buf.toString()
.replace(
"{style-placeholder}",
"body { background-color: $backgroundColor; color: $contentColor; } li {color: $textColor;} h3 {color: $accentColorString;} .tag {background-color: $accentColorString; color: $accentTextColor; } div{background-color: $cardBackgroundColor;}"
)
.replace("{link-color}", colorToCSS(accentColor(this)))
.replace(
"{link-color-active}",
colorToCSS(
lightenColor(accentColor(this))
)
)
binding.webView.loadData(changeLog, "text/html", "UTF-8")
} catch (e: Throwable) {
binding.webView.loadData(
"<h1>Unable to load</h1><p>" + e.localizedMessage + "</p>", "text/html", "UTF-8"
)
}
setChangelogRead(this)
binding.tgFab.setOnClickListener {
RetroUtil.openUrl(
this,
Constants.TELEGRAM_CHANGE_LOG
)
}
binding.tgFab.accentColor()
binding.tgFab.shrink()
binding.container.setOnScrollChangeListener { _: NestedScrollView?, _: Int, scrollY: Int, _: Int, oldScrollY: Int ->
val dy = scrollY - oldScrollY
if (dy > 0) {
binding.tgFab.shrink()
} else if (dy < 0) {
binding.tgFab.extend()
}
}
}
companion object {
private fun colorToCSS(color: Int): String {
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 fun setChangelogRead(context: Context) {
try {
val pInfo = context.packageManager.getPackageInfo(context.packageName, 0)
val currentVersion = pInfo.versionCode
lastVersion = currentVersion
} catch (e: PackageManager.NameNotFoundException) {
e.printStackTrace()
}
}
}
}

View File

@ -14,13 +14,17 @@
*/
package code.name.monkey.retromusic.activities.base
import android.animation.Animator
import android.content.res.ColorStateList
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.core.animation.doOnEnd
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.commit
@ -28,14 +32,13 @@ import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.RetroBottomSheetBehavior
import code.name.monkey.retromusic.databinding.ActivityMainContentBinding
import code.name.monkey.retromusic.databinding.SlidingMusicPanelLayoutBinding
import code.name.monkey.retromusic.extensions.*
import code.name.monkey.retromusic.fragments.LibraryViewModel
import code.name.monkey.retromusic.fragments.MiniPlayerFragment
import code.name.monkey.retromusic.fragments.NowPlayingScreen
import code.name.monkey.retromusic.fragments.NowPlayingScreen.*
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.other.MiniPlayerFragment
import code.name.monkey.retromusic.fragments.player.adaptive.AdaptiveFragment
import code.name.monkey.retromusic.fragments.player.blur.BlurPlayerFragment
import code.name.monkey.retromusic.fragments.player.card.CardFragment
@ -53,10 +56,11 @@ import code.name.monkey.retromusic.fragments.player.peak.PeakPlayerFragment
import code.name.monkey.retromusic.fragments.player.plain.PlainPlayerFragment
import code.name.monkey.retromusic.fragments.player.simple.SimplePlayerFragment
import code.name.monkey.retromusic.fragments.player.tiny.TinyPlayerFragment
import code.name.monkey.retromusic.fragments.queue.PlayingQueueFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.model.CategoryInfo
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.views.BottomNavigationBarTinted
import code.name.monkey.retromusic.util.RetroUtil
import com.google.android.material.bottomsheet.BottomSheetBehavior.*
import org.koin.androidx.viewmodel.ext.android.viewModel
@ -66,15 +70,14 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
var fromNotification: Boolean = false
}
private var windowInsets: WindowInsetsCompat? = null
private var bottomNavAnimator: Animator? = null
protected val libraryViewModel by viewModel<LibraryViewModel>()
private lateinit var bottomSheetBehavior: RetroBottomSheetBehavior<FrameLayout>
private var playerFragment: AbsPlayerFragment? = null
private var miniPlayerFragment: MiniPlayerFragment? = null
private var nowPlayingScreen: NowPlayingScreen? = null
private var navigationBarColor: Int = 0
private var taskColor: Int = 0
private var lightStatusBar: Boolean = false
private var lightNavigationBar: Boolean = false
private var paletteColor: Int = Color.WHITE
protected abstract fun createContentView(): SlidingMusicPanelLayoutBinding
private val panelState: Int
@ -82,11 +85,8 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
private lateinit var binding: SlidingMusicPanelLayoutBinding
private val bottomSheetCallbackList = object : BottomSheetCallback() {
override fun onSlide(bottomSheet: View, slideOffset: Float) {
setMiniPlayerAlphaProgress(slideOffset)
binding.dimBackground.show()
binding.dimBackground.alpha = slideOffset
}
override fun onStateChanged(bottomSheet: View, newState: Int) {
@ -96,15 +96,14 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
}
STATE_COLLAPSED -> {
onPanelCollapsed()
binding.dimBackground.hide()
if (fromNotification) {
hideBottomBar(MusicPlayerRemote.playingQueue.isEmpty())
hideBottomSheet(MusicPlayerRemote.playingQueue.isEmpty())
fromNotification = false
}
}
STATE_SETTLING, STATE_DRAGGING -> {
if (fromNotification) {
getBottomNavigationView().isVisible = true
bottomNavigationView.isVisible = true
}
}
else -> {
@ -120,23 +119,30 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
super.onCreate(savedInstanceState)
binding = createContentView()
setContentView(binding.root)
ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { _, insets ->
windowInsets = insets
insets
}
bottomNavigationView.drawAboveSystemBarsWithPadding()
if (RetroUtil.isLandscape()) {
binding.slidingPanel.drawAboveSystemBarsWithPadding(true)
}
binding.fragmentContainer.addBottomInsets()
chooseFragmentForTheme()
setupSlidingUpPanel()
setupBottomSheet()
updateColor()
val themeColor = resolveColor(android.R.attr.windowBackground, Color.GRAY)
binding.dimBackground.setBackgroundColor(ColorUtil.withAlpha(themeColor, 0.5f))
binding.dimBackground.setOnClickListener {
println("dimBackground")
collapsePanel()
}
binding.slidingPanel.backgroundTintList = ColorStateList.valueOf(darkAccentColor())
bottomNavigationView.backgroundTintList = ColorStateList.valueOf(darkAccentColor())
}
private fun setupBottomSheet() {
bottomSheetBehavior = from(binding.slidingPanel) as RetroBottomSheetBehavior
bottomSheetBehavior.addBottomSheetCallback(bottomSheetCallbackList)
bottomSheetBehavior.maxWidth = resources.displayMetrics.widthPixels
bottomSheetBehavior.isHideable = false
setMiniPlayerAlphaProgress(0F)
}
override fun onResume() {
@ -155,41 +161,37 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
}
protected fun wrapSlidingMusicPanel(): SlidingMusicPanelLayoutBinding {
val slidingMusicPanelLayoutBinding =
SlidingMusicPanelLayoutBinding.inflate(layoutInflater)
val contentContainer: ViewGroup =
slidingMusicPanelLayoutBinding.mainContentFrame
ActivityMainContentBinding.inflate(layoutInflater, contentContainer, true)
return slidingMusicPanelLayoutBinding
return SlidingMusicPanelLayoutBinding.inflate(layoutInflater)
}
fun collapsePanel() {
bottomSheetBehavior.state = STATE_COLLAPSED
setMiniPlayerAlphaProgress(0f)
}
fun expandPanel() {
bottomSheetBehavior.state = STATE_EXPANDED
setMiniPlayerAlphaProgress(1f)
}
private fun setMiniPlayerAlphaProgress(progress: Float) {
if (progress < 0) return
val alpha = 1 - progress
miniPlayerFragment?.view?.alpha = alpha
miniPlayerFragment?.view?.alpha = 1 - (progress / 0.2F)
miniPlayerFragment?.view?.visibility = if (alpha == 0f) View.GONE else View.VISIBLE
binding.bottomNavigationView.translationY = progress * 500
binding.bottomNavigationView.alpha = alpha
binding.playerFragmentContainer.alpha = (progress - 0.2F) / 0.2F
}
open fun onPanelCollapsed() {
setMiniPlayerAlphaProgress(0F)
// restore values
super.setLightStatusbar(lightStatusBar)
super.setLightStatusbarAuto(surfaceColor())
super.setLightNavigationAuto()
super.setTaskDescriptionColor(taskColor)
super.setNavigationbarColor(navigationBarColor)
super.setLightNavigationBar(lightNavigationBar)
}
open fun onPanelExpanded() {
setMiniPlayerAlphaProgress(1F)
onPaletteColorChanged()
}
@ -214,9 +216,7 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
})
}
fun getBottomNavigationView(): BottomNavigationBarTinted {
return binding.bottomNavigationView
}
val bottomNavigationView get() = binding.bottomNavigationView
override fun onServiceConnected() {
super.onServiceConnected()
@ -225,15 +225,19 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
binding.slidingPanel.viewTreeObserver.removeOnGlobalLayoutListener(this)
hideBottomBar(false)
hideBottomSheet(false)
}
})
} // don't call hideBottomBar(true) here as it causes a bug with the SlidingUpPanelLayout
} // don't call hideBottomSheet(true) here as it causes a bug with the SlidingUpPanelLayout
}
override fun onQueueChanged() {
super.onQueueChanged()
hideBottomBar(MusicPlayerRemote.playingQueue.isEmpty())
// Mini player should be hidden in Playing Queue
// it may pop up if hideBottomSheet is called
if (currentFragment(R.id.fragment_container) !is PlayingQueueFragment) {
hideBottomSheet(MusicPlayerRemote.playingQueue.isEmpty())
}
}
override fun onBackPressed() {
@ -260,13 +264,10 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
} else if (nowPlayingScreen == Card || nowPlayingScreen == Blur || nowPlayingScreen == BlurCard) {
super.setLightStatusbar(false)
super.setLightNavigationBar(true)
super.setNavigationbarColor(Color.BLACK)
} else if (nowPlayingScreen == Color || nowPlayingScreen == Tiny || nowPlayingScreen == Gradient) {
super.setNavigationbarColor(paletteColor)
super.setLightNavigationBar(isColorLight)
super.setLightStatusbar(isColorLight)
} else if (nowPlayingScreen == Full) {
super.setNavigationbarColor(paletteColor)
super.setLightNavigationBar(isColorLight)
super.setLightStatusbar(false)
} else if (nowPlayingScreen == Classic) {
@ -287,27 +288,6 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
}
}
override fun setLightStatusbar(enabled: Boolean) {
lightStatusBar = enabled
if (panelState == STATE_COLLAPSED) {
super.setLightStatusbar(enabled)
}
}
override fun setLightNavigationBar(enabled: Boolean) {
lightNavigationBar = enabled
if (panelState == STATE_COLLAPSED) {
super.setLightNavigationBar(enabled)
}
}
override fun setNavigationbarColor(color: Int) {
navigationBarColor = color
if (panelState == STATE_COLLAPSED) {
super.setNavigationbarColor(color)
}
}
override fun setTaskDescriptionColor(color: Int) {
taskColor = color
if (panelState == STATE_COLLAPSED) {
@ -337,38 +317,57 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
})
}
fun setBottomBarVisibility(visible: Boolean) {
fun setBottomNavVisibility(visible: Boolean, animate: Boolean = false) {
binding.bottomNavigationView.isVisible = visible
hideBottomBar(MusicPlayerRemote.playingQueue.isEmpty())
hideBottomSheet(MusicPlayerRemote.playingQueue.isEmpty(), animate)
}
private fun hideBottomBar(hide: Boolean) {
fun hideBottomSheet(hide: Boolean, animate: Boolean = false) {
val heightOfBar =
if (MusicPlayerRemote.isCasting) dip(R.dimen.cast_mini_player_height) else dip(R.dimen.mini_player_height)
val heightOfBarWithTabs =
if (MusicPlayerRemote.isCasting) dip(R.dimen.mini_cast_player_height_expanded) else dip(
R.dimen.mini_player_height_expanded
)
windowInsets.safeGetBottomInsets() +
if (MusicPlayerRemote.isCasting) dip(R.dimen.cast_mini_player_height) else dip(R.dimen.mini_player_height)
val heightOfBarWithTabs = heightOfBar + dip(R.dimen.bottom_nav_height)
val isVisible = binding.bottomNavigationView.isVisible
if (hide) {
bottomSheetBehavior.isHideable = true
bottomSheetBehavior.peekHeight = 0
bottomSheetBehavior.peekHeight = -windowInsets.safeGetBottomInsets()
bottomSheetBehavior.state = STATE_COLLAPSED
libraryViewModel.setFabMargin(if (isVisible) dip(R.dimen.bottom_nav_height) else 0)
ViewCompat.setElevation(binding.slidingPanel, 0f)
ViewCompat.setElevation(binding.bottomNavigationView, 10f)
collapsePanel()
} else {
if (MusicPlayerRemote.playingQueue.isNotEmpty()) {
bottomSheetBehavior.isHideable = false
ViewCompat.setElevation(binding.slidingPanel, 10f)
ViewCompat.setElevation(binding.bottomNavigationView, 10f)
if (isVisible) {
println("List")
if (bottomSheetBehavior.state != STATE_EXPANDED)
getBottomNavigationView().translateYAnimate(0F)
bottomSheetBehavior.peekHeightAnimate(heightOfBarWithTabs)
if (animate) {
bottomNavAnimator?.end()
bottomSheetBehavior.peekHeightAnimate(heightOfBarWithTabs)
bottomNavAnimator = binding.bottomNavigationView.translateYAnimate(0F)
} else {
bottomSheetBehavior.peekHeight = heightOfBarWithTabs
binding.bottomNavigationView.translationY = 0F
}
binding.bottomNavigationView.bringToFront()
libraryViewModel.setFabMargin(heightOfBarWithTabs - 2 * windowInsets.safeGetBottomInsets())
} else {
println("Details")
bottomSheetBehavior.peekHeight = heightOfBar
if (animate) {
bottomSheetBehavior.peekHeightAnimate(heightOfBar)
bottomNavAnimator?.end()
bottomNavAnimator =
bottomNavigationView.translateYAnimate(dip(R.dimen.bottom_nav_height).toFloat())
bottomNavAnimator?.doOnEnd {
binding.slidingPanel.bringToFront()
}
} else {
bottomSheetBehavior.peekHeight = heightOfBar
binding.bottomNavigationView.translationY =
dip(R.dimen.bottom_nav_height).toFloat()
binding.slidingPanel.bringToFront()
}
libraryViewModel.setFabMargin(heightOfBar - 2 * windowInsets.safeGetBottomInsets())
}
}
}
@ -376,7 +375,7 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
fun setAllowDragging(allowDragging: Boolean) {
bottomSheetBehavior.setAllowDragging(allowDragging)
hideBottomBar(false)
hideBottomSheet(false)
}
private fun chooseFragmentForTheme() {

View File

@ -33,9 +33,11 @@ import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.VersionUtils
import code.name.monkey.retromusic.LanguageContextWrapper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.extensions.surfaceColor
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.RetroUtil
import code.name.monkey.retromusic.util.theme.ThemeManager
import com.google.android.material.color.DynamicColors
import java.util.*
abstract class AbsThemeActivity : ATHToolbarActivity(), Runnable {
@ -49,12 +51,22 @@ abstract class AbsThemeActivity : ATHToolbarActivity(), Runnable {
setImmersiveFullscreen()
registerSystemUiVisibility()
toggleScreenOn()
//MaterialDialogsUtil.updateMaterialDialogsThemeSingleton(this)
setDrawUnderNavigationBar()
setLightNavigationAuto()
setLightStatusbarAuto(surfaceColor())
}
private fun updateTheme() {
setTheme(ThemeManager.getThemeResValue(this))
setDefaultNightMode(ThemeManager.getNightMode(this))
// Apply dynamic colors to activity if enabled
if (PreferenceUtil.materialYou) {
DynamicColors.applyIfAvailable(
this,
com.google.android.material.R.style.ThemeOverlay_Material3_DynamicColors_DayNight
)
}
}
private fun toggleScreenOn() {
@ -91,7 +103,7 @@ abstract class AbsThemeActivity : ATHToolbarActivity(), Runnable {
RetroUtil.setAllowDrawUnderStatusBar(window)
}
fun setDrawUnderNavigationBar() {
private fun setDrawUnderNavigationBar() {
RetroUtil.setAllowDrawUnderNavigationBar(window)
}
@ -148,6 +160,10 @@ abstract class AbsThemeActivity : ATHToolbarActivity(), Runnable {
setNavigationbarColor(ATHUtil.resolveColor(this, R.attr.colorSurface))
}
fun setLightNavigationAuto() {
ATH.setLightNavigationbarAuto(this, surfaceColor())
}
open fun setLightStatusbar(enabled: Boolean) {
ATH.setLightStatusbar(this, enabled)
}

View File

@ -30,7 +30,6 @@ import androidx.annotation.StringDef
import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.MaterialUtil
import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
@ -74,12 +73,9 @@ open class BugReportActivity : AbsThemeActivity() {
private var deviceInfo: DeviceInfo? = null
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
binding = ActivityBugReportBinding.inflate(layoutInflater)
setContentView(binding.root)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
initViews()
@ -92,8 +88,6 @@ open class BugReportActivity : AbsThemeActivity() {
private fun initViews() {
val accentColor = ThemeStore.accentColor(this)
val primaryColor = ATHUtil.resolveColor(this, R.attr.colorSurface)
binding.toolbar.setBackgroundColor(primaryColor)
setSupportActionBar(binding.toolbar)
ToolbarContentTintHelper.colorBackButton(binding.toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)

View File

@ -196,7 +196,6 @@ abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() {
_binding = bindingInflater.invoke(layoutInflater)
setContentView(binding.root)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
saveFab = findViewById(R.id.saveTags)

View File

@ -29,10 +29,10 @@ import android.view.LayoutInflater
import android.widget.ImageView
import android.widget.Toast
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.MaterialUtil
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.ActivityAlbumTagEditorBinding
import code.name.monkey.retromusic.extensions.appHandleColor
import code.name.monkey.retromusic.extensions.setTint
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
import code.name.monkey.retromusic.model.ArtworkInfo
@ -62,8 +62,6 @@ class AlbumTagEditorActivity : AbsTagEditorActivity<ActivityAlbumTagEditorBindin
}
override fun loadImageFromFile(selectedFile: Uri?) {
GlideApp.with(this@AlbumTagEditorActivity).asBitmapPalette().load(selectedFile)
.diskCacheStrategy(DiskCacheStrategy.NONE).skipMemoryCache(true)
.into(object : ImageViewTarget<BitmapPaletteWrapper>(binding.editorImage) {
@ -119,10 +117,10 @@ class AlbumTagEditorActivity : AbsTagEditorActivity<ActivityAlbumTagEditorBindin
private fun setUpViews() {
fillViewsWithFileTags()
MaterialUtil.setTint(binding.yearContainer, false)
MaterialUtil.setTint(binding.genreContainer, false)
MaterialUtil.setTint(binding.albumTitleContainer, false)
MaterialUtil.setTint(binding.albumArtistContainer, false)
binding.yearContainer.setTint(false)
binding.genreContainer.setTint(false)
binding.albumTitleContainer.setTint(false)
binding.albumArtistContainer.setTint(false)
binding.albumText.appHandleColor().addTextChangedListener(this)
binding.albumArtistText.appHandleColor().addTextChangedListener(this)

View File

@ -22,10 +22,10 @@ import android.text.TextWatcher
import android.view.LayoutInflater
import android.widget.ImageView
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.MaterialUtil
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.ActivitySongTagEditorBinding
import code.name.monkey.retromusic.extensions.appHandleColor
import code.name.monkey.retromusic.extensions.setTint
import code.name.monkey.retromusic.repository.SongRepository
import org.jaudiotagger.tag.FieldKey
import org.koin.android.ext.android.inject
@ -50,15 +50,15 @@ class SongTagEditorActivity : AbsTagEditorActivity<ActivitySongTagEditorBinding>
@SuppressLint("ClickableViewAccessibility")
private fun setUpViews() {
fillViewsWithFileTags()
MaterialUtil.setTint(binding.songTextContainer, false)
MaterialUtil.setTint(binding.composerContainer, false)
MaterialUtil.setTint(binding.albumTextContainer, false)
MaterialUtil.setTint(binding.artistContainer, false)
MaterialUtil.setTint(binding.albumArtistContainer, false)
MaterialUtil.setTint(binding.yearContainer, false)
MaterialUtil.setTint(binding.genreContainer, false)
MaterialUtil.setTint(binding.trackNumberContainer, false)
MaterialUtil.setTint(binding.lyricsContainer, false)
binding.songTextContainer.setTint(false)
binding.composerContainer.setTint(false)
binding.albumTextContainer.setTint(false)
binding.artistContainer.setTint(false)
binding.albumArtistContainer.setTint(false)
binding.yearContainer.setTint(false)
binding.genreContainer.setTint(false)
binding.trackNumberContainer.setTint(false)
binding.lyricsContainer.setTint(false)
binding.songText.appHandleColor().addTextChangedListener(this)
binding.albumText.appHandleColor().addTextChangedListener(this)

View File

@ -40,7 +40,7 @@ public class CategoryInfoAdapter extends RecyclerView.Adapter<CategoryInfoAdapte
implements SwipeAndDragHelper.ActionCompletionContract {
private List<CategoryInfo> categoryInfos;
private ItemTouchHelper touchHelper;
private final ItemTouchHelper touchHelper;
public CategoryInfoAdapter() {
SwipeAndDragHelper swipeAndDragHelper = new SwipeAndDragHelper(this);
@ -128,15 +128,15 @@ public class CategoryInfoAdapter extends RecyclerView.Adapter<CategoryInfoAdapte
}
static class ViewHolder extends RecyclerView.ViewHolder {
private MaterialCheckBox checkBox;
private View dragView;
private TextView title;
private final MaterialCheckBox checkBox;
private final View dragView;
private final TextView title;
ViewHolder(View view) {
super(view);
checkBox = view.findViewById(R.id.checkbox);
checkBox.setButtonTintList(
ColorStateList.valueOf(ThemeStore.Companion.accentColor(checkBox.getContext())));
ColorStateList.valueOf(ThemeStore.Companion.accentColor(checkBox.getContext())));
title = view.findViewById(R.id.title);
dragView = view.findViewById(R.id.drag_view);
}

View File

@ -127,8 +127,8 @@ class SearchAdapter(
val artist = dataSet[position] as Artist
holder.title?.text = artist.name
holder.text?.text = MusicUtil.getArtistInfoString(activity, artist)
GlideApp.with(activity).asDrawable().artistImageOptions(artist).load(artist)
.into(holder.image!!)
GlideApp.with(activity).asDrawable().artistImageOptions(artist).load(
RetroGlideExtension.getArtistModel(artist)).into(holder.image!!)
}
else -> {
holder.title?.text = dataSet[position].toString()

View File

@ -40,7 +40,7 @@ import kotlin.math.log10
import kotlin.math.pow
class SongFileAdapter(
private val activity: AppCompatActivity,
override val activity: AppCompatActivity,
private var dataSet: List<File>,
private val itemLayoutRes: Int,
private val iCallbacks: ICallbacks?,

View File

@ -39,7 +39,7 @@ import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import me.zhanghai.android.fastscroll.PopupTextProvider
open class AlbumAdapter(
val activity: FragmentActivity,
override val activity: FragmentActivity,
var dataSet: List<Album>,
var itemLayoutRes: Int,
iCabHolder: ICabHolder?,
@ -68,7 +68,7 @@ open class AlbumAdapter(
return ViewHolder(view)
}
private fun getAlbumTitle(album: Album): String? {
private fun getAlbumTitle(album: Album): String {
return album.title
}
@ -88,7 +88,13 @@ open class AlbumAdapter(
holder.itemView.isActivated = isChecked
holder.title?.text = getAlbumTitle(album)
holder.text?.text = getAlbumText(album)
ViewCompat.setTransitionName(holder.image!!, album.id.toString())
// Check if imageContainer exists so we can have a smooth transition without
// CardView clipping, if it doesn't exist in current layout set transition name to image instead.
if (holder.imageContainer != null) {
ViewCompat.setTransitionName(holder.imageContainer!!, album.id.toString())
} else {
ViewCompat.setTransitionName(holder.image!!, album.id.toString())
}
loadAlbumCover(album, holder)
}
@ -130,7 +136,7 @@ open class AlbumAdapter(
}
override fun getName(album: Album): String {
return album.title!!
return album.title
}
override fun onMultipleItemAction(
@ -177,7 +183,7 @@ open class AlbumAdapter(
toggleChecked(layoutPosition)
} else {
image?.let {
listener?.onAlbumClick(dataSet[layoutPosition].id, it)
listener?.onAlbumClick(dataSet[layoutPosition].id, imageContainer ?: it)
}
}
}

View File

@ -24,6 +24,7 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.lifecycleScope
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.MainActivity
import code.name.monkey.retromusic.fragments.AlbumCoverStyle
import code.name.monkey.retromusic.fragments.NowPlayingScreen.*
import code.name.monkey.retromusic.glide.GlideApp
@ -35,6 +36,7 @@ import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.NavigationUtil
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -89,6 +91,7 @@ class AlbumCoverPagerAdapter(
private lateinit var song: Song
private var colorReceiver: ColorReceiver? = null
private var request: Int = 0
private val mainActivity get() = activity as MainActivity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -106,7 +109,9 @@ class AlbumCoverPagerAdapter(
ViewCompat.setTransitionName(view, "lyrics")
albumCover = view.findViewById(R.id.player_image)
view.setOnClickListener {
showLyricsDialog()
if (mainActivity.getBottomSheetBehavior().state == STATE_EXPANDED) {
showLyricsDialog()
}
}
return view
}

View File

@ -42,7 +42,7 @@ import me.zhanghai.android.fastscroll.PopupTextProvider
import java.util.*
class ArtistAdapter(
val activity: FragmentActivity,
override val activity: FragmentActivity,
var dataSet: List<Artist>,
var itemLayoutRes: Int,
val ICabHolder: ICabHolder?,
@ -85,12 +85,12 @@ class ArtistAdapter(
holder.itemView.isActivated = isChecked
holder.title?.text = artist.name
holder.text?.hide()
holder.image?.let {
if (PreferenceUtil.albumArtistsOnly) {
ViewCompat.setTransitionName(it, artist.name)
} else {
ViewCompat.setTransitionName(it, artist.id.toString())
}
val transitionName =
if (PreferenceUtil.albumArtistsOnly) artist.name else artist.id.toString()
if (holder.imageContainer != null) {
ViewCompat.setTransitionName(holder.imageContainer!!, transitionName)
} else {
ViewCompat.setTransitionName(holder.image!!, transitionName)
}
loadArtistImage(artist, holder)
}
@ -169,9 +169,9 @@ class ArtistAdapter(
val artist = dataSet[layoutPosition]
image?.let {
if (PreferenceUtil.albumArtistsOnly && IAlbumArtistClickListener != null) {
IAlbumArtistClickListener.onAlbumArtist(artist.name, it)
IAlbumArtistClickListener.onAlbumArtist(artist.name, imageContainer ?: it)
} else {
IArtistClickListener.onArtist(artist.id, it)
IArtistClickListener.onArtist(artist.id, imageContainer ?: it)
}
}
}

View File

@ -0,0 +1,66 @@
package code.name.monkey.retromusic.adapter.backup
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.PopupMenu
import android.widget.TextView
import androidx.appcompat.widget.AppCompatImageView
import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.retromusic.R
import java.io.File
class BackupAdapter(
val activity: FragmentActivity,
var dataSet: MutableList<File>,
val backupClickedListener: BackupClickedListener
) : RecyclerView.Adapter<BackupAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
LayoutInflater.from(activity).inflate(R.layout.item_list_card, parent, false)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.title.text = dataSet[position].nameWithoutExtension
}
override fun getItemCount(): Int = dataSet.size
fun swapDataset(dataSet: List<File>) {
this.dataSet = ArrayList(dataSet)
notifyDataSetChanged()
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val title: TextView = itemView.findViewById(R.id.title)
val menu: AppCompatImageView = itemView.findViewById(R.id.menu)
init {
menu.setOnClickListener { view ->
val popupMenu = PopupMenu(activity, view)
popupMenu.inflate(R.menu.menu_backup)
popupMenu.setOnMenuItemClickListener { menuItem ->
return@setOnMenuItemClickListener backupClickedListener.onBackupMenuClicked(
dataSet[bindingAdapterPosition],
menuItem
)
}
popupMenu.show()
}
itemView.setOnClickListener {
backupClickedListener.onBackupClicked(dataSet[bindingAdapterPosition])
}
}
}
interface BackupClickedListener {
fun onBackupClicked(file: File)
fun onBackupMenuClicked(file: File, menuItem: MenuItem): Boolean
}
}

View File

@ -1,182 +0,0 @@
package code.name.monkey.retromusic.adapter.base;
import android.annotation.SuppressLint;
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.appcompat.widget.AppCompatTextView;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.RecyclerView;
import com.afollestad.materialcab.MaterialCab;
import java.util.ArrayList;
import java.util.List;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.interfaces.ICabHolder;
public abstract class AbsMultiSelectAdapter<V extends RecyclerView.ViewHolder, I>
extends RecyclerView.Adapter<V> implements MaterialCab.Callback {
@Nullable
private final ICabHolder ICabHolder;
private final Context context;
private MaterialCab cab;
private final List<I> checked;
private int menuRes;
private AppCompatTextView dummyText;
private int oldSize = 0;
public AbsMultiSelectAdapter(
@NonNull Context context, @Nullable ICabHolder ICabHolder, @MenuRes int menuRes) {
this.ICabHolder = ICabHolder;
checked = new ArrayList<>();
this.menuRes = menuRes;
this.context = context;
}
@Override
public boolean onCabCreated(MaterialCab materialCab, Menu menu) {
playCreateAnim(materialCab);
createDummyTextView();
return true;
}
@Override
public boolean onCabFinished(MaterialCab materialCab) {
clearChecked();
cab.getToolbar().removeView(dummyText);
oldSize = 0;
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;
}
protected void checkAll() {
if (ICabHolder != null) {
checked.clear();
for (int i = 0; i < getItemCount(); i++) {
I identifier = getIdentifier(i);
if (identifier != null) {
checked.add(identifier);
}
}
notifyDataSetChanged();
updateCab();
}
}
@Nullable
protected abstract I getIdentifier(int position);
protected String getName(I object) {
return object.toString();
}
protected boolean isChecked(I identifier) {
return checked.contains(identifier);
}
protected boolean isInQuickSelectMode() {
return cab != null && cab.isActive();
}
protected abstract void onMultipleItemAction(MenuItem menuItem, List<I> selection);
protected void setMultiSelectMenuRes(@MenuRes int menuRes) {
this.menuRes = menuRes;
}
protected boolean toggleChecked(final int position) {
if (ICabHolder != null) {
I identifier = getIdentifier(position);
if (identifier == null) {
return false;
}
if (!checked.remove(identifier)) {
checked.add(identifier);
}
notifyItemChanged(position);
updateCab();
return true;
}
return false;
}
private void clearChecked() {
checked.clear();
notifyDataSetChanged();
}
@SuppressLint({"StringFormatInvalid", "StringFormatMatches"})
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(context.getString(R.string.x_selected, size));
if (oldSize == 0) {
cab.getToolbar().addView(dummyText);
}
} else {
AppCompatTextView title = (AppCompatTextView) cab.getToolbar().getChildAt(2);
dummyText.setText(title.getText());
title.setAlpha(0);
cab.setTitle(context.getString(R.string.x_selected, size));
dummyText.setTranslationX(title.getLeft() - dummyText.getLeft());
dummyText.setAlpha(1);
dummyText.setTranslationY(0);
if (oldSize > size) {
title.setTranslationY(40);
dummyText.animate().translationY(-40).alpha(0.0F).setDuration(300).start();
} else {
title.setTranslationY(-40);
dummyText.animate().translationY(40).alpha(0.0F).setDuration(300).start();
}
title.animate().translationY(0).alpha(1.0F).setDuration(300).start();
}
oldSize = size;
}
}
private void playCreateAnim(MaterialCab materialCab) {
Toolbar cabToolbar = materialCab.getToolbar();
int height = context.getResources().getDimensionPixelSize(R.dimen.toolbar_height);
cabToolbar.setTranslationY(-height);
cabToolbar.animate().translationYBy(height).setDuration(300).start();
}
private void createDummyTextView() {
if (dummyText != null) return;
dummyText = new AppCompatTextView(context);
dummyText.setSingleLine();
dummyText.setTextAppearance(context, R.style.ToolbarTextAppearanceNormal);
Toolbar.LayoutParams l1 = new Toolbar.LayoutParams(Toolbar.LayoutParams.WRAP_CONTENT, Toolbar.LayoutParams.WRAP_CONTENT);
dummyText.setLayoutParams(l1);
}
}

View File

@ -0,0 +1,123 @@
package code.name.monkey.retromusic.adapter.base
import android.annotation.SuppressLint
import android.graphics.Color
import android.view.Menu
import android.view.MenuItem
import androidx.annotation.MenuRes
import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.interfaces.ICabCallback
import code.name.monkey.retromusic.interfaces.ICabHolder
import code.name.monkey.retromusic.util.RetroColorUtil
import com.afollestad.materialcab.attached.AttachedCab
import com.afollestad.materialcab.attached.destroy
import com.afollestad.materialcab.attached.isActive
import java.util.*
abstract class AbsMultiSelectAdapter<V : RecyclerView.ViewHolder?, I>(
open val activity: FragmentActivity, private val ICabHolder: ICabHolder?, @MenuRes menuRes: Int
) : RecyclerView.Adapter<V>(), ICabCallback {
private var cab: AttachedCab? = null
private val checked: MutableList<I>
private var menuRes: Int
override fun onCabCreated(cab: AttachedCab, menu: Menu): Boolean {
activity.window.statusBarColor =
RetroColorUtil.shiftBackgroundColor(ATHUtil.resolveColor(activity, R.attr.colorSurface))
return true
}
override fun onCabFinished(cab: AttachedCab): Boolean {
clearChecked()
activity.window.statusBarColor = Color.TRANSPARENT
return true
}
override fun onCabItemClicked(item: MenuItem): Boolean {
if (item.itemId == R.id.action_multi_select_adapter_check_all) {
checkAll()
} else {
onMultipleItemAction(item, ArrayList(checked))
cab?.destroy()
clearChecked()
}
return true
}
private fun checkAll() {
if (ICabHolder != null) {
checked.clear()
for (i in 0 until itemCount) {
val identifier = getIdentifier(i)
if (identifier != null) {
checked.add(identifier)
}
}
notifyDataSetChanged()
updateCab()
}
}
protected abstract fun getIdentifier(position: Int): I?
protected open fun getName(i: I): String? {
return i.toString()
}
protected fun isChecked(identifier: I): Boolean {
return checked.contains(identifier)
}
protected val isInQuickSelectMode: Boolean
get() = cab != null && cab!!.isActive()
protected abstract fun onMultipleItemAction(menuItem: MenuItem, selection: List<I>)
protected fun setMultiSelectMenuRes(@MenuRes menuRes: Int) {
this.menuRes = menuRes
}
protected fun toggleChecked(position: Int): Boolean {
if (ICabHolder != null) {
val identifier = getIdentifier(position) ?: return false
if (!checked.remove(identifier)) {
checked.add(identifier)
}
notifyItemChanged(position)
updateCab()
return true
}
return false
}
private fun clearChecked() {
checked.clear()
notifyDataSetChanged()
}
@SuppressLint("StringFormatInvalid", "StringFormatMatches")
private fun updateCab() {
if (ICabHolder != null) {
if (cab == null || !cab!!.isActive()) {
cab = ICabHolder.openCab(menuRes, this)
}
val size = checked.size
when {
size <= 0 -> {
cab?.destroy()
}
size == 1 -> {
cab?.title(literal = getName(checked[0]))
}
else -> {
cab?.title(literal = activity.getString(R.string.x_selected, size))
}
}
}
}
init {
checked = ArrayList()
this.menuRes = menuRes
}
}

View File

@ -16,13 +16,13 @@ package code.name.monkey.retromusic.adapter.base;
import android.graphics.Color;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.card.MaterialCardView;
import com.h6ah4i.android.widget.advrecyclerview.utils.AbstractDraggableSwipeableItemViewHolder;
@ -41,15 +41,12 @@ public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHold
@Nullable
public ImageView image;
@Nullable
public ImageView artistImage;
@Nullable
public ImageView playerImage;
@Nullable
public MaterialCardView imageContainerCard;
@Nullable
public FrameLayout imageContainer;
@Nullable
public TextView imageText;
@ -65,10 +62,6 @@ public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHold
@Nullable
public View paletteColorContainer;
@Nullable
public RecyclerView recyclerView;
@Nullable
public TextView text;
@ -88,18 +81,16 @@ public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHold
text2 = itemView.findViewById(R.id.text2);
image = itemView.findViewById(R.id.image);
artistImage = itemView.findViewById(R.id.artistImage);
playerImage = itemView.findViewById(R.id.player_image);
time = itemView.findViewById(R.id.time);
imageText = itemView.findViewById(R.id.imageText);
imageTextContainer = itemView.findViewById(R.id.imageTextContainer);
imageContainerCard = itemView.findViewById(R.id.imageContainerCard);
imageContainer = itemView.findViewById(R.id.imageContainer);
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);
dummyContainer = itemView.findViewById(R.id.dummy_view);

View File

@ -41,7 +41,7 @@ import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.MusicUtil
class PlaylistAdapter(
private val activity: FragmentActivity,
override val activity: FragmentActivity,
private var dataSet: List<PlaylistWithSongs>,
private var itemLayoutRes: Int,
ICabHolder: ICabHolder?,

View File

@ -16,7 +16,6 @@ package code.name.monkey.retromusic.adapter.song
import android.view.MenuItem
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.FragmentActivity
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.glide.GlideApp
@ -41,7 +40,7 @@ import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultAct
import me.zhanghai.android.fastscroll.PopupTextProvider
class PlayingQueueAdapter(
activity: AppCompatActivity,
activity: FragmentActivity,
dataSet: MutableList<Song>,
private var current: Int,
itemLayoutRes: Int

View File

@ -16,10 +16,9 @@ package code.name.monkey.retromusic.adapter.song
import android.view.View
import androidx.fragment.app.FragmentActivity
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.extensions.applyColor
import code.name.monkey.retromusic.extensions.applyOutlineColor
import code.name.monkey.retromusic.extensions.accentColor
import code.name.monkey.retromusic.extensions.accentOutlineColor
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.interfaces.ICabHolder
import code.name.monkey.retromusic.model.Song
@ -45,19 +44,18 @@ class ShuffleButtonSongAdapter(
override fun onBindViewHolder(holder: SongAdapter.ViewHolder, position: Int) {
if (holder.itemViewType == OFFSET_ITEM) {
val color = ThemeStore.accentColor(activity)
val viewHolder = holder as ViewHolder
viewHolder.playAction?.let {
it.setOnClickListener {
MusicPlayerRemote.openQueue(dataSet, 0, true)
}
it.applyOutlineColor(color)
it.accentOutlineColor()
}
viewHolder.shuffleAction?.let {
it.setOnClickListener {
MusicPlayerRemote.openAndShuffleQueue(dataSet, true)
}
it.applyColor(color)
it.accentColor()
}
} else {
super.onBindViewHolder(holder, position - 1)

View File

@ -37,12 +37,13 @@ import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.SortOrder
import code.name.monkey.retromusic.helper.menu.SongMenuHelper
import code.name.monkey.retromusic.helper.menu.SongsMenuHelper
import code.name.monkey.retromusic.interfaces.ICabCallback
import code.name.monkey.retromusic.interfaces.ICabHolder
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.RetroUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import com.afollestad.materialcab.MaterialCab
import me.zhanghai.android.fastscroll.PopupTextProvider
/**
@ -50,7 +51,7 @@ import me.zhanghai.android.fastscroll.PopupTextProvider
*/
open class SongAdapter(
protected val activity: FragmentActivity,
override val activity: FragmentActivity,
var dataSet: MutableList<Song>,
protected var itemLayoutRes: Int,
ICabHolder: ICabHolder?,
@ -59,7 +60,7 @@ open class SongAdapter(
activity,
ICabHolder,
R.menu.menu_media_selection
), MaterialCab.Callback, PopupTextProvider {
), ICabCallback, PopupTextProvider {
private var showSectionName = true
@ -104,6 +105,10 @@ open class SongAdapter(
holder.text?.text = getSongText(song)
holder.text2?.text = getSongText(song)
loadAlbumCover(song, holder)
val landscape = RetroUtil.isLandscape()
if ((PreferenceUtil.songGridSize > 2 && !landscape) || (PreferenceUtil.songGridSizeLand > 5 && landscape)) {
holder.menu?.isVisible = false
}
}
private fun setColors(color: MediaNotificationProcessor, holder: ViewHolder) {

View File

@ -35,6 +35,7 @@ public class AutoMediaIDHelper {
public static final String MEDIA_ID_MUSICS_BY_GENRE = "__BY_GENRE__";
public static final String MEDIA_ID_MUSICS_BY_SHUFFLE = "__BY_SHUFFLE__";
public static final String MEDIA_ID_MUSICS_BY_QUEUE = "__BY_QUEUE__";
public static final String RECENT_ROOT = "__RECENT__";
private static final String CATEGORY_SEPARATOR = "__/__";
private static final String LEAF_SEPARATOR = "__|__";

View File

@ -15,7 +15,6 @@ package code.name.monkey.retromusic.auto
import android.content.Context
import android.content.res.Resources
import android.net.Uri
import android.support.v4.media.MediaBrowserCompat
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.helper.MusicPlayerRemote

View File

@ -14,7 +14,6 @@
*/
package code.name.monkey.retromusic.dialogs
import android.annotation.SuppressLint
import android.app.Dialog
import android.content.Context
import android.os.Bundle
@ -42,7 +41,6 @@ import java.io.IOException
class SongDetailDialog : DialogFragment() {
@SuppressLint("InflateParams")
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val context: Context = requireContext()
val dialogView = LayoutInflater.from(context).inflate(R.layout.dialog_file_details, null)

View File

@ -21,7 +21,7 @@ import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import com.google.android.material.appbar.MaterialToolbar
fun AppCompatActivity.applyToolbar(toolbar: MaterialToolbar) {
toolbar.setBackgroundColor(surfaceColor())
//toolbar.setBackgroundColor(surfaceColor())
ToolbarContentTintHelper.colorBackButton(toolbar)
setSupportActionBar(toolbar)
}

View File

@ -29,6 +29,7 @@ import androidx.annotation.ColorRes
import androidx.appcompat.widget.AppCompatImageView
import androidx.appcompat.widget.Toolbar
import androidx.core.content.ContextCompat
import androidx.core.graphics.ColorUtils
import androidx.core.graphics.drawable.DrawableCompat
import androidx.fragment.app.Fragment
import code.name.monkey.appthemehelper.ThemeStore
@ -37,6 +38,7 @@ import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.retromusic.App
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.util.PreferenceUtil.materialYou
import com.google.android.material.button.MaterialButton
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import com.google.android.material.floatingactionbutton.FloatingActionButton
@ -84,23 +86,32 @@ fun Fragment.resolveColor(@AttrRes attr: Int, fallBackColor: Int = 0) =
fun Dialog.resolveColor(@AttrRes attr: Int, fallBackColor: Int = 0) =
ATHUtil.resolveColor(context, attr, fallBackColor)
// Don't apply accent colors if Material You is enabled
// Material Components will take care of applying material you colors
fun CheckBox.addAccentColor() {
if (materialYou) return
buttonTintList = ColorStateList.valueOf(ThemeStore.accentColor(context))
}
fun SeekBar.addAccentColor() {
if (materialYou) return
val colorState = ColorStateList.valueOf(ThemeStore.accentColor(context))
progressTintList = colorState
thumbTintList = colorState
}
fun Button.accentTextColor() = setTextColor(ThemeStore.accentColor(App.getContext()))
fun Button.accentTextColor() {
if (materialYou) return
setTextColor(ThemeStore.accentColor(App.getContext()))
}
fun MaterialButton.accentBackgroundColor() {
if (materialYou) return
backgroundTintList = ColorStateList.valueOf(ThemeStore.accentColor(App.getContext()))
}
fun MaterialButton.accentOutlineColor() {
if (materialYou) return
val color = ThemeStore.accentColor(context)
val colorStateList = ColorStateList.valueOf(color)
iconTint = colorStateList
@ -116,6 +127,7 @@ fun SeekBar.applyColor(@ColorInt color: Int) {
}
fun ExtendedFloatingActionButton.accentColor() {
if (materialYou) return
val color = ThemeStore.accentColor(context)
val textColor = MaterialValueHelper.getPrimaryTextColor(context, ColorUtil.isColorLight(color))
val colorStateList = ColorStateList.valueOf(color)
@ -126,12 +138,11 @@ fun ExtendedFloatingActionButton.accentColor() {
}
fun FloatingActionButton.accentColor() {
if (materialYou) return
val color = ThemeStore.accentColor(context)
val textColor = MaterialValueHelper.getPrimaryTextColor(context, ColorUtil.isColorLight(color))
val colorStateList = ColorStateList.valueOf(color)
val textColorStateList = ColorStateList.valueOf(textColor)
backgroundTintList = colorStateList
imageTintList = textColorStateList
backgroundTintList = ColorStateList.valueOf(color)
imageTintList = ColorStateList.valueOf(textColor)
}
fun MaterialButton.applyColor(color: Int) {
@ -147,15 +158,21 @@ fun MaterialButton.applyColor(color: Int) {
iconTint = textColorColorStateList
}
fun MaterialButton.accentColor() = applyColor(ThemeStore.accentColor(context))
fun MaterialButton.accentColor() {
if (materialYou) return
applyColor(ThemeStore.accentColor(context))
}
fun MaterialButton.applyOutlineColor(color: Int) {
val textColorColorStateList = ColorStateList.valueOf(color)
setTextColor(textColorColorStateList)
iconTint = textColorColorStateList
val colorStateList = ColorStateList.valueOf(color)
iconTint = colorStateList
strokeColor = colorStateList
setTextColor(colorStateList)
rippleColor = colorStateList
}
fun TextInputLayout.accentColor() {
if (materialYou) return
val accentColor = ThemeStore.accentColor(context)
val colorState = ColorStateList.valueOf(accentColor)
boxStrokeColor = accentColor
@ -164,6 +181,7 @@ fun TextInputLayout.accentColor() {
}
fun CircularProgressIndicator.accentColor() {
if (materialYou) return
val color = ThemeStore.accentColor(context)
setIndicatorColor(color)
trackColor = ColorUtil.withAlpha(color, 0.2f)
@ -176,6 +194,21 @@ fun CircularProgressIndicator.applyColor(color: Int) {
fun AppCompatImageView.accentColor(): Int = ThemeStore.accentColor(context)
fun TextInputLayout.setTint(background: Boolean = true) {
if (materialYou) return
val accentColor = ThemeStore.accentColor(context)
val colorState = ColorStateList.valueOf(accentColor)
if (background) {
backgroundTintList = colorState
defaultHintTextColor = colorState
} else {
boxStrokeColor = accentColor
defaultHintTextColor = colorState
isHintAnimationEnabled = true
}
}
@CheckResult
fun Drawable.tint(@ColorInt color: Int): Drawable {
val tintedDrawable = DrawableCompat.wrap(this).mutate()
@ -191,3 +224,15 @@ fun Drawable.tint(context: Context, @ColorRes color: Int): Drawable =
fun Context.getColorCompat(@ColorRes colorRes: Int): Int {
return ContextCompat.getColor(this, colorRes)
}
@ColorInt
fun Context.darkAccentColor(): Int {
return ColorUtils.blendARGB(
accentColor(),
surfaceColor(),
if (surfaceColor().isColorLight) 0.96f else 0.975f
)
}
inline val @receiver:ColorInt Int.isColorLight
get() = ColorUtil.isColorLight(this)

View File

@ -19,16 +19,14 @@ import android.content.res.Configuration
import android.graphics.drawable.Drawable
import android.os.PowerManager
import android.widget.Toast
import androidx.annotation.DrawableRes
import androidx.annotation.IdRes
import androidx.annotation.IntegerRes
import androidx.annotation.StringRes
import androidx.annotation.*
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.content.res.AppCompatResources
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.navigation.fragment.NavHostFragment
import code.name.monkey.retromusic.util.PreferenceUtil
import com.google.android.material.appbar.MaterialToolbar
fun Fragment.getIntRes(@IntegerRes int: Int): Int {
return resources.getInteger(int)
@ -97,3 +95,11 @@ fun Context.getDrawableCompat(@DrawableRes drawableRes: Int): Drawable {
fun Fragment.getDrawableCompat(@DrawableRes drawableRes: Int): Drawable {
return AppCompatResources.getDrawable(requireContext(), drawableRes)!!
}
fun Fragment.applyToolbar(toolbar: MaterialToolbar) {
(requireActivity() as AppCompatActivity).applyToolbar(toolbar)
}
fun Fragment.dip(@DimenRes id: Int): Int {
return resources.getDimensionPixelSize(id)
}

View File

@ -0,0 +1,7 @@
package code.name.monkey.retromusic.extensions
import androidx.core.view.WindowInsetsCompat
fun WindowInsetsCompat?.safeGetBottomInsets(): Int {
return this?.getInsets(WindowInsetsCompat.Type.systemBars())?.bottom ?: 0
}

View File

@ -14,19 +14,25 @@
*/
package code.name.monkey.retromusic.extensions
import android.animation.Animator
import android.animation.ObjectAnimator
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import android.view.ViewTreeObserver
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
import androidx.annotation.LayoutRes
import androidx.core.animation.doOnEnd
import androidx.core.animation.doOnStart
import androidx.core.view.*
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.RetroUtil
import com.afollestad.materialdialogs.utils.MDUtil.updatePadding
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.imageview.ShapeableImageView
import com.google.android.material.shape.ShapeAppearanceModel
@ -51,12 +57,13 @@ fun View.hidden() {
fun View.showOrHide(show: Boolean) = if (show) show() else hide()
fun EditText.appHandleColor(): EditText {
if (PreferenceUtil.materialYou) return this
TintHelper.colorHandles(this, ThemeStore.accentColor(context))
return this
}
fun View.translateYAnimate(value: Float) {
ObjectAnimator.ofFloat(this, "translationY", value)
fun View.translateYAnimate(value: Float): Animator {
return ObjectAnimator.ofFloat(this, "translationY", value)
.apply {
duration = 300
doOnStart {
@ -122,4 +129,114 @@ fun ShapeableImageView.setCircleShape(boolean: Boolean) {
val radius = width / 2f
shapeAppearanceModel = ShapeAppearanceModel().withCornerSize(radius)
}
}
}
/**
* This will draw our view above the navigation bar instead of behind it by adding margins.
*/
fun View.drawAboveSystemBars(onlyPortrait: Boolean = true) {
if (onlyPortrait && RetroUtil.isLandscape()) return
// Create a snapshot of the view's margin state
val initialMargin = recordInitialMarginForView(this)
ViewCompat.setOnApplyWindowInsetsListener(
(this)
) { _: View, windowInsets: WindowInsetsCompat ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
// Apply the insets as a margin to the view.
updateLayoutParams<MarginLayoutParams> {
leftMargin = initialMargin.left + insets.left
bottomMargin = initialMargin.bottom + insets.bottom
rightMargin = initialMargin.right + insets.right
}
windowInsets
}
}
/**
* This will draw our view above the navigation bar instead of behind it by adding padding.
*/
fun View.drawAboveSystemBarsWithPadding(consume: Boolean = false) {
val initialPadding = recordInitialPaddingForView(this)
ViewCompat.setOnApplyWindowInsetsListener(
(this)
) { v: View, windowInsets: WindowInsetsCompat ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
v.updatePadding(
left = initialPadding.left + insets.left,
bottom = initialPadding.bottom + insets.bottom,
right = initialPadding.right + insets.right
)
if (consume) WindowInsetsCompat.CONSUMED else windowInsets
}
requestApplyInsetsWhenAttached()
}
fun View.requestApplyInsetsWhenAttached() {
if (isAttachedToWindow) {
// We're already attached, just request as normal
requestApplyInsets()
} else {
// We're not attached to the hierarchy, add a listener to
// request when we are
addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View) {
v.removeOnAttachStateChangeListener(this)
v.requestApplyInsets()
}
override fun onViewDetachedFromWindow(v: View) = Unit
})
}
}
fun View.drawNextToNavbar() {
val initialPadding = recordInitialPaddingForView(this)
ViewCompat.setOnApplyWindowInsetsListener(
(this)
) { v: View, windowInsets: WindowInsetsCompat ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
v.updatePadding(
left = initialPadding.left + insets.left,
right = initialPadding.right + insets.right
)
windowInsets
}
requestApplyInsetsWhenAttached()
}
fun View.addBottomInsets() {
// Create a snapshot of the view's margin state
val initialMargin = recordInitialMarginForView(this)
ViewCompat.setOnApplyWindowInsetsListener(
(this)
) { _: View, windowInsets: WindowInsetsCompat ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
// Apply the insets as a margin to the view.
updateLayoutParams<MarginLayoutParams> {
bottomMargin = initialMargin.bottom + insets.bottom
}
windowInsets
}
}
data class InitialMargin(
val left: Int, val top: Int,
val right: Int, val bottom: Int
)
fun recordInitialMarginForView(view: View) = InitialMargin(
view.marginLeft, view.marginTop, view.marginRight, view.marginBottom
)
data class InitialPadding(
val left: Int, val top: Int,
val right: Int, val bottom: Int
)
private fun recordInitialPaddingForView(view: View) = InitialPadding(
view.paddingLeft, view.paddingTop, view.paddingRight, view.paddingBottom
)

View File

@ -19,10 +19,12 @@ import androidx.lifecycle.*
import code.name.monkey.retromusic.*
import code.name.monkey.retromusic.db.*
import code.name.monkey.retromusic.fragments.ReloadType.*
import code.name.monkey.retromusic.fragments.search.Filter
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.interfaces.IMusicServiceEventListener
import code.name.monkey.retromusic.model.*
import code.name.monkey.retromusic.repository.RealRepository
import code.name.monkey.retromusic.util.DensityUtil
import code.name.monkey.retromusic.util.PreferenceUtil
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.launch
@ -40,6 +42,7 @@ class LibraryViewModel(
private val legacyPlaylists = MutableLiveData<List<Playlist>>()
private val genres = MutableLiveData<List<Genre>>()
private val searchResults = MutableLiveData<List<Any>>()
private val fabMargin = MutableLiveData(0)
val paletteColor: LiveData<Int> = _paletteColor
init {
@ -85,6 +88,10 @@ class LibraryViewModel(
return home
}
fun getFabMargin(): LiveData<Int> {
return fabMargin
}
private fun fetchSongs() {
viewModelScope.launch(IO) {
songs.postValue(repository.allSongs())
@ -133,9 +140,9 @@ class LibraryViewModel(
}
}
fun search(query: String?, filters: List<Boolean>) {
fun search(query: String?, filter: Filter) {
viewModelScope.launch(IO) {
val result = repository.search(query, filters)
val result = repository.search(query, filter)
searchResults.postValue(result)
}
}
@ -328,6 +335,14 @@ class LibraryViewModel(
}
}
}
fun setFabMargin(bottomMargin: Int) {
fabMargin.postValue(
// Normal Margin
DensityUtil.dip2px(App.getContext(), 16F) +
bottomMargin
)
}
}
enum class ReloadType {

View File

@ -20,6 +20,7 @@ import android.net.Uri
import android.os.Bundle
import android.view.View
import androidx.core.app.ShareCompat
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
@ -30,6 +31,7 @@ import code.name.monkey.retromusic.adapter.ContributorAdapter
import code.name.monkey.retromusic.databinding.FragmentAboutBinding
import code.name.monkey.retromusic.fragments.LibraryViewModel
import code.name.monkey.retromusic.util.NavigationUtil
import code.name.monkey.retromusic.util.RetroUtil
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
class AboutFragment : Fragment(R.layout.fragment_about), View.OnClickListener {
@ -43,6 +45,12 @@ class AboutFragment : Fragment(R.layout.fragment_about), View.OnClickListener {
binding.aboutContent.cardOther.version.setSummary(getAppVersion())
setUpView()
loadContributors()
// This is a workaround as CollapsingToolbarLayout consumes insets and
// insets are not passed to child views
// https://github.com/material-components/material-components-android/issues/1310
if (!RetroUtil.isLandscape()) {
binding.root.updatePadding(bottom = RetroUtil.getNavigationBarHeight())
}
}
private fun openUrl(url: String) {

View File

@ -57,6 +57,7 @@ import code.name.monkey.retromusic.helper.SortOrder.AlbumSongSortOrder.Companion
import code.name.monkey.retromusic.helper.SortOrder.AlbumSongSortOrder.Companion.SONG_TRACK_LIST
import code.name.monkey.retromusic.helper.SortOrder.AlbumSongSortOrder.Companion.SONG_Z_A
import code.name.monkey.retromusic.interfaces.IAlbumClickListener
import code.name.monkey.retromusic.interfaces.ICabCallback
import code.name.monkey.retromusic.interfaces.ICabHolder
import code.name.monkey.retromusic.model.Album
import code.name.monkey.retromusic.model.Artist
@ -68,7 +69,11 @@ import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.RetroColorUtil
import code.name.monkey.retromusic.util.RetroUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import com.afollestad.materialcab.MaterialCab
import com.afollestad.materialcab.attached.AttachedCab
import com.afollestad.materialcab.attached.destroy
import com.afollestad.materialcab.attached.isActive
import com.afollestad.materialcab.createCab
import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.transition.MaterialArcMotion
import com.google.android.material.transition.MaterialContainerTransform
import kotlinx.coroutines.Dispatchers
@ -101,7 +106,6 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
super.onCreate(savedInstanceState)
sharedElementEnterTransition = MaterialContainerTransform().apply {
drawingViewId = R.id.fragment_container
duration = 300L
scrimColor = Color.TRANSPARENT
setAllContainerColors(requireContext().resolveColor(R.attr.colorSurface))
setPathMotion(MaterialArcMotion())
@ -176,6 +180,8 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
requireActivity().onBackPressed()
}
}
binding.appBarLayout?.statusBarForeground =
MaterialShapeDrawable.createWithElevationOverlay(requireContext())
}
override fun onDestroy() {
@ -228,9 +234,10 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
loadAlbumCover(album)
simpleSongAdapter.swapDataSet(album.songs)
if (albumArtistExists) {
detailsViewModel.getAlbumArtist(album.albumArtist.toString()).observe(viewLifecycleOwner, {
loadArtistImage(it)
})
detailsViewModel.getAlbumArtist(album.albumArtist.toString())
.observe(viewLifecycleOwner, {
loadArtistImage(it)
})
} else {
detailsViewModel.getArtist(album.artistId).observe(viewLifecycleOwner, {
loadArtistImage(it)
@ -302,7 +309,12 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
})
GlideApp.with(requireContext()).asBitmapPalette().artistImageOptions(artist)
//.forceDownload(PreferenceUtil.isAllowedToDownloadMetadata())
.load(RetroGlideExtension.getArtistModel(artist, PreferenceUtil.isAllowedToDownloadMetadata()))
.load(
RetroGlideExtension.getArtistModel(
artist,
PreferenceUtil.isAllowedToDownloadMetadata()
)
)
.dontAnimate()
.dontTransform()
.into(object : RetroMusicColoredTarget(binding.artistImage) {
@ -312,7 +324,8 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
}
private fun loadAlbumCover(album: Album) {
GlideApp.with(requireContext()).asBitmapPalette().albumCoverOptions(album.safeGetFirstSong())
GlideApp.with(requireContext()).asBitmapPalette()
.albumCoverOptions(album.safeGetFirstSong())
//.checkIgnoreMediaStore()
.load(RetroGlideExtension.getSongModel(album.safeGetFirstSong()))
.into(object : SingleColorTarget(binding.image) {
@ -323,8 +336,10 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
}
private fun setColors(color: Int) {
binding.fragmentAlbumContent.shuffleAction.applyColor(color)
binding.fragmentAlbumContent.playAction.applyOutlineColor(color)
_binding?.fragmentAlbumContent?.apply {
shuffleAction.applyColor(color)
playAction.applyOutlineColor(color)
}
}
override fun onAlbumClick(albumId: Long, view: View) {
@ -450,28 +465,34 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
private fun handleBackPress(): Boolean {
cab?.let {
if (it.isActive) {
it.finish()
if (it.isActive()) {
it.destroy()
return true
}
}
return false
}
private var cab: MaterialCab? = null
private var cab: AttachedCab? = null
override fun openCab(menuRes: Int, callback: MaterialCab.Callback): MaterialCab {
override fun openCab(menuRes: Int, callback: ICabCallback): AttachedCab {
cab?.let {
if (it.isActive) {
it.finish()
if (it.isActive()) {
it.destroy()
}
}
cab = MaterialCab(mainActivity, R.id.cab_stub)
.setMenu(menuRes)
.setCloseDrawableRes(R.drawable.ic_close)
.setBackgroundColor(RetroColorUtil.shiftBackgroundColorForLightText(surfaceColor()))
.start(callback)
return cab as MaterialCab
cab = createCab(R.id.toolbar_container) {
menu(menuRes)
closeDrawable(R.drawable.ic_close)
backgroundColor(literal = RetroColorUtil.shiftBackgroundColor(surfaceColor()))
slideDown()
onCreate { cab, menu -> callback.onCabCreated(cab, menu) }
onSelection {
callback.onCabItemClicked(it)
}
onDestroy { callback.onCabFinished(it) }
}
return cab as AttachedCab
}
override fun onDestroyView() {

View File

@ -54,7 +54,7 @@ class AlbumDetailsViewModel(
fun getAlbumInfo(album: Album): LiveData<Result<LastFmAlbum>> = liveData {
emit(Result.Loading)
emit(repository.albumInfo(album.artistName ?: "-", album.title ?: "-"))
emit(repository.albumInfo(album.artistName, album.title))
}
fun getMoreAlbums(artist: Artist): LiveData<List<Album>> = liveData(IO) {

View File

@ -24,16 +24,23 @@ import androidx.recyclerview.widget.GridLayoutManager
import code.name.monkey.retromusic.EXTRA_ALBUM_ID
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.album.AlbumAdapter
import code.name.monkey.retromusic.extensions.navigate
import code.name.monkey.retromusic.extensions.surfaceColor
import code.name.monkey.retromusic.fragments.ReloadType
import code.name.monkey.retromusic.fragments.base.AbsRecyclerViewCustomGridSizeFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.SortOrder.AlbumSortOrder
import code.name.monkey.retromusic.interfaces.IAlbumClickListener
import code.name.monkey.retromusic.interfaces.ICabCallback
import code.name.monkey.retromusic.interfaces.ICabHolder
import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.RetroColorUtil
import code.name.monkey.retromusic.util.RetroUtil
import com.afollestad.materialcab.MaterialCab
import com.afollestad.materialcab.attached.AttachedCab
import com.afollestad.materialcab.attached.destroy
import com.afollestad.materialcab.attached.isActive
import com.afollestad.materialcab.createCab
import com.google.android.gms.cast.framework.CastButtonFactory
class AlbumsFragment : AbsRecyclerViewCustomGridSizeFragment<AlbumAdapter, GridLayoutManager>(),
@ -50,7 +57,7 @@ class AlbumsFragment : AbsRecyclerViewCustomGridSizeFragment<AlbumAdapter, GridL
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
if (!handleBackPress()) {
remove()
requireActivity().onBackPressed()
mainActivity.finish()
}
}
}
@ -61,6 +68,20 @@ class AlbumsFragment : AbsRecyclerViewCustomGridSizeFragment<AlbumAdapter, GridL
override val emptyMessage: Int
get() = R.string.no_albums
override val isShuffleVisible: Boolean
get() = true
override fun onShuffleClicked() {
libraryViewModel.getAlbums().value?.let {
MusicPlayerRemote.setShuffleMode(MusicService.SHUFFLE_MODE_NONE)
MusicPlayerRemote.openQueue(
queue = it.shuffled().flatMap { album -> album.songs },
startPosition = 0,
startPlaying = true
)
}
}
override fun createLayoutManager(): GridLayoutManager {
return GridLayoutManager(requireActivity(), getGridSize())
}
@ -325,28 +346,34 @@ class AlbumsFragment : AbsRecyclerViewCustomGridSizeFragment<AlbumAdapter, GridL
private fun handleBackPress(): Boolean {
cab?.let {
if (it.isActive) {
it.finish()
if (it.isActive()) {
it.destroy()
return true
}
}
return false
}
private var cab: MaterialCab? = null
private var cab: AttachedCab? = null
override fun openCab(menuRes: Int, callback: MaterialCab.Callback): MaterialCab {
override fun openCab(menuRes: Int, callback: ICabCallback): AttachedCab {
cab?.let {
println("Cab")
if (it.isActive) {
it.finish()
if (it.isActive()) {
it.destroy()
}
}
cab = MaterialCab(mainActivity, R.id.cab_stub)
.setMenu(menuRes)
.setCloseDrawableRes(R.drawable.ic_close)
.setBackgroundColor(RetroColorUtil.shiftBackgroundColorForLightText(surfaceColor()))
.start(callback)
return cab as MaterialCab
cab = createCab(R.id.toolbar_container) {
menu(menuRes)
closeDrawable(R.drawable.ic_close)
backgroundColor(literal = RetroColorUtil.shiftBackgroundColor(surfaceColor()))
slideDown()
onCreate { cab, menu -> callback.onCabCreated(cab, menu) }
onSelection {
callback.onCabItemClicked(it)
}
onDestroy { callback.onCabFinished(it) }
}
return cab as AttachedCab
}
}

View File

@ -33,6 +33,7 @@ import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.SingleColorTarget
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.interfaces.IAlbumClickListener
import code.name.monkey.retromusic.interfaces.ICabCallback
import code.name.monkey.retromusic.interfaces.ICabHolder
import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.network.Result
@ -42,7 +43,11 @@ import code.name.monkey.retromusic.util.CustomArtistImageUtil
import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.RetroColorUtil
import code.name.monkey.retromusic.util.RetroUtil
import com.afollestad.materialcab.MaterialCab
import com.afollestad.materialcab.attached.AttachedCab
import com.afollestad.materialcab.attached.destroy
import com.afollestad.materialcab.attached.isActive
import com.afollestad.materialcab.createCab
import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.transition.MaterialContainerTransform
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -70,7 +75,6 @@ abstract class AbsArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragm
super.onCreate(savedInstanceState)
sharedElementEnterTransition = MaterialContainerTransform().apply {
drawingViewId = R.id.fragment_container
duration = 300L
scrimColor = Color.TRANSPARENT
setAllContainerColors(requireContext().resolveColor(R.attr.colorSurface))
}
@ -121,6 +125,8 @@ abstract class AbsArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragm
requireActivity().onBackPressed()
}
}
binding.appBarLayout?.statusBarForeground =
MaterialShapeDrawable.createWithElevationOverlay(requireContext())
}
private fun setupRecyclerView() {
@ -306,28 +312,34 @@ abstract class AbsArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragm
private fun handleBackPress(): Boolean {
cab?.let {
if (it.isActive) {
it.finish()
if (it.isActive()) {
it.destroy()
return true
}
}
return false
}
private var cab: MaterialCab? = null
private var cab: AttachedCab? = null
override fun openCab(menuRes: Int, callback: MaterialCab.Callback): MaterialCab {
override fun openCab(menuRes: Int, callback: ICabCallback): AttachedCab {
cab?.let {
if (it.isActive) {
it.finish()
if (it.isActive()) {
it.destroy()
}
}
cab = MaterialCab(mainActivity, R.id.cab_stub)
.setMenu(menuRes)
.setCloseDrawableRes(R.drawable.ic_close)
.setBackgroundColor(RetroColorUtil.shiftBackgroundColorForLightText(surfaceColor()))
.start(callback)
return cab as MaterialCab
cab = createCab(R.id.toolbar_container) {
menu(menuRes)
closeDrawable(R.drawable.ic_close)
backgroundColor(literal = RetroColorUtil.shiftBackgroundColor(surfaceColor()))
slideDown()
onCreate { cab, menu -> callback.onCabCreated(cab, menu) }
onSelection {
callback.onCabItemClicked(it)
}
onDestroy { callback.onCabFinished(it) }
}
return cab as AttachedCab
}

View File

@ -1,66 +0,0 @@
/*
* Copyright (c) 2020 Hemanth Savarla.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*/
package code.name.monkey.retromusic.fragments.artists
import androidx.lifecycle.*
import code.name.monkey.retromusic.interfaces.IMusicServiceEventListener
import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.network.Result
import code.name.monkey.retromusic.network.model.LastFmArtist
import code.name.monkey.retromusic.repository.RealRepository
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.launch
class AlbumArtistDetailsViewModel(
private val realRepository: RealRepository,
private val artistName: String
) : ViewModel(), IMusicServiceEventListener {
private val artistDetails = MutableLiveData<Artist>()
init {
fetchAlbumArtist()
}
private fun fetchAlbumArtist() {
viewModelScope.launch(IO) {
artistDetails.postValue(realRepository.albumArtistByName(artistName))
}
}
fun getArtist(): LiveData<Artist> = artistDetails
fun getArtistInfo(
name: String,
lang: String?,
cache: String?
): LiveData<Result<LastFmArtist>> = liveData(IO) {
emit(Result.Loading)
val info = realRepository.artistInfo(name, lang, cache)
emit(info)
}
override fun onMediaStoreChanged() {
fetchAlbumArtist()
}
override fun onServiceConnected() {}
override fun onServiceDisconnected() {}
override fun onQueueChanged() {}
override fun onFavoriteStateChanged() {}
override fun onPlayingMetaChanged() {}
override fun onPlayStateChanged() {}
override fun onRepeatModeChanged() {}
override fun onShuffleModeChanged() {}
}

View File

@ -25,17 +25,24 @@ import code.name.monkey.retromusic.EXTRA_ARTIST_ID
import code.name.monkey.retromusic.EXTRA_ARTIST_NAME
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.artist.ArtistAdapter
import code.name.monkey.retromusic.extensions.navigate
import code.name.monkey.retromusic.extensions.surfaceColor
import code.name.monkey.retromusic.fragments.ReloadType
import code.name.monkey.retromusic.fragments.base.AbsRecyclerViewCustomGridSizeFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.SortOrder.ArtistSortOrder
import code.name.monkey.retromusic.interfaces.IAlbumArtistClickListener
import code.name.monkey.retromusic.interfaces.IArtistClickListener
import code.name.monkey.retromusic.interfaces.ICabCallback
import code.name.monkey.retromusic.interfaces.ICabHolder
import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.RetroColorUtil
import code.name.monkey.retromusic.util.RetroUtil
import com.afollestad.materialcab.MaterialCab
import com.afollestad.materialcab.attached.AttachedCab
import com.afollestad.materialcab.attached.destroy
import com.afollestad.materialcab.attached.isActive
import com.afollestad.materialcab.createCab
import com.google.android.gms.cast.framework.CastButtonFactory
class ArtistsFragment : AbsRecyclerViewCustomGridSizeFragment<ArtistAdapter, GridLayoutManager>(),
@ -51,16 +58,31 @@ class ArtistsFragment : AbsRecyclerViewCustomGridSizeFragment<ArtistAdapter, Gri
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
if (!handleBackPress()) {
remove()
requireActivity().onBackPressed()
mainActivity.finish()
}
}
}
override val titleRes: Int
get() = R.string.artists
override val emptyMessage: Int
get() = R.string.no_artists
override val isShuffleVisible: Boolean
get() = true
override fun onShuffleClicked() {
libraryViewModel.getArtists().value?.let {
MusicPlayerRemote.setShuffleMode(MusicService.SHUFFLE_MODE_NONE)
MusicPlayerRemote.openQueue(
queue = it.shuffled().flatMap { artist -> artist.songs },
startPosition = 0,
startPlaying = true
)
}
}
override fun setSortOrder(sortOrder: String) {
libraryViewModel.forceReload(ReloadType.Artists)
}
@ -324,28 +346,34 @@ class ArtistsFragment : AbsRecyclerViewCustomGridSizeFragment<ArtistAdapter, Gri
private fun handleBackPress(): Boolean {
cab?.let {
if (it.isActive) {
it.finish()
if (it.isActive()) {
it.destroy()
return true
}
}
return false
}
private var cab: MaterialCab? = null
private var cab: AttachedCab? = null
override fun openCab(menuRes: Int, callback: MaterialCab.Callback): MaterialCab {
override fun openCab(menuRes: Int, callback: ICabCallback): AttachedCab {
cab?.let {
if (it.isActive) {
it.finish()
if (it.isActive()) {
it.destroy()
}
}
cab = MaterialCab(mainActivity, R.id.cab_stub)
.setMenu(menuRes)
.setCloseDrawableRes(R.drawable.ic_close)
.setBackgroundColor(RetroColorUtil.shiftBackgroundColorForLightText(surfaceColor()))
.start(callback)
return cab as MaterialCab
cab = createCab(R.id.toolbar_container) {
menu(menuRes)
closeDrawable(R.drawable.ic_close)
backgroundColor(literal = RetroColorUtil.shiftBackgroundColor(surfaceColor()))
slideDown()
onCreate { cab, menu -> callback.onCabCreated(cab, menu) }
onSelection {
callback.onCabItemClicked(it)
}
onDestroy { callback.onCabFinished(it) }
}
return cab as AttachedCab
}
override fun onResume() {

View File

@ -0,0 +1,152 @@
package code.name.monkey.retromusic.fragments.backup
import android.content.Intent
import android.os.Bundle
import android.view.MenuItem
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.backup.BackupAdapter
import code.name.monkey.retromusic.databinding.FragmentBackupBinding
import code.name.monkey.retromusic.helper.BackupHelper
import code.name.monkey.retromusic.util.BackupUtil
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.input.input
import kotlinx.coroutines.launch
import java.io.File
class BackupFragment : Fragment(R.layout.fragment_backup), BackupAdapter.BackupClickedListener {
private val backupViewModel by viewModels<BackupViewModel>()
private var backupAdapter: BackupAdapter? = null
private var _binding: FragmentBackupBinding? = null
private val binding get() = _binding!!
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentBackupBinding.bind(view)
initAdapter()
setupRecyclerview()
backupViewModel.backupsLiveData.observe(this) {
if (it.isNotEmpty())
backupAdapter?.swapDataset(it)
else
backupAdapter?.swapDataset(listOf())
}
backupViewModel.loadBackups()
setupButtons()
}
private fun setupButtons() {
binding.createBackup.setOnClickListener {
MaterialDialog(requireContext()).show {
title(res = R.string.action_rename)
input(prefill = System.currentTimeMillis().toString()) { _, text ->
// Text submitted with the action button
lifecycleScope.launch {
BackupHelper.createBackup(requireContext(), text.toString())
backupViewModel.loadBackups()
}
}
positiveButton(android.R.string.ok)
negativeButton(R.string.action_cancel)
setTitle(R.string.title_new_backup)
}
}
}
private fun initAdapter() {
backupAdapter = BackupAdapter(requireActivity(), ArrayList(), this)
backupAdapter?.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onChanged() {
super.onChanged()
checkIsEmpty()
}
})
}
private fun checkIsEmpty() {
val isEmpty = backupAdapter!!.itemCount == 0
binding.empty.isVisible = isEmpty
binding.backupTitle.isVisible = !isEmpty
binding.backupRecyclerview.isVisible = !isEmpty
}
fun setupRecyclerview() {
binding.backupRecyclerview.apply {
layoutManager = LinearLayoutManager(context)
adapter = backupAdapter
}
}
override fun onBackupClicked(file: File) {
AlertDialog.Builder(requireContext())
.setTitle(R.string.restore)
.setMessage(R.string.restore_message)
.setPositiveButton(R.string.restore) { _, _ ->
lifecycleScope.launch {
backupViewModel.restoreBackup(requireActivity(), file)
}
}
.setNegativeButton(android.R.string.cancel, null)
.create()
.show()
}
override fun onBackupMenuClicked(file: File, menuItem: MenuItem): Boolean {
when (menuItem.itemId) {
R.id.action_delete -> {
try {
file.delete()
} catch (exception: SecurityException) {
Toast.makeText(
activity,
"Could not delete backup",
Toast.LENGTH_SHORT
).show()
}
backupViewModel.loadBackups()
return true
}
R.id.action_share -> {
activity?.startActivity(
Intent.createChooser(BackupUtil.createShareFileIntent(file, requireContext()), null))
return true
}
R.id.action_rename -> {
MaterialDialog(requireContext()).show {
title(res = R.string.action_rename)
input(prefill = file.nameWithoutExtension) { _, text ->
// Text submitted with the action button
val renamedFile =
File(file.parent + File.separator + text + BackupHelper.APPEND_EXTENSION)
if (!renamedFile.exists()) {
file.renameTo(renamedFile)
backupViewModel.loadBackups()
} else {
Toast.makeText(
requireContext(),
"File already exists",
Toast.LENGTH_SHORT
).show()
}
}
positiveButton(android.R.string.ok)
negativeButton(R.string.action_cancel)
setTitle(R.string.action_rename)
}
return true
}
}
return false
}
}

View File

@ -0,0 +1,38 @@
package code.name.monkey.retromusic.fragments.backup
import android.app.Activity
import android.content.Intent
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import code.name.monkey.retromusic.helper.BackupHelper
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
import kotlin.system.exitProcess
class BackupViewModel : ViewModel() {
private val backupsMutableLiveData = MutableLiveData<List<File>>()
val backupsLiveData: LiveData<List<File>> = backupsMutableLiveData
fun loadBackups() {
File(BackupHelper.backupRootPath).listFiles { _, name ->
return@listFiles name.endsWith(BackupHelper.BACKUP_EXTENSION)
}?.toList()?.let {
backupsMutableLiveData.value = it
}
}
suspend fun restoreBackup(activity: Activity, file: File) {
BackupHelper.restoreBackup(activity, file)
withContext(Dispatchers.Main) {
val intent = Intent(
activity,
activity::class.java
)
activity.startActivity(intent)
exitProcess(0)
}
}
}

View File

@ -0,0 +1,12 @@
package code.name.monkey.retromusic.fragments.backup
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import code.name.monkey.retromusic.R
class RestoreActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_restore)
}
}

View File

@ -34,10 +34,7 @@ abstract class AbsMainActivityFragment(@LayoutRes layout: Int) : AbsMusicService
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
setHasOptionsMenu(true)
mainActivity.setNavigationbarColorAuto()
mainActivity.setLightNavigationBar(true)
mainActivity.setTaskDescriptionColorAuto()
mainActivity.hideStatusBar()
}
private fun setStatusBarColor(view: View, color: Int) {

View File

@ -20,7 +20,7 @@ import android.view.animation.AccelerateInterpolator
import android.view.animation.DecelerateInterpolator
import androidx.annotation.LayoutRes
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.fragments.VolumeFragment
import code.name.monkey.retromusic.fragments.other.VolumeFragment
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor

View File

@ -131,7 +131,7 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
}
R.id.action_go_to_album -> {
//Hide Bottom Bar First, else Bottom Sheet doesn't collapse fully
mainActivity.setBottomBarVisibility(false)
mainActivity.setBottomNavVisibility(false)
mainActivity.collapsePanel()
requireActivity().findNavController(R.id.fragment_container).navigate(
R.id.albumDetailsFragment,
@ -144,7 +144,10 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
return true
}
R.id.now_playing -> {
NavigationUtil.goToPlayingQueue(requireActivity())
requireActivity().findNavController(R.id.fragment_container).navigate(
R.id.playing_queue_fragment,
null
)
return true
}
R.id.action_show_lyrics -> {
@ -325,6 +328,7 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
requireView()
)
)
playerToolbar()?.menu?.findItem(R.id.action_toggle_lyrics)?.let { showLyricsIcon(it) }
}
class SwipeDetector(val context: Context, val viewPager: ViewPager?, val view: View) :
@ -383,7 +387,7 @@ fun goToArtist(activity: Activity) {
currentFragment(R.id.fragment_container)?.exitTransition = null
//Hide Bottom Bar First, else Bottom Sheet doesn't collapse fully
setBottomBarVisibility(false)
setBottomNavVisibility(false)
if (getBottomSheetBehavior().state == BottomSheetBehavior.STATE_EXPANDED) {
collapsePanel()
}
@ -402,7 +406,7 @@ fun goToAlbum(activity: Activity) {
currentFragment(R.id.fragment_container)?.exitTransition = null
//Hide Bottom Bar First, else Bottom Sheet doesn't collapse fully
setBottomBarVisibility(false)
setBottomNavVisibility(false)
if (getBottomSheetBehavior().state == BottomSheetBehavior.STATE_EXPANDED) {
collapsePanel()
}

View File

@ -86,7 +86,7 @@ abstract class AbsRecyclerViewCustomGridSizeFragment<A : RecyclerView.Adapter<*>
} else {
saveGridSize(gridSize)
}
recyclerView().isVisible = false
recyclerView.isVisible = false
invalidateLayoutManager()
// only recreate the adapter and layout manager if the layout currentLayoutRes has changed
if (oldLayoutRes != itemLayoutRes()) {
@ -95,10 +95,10 @@ abstract class AbsRecyclerViewCustomGridSizeFragment<A : RecyclerView.Adapter<*>
setGridSize(gridSize)
}
val transition = MaterialFade().apply {
addTarget(recyclerView())
addTarget(recyclerView)
}
TransitionManager.beginDelayedTransition(getContainer(), transition)
recyclerView().isVisible = true
TransitionManager.beginDelayedTransition(container, transition)
recyclerView.isVisible = true
}
protected abstract fun setGridSize(gridSize: Int)

View File

@ -18,8 +18,10 @@ import android.os.Bundle
import android.view.*
import androidx.annotation.NonNull
import androidx.annotation.StringRes
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.appcompat.widget.Toolbar
import androidx.core.view.doOnPreDraw
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.RecyclerView
@ -29,9 +31,12 @@ import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentMainRecyclerBinding
import code.name.monkey.retromusic.dialogs.CreatePlaylistDialog
import code.name.monkey.retromusic.dialogs.ImportPlaylistDialog
import code.name.monkey.retromusic.extensions.accentColor
import code.name.monkey.retromusic.extensions.dip
import code.name.monkey.retromusic.extensions.drawNextToNavbar
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.util.DensityUtil
import code.name.monkey.retromusic.util.ThemedFastScroller.create
import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.transition.MaterialFadeThrough
import com.google.android.material.transition.MaterialSharedAxis
import me.zhanghai.android.fastscroll.FastScroller
@ -44,21 +49,57 @@ abstract class AbsRecyclerViewFragment<A : RecyclerView.Adapter<*>, LM : Recycle
private val binding get() = _binding!!
protected var adapter: A? = null
protected var layoutManager: LM? = null
val shuffleButton get() = binding.shuffleButton
abstract val isShuffleVisible: Boolean
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentMainRecyclerBinding.bind(view)
enterTransition = MaterialFadeThrough()
exitTransition = MaterialFadeThrough()
postponeEnterTransition()
view.doOnPreDraw { startPostponedEnterTransition() }
enterTransition = MaterialFadeThrough().apply {
addTarget(binding.recyclerView)
}
mainActivity.setSupportActionBar(binding.toolbar)
mainActivity.supportActionBar?.title = null
initLayoutManager()
initAdapter()
setUpRecyclerView()
setupToolbar()
// Add listeners when shuffle is visible
if (isShuffleVisible) {
binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (dy > 0) {
binding.shuffleButton.hide()
} else if (dy < 0) {
binding.shuffleButton.show()
}
}
})
binding.shuffleButton.apply {
setOnClickListener {
onShuffleClicked()
}
accentColor()
}
} else {
binding.shuffleButton.isVisible = false
}
libraryViewModel.getFabMargin().observe(viewLifecycleOwner, {
binding.shuffleButton.updateLayoutParams<ViewGroup.MarginLayoutParams> {
bottomMargin = it
}
})
}
open fun onShuffleClicked() {
}
fun toolbar(): Toolbar {
return binding.toolbar
}
private fun setupToolbar() {
@ -73,6 +114,9 @@ abstract class AbsRecyclerViewFragment<A : RecyclerView.Adapter<*>, LM : Recycle
}
val appName = resources.getString(titleRes)
binding.appNameText.text = appName
binding.toolbarContainer.drawNextToNavbar()
binding.appBarLayout.statusBarForeground =
MaterialShapeDrawable.createWithElevationOverlay(requireContext())
}
abstract val titleRes: Int
@ -117,10 +161,10 @@ abstract class AbsRecyclerViewFragment<A : RecyclerView.Adapter<*>, LM : Recycle
val itemCount: Int = adapter?.itemCount ?: 0
if (itemCount > 0 && MusicPlayerRemote.playingQueue.isNotEmpty()) {
val height = DensityUtil.dip2px(requireContext(), 112f)
val height = dip(R.dimen.mini_player_height_expanded)
binding.recyclerView.updatePadding(0, 0, 0, height)
} else {
val height = DensityUtil.dip2px(requireContext(), 56f)
val height = dip(R.dimen.mini_player_height)
binding.recyclerView.updatePadding(0, 0, 0, height)
}
}
@ -155,16 +199,12 @@ abstract class AbsRecyclerViewFragment<A : RecyclerView.Adapter<*>, LM : Recycle
binding.recyclerView.adapter = adapter
}
fun recyclerView(): RecyclerView {
return binding.recyclerView
}
val recyclerView get() = binding.recyclerView
fun getContainer(): CoordinatorLayout {
return binding.root
}
val container get() = binding.root
fun scrollToTop() {
recyclerView().scrollToPosition(0)
recyclerView.scrollToPosition(0)
binding.appBarLayout.setExpanded(true, true)
}

View File

@ -1,942 +0,0 @@
/*
* Copyright (c) 2020 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.fragments.folder;
import static code.name.monkey.appthemehelper.common.ATHToolbarActivity.getToolbarBackgroundColor;
import android.app.Dialog;
import android.content.Context;
import android.media.MediaScannerConnection;
import android.os.Bundle;
import android.os.Environment;
import android.text.Html;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.MarginLayoutParams;
import android.webkit.MimeTypeMap;
import android.widget.PopupMenu;
import android.widget.Toast;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.afollestad.materialcab.MaterialCab;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.transition.MaterialFadeThrough;
import com.google.android.material.transition.MaterialSharedAxis;
import org.jetbrains.annotations.NotNull;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileReader;
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.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;
import code.name.monkey.appthemehelper.ThemeStore;
import code.name.monkey.appthemehelper.util.ATHUtil;
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper;
import code.name.monkey.retromusic.App;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.adapter.SongFileAdapter;
import code.name.monkey.retromusic.adapter.Storage;
import code.name.monkey.retromusic.adapter.StorageAdapter;
import code.name.monkey.retromusic.adapter.StorageClickListener;
import code.name.monkey.retromusic.databinding.FragmentFolderBinding;
import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment;
import code.name.monkey.retromusic.helper.MusicPlayerRemote;
import code.name.monkey.retromusic.helper.menu.SongMenuHelper;
import code.name.monkey.retromusic.helper.menu.SongsMenuHelper;
import code.name.monkey.retromusic.interfaces.ICabHolder;
import code.name.monkey.retromusic.interfaces.ICallbacks;
import code.name.monkey.retromusic.interfaces.IMainActivityFragmentCallbacks;
import code.name.monkey.retromusic.misc.DialogAsyncTask;
import code.name.monkey.retromusic.misc.UpdateToastMediaScannerCompletionListener;
import code.name.monkey.retromusic.misc.WrappedAsyncTaskLoader;
import code.name.monkey.retromusic.model.Song;
import code.name.monkey.retromusic.providers.BlacklistStore;
import code.name.monkey.retromusic.util.DensityUtil;
import code.name.monkey.retromusic.util.FileUtil;
import code.name.monkey.retromusic.util.PreferenceUtil;
import code.name.monkey.retromusic.util.RetroColorUtil;
import code.name.monkey.retromusic.util.ThemedFastScroller;
import code.name.monkey.retromusic.views.BreadCrumbLayout;
import code.name.monkey.retromusic.views.ScrollingViewOnApplyWindowInsetsListener;
import me.zhanghai.android.fastscroll.FastScroller;
public class FoldersFragment extends AbsMainActivityFragment
implements IMainActivityFragmentCallbacks,
ICabHolder,
BreadCrumbLayout.SelectionCallback,
ICallbacks,
LoaderManager.LoaderCallbacks<List<File>>, StorageClickListener {
private FragmentFolderBinding binding;
public static final String TAG = FoldersFragment.class.getSimpleName();
public static final FileFilter AUDIO_FILE_FILTER =
file ->
!file.isHidden()
&& (file.isDirectory()
|| FileUtil.fileIsMimeType(file, "audio/*", MimeTypeMap.getSingleton())
|| FileUtil.fileIsMimeType(file, "application/opus", MimeTypeMap.getSingleton())
|| FileUtil.fileIsMimeType(file, "application/ogg", MimeTypeMap.getSingleton()));
private static final String CRUMBS = "crumbs";
private static final int LOADER_ID = 5;
private SongFileAdapter adapter;
private StorageAdapter storageAdapter;
private MaterialCab cab;
private final Comparator<File> 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 final ArrayList<Storage> storageItems = new ArrayList<>();
public FoldersFragment() {
super(R.layout.fragment_folder);
}
public static File getDefaultStartDirectory() {
File musicDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC);
File startFolder;
if (musicDir.exists() && musicDir.isDirectory()) {
startFolder = musicDir;
} else {
File externalStorage = Environment.getExternalStorageDirectory();
if (externalStorage.exists() && externalStorage.isDirectory()) {
startFolder = externalStorage;
} else {
startFolder = new File("/"); // root
}
}
return startFolder;
}
private static File tryGetCanonicalFile(File file) {
try {
return file.getCanonicalFile();
} catch (IOException e) {
e.printStackTrace();
return file;
}
}
@NonNull
@Override
public View onCreateView(
@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
binding = FragmentFolderBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
setEnterTransition(new MaterialFadeThrough());
setExitTransition(new MaterialFadeThrough());
getMainActivity().addMusicServiceEventListener(getLibraryViewModel());
getMainActivity().setSupportActionBar(binding.toolbar);
getMainActivity().getSupportActionBar().setTitle(null);
setStatusBarColorAuto(view);
setUpAppbarColor();
setUpBreadCrumbs();
setUpRecyclerView();
listRoots();
setUpAdapter();
setUpTitle();
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
if (!handleBackPress()) {
remove();
requireActivity().onBackPressed();
}
}
});
}
private void setUpTitle() {
binding.toolbar.setNavigationOnClickListener(
v -> {
setExitTransition(new MaterialSharedAxis(MaterialSharedAxis.Z, true).setDuration(300));
setReenterTransition(new MaterialSharedAxis(MaterialSharedAxis.Z, false).setDuration(300));
Navigation.findNavController(v).navigate(R.id.searchFragment, null, getNavOptions());
});
binding.appNameText.setText(getResources().getString(R.string.folders));
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setHasOptionsMenu(true);
if (savedInstanceState == null) {
switchToFileAdapter();
setCrumb(
new BreadCrumbLayout.Crumb(
FileUtil.safeGetCanonicalFile(PreferenceUtil.INSTANCE.getStartDirectory())),
true);
} else {
binding.breadCrumbs.restoreFromStateWrapper(savedInstanceState.getParcelable(CRUMBS));
LoaderManager.getInstance(this).initLoader(LOADER_ID, null, this);
}
}
@Override
public void onPause() {
super.onPause();
saveScrollPosition();
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
if (binding != null) {
outState.putParcelable(CRUMBS, binding.breadCrumbs.getStateWrapper());
}
}
@Override
public boolean handleBackPress() {
if (cab != null && cab.isActive()) {
cab.finish();
return true;
}
if (binding.breadCrumbs.popHistory()) {
setCrumb(binding.breadCrumbs.lastHistory(), false);
return true;
}
return false;
}
@NonNull
@Override
public Loader<List<File>> onCreateLoader(int id, Bundle args) {
return new AsyncFileLoader(this);
}
@Override
public void onCrumbSelection(BreadCrumbLayout.Crumb crumb, int index) {
setCrumb(crumb, true);
}
@Override
public void onFileMenuClicked(final File file, @NotNull View view) {
PopupMenu popupMenu = new PopupMenu(getActivity(), view);
if (file.isDirectory()) {
popupMenu.inflate(R.menu.menu_item_directory);
popupMenu.setOnMenuItemClickListener(
item -> {
final int itemId = item.getItemId();
switch (itemId) {
case R.id.action_play_next:
case R.id.action_add_to_current_playing:
case R.id.action_add_to_playlist:
case R.id.action_delete_from_device:
new ListSongsAsyncTask(
getActivity(),
null,
(songs, extra) -> {
if (!songs.isEmpty()) {
SongsMenuHelper.INSTANCE.handleMenuClick(
requireActivity(), songs, itemId);
}
})
.execute(
new ListSongsAsyncTask.LoadingInfo(
toList(file), AUDIO_FILE_FILTER, getFileComparator()));
return true;
case R.id.action_add_to_blacklist:
BlacklistStore.getInstance(App.Companion.getContext()).addPath(file);
return true;
case R.id.action_set_as_start_directory:
PreferenceUtil.INSTANCE.setStartDirectory(file);
Toast.makeText(
getActivity(),
String.format(getString(R.string.new_start_directory), file.getPath()),
Toast.LENGTH_SHORT)
.show();
return true;
case R.id.action_scan:
new ListPathsAsyncTask(getActivity(), this::scanPaths)
.execute(new ListPathsAsyncTask.LoadingInfo(file, AUDIO_FILE_FILTER));
return true;
}
return false;
});
} else {
popupMenu.inflate(R.menu.menu_item_file);
popupMenu.setOnMenuItemClickListener(
item -> {
final int itemId = item.getItemId();
switch (itemId) {
case R.id.action_play_next:
case R.id.action_add_to_current_playing:
case R.id.action_add_to_playlist:
case R.id.action_go_to_album:
case R.id.action_go_to_artist:
case R.id.action_share:
case R.id.action_tag_editor:
case R.id.action_details:
case R.id.action_set_as_ringtone:
case R.id.action_delete_from_device:
new ListSongsAsyncTask(
getActivity(),
null,
(songs, extra) ->
SongMenuHelper.INSTANCE.handleMenuClick(
requireActivity(), songs.get(0), itemId))
.execute(
new ListSongsAsyncTask.LoadingInfo(
toList(file), AUDIO_FILE_FILTER, getFileComparator()));
return true;
case R.id.action_scan:
new ListPathsAsyncTask(getActivity(), this::scanPaths)
.execute(new ListPathsAsyncTask.LoadingInfo(file, AUDIO_FILE_FILTER));
return true;
}
return false;
});
}
popupMenu.show();
}
@Override
public void onFileSelected(@NotNull File file) {
file = tryGetCanonicalFile(file); // important as we compare the path value later
if (file.isDirectory()) {
setCrumb(new BreadCrumbLayout.Crumb(file), true);
} else {
FileFilter fileFilter =
pathname -> !pathname.isDirectory() && AUDIO_FILE_FILTER.accept(pathname);
new ListSongsAsyncTask(
getActivity(),
file,
(songs, extra) -> {
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(
binding.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()));
}
}
@Override
public void onLoadFinished(@NonNull Loader<List<File>> loader, List<File> data) {
updateAdapter(data);
}
@Override
public void onLoaderReset(@NonNull Loader<List<File>> loader) {
updateAdapter(new LinkedList<>());
}
@Override
public void onMultipleItemAction(MenuItem item, @NotNull ArrayList<File> files) {
final int itemId = item.getItemId();
new ListSongsAsyncTask(
getActivity(),
null,
(songs, extra) ->
SongsMenuHelper.INSTANCE.handleMenuClick(requireActivity(), songs, itemId))
.execute(new ListSongsAsyncTask.LoadingInfo(files, AUDIO_FILE_FILTER, getFileComparator()));
}
@Override
public void onPrepareOptionsMenu(@NonNull Menu menu) {
super.onPrepareOptionsMenu(menu);
ToolbarContentTintHelper.handleOnPrepareOptionsMenu(requireActivity(), binding.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(), binding.toolbar, menu, getToolbarBackgroundColor(binding.toolbar));
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.action_go_to_start_directory:
setCrumb(
new BreadCrumbLayout.Crumb(
tryGetCanonicalFile(PreferenceUtil.INSTANCE.getStartDirectory())),
true);
return true;
case R.id.action_scan:
BreadCrumbLayout.Crumb crumb = getActiveCrumb();
if (crumb != null) {
//noinspection Convert2MethodRef
new ListPathsAsyncTask(getActivity(), paths -> scanPaths(paths))
.execute(new ListPathsAsyncTask.LoadingInfo(crumb.getFile(), AUDIO_FILE_FILTER));
}
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onQueueChanged() {
super.onQueueChanged();
checkForPadding();
}
@Override
public void onServiceConnected() {
super.onServiceConnected();
checkForPadding();
}
@NonNull
@Override
public MaterialCab openCab(int menuRes, @NotNull MaterialCab.Callback callback) {
if (cab != null && cab.isActive()) {
cab.finish();
}
cab =
new MaterialCab(getMainActivity(), R.id.cab_stub)
.setMenu(menuRes)
.setCloseDrawableRes(R.drawable.ic_close)
.setBackgroundColor(
RetroColorUtil.shiftBackgroundColorForLightText(
ATHUtil.INSTANCE.resolveColor(requireContext(), R.attr.colorSurface)))
.start(callback);
return cab;
}
private void checkForPadding() {
final int count = adapter.getItemCount();
if (binding != null) {
final MarginLayoutParams params = (MarginLayoutParams) binding.coordinatorLayout.getLayoutParams();
params.bottomMargin =
count > 0 && !MusicPlayerRemote.getPlayingQueue().isEmpty()
? DensityUtil.dip2px(requireContext(), 104f)
: DensityUtil.dip2px(requireContext(), 54f);
binding.coordinatorLayout.setLayoutParams(params);
}
}
private void checkIsEmpty() {
if (binding != null) {
binding.emptyEmoji.setText(getEmojiByUnicode(0x1F631));
binding.empty.setVisibility(
adapter == null || adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
}
}
@Nullable
private BreadCrumbLayout.Crumb getActiveCrumb() {
if (binding != null) {
return binding.breadCrumbs.size() > 0
? binding.breadCrumbs.getCrumb(binding.breadCrumbs.getActiveIndex())
: null;
}
return null;
}
private String getEmojiByUnicode(int unicode) {
return new String(Character.toChars(unicode));
}
private Comparator<File> getFileComparator() {
return fileComparator;
}
private void saveScrollPosition() {
BreadCrumbLayout.Crumb crumb = getActiveCrumb();
if (crumb != null) {
crumb.setScrollPosition(
((LinearLayoutManager) binding.recyclerView.getLayoutManager()).findFirstVisibleItemPosition());
}
}
private void scanPaths(@Nullable String[] toBeScanned) {
if (getActivity() == null) {
return;
}
if (toBeScanned == null || toBeScanned.length < 1) {
Toast.makeText(getActivity(), R.string.nothing_to_scan, Toast.LENGTH_SHORT).show();
} else {
MediaScannerConnection.scanFile(
getActivity().getApplicationContext(),
toBeScanned,
null,
new UpdateToastMediaScannerCompletionListener(getActivity(), Arrays.asList(toBeScanned)));
}
}
private void setCrumb(BreadCrumbLayout.Crumb crumb, boolean addToHistory) {
if (crumb == null) {
return;
}
String path = crumb.getFile().getPath();
if (path.equals("/") || path.equals("/storage") || path.equals("/storage/emulated")) {
switchToStorageAdapter();
} else {
saveScrollPosition();
binding.breadCrumbs.setActiveOrAdd(crumb, false);
if (addToHistory) {
binding.breadCrumbs.addHistory(crumb);
}
LoaderManager.getInstance(this).restartLoader(LOADER_ID, null, this);
}
}
private void setUpAdapter() {
switchToFileAdapter();
}
private void setUpAppbarColor() {
binding.breadCrumbs.setActivatedContentColor(
ATHUtil.INSTANCE.resolveColor(requireContext(), android.R.attr.textColorPrimary));
binding.breadCrumbs.setDeactivatedContentColor(
ATHUtil.INSTANCE.resolveColor(requireContext(), android.R.attr.textColorSecondary));
}
private void setUpBreadCrumbs() {
binding.breadCrumbs.setCallback(this);
}
private void setUpRecyclerView() {
binding.recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
FastScroller fastScroller = ThemedFastScroller.INSTANCE.create(binding.recyclerView);
binding.recyclerView.setOnApplyWindowInsetsListener(
new ScrollingViewOnApplyWindowInsetsListener(binding.recyclerView, fastScroller));
}
private ArrayList<File> toList(File file) {
ArrayList<File> files = new ArrayList<>(1);
files.add(file);
return files;
}
private void updateAdapter(@NonNull List<File> files) {
adapter.swapDataSet(files);
BreadCrumbLayout.Crumb crumb = getActiveCrumb();
if (crumb != null) {
((LinearLayoutManager) binding.recyclerView.getLayoutManager())
.scrollToPositionWithOffset(crumb.getScrollPosition(), 0);
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
public static class ListPathsAsyncTask
extends ListingFilesDialogAsyncTask<ListPathsAsyncTask.LoadingInfo, String, String[]> {
private final WeakReference<OnPathsListedCallback> onPathsListedCallbackWeakReference;
public ListPathsAsyncTask(Context context, OnPathsListedCallback callback) {
super(context);
onPathsListedCallbackWeakReference = new WeakReference<>(callback);
}
@Override
protected String[] doInBackground(LoadingInfo... params) {
try {
if (isCancelled() || checkCallbackReference() == null) {
return null;
}
LoadingInfo info = params[0];
final String[] paths;
if (info.file.isDirectory()) {
List<File> files = FileUtil.listFilesDeep(info.file, info.fileFilter);
if (isCancelled() || checkCallbackReference() == null) {
return null;
}
paths = new String[files.size()];
for (int i = 0; i < files.size(); i++) {
File f = files.get(i);
paths[i] = FileUtil.safeGetCanonicalPath(f);
if (isCancelled() || checkCallbackReference() == null) {
return null;
}
}
} else {
paths = new String[1];
paths[0] = info.file.getPath();
}
return paths;
} catch (Exception e) {
e.printStackTrace();
cancel(false);
return null;
}
}
@Override
protected void onPostExecute(String[] paths) {
super.onPostExecute(paths);
OnPathsListedCallback callback = checkCallbackReference();
if (callback != null && paths != null) {
callback.onPathsListed(paths);
}
}
@Override
protected void onPreExecute() {
super.onPreExecute();
checkCallbackReference();
}
private OnPathsListedCallback checkCallbackReference() {
OnPathsListedCallback callback = onPathsListedCallbackWeakReference.get();
if (callback == null) {
cancel(false);
}
return callback;
}
public interface OnPathsListedCallback {
void onPathsListed(@NonNull String[] paths);
}
public static class LoadingInfo {
public final File file;
final FileFilter fileFilter;
public LoadingInfo(File file, FileFilter fileFilter) {
this.file = file;
this.fileFilter = fileFilter;
}
}
}
private static class AsyncFileLoader extends WrappedAsyncTaskLoader<List<File>> {
private final WeakReference<FoldersFragment> fragmentWeakReference;
AsyncFileLoader(FoldersFragment foldersFragment) {
super(foldersFragment.requireActivity());
fragmentWeakReference = new WeakReference<>(foldersFragment);
}
@Override
public List<File> loadInBackground() {
FoldersFragment foldersFragment = fragmentWeakReference.get();
File directory = null;
if (foldersFragment != null) {
BreadCrumbLayout.Crumb crumb = foldersFragment.getActiveCrumb();
if (crumb != null) {
directory = crumb.getFile();
}
}
if (directory != null) {
List<File> files = FileUtil.listFiles(directory, AUDIO_FILE_FILTER);
Collections.sort(files, foldersFragment.getFileComparator());
return files;
} else {
return new LinkedList<>();
}
}
}
private static class ListSongsAsyncTask
extends ListingFilesDialogAsyncTask<ListSongsAsyncTask.LoadingInfo, Void, List<Song>> {
private final Object extra;
private final WeakReference<OnSongsListedCallback> callbackWeakReference;
private final WeakReference<Context> contextWeakReference;
ListSongsAsyncTask(Context context, Object extra, OnSongsListedCallback callback) {
super(context);
this.extra = extra;
contextWeakReference = new WeakReference<>(context);
callbackWeakReference = new WeakReference<>(callback);
}
@Override
protected List<Song> doInBackground(LoadingInfo... params) {
try {
LoadingInfo info = params[0];
List<File> files = FileUtil.listFilesDeep(info.files, info.fileFilter);
if (isCancelled() || checkContextReference() == null || checkCallbackReference() == null) {
return null;
}
Collections.sort(files, info.fileComparator);
Context context = checkContextReference();
if (isCancelled() || context == null || checkCallbackReference() == null) {
return null;
}
return FileUtil.matchFilesWithMediaStore(context, files);
} catch (Exception e) {
e.printStackTrace();
cancel(false);
return null;
}
}
@Override
protected void onPostExecute(List<Song> songs) {
super.onPostExecute(songs);
OnSongsListedCallback callback = checkCallbackReference();
if (songs != null && callback != null) {
callback.onSongsListed(songs, extra);
}
}
@Override
protected void onPreExecute() {
super.onPreExecute();
checkCallbackReference();
checkContextReference();
}
private OnSongsListedCallback checkCallbackReference() {
OnSongsListedCallback callback = callbackWeakReference.get();
if (callback == null) {
cancel(false);
}
return callback;
}
private Context checkContextReference() {
Context context = contextWeakReference.get();
if (context == null) {
cancel(false);
}
return context;
}
public interface OnSongsListedCallback {
void onSongsListed(@NonNull List<Song> songs, Object extra);
}
static class LoadingInfo {
final Comparator<File> fileComparator;
final FileFilter fileFilter;
final List<File> files;
LoadingInfo(
@NonNull List<File> files,
@NonNull FileFilter fileFilter,
@NonNull Comparator<File> fileComparator) {
this.fileComparator = fileComparator;
this.fileFilter = fileFilter;
this.files = files;
}
}
}
private abstract static class ListingFilesDialogAsyncTask<Params, Progress, Result>
extends DialogAsyncTask<Params, Progress, Result> {
ListingFilesDialogAsyncTask(Context context) {
super(context);
}
public ListingFilesDialogAsyncTask(Context context, int showDelay) {
super(context, showDelay);
}
@Override
protected Dialog createDialog(@NonNull Context context) {
return new MaterialAlertDialogBuilder(context)
.setTitle(R.string.listing_files)
.setCancelable(false)
.setView(R.layout.loading)
.setOnCancelListener(dialog -> cancel(false))
.setOnDismissListener(dialog -> cancel(false))
.create();
}
}
// https://github.com/DrKLO/Telegram/blob/ab221dafadbc17459d78d9ea3e643ae18e934b16/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertDocumentLayout.java#L939
private void listRoots() {
storageItems.clear();
HashSet<String> paths = new HashSet<>();
String defaultPath = Environment.getExternalStorageDirectory().getPath();
String defaultPathState = Environment.getExternalStorageState();
if (defaultPathState.equals(Environment.MEDIA_MOUNTED) || defaultPathState.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) {
Storage ext = new Storage();
if (Environment.isExternalStorageRemovable()) {
ext.title = "SD Card";
} else {
ext.title = "Internal Storage";
}
ext.file = Environment.getExternalStorageDirectory();
storageItems.add(ext);
paths.add(defaultPath);
}
BufferedReader bufferedReader = null;
try {
bufferedReader = new BufferedReader(new FileReader("/proc/mounts"));
String line;
while ((line = bufferedReader.readLine()) != null) {
if (line.contains("vfat") || line.contains("/mnt")) {
StringTokenizer tokens = new StringTokenizer(line, " ");
tokens.nextToken();
String path = tokens.nextToken();
if (paths.contains(path)) {
continue;
}
if (line.contains("/dev/block/vold")) {
if (!line.contains("/mnt/secure") && !line.contains("/mnt/asec") && !line.contains("/mnt/obb") && !line.contains("/dev/mapper") && !line.contains("tmpfs")) {
if (!new File(path).isDirectory()) {
int index = path.lastIndexOf('/');
if (index != -1) {
String newPath = "/storage/" + path.substring(index + 1);
if (new File(newPath).isDirectory()) {
path = newPath;
}
}
}
paths.add(path);
try {
Storage item = new Storage();
if (path.toLowerCase().contains("sd")) {
item.title = "SD Card";
} else {
item.title = "External Storage";
}
item.file = new File(path);
storageItems.add(item);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
@Override
public void onStorageClicked(@NonNull Storage storage) {
switchToFileAdapter();
setCrumb(
new BreadCrumbLayout.Crumb(
FileUtil.safeGetCanonicalFile(storage.file)),
true);
}
public void switchToFileAdapter() {
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();
}
});
binding.recyclerView.setAdapter(adapter);
checkIsEmpty();
}
public void switchToStorageAdapter() {
listRoots();
storageAdapter = new StorageAdapter(storageItems, this);
binding.recyclerView.setAdapter(storageAdapter);
binding.breadCrumbs.clearCrumbs();
}
}

View File

@ -0,0 +1,817 @@
/*
* Copyright (c) 2020 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.fragments.folder
import android.app.Dialog
import android.content.Context
import android.media.MediaScannerConnection
import android.os.Bundle
import android.os.Environment
import android.text.Html
import android.view.*
import android.webkit.MimeTypeMap
import android.widget.PopupMenu
import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.loader.app.LoaderManager
import androidx.loader.content.Loader
import androidx.navigation.Navigation.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.appthemehelper.ThemeStore.Companion.accentColor
import code.name.monkey.appthemehelper.common.ATHToolbarActivity
import code.name.monkey.appthemehelper.util.ATHUtil.resolveColor
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.App
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.SongFileAdapter
import code.name.monkey.retromusic.adapter.Storage
import code.name.monkey.retromusic.adapter.StorageAdapter
import code.name.monkey.retromusic.adapter.StorageClickListener
import code.name.monkey.retromusic.databinding.FragmentFolderBinding
import code.name.monkey.retromusic.extensions.drawNextToNavbar
import code.name.monkey.retromusic.extensions.navigate
import code.name.monkey.retromusic.extensions.surfaceColor
import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment
import code.name.monkey.retromusic.fragments.folder.FoldersFragment.ListPathsAsyncTask.OnPathsListedCallback
import code.name.monkey.retromusic.fragments.folder.FoldersFragment.ListSongsAsyncTask.OnSongsListedCallback
import code.name.monkey.retromusic.helper.MusicPlayerRemote.openQueue
import code.name.monkey.retromusic.helper.MusicPlayerRemote.playingQueue
import code.name.monkey.retromusic.helper.menu.SongMenuHelper.handleMenuClick
import code.name.monkey.retromusic.helper.menu.SongsMenuHelper
import code.name.monkey.retromusic.interfaces.ICabCallback
import code.name.monkey.retromusic.interfaces.ICabHolder
import code.name.monkey.retromusic.interfaces.ICallbacks
import code.name.monkey.retromusic.interfaces.IMainActivityFragmentCallbacks
import code.name.monkey.retromusic.misc.DialogAsyncTask
import code.name.monkey.retromusic.misc.UpdateToastMediaScannerCompletionListener
import code.name.monkey.retromusic.misc.WrappedAsyncTaskLoader
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.providers.BlacklistStore
import code.name.monkey.retromusic.util.*
import code.name.monkey.retromusic.util.DensityUtil.dip2px
import code.name.monkey.retromusic.util.PreferenceUtil.startDirectory
import code.name.monkey.retromusic.util.ThemedFastScroller.create
import code.name.monkey.retromusic.views.BreadCrumbLayout.Crumb
import code.name.monkey.retromusic.views.BreadCrumbLayout.SelectionCallback
import code.name.monkey.retromusic.views.ScrollingViewOnApplyWindowInsetsListener
import com.afollestad.materialcab.attached.AttachedCab
import com.afollestad.materialcab.attached.destroy
import com.afollestad.materialcab.attached.isActive
import com.afollestad.materialcab.createCab
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.transition.MaterialFadeThrough
import com.google.android.material.transition.MaterialSharedAxis
import java.io.*
import java.lang.ref.WeakReference
import java.util.*
class FoldersFragment : AbsMainActivityFragment(R.layout.fragment_folder),
IMainActivityFragmentCallbacks, ICabHolder, SelectionCallback, ICallbacks,
LoaderManager.LoaderCallbacks<List<File>>, StorageClickListener {
private var _binding: FragmentFolderBinding? = null
private val binding get() = _binding!!
private var adapter: SongFileAdapter? = null
private var storageAdapter: StorageAdapter? = null
private var cab: AttachedCab? = null
private val fileComparator = Comparator { lhs: File, rhs: File ->
if (lhs.isDirectory && !rhs.isDirectory) {
return@Comparator -1
} else if (!lhs.isDirectory && rhs.isDirectory) {
return@Comparator 1
} else {
return@Comparator lhs.name.compareTo(rhs.name, ignoreCase = true)
}
}
private var storageItems = ArrayList<Storage>()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
_binding = FragmentFolderBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
mainActivity.addMusicServiceEventListener(libraryViewModel)
mainActivity.setSupportActionBar(binding.toolbar)
mainActivity.supportActionBar?.title = null
enterTransition = MaterialFadeThrough().apply {
addTarget(binding.recyclerView)
}
setUpBreadCrumbs()
setUpRecyclerView()
setUpAdapter()
setUpTitle()
requireActivity().onBackPressedDispatcher.addCallback(
viewLifecycleOwner,
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (!handleBackPress()) {
remove()
mainActivity.finish()
}
}
})
binding.toolbarContainer.drawNextToNavbar()
binding.appBarLayout.statusBarForeground =
MaterialShapeDrawable.createWithElevationOverlay(requireContext())
}
private fun setUpTitle() {
binding.toolbar.setNavigationOnClickListener { v: View? ->
exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true).setDuration(300)
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false).setDuration(300)
findNavController(v!!).navigate(R.id.searchFragment, null, navOptions)
}
binding.appNameText.text = resources.getString(R.string.folders)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
setHasOptionsMenu(true)
if (savedInstanceState == null) {
switchToFileAdapter()
setCrumb(
Crumb(
FileUtil.safeGetCanonicalFile(startDirectory)
),
true
)
} else {
binding.breadCrumbs.restoreFromStateWrapper(savedInstanceState.getParcelable(CRUMBS))
LoaderManager.getInstance(this).initLoader(LOADER_ID, null, this)
}
}
override fun onPause() {
super.onPause()
saveScrollPosition()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
if (_binding != null) {
outState.putParcelable(CRUMBS, binding.breadCrumbs.stateWrapper)
}
}
override fun handleBackPress(): Boolean {
if (cab != null && cab!!.isActive()) {
cab?.destroy()
return true
}
if (binding.breadCrumbs.popHistory()) {
setCrumb(binding.breadCrumbs.lastHistory(), false)
return true
}
return false
}
override fun onCreateLoader(id: Int, args: Bundle?): Loader<List<File>> {
return AsyncFileLoader(this)
}
override fun onCrumbSelection(crumb: Crumb, index: Int) {
setCrumb(crumb, true)
}
override fun onFileMenuClicked(file: File, view: View) {
val popupMenu = PopupMenu(activity, view)
if (file.isDirectory) {
popupMenu.inflate(R.menu.menu_item_directory)
popupMenu.setOnMenuItemClickListener { item: MenuItem ->
when (val itemId = item.itemId) {
R.id.action_play_next, R.id.action_add_to_current_playing, R.id.action_add_to_playlist, R.id.action_delete_from_device -> {
ListSongsAsyncTask(
activity,
null,
object : OnSongsListedCallback {
override fun onSongsListed(songs: List<Song>, extra: Any?) {
if (songs.isNotEmpty()) {
SongsMenuHelper.handleMenuClick(
requireActivity(), songs, itemId
)
}
}
})
.execute(
ListSongsAsyncTask.LoadingInfo(
toList(file), AUDIO_FILE_FILTER, fileComparator
)
)
return@setOnMenuItemClickListener true
}
R.id.action_add_to_blacklist -> {
BlacklistStore.getInstance(App.getContext()).addPath(file)
return@setOnMenuItemClickListener true
}
R.id.action_set_as_start_directory -> {
startDirectory = file
Toast.makeText(
activity,
String.format(getString(R.string.new_start_directory), file.path),
Toast.LENGTH_SHORT
)
.show()
return@setOnMenuItemClickListener true
}
R.id.action_scan -> {
ListPathsAsyncTask(
activity,
object : OnPathsListedCallback {
override fun onPathsListed(paths: Array<String?>) {
scanPaths(paths)
}
})
.execute(ListPathsAsyncTask.LoadingInfo(file, AUDIO_FILE_FILTER))
return@setOnMenuItemClickListener true
}
}
false
}
} else {
popupMenu.inflate(R.menu.menu_item_file)
popupMenu.setOnMenuItemClickListener { item: MenuItem ->
when (val itemId = item.itemId) {
R.id.action_play_next, R.id.action_add_to_current_playing, R.id.action_add_to_playlist, R.id.action_go_to_album, R.id.action_go_to_artist, R.id.action_share, R.id.action_tag_editor, R.id.action_details, R.id.action_set_as_ringtone, R.id.action_delete_from_device -> {
ListSongsAsyncTask(
activity,
null,
object : OnSongsListedCallback {
override fun onSongsListed(songs: List<Song>, extra: Any?) {
handleMenuClick(
requireActivity(), songs[0], itemId
)
}
})
.execute(
ListSongsAsyncTask.LoadingInfo(
toList(file), AUDIO_FILE_FILTER, fileComparator
)
)
return@setOnMenuItemClickListener true
}
R.id.action_scan -> {
ListPathsAsyncTask(
activity,
object : OnPathsListedCallback {
override fun onPathsListed(paths: Array<String?>) {
scanPaths(paths)
}
})
.execute(ListPathsAsyncTask.LoadingInfo(file, AUDIO_FILE_FILTER))
return@setOnMenuItemClickListener true
}
}
false
}
}
popupMenu.show()
}
override fun onFileSelected(file: File) {
var mFile = file
mFile = tryGetCanonicalFile(mFile) // important as we compare the path value later
if (mFile.isDirectory) {
setCrumb(Crumb(mFile), true)
} else {
val fileFilter = FileFilter { pathname: File ->
!pathname.isDirectory && AUDIO_FILE_FILTER.accept(pathname)
}
ListSongsAsyncTask(
activity,
mFile,
object : OnSongsListedCallback {
override fun onSongsListed(songs: List<Song>, extra: Any?) {
val file1 = extra as File
var startIndex = -1
for (i in songs.indices) {
if (file1
.path
== songs[i].data
) { // path is already canonical here
startIndex = i
break
}
}
if (startIndex > -1) {
openQueue(songs, startIndex, true)
} else {
Snackbar.make(
binding.root,
Html.fromHtml(
String.format(
getString(R.string.not_listed_in_media_store), file1.name
)
),
Snackbar.LENGTH_LONG
)
.setAction(
R.string.action_scan
) {
ListPathsAsyncTask(
requireActivity(),
object : OnPathsListedCallback {
override fun onPathsListed(paths: Array<String?>) {
scanPaths(paths)
}
})
.execute(
ListPathsAsyncTask.LoadingInfo(
file1, AUDIO_FILE_FILTER
)
)
}
.setActionTextColor(accentColor(requireActivity()))
.show()
}
}
})
.execute(
ListSongsAsyncTask.LoadingInfo(
toList(mFile.parentFile), fileFilter, fileComparator
)
)
}
}
override fun onLoadFinished(loader: Loader<List<File>>, data: List<File>) {
updateAdapter(data)
}
override fun onLoaderReset(loader: Loader<List<File>>) {
updateAdapter(LinkedList())
}
override fun onMultipleItemAction(item: MenuItem, files: ArrayList<File>) {
val itemId = item.itemId
ListSongsAsyncTask(
activity,
null,
object : OnSongsListedCallback {
override fun onSongsListed(songs: List<Song>, extra: Any?) {
SongsMenuHelper.handleMenuClick(
requireActivity(),
songs,
itemId
)
}
})
.execute(ListSongsAsyncTask.LoadingInfo(files, AUDIO_FILE_FILTER, fileComparator))
}
override fun onPrepareOptionsMenu(menu: Menu) {
super.onPrepareOptionsMenu(menu)
ToolbarContentTintHelper.handleOnPrepareOptionsMenu(requireActivity(), binding.toolbar)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
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(), binding.toolbar, menu, ATHToolbarActivity.getToolbarBackgroundColor(
binding.toolbar
)
)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_go_to_start_directory -> {
setCrumb(
Crumb(
tryGetCanonicalFile(startDirectory)
),
true
)
return true
}
R.id.action_scan -> {
val crumb = activeCrumb
if (crumb != null) {
ListPathsAsyncTask(
activity,
object : OnPathsListedCallback {
override fun onPathsListed(paths: Array<String?>) {
scanPaths(paths)
}
})
.execute(ListPathsAsyncTask.LoadingInfo(crumb.file, AUDIO_FILE_FILTER))
}
return true
}
}
return super.onOptionsItemSelected(item)
}
override fun onQueueChanged() {
super.onQueueChanged()
checkForPadding()
}
override fun onServiceConnected() {
super.onServiceConnected()
checkForPadding()
}
override fun openCab(menuRes: Int, callback: ICabCallback): AttachedCab {
if (cab != null && cab!!.isActive()) {
cab?.destroy()
}
cab = createCab(R.id.toolbar_container) {
menu(menuRes)
closeDrawable(R.drawable.ic_close)
backgroundColor(literal = RetroColorUtil.shiftBackgroundColor(surfaceColor()))
slideDown()
onCreate { cab, menu -> callback.onCabCreated(cab, menu) }
onSelection {
callback.onCabItemClicked(it)
}
onDestroy { callback.onCabFinished(it) }
}
return cab as AttachedCab
}
private fun checkForPadding() {
val count = adapter?.itemCount ?: 0
if (_binding != null) {
val params = binding.root.layoutParams as ViewGroup.MarginLayoutParams
params.bottomMargin = if (count > 0 && playingQueue.isNotEmpty()) dip2px(
requireContext(),
104f
) else dip2px(requireContext(), 54f)
binding.root.layoutParams = params
}
}
private fun checkIsEmpty() {
if (_binding != null) {
binding.emptyEmoji.text = getEmojiByUnicode(0x1F631)
binding.empty.visibility =
if (adapter == null || adapter!!.itemCount == 0) View.VISIBLE else View.GONE
}
}
private val activeCrumb: Crumb?
get() = if (_binding != null) {
if (binding.breadCrumbs.size() > 0) binding.breadCrumbs.getCrumb(binding.breadCrumbs.activeIndex) else null
} else null
private fun getEmojiByUnicode(unicode: Int): String {
return String(Character.toChars(unicode))
}
private fun saveScrollPosition() {
val crumb = activeCrumb
if (crumb != null) {
crumb.scrollPosition =
(binding.recyclerView.layoutManager as LinearLayoutManager?)!!.findFirstVisibleItemPosition()
}
}
private fun scanPaths(toBeScanned: Array<String?>) {
if (activity == null) {
return
}
if (toBeScanned.isEmpty()) {
Toast.makeText(activity, R.string.nothing_to_scan, Toast.LENGTH_SHORT).show()
} else {
MediaScannerConnection.scanFile(
requireContext(),
toBeScanned,
null,
UpdateToastMediaScannerCompletionListener(activity, listOf(*toBeScanned))
)
}
}
private fun setCrumb(crumb: Crumb?, addToHistory: Boolean) {
if (crumb == null) {
return
}
val path = crumb.file.path
if (path == "/" || path == "/storage" || path == "/storage/emulated") {
switchToStorageAdapter()
} else {
saveScrollPosition()
binding.breadCrumbs.setActiveOrAdd(crumb, false)
if (addToHistory) {
binding.breadCrumbs.addHistory(crumb)
}
LoaderManager.getInstance(this).restartLoader(LOADER_ID, null, this)
}
}
private fun setUpAdapter() {
switchToFileAdapter()
}
private fun setUpBreadCrumbs() {
binding.breadCrumbs.setActivatedContentColor(
resolveColor(requireContext(), android.R.attr.textColorPrimary)
)
binding.breadCrumbs.setDeactivatedContentColor(
resolveColor(requireContext(), android.R.attr.textColorSecondary)
)
binding.breadCrumbs.setCallback(this)
}
private fun setUpRecyclerView() {
binding.recyclerView.layoutManager = LinearLayoutManager(
activity
)
val fastScroller = create(
binding.recyclerView
)
binding.recyclerView.setOnApplyWindowInsetsListener(
ScrollingViewOnApplyWindowInsetsListener(binding.recyclerView, fastScroller)
)
}
private fun toList(file: File): ArrayList<File> {
val files = ArrayList<File>(1)
files.add(file)
return files
}
private fun updateAdapter(files: List<File>) {
adapter?.swapDataSet(files)
val crumb = activeCrumb
if (crumb != null) {
(binding.recyclerView.layoutManager as LinearLayoutManager?)
?.scrollToPositionWithOffset(crumb.scrollPosition, 0)
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
class ListPathsAsyncTask(context: Context?, callback: OnPathsListedCallback) :
ListingFilesDialogAsyncTask<ListPathsAsyncTask.LoadingInfo, String?, Array<String?>>(
context
) {
private val onPathsListedCallbackWeakReference: WeakReference<OnPathsListedCallback> =
WeakReference(callback)
override fun doInBackground(vararg params: LoadingInfo): Array<String?> {
return try {
if (isCancelled || checkCallbackReference() == null) {
return arrayOf()
}
val info = params[0]
val paths: Array<String?>
if (info.file.isDirectory) {
val files = FileUtil.listFilesDeep(info.file, info.fileFilter)
if (isCancelled || checkCallbackReference() == null) {
return arrayOf()
}
paths = arrayOfNulls(files.size)
for (i in files.indices) {
val f = files[i]
paths[i] = FileUtil.safeGetCanonicalPath(f)
if (isCancelled || checkCallbackReference() == null) {
return arrayOf()
}
}
} else {
paths = arrayOfNulls(1)
paths[0] = info.file.path
}
paths
} catch (e: Exception) {
e.printStackTrace()
cancel(false)
arrayOf()
}
}
override fun onPostExecute(paths: Array<String?>) {
super.onPostExecute(paths)
checkCallbackReference()?.onPathsListed(paths)
}
override fun onPreExecute() {
super.onPreExecute()
checkCallbackReference()
}
private fun checkCallbackReference(): OnPathsListedCallback? {
val callback = onPathsListedCallbackWeakReference.get()
if (callback == null) {
cancel(false)
}
return callback
}
interface OnPathsListedCallback {
fun onPathsListed(paths: Array<String?>)
}
class LoadingInfo(val file: File, val fileFilter: FileFilter)
}
private class AsyncFileLoader(foldersFragment: FoldersFragment) :
WrappedAsyncTaskLoader<List<File>>(foldersFragment.requireActivity()) {
private val fragmentWeakReference: WeakReference<FoldersFragment> =
WeakReference(foldersFragment)
override fun loadInBackground(): List<File> {
val foldersFragment = fragmentWeakReference.get()
var directory: File? = null
if (foldersFragment != null) {
val crumb = foldersFragment.activeCrumb
if (crumb != null) {
directory = crumb.file
}
}
return if (directory != null) {
val files = FileUtil.listFiles(
directory,
AUDIO_FILE_FILTER
)
Collections.sort(files, foldersFragment!!.fileComparator)
files
} else {
LinkedList()
}
}
}
private open class ListSongsAsyncTask(
context: Context?,
private val extra: Any?,
callback: OnSongsListedCallback
) : ListingFilesDialogAsyncTask<ListSongsAsyncTask.LoadingInfo, Void, List<Song>>(context) {
private val callbackWeakReference = WeakReference(callback)
private val contextWeakReference = WeakReference(context)
override fun doInBackground(vararg params: LoadingInfo): List<Song> {
return try {
val info = params[0]
val files = FileUtil.listFilesDeep(info.files, info.fileFilter)
if (isCancelled || checkContextReference() == null || checkCallbackReference() == null) {
return emptyList()
}
Collections.sort(files, info.fileComparator)
val context = checkContextReference()
if (isCancelled || context == null || checkCallbackReference() == null) {
emptyList()
} else FileUtil.matchFilesWithMediaStore(context, files)
} catch (e: Exception) {
e.printStackTrace()
cancel(false)
emptyList()
}
}
override fun onPostExecute(songs: List<Song>) {
super.onPostExecute(songs)
checkCallbackReference()?.onSongsListed(songs, extra)
}
override fun onPreExecute() {
super.onPreExecute()
checkCallbackReference()
checkContextReference()
}
private fun checkCallbackReference(): OnSongsListedCallback? {
val callback = callbackWeakReference.get()
if (callback == null) {
cancel(false)
}
return callback
}
private fun checkContextReference(): Context? {
val context = contextWeakReference.get()
if (context == null) {
cancel(false)
}
return context
}
interface OnSongsListedCallback {
fun onSongsListed(songs: List<Song>, extra: Any?)
}
class LoadingInfo(
val files: List<File>,
val fileFilter: FileFilter,
val fileComparator: Comparator<File>
)
}
abstract class ListingFilesDialogAsyncTask<Params, Progress, Result> :
DialogAsyncTask<Params, Progress, Result> {
internal constructor(context: Context?) : super(context)
override fun createDialog(context: Context): Dialog {
return MaterialAlertDialogBuilder(context)
.setTitle(R.string.listing_files)
.setCancelable(false)
.setView(R.layout.loading)
.setOnCancelListener { cancel(false) }
.setOnDismissListener { cancel(false) }
.create()
}
}
override fun onStorageClicked(storage: Storage) {
switchToFileAdapter()
setCrumb(
Crumb(
FileUtil.safeGetCanonicalFile(storage.file)
),
true
)
}
private fun switchToFileAdapter() {
adapter = SongFileAdapter(mainActivity, LinkedList(), R.layout.item_list, this, this)
adapter!!.registerAdapterDataObserver(
object : RecyclerView.AdapterDataObserver() {
override fun onChanged() {
super.onChanged()
checkIsEmpty()
checkForPadding()
}
})
binding.recyclerView.adapter = adapter
checkIsEmpty()
}
private fun switchToStorageAdapter() {
storageItems = FileUtil.listRoots()
storageAdapter = StorageAdapter(storageItems, this)
binding.recyclerView.adapter = storageAdapter
binding.breadCrumbs.clearCrumbs()
}
companion object {
val TAG: String = FoldersFragment::class.java.simpleName
val AUDIO_FILE_FILTER = FileFilter { file: 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 const val CRUMBS = "crumbs"
private const val LOADER_ID = 5
// root
val defaultStartDirectory: File
get() {
val musicDir =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC)
val startFolder = if (musicDir.exists() && musicDir.isDirectory) {
musicDir
} else {
val externalStorage = Environment.getExternalStorageDirectory()
if (externalStorage.exists() && externalStorage.isDirectory) {
externalStorage
} else {
File("/") // root
}
}
return startFolder
}
private fun tryGetCanonicalFile(file: File): File {
return try {
file.canonicalFile
} catch (e: IOException) {
e.printStackTrace()
file
}
}
}
}

View File

@ -33,6 +33,7 @@ import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment
import code.name.monkey.retromusic.helper.menu.GenreMenuHelper
import code.name.monkey.retromusic.model.Genre
import code.name.monkey.retromusic.model.Song
import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.transition.MaterialSharedAxis
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
@ -67,6 +68,8 @@ class GenreDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playlist_
view.doOnPreDraw {
startPostponedEnterTransition()
}
binding.appBarLayout.statusBarForeground =
MaterialShapeDrawable.createWithElevationOverlay(requireContext())
}
private fun setupRecyclerView() {

View File

@ -19,6 +19,7 @@ import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import androidx.activity.addCallback
import androidx.core.os.bundleOf
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.GridLayoutManager
@ -26,10 +27,12 @@ import androidx.recyclerview.widget.LinearLayoutManager
import code.name.monkey.retromusic.EXTRA_GENRE
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.GenreAdapter
import code.name.monkey.retromusic.extensions.navigate
import code.name.monkey.retromusic.fragments.ReloadType
import code.name.monkey.retromusic.fragments.base.AbsRecyclerViewFragment
import code.name.monkey.retromusic.interfaces.IGenreClickListener
import code.name.monkey.retromusic.model.Genre
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.RetroUtil
import com.google.android.gms.cast.framework.CastButtonFactory
import com.google.android.material.transition.MaterialSharedAxis
@ -45,6 +48,10 @@ GenresFragment : AbsRecyclerViewFragment<GenreAdapter, LinearLayoutManager>(),
else
adapter?.swapDataSet(listOf())
})
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
remove()
mainActivity.finish()
}
}
override fun createLayoutManager(): LinearLayoutManager {
@ -82,6 +89,9 @@ GenresFragment : AbsRecyclerViewFragment<GenreAdapter, LinearLayoutManager>(),
override val emptyMessage: Int
get() = R.string.no_genres
override val isShuffleVisible: Boolean
get() = false
companion object {
@JvmField
val TAG: String = GenresFragment::class.java.simpleName

View File

@ -14,19 +14,19 @@
*/
package code.name.monkey.retromusic.fragments.home
import android.app.ActivityOptions
import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.MenuItem.SHOW_AS_ACTION_IF_ROOM
import android.view.View
import androidx.activity.addCallback
import androidx.core.os.bundleOf
import androidx.core.text.HtmlCompat
import androidx.core.view.doOnPreDraw
import androidx.navigation.fragment.FragmentNavigatorExtras
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.common.ATHToolbarActivity
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.*
@ -35,12 +35,14 @@ import code.name.monkey.retromusic.databinding.FragmentBannerHomeBinding
import code.name.monkey.retromusic.databinding.FragmentHomeBinding
import code.name.monkey.retromusic.dialogs.CreatePlaylistDialog
import code.name.monkey.retromusic.dialogs.ImportPlaylistDialog
import code.name.monkey.retromusic.extensions.accentColor
import code.name.monkey.retromusic.extensions.drawNextToNavbar
import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.util.NavigationUtil
import code.name.monkey.retromusic.util.PreferenceUtil
import com.google.android.gms.cast.framework.CastButtonFactory
import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.transition.MaterialFadeThrough
import com.google.android.material.transition.MaterialSharedAxis
@ -53,18 +55,45 @@ class HomeFragment :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = getBinding(PreferenceUtil.isHomeBanner, view)
enterTransition = MaterialFadeThrough()
exitTransition = MaterialFadeThrough()
mainActivity.setSupportActionBar(binding.toolbar)
mainActivity.supportActionBar?.title = null
setStatusBarColorAuto(view)
setupListeners()
binding.titleWelcome.text = String.format("%s", PreferenceUtil.userName)
enterTransition = MaterialFadeThrough().apply {
addTarget(binding.contentContainer)
}
val homeAdapter = HomeAdapter(mainActivity)
binding.recyclerView.apply {
layoutManager = LinearLayoutManager(mainActivity)
adapter = homeAdapter
}
libraryViewModel.getHome().observe(viewLifecycleOwner, {
homeAdapter.swapData(it)
})
loadProfile()
setupTitle()
postponeEnterTransition()
view.doOnPreDraw { startPostponedEnterTransition() }
binding.appBarLayout.statusBarForeground =
MaterialShapeDrawable.createWithElevationOverlay(requireContext())
binding.toolbar.drawNextToNavbar()
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
remove()
mainActivity.finish()
}
}
private fun setupListeners() {
binding.bannerImage?.setOnClickListener {
val options = ActivityOptions.makeSceneTransitionAnimation(
mainActivity,
binding.userImage,
getString(R.string.transition_user_image)
findNavController().navigate(
R.id.user_info_fragment, null, null, FragmentNavigatorExtras(
binding.userImage to "user_image"
)
)
NavigationUtil.goToUserInfo(requireActivity(), options)
reenterTransition = null
}
binding.lastAdded.setOnClickListener {
@ -96,28 +125,12 @@ class HomeFragment :
}
binding.userImage.setOnClickListener {
val options = ActivityOptions.makeSceneTransitionAnimation(
mainActivity,
binding.userImage,
getString(R.string.transition_user_image)
findNavController().navigate(
R.id.user_info_fragment, null, null, FragmentNavigatorExtras(
binding.userImage to "user_image"
)
)
NavigationUtil.goToUserInfo(requireActivity(), options)
}
binding.titleWelcome.text = String.format("%s", PreferenceUtil.userName)
val homeAdapter = HomeAdapter(mainActivity)
binding.recyclerView.apply {
layoutManager = LinearLayoutManager(mainActivity)
adapter = homeAdapter
}
libraryViewModel.getHome().observe(viewLifecycleOwner, {
homeAdapter.swapData(it)
})
loadProfile()
setupTitle()
postponeEnterTransition()
view.doOnPreDraw { startPostponedEnterTransition() }
}
private fun getBinding(homeBanner: Boolean, view: View): HomeBindingAdapter {
@ -137,8 +150,7 @@ class HomeFragment :
MaterialSharedAxis(MaterialSharedAxis.Z, false)
findNavController().navigate(R.id.searchFragment, null, navOptions)
}
val color = ThemeStore.accentColor(requireContext())
val hexColor = String.format("#%06X", 0xFFFFFF and color)
val hexColor = String.format("#%06X", 0xFFFFFF and accentColor())
val appName = HtmlCompat.fromHtml(
"Retro <span style='color:$hexColor';>Music</span>",
HtmlCompat.FROM_HTML_MODE_COMPACT

View File

@ -49,7 +49,7 @@ class LibraryFragment : AbsMainActivityFragment(R.layout.fragment_library) {
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
setHasOptionsMenu(true)
mainActivity.setBottomBarVisibility(true)
mainActivity.setBottomNavVisibility(true)
mainActivity.setSupportActionBar(binding.toolbar)
mainActivity.supportActionBar?.title = null
binding.toolbar.setNavigationOnClickListener {
@ -84,7 +84,7 @@ class LibraryFragment : AbsMainActivityFragment(R.layout.fragment_library) {
navGraph.setStartDestination(categoryInfo.category.id)
}
navController.graph = navGraph
NavigationUI.setupWithNavController(mainActivity.getBottomNavigationView(), navController)
NavigationUI.setupWithNavController(mainActivity.bottomNavigationView, navController)
navController.addOnDestinationChangedListener { _, _, _ ->
binding.appBarLayout.setExpanded(true, true)
}

View File

@ -12,7 +12,7 @@
* See the GNU General Public License for more details.
*
*/
package code.name.monkey.retromusic.fragments
package code.name.monkey.retromusic.fragments.other
import android.os.Bundle
import android.view.View
@ -38,6 +38,7 @@ import code.name.monkey.retromusic.interfaces.IArtistClickListener
import code.name.monkey.retromusic.model.Album
import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.util.RetroUtil
import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.transition.MaterialSharedAxis
class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_detail),
@ -63,6 +64,8 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de
returnTransition = MaterialSharedAxis(MaterialSharedAxis.Y, false)
}
}
binding.appBarLayout.statusBarForeground =
MaterialShapeDrawable.createWithElevationOverlay(requireContext())
postponeEnterTransition()
view.doOnPreDraw { startPostponedEnterTransition() }
}

View File

@ -12,7 +12,7 @@
* See the GNU General Public License for more details.
*
*/
package code.name.monkey.retromusic.activities
package code.name.monkey.retromusic.fragments.other
import android.os.Bundle
import android.text.InputType
@ -20,13 +20,14 @@ import android.view.*
import androidx.core.view.ViewCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.navigation.fragment.findNavController
import androidx.transition.Fade
import androidx.viewpager2.adapter.FragmentStateAdapter
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity
import code.name.monkey.retromusic.activities.MainActivity
import code.name.monkey.retromusic.activities.tageditor.WriteTagsAsyncTask
import code.name.monkey.retromusic.databinding.ActivityLyricsBinding
import code.name.monkey.retromusic.databinding.FragmentLyricsBinding
import code.name.monkey.retromusic.databinding.FragmentNormalLyricsBinding
import code.name.monkey.retromusic.databinding.FragmentSyncedLyricsBinding
import code.name.monkey.retromusic.extensions.accentColor
@ -53,12 +54,16 @@ import org.jaudiotagger.tag.FieldKey
import java.io.File
import java.util.*
class LyricsActivity : AbsMusicServiceActivity() {
class LyricsFragment : AbsMusicServiceFragment(R.layout.fragment_lyrics) {
private lateinit var binding: ActivityLyricsBinding
private var _binding: FragmentLyricsBinding? = null
private val binding get() = _binding!!
private lateinit var song: Song
private val lyricsSectionsAdapter = LyricsSectionsAdapter(this)
val mainActivity: MainActivity
get() = activity as MainActivity
private lateinit var lyricsSectionsAdapter: LyricsSectionsAdapter
private val googleSearchLrcUrl: String
get() {
@ -80,34 +85,30 @@ class LyricsActivity : AbsMusicServiceActivity() {
private fun buildContainerTransform(): MaterialContainerTransform {
val transform = MaterialContainerTransform()
transform.setAllContainerColors(
MaterialColors.getColor(findViewById(R.id.container), R.attr.colorSurface)
MaterialColors.getColor(requireView().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)
binding = ActivityLyricsBinding.inflate(layoutInflater)
setContentView(binding.root)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
enterTransition = Fade()
exitTransition = Fade()
lyricsSectionsAdapter = LyricsSectionsAdapter(requireActivity())
_binding = FragmentLyricsBinding.bind(view)
ViewCompat.setTransitionName(binding.container, "lyrics")
setStatusbarColorAuto()
setTaskDescriptionColorAuto()
setNavigationbarColorAuto()
setupWakelock()
binding.toolbar.setBackgroundColor(surfaceColor())
binding.tabLyrics.setBackgroundColor(surfaceColor())
binding.container.setBackgroundColor(surfaceColor())
ToolbarContentTintHelper.colorBackButton(binding.toolbar)
setSupportActionBar(binding.toolbar)
setupViews()
setupToolbar()
updateTitleSong()
}
private fun setupViews() {
binding.lyricsPager.adapter = lyricsSectionsAdapter
TabLayoutMediator(binding.tabLyrics, binding.lyricsPager) { tab, position ->
@ -119,10 +120,29 @@ class LyricsActivity : AbsMusicServiceActivity() {
}.attach()
// lyricsPager.isUserInputEnabled = false
binding.tabLyrics.setSelectedTabIndicatorColor(ThemeStore.accentColor(this))
binding.tabLyrics.setSelectedTabIndicatorColor(accentColor())
binding.tabLyrics.setTabTextColors(textColorSecondary(), accentColor())
binding.editButton.accentColor()
binding.editButton.setOnClickListener {
when (binding.lyricsPager.currentItem) {
0 -> {
editSyncedLyrics()
}
1 -> {
editNormalLyrics()
}
}
}
}
private fun setupToolbar() {
mainActivity.setSupportActionBar(binding.toolbar)
binding.toolbar.setBackgroundColor(surfaceColor())
ToolbarContentTintHelper.colorBackButton(binding.toolbar)
binding.toolbar.setNavigationOnClickListener {
findNavController().navigateUp()
}
}
override fun onPlayingMetaChanged() {
super.onPlayingMetaChanged()
@ -141,36 +161,27 @@ class LyricsActivity : AbsMusicServiceActivity() {
}
private fun setupWakelock() {
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
requireActivity().window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_search, menu)
return super.onCreateOptionsMenu(menu)
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_search, menu)
return super.onCreateOptionsMenu(menu, inflater)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
finish()
findNavController().navigateUp()
return true
}
if (item.itemId == R.id.action_search) {
RetroUtil.openUrl(
this, when (binding.lyricsPager.currentItem) {
requireActivity(), when (binding.lyricsPager.currentItem) {
0 -> syairSearchLrcUrl
1 -> googleSearchLrcUrl
else -> googleSearchLrcUrl
}
)
} else if (item.itemId == R.id.action_edit) {
when (binding.lyricsPager.currentItem) {
0 -> {
editSyncedLyrics()
}
1 -> {
editNormalLyrics()
}
}
}
return super.onOptionsItemSelected(item)
}
@ -185,7 +196,7 @@ class LyricsActivity : AbsMusicServiceActivity() {
e.printStackTrace()
}
MaterialDialog(this, BottomSheet(LayoutMode.WRAP_CONTENT)).show {
MaterialDialog(requireContext(), BottomSheet(LayoutMode.WRAP_CONTENT)).show {
title(res = R.string.edit_normal_lyrics)
input(
hintRes = R.string.paste_lyrics_here,
@ -194,7 +205,7 @@ class LyricsActivity : AbsMusicServiceActivity() {
) { _, input ->
val fieldKeyValueMap = EnumMap<FieldKey, String>(FieldKey::class.java)
fieldKeyValueMap[FieldKey.LYRICS] = input.toString()
WriteTagsAsyncTask(this@LyricsActivity).execute(
WriteTagsAsyncTask(requireActivity()).execute(
LoadingInfo(
listOf(song.data), fieldKeyValueMap, null
)
@ -217,7 +228,7 @@ class LyricsActivity : AbsMusicServiceActivity() {
}
val content: String = LyricUtil.getStringFromLrc(lrcFile)
MaterialDialog(this, BottomSheet(LayoutMode.WRAP_CONTENT)).show {
MaterialDialog(requireContext(), BottomSheet(LayoutMode.WRAP_CONTENT)).show {
title(res = R.string.edit_synced_lyrics)
input(
hintRes = R.string.paste_timeframe_lyrics_here,
@ -320,10 +331,10 @@ class LyricsActivity : AbsMusicServiceActivity() {
private fun setupLyricsView() {
binding.lyricsView.apply {
setCurrentColor(ThemeStore.accentColor(context))
setTimeTextColor(ThemeStore.accentColor(context))
setTimelineColor(ThemeStore.accentColor(context))
setTimelineTextColor(ThemeStore.accentColor(context))
setCurrentColor(accentColor())
setTimeTextColor(accentColor())
setTimelineColor(accentColor())
setTimelineTextColor(accentColor())
setDraggable(true, LrcView.OnPlayClickListener {
MusicPlayerRemote.seekTo(it.toInt())
return@OnPlayClickListener true
@ -356,5 +367,8 @@ class LyricsActivity : AbsMusicServiceActivity() {
}
}
override fun onDestroyView() {
super.onDestroyView()
(requireActivity() as MainActivity).expandPanel()
}
}

View File

@ -12,7 +12,7 @@
* See the GNU General Public License for more details.
*
*/
package code.name.monkey.retromusic.fragments
package code.name.monkey.retromusic.fragments.other
import android.animation.ObjectAnimator
import android.annotation.SuppressLint
@ -32,8 +32,6 @@ import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.extensions.textColorPrimary
import code.name.monkey.retromusic.extensions.textColorSecondary
import code.name.monkey.retromusic.fragments.base.AbsMusicServiceFragment
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler

View File

@ -0,0 +1,159 @@
/*
* Copyright (c) 2020 Hemanth Savarla.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*/
package code.name.monkey.retromusic.fragments.other
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.song.PlayingQueueAdapter
import code.name.monkey.retromusic.fragments.base.AbsRecyclerViewFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import com.h6ah4i.android.widget.advrecyclerview.animator.DraggableItemAnimator
import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager
import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager
import com.h6ah4i.android.widget.advrecyclerview.touchguard.RecyclerViewTouchActionGuardManager
import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils
/**
* Created by hemanths on 2019-12-08.
*/
class PlayingQueueRVFragment : AbsRecyclerViewFragment<PlayingQueueAdapter, LinearLayoutManager>() {
private lateinit var wrappedAdapter: RecyclerView.Adapter<*>
private var recyclerViewDragDropManager: RecyclerViewDragDropManager? = null
private var recyclerViewSwipeManager: RecyclerViewSwipeManager? = null
private var recyclerViewTouchActionGuardManager: RecyclerViewTouchActionGuardManager? = null
override val titleRes: Int
get() = R.string.now_playing_queue
override val isShuffleVisible: Boolean
get() = false
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupRecyclerView()
setupToolbar()
}
private fun setupToolbar() {
toolbar().apply {
setNavigationOnClickListener {
findNavController().navigateUp()
}
setNavigationIcon(R.drawable.ic_keyboard_backspace_black)
}
}
private fun setupRecyclerView() {
recyclerViewTouchActionGuardManager = RecyclerViewTouchActionGuardManager()
recyclerViewDragDropManager = RecyclerViewDragDropManager()
recyclerViewSwipeManager = RecyclerViewSwipeManager()
val animator = DraggableItemAnimator()
animator.supportsChangeAnimations = false
wrappedAdapter =
recyclerViewDragDropManager?.createWrappedAdapter(adapter!!) as RecyclerView.Adapter<*>
wrappedAdapter =
recyclerViewSwipeManager?.createWrappedAdapter(wrappedAdapter) as RecyclerView.Adapter<*>
recyclerView.layoutManager = layoutManager
recyclerView.adapter = wrappedAdapter
recyclerView.itemAnimator = animator
recyclerViewTouchActionGuardManager?.attachRecyclerView(recyclerView)
recyclerViewDragDropManager?.attachRecyclerView(recyclerView)
recyclerViewSwipeManager?.attachRecyclerView(recyclerView)
layoutManager?.scrollToPositionWithOffset(MusicPlayerRemote.position + 1, 0)
}
override fun createLayoutManager(): LinearLayoutManager {
return LinearLayoutManager(requireContext())
}
override fun createAdapter(): PlayingQueueAdapter {
return PlayingQueueAdapter(
requireActivity() as AppCompatActivity,
MusicPlayerRemote.playingQueue.toMutableList(),
MusicPlayerRemote.position,
R.layout.item_queue
)
}
override fun onServiceConnected() {
super.onServiceConnected()
updateQueue()
}
override fun onQueueChanged() {
super.onQueueChanged()
updateQueue()
mainActivity.hideBottomSheet(true)
}
override fun onPlayingMetaChanged() {
updateQueuePosition()
mainActivity.hideBottomSheet(true)
}
private fun updateQueuePosition() {
adapter?.setCurrent(MusicPlayerRemote.position)
resetToCurrentPosition()
}
private fun updateQueue() {
adapter?.swapDataSet(MusicPlayerRemote.playingQueue, MusicPlayerRemote.position)
resetToCurrentPosition()
}
private fun resetToCurrentPosition() {
recyclerView.stopScroll()
layoutManager?.scrollToPositionWithOffset(MusicPlayerRemote.position + 1, 0)
}
override fun onPause() {
recyclerViewDragDropManager?.cancelDrag()
super.onPause()
}
override val emptyMessage: Int
get() = R.string.no_playing_queue
override fun onDestroyView() {
super.onDestroyView()
if (recyclerViewDragDropManager != null) {
recyclerViewDragDropManager?.release()
recyclerViewDragDropManager = null
}
if (recyclerViewSwipeManager != null) {
recyclerViewSwipeManager?.release()
recyclerViewSwipeManager = null
}
WrapperAdapterUtils.releaseAll(wrappedAdapter)
}
companion object {
@JvmField
val TAG: String = PlayingQueueRVFragment::class.java.simpleName
@JvmStatic
fun newInstance(): PlayingQueueRVFragment {
return PlayingQueueRVFragment()
}
}
}

View File

@ -12,25 +12,34 @@
* See the GNU General Public License for more details.
*
*/
package code.name.monkey.retromusic.activities
package code.name.monkey.retromusic.fragments.other
import android.app.Activity
import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.Bitmap
import android.graphics.Color
import android.net.Uri
import android.os.Bundle
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.view.doOnPreDraw
import androidx.core.view.updateLayoutParams
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.retromusic.Constants.USER_BANNER
import code.name.monkey.retromusic.Constants.USER_PROFILE
import code.name.monkey.retromusic.activities.base.AbsBaseActivity
import code.name.monkey.retromusic.databinding.ActivityUserInfoBinding
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentUserInfoBinding
import code.name.monkey.retromusic.extensions.accentColor
import code.name.monkey.retromusic.extensions.applyToolbar
import code.name.monkey.retromusic.fragments.LibraryViewModel
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.util.ImageUtil
@ -43,27 +52,39 @@ 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 com.google.android.material.transition.MaterialContainerTransform
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
class UserInfoActivity : AbsBaseActivity() {
class UserInfoFragment : Fragment() {
private lateinit var binding: ActivityUserInfoBinding
private var _binding: FragmentUserInfoBinding? = null
private val binding get() = _binding!!
private val libraryViewModel: LibraryViewModel by sharedViewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityUserInfoBinding.inflate(layoutInflater)
setContentView(binding.root)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
setLightNavigationBar(true)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
sharedElementEnterTransition = MaterialContainerTransform().apply {
drawingViewId = R.id.fragment_container
duration = 300L
scrimColor = Color.TRANSPARENT
}
_binding = FragmentUserInfoBinding.inflate(layoutInflater)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
applyToolbar(binding.toolbar)
binding.nameContainer.accentColor()
@ -80,20 +101,35 @@ class UserInfoActivity : AbsBaseActivity() {
binding.next.setOnClickListener {
val nameString = binding.name.text.toString().trim { it <= ' ' }
if (TextUtils.isEmpty(nameString)) {
Toast.makeText(this, "Umm you're name can't be empty!", Toast.LENGTH_SHORT).show()
Toast.makeText(
requireContext(),
"Umm you're name can't be empty!",
Toast.LENGTH_SHORT
).show()
return@setOnClickListener
}
PreferenceUtil.userName = nameString
setResult(Activity.RESULT_OK)
finish()
findNavController().navigateUp()
}
val textColor =
MaterialValueHelper.getPrimaryTextColor(this, ColorUtil.isColorLight(accentColor()))
MaterialValueHelper.getPrimaryTextColor(
requireContext(),
ColorUtil.isColorLight(accentColor())
)
binding.next.backgroundTintList = ColorStateList.valueOf(accentColor())
binding.next.iconTint = ColorStateList.valueOf(textColor)
binding.next.setTextColor(textColor)
loadProfile()
postponeEnterTransition()
view.doOnPreDraw {
startPostponedEnterTransition()
}
libraryViewModel.getFabMargin().observe(viewLifecycleOwner, {
binding.next.updateLayoutParams<ViewGroup.MarginLayoutParams> {
bottomMargin = it
}
})
}
private fun loadProfile() {
@ -112,7 +148,7 @@ class UserInfoActivity : AbsBaseActivity() {
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
onBackPressed()
findNavController().navigateUp()
}
return super.onOptionsItemSelected(item)
}
@ -133,7 +169,7 @@ class UserInfoActivity : AbsBaseActivity() {
.start(PICK_IMAGE_REQUEST)
}
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK && requestCode == PICK_IMAGE_REQUEST) {
val fileUri = data?.data
@ -142,9 +178,9 @@ class UserInfoActivity : AbsBaseActivity() {
val fileUri = data?.data
fileUri?.let { setAndSaveBannerImage(it) }
} else if (resultCode == ImagePicker.RESULT_ERROR) {
Toast.makeText(this, ImagePicker.getError(data), Toast.LENGTH_SHORT).show()
Toast.makeText(requireContext(), ImagePicker.getError(data), Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "Task Cancelled", Toast.LENGTH_SHORT).show()
Toast.makeText(requireContext(), "Task Cancelled", Toast.LENGTH_SHORT).show()
}
}
@ -179,7 +215,7 @@ class UserInfoActivity : AbsBaseActivity() {
private fun saveImage(bitmap: Bitmap, fileName: String) {
CoroutineScope(Dispatchers.IO).launch {
val appDir = applicationContext.filesDir
val appDir = requireContext().filesDir
val file = File(appDir, fileName)
var successful = false
try {
@ -192,7 +228,7 @@ class UserInfoActivity : AbsBaseActivity() {
}
if (successful) {
withContext(Dispatchers.Main) {
Toast.makeText(this@UserInfoActivity, "Updated", Toast.LENGTH_SHORT).show()
Toast.makeText(requireContext(), "Updated", Toast.LENGTH_SHORT).show()
}
}
}
@ -227,6 +263,11 @@ class UserInfoActivity : AbsBaseActivity() {
.into(binding.userImage)
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
companion object {
private const val PICK_IMAGE_REQUEST = 9002
private const val PICK_BANNER_REQUEST = 9004

View File

@ -12,7 +12,7 @@
* See the GNU General Public License for more details.
*
*/
package code.name.monkey.retromusic.fragments
package code.name.monkey.retromusic.fragments.other
import android.content.Context
import android.graphics.Color

View File

@ -281,7 +281,7 @@ class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_playe
private fun updatePlayingQueue() {
binding.viewPager.apply {
adapter = AlbumCoverPagerAdapter(childFragmentManager, MusicPlayerRemote.playingQueue)
adapter!!.notifyDataSetChanged()
adapter?.notifyDataSetChanged()
currentItem = MusicPlayerRemote.position
onPageSelected(MusicPlayerRemote.position)
}

View File

@ -21,6 +21,7 @@ import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentAdaptivePlayerBinding
import code.name.monkey.retromusic.extensions.drawAboveSystemBars
import code.name.monkey.retromusic.extensions.surfaceColor
import code.name.monkey.retromusic.extensions.textColorPrimary
import code.name.monkey.retromusic.extensions.textColorSecondary
@ -46,6 +47,7 @@ class AdaptiveFragment : AbsPlayerFragment(R.layout.fragment_adaptive_player) {
_binding = FragmentAdaptivePlayerBinding.bind(view)
setUpSubFragments()
setUpPlayerToolbar()
binding.root.drawAboveSystemBars()
}
private fun setUpSubFragments() {

View File

@ -24,6 +24,7 @@ import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.NEW_BLUR_AMOUNT
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentBlurBinding
import code.name.monkey.retromusic.extensions.drawAboveSystemBars
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment
import code.name.monkey.retromusic.glide.BlurTransformation
@ -32,6 +33,7 @@ import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.PreferenceUtil.blurAmount
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
class BlurPlayerFragment : AbsPlayerFragment(R.layout.fragment_blur),
@ -54,6 +56,7 @@ class BlurPlayerFragment : AbsPlayerFragment(R.layout.fragment_blur),
_binding = FragmentBlurBinding.bind(view)
setUpSubFragments()
setUpPlayerToolbar()
binding.playerToolbar.drawAboveSystemBars()
}
private fun setUpSubFragments() {
@ -108,8 +111,6 @@ class BlurPlayerFragment : AbsPlayerFragment(R.layout.fragment_blur),
get() = lastColor
private fun updateBlur() {
val blurAmount = PreferenceManager.getDefaultSharedPreferences(requireContext())
.getInt(NEW_BLUR_AMOUNT, 25)
binding.colorBackground.clearColorFilter()
GlideApp.with(requireActivity()).asBitmapPalette()
.songCoverOptions(MusicPlayerRemote.currentSong)

View File

@ -21,6 +21,7 @@ import androidx.appcompat.widget.Toolbar
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentCardPlayerBinding
import code.name.monkey.retromusic.extensions.drawAboveSystemBars
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment
import code.name.monkey.retromusic.fragments.player.normal.PlayerFragment
@ -82,6 +83,7 @@ class CardFragment : AbsPlayerFragment(R.layout.fragment_card_player) {
_binding = FragmentCardPlayerBinding.bind(view)
setUpSubFragments()
setUpPlayerToolbar()
(binding.playbackControlsFragment.parent as View).drawAboveSystemBars()
}
private fun setUpSubFragments() {

View File

@ -24,6 +24,7 @@ import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.NEW_BLUR_AMOUNT
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentCardBlurPlayerBinding
import code.name.monkey.retromusic.extensions.drawAboveSystemBars
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment
import code.name.monkey.retromusic.fragments.player.normal.PlayerFragment
@ -33,6 +34,7 @@ import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.PreferenceUtil.blurAmount
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
class CardBlurFragment : AbsPlayerFragment(R.layout.fragment_card_blur_player),
@ -92,6 +94,7 @@ class CardBlurFragment : AbsPlayerFragment(R.layout.fragment_card_blur_player),
_binding = FragmentCardBlurPlayerBinding.bind(view)
setUpSubFragments()
setUpPlayerToolbar()
binding.cardContainer?.drawAboveSystemBars()
}
private fun setUpSubFragments() {
@ -133,8 +136,6 @@ class CardBlurFragment : AbsPlayerFragment(R.layout.fragment_card_blur_player),
}
private fun updateBlur() {
val blurAmount = PreferenceManager.getDefaultSharedPreferences(requireContext())
.getInt(NEW_BLUR_AMOUNT, 25)
binding.colorBackground.clearColorFilter()
GlideApp.with(requireActivity()).asBitmapPalette()
.songCoverOptions(MusicPlayerRemote.currentSong)

View File

@ -33,10 +33,7 @@ import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentCirclePlayerBinding
import code.name.monkey.retromusic.extensions.accentColor
import code.name.monkey.retromusic.extensions.applyColor
import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.extensions.*
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.base.goToAlbum
@ -96,6 +93,7 @@ class CirclePlayerFragment : AbsPlayerFragment(R.layout.fragment_circle_player),
binding.text.setOnClickListener {
goToArtist(requireActivity())
}
binding.songInfo.drawAboveSystemBars()
}
private fun setUpPlayerToolbar() {

View File

@ -38,11 +38,11 @@ import code.name.monkey.retromusic.adapter.song.PlayingQueueAdapter
import code.name.monkey.retromusic.databinding.FragmentClassicPlayerBinding
import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.fragments.VolumeFragment
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.base.goToAlbum
import code.name.monkey.retromusic.fragments.base.goToArtist
import code.name.monkey.retromusic.fragments.other.VolumeFragment
import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper

View File

@ -16,15 +16,14 @@ package code.name.monkey.retromusic.fragments.player.color
import android.animation.ValueAnimator
import android.os.Bundle
import android.os.Handler
import android.view.View
import androidx.appcompat.widget.Toolbar
import androidx.core.animation.doOnEnd
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentColorPlayerBinding
import code.name.monkey.retromusic.extensions.drawAboveSystemBars
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote
@ -61,14 +60,11 @@ class ColorFragment : AbsPlayerFragment(R.layout.fragment_color_player) {
_binding?.root?.setBackgroundColor(color.backgroundColor)
}
animator.start()
serviceActivity?.setLightNavigationBar(ColorUtil.isColorLight(color.backgroundColor))
Handler().post {
ToolbarContentTintHelper.colorizeToolbar(
binding.playerToolbar,
color.secondaryTextColor,
requireActivity()
)
}
ToolbarContentTintHelper.colorizeToolbar(
binding.playerToolbar,
color.secondaryTextColor,
requireActivity()
)
}
override fun onFavoriteToggled() {
@ -116,6 +112,7 @@ class ColorFragment : AbsPlayerFragment(R.layout.fragment_color_player) {
val playerAlbumCoverFragment =
childFragmentManager.findFragmentById(R.id.playerAlbumCoverFragment) as PlayerAlbumCoverFragment
playerAlbumCoverFragment.setCallbacks(this)
playerToolbar().drawAboveSystemBars()
}
private fun setUpSubFragments() {

View File

@ -21,6 +21,7 @@ import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentFitBinding
import code.name.monkey.retromusic.extensions.drawAboveSystemBars
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote
@ -85,6 +86,7 @@ class FitFragment : AbsPlayerFragment(R.layout.fragment_fit) {
_binding = FragmentFitBinding.bind(view)
setUpSubFragments()
setUpPlayerToolbar()
playerToolbar().drawAboveSystemBars()
}
private fun setUpSubFragments() {

View File

@ -26,6 +26,7 @@ import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentFlatPlayerBinding
import code.name.monkey.retromusic.extensions.drawAboveSystemBars
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote
@ -90,6 +91,7 @@ class FlatPlayerFragment : AbsPlayerFragment(R.layout.fragment_flat_player) {
_binding = FragmentFlatPlayerBinding.bind(view)
setUpPlayerToolbar()
setUpSubFragments()
binding.playbackControlsFragment.drawAboveSystemBars()
}
override fun onShow() {

View File

@ -22,6 +22,7 @@ import androidx.appcompat.widget.Toolbar
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentFullBinding
import code.name.monkey.retromusic.extensions.drawAboveSystemBars
import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.extensions.whichFragment
@ -62,6 +63,7 @@ class FullPlayerFragment : AbsPlayerFragment(R.layout.fragment_full) {
setUpPlayerToolbar()
setupArtist()
binding.nextSong.isSelected = true
binding.playbackControlsFragment.drawAboveSystemBars()
}
private fun setupArtist() {
@ -131,12 +133,15 @@ class FullPlayerFragment : AbsPlayerFragment(R.layout.fragment_full) {
private fun updateArtistImage() {
libraryViewModel.artist(MusicPlayerRemote.currentSong.artistId)
.observe(viewLifecycleOwner, { artist ->
GlideApp.with(requireActivity()).asBitmapPalette().artistImageOptions(artist)
.load(RetroGlideExtension.getArtistModel(artist))
.into(object : RetroMusicColoredTarget(binding.artistImage) {
override fun onColorReady(colors: MediaNotificationProcessor) {
}
})
if (artist.id != -1L) {
GlideApp.with(requireActivity()).asBitmapPalette().artistImageOptions(artist)
.load(RetroGlideExtension.getArtistModel(artist))
.into(object : RetroMusicColoredTarget(binding.artistImage) {
override fun onColorReady(colors: MediaNotificationProcessor) {
}
})
}
})
}

View File

@ -29,6 +29,8 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
@ -40,14 +42,12 @@ import code.name.monkey.retromusic.databinding.FragmentGradientPlayerBinding
import code.name.monkey.retromusic.db.PlaylistEntity
import code.name.monkey.retromusic.db.SongEntity
import code.name.monkey.retromusic.db.toSongEntity
import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.extensions.ripAlpha
import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.fragments.VolumeFragment
import code.name.monkey.retromusic.extensions.*
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.base.goToAlbum
import code.name.monkey.retromusic.fragments.base.goToArtist
import code.name.monkey.retromusic.fragments.other.VolumeFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler
@ -81,6 +81,7 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
private var recyclerViewTouchActionGuardManager: RecyclerViewTouchActionGuardManager? = null
private var playingQueueAdapter: PlayingQueueAdapter? = null
private lateinit var linearLayoutManager: LinearLayoutManager
private var bottomInsets = 0
private var _binding: FragmentGradientPlayerBinding? = null
private val binding get() = _binding!!
@ -88,11 +89,11 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
private val bottomSheetCallbackList = object : BottomSheetCallback() {
override fun onSlide(bottomSheet: View, slideOffset: Float) {
mainActivity.getBottomSheetBehavior().setAllowDragging(false)
binding.playerQueueSheet.setPadding(
binding.playerQueueSheet.paddingLeft,
(slideOffset * binding.statusBarLayout.statusBar.height).toInt(),
binding.playerQueueSheet.paddingRight,
binding.playerQueueSheet.paddingBottom
binding.playerQueueSheet.updatePadding(
top = (slideOffset * binding.statusBarLayout.statusBar.height).toInt()
)
binding.container.updatePadding(
bottom = ((1 - slideOffset) * bottomInsets).toInt()
)
}
@ -157,6 +158,14 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
binding.playbackControlsFragment.text.setOnClickListener {
goToArtist(requireActivity())
}
ViewCompat.setOnApplyWindowInsetsListener(
(binding.container)
) { v: View, insets: WindowInsetsCompat ->
bottomInsets = insets.safeGetBottomInsets()
v.updatePadding(bottom = bottomInsets)
insets
}
binding.playbackControlsFragment.root.drawAboveSystemBars()
}
@SuppressLint("ClickableViewAccessibility")

View File

@ -21,6 +21,7 @@ import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentMaterialBinding
import code.name.monkey.retromusic.extensions.drawAboveSystemBars
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment
import code.name.monkey.retromusic.fragments.player.normal.PlayerFragment
@ -93,6 +94,7 @@ class MaterialFragment : AbsPlayerFragment(R.layout.fragment_material) {
_binding = FragmentMaterialBinding.bind(view)
setUpSubFragments()
setUpPlayerToolbar()
playerToolbar().drawAboveSystemBars()
}
private fun setUpSubFragments() {

View File

@ -24,6 +24,7 @@ import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentPlayerBinding
import code.name.monkey.retromusic.extensions.drawAboveSystemBars
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote
@ -120,6 +121,7 @@ class PlayerFragment : AbsPlayerFragment(R.layout.fragment_player) {
_binding = FragmentPlayerBinding.bind(view)
setUpSubFragments()
setUpPlayerToolbar()
playerToolbar().drawAboveSystemBars()
}
override fun onDestroyView() {

View File

@ -21,6 +21,7 @@ import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentPeakPlayerBinding
import code.name.monkey.retromusic.extensions.drawAboveSystemBars
import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
@ -55,6 +56,7 @@ class PeakPlayerFragment : AbsPlayerFragment(R.layout.fragment_peak_player) {
binding.text.setOnClickListener {
goToArtist(requireActivity())
}
binding.root.drawAboveSystemBars()
}
private fun setUpSubFragments() {

View File

@ -21,6 +21,7 @@ import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentPlainPlayerBinding
import code.name.monkey.retromusic.extensions.drawAboveSystemBars
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.base.goToAlbum
import code.name.monkey.retromusic.fragments.base.goToArtist
@ -84,6 +85,7 @@ class PlainPlayerFragment : AbsPlayerFragment(R.layout.fragment_plain_player) {
binding.text.setOnClickListener {
goToArtist(requireActivity())
}
playerToolbar().drawAboveSystemBars()
}
private fun setUpSubFragments() {

View File

@ -21,6 +21,7 @@ import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentSimplePlayerBinding
import code.name.monkey.retromusic.extensions.drawAboveSystemBars
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote
@ -51,6 +52,7 @@ class SimplePlayerFragment : AbsPlayerFragment(R.layout.fragment_simple_player)
_binding = FragmentSimplePlayerBinding.bind(view)
setUpSubFragments()
setUpPlayerToolbar()
playerToolbar().drawAboveSystemBars()
}
private fun setUpSubFragments() {

View File

@ -27,6 +27,7 @@ import androidx.appcompat.widget.Toolbar
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentTinyPlayerBinding
import code.name.monkey.retromusic.extensions.drawAboveSystemBars
import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
@ -147,6 +148,7 @@ class TinyPlayerFragment : AbsPlayerFragment(R.layout.fragment_tiny_player),
binding.text.setOnClickListener {
goToArtist(requireActivity())
}
playerToolbar().drawAboveSystemBars()
}
private fun setUpSubFragments() {

View File

@ -22,10 +22,15 @@ import code.name.monkey.retromusic.extensions.dipToPix
import code.name.monkey.retromusic.extensions.surfaceColor
import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment
import code.name.monkey.retromusic.helper.menu.PlaylistMenuHelper
import code.name.monkey.retromusic.interfaces.ICabCallback
import code.name.monkey.retromusic.interfaces.ICabHolder
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.RetroColorUtil
import com.afollestad.materialcab.MaterialCab
import com.afollestad.materialcab.attached.AttachedCab
import com.afollestad.materialcab.attached.destroy
import com.afollestad.materialcab.attached.isActive
import com.afollestad.materialcab.createCab
import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.transition.MaterialSharedAxis
import com.h6ah4i.android.widget.advrecyclerview.animator.DraggableItemAnimator
import com.h6ah4i.android.widget.advrecyclerview.animator.GeneralItemAnimator
@ -71,6 +76,8 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
requireActivity().onBackPressed()
}
}
binding.appBarLayout.statusBarForeground =
MaterialShapeDrawable.createWithElevationOverlay(requireContext())
}
private fun setUpRecyclerView() {
@ -78,7 +85,7 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
playlist.playlistEntity,
requireActivity(),
ArrayList(),
R.layout.item_list,
R.layout.item_queue,
this
)
@ -158,29 +165,35 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
private fun handleBackPress(): Boolean {
cab?.let {
if (it.isActive) {
it.finish()
if (it.isActive()) {
it.destroy()
return true
}
}
return false
}
private var cab: MaterialCab? = null
private var cab: AttachedCab? = null
override fun openCab(menuRes: Int, callback: MaterialCab.Callback): MaterialCab {
override fun openCab(menuRes: Int, callback: ICabCallback): AttachedCab {
cab?.let {
println("Cab")
if (it.isActive) {
it.finish()
if (it.isActive()) {
it.destroy()
}
}
cab = MaterialCab(mainActivity, R.id.cab_stub)
.setMenu(menuRes)
.setCloseDrawableRes(R.drawable.ic_close)
.setBackgroundColor(RetroColorUtil.shiftBackgroundColorForLightText(surfaceColor()))
.start(callback)
return cab as MaterialCab
cab = createCab(R.id.toolbar_container) {
menu(menuRes)
closeDrawable(R.drawable.ic_close)
backgroundColor(literal = RetroColorUtil.shiftBackgroundColor(surfaceColor()))
slideDown()
onCreate { cab, menu -> callback.onCabCreated(cab, menu) }
onSelection {
callback.onCabItemClicked(it)
}
onDestroy { callback.onCabFinished(it) }
}
return cab as AttachedCab
}
}

View File

@ -25,21 +25,18 @@ import code.name.monkey.retromusic.EXTRA_PLAYLIST
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.playlist.PlaylistAdapter
import code.name.monkey.retromusic.db.PlaylistWithSongs
import code.name.monkey.retromusic.extensions.surfaceColor
import code.name.monkey.retromusic.extensions.navigate
import code.name.monkey.retromusic.fragments.ReloadType
import code.name.monkey.retromusic.fragments.base.AbsRecyclerViewCustomGridSizeFragment
import code.name.monkey.retromusic.helper.SortOrder.PlaylistSortOrder
import code.name.monkey.retromusic.interfaces.ICabHolder
import code.name.monkey.retromusic.interfaces.IPlaylistClickListener
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.RetroColorUtil
import com.afollestad.materialcab.MaterialCab
import com.google.android.gms.cast.framework.CastButtonFactory
import com.google.android.material.transition.MaterialSharedAxis
class PlaylistsFragment :
AbsRecyclerViewCustomGridSizeFragment<PlaylistAdapter, GridLayoutManager>(),
IPlaylistClickListener, ICabHolder {
IPlaylistClickListener {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -50,18 +47,20 @@ class PlaylistsFragment :
adapter?.swapDataSet(listOf())
})
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
if (!handleBackPress()) {
remove()
requireActivity().onBackPressed()
}
remove()
mainActivity.finish()
}
}
override val titleRes: Int
get() = R.string.playlists
override val emptyMessage: Int
get() = R.string.no_playlists
override val isShuffleVisible: Boolean
get() = false
override fun createLayoutManager(): GridLayoutManager {
return GridLayoutManager(requireContext(), getGridSize())
}
@ -71,7 +70,7 @@ class PlaylistsFragment :
requireActivity(),
ArrayList(),
itemLayoutRes(),
this,
null,
this
)
}
@ -181,7 +180,7 @@ class PlaylistsFragment :
}
override fun loadLayoutRes(): Int {
return R.layout.item_grid
return R.layout.item_card
}
override fun saveLayoutRes(layoutRes: Int) {
@ -198,31 +197,4 @@ class PlaylistsFragment :
null
)
}
private fun handleBackPress(): Boolean {
cab?.let {
if (it.isActive) {
it.finish()
return true
}
}
return false
}
private var cab: MaterialCab? = null
override fun openCab(menuRes: Int, callback: MaterialCab.Callback): MaterialCab {
cab?.let {
println("Cab")
if (it.isActive) {
it.finish()
}
}
cab = MaterialCab(mainActivity, R.id.cab_stub)
.setMenu(menuRes)
.setCloseDrawableRes(R.drawable.ic_close)
.setBackgroundColor(RetroColorUtil.shiftBackgroundColorForLightText(surfaceColor()))
.start(callback)
return cab as MaterialCab
}
}
}

View File

@ -14,80 +14,121 @@
*/
package code.name.monkey.retromusic.fragments.queue
import android.content.res.ColorStateList
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.findNavController
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.song.PlayingQueueAdapter
import code.name.monkey.retromusic.fragments.base.AbsRecyclerViewFragment
import code.name.monkey.retromusic.databinding.FragmentPlayingQueueBinding
import code.name.monkey.retromusic.extensions.accentColor
import code.name.monkey.retromusic.fragments.base.AbsMusicServiceFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.ThemedFastScroller
import com.h6ah4i.android.widget.advrecyclerview.animator.DraggableItemAnimator
import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager
import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager
import com.h6ah4i.android.widget.advrecyclerview.touchguard.RecyclerViewTouchActionGuardManager
import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils
/**
* Created by hemanths on 2019-12-08.
*/
class PlayingQueueFragment : AbsRecyclerViewFragment<PlayingQueueAdapter, LinearLayoutManager>() {
class PlayingQueueFragment : AbsMusicServiceFragment(R.layout.fragment_playing_queue) {
private lateinit var wrappedAdapter: RecyclerView.Adapter<*>
private var _binding: FragmentPlayingQueueBinding? = null
private val binding get() = _binding!!
private var wrappedAdapter: RecyclerView.Adapter<*>? = null
private var recyclerViewDragDropManager: RecyclerViewDragDropManager? = null
private var recyclerViewSwipeManager: RecyclerViewSwipeManager? = null
private var recyclerViewTouchActionGuardManager: RecyclerViewTouchActionGuardManager? = null
override val titleRes: Int
get() = R.string.now_playing_queue
private var playingQueueAdapter: PlayingQueueAdapter? = null
private lateinit var linearLayoutManager: LinearLayoutManager
private fun getUpNextAndQueueTime(): String {
val duration = MusicPlayerRemote.getQueueDurationMillis(MusicPlayerRemote.position)
return MusicUtil.buildInfoString(
resources.getString(R.string.up_next),
MusicUtil.getReadableDurationString(duration)
)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupRecyclerView()
_binding = FragmentPlayingQueueBinding.bind(view)
setupToolbar()
setUpRecyclerView()
binding.clearQueue.setOnClickListener {
MusicPlayerRemote.clearQueue()
}
checkForPadding()
}
private fun setupRecyclerView() {
private fun setUpRecyclerView() {
recyclerViewTouchActionGuardManager = RecyclerViewTouchActionGuardManager()
recyclerViewDragDropManager = RecyclerViewDragDropManager()
recyclerViewSwipeManager = RecyclerViewSwipeManager()
val animator = DraggableItemAnimator()
animator.supportsChangeAnimations = false
wrappedAdapter =
recyclerViewDragDropManager?.createWrappedAdapter(adapter!!) as RecyclerView.Adapter<*>
wrappedAdapter =
recyclerViewSwipeManager?.createWrappedAdapter(wrappedAdapter) as RecyclerView.Adapter<*>
recyclerView().layoutManager = layoutManager
recyclerView().adapter = wrappedAdapter
recyclerView().itemAnimator = animator
recyclerViewTouchActionGuardManager?.attachRecyclerView(recyclerView())
recyclerViewDragDropManager?.attachRecyclerView(recyclerView())
recyclerViewSwipeManager?.attachRecyclerView(recyclerView())
layoutManager?.scrollToPositionWithOffset(MusicPlayerRemote.position + 1, 0)
}
override fun createLayoutManager(): LinearLayoutManager {
return LinearLayoutManager(requireContext())
}
override fun createAdapter(): PlayingQueueAdapter {
return PlayingQueueAdapter(
requireActivity() as AppCompatActivity,
playingQueueAdapter = PlayingQueueAdapter(
requireActivity(),
MusicPlayerRemote.playingQueue.toMutableList(),
MusicPlayerRemote.position,
R.layout.item_queue
)
wrappedAdapter = recyclerViewDragDropManager?.createWrappedAdapter(playingQueueAdapter!!)
wrappedAdapter = wrappedAdapter?.let { recyclerViewSwipeManager?.createWrappedAdapter(it) }
linearLayoutManager = LinearLayoutManager(requireContext())
binding.recyclerView.layoutManager = linearLayoutManager
binding.recyclerView.adapter = wrappedAdapter
binding.recyclerView.itemAnimator = animator
recyclerViewTouchActionGuardManager?.attachRecyclerView(binding.recyclerView)
recyclerViewDragDropManager?.attachRecyclerView(binding.recyclerView)
recyclerViewSwipeManager?.attachRecyclerView(binding.recyclerView)
linearLayoutManager.scrollToPositionWithOffset(MusicPlayerRemote.position + 1, 0)
binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (dy > 0) {
binding.clearQueue.shrink()
} else if (dy < 0) {
binding.clearQueue.extend()
}
}
})
ThemedFastScroller.create(binding.recyclerView)
}
override fun onServiceConnected() {
super.onServiceConnected()
updateQueue()
private fun checkForPadding() {
}
override fun onQueueChanged() {
super.onQueueChanged()
if (MusicPlayerRemote.playingQueue.isEmpty()) {
findNavController().navigateUp()
return
}
checkForPadding()
updateQueue()
updateCurrentSong()
}
override fun onMediaStoreChanged() {
updateQueue()
updateCurrentSong()
}
private fun updateCurrentSong() {
binding.toolbar.subtitle = getUpNextAndQueueTime()
}
override fun onPlayingMetaChanged() {
@ -95,50 +136,62 @@ class PlayingQueueFragment : AbsRecyclerViewFragment<PlayingQueueAdapter, Linear
}
private fun updateQueuePosition() {
adapter?.setCurrent(MusicPlayerRemote.position)
playingQueueAdapter?.setCurrent(MusicPlayerRemote.position)
resetToCurrentPosition()
binding.toolbar.subtitle = getUpNextAndQueueTime()
}
private fun updateQueue() {
adapter?.swapDataSet(MusicPlayerRemote.playingQueue, MusicPlayerRemote.position)
resetToCurrentPosition()
playingQueueAdapter?.swapDataSet(MusicPlayerRemote.playingQueue, MusicPlayerRemote.position)
}
private fun resetToCurrentPosition() {
recyclerView().stopScroll()
layoutManager?.scrollToPositionWithOffset(MusicPlayerRemote.position + 1, 0)
binding.recyclerView.stopScroll()
linearLayoutManager.scrollToPositionWithOffset(MusicPlayerRemote.position + 1, 0)
}
override fun onPause() {
recyclerViewDragDropManager?.cancelDrag()
if (recyclerViewDragDropManager != null) {
recyclerViewDragDropManager!!.cancelDrag()
}
super.onPause()
}
override val emptyMessage: Int
get() = R.string.no_playing_queue
override fun onDestroyView() {
super.onDestroyView()
override fun onDestroy() {
if (recyclerViewDragDropManager != null) {
recyclerViewDragDropManager?.release()
recyclerViewDragDropManager!!.release()
recyclerViewDragDropManager = null
}
if (recyclerViewSwipeManager != null) {
recyclerViewSwipeManager?.release()
recyclerViewSwipeManager = null
}
WrapperAdapterUtils.releaseAll(wrappedAdapter)
if (wrappedAdapter != null) {
WrapperAdapterUtils.releaseAll(wrappedAdapter)
wrappedAdapter = null
}
playingQueueAdapter = null
super.onDestroy()
}
companion object {
@JvmField
val TAG: String = PlayingQueueFragment::class.java.simpleName
@JvmStatic
fun newInstance(): PlayingQueueFragment {
return PlayingQueueFragment()
private fun setupToolbar() {
binding.toolbar.subtitle = getUpNextAndQueueTime()
binding.clearQueue.backgroundTintList = ColorStateList.valueOf(accentColor())
ColorStateList.valueOf(
MaterialValueHelper.getPrimaryTextColor(
requireContext(),
ColorUtil.isColorLight(accentColor())
)
).apply {
binding.clearQueue.setTextColor(this)
binding.clearQueue.iconTint = this
}
binding.toolbar.apply {
setNavigationOnClickListener {
findNavController().navigateUp()
}
setNavigationIcon(R.drawable.ic_keyboard_backspace_black)
}
}
}

Some files were not shown because too many files have changed in this diff Show More