Here's a list of changes/features: https://github.com/RetroMusicPlayer/RetroMusicPlayer/releases/tag/v5.0

Internal Changes:
1) Migrated to ViewBinding
2) Migrated to Glide V4
3) Migrated to kotlin version of Material Dialogs
main
Prathamesh More 2021-09-09 00:00:20 +05:30
parent fc42767031
commit bce6dbfa27
421 changed files with 13285 additions and 5757 deletions

View File

@ -1,31 +1,29 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
apply plugin: "androidx.navigation.safeargs.kotlin"
apply plugin: 'kotlin-parcelize'
android {
compileSdkVersion 29
compileSdkVersion 31
buildToolsVersion = '29.0.3'
defaultConfig {
minSdkVersion 21
targetSdkVersion 29
renderscriptTargetApi 29 //must match target sdk and build tools
renderscriptTargetApi 29//must match target sdk and build tools
vectorDrawables.useSupportLibrary = true
applicationId "code.name.monkey.retromusic"
versionCode 10503
versionName '4.0.010' + "_" + getDate()
multiDexEnabled true
versionCode 10519
versionName '5.0.0' + "_" + getDate()
buildConfigField("String", "GOOGLE_PLAY_LICENSING_KEY", "\"${getProperty(getProperties('../public.properties'), 'GOOGLE_PLAY_LICENSE_KEY')}\"")
}
signingConfigs {
release {
Properties properties = getProperties('/Users/apple/Documents/Github/music.jks')
Properties properties = getProperties('retro.properties')
storeFile file(getProperty(properties, 'storeFile'))
keyAlias getProperty(properties, 'keyAlias')
storePassword getProperty(properties, 'storePassword')
@ -36,7 +34,6 @@ android {
release {
//debuggable true
minifyEnabled true
//shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
@ -46,6 +43,10 @@ android {
}
}
buildFeatures{
viewBinding true
}
packagingOptions {
exclude 'META-INF/LICENSE'
exclude 'META-INF/NOTICE'
@ -67,17 +68,11 @@ android {
configurations.all {
resolutionStrategy.force 'com.google.code.findbugs:jsr305:1.3.9'
}
androidExtensions {
experimental = true
}
kapt {
generateStubs = true
}
}
def getProperties(String fileName) {
final Properties properties = new Properties()
def file = file(fileName)
def file = rootProject.file(fileName)
if (file.exists()) {
file.withInputStream { stream -> properties.load(stream) }
}
@ -95,76 +90,76 @@ static def getDate() {
dependencies {
implementation project(':appthemehelper')
implementation 'androidx.multidex:multidex:2.0.1'
implementation "androidx.gridlayout:gridlayout:1.0.0"
implementation "androidx.cardview:cardview:1.0.0"
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.annotation:annotation:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'androidx.annotation:annotation:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'androidx.preference:preference-ktx:1.1.1'
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.fragment:fragment-ktx:1.2.5'
implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.palette:palette-ktx:1.0.0'
def nav_version = "2.3.2"
//Cast Dependencies
implementation 'androidx.mediarouter:mediarouter:1.2.5'
implementation 'com.google.android.gms:play-services-cast-framework:20.0.0'
//WebServer by NanoHttpd
implementation "org.nanohttpd:nanohttpd:2.3.1"
def nav_version = "2.4.0-alpha07"
implementation "androidx.navigation:navigation-runtime-ktx:$nav_version"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
def room_version = "2.2.5"
def room_version = "2.3.0"
implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-ktx:$room_version"
kapt "androidx.room:room-compiler:$room_version"
def lifecycle_version = "2.2.0"
def lifecycle_version = "2.3.1"
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.3.0-alpha04'
implementation 'com.google.android.material:material:1.5.0-alpha03'
def retrofit_version = '2.9.0'
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
implementation 'com.squareup.okhttp3:logging-interceptor:3.6.0'
implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.2'
def material_dialog_version = "0.9.6.0"
def material_dialog_version = "3.3.0"
implementation "com.afollestad.material-dialogs:core:$material_dialog_version"
implementation "com.afollestad.material-dialogs:commons:$material_dialog_version"
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'
def kotlin_coroutines_version = "1.3.8"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.10"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
def kotlin_coroutines_version = "1.5.1"
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-core-ext:$koin_version"
implementation "org.koin:koin-androidx-scope:$koin_version"
implementation "org.koin:koin-androidx-viewmodel:$koin_version"
implementation "org.koin:koin-androidx-fragment:$koin_version"
implementation "org.koin:koin-androidx-ext:$koin_version"
implementation 'com.github.bumptech.glide:glide:3.8.0'
implementation 'com.github.bumptech.glide:okhttp3-integration:1.5.0'
implementation 'com.github.bumptech.glide:glide:4.12.0'
kapt 'com.github.bumptech.glide:compiler:4.12.0'
implementation 'com.github.bumptech.glide:okhttp3-integration:4.12.0'
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 'com.github.ksoichiro:android-observablescrollview:1.6.0'
implementation 'com.github.kabouzeid:recyclerview-fastscroll:1.9-kmod'
implementation 'com.github.AdrienPoupa:jaudiotagger:2.2.3'
implementation 'org.bitbucket.ijabz:jaudiotagger:2.2.5'
implementation 'com.anjlab.android.iab.v3:library:1.1.0'
implementation 'com.r0adkll:slidableactivity:2.1.0'
implementation 'com.heinrichreimersoftware:material-intro:1.6'
implementation 'com.heinrichreimersoftware:material-intro:2.0.0'
implementation 'com.github.dhaval2404:imagepicker:1.7.1'
implementation 'org.jsoup:jsoup:1.11.1'
implementation 'me.zhanghai.android.fastscroll:library:1.1.0'
implementation 'me.jorgecastillo:androidcolorx:0.2.0'
implementation 'org.jsoup:jsoup:1.11.1'
implementation 'me.zhanghai.android.fastscroll:library:1.1.7'
debugImplementation 'com.amitshekhar.android:debug-db:1.0.6'
}

View File

@ -23,6 +23,9 @@
# 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$*
@ -34,9 +37,10 @@
# Glide
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
**[] $VALUES;
public *;
-keep public class * extends com.bumptech.glide.module.AppGlideModule
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
# OkHttp
@ -47,8 +51,6 @@
#-dontwarn
#-ignorewarnings
-dontshrink
-dontobfuscate
-dontwarn org.jaudiotagger.**
-keep class org.jaudiotagger.** { *; }
@ -59,4 +61,8 @@
-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
-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 { *; }

View File

@ -6,6 +6,11 @@
<item name="fontFamily">@font/sans</item>
</style>
<style name="TextViewNormalCompress" parent="TextAppearance.MaterialComponents.Caption">
<item name="android:textSize">14sp</item>
<item name="fontFamily">@font/sans</item>
</style>
<style name="TextViewHeadline4" parent="TextAppearance.MaterialComponents.Headline4">
<item name="fontFamily">@font/sans</item>
</style>
@ -91,6 +96,11 @@
<style name="circleImageView" parent="">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">5%</item>
<item name="cornerSize">16dp</item>
</style>
<style name="BottomSheetItemTextAppearance" parent="Widget.MaterialComponents.BottomNavigationView.Colored">
<item name="android:textSize">13sp</item>
<item name="fontFamily">@font/sans</item>
</style>
</resources>

View File

@ -5,9 +5,10 @@
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<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" />
@ -29,7 +30,7 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.RetroMusic.FollowSystem"
android:usesCleartextTraffic="false"
android:usesCleartextTraffic="true"
tools:ignore="AllowBackup,GoogleAppIndexingWarning"
tools:targetApi="m">
<activity
@ -131,6 +132,20 @@
android:name=".activities.saf.SAFGuideActivity"
android:theme="@style/Theme.Intro" />
<activity
android:name=".cast.ExpandedControlsActivity"
android:label="@string/app_name"
android:launchMode="singleTask"
android:theme="@style/Theme.AppCompat.NoActionBar"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
</intent-filter>
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.MainActivity"/>
</activity>
<provider
android:name=".misc.GenericFileProvider"
android:authorities="${applicationId}.provider"
@ -246,16 +261,24 @@
android:name="com.lge.support.SPLIT_WINDOW"
android:value="true" />
<meta-data
android:name="code.name.monkey.retromusic.glide.RetroMusicGlideModule"
android:value="GlideModule" />
<meta-data
android:name="com.bumptech.glide.integration.okhttp3.OkHttpGlideModule"
android:value="GlideModule" />
<meta-data
android:name="com.android.vending.splits.required"
android:value="true" />
<!-- Android Auto -->
<!-- <meta-data
android:name="com.google.android.gms.car.application"
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"/>
-->
<!-- ChromeCast -->
<meta-data
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="code.name.monkey.retromusic.cast.CastOptionsProvider" />
</application>
</manifest>

View File

@ -6,14 +6,14 @@
"image": "https://i.imgur.com/AoVs9oj.jpg"
},
{
"name": "Lennart Glamann",
"summary": "Play Store Banner & Images",
"link": "https://t.me/FlixbusLennart",
"image": "https://i.imgur.com/Q5Nsx1R.jpg"
"name": "Prathamesh More",
"summary": "Developer",
"link": "https://prathameshmm02.github.io",
"image": "https://i.imgur.com/ZHoOrHx.jpg"
},
{
"name": "Daksh P. Jain",
"summary": "Support Representative & Moderator",
"summary": "Website & GitHub Maintainer",
"link": "https://daksh.eu.org",
"image": "https://i.imgur.com/fnYpg65.jpg"
},
@ -23,9 +23,15 @@
"link": "https://t.me/MilindGoel15",
"image": "https://i.imgur.com/Bz4De21_d.jpg"
},
{
{
"name": "Lennart Glamann",
"summary": "Play Store Banner & Images",
"link": "https://t.me/FlixbusLennart",
"image": "https://i.imgur.com/Q5Nsx1R.jpg"
},
{
"name": "Haythem Gataa",
"summary": "App Logo Designer",
"summary": "App Logo & Banners",
"link": "https://dribbble.com/haythemgataa",
"image": "https://i.imgur.com/g5RuIZq.jpg"
}

View File

@ -6,9 +6,11 @@
word-wrap: break-word;
}
body {
padding-left: 1rem;
padding-right: 1rem;
div{
margin: 20px 10px;
padding: 10px;
border-radius: 10px;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2);
}
h2 {
@ -18,10 +20,7 @@
li {
font-size: 0.85rem;
padding-top: 0.5rem;
padding-left: 0;
padding-right: 0;
color: rgba(0, 0, 0, 0.8);
padding: 0.5rem 0;
}
ul {
@ -44,39 +43,62 @@
margin-block-end: 0.5rem;
}
h3 span {
border-radius: 0.2rem;
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.3rem;
padding-bottom: 0.3rem;
h3 {
margin: 10px 0px;
font-size: 1rem;
}
{style-placeholder}
</style>
</head>
<body>
<h5>April 30, 2020</h5>
<h2>v3.5.110</h2>
<span class="tag"><i>Beta version</i></span>
<h3><span class="colorHeader">What's New</span></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><span class="colorHeader">Improved</span></h3>
<ul>
<li>Improved loading of Songs, Albums, Artists, Genres, Playlists</li>
</ul>
<h2>
<b>Clear the app if it crashes after updating</b>
</h2>
<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>
</ul>
<h3>Fixed</h3>
<ul>
<li> Fixed playlist preview images</li>
<li> Fixed language switching</li>
</ul>
<h3>Improved</h3>
<ul>
<li>Improved playlists tab</li>
<li> Improved genres tab</li>
</ul>
</div>
<!--<h3><span class="colorHeader">Bug fixes</span></h3>
<ul>
<li></li>
</ul>-->
<p>*If you face any UI related issues you clear app data and cache, if itsnot working try to
uninstall and install
again. </p>
</body>
</body>

View File

@ -14,8 +14,8 @@
*/
package code.name.monkey.retromusic
import android.app.Application
import android.widget.Toast
import androidx.multidex.MultiDexApplication
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.VersionUtils
import code.name.monkey.retromusic.Constants.PRO_VERSION_PRODUCT_ID
@ -25,7 +25,7 @@ import com.anjlab.android.iab.v3.TransactionDetails
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin
class App : MultiDexApplication() {
class App : Application() {
lateinit var billingProcessor: BillingProcessor

View File

@ -21,7 +21,8 @@ object Constants {
const val PRO_VERSION_PRODUCT_ID = "pro_version"
const val RATE_ON_GOOGLE_PLAY =
"https://play.google.com/store/apps/details?id=code.name.monkey.retromusic"
const val TRANSLATE = "https://github.com/RetroMusicPlayer/RetroMusicPlayer"
const val TRANSLATE = "https://crowdin.com/project/retromusicplayer"
const val WEBSITE = "https://retromusic.app"
const val GITHUB_PROJECT = "https://github.com/RetroMusicPlayer/RetroMusicPlayer"
const val TELEGRAM_CHANGE_LOG = "https://t.me/retromusiclog"
const val USER_PROFILE = "profile.jpg"
@ -49,7 +50,7 @@ object Constants {
MediaStore.Audio.AudioColumns.ARTIST_ID, // 9
MediaStore.Audio.AudioColumns.ARTIST, // 10
MediaStore.Audio.AudioColumns.COMPOSER, // 11
"album_artist" // 12
ALBUM_ARTIST // 12
)
const val NUMBER_OF_TOP_TRACKS = 99
}
@ -66,7 +67,7 @@ const val EXTRA_SONG_INFO = "extra_song_info"
const val DESATURATED_COLOR = "desaturated_color"
const val BLACK_THEME = "black_theme"
const val KEEP_SCREEN_ON = "keep_screen_on"
const val TYPE_HOME_BANNER = "type_home_banner"
const val TOGGLE_HOME_BANNER = "toggle_home_banner"
const val NOW_PLAYING_SCREEN_ID = "now_playing_screen_id"
const val CAROUSEL_EFFECT = "carousel_effect"
const val COLORED_NOTIFICATION = "colored_notification"
@ -129,6 +130,7 @@ const val START_DIRECTORY = "start_directory"
const val RECENTLY_PLAYED_CUTOFF = "recently_played_interval"
const val LOCK_SCREEN = "lock_screen"
const val ALBUM_ARTISTS_ONLY = "album_artists_only"
const val ALBUM_ARTIST = "album_artist"
const val ALBUM_DETAIL_SONG_SORT_ORDER = "album_detail_song_sort_order"
const val LYRICS_OPTIONS = "lyrics_tab_position"
const val CHOOSE_EQUALIZER = "choose_equalizer"
@ -138,3 +140,11 @@ const val SONG_GRID_STYLE = "song_grid_style"
const val PAUSE_ON_ZERO_VOLUME = "pause_on_zero_volume"
const val FILTER_SONG = "filter_song"
const val EXPAND_NOW_PLAYING_PANEL = "expand_now_playing_panel"
const val EXTRA_ARTIST_NAME = "extra_artist_name"
const val TOGGLE_SUGGESTIONS = "toggle_suggestions"
const val AUDIO_FADE_DURATION = "audio_fade_duration"
const val CROSS_FADE_DURATION = "cross_fade_duration"
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"

View File

@ -5,15 +5,20 @@ import android.content.ContextWrapper;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.LocaleList;
import code.name.monkey.appthemehelper.util.VersionUtils;
import com.google.android.gms.common.annotation.KeepName;
import java.util.Locale;
import code.name.monkey.appthemehelper.util.VersionUtils;
public class LanguageContextWrapper extends ContextWrapper {
public LanguageContextWrapper(Context base) {
super(base);
}
@KeepName
public static LanguageContextWrapper wrap(Context context, Locale newLocale) {
Resources res = context.getResources();
Configuration configuration = res.getConfiguration();

View File

@ -3,6 +3,7 @@ package code.name.monkey.retromusic
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.sqlite.db.SupportSQLiteDatabase
import code.name.monkey.retromusic.auto.AutoMusicProvider
import code.name.monkey.retromusic.db.BlackListStoreDao
import code.name.monkey.retromusic.db.BlackListStoreEntity
import code.name.monkey.retromusic.db.PlaylistWithSongs
@ -85,6 +86,18 @@ private val roomModule = module {
RealRoomRepository(get(), get(), get(), get(), get())
} bind RoomRepository::class
}
private val autoModule = module {
single {
AutoMusicProvider(androidContext(),
get(),
get(),
get(),
get(),
get(),
get()
)
}
}
private val mainModule = module {
single {
androidContext().contentResolver
@ -167,10 +180,11 @@ private val viewModules = module {
)
}
viewModel { (artistId: Long) ->
viewModel { (artistId: Long?, artistName: String?) ->
ArtistDetailsViewModel(
get(),
artistId
artistId,
artistName
)
}
@ -189,4 +203,4 @@ private val viewModules = module {
}
}
val appModules = listOf(mainModule, dataModule, viewModules, networkModule, roomModule)
val appModules = listOf(mainModule, dataModule, autoModule, viewModules, networkModule, roomModule)

View File

@ -4,8 +4,11 @@ import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import org.jetbrains.annotations.NotNull;
public class RetroBottomSheetBehavior<V extends View> extends BottomSheetBehavior<V> {

View File

@ -23,10 +23,12 @@ import android.widget.SeekBar
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity
import code.name.monkey.retromusic.databinding.ActivityDriveModeBinding
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
import code.name.monkey.retromusic.glide.BlurTransformation
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.glide.SongGlideRequest
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper.Callback
@ -35,19 +37,19 @@ import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener
import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import com.bumptech.glide.Glide
import kotlinx.android.synthetic.main.activity_drive_mode.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
/**
* Created by hemanths on 2020-02-02.
*/
class DriveModeActivity : AbsMusicServiceActivity(), Callback {
private lateinit var binding: ActivityDriveModeBinding
private var lastPlaybackControlsColor: Int = Color.GRAY
private var lastDisabledPlaybackControlsColor: Int = Color.GRAY
private lateinit var progressViewUpdateHelper: MusicProgressViewUpdateHelper
@ -55,12 +57,13 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_drive_mode)
binding = ActivityDriveModeBinding.inflate(layoutInflater)
setContentView(binding.root)
setUpMusicControllers()
progressViewUpdateHelper = MusicProgressViewUpdateHelper(this)
lastPlaybackControlsColor = ThemeStore.accentColor(this)
close.setOnClickListener {
binding.close.setOnClickListener {
onBackPressed()
}
}
@ -75,7 +78,7 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
}
private fun setupFavouriteToggle() {
songFavourite.setOnClickListener {
binding.songFavourite.setOnClickListener {
MusicUtil.toggleFavorite(
this@DriveModeActivity,
MusicPlayerRemote.currentSong
@ -88,13 +91,13 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
val isFavourite =
MusicUtil.isFavorite(this@DriveModeActivity, MusicPlayerRemote.currentSong)
withContext(Dispatchers.Main) {
songFavourite.setImageResource(if (isFavourite) R.drawable.ic_favorite else R.drawable.ic_favorite_border)
binding.songFavourite.setImageResource(if (isFavourite) R.drawable.ic_favorite else R.drawable.ic_favorite_border)
}
}
}
private fun setUpProgressSlider() {
progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() {
binding.progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
if (fromUser) {
MusicPlayerRemote.seekTo(progress)
@ -119,20 +122,20 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
private fun setUpPrevNext() {
nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() }
previousButton.setOnClickListener { MusicPlayerRemote.back() }
binding.nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() }
binding.previousButton.setOnClickListener { MusicPlayerRemote.back() }
}
private fun setUpShuffleButton() {
shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() }
binding.shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() }
}
private fun setUpRepeatButton() {
repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() }
binding.repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() }
}
private fun setUpPlayPauseFab() {
playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler())
binding.playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler())
}
override fun onRepeatModeChanged() {
@ -161,19 +164,19 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
private fun updatePlayPauseDrawableState() {
if (MusicPlayerRemote.isPlaying) {
playPauseButton.setImageResource(R.drawable.ic_pause)
binding.playPauseButton.setImageResource(R.drawable.ic_pause)
} else {
playPauseButton.setImageResource(R.drawable.ic_play_arrow)
binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow)
}
}
fun updateShuffleState() {
when (MusicPlayerRemote.shuffleMode) {
MusicService.SHUFFLE_MODE_SHUFFLE -> shuffleButton.setColorFilter(
MusicService.SHUFFLE_MODE_SHUFFLE -> binding.shuffleButton.setColorFilter(
lastPlaybackControlsColor,
PorterDuff.Mode.SRC_IN
)
else -> shuffleButton.setColorFilter(
else -> binding.shuffleButton.setColorFilter(
lastDisabledPlaybackControlsColor,
PorterDuff.Mode.SRC_IN
)
@ -183,19 +186,25 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
private fun updateRepeatState() {
when (MusicPlayerRemote.repeatMode) {
MusicService.REPEAT_MODE_NONE -> {
repeatButton.setImageResource(R.drawable.ic_repeat)
repeatButton.setColorFilter(
binding.repeatButton.setImageResource(R.drawable.ic_repeat)
binding.repeatButton.setColorFilter(
lastDisabledPlaybackControlsColor,
PorterDuff.Mode.SRC_IN
)
}
MusicService.REPEAT_MODE_ALL -> {
repeatButton.setImageResource(R.drawable.ic_repeat)
repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN)
binding.repeatButton.setImageResource(R.drawable.ic_repeat)
binding.repeatButton.setColorFilter(
lastPlaybackControlsColor,
PorterDuff.Mode.SRC_IN
)
}
MusicService.REPEAT_MODE_THIS -> {
repeatButton.setImageResource(R.drawable.ic_repeat_one)
repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN)
binding.repeatButton.setImageResource(R.drawable.ic_repeat_one)
binding.repeatButton.setColorFilter(
lastPlaybackControlsColor,
PorterDuff.Mode.SRC_IN
)
}
}
}
@ -209,29 +218,29 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
private fun updateSong() {
val song = MusicPlayerRemote.currentSong
songTitle.text = song.title
songText.text = song.artistName
binding.songTitle.text = song.title
binding.songText.text = song.artistName
SongGlideRequest.Builder.from(Glide.with(this), song)
.checkIgnoreMediaStore(this)
.generatePalette(this)
.build()
GlideApp.with(this)
.asBitmapPalette()
.songCoverOptions(song)
.load(RetroGlideExtension.getSongModel(song))
.transform(BlurTransformation.Builder(this).build())
.into(object : RetroMusicColoredTarget(image) {
.into(object : RetroMusicColoredTarget(binding.image) {
override fun onColorReady(colors: MediaNotificationProcessor) {
}
})
}
override fun onUpdateProgressViews(progress: Int, total: Int) {
progressSlider.max = total
binding.progressSlider.max = total
val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress)
val animator = ObjectAnimator.ofInt(binding.progressSlider, "progress", progress)
animator.duration = AbsPlayerControlsFragment.SLIDER_ANIMATION_TIME
animator.interpolator = LinearInterpolator()
animator.start()
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
binding.songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
binding.songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
}
}

View File

@ -18,19 +18,23 @@ import android.graphics.Color;
import android.os.Bundle;
import android.view.MenuItem;
import android.webkit.WebView;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.Toolbar;
import org.jetbrains.annotations.Nullable;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import code.name.monkey.appthemehelper.ThemeStore;
import code.name.monkey.appthemehelper.util.ATHUtil;
import code.name.monkey.appthemehelper.util.ColorUtil;
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.activities.base.AbsBaseActivity;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import org.jetbrains.annotations.Nullable;
/** Created by hemanths on 2019-09-27. */
public class LicenseActivity extends AbsBaseActivity {

View File

@ -23,27 +23,29 @@ import android.view.WindowManager
import androidx.core.view.ViewCompat
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity
import code.name.monkey.retromusic.databinding.ActivityLockScreenBinding
import code.name.monkey.retromusic.extensions.whichFragment
import code.name.monkey.retromusic.fragments.player.lockscreen.LockScreenControlsFragment
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.glide.SongGlideRequest
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import com.bumptech.glide.Glide
import com.r0adkll.slidr.Slidr
import com.r0adkll.slidr.model.SlidrConfig
import com.r0adkll.slidr.model.SlidrListener
import com.r0adkll.slidr.model.SlidrPosition
import kotlinx.android.synthetic.main.activity_lock_screen.*
class LockScreenActivity : AbsMusicServiceActivity() {
private lateinit var binding: ActivityLockScreenBinding
private var fragment: LockScreenControlsFragment? = null
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
lockScreenInit()
setContentView(R.layout.activity_lock_screen)
binding = ActivityLockScreenBinding.inflate(layoutInflater)
setContentView(binding.root)
hideStatusBar()
setStatusbarColorAuto()
setNavigationbarColorAuto()
@ -107,9 +109,12 @@ class LockScreenActivity : AbsMusicServiceActivity() {
private fun updateSongs() {
val song = MusicPlayerRemote.currentSong
SongGlideRequest.Builder.from(Glide.with(this), song).checkIgnoreMediaStore(this)
.generatePalette(this).build().dontAnimate()
.into(object : RetroMusicColoredTarget(image) {
GlideApp.with(this)
.asBitmapPalette()
.songCoverOptions(song)
.load(RetroGlideExtension.getSongModel(song))
.dontAnimate()
.into(object : RetroMusicColoredTarget(binding.image) {
override fun onColorReady(colors: MediaNotificationProcessor) {
fragment?.setColor(colors)
}

View File

@ -15,38 +15,64 @@
package code.name.monkey.retromusic.activities
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.WindowManager
import android.text.InputType
import android.view.*
import androidx.core.view.ViewCompat
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
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.tageditor.WriteTagsAsyncTask
import code.name.monkey.retromusic.databinding.ActivityLyricsBinding
import code.name.monkey.retromusic.databinding.FragmentNormalLyricsBinding
import code.name.monkey.retromusic.databinding.FragmentSyncedLyricsBinding
import code.name.monkey.retromusic.extensions.accentColor
import code.name.monkey.retromusic.extensions.surfaceColor
import code.name.monkey.retromusic.extensions.textColorSecondary
import code.name.monkey.retromusic.fragments.base.AbsMusicServiceFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
import code.name.monkey.retromusic.lyrics.LrcView
import code.name.monkey.retromusic.model.LoadingInfo
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.LyricUtil
import code.name.monkey.retromusic.util.RetroUtil
import com.afollestad.materialdialogs.LayoutMode
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.bottomsheets.BottomSheet
import com.afollestad.materialdialogs.input.input
import com.google.android.material.color.MaterialColors
import com.google.android.material.transition.platform.MaterialArcMotion
import com.google.android.material.tabs.TabLayoutMediator
import com.google.android.material.transition.platform.MaterialContainerTransform
import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback
import kotlinx.android.synthetic.main.activity_lyrics.*
import kotlinx.coroutines.*
import org.jaudiotagger.audio.AudioFileIO
import org.jaudiotagger.tag.FieldKey
import java.io.File
import java.util.*
class LyricsActivity : AbsMusicServiceActivity(), MusicProgressViewUpdateHelper.Callback {
private lateinit var updateHelper: MusicProgressViewUpdateHelper
class LyricsActivity : AbsMusicServiceActivity() {
private lateinit var binding: ActivityLyricsBinding
private lateinit var song: Song
private val lyricsSectionsAdapter = LyricsSectionsAdapter(this)
private val googleSearchLrcUrl: String
get() {
var baseUrl = "http://www.google.com/search?"
var query = song.title + "+" + song.artistName
query = "q=" + query.replace(" ", "+") + " .lrc"
query = "q=" + query.replace(" ", "+") + " lyrics"
baseUrl += query
return baseUrl
}
private val syairSearchLrcUrl: String
get() {
var baseUrl = "https://www.syair.info/search?"
var query = song.title + "+" + song.artistName
query = "q=" + query.replace(" ", "+")
baseUrl += query
return baseUrl
}
@ -63,75 +89,55 @@ class LyricsActivity : AbsMusicServiceActivity(), MusicProgressViewUpdateHelper.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_lyrics)
ViewCompat.setTransitionName(container, "lyrics")
binding = ActivityLyricsBinding.inflate(layoutInflater)
setContentView(binding.root)
ViewCompat.setTransitionName(binding.container, "lyrics")
setStatusbarColorAuto()
setTaskDescriptionColorAuto()
setNavigationbarColorAuto()
setupWakelock()
toolbar.setBackgroundColor(surfaceColor())
ToolbarContentTintHelper.colorBackButton(toolbar)
setSupportActionBar(toolbar)
binding.toolbar.setBackgroundColor(surfaceColor())
binding.tabLyrics.setBackgroundColor(surfaceColor())
binding.container.setBackgroundColor(surfaceColor())
ToolbarContentTintHelper.colorBackButton(binding.toolbar)
setSupportActionBar(binding.toolbar)
setupViews()
updateHelper = MusicProgressViewUpdateHelper(this, 500, 1000)
setupLyricsView()
}
private fun setupLyricsView() {
lyricsView.apply {
setCurrentColor(ThemeStore.accentColor(context))
setTimeTextColor(ThemeStore.accentColor(context))
setTimelineColor(ThemeStore.accentColor(context))
setTimelineTextColor(ThemeStore.accentColor(context))
setDraggable(true, LrcView.OnPlayClickListener {
MusicPlayerRemote.seekTo(it.toInt())
return@OnPlayClickListener true
})
}
private fun setupViews() {
binding.lyricsPager.adapter = lyricsSectionsAdapter
TabLayoutMediator(binding.tabLyrics, binding.lyricsPager) { tab, position ->
tab.text = when (position) {
0 -> "Synced Lyrics"
1 -> "Normal Lyrics"
else -> ""
}
}.attach()
// lyricsPager.isUserInputEnabled = false
binding.tabLyrics.setSelectedTabIndicatorColor(ThemeStore.accentColor(this))
binding.tabLyrics.setTabTextColors(textColorSecondary(), accentColor())
}
override fun onResume() {
super.onResume()
updateHelper.start()
}
override fun onPause() {
super.onPause()
updateHelper.stop()
}
override fun onUpdateProgressViews(progress: Int, total: Int) {
lyricsView.updateTime(progress.toLong())
}
private fun loadLRCLyrics() {
lyricsView.setLabel("Empty")
val song = MusicPlayerRemote.currentSong
if (LyricUtil.isLrcOriginalFileExist(song.data)) {
lyricsView.loadLrc(LyricUtil.getLocalLyricOriginalFile(song.data))
} else if (LyricUtil.isLrcFileExist(song.title, song.artistName)) {
lyricsView.loadLrc(LyricUtil.getLocalLyricFile(song.title, song.artistName))
}
}
override fun onPlayingMetaChanged() {
super.onPlayingMetaChanged()
updateTitleSong()
loadLRCLyrics()
}
override fun onServiceConnected() {
super.onServiceConnected()
updateTitleSong()
loadLRCLyrics()
}
private fun updateTitleSong() {
song = MusicPlayerRemote.currentSong
toolbar.title = song.title
toolbar.subtitle = song.artistName
binding.toolbar.title = song.title
binding.toolbar.subtitle = song.artistName
}
private fun setupWakelock() {
@ -149,8 +155,206 @@ class LyricsActivity : AbsMusicServiceActivity(), MusicProgressViewUpdateHelper.
return true
}
if (item.itemId == R.id.action_search) {
RetroUtil.openUrl(this, googleSearchLrcUrl)
RetroUtil.openUrl(
this, 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)
}
private fun editNormalLyrics() {
var content = ""
val file = File(MusicPlayerRemote.currentSong.data)
try {
content = AudioFileIO.read(file).tagOrCreateDefault.getFirst(FieldKey.LYRICS)
} catch (e: Exception) {
e.printStackTrace()
}
MaterialDialog(this, BottomSheet(LayoutMode.WRAP_CONTENT)).show {
title(res = R.string.edit_normal_lyrics)
input(
hintRes = R.string.paste_lyrics_here,
prefill = content,
inputType = InputType.TYPE_TEXT_FLAG_MULTI_LINE or InputType.TYPE_CLASS_TEXT
) { _, input ->
val fieldKeyValueMap = EnumMap<FieldKey, String>(FieldKey::class.java)
fieldKeyValueMap[FieldKey.LYRICS] = input.toString()
WriteTagsAsyncTask(this@LyricsActivity).execute(
LoadingInfo(
listOf(song.data), fieldKeyValueMap, null
)
)
}
positiveButton(res = R.string.save) {
(lyricsSectionsAdapter.fragments[1].first as NormalLyrics).loadNormalLyrics()
}
negativeButton(res = android.R.string.cancel)
}
}
private fun editSyncedLyrics() {
var lrcFile: File? = null
if (LyricUtil.isLrcOriginalFileExist(song.data)) {
lrcFile = LyricUtil.getLocalLyricOriginalFile(song.data)
} else if (LyricUtil.isLrcFileExist(song.title, song.artistName)) {
lrcFile = LyricUtil.getLocalLyricFile(song.title, song.artistName)
}
val content: String = LyricUtil.getStringFromLrc(lrcFile)
MaterialDialog(this, BottomSheet(LayoutMode.WRAP_CONTENT)).show {
title(res = R.string.edit_synced_lyrics)
input(
hintRes = R.string.paste_timeframe_lyrics_here,
prefill = content,
inputType = InputType.TYPE_TEXT_FLAG_MULTI_LINE or InputType.TYPE_CLASS_TEXT
) { _, input ->
LyricUtil.writeLrc(song, input.toString())
}
positiveButton(res = R.string.save) {
(lyricsSectionsAdapter.fragments[0].first as SyncedLyrics).loadLRCLyrics()
}
negativeButton(res = android.R.string.cancel)
}
}
class LyricsSectionsAdapter(fragmentActivity: FragmentActivity) :
FragmentStateAdapter(fragmentActivity) {
val fragments = listOf(
Pair(SyncedLyrics(), R.string.synced_lyrics),
Pair(NormalLyrics(), R.string.normal_lyrics)
)
override fun getItemCount(): Int {
return fragments.size
}
override fun createFragment(position: Int): Fragment {
return fragments[position].first
}
}
class NormalLyrics : AbsMusicServiceFragment(R.layout.fragment_normal_lyrics) {
private var _binding: FragmentNormalLyricsBinding? = null
private val binding get() = _binding!!
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
_binding = FragmentNormalLyricsBinding.bind(view)
loadNormalLyrics()
super.onViewCreated(view, savedInstanceState)
}
fun loadNormalLyrics() {
var lyrics: String? = null
val file = File(MusicPlayerRemote.currentSong.data)
try {
lyrics = AudioFileIO.read(file).tagOrCreateDefault.getFirst(FieldKey.LYRICS)
} catch (e: Exception) {
e.printStackTrace()
}
if (lyrics.isNullOrEmpty()) {
binding.noLyricsFound.visibility = View.VISIBLE
} else {
binding.noLyricsFound.visibility = View.GONE
}
binding.normalLyrics.text = lyrics
}
override fun onPlayingMetaChanged() {
super.onPlayingMetaChanged()
loadNormalLyrics()
}
override fun onServiceConnected() {
super.onServiceConnected()
loadNormalLyrics()
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
class SyncedLyrics : AbsMusicServiceFragment(R.layout.fragment_synced_lyrics),
MusicProgressViewUpdateHelper.Callback {
private var _binding: FragmentSyncedLyricsBinding? = null
private val binding get() = _binding!!
private lateinit var updateHelper: MusicProgressViewUpdateHelper
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
updateHelper = MusicProgressViewUpdateHelper(this, 500, 1000)
_binding = FragmentSyncedLyricsBinding.bind(view)
setupLyricsView()
loadLRCLyrics()
super.onViewCreated(view, savedInstanceState)
}
fun loadLRCLyrics() {
binding.lyricsView.setLabel("Empty")
val song = MusicPlayerRemote.currentSong
if (LyricUtil.isLrcOriginalFileExist(song.data)) {
binding.lyricsView.loadLrc(LyricUtil.getLocalLyricOriginalFile(song.data))
} else if (LyricUtil.isLrcFileExist(song.title, song.artistName)) {
binding.lyricsView.loadLrc(LyricUtil.getLocalLyricFile(song.title, song.artistName))
}
}
private fun setupLyricsView() {
binding.lyricsView.apply {
setCurrentColor(ThemeStore.accentColor(context))
setTimeTextColor(ThemeStore.accentColor(context))
setTimelineColor(ThemeStore.accentColor(context))
setTimelineTextColor(ThemeStore.accentColor(context))
setDraggable(true, LrcView.OnPlayClickListener {
MusicPlayerRemote.seekTo(it.toInt())
return@OnPlayClickListener true
})
}
}
override fun onUpdateProgressViews(progress: Int, total: Int) {
binding.lyricsView.updateTime(progress.toLong())
}
override fun onPlayingMetaChanged() {
super.onPlayingMetaChanged()
loadLRCLyrics()
}
override fun onServiceConnected() {
super.onServiceConnected()
loadLRCLyrics()
}
override fun onResume() {
super.onResume()
updateHelper.start()
}
override fun onPause() {
super.onPause()
updateHelper.stop()
}
}
}

View File

@ -20,38 +20,15 @@ import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import android.view.View
import androidx.lifecycle.lifecycleScope
import androidx.navigation.ui.NavigationUI
import code.name.monkey.retromusic.ADAPTIVE_COLOR_APP
import code.name.monkey.retromusic.ALBUM_COVER_STYLE
import code.name.monkey.retromusic.ALBUM_COVER_TRANSFORM
import code.name.monkey.retromusic.BANNER_IMAGE_PATH
import code.name.monkey.retromusic.BLACK_THEME
import code.name.monkey.retromusic.CAROUSEL_EFFECT
import code.name.monkey.retromusic.CIRCULAR_ALBUM_ART
import code.name.monkey.retromusic.DESATURATED_COLOR
import code.name.monkey.retromusic.EXTRA_SONG_INFO
import code.name.monkey.retromusic.GENERAL_THEME
import code.name.monkey.retromusic.HOME_ARTIST_GRID_STYLE
import code.name.monkey.retromusic.KEEP_SCREEN_ON
import code.name.monkey.retromusic.LANGUAGE_NAME
import code.name.monkey.retromusic.LIBRARY_CATEGORIES
import code.name.monkey.retromusic.NOW_PLAYING_SCREEN_ID
import code.name.monkey.retromusic.PROFILE_IMAGE_PATH
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.ROUND_CORNERS
import code.name.monkey.retromusic.TAB_TEXT_MODE
import code.name.monkey.retromusic.TOGGLE_ADD_CONTROLS
import code.name.monkey.retromusic.TOGGLE_FULL_SCREEN
import code.name.monkey.retromusic.TOGGLE_GENRE
import code.name.monkey.retromusic.TYPE_HOME_BANNER
import code.name.monkey.retromusic.TOGGLE_SEPARATE_LINE
import code.name.monkey.retromusic.TOGGLE_VOLUME
import code.name.monkey.retromusic.USER_NAME
import code.name.monkey.retromusic.activities.base.AbsSlidingMusicPanelActivity
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.extra
import code.name.monkey.retromusic.extensions.findNavController
import code.name.monkey.retromusic.fragments.base.AbsRecyclerViewFragment
import code.name.monkey.retromusic.fragments.home.HomeFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.SearchQueryHelper.getSongs
import code.name.monkey.retromusic.model.CategoryInfo
@ -64,13 +41,13 @@ import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.launch
import org.koin.android.ext.android.get
class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeListener {
class MainActivity : AbsCastActivity(), OnSharedPreferenceChangeListener {
companion object {
const val TAG = "MainActivity"
const val EXPAND_PANEL = "expand_panel"
}
override fun createContentView(): View {
override fun createContentView(): SlidingMusicPanelLayoutBinding {
return wrapSlidingMusicPanel()
}
@ -98,10 +75,50 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis
val categoryInfo: CategoryInfo = PreferenceUtil.libraryCategory.first { it.visible }
if (categoryInfo.visible) {
navGraph.startDestination = categoryInfo.category.id
navGraph.setStartDestination(
if (PreferenceUtil.rememberLastTab) {
PreferenceUtil.lastTab.let {
if (it == 0) {
categoryInfo.category.id
} else {
it
}
}
} else categoryInfo.category.id
)
}
navController.graph = navGraph
NavigationUI.setupWithNavController(getBottomNavigationView(), navController)
getBottomNavigationView().setupWithNavController(navController)
// Scroll Fragment to top
getBottomNavigationView().setOnItemReselectedListener {
supportFragmentManager.findFragmentById(R.id.fragment_container)?.childFragmentManager?.fragments?.get(0)
.also {
if (it is AbsRecyclerViewFragment<*, *>) {
it.scrollToTop()
}
if (it is HomeFragment) {
it.scrollToTop()
}
}
}
navController.addOnDestinationChangedListener { _, destination, _ ->
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)
}
else -> setBottomBarVisibility(false) // Hide Bottom Navigation Bar
}
}
}
private fun saveTab(id: Int) {
PreferenceUtil.lastTab = id
}
override fun onSupportNavigateUp(): Boolean =
@ -112,6 +129,8 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis
PreferenceUtil.registerOnSharedPreferenceChangedListener(this)
val expand = extra<Boolean>(EXPAND_PANEL).value ?: false
if (expand && PreferenceUtil.isExpandPanel) {
setBottomBarVisibility(false)
fromNotification = true
expandPanel()
intent.removeExtra(EXPAND_PANEL)
}
@ -123,7 +142,7 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis
}
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 == TYPE_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 == 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

@ -14,44 +14,51 @@
*/
package code.name.monkey.retromusic.activities
import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.ColorStateList
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.view.View
import androidx.annotation.RequiresApi
import androidx.core.text.HtmlCompat
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.AbsMusicServiceActivity
import code.name.monkey.retromusic.databinding.ActivityPermissionBinding
import code.name.monkey.retromusic.extensions.accentBackgroundColor
import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.util.RingtoneManager
import kotlinx.android.synthetic.main.activity_permission.*
import kotlinx.android.synthetic.main.fragment_library.appNameText
class PermissionActivity : AbsMusicServiceActivity() {
private lateinit var binding: ActivityPermissionBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView((R.layout.activity_permission))
binding = ActivityPermissionBinding.inflate(layoutInflater)
setContentView(binding.root)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setLightNavigationBar(true)
setTaskDescriptionColorAuto()
setupTitle()
storagePermission.setButtonClick {
binding.storagePermission.setButtonClick {
requestPermissions()
}
if (VersionUtils.hasMarshmallow()) audioPermission.show()
audioPermission.setButtonClick {
if (VersionUtils.hasMarshmallow()) binding.audioPermission.show()
binding.audioPermission.setButtonClick {
if (RingtoneManager.requiresDialog(this@PermissionActivity)) {
val intent = Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS)
intent.data = Uri.parse("package:" + applicationContext.packageName)
startActivity(intent)
}
}
finish.accentBackgroundColor()
finish.setOnClickListener {
binding.finish.accentBackgroundColor()
binding.finish.setOnClickListener {
if (hasPermissions()) {
startActivity(
Intent(this, MainActivity::class.java).addFlags(
@ -71,6 +78,32 @@ class PermissionActivity : AbsMusicServiceActivity() {
"Hello there! <br>Welcome to <b>Retro <span style='color:$hexColor';>Music</span></b>",
HtmlCompat.FROM_HTML_MODE_COMPACT
)
appNameText.text = appName
binding.appNameText.text = appName
}
@RequiresApi(Build.VERSION_CODES.M)
override fun onResume() {
if (hasStoragePermission()) {
binding.storagePermission.checkImage.visibility = View.VISIBLE
binding.storagePermission.checkImage.imageTintList =
ColorStateList.valueOf(ThemeStore.accentColor(this))
}
if (hasAudioPermission()) {
binding.audioPermission.checkImage.visibility = View.VISIBLE
binding.audioPermission.checkImage.imageTintList =
ColorStateList.valueOf(ThemeStore.accentColor(this))
}
super.onResume()
}
@RequiresApi(Build.VERSION_CODES.M)
private fun hasStoragePermission(): Boolean {
return checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
}
@RequiresApi(Build.VERSION_CODES.M)
private fun hasAudioPermission(): Boolean {
return Settings.System.canWrite(this)
}
}

View File

@ -24,6 +24,7 @@ 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
@ -34,10 +35,10 @@ import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropM
import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager
import com.h6ah4i.android.widget.advrecyclerview.touchguard.RecyclerViewTouchActionGuardManager
import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils
import kotlinx.android.synthetic.main.activity_playing_queue.*
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
@ -56,7 +57,8 @@ open class PlayingQueueActivity : AbsMusicServiceActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_playing_queue)
binding = ActivityPlayingQueueBinding.inflate(layoutInflater)
setContentView(binding.root)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
@ -65,7 +67,7 @@ open class PlayingQueueActivity : AbsMusicServiceActivity() {
setupToolbar()
setUpRecyclerView()
clearQueue.setOnClickListener {
binding.clearQueue.setOnClickListener {
MusicPlayerRemote.clearQueue()
}
checkForPadding()
@ -100,25 +102,25 @@ open class PlayingQueueActivity : AbsMusicServiceActivity() {
linearLayoutManager = LinearLayoutManager(this)
recyclerView.layoutManager = linearLayoutManager
recyclerView.adapter = wrappedAdapter
recyclerView.itemAnimator = animator
recyclerViewTouchActionGuardManager?.attachRecyclerView(recyclerView)
recyclerViewDragDropManager?.attachRecyclerView(recyclerView)
recyclerViewSwipeManager?.attachRecyclerView(recyclerView)
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)
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (dy > 0) {
clearQueue.shrink()
binding.clearQueue.shrink()
} else if (dy < 0) {
clearQueue.extend()
binding.clearQueue.extend()
}
}
})
ThemedFastScroller.create(recyclerView)
ThemedFastScroller.create(binding.recyclerView)
}
private fun checkForPadding() {
@ -140,7 +142,7 @@ open class PlayingQueueActivity : AbsMusicServiceActivity() {
}
private fun updateCurrentSong() {
toolbar.subtitle = getUpNextAndQueueTime()
binding.toolbar.subtitle = getUpNextAndQueueTime()
}
override fun onPlayingMetaChanged() {
@ -150,7 +152,7 @@ open class PlayingQueueActivity : AbsMusicServiceActivity() {
private fun updateQueuePosition() {
playingQueueAdapter?.setCurrent(MusicPlayerRemote.position)
resetToCurrentPosition()
toolbar.subtitle = getUpNextAndQueueTime()
binding.toolbar.subtitle = getUpNextAndQueueTime()
}
private fun updateQueue() {
@ -159,7 +161,7 @@ open class PlayingQueueActivity : AbsMusicServiceActivity() {
}
private fun resetToCurrentPosition() {
recyclerView.stopScroll()
binding.recyclerView.stopScroll()
linearLayoutManager.scrollToPositionWithOffset(MusicPlayerRemote.position + 1, 0)
}
@ -188,15 +190,15 @@ open class PlayingQueueActivity : AbsMusicServiceActivity() {
}
private fun setupToolbar() {
toolbar.subtitle = getUpNextAndQueueTime()
toolbar.setBackgroundColor(surfaceColor())
setSupportActionBar(toolbar)
clearQueue.backgroundTintList = ColorStateList.valueOf(accentColor())
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 {
clearQueue.setTextColor(this)
clearQueue.iconTint = this
binding.clearQueue.setTextColor(this)
binding.clearQueue.iconTint = this
}
}
}

View File

@ -29,43 +29,45 @@ import code.name.monkey.retromusic.BuildConfig
import code.name.monkey.retromusic.Constants.PRO_VERSION_PRODUCT_ID
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 kotlinx.android.synthetic.main.activity_pro_version.*
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()
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_pro_version)
binding = ActivityProVersionBinding.inflate(layoutInflater)
setContentView(binding.root)
setStatusbarColor(Color.TRANSPARENT)
setLightStatusbar(false)
setNavigationbarColor(Color.BLACK)
setLightNavigationBar(false)
toolbar.navigationIcon?.setTint(Color.WHITE)
toolbar.setNavigationOnClickListener { onBackPressed() }
binding.toolbar.navigationIcon?.setTint(Color.WHITE)
binding.toolbar.setNavigationOnClickListener { onBackPressed() }
restoreButton.isEnabled = false
purchaseButton.isEnabled = false
binding.restoreButton.isEnabled = false
binding.purchaseButton.isEnabled = false
billingProcessor = BillingProcessor(this, BuildConfig.GOOGLE_PLAY_LICENSING_KEY, this)
MaterialUtil.setTint(purchaseButton, true)
MaterialUtil.setTint(binding.purchaseButton, true)
restoreButton.setOnClickListener {
binding.restoreButton.setOnClickListener {
if (restorePurchaseAsyncTask == null || restorePurchaseAsyncTask!!.status != AsyncTask.Status.RUNNING) {
restorePurchase()
}
}
purchaseButton.setOnClickListener {
binding.purchaseButton.setOnClickListener {
billingProcessor.purchase(this@PurchaseActivity, PRO_VERSION_PRODUCT_ID)
}
bannerContainer.backgroundTintList =
binding.bannerContainer.backgroundTintList =
ColorStateList.valueOf(ThemeStore.accentColor(this))
}
@ -99,8 +101,8 @@ class PurchaseActivity : AbsBaseActivity(), BillingProcessor.IBillingHandler {
}
override fun onBillingInitialized() {
restoreButton.isEnabled = true
purchaseButton.isEnabled = true
binding.restoreButton.isEnabled = true
binding.purchaseButton.isEnabled = true
}
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {

View File

@ -23,16 +23,19 @@ 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.appshortcuts.DynamicShortcutManager
import code.name.monkey.retromusic.databinding.ActivitySettingsBinding
import code.name.monkey.retromusic.extensions.applyToolbar
import code.name.monkey.retromusic.extensions.findNavController
import com.afollestad.materialdialogs.color.ColorChooserDialog
import kotlinx.android.synthetic.main.activity_settings.*
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.color.ColorCallback
class SettingsActivity : AbsBaseActivity(), ColorChooserDialog.ColorCallback {
class SettingsActivity : AbsBaseActivity(), ColorCallback {
private lateinit var binding: ActivitySettingsBinding
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings)
binding = ActivitySettingsBinding.inflate(layoutInflater)
setContentView(binding.root)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setLightNavigationBar(true)
@ -41,10 +44,11 @@ class SettingsActivity : AbsBaseActivity(), ColorChooserDialog.ColorCallback {
private fun setupToolbar() {
setTitle(R.string.action_settings)
applyToolbar(toolbar)
applyToolbar(binding.toolbar)
val navController: NavController = findNavController(R.id.contentFrame)
navController.addOnDestinationChangedListener { _, _, _ ->
toolbar.title = navController.currentDestination?.let { getStringFromDestination(it) }
binding.toolbar.title =
navController.currentDestination?.let { getStringFromDestination(it) }
}
}
@ -68,24 +72,18 @@ class SettingsActivity : AbsBaseActivity(), ColorChooserDialog.ColorCallback {
return findNavController(R.id.contentFrame).navigateUp() || super.onSupportNavigateUp()
}
override fun onColorSelection(dialog: ColorChooserDialog, selectedColor: Int) {
when (dialog.title) {
R.string.accent_color -> {
ThemeStore.editTheme(this).accentColor(selectedColor).commit()
if (VersionUtils.hasNougatMR())
DynamicShortcutManager(this).updateDynamicShortcuts()
}
}
recreate()
}
override fun onColorChooserDismissed(dialog: ColorChooserDialog) {
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
onBackPressed()
}
return super.onOptionsItemSelected(item)
}
override fun invoke(dialog: MaterialDialog, color: Int) {
ThemeStore.editTheme(this).accentColor(color).commit()
if (VersionUtils.hasNougatMR())
DynamicShortcutManager(this).updateDynamicShortcuts()
recreate()
}
}

View File

@ -26,15 +26,14 @@ import androidx.core.view.drawToBitmap
import code.name.monkey.appthemehelper.ThemeStore
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.AbsBaseActivity
import code.name.monkey.retromusic.databinding.ActivityShareInstagramBinding
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.glide.SongGlideRequest
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.Share
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import com.bumptech.glide.Glide
import kotlinx.android.synthetic.main.activity_share_instagram.*
/**
* Created by hemanths on 2020-02-02.
@ -42,6 +41,8 @@ import kotlinx.android.synthetic.main.activity_share_instagram.*
class ShareInstagramStory : AbsBaseActivity() {
private lateinit var binding: ActivityShareInstagramBinding
companion object {
const val EXTRA_SONG = "extra_song"
}
@ -57,32 +58,33 @@ class ShareInstagramStory : AbsBaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_share_instagram)
binding = ActivityShareInstagramBinding.inflate(layoutInflater)
setContentView(binding.root)
setStatusbarColor(Color.TRANSPARENT)
setNavigationbarColor(Color.BLACK)
toolbar.setBackgroundColor(Color.TRANSPARENT)
setSupportActionBar(toolbar)
binding.toolbar.setBackgroundColor(Color.TRANSPARENT)
setSupportActionBar(binding.toolbar)
val song = intent.extras?.getParcelable<Song>(EXTRA_SONG)
song?.let { songFinal ->
SongGlideRequest.Builder.from(Glide.with(this), songFinal)
.checkIgnoreMediaStore(this@ShareInstagramStory)
.generatePalette(this@ShareInstagramStory)
.build()
.into(object : RetroMusicColoredTarget(image) {
GlideApp.with(this)
.asBitmapPalette()
.songCoverOptions(songFinal)
.load(RetroGlideExtension.getSongModel(songFinal))
.into(object : RetroMusicColoredTarget(binding.image) {
override fun onColorReady(colors: MediaNotificationProcessor) {
val isColorLight = ColorUtil.isColorLight(colors.backgroundColor)
setColors(isColorLight, colors.backgroundColor)
}
})
shareTitle.text = songFinal.title
shareText.text = songFinal.artistName
shareButton.setOnClickListener {
binding.shareTitle.text = songFinal.title
binding.shareText.text = songFinal.artistName
binding.shareButton.setOnClickListener {
val path: String = Media.insertImage(
contentResolver,
mainContent.drawToBitmap(Bitmap.Config.ARGB_8888),
binding.mainContent.drawToBitmap(Bitmap.Config.ARGB_8888),
"Design", null
)
val uri = Uri.parse(path)
@ -92,24 +94,25 @@ class ShareInstagramStory : AbsBaseActivity() {
)
}
}
shareButton.setTextColor(
binding.shareButton.setTextColor(
MaterialValueHelper.getPrimaryTextColor(
this,
ColorUtil.isColorLight(ThemeStore.accentColor(this))
)
)
shareButton.backgroundTintList = ColorStateList.valueOf(ThemeStore.accentColor(this))
binding.shareButton.backgroundTintList =
ColorStateList.valueOf(ThemeStore.accentColor(this))
}
private fun setColors(colorLight: Boolean, color: Int) {
setLightStatusbar(colorLight)
toolbar.setTitleTextColor(
binding.toolbar.setTitleTextColor(
MaterialValueHelper.getPrimaryTextColor(
this@ShareInstagramStory,
colorLight
)
)
toolbar.navigationIcon?.setTintList(
binding.toolbar.navigationIcon?.setTintList(
ColorStateList.valueOf(
MaterialValueHelper.getPrimaryTextColor(
this@ShareInstagramStory,
@ -117,7 +120,7 @@ class ShareInstagramStory : AbsBaseActivity() {
)
)
)
mainContent.background =
binding.mainContent.background =
GradientDrawable(
GradientDrawable.Orientation.TOP_BOTTOM,
intArrayOf(color, Color.BLACK)

View File

@ -37,6 +37,7 @@ import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.BuildConfig
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsBaseActivity
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
@ -44,10 +45,11 @@ import com.anjlab.android.iab.v3.SkuDetails
import com.anjlab.android.iab.v3.TransactionDetails
import java.lang.ref.WeakReference
import java.util.*
import kotlinx.android.synthetic.main.activity_donation.*
class SupportDevelopmentActivity : AbsBaseActivity(), BillingProcessor.IBillingHandler {
lateinit var binding: ActivityDonationBinding
companion object {
val TAG: String = SupportDevelopmentActivity::class.java.simpleName
const val DONATION_PRODUCT_IDS = R.array.donation_ids
@ -72,6 +74,7 @@ class SupportDevelopmentActivity : AbsBaseActivity(), BillingProcessor.IBillingH
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityDonationBinding.inflate(layoutInflater)
setContentView(R.layout.activity_donation)
setStatusbarColorAuto()
@ -82,15 +85,15 @@ class SupportDevelopmentActivity : AbsBaseActivity(), BillingProcessor.IBillingH
setupToolbar()
billingProcessor = BillingProcessor(this, BuildConfig.GOOGLE_PLAY_LICENSING_KEY, this)
TintHelper.setTint(progress, ThemeStore.accentColor(this))
donation.setTextColor(ThemeStore.accentColor(this))
TintHelper.setTint(binding.progress, ThemeStore.accentColor(this))
binding.donation.setTextColor(ThemeStore.accentColor(this))
}
private fun setupToolbar() {
val toolbarColor = ATHUtil.resolveColor(this, R.attr.colorSurface)
toolbar.setBackgroundColor(toolbarColor)
ToolbarContentTintHelper.colorBackButton(toolbar)
setSupportActionBar(toolbar)
binding.toolbar.setBackgroundColor(toolbarColor)
ToolbarContentTintHelper.colorBackButton(binding.toolbar)
setSupportActionBar(binding.toolbar)
}
override fun onBillingInitialized() {
@ -146,8 +149,8 @@ private class SkuDetailsLoadAsyncTask(supportDevelopmentActivity: SupportDevelop
super.onPreExecute()
val supportDevelopmentActivity = weakReference.get() ?: return
supportDevelopmentActivity.progressContainer.visibility = View.VISIBLE
supportDevelopmentActivity.recyclerView.visibility = View.GONE
supportDevelopmentActivity.binding.progressContainer.visibility = View.VISIBLE
supportDevelopmentActivity.binding.recyclerView.visibility = View.GONE
}
override fun doInBackground(vararg params: Void): List<SkuDetails>? {
@ -166,15 +169,17 @@ private class SkuDetailsLoadAsyncTask(supportDevelopmentActivity: SupportDevelop
val dialog = weakReference.get() ?: return
if (skuDetails == null || skuDetails.isEmpty()) {
dialog.progressContainer.visibility = View.GONE
dialog.binding.progressContainer.visibility = View.GONE
return
}
dialog.progressContainer.visibility = View.GONE
dialog.recyclerView.itemAnimator = DefaultItemAnimator()
dialog.recyclerView.layoutManager = GridLayoutManager(dialog, 2)
dialog.recyclerView.adapter = SkuDetailsAdapter(dialog, skuDetails)
dialog.recyclerView.visibility = View.VISIBLE
dialog.binding.progressContainer.visibility = View.GONE
dialog.binding.recyclerView.apply {
itemAnimator = DefaultItemAnimator()
layoutManager = GridLayoutManager(dialog, 2)
adapter = SkuDetailsAdapter(dialog, skuDetails)
visibility = View.VISIBLE
}
}
}

View File

@ -27,54 +27,58 @@ 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.R
import code.name.monkey.retromusic.activities.base.AbsBaseActivity
import code.name.monkey.retromusic.databinding.ActivityUserInfoBinding
import code.name.monkey.retromusic.extensions.accentColor
import code.name.monkey.retromusic.extensions.applyToolbar
import code.name.monkey.retromusic.glide.ProfileBannerGlideRequest
import code.name.monkey.retromusic.glide.UserProfileGlideRequest
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.util.ImageUtil
import code.name.monkey.retromusic.util.PreferenceUtil
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.github.dhaval2404.imagepicker.ImagePicker
import com.github.dhaval2404.imagepicker.constant.ImageProvider
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import kotlinx.android.synthetic.main.activity_user_info.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
class UserInfoActivity : AbsBaseActivity() {
private lateinit var binding: ActivityUserInfoBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user_info)
binding = ActivityUserInfoBinding.inflate(layoutInflater)
setContentView(binding.root)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
setLightNavigationBar(true)
applyToolbar(toolbar)
applyToolbar(binding.toolbar)
nameContainer.accentColor()
name.setText(PreferenceUtil.userName)
binding.nameContainer.accentColor()
binding.name.setText(PreferenceUtil.userName)
userImage.setOnClickListener {
binding.userImage.setOnClickListener {
pickNewPhoto()
}
bannerImage.setOnClickListener {
binding.bannerImage.setOnClickListener {
selectBannerImage()
}
next.setOnClickListener {
val nameString = name.text.toString().trim { it <= ' ' }
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()
return@setOnClickListener
@ -86,23 +90,24 @@ class UserInfoActivity : AbsBaseActivity() {
val textColor =
MaterialValueHelper.getPrimaryTextColor(this, ColorUtil.isColorLight(accentColor()))
next.backgroundTintList = ColorStateList.valueOf(accentColor())
next.iconTint = ColorStateList.valueOf(textColor)
next.setTextColor(textColor)
binding.next.backgroundTintList = ColorStateList.valueOf(accentColor())
binding.next.iconTint = ColorStateList.valueOf(textColor)
binding.next.setTextColor(textColor)
loadProfile()
}
private fun loadProfile() {
bannerImage?.let {
ProfileBannerGlideRequest.Builder.from(
Glide.with(this),
ProfileBannerGlideRequest.getBannerModel()
).build().into(it)
binding.bannerImage.let {
GlideApp.with(this)
.asBitmap()
.load(RetroGlideExtension.getBannerModel())
.profileBannerOptions(RetroGlideExtension.getBannerModel())
.into(it)
}
UserProfileGlideRequest.Builder.from(
Glide.with(this),
UserProfileGlideRequest.getUserModel()
).build().into(userImage)
GlideApp.with(this).asBitmap()
.load(RetroGlideExtension.getUserModel())
.userProfileOptions(RetroGlideExtension.getUserModel())
.into(binding.userImage)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
@ -145,31 +150,31 @@ class UserInfoActivity : AbsBaseActivity() {
private fun setAndSaveBannerImage(fileUri: Uri) {
Glide.with(this)
.load(fileUri)
.asBitmap()
.load(fileUri)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.listener(object : RequestListener<Any, Bitmap> {
override fun onException(
e: java.lang.Exception?,
model: Any?,
target: Target<Bitmap>?,
isFirstResource: Boolean
): Boolean {
return false
}
.listener(object : RequestListener<Bitmap> {
override fun onResourceReady(
resource: Bitmap?,
model: Any?,
target: Target<Bitmap>?,
isFromMemoryCache: Boolean,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
resource?.let { saveImage(it, USER_BANNER) }
return false
}
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Bitmap>?,
isFirstResource: Boolean
): Boolean {
return false
}
})
.into(bannerImage)
.into(binding.bannerImage)
}
private fun saveImage(bitmap: Bitmap, fileName: String) {
@ -195,31 +200,31 @@ class UserInfoActivity : AbsBaseActivity() {
private fun setAndSaveUserImage(fileUri: Uri) {
Glide.with(this)
.load(fileUri)
.asBitmap()
.load(fileUri)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.listener(object : RequestListener<Any, Bitmap> {
override fun onException(
e: java.lang.Exception?,
model: Any?,
target: Target<Bitmap>?,
isFirstResource: Boolean
): Boolean {
return false
}
.listener(object : RequestListener<Bitmap> {
override fun onResourceReady(
resource: Bitmap?,
model: Any?,
target: Target<Bitmap>?,
isFromMemoryCache: Boolean,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
resource?.let { saveImage(it, USER_PROFILE) }
return false
}
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Bitmap>?,
isFirstResource: Boolean
): Boolean {
return false
}
})
.into(userImage)
.into(binding.userImage)
}
companion object {

View File

@ -5,34 +5,40 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.Bundle;
import android.webkit.WebView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import 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.R;
import code.name.monkey.retromusic.activities.base.AbsBaseActivity;
import code.name.monkey.retromusic.util.PreferenceUtil;
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
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) {
@ -49,16 +55,15 @@ public class WhatsNewActivity extends AbsBaseActivity {
protected void onCreate(@Nullable Bundle savedInstanceState) {
setDrawUnderStatusBar();
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_whats_new);
ActivityWhatsNewBinding binding = ActivityWhatsNewBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setStatusbarColorAuto();
setNavigationbarColorAuto();
setTaskDescriptionColorAuto();
WebView webView = findViewById(R.id.webView);
Toolbar toolbar = findViewById(R.id.toolbar);
toolbar.setBackgroundColor(ATHUtil.INSTANCE.resolveColor(this, R.attr.colorSurface));
toolbar.setNavigationOnClickListener(v -> onBackPressed());
ToolbarContentTintHelper.colorBackButton(toolbar);
binding.toolbar.setBackgroundColor(ATHUtil.INSTANCE.resolveColor(this, R.attr.colorSurface));
binding.toolbar.setNavigationOnClickListener(v -> onBackPressed());
ToolbarContentTintHelper.colorBackButton(binding.toolbar);
try {
StringBuilder buf = new StringBuilder();
@ -74,38 +79,50 @@ public class WhatsNewActivity extends AbsBaseActivity {
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")));
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)));
colorToCSS(
MaterialValueHelper.getPrimaryTextColor(
this, ColorUtil.INSTANCE.isColorLight(accentColor)));
final String changeLog =
buf.toString()
.replace(
"{style-placeholder}",
String.format(
"body { background-color: %s; color: %s; } li {color: %s;} .colorHeader {background-color: %s; color: %s;} .tag {color: %s;}",
backgroundColor,
contentColor,
textColor,
accentColorString,
accentTextColor,
accentColorString))
.replace("{link-color}", colorToCSS(ThemeStore.Companion.accentColor(this)))
.replace(
"{link-color-active}",
colorToCSS(
ColorUtil.INSTANCE.lightenColor(ThemeStore.Companion.accentColor(this))));
webView.loadData(changeLog, "text/html", "UTF-8");
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) {
webView.loadData(
"<h1>Unable to load</h1><p>" + e.getLocalizedMessage() + "</p>", "text/html", "UTF-8");
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,141 @@
package code.name.monkey.retromusic.activities.base
import android.os.Bundle
import android.view.ViewStub
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.cast.CastHelper
import code.name.monkey.retromusic.cast.RetroSessionManager
import code.name.monkey.retromusic.cast.RetroWebServer
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import com.google.android.gms.cast.framework.CastContext
import com.google.android.gms.cast.framework.CastSession
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailability
import java.util.*
abstract class AbsCastActivity : AbsSlidingMusicPanelActivity() {
private var mCastSession: CastSession? = null
private lateinit var castContext: CastContext
private var webServer: RetroWebServer? = null
private var playServicesAvailable: Boolean = false
private val sessionManagerListener by lazy {
object : RetroSessionManager {
override fun onSessionStarting(castSession: CastSession) {
invalidateOptionsMenu()
webServer = RetroWebServer.getInstance(this@AbsCastActivity)
webServer?.start()
}
override fun onSessionStarted(castSession: CastSession, p1: String) {
invalidateOptionsMenu()
mCastSession = castSession
loadCastQueue(MusicPlayerRemote.position)
inflateCastController()
MusicPlayerRemote.isCasting = true
setAllowDragging(false)
collapsePanel()
}
override fun onSessionEnding(p0: CastSession) {
invalidateOptionsMenu()
webServer?.stop()
}
override fun onSessionEnded(castSession: CastSession, p1: Int) {
invalidateOptionsMenu()
if (mCastSession == castSession) {
mCastSession = null
}
MusicPlayerRemote.isCasting = false
setAllowDragging(true)
}
override fun onSessionResumed(castSession: CastSession, p1: Boolean) {
invalidateOptionsMenu()
mCastSession = castSession
loadCastQueue(MusicPlayerRemote.position)
inflateCastController()
MusicPlayerRemote.isCasting = true
setAllowDragging(false)
collapsePanel()
}
override fun onSessionSuspended(castSession: CastSession, p1: Int) {
invalidateOptionsMenu()
if (mCastSession == castSession) {
mCastSession = null
}
MusicPlayerRemote.isCasting = false
setAllowDragging(true)
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
playServicesAvailable = try {
GoogleApiAvailability
.getInstance().isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS
} catch (e: Exception) {
false
}
if (playServicesAvailable) {
setupCast()
}
}
private fun setupCast() {
castContext = CastContext.getSharedInstance(this)
}
override fun onResume() {
if (playServicesAvailable) {
castContext.sessionManager.addSessionManagerListener(
sessionManagerListener,
CastSession::class.java
)
if (mCastSession == null) {
mCastSession = castContext.sessionManager.currentCastSession
}
}
super.onResume()
}
override fun onStop() {
super.onStop()
mCastSession = null
}
private fun songChanged(position: Int) {
loadCastQueue(position)
}
fun loadCastQueue(position: Int) {
if (!MusicPlayerRemote.playingQueue.isNullOrEmpty()) {
mCastSession?.let {
CastHelper.castQueue(
it,
MusicPlayerRemote.playingQueue,
position,
MusicPlayerRemote.songProgressMillis.toLong()
)
}
} else {
mCastSession?.let { CastHelper.castSong(it, MusicPlayerRemote.currentSong) }
}
}
override fun onPlayingMetaChanged() {
super.onPlayingMetaChanged()
if (playServicesAvailable) {
songChanged(MusicPlayerRemote.position)
}
}
fun inflateCastController() {
findViewById<ViewStub>(R.id.cast_stub)?.inflate()
}
}

View File

@ -15,12 +15,7 @@
package code.name.monkey.retromusic.activities.base
import android.Manifest
import android.content.BroadcastReceiver
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.ServiceConnection
import android.content.*
import android.os.Bundle
import android.os.IBinder
import androidx.lifecycle.lifecycleScope
@ -30,11 +25,11 @@ import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.interfaces.IMusicServiceEventListener
import code.name.monkey.retromusic.repository.RealRepository
import code.name.monkey.retromusic.service.MusicService.*
import java.lang.ref.WeakReference
import java.util.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject
import java.lang.ref.WeakReference
import java.util.*
abstract class AbsMusicServiceActivity : AbsBaseActivity(), IMusicServiceEventListener {
@ -166,6 +161,12 @@ abstract class AbsMusicServiceActivity : AbsBaseActivity(), IMusicServiceEventLi
}
}
override fun onFavoriteStateChanged() {
for (listener in mMusicServiceEventListeners) {
listener.onFavoriteStateChanged()
}
}
override fun onHasPermissionsChanged(hasPermissions: Boolean) {
super.onHasPermissionsChanged(hasPermissions)
val intent = Intent(MEDIA_STORE_CHANGED)
@ -194,7 +195,8 @@ abstract class AbsMusicServiceActivity : AbsBaseActivity(), IMusicServiceEventLi
val activity = reference.get()
if (activity != null && action != null) {
when (action) {
FAVORITE_STATE_CHANGED, META_CHANGED -> activity.onPlayingMetaChanged()
FAVORITE_STATE_CHANGED -> activity.onFavoriteStateChanged()
META_CHANGED -> activity.onPlayingMetaChanged()
QUEUE_CHANGED -> activity.onQueueChanged()
PLAY_STATE_CHANGED -> activity.onPlayStateChanged()
REPEAT_MODE_CHANGED -> activity.onRepeatModeChanged()

View File

@ -14,7 +14,6 @@
*/
package code.name.monkey.retromusic.activities.base
import android.annotation.SuppressLint
import android.graphics.Color
import android.os.Bundle
import android.view.View
@ -29,6 +28,8 @@ 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
@ -57,12 +58,12 @@ import code.name.monkey.retromusic.model.CategoryInfo
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.views.BottomNavigationBarTinted
import com.google.android.material.bottomsheet.BottomSheetBehavior.*
import kotlinx.android.synthetic.main.sliding_music_panel_layout.*
import org.koin.androidx.viewmodel.ext.android.viewModel
abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
companion object {
val TAG: String = AbsSlidingMusicPanelActivity::class.java.simpleName
var fromNotification: Boolean = false
}
protected val libraryViewModel by viewModel<LibraryViewModel>()
@ -75,16 +76,17 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
private var lightStatusBar: Boolean = false
private var lightNavigationBar: Boolean = false
private var paletteColor: Int = Color.WHITE
protected abstract fun createContentView(): View
protected abstract fun createContentView(): SlidingMusicPanelLayoutBinding
private val panelState: Int
get() = bottomSheetBehavior.state
private lateinit var binding: SlidingMusicPanelLayoutBinding
private val bottomSheetCallbackList = object : BottomSheetCallback() {
override fun onSlide(bottomSheet: View, slideOffset: Float) {
setMiniPlayerAlphaProgress(slideOffset)
dimBackground.show()
dimBackground.alpha = slideOffset
binding.dimBackground.show()
binding.dimBackground.alpha = slideOffset
}
override fun onStateChanged(bottomSheet: View, newState: Int) {
@ -94,9 +96,17 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
}
STATE_COLLAPSED -> {
onPanelCollapsed()
dimBackground.hide()
binding.dimBackground.hide()
if (fromNotification) {
hideBottomBar(MusicPlayerRemote.playingQueue.isEmpty())
fromNotification = false
}
}
STATE_SETTLING, STATE_DRAGGING -> {
if (fromNotification) {
getBottomNavigationView().isVisible = true
}
}
else -> {
println("Do something")
}
@ -108,23 +118,25 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(createContentView())
binding = createContentView()
setContentView(binding.root)
chooseFragmentForTheme()
setupSlidingUpPanel()
setupBottomSheet()
updateColor()
val themeColor = resolveColor(android.R.attr.windowBackground, Color.GRAY)
dimBackground.setBackgroundColor(ColorUtil.withAlpha(themeColor, 0.5f))
dimBackground.setOnClickListener {
binding.dimBackground.setBackgroundColor(ColorUtil.withAlpha(themeColor, 0.5f))
binding.dimBackground.setOnClickListener {
println("dimBackground")
collapsePanel()
}
}
private fun setupBottomSheet() {
bottomSheetBehavior = from(slidingPanel) as RetroBottomSheetBehavior
bottomSheetBehavior = from(binding.slidingPanel) as RetroBottomSheetBehavior
bottomSheetBehavior.addBottomSheetCallback(bottomSheetCallbackList)
bottomSheetBehavior.maxWidth = resources.displayMetrics.widthPixels
}
override fun onResume() {
@ -142,14 +154,13 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
bottomSheetBehavior.removeBottomSheetCallback(bottomSheetCallbackList)
}
@SuppressLint("InflateParams")
protected fun wrapSlidingMusicPanel(): View {
val slidingMusicPanelLayout =
layoutInflater.inflate(R.layout.sliding_music_panel_layout, null)
protected fun wrapSlidingMusicPanel(): SlidingMusicPanelLayoutBinding {
val slidingMusicPanelLayoutBinding =
SlidingMusicPanelLayoutBinding.inflate(layoutInflater)
val contentContainer: ViewGroup =
slidingMusicPanelLayout.findViewById(R.id.mainContentFrame)
layoutInflater.inflate(R.layout.activity_main_content, contentContainer)
return slidingMusicPanelLayout
slidingMusicPanelLayoutBinding.mainContentFrame
ActivityMainContentBinding.inflate(layoutInflater, contentContainer, true)
return slidingMusicPanelLayoutBinding
}
fun collapsePanel() {
@ -166,8 +177,8 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
val alpha = 1 - progress
miniPlayerFragment?.view?.alpha = alpha
miniPlayerFragment?.view?.visibility = if (alpha == 0f) View.GONE else View.VISIBLE
bottomNavigationView.translationY = progress * 500
bottomNavigationView.alpha = alpha
binding.bottomNavigationView.translationY = progress * 500
binding.bottomNavigationView.alpha = alpha
}
open fun onPanelCollapsed() {
@ -183,14 +194,14 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
}
private fun setupSlidingUpPanel() {
slidingPanel.viewTreeObserver.addOnGlobalLayoutListener(object :
binding.slidingPanel.viewTreeObserver.addOnGlobalLayoutListener(object :
ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
slidingPanel.viewTreeObserver.removeOnGlobalLayoutListener(this)
binding.slidingPanel.viewTreeObserver.removeOnGlobalLayoutListener(this)
if (nowPlayingScreen != Peak) {
val params = slidingPanel.layoutParams as ViewGroup.LayoutParams
val params = binding.slidingPanel.layoutParams as ViewGroup.LayoutParams
params.height = ViewGroup.LayoutParams.MATCH_PARENT
slidingPanel.layoutParams = params
binding.slidingPanel.layoutParams = params
}
when (panelState) {
STATE_EXPANDED -> onPanelExpanded()
@ -204,16 +215,16 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
}
fun getBottomNavigationView(): BottomNavigationBarTinted {
return bottomNavigationView
return binding.bottomNavigationView
}
override fun onServiceConnected() {
super.onServiceConnected()
if (MusicPlayerRemote.playingQueue.isNotEmpty()) {
slidingPanel.viewTreeObserver.addOnGlobalLayoutListener(object :
binding.slidingPanel.viewTreeObserver.addOnGlobalLayoutListener(object :
ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
slidingPanel.viewTreeObserver.removeOnGlobalLayoutListener(this)
binding.slidingPanel.viewTreeObserver.removeOnGlobalLayoutListener(this)
hideBottomBar(false)
}
})
@ -305,16 +316,17 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
}
fun updateTabs() {
bottomNavigationView.menu.clear()
binding.bottomNavigationView.menu.clear()
val currentTabs: List<CategoryInfo> = PreferenceUtil.libraryCategory
for (tab in currentTabs) {
if (tab.visible) {
val menu = tab.category
bottomNavigationView.menu.add(0, menu.id, 0, menu.stringRes).setIcon(menu.icon)
binding.bottomNavigationView.menu.add(0, menu.id, 0, menu.stringRes)
.setIcon(menu.icon)
}
}
if (bottomNavigationView.menu.size() == 1) {
bottomNavigationView.hide()
if (binding.bottomNavigationView.menu.size() == 1) {
binding.bottomNavigationView.hide()
}
}
@ -326,28 +338,34 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
}
fun setBottomBarVisibility(visible: Boolean) {
bottomNavigationView.isVisible = visible
binding.bottomNavigationView.isVisible = visible
hideBottomBar(MusicPlayerRemote.playingQueue.isEmpty())
}
private fun hideBottomBar(hide: Boolean) {
val heightOfBar = dip(R.dimen.mini_player_height)
val heightOfBarWithTabs = heightOfBar * 2
val isVisible = bottomNavigationView.isVisible
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
)
val isVisible = binding.bottomNavigationView.isVisible
if (hide) {
bottomSheetBehavior.isHideable = true
bottomSheetBehavior.peekHeight = 0
ViewCompat.setElevation(slidingPanel, 0f)
ViewCompat.setElevation(bottomNavigationView, 10f)
ViewCompat.setElevation(binding.slidingPanel, 0f)
ViewCompat.setElevation(binding.bottomNavigationView, 10f)
collapsePanel()
} else {
if (MusicPlayerRemote.playingQueue.isNotEmpty()) {
bottomSheetBehavior.isHideable = false
ViewCompat.setElevation(slidingPanel, 10f)
ViewCompat.setElevation(bottomNavigationView, 10f)
ViewCompat.setElevation(binding.slidingPanel, 10f)
ViewCompat.setElevation(binding.bottomNavigationView, 10f)
if (isVisible) {
println("List")
bottomSheetBehavior.peekHeight = heightOfBarWithTabs - 22
if (bottomSheetBehavior.state != STATE_EXPANDED)
getBottomNavigationView().translateYAnimate(0F)
bottomSheetBehavior.peekHeightAnimate(heightOfBarWithTabs)
} else {
println("Details")
bottomSheetBehavior.peekHeight = heightOfBar
@ -356,6 +374,11 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
}
}
fun setAllowDragging(allowDragging: Boolean) {
bottomSheetBehavior.setAllowDragging(allowDragging)
hideBottomBar(false)
}
private fun chooseFragmentForTheme() {
nowPlayingScreen = PreferenceUtil.nowPlayingScreen

View File

@ -15,6 +15,7 @@
package code.name.monkey.retromusic.activities.base
import android.content.Context
import android.content.res.Resources
import android.graphics.Color
import android.os.Bundle
import android.os.Handler
@ -23,12 +24,12 @@ import android.view.View
import android.view.WindowManager
import androidx.annotation.ColorInt
import androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode
import androidx.core.os.ConfigurationCompat
import code.name.monkey.appthemehelper.ATH
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.common.ATHToolbarActivity
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialDialogsUtil
import code.name.monkey.appthemehelper.util.VersionUtils
import code.name.monkey.retromusic.LanguageContextWrapper
import code.name.monkey.retromusic.R
@ -48,7 +49,7 @@ abstract class AbsThemeActivity : ATHToolbarActivity(), Runnable {
setImmersiveFullscreen()
registerSystemUiVisibility()
toggleScreenOn()
MaterialDialogsUtil.updateMaterialDialogsThemeSingleton(this)
//MaterialDialogsUtil.updateMaterialDialogsThemeSingleton(this)
}
private fun updateTheme() {
@ -213,8 +214,12 @@ abstract class AbsThemeActivity : ATHToolbarActivity(), Runnable {
override fun attachBaseContext(newBase: Context?) {
val code = PreferenceUtil.languageCode
if (code != "auto") {
super.attachBaseContext(LanguageContextWrapper.wrap(newBase, Locale(code)))
} else super.attachBaseContext(newBase)
val locale = if (code == "auto") {
// Get the device default locale
ConfigurationCompat.getLocales(Resources.getSystem().configuration)[0]
} else {
Locale.forLanguageTag(code)
}
super.attachBaseContext(LanguageContextWrapper.wrap(newBase, locale))
}
}

View File

@ -41,18 +41,16 @@ import code.name.monkey.retromusic.activities.bugreport.model.Report
import code.name.monkey.retromusic.activities.bugreport.model.github.ExtraInfo
import code.name.monkey.retromusic.activities.bugreport.model.github.GithubLogin
import code.name.monkey.retromusic.activities.bugreport.model.github.GithubTarget
import code.name.monkey.retromusic.databinding.ActivityBugReportBinding
import code.name.monkey.retromusic.misc.DialogAsyncTask
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.textfield.TextInputLayout
import java.io.IOException
import kotlinx.android.synthetic.main.activity_bug_report.*
import kotlinx.android.synthetic.main.bug_report_card_device_info.*
import kotlinx.android.synthetic.main.bug_report_card_report.*
import org.eclipse.egit.github.core.Issue
import org.eclipse.egit.github.core.client.GitHubClient
import org.eclipse.egit.github.core.client.RequestException
import org.eclipse.egit.github.core.service.IssueService
import java.io.IOException
private const val RESULT_SUCCESS = "RESULT_OK"
private const val RESULT_BAD_CREDENTIALS = "RESULT_BAD_CREDENTIALS"
@ -72,12 +70,14 @@ private annotation class Result
open class BugReportActivity : AbsThemeActivity() {
private lateinit var binding: ActivityBugReportBinding
private var deviceInfo: DeviceInfo? = null
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_bug_report)
binding = ActivityBugReportBinding.inflate(layoutInflater)
setContentView(binding.root)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
@ -87,50 +87,50 @@ open class BugReportActivity : AbsThemeActivity() {
if (TextUtils.isEmpty(title)) setTitle(R.string.report_an_issue)
deviceInfo = DeviceInfo(this)
airTextDeviceInfo.text = deviceInfo.toString()
binding.cardDeviceInfo.airTextDeviceInfo.text = deviceInfo.toString()
}
private fun initViews() {
val accentColor = ThemeStore.accentColor(this)
val primaryColor = ATHUtil.resolveColor(this, R.attr.colorSurface)
toolbar.setBackgroundColor(primaryColor)
setSupportActionBar(toolbar)
ToolbarContentTintHelper.colorBackButton(toolbar)
binding.toolbar.setBackgroundColor(primaryColor)
setSupportActionBar(binding.toolbar)
ToolbarContentTintHelper.colorBackButton(binding.toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
TintHelper.setTintAuto(optionUseAccount, accentColor, false)
optionUseAccount?.setOnClickListener {
inputTitle.isEnabled = true
inputDescription.isEnabled = true
inputUsername.isEnabled = true
inputPassword.isEnabled = true
TintHelper.setTintAuto(binding.cardReport.optionUseAccount, accentColor, false)
binding.cardReport.optionUseAccount.setOnClickListener {
binding.cardReport.inputTitle.isEnabled = true
binding.cardReport.inputDescription.isEnabled = true
binding.cardReport.inputUsername.isEnabled = true
binding.cardReport.inputPassword.isEnabled = true
optionAnonymous.isChecked = false
sendFab.hide(object : FloatingActionButton.OnVisibilityChangedListener() {
binding.cardReport.optionAnonymous.isChecked = false
binding.sendFab.hide(object : FloatingActionButton.OnVisibilityChangedListener() {
override fun onHidden(fab: FloatingActionButton?) {
super.onHidden(fab)
sendFab.setImageResource(R.drawable.ic_send)
sendFab.show()
binding.sendFab.setImageResource(R.drawable.ic_send)
binding.sendFab.show()
}
})
}
TintHelper.setTintAuto(optionAnonymous, accentColor, false)
optionAnonymous.setOnClickListener {
inputTitle.isEnabled = false
inputDescription.isEnabled = false
inputUsername.isEnabled = false
inputPassword.isEnabled = false
TintHelper.setTintAuto(binding.cardReport.optionAnonymous, accentColor, false)
binding.cardReport.optionAnonymous.setOnClickListener {
binding.cardReport.inputTitle.isEnabled = false
binding.cardReport.inputDescription.isEnabled = false
binding.cardReport.inputUsername.isEnabled = false
binding.cardReport.inputPassword.isEnabled = false
optionUseAccount.isChecked = false
sendFab.hide(object : FloatingActionButton.OnVisibilityChangedListener() {
binding.cardReport.optionUseAccount.isChecked = false
binding.sendFab.hide(object : FloatingActionButton.OnVisibilityChangedListener() {
override fun onHidden(fab: FloatingActionButton?) {
super.onHidden(fab)
sendFab.setImageResource(R.drawable.ic_open_in_browser)
sendFab.show()
binding.sendFab.setImageResource(R.drawable.ic_open_in_browser)
binding.sendFab.show()
}
})
}
inputPassword.setOnEditorActionListener { _, actionId, _ ->
binding.cardReport.inputPassword.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_SEND) {
reportIssue()
return@setOnEditorActionListener true
@ -138,22 +138,22 @@ open class BugReportActivity : AbsThemeActivity() {
false
}
airTextDeviceInfo.setOnClickListener { copyDeviceInfoToClipBoard() }
binding.cardDeviceInfo.airTextDeviceInfo.setOnClickListener { copyDeviceInfoToClipBoard() }
TintHelper.setTintAuto(sendFab, accentColor, true)
sendFab.setOnClickListener { reportIssue() }
TintHelper.setTintAuto(binding.sendFab, accentColor, true)
binding.sendFab.setOnClickListener { reportIssue() }
MaterialUtil.setTint(inputLayoutTitle, false)
MaterialUtil.setTint(inputLayoutDescription, false)
MaterialUtil.setTint(inputLayoutUsername, false)
MaterialUtil.setTint(inputLayoutPassword, false)
MaterialUtil.setTint(binding.cardReport.inputLayoutTitle, false)
MaterialUtil.setTint(binding.cardReport.inputLayoutDescription, false)
MaterialUtil.setTint(binding.cardReport.inputLayoutUsername, false)
MaterialUtil.setTint(binding.cardReport.inputLayoutPassword, false)
}
private fun reportIssue() {
if (optionUseAccount.isChecked) {
if (binding.cardReport.optionUseAccount.isChecked) {
if (!validateInput()) return
val username = inputUsername.text.toString()
val password = inputPassword.text.toString()
val username = binding.cardReport.inputUsername.text.toString()
val password = binding.cardReport.inputPassword.text.toString()
sendBugReport(GithubLogin(username, password))
} else {
copyDeviceInfoToClipBoard()
@ -179,34 +179,34 @@ open class BugReportActivity : AbsThemeActivity() {
private fun validateInput(): Boolean {
var hasErrors = false
if (optionUseAccount.isChecked) {
if (TextUtils.isEmpty(inputUsername.text)) {
setError(inputLayoutUsername, R.string.bug_report_no_username)
if (binding.cardReport.optionUseAccount.isChecked) {
if (TextUtils.isEmpty(binding.cardReport.inputUsername.text)) {
setError(binding.cardReport.inputLayoutUsername, R.string.bug_report_no_username)
hasErrors = true
} else {
removeError(inputLayoutUsername)
removeError(binding.cardReport.inputLayoutUsername)
}
if (TextUtils.isEmpty(inputPassword.text)) {
setError(inputLayoutPassword, R.string.bug_report_no_password)
if (TextUtils.isEmpty(binding.cardReport.inputPassword.text)) {
setError(binding.cardReport.inputLayoutPassword, R.string.bug_report_no_password)
hasErrors = true
} else {
removeError(inputLayoutPassword)
removeError(binding.cardReport.inputLayoutPassword)
}
}
if (TextUtils.isEmpty(inputTitle.text)) {
setError(inputLayoutTitle, R.string.bug_report_no_title)
if (TextUtils.isEmpty(binding.cardReport.inputTitle.text)) {
setError(binding.cardReport.inputLayoutTitle, R.string.bug_report_no_title)
hasErrors = true
} else {
removeError(inputLayoutTitle)
removeError(binding.cardReport.inputLayoutTitle)
}
if (TextUtils.isEmpty(inputDescription.text)) {
setError(inputLayoutDescription, R.string.bug_report_no_description)
if (TextUtils.isEmpty(binding.cardReport.inputDescription.text)) {
setError(binding.cardReport.inputLayoutDescription, R.string.bug_report_no_description)
hasErrors = true
} else {
removeError(inputLayoutDescription)
removeError(binding.cardReport.inputLayoutDescription)
}
return !hasErrors
@ -223,8 +223,8 @@ open class BugReportActivity : AbsThemeActivity() {
private fun sendBugReport(login: GithubLogin) {
if (!validateInput()) return
val bugTitle = inputTitle.text.toString()
val bugDescription = inputDescription.text.toString()
val bugTitle = binding.cardReport.inputTitle.text.toString()
val bugDescription = binding.cardReport.inputDescription.text.toString()
val extraInfo = ExtraInfo()
onSaveExtraInfo()

View File

@ -5,11 +5,14 @@ import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import androidx.annotation.IntRange;
import code.name.monkey.retromusic.util.PreferenceUtil;
import java.util.Arrays;
import java.util.Locale;
import code.name.monkey.retromusic.util.PreferenceUtil;
public class DeviceInfo {
@SuppressLint("NewApi")

View File

@ -16,11 +16,14 @@ package code.name.monkey.retromusic.activities.saf;
import android.os.Build;
import android.os.Bundle;
import androidx.annotation.Nullable;
import code.name.monkey.retromusic.R;
import com.heinrichreimersoftware.materialintro.app.IntroActivity;
import com.heinrichreimersoftware.materialintro.slide.SimpleSlide;
import code.name.monkey.retromusic.R;
/** Created by hemanths on 2019-07-31. */
public class SAFGuideActivity extends IntroActivity {

View File

@ -22,10 +22,12 @@ import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.animation.OvershootInterpolator
import android.widget.ImageView
import androidx.appcompat.app.AlertDialog
import androidx.viewbinding.ViewBinding
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.TintHelper
@ -41,7 +43,6 @@ import code.name.monkey.retromusic.util.RetroUtil
import code.name.monkey.retromusic.util.SAFUtil
import com.google.android.material.button.MaterialButton
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.android.synthetic.main.activity_album_tag_editor.*
import org.jaudiotagger.audio.AudioFile
import org.jaudiotagger.audio.AudioFileIO
import org.jaudiotagger.tag.FieldKey
@ -49,7 +50,8 @@ import org.koin.android.ext.android.inject
import java.io.File
import java.util.*
abstract class AbsTagEditorActivity : AbsBaseActivity() {
abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() {
abstract val editorImage: ImageView?
val repository by inject<Repository>()
lateinit var saveFab: MaterialButton
@ -62,7 +64,11 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
private val currentSongPath: String? = null
private var savedTags: Map<FieldKey, String>? = null
private var savedArtworkInfo: ArtworkInfo? = null
protected abstract val contentViewLayout: Int
private var _binding: VB? = null
protected val binding: VB get() = _binding!!
abstract val bindingInflater: (LayoutInflater) -> VB
protected abstract fun loadImageFromFile(selectedFile: Uri?)
protected val show: AlertDialog
@ -187,7 +193,8 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(contentViewLayout)
_binding = bindingInflater.invoke(layoutInflater)
setContentView(binding.root)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
@ -284,10 +291,6 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
protected fun setNoImageMode() {
isInNoImageMode = true
imageContainer?.visibility = View.GONE
editorImage?.visibility = View.GONE
editorImage?.isEnabled = false
setColors(
intent.getIntExtra(
EXTRA_PALETTE,
@ -296,6 +299,7 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
)
}
protected fun dataChanged() {
showFab()
}
@ -314,9 +318,9 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
protected fun setImageBitmap(bitmap: Bitmap?, bgColor: Int) {
if (bitmap == null) {
editorImage.setImageResource(drawable.default_audio_art)
editorImage?.setImageResource(drawable.default_audio_art)
} else {
editorImage.setImageBitmap(bitmap)
editorImage?.setImageBitmap(bitmap)
}
setColors(bgColor)
}

View File

@ -25,30 +25,31 @@ import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.transition.Slide
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.glide.palette.BitmapPaletteTranscoder
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
import code.name.monkey.retromusic.model.ArtworkInfo
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.ImageUtil
import code.name.monkey.retromusic.util.RetroColorUtil.generatePalette
import code.name.monkey.retromusic.util.RetroColorUtil.getColor
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.animation.GlideAnimation
import com.bumptech.glide.request.target.SimpleTarget
import java.util.*
import kotlinx.android.synthetic.main.activity_album_tag_editor.*
import com.bumptech.glide.request.target.ImageViewTarget
import com.bumptech.glide.request.transition.Transition
import org.jaudiotagger.tag.FieldKey
import java.util.*
class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
class AlbumTagEditorActivity : AbsTagEditorActivity<ActivityAlbumTagEditorBinding>(), TextWatcher {
override val contentViewLayout: Int
get() = R.layout.activity_album_tag_editor
override val bindingInflater: (LayoutInflater) -> ActivityAlbumTagEditorBinding =
ActivityAlbumTagEditorBinding::inflate
private fun windowEnterTransition() {
val slide = Slide()
@ -62,20 +63,20 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
override fun loadImageFromFile(selectedFile: Uri?) {
Glide.with(this@AlbumTagEditorActivity).load(selectedFile).asBitmap()
.transcode(BitmapPaletteTranscoder(this), BitmapPaletteWrapper::class.java)
GlideApp.with(this@AlbumTagEditorActivity).asBitmapPalette().load(selectedFile)
.diskCacheStrategy(DiskCacheStrategy.NONE).skipMemoryCache(true)
.into(object : SimpleTarget<BitmapPaletteWrapper>() {
.into(object : ImageViewTarget<BitmapPaletteWrapper>(binding.editorImage) {
override fun onResourceReady(
resource: BitmapPaletteWrapper?,
glideAnimation: GlideAnimation<in BitmapPaletteWrapper>?
resource: BitmapPaletteWrapper,
transition: Transition<in BitmapPaletteWrapper>?
) {
getColor(resource?.palette, Color.TRANSPARENT)
albumArtBitmap = resource?.bitmap?.let { ImageUtil.resizeBitmap(it, 2048) }
getColor(resource.palette, Color.TRANSPARENT)
albumArtBitmap = resource.bitmap?.let { ImageUtil.resizeBitmap(it, 2048) }
setImageBitmap(
albumArtBitmap,
getColor(
resource?.palette,
resource.palette,
ATHUtil.resolveColor(
this@AlbumTagEditorActivity,
R.attr.defaultFooterColor
@ -87,11 +88,13 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
setResult(Activity.RESULT_OK)
}
override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) {
super.onLoadFailed(e, errorDrawable)
Toast.makeText(this@AlbumTagEditorActivity, e.toString(), Toast.LENGTH_LONG)
override fun onLoadFailed(errorDrawable: Drawable?) {
super.onLoadFailed(errorDrawable)
Toast.makeText(this@AlbumTagEditorActivity, "Load Failed", Toast.LENGTH_LONG)
.show()
}
override fun setResource(resource: BitmapPaletteWrapper?) {}
})
}
@ -99,15 +102,15 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
private var deleteAlbumArt: Boolean = false
private fun setupToolbar() {
toolbar.setBackgroundColor(ATHUtil.resolveColor(this, R.attr.colorSurface))
setSupportActionBar(toolbar)
binding.toolbar.setBackgroundColor(ATHUtil.resolveColor(this, R.attr.colorSurface))
setSupportActionBar(binding.toolbar)
}
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
window.sharedElementsUseOverlay = true
imageContainer?.transitionName = getString(R.string.transition_album_art)
binding.imageContainer.transitionName = getString(R.string.transition_album_art)
windowEnterTransition()
setUpViews()
setupToolbar()
@ -116,22 +119,23 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
private fun setUpViews() {
fillViewsWithFileTags()
MaterialUtil.setTint(yearContainer, false)
MaterialUtil.setTint(genreContainer, false)
MaterialUtil.setTint(albumTitleContainer, false)
MaterialUtil.setTint(albumArtistContainer, false)
MaterialUtil.setTint(binding.yearContainer, false)
MaterialUtil.setTint(binding.genreContainer, false)
MaterialUtil.setTint(binding.albumTitleContainer, false)
MaterialUtil.setTint(binding.albumArtistContainer, false)
albumText.appHandleColor().addTextChangedListener(this)
albumArtistText.appHandleColor().addTextChangedListener(this)
genreTitle.appHandleColor().addTextChangedListener(this)
yearTitle.appHandleColor().addTextChangedListener(this)
binding.albumText.appHandleColor().addTextChangedListener(this)
binding.albumArtistText.appHandleColor().addTextChangedListener(this)
binding.genreTitle.appHandleColor().addTextChangedListener(this)
binding.yearTitle.appHandleColor().addTextChangedListener(this)
}
private fun fillViewsWithFileTags() {
albumText.setText(albumTitle)
albumArtistText.setText(albumArtistName)
genreTitle.setText(genreName)
yearTitle.setText(songYear)
binding.albumText.setText(albumTitle)
binding.albumArtistText.setText(albumArtistName)
binding.genreTitle.setText(genreName)
binding.yearTitle.setText(songYear)
println(albumTitle + albumArtistName)
}
override fun loadCurrentImage() {
@ -155,7 +159,7 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
}
override fun searchImageOnWeb() {
searchWebFor(albumText.text.toString(), albumArtistText.text.toString())
searchWebFor(binding.albumText.text.toString(), binding.albumArtistText.text.toString())
}
override fun deleteImage() {
@ -169,12 +173,12 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
override fun save() {
val fieldKeyValueMap = EnumMap<FieldKey, String>(FieldKey::class.java)
fieldKeyValueMap[FieldKey.ALBUM] = albumText.text.toString()
fieldKeyValueMap[FieldKey.ALBUM] = binding.albumText.text.toString()
// android seems not to recognize album_artist field so we additionally write the normal artist field
fieldKeyValueMap[FieldKey.ARTIST] = albumArtistText.text.toString()
fieldKeyValueMap[FieldKey.ALBUM_ARTIST] = albumArtistText.text.toString()
fieldKeyValueMap[FieldKey.GENRE] = genreTitle.text.toString()
fieldKeyValueMap[FieldKey.YEAR] = yearTitle.text.toString()
fieldKeyValueMap[FieldKey.ARTIST] = binding.albumArtistText.text.toString()
fieldKeyValueMap[FieldKey.ALBUM_ARTIST] = binding.albumArtistText.text.toString()
fieldKeyValueMap[FieldKey.GENRE] = binding.genreTitle.text.toString()
fieldKeyValueMap[FieldKey.YEAR] = binding.yearTitle.text.toString()
writeValuesToFiles(
fieldKeyValueMap,
@ -206,6 +210,10 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
saveFab.backgroundTintList = ColorStateList.valueOf(color)
}
override val editorImage: ImageView
get() = binding.editorImage
companion object {
val TAG: String = AlbumTagEditorActivity::class.java.simpleName

View File

@ -14,91 +14,98 @@
*/
package code.name.monkey.retromusic.activities.tageditor
import android.annotation.SuppressLint
import android.net.Uri
import android.os.Bundle
import android.text.Editable
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.repository.SongRepository
import kotlinx.android.synthetic.main.activity_song_tag_editor.*
import org.jaudiotagger.tag.FieldKey
import org.koin.android.ext.android.inject
import java.util.*
class SongTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
class SongTagEditorActivity : AbsTagEditorActivity<ActivitySongTagEditorBinding>(), TextWatcher {
override val bindingInflater: (LayoutInflater) -> ActivitySongTagEditorBinding =
ActivitySongTagEditorBinding::inflate
override val contentViewLayout: Int
get() = R.layout.activity_song_tag_editor
private val songRepository by inject<SongRepository>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setNoImageMode()
setUpViews()
toolbar.setBackgroundColor(ATHUtil.resolveColor(this, R.attr.colorSurface))
setSupportActionBar(toolbar)
setNoImageMode()
binding.toolbar.setBackgroundColor(ATHUtil.resolveColor(this, R.attr.colorSurface))
setSupportActionBar(binding.toolbar)
}
@SuppressLint("ClickableViewAccessibility")
private fun setUpViews() {
fillViewsWithFileTags()
MaterialUtil.setTint(songTextContainer, false)
MaterialUtil.setTint(composerContainer, false)
MaterialUtil.setTint(albumTextContainer, false)
MaterialUtil.setTint(artistContainer, false)
MaterialUtil.setTint(albumArtistContainer, false)
MaterialUtil.setTint(yearContainer, false)
MaterialUtil.setTint(genreContainer, false)
MaterialUtil.setTint(trackNumberContainer, false)
MaterialUtil.setTint(lyricsContainer, false)
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)
songText.appHandleColor().addTextChangedListener(this)
albumText.appHandleColor().addTextChangedListener(this)
albumArtistText.appHandleColor().addTextChangedListener(this)
artistText.appHandleColor().addTextChangedListener(this)
genreText.appHandleColor().addTextChangedListener(this)
yearText.appHandleColor().addTextChangedListener(this)
trackNumberText.appHandleColor().addTextChangedListener(this)
lyricsText.appHandleColor().addTextChangedListener(this)
songComposerText.appHandleColor().addTextChangedListener(this)
binding.songText.appHandleColor().addTextChangedListener(this)
binding.albumText.appHandleColor().addTextChangedListener(this)
binding.albumArtistText.appHandleColor().addTextChangedListener(this)
binding.artistText.appHandleColor().addTextChangedListener(this)
binding.genreText.appHandleColor().addTextChangedListener(this)
binding.yearText.appHandleColor().addTextChangedListener(this)
binding.trackNumberText.appHandleColor().addTextChangedListener(this)
binding.lyricsText.appHandleColor().addTextChangedListener(this)
binding.songComposerText.appHandleColor().addTextChangedListener(this)
binding.lyricsText.setOnTouchListener { view, _ ->
view.parent.requestDisallowInterceptTouchEvent(true)
return@setOnTouchListener false
}
}
private fun fillViewsWithFileTags() {
songText.setText(songTitle)
albumArtistText.setText(albumArtist)
albumText.setText(albumTitle)
artistText.setText(artistName)
genreText.setText(genreName)
yearText.setText(songYear)
trackNumberText.setText(trackNumber)
lyricsText.setText(lyrics)
songComposerText.setText(composer)
binding.songText.setText(songTitle)
binding.albumArtistText.setText(albumArtist)
binding.albumText.setText(albumTitle)
binding.artistText.setText(artistName)
binding.genreText.setText(genreName)
binding.yearText.setText(songYear)
binding.trackNumberText.setText(trackNumber)
binding.lyricsText.setText(lyrics)
binding.songComposerText.setText(composer)
println(songTitle + songYear)
}
override fun loadCurrentImage() {
}
override fun loadCurrentImage() {}
override fun searchImageOnWeb() {
}
override fun searchImageOnWeb() {}
override fun deleteImage() {
}
override fun deleteImage() {}
override fun save() {
val fieldKeyValueMap = EnumMap<FieldKey, String>(FieldKey::class.java)
fieldKeyValueMap[FieldKey.TITLE] = songText.text.toString()
fieldKeyValueMap[FieldKey.ALBUM] = albumText.text.toString()
fieldKeyValueMap[FieldKey.ARTIST] = artistText.text.toString()
fieldKeyValueMap[FieldKey.GENRE] = genreText.text.toString()
fieldKeyValueMap[FieldKey.YEAR] = yearText.text.toString()
fieldKeyValueMap[FieldKey.TRACK] = trackNumberText.text.toString()
fieldKeyValueMap[FieldKey.LYRICS] = lyricsText.text.toString()
fieldKeyValueMap[FieldKey.ALBUM_ARTIST] = albumArtistText.text.toString()
fieldKeyValueMap[FieldKey.COMPOSER] = songComposerText.text.toString()
fieldKeyValueMap[FieldKey.TITLE] = binding.songText.text.toString()
fieldKeyValueMap[FieldKey.ALBUM] = binding.albumText.text.toString()
fieldKeyValueMap[FieldKey.ARTIST] = binding.artistText.text.toString()
fieldKeyValueMap[FieldKey.GENRE] = binding.genreText.text.toString()
fieldKeyValueMap[FieldKey.YEAR] = binding.yearText.text.toString()
fieldKeyValueMap[FieldKey.TRACK] = binding.trackNumberText.text.toString()
fieldKeyValueMap[FieldKey.LYRICS] = binding.lyricsText.text.toString()
fieldKeyValueMap[FieldKey.ALBUM_ARTIST] = binding.albumArtistText.text.toString()
fieldKeyValueMap[FieldKey.COMPOSER] = binding.songComposerText.text.toString()
writeValuesToFiles(fieldKeyValueMap, null)
}
@ -120,4 +127,7 @@ class SongTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
companion object {
val TAG: String = SongTagEditorActivity::class.java.simpleName
}
override val editorImage: ImageView?
get() = null
}

View File

@ -10,7 +10,7 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import com.afollestad.materialdialogs.MaterialDialog;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.jaudiotagger.audio.AudioFile;
import org.jaudiotagger.audio.AudioFileIO;
@ -136,17 +136,17 @@ public class WriteTagsAsyncTask extends DialogAsyncTask<LoadingInfo, Integer, Li
@Override
protected Dialog createDialog(@NonNull Context context) {
return new MaterialDialog.Builder(context)
.title(R.string.saving_changes)
.cancelable(false)
.progress(false, 0)
.build();
return new MaterialAlertDialogBuilder(context)
.setTitle(R.string.saving_changes)
.setCancelable(false)
.setView(R.layout.loading)
.create();
}
@Override
protected void onProgressUpdate(@NonNull Dialog dialog, Integer... values) {
super.onProgressUpdate(dialog, values);
((MaterialDialog) dialog).setMaxProgress(values[1]);
((MaterialDialog) dialog).setProgress(values[0]);
// ((MaterialDialog) dialog).setMaxProgress(values[1]);
// ((MaterialDialog) dialog).setProgress(values[0]);
}
}

View File

@ -22,15 +22,19 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.checkbox.MaterialCheckBox;
import java.util.List;
import code.name.monkey.appthemehelper.ThemeStore;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.model.CategoryInfo;
import code.name.monkey.retromusic.util.SwipeAndDragHelper;
import com.google.android.material.checkbox.MaterialCheckBox;
import java.util.List;
public class CategoryInfoAdapter extends RecyclerView.Adapter<CategoryInfoAdapter.ViewHolder>
implements SwipeAndDragHelper.ActionCompletionContract {

View File

@ -17,13 +17,18 @@ package code.name.monkey.retromusic.adapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.ViewCompat
import android.view.ViewOutlineProvider
import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.interfaces.IGenreClickListener
import code.name.monkey.retromusic.model.Genre
import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import java.util.*
/**
@ -36,6 +41,15 @@ class GenreAdapter(
private val mItemLayoutRes: Int,
private val listener: IGenreClickListener
) : RecyclerView.Adapter<GenreAdapter.ViewHolder>() {
init {
this.setHasStableIds(true)
}
override fun getItemId(position: Int): Long {
return dataSet[position].id
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(activity).inflate(mItemLayoutRes, parent, false))
}
@ -49,6 +63,28 @@ class GenreAdapter(
genre.songCount,
if (genre.songCount > 1) activity.getString(R.string.songs) else activity.getString(R.string.song)
)
loadGenreImage(genre, holder)
}
private fun loadGenreImage(genre: Genre, holder: GenreAdapter.ViewHolder) {
val genreSong = MusicUtil.songByGenre(genre.id)
GlideApp.with(activity)
.asBitmapPalette()
.load(RetroGlideExtension.getSongModel(genreSong))
.songCoverOptions(genreSong)
.into(object : RetroMusicColoredTarget(holder.image!!) {
override fun onColorReady(colors: MediaNotificationProcessor) {
setColors(holder, colors)
}
})
// Just for a bit of shadow around image
holder.image?.outlineProvider = ViewOutlineProvider.BOUNDS
}
private fun setColors(holder: ViewHolder, color: MediaNotificationProcessor) {
holder.imageContainerCard?.setCardBackgroundColor(color.backgroundColor)
holder.title?.setTextColor(color.primaryTextColor)
holder.text?.setTextColor(color.secondaryTextColor)
}
override fun getItemCount(): Int {
@ -62,7 +98,6 @@ class GenreAdapter(
inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) {
override fun onClick(v: View?) {
ViewCompat.setTransitionName(itemView, "genre")
listener.onClickGenre(dataSet[layoutPosition], itemView)
}
}

View File

@ -22,6 +22,7 @@ import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.AppCompatTextView
import androidx.core.os.bundleOf
import androidx.fragment.app.findFragment
import androidx.navigation.findNavController
import androidx.navigation.fragment.FragmentNavigatorExtras
import androidx.recyclerview.widget.GridLayoutManager
@ -34,14 +35,15 @@ import code.name.monkey.retromusic.adapter.album.AlbumAdapter
import code.name.monkey.retromusic.adapter.artist.ArtistAdapter
import code.name.monkey.retromusic.adapter.song.SongAdapter
import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.glide.SongGlideRequest
import code.name.monkey.retromusic.fragments.home.HomeFragment
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.interfaces.IAlbumClickListener
import code.name.monkey.retromusic.interfaces.IArtistClickListener
import code.name.monkey.retromusic.interfaces.IGenreClickListener
import code.name.monkey.retromusic.model.*
import code.name.monkey.retromusic.util.PreferenceUtil
import com.bumptech.glide.Glide
import com.google.android.material.card.MaterialCardView
class HomeAdapter(
@ -82,6 +84,7 @@ class HomeAdapter(
val viewHolder = holder as AlbumViewHolder
viewHolder.bindView(home)
viewHolder.clickableArea.setOnClickListener {
it.findFragment<HomeFragment>().setSharedAxisXTransitions()
activity.findNavController(R.id.fragment_container).navigate(
R.id.detailListFragment,
bundleOf("type" to RECENT_ALBUMS)
@ -92,6 +95,7 @@ class HomeAdapter(
val viewHolder = holder as AlbumViewHolder
viewHolder.bindView(home)
viewHolder.clickableArea.setOnClickListener {
it.findFragment<HomeFragment>().setSharedAxisXTransitions()
activity.findNavController(R.id.fragment_container).navigate(
R.id.detailListFragment,
bundleOf("type" to TOP_ALBUMS)
@ -102,6 +106,7 @@ class HomeAdapter(
val viewHolder = holder as ArtistViewHolder
viewHolder.bindView(home)
viewHolder.clickableArea.setOnClickListener {
it.findFragment<HomeFragment>().setSharedAxisXTransitions()
activity.findNavController(R.id.fragment_container).navigate(
R.id.detailListFragment,
bundleOf("type" to RECENT_ARTISTS)
@ -112,6 +117,7 @@ class HomeAdapter(
val viewHolder = holder as ArtistViewHolder
viewHolder.bindView(home)
viewHolder.clickableArea.setOnClickListener {
it.findFragment<HomeFragment>().setSharedAxisXTransitions()
activity.findNavController(R.id.fragment_container).navigate(
R.id.detailListFragment,
bundleOf("type" to TOP_ARTISTS)
@ -126,6 +132,7 @@ class HomeAdapter(
val viewHolder = holder as PlaylistViewHolder
viewHolder.bindView(home)
viewHolder.clickableArea.setOnClickListener {
it.findFragment<HomeFragment>().setSharedAxisXTransitions()
activity.findNavController(R.id.fragment_container).navigate(
R.id.detailListFragment,
bundleOf("type" to FAVOURITES)
@ -184,17 +191,29 @@ class HomeAdapter(
fun bindView(home: Home) {
val color = ThemeStore.accentColor(activity)
itemView.findViewById<TextView>(R.id.message).setTextColor(color)
itemView.findViewById<TextView>(R.id.message).apply {
setTextColor(color)
setOnClickListener {
MusicPlayerRemote.playNext((home.arrayList as List<Song>).subList(0, 8))
if (!MusicPlayerRemote.isPlaying) {
MusicPlayerRemote.playNextSong()
}
}
}
itemView.findViewById<MaterialCardView>(R.id.card6).apply {
setCardBackgroundColor(ColorUtil.withAlpha(color, 0.12f))
}
images.forEachIndexed { index, id ->
itemView.findViewById<View>(id).setOnClickListener {
MusicPlayerRemote.playNext(home.arrayList[index] as Song)
if (!MusicPlayerRemote.isPlaying) {
MusicPlayerRemote.playNextSong()
}
}
SongGlideRequest.Builder.from(Glide.with(activity), home.arrayList[index] as Song)
GlideApp.with(activity)
.asBitmap()
.build()
.songCoverOptions(home.arrayList[index] as Song)
.load(RetroGlideExtension.getSongModel(home.arrayList[index] as Song))
.into(itemView.findViewById(id))
}
}
@ -207,7 +226,7 @@ class HomeAdapter(
val songAdapter = SongAdapter(
activity,
home.arrayList as MutableList<Song>,
R.layout.item_album_card, null
R.layout.item_favourite_card, null
)
layoutManager = linearLayoutManager()
adapter = songAdapter
@ -257,7 +276,7 @@ class HomeAdapter(
bundleOf(EXTRA_ARTIST_ID to artistId),
null,
FragmentNavigatorExtras(
view to "artist"
view to artistId.toString()
)
)
}
@ -268,7 +287,7 @@ class HomeAdapter(
bundleOf(EXTRA_ALBUM_ID to albumId),
null,
FragmentNavigatorExtras(
view to "album"
view to albumId.toString()
)
)
}

View File

@ -29,15 +29,14 @@ import code.name.monkey.retromusic.*
import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder
import code.name.monkey.retromusic.db.PlaylistEntity
import code.name.monkey.retromusic.db.PlaylistWithSongs
import code.name.monkey.retromusic.glide.AlbumGlideRequest
import code.name.monkey.retromusic.glide.ArtistGlideRequest
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.menu.SongMenuHelper
import code.name.monkey.retromusic.model.*
import code.name.monkey.retromusic.model.smartplaylist.AbsSmartPlaylist
import code.name.monkey.retromusic.repository.PlaylistSongsLoader
import code.name.monkey.retromusic.util.MusicUtil
import com.bumptech.glide.Glide
import java.util.*
class SearchAdapter(
@ -52,7 +51,7 @@ class SearchAdapter(
override fun getItemViewType(position: Int): Int {
if (dataSet[position] is Album) return ALBUM
if (dataSet[position] is Artist) return ARTIST
if (dataSet[position] is Artist) return if ((dataSet[position] as Artist).isAlbumArtist) ALBUM_ARTIST else ARTIST
if (dataSet[position] is Genre) return GENRE
if (dataSet[position] is PlaylistEntity) return PLAYLIST
return if (dataSet[position] is Song) SONG else HEADER
@ -66,6 +65,14 @@ class SearchAdapter(
false
), viewType
)
else if (viewType == ALBUM || viewType == ARTIST || viewType== ALBUM_ARTIST)
ViewHolder(
LayoutInflater.from(activity).inflate(
R.layout.item_list_big,
parent,
false
), viewType
)
else
ViewHolder(
LayoutInflater.from(activity).inflate(R.layout.item_list, parent, false),
@ -80,21 +87,23 @@ class SearchAdapter(
val album = dataSet[position] as Album
holder.title?.text = album.title
holder.text?.text = album.artistName
AlbumGlideRequest.Builder.from(Glide.with(activity), album.safeGetFirstSong())
.checkIgnoreMediaStore().build().into(holder.image)
GlideApp.with(activity).asDrawable().albumCoverOptions(album.safeGetFirstSong()).load(RetroGlideExtension.getSongModel(album.safeGetFirstSong()))
.into(holder.image!!)
}
ARTIST -> {
holder.imageTextContainer?.isVisible = true
val artist = dataSet[position] as Artist
holder.title?.text = artist.name
holder.text?.text = MusicUtil.getArtistInfoString(activity, artist)
ArtistGlideRequest.Builder.from(Glide.with(activity), artist).build()
.into(holder.image)
GlideApp.with(activity).asDrawable().artistImageOptions(artist).load(
RetroGlideExtension.getArtistModel(artist)).into(holder.image!!)
}
SONG -> {
holder.imageTextContainer?.isVisible = true
val song = dataSet[position] as Song
holder.title?.text = song.title
holder.text?.text = song.albumName
GlideApp.with(activity).asDrawable().songCoverOptions(song).load(RetroGlideExtension.getSongModel(song)).into(holder.image!!)
}
GENRE -> {
val genre = dataSet[position] as Genre
@ -113,6 +122,14 @@ class SearchAdapter(
holder.title?.text = playlist.playlistName
//holder.text?.text = MusicUtil.playlistInfoString(activity, playlist.songs)
}
ALBUM_ARTIST -> {
holder.imageTextContainer?.isVisible = true
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!!)
}
else -> {
holder.title?.text = dataSet[position].toString()
holder.title?.setTextColor(ThemeStore.accentColor(activity))
@ -174,6 +191,12 @@ class SearchAdapter(
bundleOf(EXTRA_ARTIST_ID to (item as Artist).id)
)
}
ALBUM_ARTIST ->{
activity.findNavController(R.id.fragment_container).navigate(
R.id.albumArtistDetailsFragment,
bundleOf(EXTRA_ARTIST_NAME to (item as Artist).name)
)
}
GENRE -> {
activity.findNavController(R.id.fragment_container).navigate(
R.id.genreDetailsFragment,
@ -202,5 +225,6 @@ class SearchAdapter(
private const val SONG = 3
private const val GENRE = 4
private const val PLAYLIST = 5
private const val ALBUM_ARTIST = 6
}
}

View File

@ -24,19 +24,20 @@ import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter
import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.audiocover.AudioFileCover
import code.name.monkey.retromusic.interfaces.ICabHolder
import code.name.monkey.retromusic.interfaces.ICallbacks
import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.RetroUtil
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.signature.MediaStoreSignature
import me.zhanghai.android.fastscroll.PopupTextProvider
import java.io.File
import java.text.DecimalFormat
import kotlin.math.log10
import kotlin.math.pow
import me.zhanghai.android.fastscroll.PopupTextProvider
class SongFileAdapter(
private val activity: AppCompatActivity,
@ -111,14 +112,14 @@ class SongFileAdapter(
val error = RetroUtil.getTintedVectorDrawable(
activity, R.drawable.ic_file_music, iconColor
)
Glide.with(activity)
GlideApp.with(activity)
.load(AudioFileCover(file.path))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.error(error)
.placeholder(error)
.animate(android.R.anim.fade_in)
.transition(RetroGlideExtension.getDefaultTransition())
.signature(MediaStoreSignature("", file.lastModified(), 0))
.into(holder.image)
.into(holder.image!!)
}
}
@ -126,7 +127,7 @@ class SongFileAdapter(
return dataSet.size
}
override fun getIdentifier(position: Int): File? {
override fun getIdentifier(position: Int): File {
return dataSet[position]
}

View File

@ -0,0 +1,55 @@
package code.name.monkey.retromusic.adapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.retromusic.R
import java.io.File
class StorageAdapter(
val storageList: List<Storage>,
val storageClickListener: StorageClickListener
) :
RecyclerView.Adapter<StorageAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.item_storage,
parent,
false
)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bindData(storageList[position])
}
override fun getItemCount(): Int {
return storageList.size
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val title: TextView = itemView.findViewById(R.id.title)
fun bindData(storage: Storage) {
title.text = storage.title
}
init {
itemView.setOnClickListener { storageClickListener.onStorageClicked(storageList[bindingAdapterPosition]) }
}
}
}
interface StorageClickListener {
fun onStorageClicked(storage: Storage)
}
class Storage {
lateinit var title: String
lateinit var file: File
}

View File

@ -24,7 +24,8 @@ import androidx.fragment.app.FragmentActivity
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter
import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder
import code.name.monkey.retromusic.glide.AlbumGlideRequest
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.helper.SortOrder
import code.name.monkey.retromusic.helper.menu.SongsMenuHelper
@ -35,7 +36,6 @@ 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.color.MediaNotificationProcessor
import com.bumptech.glide.Glide
import me.zhanghai.android.fastscroll.PopupTextProvider
open class AlbumAdapter(
@ -73,7 +73,13 @@ open class AlbumAdapter(
}
protected open fun getAlbumText(album: Album): String? {
return album.artistName
return album.albumArtist.let {
if (it.isNullOrEmpty()) {
album.artistName
} else {
it
}
}
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
@ -82,6 +88,7 @@ open class AlbumAdapter(
holder.itemView.isActivated = isChecked
holder.title?.text = getAlbumTitle(album)
holder.text?.text = getAlbumText(album)
ViewCompat.setTransitionName(holder.image!!, album.id.toString())
loadAlbumCover(album, holder)
}
@ -92,17 +99,17 @@ open class AlbumAdapter(
holder.paletteColorContainer?.setBackgroundColor(color.backgroundColor)
}
holder.mask?.backgroundTintList = ColorStateList.valueOf(color.primaryTextColor)
holder.imageContainerCard?.setCardBackgroundColor(color.backgroundColor) }
holder.imageContainerCard?.setCardBackgroundColor(color.backgroundColor)
}
protected open fun loadAlbumCover(album: Album, holder: ViewHolder) {
if (holder.image == null) {
return
}
AlbumGlideRequest.Builder.from(Glide.with(activity), album.safeGetFirstSong())
.checkIgnoreMediaStore()
.generatePalette(activity)
.build()
val song = album.safeGetFirstSong()
GlideApp.with(activity).asBitmapPalette().albumCoverOptions(song)
//.checkIgnoreMediaStore()
.load(RetroGlideExtension.getSongModel(song))
.into(object : RetroMusicColoredTarget(holder.image!!) {
override fun onColorReady(colors: MediaNotificationProcessor) {
setColors(colors, holder)
@ -161,7 +168,6 @@ open class AlbumAdapter(
inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) {
init {
setImageTransitionName("Album")
menu?.visibility = View.GONE
}
@ -171,16 +177,13 @@ open class AlbumAdapter(
toggleChecked(layoutPosition)
} else {
image?.let {
ViewCompat.setTransitionName(it, "album")
listener?.onAlbumClick(dataSet[layoutPosition].id, it)
}
}
}
override fun onLongClick(v: View?): Boolean {
toggleChecked(layoutPosition)
return super.onLongClick(v)
return toggleChecked(layoutPosition)
}
}

View File

@ -26,15 +26,15 @@ import androidx.lifecycle.lifecycleScope
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.fragments.AlbumCoverStyle
import code.name.monkey.retromusic.fragments.NowPlayingScreen.*
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.glide.SongGlideRequest
import code.name.monkey.retromusic.misc.CustomFragmentStatePagerAdapter
import code.name.monkey.retromusic.model.Song
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.bumptech.glide.Glide
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -162,9 +162,10 @@ class AlbumCoverPagerAdapter(
}
private fun loadAlbumCover() {
SongGlideRequest.Builder.from(Glide.with(requireContext()), song)
.checkIgnoreMediaStore(requireContext())
.generatePalette(requireContext()).build()
GlideApp.with(this).asBitmapPalette().songCoverOptions(song)
//.checkIgnoreMediaStore()
.load(RetroGlideExtension.getSongModel(song))
.dontAnimate()
.into(object : RetroMusicColoredTarget(albumCover) {
override fun onColorReady(colors: MediaNotificationProcessor) {
setColor(colors)

View File

@ -17,7 +17,8 @@ package code.name.monkey.retromusic.adapter.album
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.FragmentActivity
import code.name.monkey.retromusic.glide.AlbumGlideRequest
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.helper.HorizontalAdapterHelper
import code.name.monkey.retromusic.interfaces.IAlbumClickListener
@ -25,7 +26,6 @@ import code.name.monkey.retromusic.interfaces.ICabHolder
import code.name.monkey.retromusic.model.Album
import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import com.bumptech.glide.Glide
class HorizontalAlbumAdapter(
activity: FragmentActivity,
@ -49,10 +49,8 @@ class HorizontalAlbumAdapter(
override fun loadAlbumCover(album: Album, holder: ViewHolder) {
if (holder.image == null) return
AlbumGlideRequest.Builder.from(Glide.with(activity), album.safeGetFirstSong())
.checkIgnoreMediaStore()
.generatePalette(activity)
.build()
GlideApp.with(activity).asBitmapPalette().albumCoverOptions(album.safeGetFirstSong())
.load(RetroGlideExtension.getSongModel(album.safeGetFirstSong()))
.into(object : RetroMusicColoredTarget(holder.image!!) {
override fun onColorReady(colors: MediaNotificationProcessor) {
setColors(colors, holder)
@ -60,7 +58,7 @@ class HorizontalAlbumAdapter(
})
}
override fun getAlbumText(album: Album): String? {
override fun getAlbumText(album: Album): String {
return MusicUtil.getYearString(album.year)
}

View File

@ -26,25 +26,28 @@ import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter
import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder
import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.glide.ArtistGlideRequest
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.helper.menu.SongsMenuHelper
import code.name.monkey.retromusic.interfaces.IAlbumArtistClickListener
import code.name.monkey.retromusic.interfaces.IArtistClickListener
import code.name.monkey.retromusic.interfaces.ICabHolder
import code.name.monkey.retromusic.model.Artist
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.color.MediaNotificationProcessor
import com.bumptech.glide.Glide
import java.util.*
import me.zhanghai.android.fastscroll.PopupTextProvider
import java.util.*
class ArtistAdapter(
val activity: FragmentActivity,
var dataSet: List<Artist>,
var itemLayoutRes: Int,
val ICabHolder: ICabHolder?,
val IArtistClickListener: IArtistClickListener
val IArtistClickListener: IArtistClickListener,
val IAlbumArtistClickListener: IAlbumArtistClickListener? = null
) : AbsMultiSelectAdapter<ArtistAdapter.ViewHolder, Artist>(
activity, ICabHolder, R.menu.menu_media_selection
), PopupTextProvider {
@ -82,6 +85,13 @@ 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())
}
}
loadArtistImage(artist, holder)
}
@ -98,9 +108,11 @@ class ArtistAdapter(
if (holder.image == null) {
return
}
ArtistGlideRequest.Builder.from(Glide.with(activity), artist)
.generatePalette(activity)
.build()
GlideApp.with(activity)
.asBitmapPalette()
.load(RetroGlideExtension.getArtistModel(artist))
.artistImageOptions(artist)
.transition(RetroGlideExtension.getDefaultTransition())
.into(object : RetroMusicColoredTarget(holder.image!!) {
override fun onColorReady(colors: MediaNotificationProcessor) {
setColors(colors, holder)
@ -112,7 +124,7 @@ class ArtistAdapter(
return dataSet.size
}
override fun getIdentifier(position: Int): Artist? {
override fun getIdentifier(position: Int): Artist {
return dataSet[position]
}
@ -154,16 +166,19 @@ class ArtistAdapter(
if (isInQuickSelectMode) {
toggleChecked(layoutPosition)
} else {
val artist = dataSet[layoutPosition]
image?.let {
ViewCompat.setTransitionName(it, "artist")
IArtistClickListener.onArtist(dataSet[layoutPosition].id, it)
if (PreferenceUtil.albumArtistsOnly && IAlbumArtistClickListener != null) {
IAlbumArtistClickListener.onAlbumArtist(artist.name, it)
} else {
IArtistClickListener.onArtist(artist.id, it)
}
}
}
}
override fun onLongClick(v: View?): Boolean {
toggleChecked(layoutPosition)
return super.onLongClick(v)
return toggleChecked(layoutPosition)
}
}
}

View File

@ -1,29 +1,39 @@
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 code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.interfaces.ICabHolder;
import com.afollestad.materialcab.MaterialCab;
import java.util.ArrayList;
import java.util.List;
public abstract class AbsMultiSelectAdapter<V extends RecyclerView.ViewHolder, I>
extends RecyclerView.Adapter<V> implements MaterialCab.Callback {
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.interfaces.ICabHolder;
@Nullable private final ICabHolder 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 List<I> checked;
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) {
@NonNull Context context, @Nullable ICabHolder ICabHolder, @MenuRes int menuRes) {
this.ICabHolder = ICabHolder;
checked = new ArrayList<>();
this.menuRes = menuRes;
@ -32,12 +42,16 @@ public abstract class AbsMultiSelectAdapter<V extends RecyclerView.ViewHolder, I
@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;
}
@ -111,6 +125,7 @@ public abstract class AbsMultiSelectAdapter<V extends RecyclerView.ViewHolder, I
notifyDataSetChanged();
}
@SuppressLint({"StringFormatInvalid", "StringFormatMatches"})
private void updateCab() {
if (ICabHolder != null) {
if (cab == null || !cab.isActive()) {
@ -120,10 +135,48 @@ public abstract class AbsMultiSelectAdapter<V extends RecyclerView.ViewHolder, I
if (size <= 0) {
cab.finish();
} else if (size == 1) {
cab.setTitle(getName(checked.get(0)));
} else {
cab.setTitle(context.getString(R.string.x_selected, size));
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

@ -24,11 +24,11 @@ import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.recyclerview.widget.RecyclerView;
import code.name.monkey.retromusic.R;
import com.google.android.material.card.MaterialCardView;
import com.h6ah4i.android.widget.advrecyclerview.utils.AbstractDraggableSwipeableItemViewHolder;
import code.name.monkey.retromusic.R;
public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHolder
implements View.OnLongClickListener, View.OnClickListener {

View File

@ -15,7 +15,6 @@
package code.name.monkey.retromusic.adapter.playlist
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.MenuItem
@ -24,29 +23,22 @@ import android.view.ViewGroup
import androidx.appcompat.widget.PopupMenu
import androidx.core.view.ViewCompat
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter
import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder
import code.name.monkey.retromusic.db.PlaylistEntity
import code.name.monkey.retromusic.db.PlaylistWithSongs
import code.name.monkey.retromusic.db.SongEntity
import code.name.monkey.retromusic.db.toSongs
import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.playlistPreview.PlaylistPreview
import code.name.monkey.retromusic.helper.menu.PlaylistMenuHelper
import code.name.monkey.retromusic.helper.menu.SongsMenuHelper
import code.name.monkey.retromusic.interfaces.ICabHolder
import code.name.monkey.retromusic.interfaces.IPlaylistClickListener
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.AutoGeneratedPlaylistBitmap
import code.name.monkey.retromusic.util.MusicUtil
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class PlaylistAdapter(
private val activity: FragmentActivity,
@ -95,27 +87,23 @@ class PlaylistAdapter(
holder.itemView.isActivated = isChecked(playlist)
holder.title?.text = getPlaylistTitle(playlist.playlistEntity)
holder.text?.text = getPlaylistText(playlist)
holder.image?.setImageDrawable(getIconRes())
val isChecked = isChecked(playlist)
if (isChecked) {
holder.menu?.hide()
} else {
holder.menu?.show()
}
//playlistBitmapLoader(activity, holder, playlist)
GlideApp.with(activity)
.load(PlaylistPreview(playlist))
.playlistOptions()
.into(holder.image!!)
}
private fun getIconRes(): Drawable = TintHelper.createTintedDrawable(
activity,
R.drawable.ic_playlist_play,
ATHUtil.resolveColor(activity, R.attr.colorControlNormal)
)
override fun getItemCount(): Int {
return dataSet.size
}
override fun getIdentifier(position: Int): PlaylistWithSongs? {
override fun getIdentifier(position: Int): PlaylistWithSongs {
return dataSet[position]
}
@ -141,18 +129,8 @@ class PlaylistAdapter(
return songs
}
private fun getSongs(playlist: PlaylistWithSongs): List<SongEntity> =
mutableListOf<SongEntity>().apply {
addAll(playlist.songs)
}
inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) {
init {
image?.apply {
val iconPadding =
activity.resources.getDimensionPixelSize(R.dimen.list_item_image_icon_padding)
setPadding(iconPadding, iconPadding, iconPadding, iconPadding)
}
menu?.setOnClickListener { view ->
val popupMenu = PopupMenu(activity, view)
popupMenu.inflate(R.menu.menu_item_playlist)
@ -183,37 +161,6 @@ class PlaylistAdapter(
}
}
private fun playlistBitmapLoader(
activity: FragmentActivity,
viewHolder: ViewHolder,
playlist: PlaylistWithSongs
) {
activity.lifecycleScope.launch(IO) {
val songs = playlist.songs.toSongs()
val bitmap = AutoGeneratedPlaylistBitmap.getBitmap(activity, songs, false, false)
withContext(Main) { viewHolder.image?.setImageBitmap(bitmap) }
}
/*
override fun doInBackground(vararg params: Void?): Bitmap {
val songs = playlist.songs.toSongs()
return AutoGeneratedPlaylistBitmap.getBitmap(activity, songs, false, false)
}
override fun onPostExecute(result: Bitmap?) {
super.onPostExecute(result)
viewHolder.image?.setImageBitmap(result)
val color = RetroColorUtil.getColor(
RetroColorUtil.generatePalette(
result
),
ATHUtil.resolveColor(activity, R.attr.colorSurface)
)
viewHolder.paletteColorContainer?.setBackgroundColor(color)
}*/
}
companion object {
val TAG: String = PlaylistAdapter::class.java.simpleName
}

View File

@ -17,105 +17,99 @@ package code.name.monkey.retromusic.adapter.song
import android.view.MenuItem
import android.view.View
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.R.menu
import code.name.monkey.retromusic.db.PlaylistEntity
import code.name.monkey.retromusic.db.toSongEntity
import code.name.monkey.retromusic.db.toSongs
import code.name.monkey.retromusic.db.toSongsEntity
import code.name.monkey.retromusic.dialogs.RemoveSongFromPlaylistDialog
import code.name.monkey.retromusic.extensions.applyColor
import code.name.monkey.retromusic.extensions.applyOutlineColor
import code.name.monkey.retromusic.fragments.LibraryViewModel
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.interfaces.ICabHolder
import code.name.monkey.retromusic.model.PlaylistSong
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.ViewUtil
import com.google.android.material.button.MaterialButton
import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter
import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemViewHolder
import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange
import com.h6ah4i.android.widget.advrecyclerview.draggable.annotation.DraggableItemStateFlags
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.koin.androidx.viewmodel.ext.android.viewModel
class OrderablePlaylistSongAdapter(
private val playlist: PlaylistEntity,
activity: FragmentActivity,
dataSet: ArrayList<Song>,
dataSet: MutableList<Song>,
itemLayoutRes: Int,
ICabHolder: ICabHolder?,
private val onMoveItemListener: OnMoveItemListener?
) : SongAdapter(
activity,
dataSet,
itemLayoutRes,
ICabHolder
), DraggableItemAdapter<OrderablePlaylistSongAdapter.ViewHolder> {
) : AbsOffsetSongAdapter(activity, dataSet, itemLayoutRes, ICabHolder),
DraggableItemAdapter<OrderablePlaylistSongAdapter.ViewHolder> {
val libraryViewModel: LibraryViewModel by activity.viewModel()
val tempDataSet = dataSet
init {
setMultiSelectMenuRes(menu.menu_playlists_songs_selection)
this.setHasStableIds(true)
this.setMultiSelectMenuRes(R.menu.menu_playlists_songs_selection)
}
override fun getItemId(position: Int): Long {
// requires static value, it means need to keep the same value
// even if the item position has been changed.
return if (position != 0) {
dataSet[position - 1].id
} else {
-1
}
}
override fun createViewHolder(view: View): SongAdapter.ViewHolder {
return ViewHolder(view)
}
override fun getItemId(position: Int): Long {
var positionFinal = position
positionFinal--
override fun getItemViewType(position: Int): Int {
return if (position == 0) OFFSET_ITEM else SONG
}
var long: Long = 0
if (positionFinal < 0) {
long = -2
} else {
if (dataSet[positionFinal] is PlaylistSong) {
long = (dataSet[positionFinal] as PlaylistSong).idInPlayList.toLong()
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)
}
viewHolder.shuffleAction?.let {
it.setOnClickListener {
MusicPlayerRemote.openAndShuffleQueue(dataSet, true)
}
it.applyColor(color)
}
} else {
super.onBindViewHolder(holder, position - 1)
}
return long
}
override fun onMultipleItemAction(menuItem: MenuItem, selection: List<Song>) {
when (menuItem.itemId) {
R.id.action_remove_from_playlist -> {
RemoveSongFromPlaylistDialog.create(selection.toSongs(playlist.playListId))
.show(activity.supportFragmentManager, "REMOVE_FROM_PLAYLIST")
return
}
}
super.onMultipleItemAction(menuItem, selection)
}
override fun onCheckCanStartDrag(holder: ViewHolder, position: Int, x: Int, y: Int): Boolean {
return onMoveItemListener != null && position > 0 && (ViewUtil.hitTest(
holder.dragView!!, x, y
) || ViewUtil.hitTest(holder.image!!, x, y))
}
override fun onGetItemDraggableRange(holder: ViewHolder, position: Int): ItemDraggableRange {
return ItemDraggableRange(1, dataSet.size)
}
override fun onMoveItem(fromPosition: Int, toPosition: Int) {
if (onMoveItemListener != null && fromPosition != toPosition) {
onMoveItemListener.onMoveItem(fromPosition - 1, toPosition - 1)
R.id.action_remove_from_playlist -> RemoveSongFromPlaylistDialog.create(
selection.toSongsEntity(
playlist
)
)
.show(activity.supportFragmentManager, "REMOVE_FROM_PLAYLIST")
else -> super.onMultipleItemAction(menuItem, selection)
}
}
override fun onCheckCanDrop(draggingPosition: Int, dropPosition: Int): Boolean {
return dropPosition > 0
}
override fun onItemDragStarted(position: Int) {
notifyDataSetChanged()
}
override fun onItemDragFinished(fromPosition: Int, toPosition: Int, result: Boolean) {
notifyDataSetChanged()
}
interface OnMoveItemListener {
fun onMoveItem(fromPosition: Int, toPosition: Int)
}
inner class ViewHolder(itemView: View) : SongAdapter.ViewHolder(itemView),
DraggableItemViewHolder {
@DraggableItemStateFlags
private var mDragStateFlags: Int = 0
inner class ViewHolder(itemView: View) : AbsOffsetSongAdapter.ViewHolder(itemView) {
val playAction: MaterialButton? = itemView.findViewById(R.id.playAction)
val shuffleAction: MaterialButton? = itemView.findViewById(R.id.shuffleAction)
override var songMenuRes: Int
get() = R.menu.menu_item_playlist_song
@ -123,16 +117,6 @@ class OrderablePlaylistSongAdapter(
super.songMenuRes = value
}
init {
if (dragView != null) {
if (onMoveItemListener != null) {
dragView?.visibility = View.VISIBLE
} else {
dragView?.visibility = View.GONE
}
}
}
override fun onSongMenuItemClick(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_remove_from_playlist -> {
@ -144,13 +128,58 @@ class OrderablePlaylistSongAdapter(
return super.onSongMenuItemClick(item)
}
@DraggableItemStateFlags
override fun getDragStateFlags(): Int {
return mDragStateFlags
init {
dragView?.visibility = View.VISIBLE
}
override fun setDragStateFlags(@DraggableItemStateFlags flags: Int) {
mDragStateFlags = flags
override fun onClick(v: View?) {
if (itemViewType == OFFSET_ITEM) {
MusicPlayerRemote.openAndShuffleQueue(dataSet, true)
return
}
super.onClick(v)
}
}
override fun onCheckCanStartDrag(holder: ViewHolder, position: Int, x: Int, y: Int): Boolean {
if (dataSet.size == 0 or 1) {
return false
}
val dragHandle = holder.dragView ?: return false
val handleWidth = dragHandle.width
val handleHeight = dragHandle.height
val handleLeft = dragHandle.left
val handleTop = dragHandle.top
return (x >= handleLeft && x < handleLeft + handleWidth &&
y >= handleTop && y < handleTop + handleHeight) && position != 0
}
override fun onMoveItem(fromPosition: Int, toPosition: Int) {
dataSet.add(toPosition - 1, dataSet.removeAt(fromPosition - 1))
}
override fun onGetItemDraggableRange(holder: ViewHolder, position: Int): ItemDraggableRange {
return ItemDraggableRange(1, itemCount - 1)
}
override fun onCheckCanDrop(draggingPosition: Int, dropPosition: Int): Boolean {
return true
}
override fun onItemDragStarted(position: Int) {
notifyDataSetChanged()
}
override fun onItemDragFinished(fromPosition: Int, toPosition: Int, result: Boolean) {
notifyDataSetChanged()
}
fun saveSongs(playlistEntity: PlaylistEntity) {
activity.lifecycleScope.launch(Dispatchers.IO) {
libraryViewModel.insertSongs(dataSet.toSongsEntity(playlistEntity))
}
}
}

View File

@ -19,8 +19,9 @@ 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
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.glide.SongGlideRequest
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.MusicPlayerRemote.isPlaying
import code.name.monkey.retromusic.helper.MusicPlayerRemote.playNextSong
@ -29,7 +30,6 @@ import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.ViewUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import com.bumptech.glide.Glide
import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter
import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange
import com.h6ah4i.android.widget.advrecyclerview.draggable.annotation.DraggableItemStateFlags
@ -79,9 +79,8 @@ class PlayingQueueAdapter(
if (holder.image == null) {
return
}
SongGlideRequest.Builder.from(Glide.with(activity), song)
.checkIgnoreMediaStore(activity)
.generatePalette(activity).build()
GlideApp.with(activity).asBitmapPalette().songCoverOptions(song)
.load(RetroGlideExtension.getSongModel(song))
.into(object : RetroMusicColoredTarget(holder.image!!) {
override fun onColorReady(colors: MediaNotificationProcessor) {
//setColors(colors, holder)

View File

@ -23,6 +23,8 @@ import code.name.monkey.retromusic.extensions.applyOutlineColor
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.interfaces.ICabHolder
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.RetroUtil
import com.google.android.material.button.MaterialButton
class ShuffleButtonSongAdapter(
@ -32,10 +34,15 @@ class ShuffleButtonSongAdapter(
ICabHolder: ICabHolder?
) : AbsOffsetSongAdapter(activity, dataSet, itemLayoutRes, ICabHolder) {
override fun createViewHolder(view: View): SongAdapter.ViewHolder {
return ViewHolder(view)
}
override fun getItemViewType(position: Int): Int {
return if (position == 0) OFFSET_ITEM else SONG
}
override fun onBindViewHolder(holder: SongAdapter.ViewHolder, position: Int) {
if (holder.itemViewType == OFFSET_ITEM) {
val color = ThemeStore.accentColor(activity)
@ -54,6 +61,10 @@ class ShuffleButtonSongAdapter(
}
} else {
super.onBindViewHolder(holder, position - 1)
val landscape = RetroUtil.isLandscape()
if ((PreferenceUtil.songGridSize > 2 && !landscape) || (PreferenceUtil.songGridSizeLand > 5 && landscape)) {
holder.menu?.visibility = View.GONE
}
}
}
@ -69,4 +80,5 @@ class ShuffleButtonSongAdapter(
super.onClick(v)
}
}
}

View File

@ -30,8 +30,9 @@ import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter
import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder
import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.glide.SongGlideRequest
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.SortOrder
import code.name.monkey.retromusic.helper.menu.SongMenuHelper
@ -42,7 +43,6 @@ import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import com.afollestad.materialcab.MaterialCab
import com.bumptech.glide.Glide
import me.zhanghai.android.fastscroll.PopupTextProvider
/**
@ -120,9 +120,8 @@ open class SongAdapter(
if (holder.image == null) {
return
}
SongGlideRequest.Builder.from(Glide.with(activity), song)
.checkIgnoreMediaStore(activity)
.generatePalette(activity).build()
GlideApp.with(activity).asBitmapPalette().songCoverOptions(song)
.load(RetroGlideExtension.getSongModel(song))
.into(object : RetroMusicColoredTarget(holder.image!!) {
override fun onColorReady(colors: MediaNotificationProcessor) {
setColors(colors, holder)
@ -130,15 +129,15 @@ open class SongAdapter(
})
}
private fun getSongTitle(song: Song): String? {
private fun getSongTitle(song: Song): String {
return song.title
}
private fun getSongText(song: Song): String? {
private fun getSongText(song: Song): String {
return song.artistName
}
private fun getSongText2(song: Song): String? {
private fun getSongText2(song: Song): String {
return song.albumName
}
@ -165,6 +164,7 @@ open class SongAdapter(
SortOrder.SongSortOrder.SONG_ARTIST -> dataSet[position].artistName
SortOrder.SongSortOrder.SONG_YEAR -> return MusicUtil.getYearString(dataSet[position].year)
SortOrder.SongSortOrder.COMPOSER -> dataSet[position].composer
SortOrder.SongSortOrder.SONG_ALBUM_ARTIST -> dataSet[position].albumArtist
else -> {
return ""
}

View File

@ -24,7 +24,6 @@ import android.os.Build
import code.name.monkey.retromusic.appshortcuts.shortcuttype.LastAddedShortcutType
import code.name.monkey.retromusic.appshortcuts.shortcuttype.ShuffleAllShortcutType
import code.name.monkey.retromusic.appshortcuts.shortcuttype.TopTracksShortcutType
import java.util.*
@TargetApi(Build.VERSION_CODES.N_MR1)
class DynamicShortcutManager(private val context: Context) {

View File

@ -27,15 +27,16 @@ import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.MainActivity
import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget
import code.name.monkey.retromusic.glide.SongGlideRequest
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.service.MusicService.*
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.RetroUtil
import com.bumptech.glide.Glide
import com.bumptech.glide.request.animation.GlideAnimation
import com.bumptech.glide.request.target.SimpleTarget
import com.bumptech.glide.request.target.Target
import com.bumptech.glide.request.transition.Transition
class AppWidgetBig : BaseAppWidget() {
private var target: Target<Bitmap>? = null // for cancellation
@ -158,20 +159,22 @@ class AppWidgetBig : BaseAppWidget() {
val appContext = service.applicationContext
service.runOnUiThread {
if (target != null) {
Glide.clear(target)
Glide.with(service).clear(target)
}
target = SongGlideRequest.Builder.from(Glide.with(appContext), song)
.checkIgnoreMediaStore(appContext).asBitmap().build()
target = GlideApp.with(appContext)
.asBitmap()
//.checkIgnoreMediaStore()
.load(RetroGlideExtension.getSongModel(song))
.into(object : SimpleTarget<Bitmap>(widgetImageSize, widgetImageSize) {
override fun onResourceReady(
resource: Bitmap,
glideAnimation: GlideAnimation<in Bitmap>
transition: Transition<in Bitmap>?
) {
update(resource)
}
override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) {
super.onLoadFailed(e, errorDrawable)
override fun onLoadFailed(errorDrawable: Drawable?) {
super.onLoadFailed(errorDrawable)
update(null)
}

View File

@ -27,7 +27,8 @@ import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.MainActivity
import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget
import code.name.monkey.retromusic.glide.SongGlideRequest
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.service.MusicService.*
@ -35,9 +36,9 @@ import code.name.monkey.retromusic.util.ImageUtil
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.RetroUtil
import com.bumptech.glide.Glide
import com.bumptech.glide.request.animation.GlideAnimation
import com.bumptech.glide.request.target.SimpleTarget
import com.bumptech.glide.request.target.Target
import com.bumptech.glide.request.transition.Transition
class AppWidgetCard : BaseAppWidget() {
private var target: Target<BitmapPaletteWrapper>? = null // for cancellation
@ -150,14 +151,15 @@ class AppWidgetCard : BaseAppWidget() {
// Load the album cover async and push the update on completion
service.runOnUiThread {
if (target != null) {
Glide.clear(target)
Glide.with(service).clear(target)
}
target = SongGlideRequest.Builder.from(Glide.with(service), song)
.checkIgnoreMediaStore(service).generatePalette(service).build().centerCrop()
target = GlideApp.with(service).asBitmapPalette().songCoverOptions(song)
.load(RetroGlideExtension.getSongModel(song))
.centerCrop()
.into(object : SimpleTarget<BitmapPaletteWrapper>(imageSize, imageSize) {
override fun onResourceReady(
resource: BitmapPaletteWrapper,
glideAnimation: GlideAnimation<in BitmapPaletteWrapper>
transition: Transition<in BitmapPaletteWrapper>?
) {
val palette = resource.palette
update(
@ -171,8 +173,8 @@ class AppWidgetCard : BaseAppWidget() {
)
}
override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) {
super.onLoadFailed(e, errorDrawable)
override fun onLoadFailed(errorDrawable: Drawable?) {
super.onLoadFailed(errorDrawable)
update(null, MaterialValueHelper.getSecondaryTextColor(service, true))
}

View File

@ -28,7 +28,8 @@ import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.MainActivity
import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget
import code.name.monkey.retromusic.glide.SongGlideRequest
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.service.MusicService.*
@ -36,9 +37,9 @@ import code.name.monkey.retromusic.util.ImageUtil
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.RetroUtil
import com.bumptech.glide.Glide
import com.bumptech.glide.request.animation.GlideAnimation
import com.bumptech.glide.request.target.SimpleTarget
import com.bumptech.glide.request.target.Target
import com.bumptech.glide.request.transition.Transition
class AppWidgetClassic : BaseAppWidget() {
private var target: Target<BitmapPaletteWrapper>? = null // for cancellation
@ -120,14 +121,16 @@ class AppWidgetClassic : BaseAppWidget() {
val appContext = service.applicationContext
service.runOnUiThread {
if (target != null) {
Glide.clear(target)
Glide.with(service).clear(target)
}
target = SongGlideRequest.Builder.from(Glide.with(service), song)
.checkIgnoreMediaStore(service).generatePalette(service).build().centerCrop()
target = GlideApp.with(service).asBitmapPalette().songCoverOptions(song)
.load(RetroGlideExtension.getSongModel(song))
//.checkIgnoreMediaStore()
.centerCrop()
.into(object : SimpleTarget<BitmapPaletteWrapper>(imageSize, imageSize) {
override fun onResourceReady(
resource: BitmapPaletteWrapper,
glideAnimation: GlideAnimation<in BitmapPaletteWrapper>
transition: Transition<in BitmapPaletteWrapper>?
) {
val palette = resource.palette
update(
@ -143,8 +146,8 @@ class AppWidgetClassic : BaseAppWidget() {
)
}
override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) {
super.onLoadFailed(e, errorDrawable)
override fun onLoadFailed(errorDrawable: Drawable?) {
super.onLoadFailed(errorDrawable)
update(null, Color.WHITE)
}

View File

@ -27,16 +27,17 @@ import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.MainActivity
import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget
import code.name.monkey.retromusic.glide.SongGlideRequest
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.service.MusicService.*
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.RetroUtil
import com.bumptech.glide.Glide
import com.bumptech.glide.request.animation.GlideAnimation
import com.bumptech.glide.request.target.SimpleTarget
import com.bumptech.glide.request.target.Target
import com.bumptech.glide.request.transition.Transition
class AppWidgetSmall : BaseAppWidget() {
private var target: Target<BitmapPaletteWrapper>? = null // for cancellation
@ -123,14 +124,16 @@ class AppWidgetSmall : BaseAppWidget() {
val appContext = service.applicationContext
service.runOnUiThread {
if (target != null) {
Glide.clear(target)
Glide.with(service).clear(target)
}
target = SongGlideRequest.Builder.from(Glide.with(service), song)
.checkIgnoreMediaStore(service).generatePalette(service).build().centerCrop()
target = GlideApp.with(service).asBitmapPalette().songCoverOptions(song)
//.checkIgnoreMediaStore()
.load(RetroGlideExtension.getSongModel(song))
.centerCrop()
.into(object : SimpleTarget<BitmapPaletteWrapper>(imageSize, imageSize) {
override fun onResourceReady(
resource: BitmapPaletteWrapper,
glideAnimation: GlideAnimation<in BitmapPaletteWrapper>
transition: Transition<in BitmapPaletteWrapper>?
) {
val palette = resource.palette
update(
@ -144,8 +147,8 @@ class AppWidgetSmall : BaseAppWidget() {
)
}
override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) {
super.onLoadFailed(e, errorDrawable)
override fun onLoadFailed(errorDrawable: Drawable?) {
super.onLoadFailed(errorDrawable)
update(null, MaterialValueHelper.getSecondaryTextColor(service, true))
}

View File

@ -0,0 +1,101 @@
/*
* 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.auto;
import androidx.annotation.NonNull;
/**
* Created by Beesham Sarendranauth (Beesham)
*/
public class AutoMediaIDHelper {
// Media IDs used on browseable items of MediaBrowser
public static final String MEDIA_ID_EMPTY_ROOT = "__EMPTY_ROOT__";
public static final String MEDIA_ID_ROOT = "__ROOT__";
public static final String MEDIA_ID_MUSICS_BY_SEARCH = "__BY_SEARCH__"; // TODO
public static final String MEDIA_ID_MUSICS_BY_HISTORY = "__BY_HISTORY__";
public static final String MEDIA_ID_MUSICS_BY_TOP_TRACKS = "__BY_TOP_TRACKS__";
public static final String MEDIA_ID_MUSICS_BY_SUGGESTIONS = "__BY_SUGGESTIONS__";
public static final String MEDIA_ID_MUSICS_BY_PLAYLIST = "__BY_PLAYLIST__";
public static final String MEDIA_ID_MUSICS_BY_ALBUM = "__BY_ALBUM__";
public static final String MEDIA_ID_MUSICS_BY_ARTIST = "__BY_ARTIST__";
public static final String MEDIA_ID_MUSICS_BY_ALBUM_ARTIST = "__BY_ALBUM_ARTIST__";
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__";
private static final String CATEGORY_SEPARATOR = "__/__";
private static final String LEAF_SEPARATOR = "__|__";
/**
* Create a String value that represents a playable or a browsable media.
* <p/>
* Encode the media browseable categories, if any, and the unique music ID, if any,
* into a single String mediaID.
* <p/>
* MediaIDs are of the form <categoryType>__/__<categoryValue>__|__<musicUniqueId>, to make it
* easy to find the category (like genre) that a music was selected from, so we
* can correctly build the playing queue. This is specially useful when
* one music can appear in more than one list, like "by genre -> genre_1"
* and "by artist -> artist_1".
*
* @param mediaID Unique ID for playable items, or null for browseable items.
* @param categories Hierarchy of categories representing this item's browsing parents.
* @return A hierarchy-aware media ID.
*/
public static String createMediaID(String mediaID, String... categories) {
StringBuilder sb = new StringBuilder();
if (categories != null) {
for (int i = 0; i < categories.length; i++) {
if (!isValidCategory(categories[i])) {
throw new IllegalArgumentException("Invalid category: " + categories[i]);
}
sb.append(categories[i]);
if (i < categories.length - 1) {
sb.append(CATEGORY_SEPARATOR);
}
}
}
if (mediaID != null) {
sb.append(LEAF_SEPARATOR).append(mediaID);
}
return sb.toString();
}
public static String extractCategory(@NonNull String mediaID) {
int pos = mediaID.indexOf(LEAF_SEPARATOR);
if (pos >= 0) {
return mediaID.substring(0, pos);
}
return mediaID;
}
public static String extractMusicID(@NonNull String mediaID) {
int pos = mediaID.indexOf(LEAF_SEPARATOR);
if (pos >= 0) {
return mediaID.substring(pos + LEAF_SEPARATOR.length());
}
return null;
}
public static boolean isBrowseable(@NonNull String mediaID) {
return !mediaID.contains(LEAF_SEPARATOR);
}
private static boolean isValidCategory(String category) {
return category == null ||
(!category.contains(CATEGORY_SEPARATOR) && !category.contains(LEAF_SEPARATOR));
}
}

View File

@ -0,0 +1,285 @@
/*
* 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.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
import code.name.monkey.retromusic.model.CategoryInfo
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.repository.*
import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.PreferenceUtil
import java.lang.ref.WeakReference
import java.util.*
/**
* Created by Beesham Sarendranauth (Beesham)
*/
class AutoMusicProvider(
val mContext: Context,
private val songsRepository: SongRepository,
private val albumsRepository: AlbumRepository,
private val artistsRepository: ArtistRepository,
private val genresRepository: GenreRepository,
private val playlistsRepository: PlaylistRepository,
private val topPlayedRepository: TopPlayedRepository
) {
private var mMusicService: WeakReference<MusicService>? = null
fun setMusicService(service: MusicService) {
mMusicService = WeakReference(service)
}
fun getChildren(mediaId: String?, resources: Resources): List<MediaBrowserCompat.MediaItem> {
val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = ArrayList()
when (mediaId) {
AutoMediaIDHelper.MEDIA_ID_ROOT -> {
mediaItems.addAll(getRootChildren(resources))
}
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_PLAYLIST -> for (playlist in playlistsRepository.playlists()) {
mediaItems.add(
AutoMediaItem.with(mContext)
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_PLAYLIST, playlist.id)
.icon(R.drawable.ic_playlist_play)
.title(playlist.name)
.subTitle(playlist.getInfoString(mContext))
.asPlayable()
.build()
)
}
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ALBUM -> for (album in albumsRepository.albums()) {
mediaItems.add(
AutoMediaItem.with(mContext)
.path(mediaId, album.id)
.title(album.title)
.subTitle(album.albumArtist ?: album.artistName)
.icon(MusicUtil.getMediaStoreAlbumCoverUri(album.id))
.asPlayable()
.build()
)
}
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ARTIST -> for (artist in artistsRepository.artists()) {
mediaItems.add(
AutoMediaItem.with(mContext)
.asPlayable()
.path(mediaId, artist.id)
.title(artist.name)
.build()
)
}
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ALBUM_ARTIST -> for (artist in artistsRepository.albumArtists()) {
mediaItems.add(
AutoMediaItem.with(mContext)
.asPlayable()
// we just pass album id here as we don't have album artist id's
.path(mediaId, artist.safeGetFirstAlbum().id)
.title(artist.name)
.build()
)
}
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_GENRE -> for (genre in genresRepository.genres()) {
mediaItems.add(
AutoMediaItem.with(mContext)
.asPlayable()
.path(mediaId, genre.id)
.title(genre.name)
.build()
)
}
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_QUEUE ->
mMusicService?.get()?.playingQueue
?.let {
for (song in it) {
mediaItems.add(
AutoMediaItem.with(mContext)
.asPlayable()
.path(mediaId, song.id)
.title(song.title)
.subTitle(song.artistName)
.icon(MusicUtil.getMediaStoreAlbumCoverUri(song.albumId))
.build()
)
}
}
else -> {
getPlaylistChildren(mediaId, mediaItems)
}
}
return mediaItems
}
private fun getPlaylistChildren(
mediaId: String?,
mediaItems: MutableList<MediaBrowserCompat.MediaItem>
) {
val songs = when (mediaId) {
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_TOP_TRACKS -> {
topPlayedRepository.topTracks()
}
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_HISTORY -> {
topPlayedRepository.recentlyPlayedTracks()
}
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_SUGGESTIONS -> {
topPlayedRepository.notRecentlyPlayedTracks().take(8)
}
else -> {
emptyList()
}
}
songs.forEach { song ->
mediaItems.add(
getPlayableSong(mediaId, song)
)
}
}
private fun getRootChildren(resources: Resources): List<MediaBrowserCompat.MediaItem> {
val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = ArrayList()
val libraryCategories = PreferenceUtil.libraryCategory
libraryCategories.forEach {
if (it.visible) {
when (it.category) {
CategoryInfo.Category.Albums -> {
mediaItems.add(
AutoMediaItem.with(mContext)
.asBrowsable()
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ALBUM)
.gridLayout(true)
.icon(R.drawable.ic_album)
.title(resources.getString(R.string.albums)).build()
)
}
CategoryInfo.Category.Artists -> {
if (PreferenceUtil.albumArtistsOnly) {
mediaItems.add(
AutoMediaItem.with(mContext)
.asBrowsable()
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ALBUM_ARTIST)
.icon(R.drawable.ic_album_artist)
.title(resources.getString(R.string.album_artist)).build()
)
} else {
mediaItems.add(
AutoMediaItem.with(mContext)
.asBrowsable()
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ARTIST)
.icon(R.drawable.ic_artist)
.title(resources.getString(R.string.artists)).build()
)
}
}
CategoryInfo.Category.Genres -> {
mediaItems.add(
AutoMediaItem.with(mContext)
.asBrowsable()
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_GENRE)
.icon(R.drawable.ic_guitar)
.title(resources.getString(R.string.genres)).build()
)
}
CategoryInfo.Category.Playlists -> {
mediaItems.add(
AutoMediaItem.with(mContext)
.asBrowsable()
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_PLAYLIST)
.icon(R.drawable.ic_playlist_play)
.title(resources.getString(R.string.playlists)).build()
)
}
else -> {
}
}
}
}
mediaItems.add(
AutoMediaItem.with(mContext)
.asPlayable()
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_SHUFFLE)
.icon(R.drawable.ic_shuffle)
.title(resources.getString(R.string.action_shuffle_all))
.subTitle(MusicUtil.getPlaylistInfoString(mContext, songsRepository.songs()))
.build()
)
mediaItems.add(
AutoMediaItem.with(mContext)
.asBrowsable()
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_QUEUE)
.icon(R.drawable.ic_queue_music)
.title(resources.getString(R.string.queue))
.subTitle(MusicUtil.getPlaylistInfoString(mContext, MusicPlayerRemote.playingQueue))
.asBrowsable().build()
)
mediaItems.add(
AutoMediaItem.with(mContext)
.asBrowsable()
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_TOP_TRACKS)
.icon(R.drawable.ic_trending_up)
.title(resources.getString(R.string.my_top_tracks))
.subTitle(
MusicUtil.getPlaylistInfoString(
mContext,
topPlayedRepository.topTracks()
)
)
.asBrowsable().build()
)
mediaItems.add(
AutoMediaItem.with(mContext)
.asBrowsable()
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_SUGGESTIONS)
.icon(R.drawable.ic_face)
.title(resources.getString(R.string.suggestion_songs))
.subTitle(
MusicUtil.getPlaylistInfoString(
mContext,
topPlayedRepository.notRecentlyPlayedTracks().takeIf {
it.size > 9
} ?: emptyList()
)
)
.asBrowsable().build()
)
mediaItems.add(
AutoMediaItem.with(mContext)
.asBrowsable()
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_HISTORY)
.icon(R.drawable.ic_history)
.title(resources.getString(R.string.history))
.subTitle(
MusicUtil.getPlaylistInfoString(
mContext,
topPlayedRepository.recentlyPlayedTracks()
)
)
.asBrowsable().build()
)
return mediaItems
}
private fun getPlayableSong(mediaId: String?, song: Song): MediaBrowserCompat.MediaItem {
return AutoMediaItem.with(mContext)
.asPlayable()
.path(mediaId, song.id)
.title(song.title)
.subTitle(song.artistName)
.icon(MusicUtil.getMediaStoreAlbumCoverUri(song.albumId))
.build()
}
}

View File

@ -0,0 +1,102 @@
package code.name.monkey.retromusic.auto
import android.content.Context
import android.net.Uri
import android.os.Bundle
import android.support.v4.media.MediaBrowserCompat
import android.support.v4.media.MediaDescriptionCompat
import code.name.monkey.retromusic.util.ImageUtil
internal object AutoMediaItem {
fun with(context: Context): Builder {
return Builder(context)
}
internal class Builder(val mContext: Context) {
private var mBuilder: MediaDescriptionCompat.Builder?
private var mFlags = 0
fun path(fullPath: String): Builder {
mBuilder?.setMediaId(fullPath)
return this
}
fun path(path: String?, id: Long): Builder {
return path(AutoMediaIDHelper.createMediaID(id.toString(), path))
}
fun title(title: String): Builder {
mBuilder?.setTitle(title)
return this
}
fun subTitle(subTitle: String): Builder {
mBuilder?.setSubtitle(subTitle)
return this
}
fun icon(uri: Uri?): Builder {
mBuilder?.setIconUri(uri)
return this
}
fun icon(iconDrawableId: Int): Builder {
mBuilder?.setIconBitmap(
ImageUtil.createBitmap(
ImageUtil.getVectorDrawable(
mContext.resources,
iconDrawableId,
mContext.theme
)
)
)
return this
}
fun gridLayout(isGrid: Boolean): Builder {
val hints = Bundle()
hints.putBoolean(CONTENT_STYLE_SUPPORTED, true)
hints.putInt(
CONTENT_STYLE_BROWSABLE_HINT,
if (isGrid) CONTENT_STYLE_GRID_ITEM_HINT_VALUE else CONTENT_STYLE_LIST_ITEM_HINT_VALUE
)
hints.putInt(
CONTENT_STYLE_PLAYABLE_HINT,
if (isGrid) CONTENT_STYLE_GRID_ITEM_HINT_VALUE else CONTENT_STYLE_LIST_ITEM_HINT_VALUE
)
mBuilder?.setExtras(hints)
return this
}
fun asBrowsable(): Builder {
mFlags = mFlags or MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
return this
}
fun asPlayable(): Builder {
mFlags = mFlags or MediaBrowserCompat.MediaItem.FLAG_PLAYABLE
return this
}
fun build(): MediaBrowserCompat.MediaItem {
val result = MediaBrowserCompat.MediaItem(mBuilder!!.build(), mFlags)
mBuilder = null
mFlags = 0
return result
}
init {
mBuilder = MediaDescriptionCompat.Builder()
}
companion object{
// Hints - see https://developer.android.com/training/cars/media#default-content-style
const val CONTENT_STYLE_SUPPORTED = "android.media.browse.CONTENT_STYLE_SUPPORTED"
const val CONTENT_STYLE_BROWSABLE_HINT = "android.media.browse.CONTENT_STYLE_BROWSABLE_HINT"
const val CONTENT_STYLE_PLAYABLE_HINT = "android.media.browse.CONTENT_STYLE_PLAYABLE_HINT"
const val CONTENT_STYLE_LIST_ITEM_HINT_VALUE = 1
const val CONTENT_STYLE_GRID_ITEM_HINT_VALUE = 2
}
}
}

View File

@ -0,0 +1,83 @@
package code.name.monkey.retromusic.cast
import androidx.core.net.toUri
import code.name.monkey.retromusic.cast.RetroWebServer.Companion.MIME_TYPE_AUDIO
import code.name.monkey.retromusic.cast.RetroWebServer.Companion.PART_COVER_ART
import code.name.monkey.retromusic.cast.RetroWebServer.Companion.PART_SONG
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.RetroUtil
import com.google.android.gms.cast.*
import com.google.android.gms.cast.MediaInfo.STREAM_TYPE_BUFFERED
import com.google.android.gms.cast.MediaMetadata.*
import com.google.android.gms.cast.framework.CastSession
import com.google.android.gms.common.images.WebImage
import java.net.MalformedURLException
import java.net.URL
object CastHelper {
private const val CAST_MUSIC_METADATA_ID = "metadata_id"
private const val CAST_MUSIC_METADATA_ALBUM_ID = "metadata_album_id"
private const val CAST_URL_PROTOCOL = "http"
fun castSong(castSession: CastSession, song: Song) {
try {
val remoteMediaClient = castSession.remoteMediaClient
val mediaLoadOptions = MediaLoadOptions.Builder().apply {
setPlayPosition(0)
setAutoplay(true)
}.build()
remoteMediaClient?.load(song.toMediaInfo()!!, mediaLoadOptions)
} catch (e: Exception) {
e.printStackTrace()
}
}
fun castQueue(castSession: CastSession, songs: List<Song>, position: Int, progress: Long) {
try {
val remoteMediaClient = castSession.remoteMediaClient
remoteMediaClient?.queueLoad(
songs.toMediaInfoList(),
position,
MediaStatus.REPEAT_MODE_REPEAT_OFF,
progress,
null
)
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun List<Song>.toMediaInfoList(): Array<MediaQueueItem> {
return map { MediaQueueItem.Builder(it.toMediaInfo()!!).build() }.toTypedArray()
}
private fun Song.toMediaInfo(): MediaInfo? {
val song = this
val baseUrl: URL
try {
baseUrl = URL(CAST_URL_PROTOCOL, RetroUtil.getIpAddress(true), SERVER_PORT, "")
} catch (e: MalformedURLException) {
return null
}
val songUrl = "$baseUrl/$PART_SONG?id=${song.id}"
val albumArtUrl = "$baseUrl/$PART_COVER_ART?id=${song.albumId}"
val musicMetadata = MediaMetadata(MEDIA_TYPE_MUSIC_TRACK).apply {
putInt(CAST_MUSIC_METADATA_ID, song.id.toInt())
putInt(CAST_MUSIC_METADATA_ALBUM_ID, song.albumId.toInt())
putString(KEY_TITLE, song.title)
putString(KEY_ARTIST, song.artistName)
putString(KEY_ALBUM_TITLE, song.albumName)
putInt(KEY_TRACK_NUMBER, song.trackNumber)
addImage(WebImage(albumArtUrl.toUri()))
}
return MediaInfo.Builder(songUrl).apply {
setStreamType(STREAM_TYPE_BUFFERED)
setContentType(MIME_TYPE_AUDIO)
setMetadata(musicMetadata)
setStreamDuration(song.duration)
}.build()
}
}

View File

@ -0,0 +1,41 @@
package code.name.monkey.retromusic.cast
import android.content.Context
import com.google.android.gms.cast.CastMediaControlIntent
import com.google.android.gms.cast.framework.CastOptions
import com.google.android.gms.cast.framework.OptionsProvider
import com.google.android.gms.cast.framework.SessionProvider
import com.google.android.gms.cast.framework.media.CastMediaOptions
import com.google.android.gms.cast.framework.media.MediaIntentReceiver
import com.google.android.gms.cast.framework.media.NotificationOptions
import java.util.*
class CastOptionsProvider : OptionsProvider {
override fun getCastOptions(context: Context): CastOptions {
val buttonActions: MutableList<String> = ArrayList()
buttonActions.add(MediaIntentReceiver.ACTION_SKIP_PREV)
buttonActions.add(MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK)
buttonActions.add(MediaIntentReceiver.ACTION_SKIP_NEXT)
buttonActions.add(MediaIntentReceiver.ACTION_STOP_CASTING)
val compatButtonActionsIndices = intArrayOf(1, 3)
val notificationOptions = NotificationOptions.Builder()
.setActions(buttonActions, compatButtonActionsIndices)
.setTargetActivityClassName(ExpandedControlsActivity::class.java.name)
.build()
val mediaOptions = CastMediaOptions.Builder()
.setNotificationOptions(notificationOptions)
.setExpandedControllerActivityClassName(ExpandedControlsActivity::class.java.name)
.build()
return CastOptions.Builder()
.setReceiverApplicationId(CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID)
.setCastMediaOptions(mediaOptions)
.build()
}
override fun getAdditionalSessionProviders(context: Context?): List<SessionProvider>? {
return null
}
}

View File

@ -0,0 +1,19 @@
package code.name.monkey.retromusic.cast
import android.view.Menu
import code.name.monkey.retromusic.R
import com.google.android.gms.cast.framework.CastButtonFactory
import com.google.android.gms.cast.framework.media.widget.ExpandedControllerActivity
class ExpandedControlsActivity : ExpandedControllerActivity() {
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
super.onCreateOptionsMenu(menu)
menuInflater.inflate(R.menu.menu_cast, menu)
CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.action_cast)
return true
}
}

View File

@ -0,0 +1,22 @@
package code.name.monkey.retromusic.cast
import com.google.android.gms.cast.framework.CastSession
import com.google.android.gms.cast.framework.SessionManagerListener
interface RetroSessionManager : SessionManagerListener<CastSession> {
override fun onSessionResuming(p0: CastSession, p1: String) {
}
override fun onSessionStartFailed(p0: CastSession, p1: Int) {
}
override fun onSessionResumeFailed(p0: CastSession, p1: Int) {
}
override fun onSessionEnding(p0: CastSession) {
}
}

View File

@ -0,0 +1,140 @@
package code.name.monkey.retromusic.cast
import android.content.Context
import code.name.monkey.retromusic.util.MusicUtil
import fi.iki.elonen.NanoHTTPD
import fi.iki.elonen.NanoHTTPD.Response.Status
import java.io.*
const val SERVER_PORT = 9090
class RetroWebServer(val context: Context) : NanoHTTPD(SERVER_PORT) {
companion object {
private const val MIME_TYPE_IMAGE = "image/jpg"
const val MIME_TYPE_AUDIO = "audio/mp3"
const val PART_COVER_ART = "coverart"
const val PART_SONG = "song"
const val PARAM_ID = "id"
var mRetroWebServer: RetroWebServer? = null
fun getInstance(context: Context): RetroWebServer {
if (mRetroWebServer == null) {
mRetroWebServer = RetroWebServer(context)
}
return mRetroWebServer!!
}
}
override fun serve(
uri: String?,
method: Method?,
headers: MutableMap<String, String>?,
parms: MutableMap<String, String>?,
files: MutableMap<String, String>?
): Response {
if (uri?.contains(PART_COVER_ART) == true) {
val albumId = parms?.get(PARAM_ID) ?: return errorResponse()
val albumArtUri = MusicUtil.getMediaStoreAlbumCoverUri(albumId.toLong())
val fis: InputStream?
try {
fis = context.contentResolver.openInputStream(albumArtUri)
} catch (e: FileNotFoundException) {
return errorResponse()
}
return newChunkedResponse(Status.OK, MIME_TYPE_IMAGE, fis)
} else if (uri?.contains(PART_SONG) == true) {
val songId = parms?.get(PARAM_ID) ?: return errorResponse()
val songUri = MusicUtil.getSongFileUri(songId.toLong())
val songPath = MusicUtil.getSongFilePath(context, songUri)
val song = File(songPath)
return serveFile(headers!!, song, MIME_TYPE_AUDIO)
}
return newFixedLengthResponse(Status.NOT_FOUND, MIME_PLAINTEXT, "Not Found")
}
private fun serveFile(
header: MutableMap<String, String>, file: File,
mime: String
): Response {
var res: Response
try {
// Support (simple) skipping:
var startFrom: Long = 0
var endAt: Long = -1
// The value of header range will be bytes=0-1024 something like this
// We get the value of from Bytes i.e. startFrom and toBytes i.e. endAt below
var range = header["range"]
if (range != null) {
if (range.startsWith("bytes=")) {
range = range.substring("bytes=".length)
val minus = range.indexOf('-')
try {
if (minus > 0) {
startFrom = range
.substring(0, minus).toLong()
endAt = range.substring(minus + 1).toLong()
}
} catch (ignored: NumberFormatException) {
}
}
}
// Chunked Response is used when serving audio file
// Change return code and add Content-Range header when skipping is
// requested
val fileLen = file.length()
if (range != null && startFrom >= 0) {
if (startFrom >= fileLen) {
res = newFixedLengthResponse(
Status.RANGE_NOT_SATISFIABLE,
MIME_PLAINTEXT, ""
)
res.addHeader("Content-Range", "bytes 0-0/$fileLen")
} else {
if (endAt < 0) {
endAt = fileLen - 1
}
var newLen = endAt - startFrom + 1
if (newLen < 0) {
newLen = 0
}
val dataLen = newLen
val fis: FileInputStream = object : FileInputStream(file) {
@Throws(IOException::class)
override fun available(): Int {
return dataLen.toInt()
}
}
fis.skip(startFrom)
res = newChunkedResponse(
Status.PARTIAL_CONTENT, mime,
fis
)
res.addHeader("Content-Length", "" + dataLen)
res.addHeader(
"Content-Range", "bytes " + startFrom + "-"
+ endAt + "/" + fileLen
)
}
} else {
res = newFixedLengthResponse(
Status.OK, mime,
FileInputStream(file), file.length()
)
res.addHeader("Accept-Ranges", "bytes")
res.addHeader("Content-Length", "" + fileLen)
}
} catch (ioe: IOException) {
res = newFixedLengthResponse(
Status.FORBIDDEN,
MIME_PLAINTEXT, "FORBIDDEN: Reading file failed."
)
}
return res
}
private fun errorResponse(message: String = "Error Occurred"): Response {
return newFixedLengthResponse(Status.INTERNAL_ERROR, MIME_PLAINTEXT, message)
}
}

View File

@ -47,7 +47,7 @@ interface PlaylistDao {
@Query("SELECT * FROM SongEntity WHERE playlist_creator_id = :playlistId AND id = :songId")
suspend fun isSongExistsInPlaylist(playlistId: Long, songId: Long): List<SongEntity>
@Query("SELECT * FROM SongEntity WHERE playlist_creator_id = :playlistId")
@Query("SELECT * FROM SongEntity WHERE playlist_creator_id = :playlistId ORDER BY song_key asc")
fun songsFromPlaylist(playlistId: Long): LiveData<List<SongEntity>>
@Delete

View File

@ -18,7 +18,7 @@ import android.os.Parcelable
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import kotlinx.android.parcel.Parcelize
import kotlinx.parcelize.Parcelize
@Entity
@Parcelize

View File

@ -17,7 +17,7 @@ package code.name.monkey.retromusic.db
import android.os.Parcelable
import androidx.room.Embedded
import androidx.room.Relation
import kotlinx.android.parcel.Parcelize
import kotlinx.parcelize.Parcelize
@Parcelize
data class PlaylistWithSongs(

View File

@ -19,7 +19,7 @@ import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
import kotlinx.android.parcel.Parcelize
import kotlinx.parcelize.Parcelize
@Parcelize
@Entity(indices = [Index(value = ["playlist_creator_id", "id"], unique = true)])

View File

@ -1,155 +0,0 @@
package code.name.monkey.retromusic.dialogs;
import android.Manifest;
import android.app.Dialog;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.DialogFragment;
import code.name.monkey.retromusic.R;
import com.afollestad.materialdialogs.MaterialDialog;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class BlacklistFolderChooserDialog extends DialogFragment
implements MaterialDialog.ListCallback {
String initialPath = Environment.getExternalStorageDirectory().getAbsolutePath();
private File parentFolder;
private File[] parentContents;
private boolean canGoUp = false;
private FolderCallback callback;
public static BlacklistFolderChooserDialog create() {
return new BlacklistFolderChooserDialog();
}
private String[] getContentsArray() {
if (parentContents == null) {
if (canGoUp) {
return new String[] {".."};
}
return new String[] {};
}
String[] results = new String[parentContents.length + (canGoUp ? 1 : 0)];
if (canGoUp) {
results[0] = "..";
}
for (int i = 0; i < parentContents.length; i++) {
results[canGoUp ? i + 1 : i] = parentContents[i].getName();
}
return results;
}
private File[] listFiles() {
File[] contents = parentFolder.listFiles();
List<File> results = new ArrayList<>();
if (contents != null) {
for (File fi : contents) {
if (fi.isDirectory()) {
results.add(fi);
}
}
Collections.sort(results, new FolderSorter());
return results.toArray(new File[results.size()]);
}
return null;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& ActivityCompat.checkSelfPermission(
requireActivity(), Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
return new MaterialDialog.Builder(requireActivity())
.title(R.string.md_error_label)
.content(R.string.md_storage_perm_error)
.positiveText(android.R.string.ok)
.build();
}
if (savedInstanceState == null) {
savedInstanceState = new Bundle();
}
if (!savedInstanceState.containsKey("current_path")) {
savedInstanceState.putString("current_path", initialPath);
}
parentFolder = new File(savedInstanceState.getString("current_path", File.pathSeparator));
checkIfCanGoUp();
parentContents = listFiles();
MaterialDialog.Builder builder =
new MaterialDialog.Builder(requireContext())
.title(parentFolder.getAbsolutePath())
.items((CharSequence[]) getContentsArray())
.itemsCallback(this)
.autoDismiss(false)
.onPositive(
(dialog, which) -> {
callback.onFolderSelection(BlacklistFolderChooserDialog.this, parentFolder);
dismiss();
})
.onNegative((materialDialog, dialogAction) -> dismiss())
.positiveText(R.string.add_action)
.negativeText(android.R.string.cancel);
return builder.build();
}
@Override
public void onSelection(MaterialDialog materialDialog, View view, int i, CharSequence s) {
if (canGoUp && i == 0) {
parentFolder = parentFolder.getParentFile();
if (parentFolder.getAbsolutePath().equals("/storage/emulated")) {
parentFolder = parentFolder.getParentFile();
}
checkIfCanGoUp();
} else {
parentFolder = parentContents[canGoUp ? i - 1 : i];
canGoUp = true;
if (parentFolder.getAbsolutePath().equals("/storage/emulated")) {
parentFolder = Environment.getExternalStorageDirectory();
}
}
reload();
}
private void checkIfCanGoUp() {
canGoUp = parentFolder.getParent() != null;
}
private void reload() {
parentContents = listFiles();
MaterialDialog dialog = (MaterialDialog) getDialog();
dialog.setTitle(parentFolder.getAbsolutePath());
dialog.setItems((CharSequence[]) getContentsArray());
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("current_path", parentFolder.getAbsolutePath());
}
public void setCallback(FolderCallback callback) {
this.callback = callback;
}
public interface FolderCallback {
void onFolderSelection(@NonNull BlacklistFolderChooserDialog dialog, @NonNull File folder);
}
private static class FolderSorter implements Comparator<File> {
@Override
public int compare(File lhs, File rhs) {
return lhs.getName().compareTo(rhs.getName());
}
}
}

View File

@ -0,0 +1,152 @@
package code.name.monkey.retromusic.dialogs
import android.Manifest
import android.app.Dialog
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.os.Environment
import androidx.core.app.ActivityCompat
import androidx.fragment.app.DialogFragment
import code.name.monkey.retromusic.R
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.list.listItems
import com.afollestad.materialdialogs.list.updateListItems
import java.io.File
import java.util.*
class BlacklistFolderChooserDialog : DialogFragment() {
private var initialPath: String = Environment.getExternalStorageDirectory().absolutePath
private var parentFolder: File? = null
private var parentContents: Array<File>? = null
private var canGoUp = false
private var callback: FolderCallback? = null
private val contentsArray: Array<String?>
get() {
if (parentContents == null) {
return if (canGoUp) {
arrayOf("..")
} else arrayOf()
}
val results = arrayOfNulls<String>(parentContents!!.size + if (canGoUp) 1 else 0)
if (canGoUp) {
results[0] = ".."
}
for (i in parentContents!!.indices) {
results[if (canGoUp) i + 1 else i] = parentContents!![i].name
}
return results
}
private fun listFiles(): Array<File>? {
val contents = parentFolder!!.listFiles()
val results: MutableList<File> = ArrayList()
if (contents != null) {
for (fi in contents) {
if (fi.isDirectory) {
results.add(fi)
}
}
Collections.sort(results, FolderSorter())
return results.toTypedArray()
}
return null
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
var mSavedInstanceState = savedInstanceState
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& ActivityCompat.checkSelfPermission(
requireActivity(), Manifest.permission.READ_EXTERNAL_STORAGE
)
!= PackageManager.PERMISSION_GRANTED
) {
return MaterialDialog(requireActivity()).show {
title(res = R.string.md_error_label)
message(res = R.string.md_storage_perm_error)
positiveButton(res = android.R.string.ok)
}
}
if (mSavedInstanceState == null) {
mSavedInstanceState = Bundle()
}
if (!mSavedInstanceState.containsKey("current_path")) {
mSavedInstanceState.putString("current_path", initialPath)
}
parentFolder = File(mSavedInstanceState.getString("current_path", File.pathSeparator))
checkIfCanGoUp()
parentContents = listFiles()
return MaterialDialog(requireContext())
.title(text = parentFolder!!.absolutePath)
.listItems(
items = contentsArray.toCharSequence(),
waitForPositiveButton = false
) { _: MaterialDialog, i: Int, _: CharSequence ->
onSelection(i)
}
.noAutoDismiss()
.cornerRadius(literalDp = 20F)
.positiveButton(res = R.string.add_action) {
callback!!.onFolderSelection(this@BlacklistFolderChooserDialog, parentFolder!!)
dismiss()
}
.negativeButton(res = android.R.string.cancel) { dismiss() }
}
private fun onSelection(i: Int) {
if (canGoUp && i == 0) {
parentFolder = parentFolder!!.parentFile
if (parentFolder!!.absolutePath == "/storage/emulated") {
parentFolder = parentFolder!!.parentFile
}
checkIfCanGoUp()
} else {
parentFolder = parentContents!![if (canGoUp) i - 1 else i]
canGoUp = true
if (parentFolder!!.absolutePath == "/storage/emulated") {
parentFolder = Environment.getExternalStorageDirectory()
}
}
reload()
}
private fun checkIfCanGoUp() {
canGoUp = parentFolder!!.parent != null
}
private fun reload() {
parentContents = listFiles()
val dialog = dialog as MaterialDialog?
dialog!!.setTitle(parentFolder!!.absolutePath)
dialog.updateListItems(items = contentsArray.toCharSequence())
}
private fun Array<String?>.toCharSequence(): List<CharSequence> {
return map { it as CharSequence }
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString("current_path", parentFolder!!.absolutePath)
}
fun setCallback(callback: FolderCallback?) {
this.callback = callback
}
interface FolderCallback {
fun onFolderSelection(dialog: BlacklistFolderChooserDialog, folder: File)
}
private class FolderSorter : Comparator<File> {
override fun compare(lhs: File, rhs: File): Int {
return lhs.name.compareTo(rhs.name)
}
}
companion object {
fun create(): BlacklistFolderChooserDialog {
return BlacklistFolderChooserDialog()
}
}
}

View File

@ -17,29 +17,23 @@ package code.name.monkey.retromusic.dialogs
import android.app.Dialog
import android.os.Bundle
import android.text.TextUtils
import android.view.LayoutInflater
import android.widget.Toast
import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import code.name.monkey.retromusic.EXTRA_SONG
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.db.PlaylistEntity
import code.name.monkey.retromusic.db.toSongEntity
import code.name.monkey.retromusic.databinding.DialogPlaylistBinding
import code.name.monkey.retromusic.extensions.colorButtons
import code.name.monkey.retromusic.extensions.extra
import code.name.monkey.retromusic.extensions.materialDialog
import code.name.monkey.retromusic.fragments.LibraryViewModel
import code.name.monkey.retromusic.fragments.ReloadType.Playlists
import code.name.monkey.retromusic.model.Song
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import kotlinx.android.synthetic.main.dialog_playlist.view.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
class CreatePlaylistDialog : DialogFragment() {
private var _binding: DialogPlaylistBinding? = null
private val binding get() = _binding!!
private val libraryViewModel by sharedViewModel<LibraryViewModel>()
companion object {
@ -56,13 +50,15 @@ class CreatePlaylistDialog : DialogFragment() {
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val view = LayoutInflater.from(requireActivity()).inflate(R.layout.dialog_playlist, null)
_binding = DialogPlaylistBinding.inflate(layoutInflater)
val songs: List<Song> = extra<List<Song>>(EXTRA_SONG).value ?: emptyList()
val playlistView: TextInputEditText = view.actionNewPlaylist
val playlistContainer: TextInputLayout = view.actionNewPlaylistContainer
val playlistView: TextInputEditText = binding.actionNewPlaylist
val playlistContainer: TextInputLayout = binding.actionNewPlaylistContainer
return materialDialog(R.string.new_playlist_title)
.setView(view)
.setView(binding.root)
.setPositiveButton(
R.string.create_action
) { _, _ ->
@ -77,4 +73,9 @@ class CreatePlaylistDialog : DialogFragment() {
.create()
.colorButtons()
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

View File

@ -14,24 +14,31 @@
*/
package code.name.monkey.retromusic.dialogs
import android.app.Activity
import android.app.Dialog
import android.content.Intent
import android.os.Bundle
import androidx.core.os.bundleOf
import androidx.core.text.HtmlCompat
import androidx.fragment.app.DialogFragment
import code.name.monkey.retromusic.EXTRA_SONG
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.extensions.colorButtons
import code.name.monkey.retromusic.activities.saf.SAFGuideActivity
import code.name.monkey.retromusic.extensions.extraNotNull
import code.name.monkey.retromusic.extensions.materialDialog
import code.name.monkey.retromusic.fragments.LibraryViewModel
import code.name.monkey.retromusic.fragments.ReloadType
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.MusicUtil
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
import code.name.monkey.retromusic.util.SAFUtil
import com.afollestad.materialdialogs.MaterialDialog
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.koin.androidx.viewmodel.ext.android.getViewModel
class DeleteSongsDialog : DialogFragment() {
private val libraryViewModel by sharedViewModel<LibraryViewModel>()
lateinit var libraryViewModel: LibraryViewModel
companion object {
fun create(song: Song): DeleteSongsDialog {
@ -50,6 +57,7 @@ class DeleteSongsDialog : DialogFragment() {
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
libraryViewModel = activity?.getViewModel() as LibraryViewModel
val songs = extraNotNull<List<Song>>(EXTRA_SONG).value
val pair = if (songs.size > 1) {
Pair(
@ -69,17 +77,66 @@ class DeleteSongsDialog : DialogFragment() {
)
}
return materialDialog(pair.first)
.setMessage(pair.second)
.setCancelable(false)
.setPositiveButton(R.string.action_delete) { _, _ ->
if (songs.isNotEmpty() and (songs.size == 1) and MusicPlayerRemote.isPlaying(songs.first())) {
return MaterialDialog(requireContext())
.title(pair.first)
.message(text = pair.second)
.noAutoDismiss()
.cornerRadius(16F)
.negativeButton(android.R.string.cancel) {
dismiss()
}
.positiveButton(R.string.action_delete) {
if ((songs.size == 1) && MusicPlayerRemote.isPlaying(songs[0])) {
MusicPlayerRemote.playNextSong()
}
MusicUtil.deleteTracks(requireActivity(), songs)
libraryViewModel.deleteTracks(songs)
if (!SAFUtil.isSAFRequiredForSongs(songs)) {
CoroutineScope(Dispatchers.IO).launch {
dismiss()
MusicUtil.deleteTracks(requireContext(), songs)
reloadTabs()
}
} else {
if (SAFUtil.isSDCardAccessGranted(requireActivity())) {
deleteSongs(songs)
} else {
startActivityForResult(
Intent(requireActivity(), SAFGuideActivity::class.java),
SAFGuideActivity.REQUEST_CODE_SAF_GUIDE
)
}
}
}
.create()
.colorButtons()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
SAFGuideActivity.REQUEST_CODE_SAF_GUIDE -> {
SAFUtil.openTreePicker(this)
}
SAFUtil.REQUEST_SAF_PICK_TREE,
SAFUtil.REQUEST_SAF_PICK_FILE -> {
if (resultCode == Activity.RESULT_OK) {
SAFUtil.saveTreeUri(requireActivity(), data)
val songs = extraNotNull<List<Song>>(EXTRA_SONG).value
deleteSongs(songs)
}
}
}
}
fun deleteSongs(songs: List<Song>) {
CoroutineScope(Dispatchers.IO).launch {
dismiss()
MusicUtil.deleteTracks(requireActivity(), songs, null, null)
reloadTabs()
}
}
fun reloadTabs() {
libraryViewModel.forceReload(ReloadType.Songs)
libraryViewModel.forceReload(ReloadType.HomeSections)
libraryViewModel.forceReload(ReloadType.Artists)
libraryViewModel.forceReload(ReloadType.Albums)
}
}

View File

@ -38,8 +38,9 @@ import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.service.MusicService.ACTION_PENDING_QUIT
import code.name.monkey.retromusic.service.MusicService.ACTION_QUIT
import code.name.monkey.retromusic.util.PreferenceUtil
import com.afollestad.materialdialogs.DialogAction
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.WhichButton
import com.afollestad.materialdialogs.actions.getActionButton
class SleepTimerDialog : DialogFragment() {
@ -150,14 +151,14 @@ class SleepTimerDialog : DialogFragment() {
private fun updateCancelButton() {
val musicService = MusicPlayerRemote.musicService
if (musicService != null && musicService.pendingQuit) {
dialog.getActionButton(DialogAction.NEUTRAL).text =
dialog.getActionButton(WhichButton.NEUTRAL).text =
dialog.context.getString(R.string.cancel_current_timer)
} else {
dialog.getActionButton(DialogAction.NEUTRAL).text = null
dialog.getActionButton(WhichButton.NEUTRAL).text = null
}
}
private inner class TimerUpdater() :
private inner class TimerUpdater :
CountDownTimer(
PreferenceUtil.nextSleepTimerElapsedRealTime - SystemClock.elapsedRealtime(),
1000

View File

@ -32,13 +32,13 @@ import code.name.monkey.retromusic.extensions.colorButtons
import code.name.monkey.retromusic.extensions.materialDialog
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.MusicUtil
import java.io.File
import java.io.IOException
import org.jaudiotagger.audio.AudioFileIO
import org.jaudiotagger.audio.exceptions.CannotReadException
import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException
import org.jaudiotagger.tag.TagException
import java.io.File
import java.io.IOException
class SongDetailDialog : DialogFragment() {

View File

@ -41,7 +41,6 @@ import com.google.android.material.button.MaterialButton
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.progressindicator.CircularProgressIndicator
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
fun Int.ripAlpha(): Int {

View File

@ -18,14 +18,10 @@ import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.drawable.AdaptiveIconDrawable
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.os.Build
import androidx.annotation.DimenRes
import androidx.annotation.DrawableRes
import androidx.core.content.ContextCompat
import code.name.monkey.retromusic.R
fun Context.scaledDrawableResources(
@DrawableRes id: Int,

View File

@ -17,6 +17,7 @@ package code.name.monkey.retromusic.fragments
import android.os.Bundle
import android.view.View
import androidx.core.os.bundleOf
import androidx.core.view.doOnPreDraw
import androidx.navigation.fragment.FragmentNavigatorExtras
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
@ -28,26 +29,48 @@ import code.name.monkey.retromusic.adapter.album.AlbumAdapter
import code.name.monkey.retromusic.adapter.artist.ArtistAdapter
import code.name.monkey.retromusic.adapter.song.ShuffleButtonSongAdapter
import code.name.monkey.retromusic.adapter.song.SongAdapter
import code.name.monkey.retromusic.databinding.FragmentPlaylistDetailBinding
import code.name.monkey.retromusic.db.toSong
import code.name.monkey.retromusic.extensions.dipToPix
import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment
import code.name.monkey.retromusic.interfaces.IAlbumClickListener
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 kotlinx.android.synthetic.main.fragment_playlist_detail.*
import com.google.android.material.transition.MaterialSharedAxis
class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_detail),
IArtistClickListener, IAlbumClickListener {
private val args by navArgs<DetailListFragmentArgs>()
private var _binding: FragmentPlaylistDetailBinding? = null
private val binding get() = _binding!!
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentPlaylistDetailBinding.bind(view)
when (args.type) {
TOP_ARTISTS,
RECENT_ARTISTS,
TOP_ALBUMS,
RECENT_ALBUMS,
FAVOURITES -> {
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
}
else -> {
enterTransition = MaterialSharedAxis(MaterialSharedAxis.Y, true)
returnTransition = MaterialSharedAxis(MaterialSharedAxis.Y, false)
}
}
postponeEnterTransition()
view.doOnPreDraw { startPostponedEnterTransition() }
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
mainActivity.setBottomBarVisibility(false)
mainActivity.setSupportActionBar(toolbar)
progressIndicator.hide()
mainActivity.setSupportActionBar(binding.toolbar)
binding.progressIndicator.hide()
when (args.type) {
TOP_ARTISTS -> loadArtists(R.string.top_artists, TOP_ARTISTS)
RECENT_ARTISTS -> loadArtists(R.string.recent_artists, RECENT_ARTISTS)
@ -59,23 +82,23 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de
TOP_PLAYED_PLAYLIST -> topPlayed()
}
recyclerView.adapter?.registerAdapterDataObserver(object : AdapterDataObserver() {
binding.recyclerView.adapter?.registerAdapterDataObserver(object : AdapterDataObserver() {
override fun onChanged() {
super.onChanged()
val height = dipToPix(52f)
recyclerView.setPadding(0, 0, 0, height.toInt())
binding.recyclerView.setPadding(0, 0, 0, height.toInt())
}
})
}
private fun lastAddedSongs() {
toolbar.setTitle(R.string.last_added)
binding.toolbar.setTitle(R.string.last_added)
val songAdapter = ShuffleButtonSongAdapter(
requireActivity(),
mutableListOf(),
R.layout.item_list, null
)
recyclerView.apply {
binding.recyclerView.apply {
adapter = songAdapter
layoutManager = linearLayoutManager()
}
@ -85,13 +108,13 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de
}
private fun topPlayed() {
toolbar.setTitle(R.string.my_top_tracks)
binding.toolbar.setTitle(R.string.my_top_tracks)
val songAdapter = ShuffleButtonSongAdapter(
requireActivity(),
mutableListOf(),
R.layout.item_list, null
)
recyclerView.apply {
binding.recyclerView.apply {
adapter = songAdapter
layoutManager = linearLayoutManager()
}
@ -101,14 +124,14 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de
}
private fun loadHistory() {
toolbar.setTitle(R.string.history)
binding.toolbar.setTitle(R.string.history)
val songAdapter = ShuffleButtonSongAdapter(
requireActivity(),
mutableListOf(),
R.layout.item_list, null
)
recyclerView.apply {
binding.recyclerView.apply {
adapter = songAdapter
layoutManager = linearLayoutManager()
}
@ -118,13 +141,13 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de
}
private fun loadFavorite() {
toolbar.setTitle(R.string.favorites)
binding.toolbar.setTitle(R.string.favorites)
val songAdapter = SongAdapter(
requireActivity(),
mutableListOf(),
R.layout.item_list, null
)
recyclerView.apply {
binding.recyclerView.apply {
adapter = songAdapter
layoutManager = linearLayoutManager()
}
@ -135,9 +158,9 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de
}
private fun loadArtists(title: Int, type: Int) {
toolbar.setTitle(title)
binding.toolbar.setTitle(title)
libraryViewModel.artists(type).observe(viewLifecycleOwner, { artists ->
recyclerView.apply {
binding.recyclerView.apply {
adapter = artistAdapter(artists)
layoutManager = gridLayoutManager()
}
@ -145,9 +168,9 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de
}
private fun loadAlbums(title: Int, type: Int) {
toolbar.setTitle(title)
binding.toolbar.setTitle(title)
libraryViewModel.albums(type).observe(viewLifecycleOwner, { albums ->
recyclerView.apply {
binding.recyclerView.apply {
adapter = albumAdapter(albums)
layoutManager = gridLayoutManager()
}
@ -186,7 +209,7 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de
R.id.artistDetailsFragment,
bundleOf(EXTRA_ARTIST_ID to artistId),
null,
FragmentNavigatorExtras(view to "artist")
FragmentNavigatorExtras(view to artistId.toString())
)
}
@ -196,8 +219,13 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de
bundleOf(EXTRA_ALBUM_ID to albumId),
null,
FragmentNavigatorExtras(
view to "album"
view to albumId.toString()
)
)
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

View File

@ -25,9 +25,7 @@ import code.name.monkey.retromusic.model.*
import code.name.monkey.retromusic.repository.RealRepository
import code.name.monkey.retromusic.util.PreferenceUtil
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class LibraryViewModel(
private val repository: RealRepository
@ -129,16 +127,16 @@ class LibraryViewModel(
}
}
private fun fetchHomeSections() {
fun fetchHomeSections() {
viewModelScope.launch(IO) {
home.postValue(repository.homeSections())
}
}
fun search(query: String?) {
fun search(query: String?, filters: List<Boolean>) {
viewModelScope.launch(IO) {
val result = repository.search(query)
withContext(Main) { searchResults.postValue(result) }
val result = repository.search(query, filters)
searchResults.postValue(result)
}
}
@ -190,6 +188,10 @@ class LibraryViewModel(
println("onShuffleModeChanged")
}
override fun onFavoriteStateChanged() {
println("onFavoriteStateChanged")
}
fun shuffleSongs() = viewModelScope.launch(IO) {
val songs = repository.allSongs()
MusicPlayerRemote.openAndShuffleQueue(

View File

@ -26,23 +26,26 @@ import android.view.MotionEvent
import android.view.View
import android.view.animation.DecelerateInterpolator
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentMiniPlayerBinding
import code.name.monkey.retromusic.extensions.accentColor
import code.name.monkey.retromusic.extensions.applyColor
import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.extensions.textColorPrimary
import code.name.monkey.retromusic.extensions.textColorSecondary
import code.name.monkey.retromusic.fragments.base.AbsMusicServiceFragment
import code.name.monkey.retromusic.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
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.RetroUtil
import kotlin.math.abs
import kotlinx.android.synthetic.main.fragment_mini_player.*
open class MiniPlayerFragment : AbsMusicServiceFragment(R.layout.fragment_mini_player),
MusicProgressViewUpdateHelper.Callback, View.OnClickListener {
private var _binding: FragmentMiniPlayerBinding? = null
private val binding get() = _binding!!
private lateinit var progressViewUpdateHelper: MusicProgressViewUpdateHelper
override fun onCreate(savedInstanceState: Bundle?) {
@ -59,38 +62,38 @@ open class MiniPlayerFragment : AbsMusicServiceFragment(R.layout.fragment_mini_p
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentMiniPlayerBinding.bind(view)
view.setOnTouchListener(FlingPlayBackController(requireContext()))
setUpMiniPlayer()
if (RetroUtil.isTablet()) {
actionNext.show()
actionPrevious.show()
actionNext?.show()
actionPrevious?.show()
binding.actionNext.show()
binding.actionPrevious.show()
} else {
actionNext.visibility = if (PreferenceUtil.isExtraControls) View.VISIBLE else View.GONE
actionPrevious.visibility =
binding.actionNext.visibility =
if (PreferenceUtil.isExtraControls) View.VISIBLE else View.GONE
binding.actionPrevious.visibility =
if (PreferenceUtil.isExtraControls) View.VISIBLE else View.GONE
}
actionNext.setOnClickListener(this)
actionPrevious.setOnClickListener(this)
actionNext?.setOnClickListener(this)
actionPrevious?.setOnClickListener(this)
binding.actionNext.setOnClickListener(this)
binding.actionPrevious.setOnClickListener(this)
}
private fun setUpMiniPlayer() {
setUpPlayPauseButton()
progressBar.accentColor()
binding.progressBar.accentColor()
}
private fun setUpPlayPauseButton() {
miniPlayerPlayPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler())
binding.miniPlayerPlayPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler())
}
private fun updateSongTitle() {
val builder = SpannableStringBuilder()
val song = MusicPlayerRemote.currentSong
val builder = SpannableStringBuilder()
val title = SpannableString(song.title)
title.setSpan(ForegroundColorSpan(textColorPrimary()), 0, title.length, 0)
@ -99,17 +102,34 @@ open class MiniPlayerFragment : AbsMusicServiceFragment(R.layout.fragment_mini_p
builder.append(title).append("").append(text)
miniPlayerTitle.isSelected = true
miniPlayerTitle.text = builder
binding.miniPlayerTitle.isSelected = true
binding.miniPlayerTitle.text = builder
// binding.title.isSelected = true
// binding.title.text = song.title
// binding.text.isSelected = true
// binding.text.text = song.artistName
}
private fun updateSongCover() {
// val song = MusicPlayerRemote.currentSong
// GlideApp.with(requireContext())
// .asBitmap()
// .songCoverOptions(song)
// .transition(RetroGlideExtension.getDefaultTransition())
// .load(RetroGlideExtension.getSongModel(song))
// .into(binding.image)
}
override fun onServiceConnected() {
updateSongTitle()
updateSongCover()
updatePlayPauseDrawableState()
}
override fun onPlayingMetaChanged() {
updateSongTitle()
updateSongCover()
}
override fun onPlayStateChanged() {
@ -117,8 +137,8 @@ open class MiniPlayerFragment : AbsMusicServiceFragment(R.layout.fragment_mini_p
}
override fun onUpdateProgressViews(progress: Int, total: Int) {
progressBar.max = total
val animator = ObjectAnimator.ofInt(progressBar, "progress", progress)
binding.progressBar.max = total
val animator = ObjectAnimator.ofInt(binding.progressBar, "progress", progress)
animator.duration = 1000
animator.interpolator = DecelerateInterpolator()
animator.start()
@ -136,16 +156,12 @@ open class MiniPlayerFragment : AbsMusicServiceFragment(R.layout.fragment_mini_p
protected fun updatePlayPauseDrawableState() {
if (MusicPlayerRemote.isPlaying) {
miniPlayerPlayPauseButton.setImageResource(R.drawable.ic_pause)
binding.miniPlayerPlayPauseButton.setImageResource(R.drawable.ic_pause)
} else {
miniPlayerPlayPauseButton.setImageResource(R.drawable.ic_play_arrow)
binding.miniPlayerPlayPauseButton.setImageResource(R.drawable.ic_play_arrow)
}
}
fun updateProgressBar(paletteColor: Int) {
progressBar.applyColor(paletteColor)
}
class FlingPlayBackController(context: Context) : View.OnTouchListener {
private var flingPlayBackController: GestureDetector
@ -178,4 +194,9 @@ open class MiniPlayerFragment : AbsMusicServiceFragment(R.layout.fragment_mini_p
return flingPlayBackController.onTouchEvent(event)
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

View File

@ -26,34 +26,38 @@ import android.widget.SeekBar
import androidx.fragment.app.Fragment
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentVolumeBinding
import code.name.monkey.retromusic.extensions.applyColor
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.volume.AudioVolumeObserver
import code.name.monkey.retromusic.volume.OnAudioVolumeChangedListener
import kotlinx.android.synthetic.main.fragment_volume.*
class VolumeFragment : Fragment(), SeekBar.OnSeekBarChangeListener, OnAudioVolumeChangedListener,
View.OnClickListener {
private var _binding: FragmentVolumeBinding? = null
private val binding get() = _binding!!
private var audioVolumeObserver: AudioVolumeObserver? = null
private val audioManager: AudioManager?
private val audioManager: AudioManager
get() = requireContext().getSystemService(Context.AUDIO_SERVICE) as AudioManager
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_volume, container, false)
): View {
_binding = FragmentVolumeBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setTintable(ThemeStore.accentColor(requireContext()))
volumeDown.setOnClickListener(this)
volumeUp.setOnClickListener(this)
binding.volumeDown.setOnClickListener(this)
binding.volumeUp.setOnClickListener(this)
}
override fun onResume() {
@ -64,33 +68,30 @@ class VolumeFragment : Fragment(), SeekBar.OnSeekBarChangeListener, OnAudioVolum
audioVolumeObserver?.register(AudioManager.STREAM_MUSIC, this)
val audioManager = audioManager
if (audioManager != null) {
volumeSeekBar.max = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
volumeSeekBar.progress = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
}
volumeSeekBar.setOnSeekBarChangeListener(this)
binding.volumeSeekBar.max = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
binding.volumeSeekBar.progress = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
binding.volumeSeekBar.setOnSeekBarChangeListener(this)
}
override fun onAudioVolumeChanged(currentVolume: Int, maxVolume: Int) {
if (volumeSeekBar == null) {
return
if (_binding != null) {
binding.volumeSeekBar.max = maxVolume
binding.volumeSeekBar.progress = currentVolume
binding.volumeDown.setImageResource(if (currentVolume == 0) R.drawable.ic_volume_off else R.drawable.ic_volume_down)
}
volumeSeekBar.max = maxVolume
volumeSeekBar.progress = currentVolume
volumeDown.setImageResource(if (currentVolume == 0) R.drawable.ic_volume_off else R.drawable.ic_volume_down)
}
override fun onDestroyView() {
super.onDestroyView()
audioVolumeObserver?.unregister()
_binding = null
}
override fun onProgressChanged(seekBar: SeekBar, i: Int, b: Boolean) {
val audioManager = audioManager
audioManager?.setStreamVolume(AudioManager.STREAM_MUSIC, i, 0)
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, i, 0)
setPauseWhenZeroVolume(i < 1)
volumeDown?.setImageResource(if (i == 0) R.drawable.ic_volume_off else R.drawable.ic_volume_down)
binding.volumeDown.setImageResource(if (i == 0) R.drawable.ic_volume_off else R.drawable.ic_volume_down)
}
override fun onStartTrackingTouch(seekBar: SeekBar) {
@ -102,10 +103,10 @@ class VolumeFragment : Fragment(), SeekBar.OnSeekBarChangeListener, OnAudioVolum
override fun onClick(view: View) {
val audioManager = audioManager
when (view.id) {
R.id.volumeDown -> audioManager?.adjustStreamVolume(
R.id.volumeDown -> audioManager.adjustStreamVolume(
AudioManager.STREAM_MUSIC, AudioManager.ADJUST_LOWER, 0
)
R.id.volumeUp -> audioManager?.adjustStreamVolume(
R.id.volumeUp -> audioManager.adjustStreamVolume(
AudioManager.STREAM_MUSIC, AudioManager.ADJUST_RAISE, 0
)
}
@ -113,13 +114,13 @@ class VolumeFragment : Fragment(), SeekBar.OnSeekBarChangeListener, OnAudioVolum
fun tintWhiteColor() {
val color = Color.WHITE
volumeDown.setColorFilter(color, PorterDuff.Mode.SRC_IN)
volumeUp.setColorFilter(color, PorterDuff.Mode.SRC_IN)
volumeSeekBar.applyColor(color)
binding.volumeDown.setColorFilter(color, PorterDuff.Mode.SRC_IN)
binding.volumeUp.setColorFilter(color, PorterDuff.Mode.SRC_IN)
binding.volumeSeekBar.applyColor(color)
}
fun setTintable(color: Int) {
volumeSeekBar.applyColor(color)
binding.volumeSeekBar.applyColor(color)
}
private fun setPauseWhenZeroVolume(pauseWhenZeroVolume: Boolean) {
@ -129,10 +130,10 @@ class VolumeFragment : Fragment(), SeekBar.OnSeekBarChangeListener, OnAudioVolum
}
fun setTintableColor(color: Int) {
volumeDown.setColorFilter(color, PorterDuff.Mode.SRC_IN)
volumeUp.setColorFilter(color, PorterDuff.Mode.SRC_IN)
binding.volumeDown.setColorFilter(color, PorterDuff.Mode.SRC_IN)
binding.volumeUp.setColorFilter(color, PorterDuff.Mode.SRC_IN)
// TintHelper.setTint(volumeSeekBar, color, false)
volumeSeekBar.applyColor(color)
binding.volumeSeekBar.applyColor(color)
}
companion object {

View File

@ -27,20 +27,20 @@ import code.name.monkey.retromusic.App
import code.name.monkey.retromusic.Constants
import code.name.monkey.retromusic.R
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 kotlinx.android.synthetic.main.card_credit.*
import kotlinx.android.synthetic.main.card_other.*
import kotlinx.android.synthetic.main.card_retro_info.*
import kotlinx.android.synthetic.main.card_social.*
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
class AboutFragment : Fragment(R.layout.fragment_about), View.OnClickListener {
private var _binding: FragmentAboutBinding? = null
private val binding get() = _binding!!
private val libraryViewModel by sharedViewModel<LibraryViewModel>()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
version.setSummary(getAppVersion())
_binding = FragmentAboutBinding.bind(view)
binding.aboutContent.cardOther.version.setSummary(getAppVersion())
setUpView()
loadContributors()
}
@ -53,19 +53,20 @@ class AboutFragment : Fragment(R.layout.fragment_about), View.OnClickListener {
}
private fun setUpView() {
appGithub.setOnClickListener(this)
faqLink.setOnClickListener(this)
telegramLink.setOnClickListener(this)
appRate.setOnClickListener(this)
appTranslation.setOnClickListener(this)
appShare.setOnClickListener(this)
donateLink.setOnClickListener(this)
instagramLink.setOnClickListener(this)
twitterLink.setOnClickListener(this)
changelog.setOnClickListener(this)
openSource.setOnClickListener(this)
pinterestLink.setOnClickListener(this)
bugReportLink.setOnClickListener(this)
binding.aboutContent.cardRetroInfo.appGithub.setOnClickListener(this)
binding.aboutContent.cardRetroInfo.faqLink.setOnClickListener(this)
binding.aboutContent.cardSocial.telegramLink.setOnClickListener(this)
binding.aboutContent.cardRetroInfo.appRate.setOnClickListener(this)
binding.aboutContent.cardRetroInfo.appTranslation.setOnClickListener(this)
binding.aboutContent.cardRetroInfo.appShare.setOnClickListener(this)
binding.aboutContent.cardRetroInfo.donateLink.setOnClickListener(this)
binding.aboutContent.cardSocial.instagramLink.setOnClickListener(this)
binding.aboutContent.cardSocial.twitterLink.setOnClickListener(this)
binding.aboutContent.cardOther.changelog.setOnClickListener(this)
binding.aboutContent.cardOther.openSource.setOnClickListener(this)
binding.aboutContent.cardSocial.pinterestLink.setOnClickListener(this)
binding.aboutContent.cardRetroInfo.bugReportLink.setOnClickListener(this)
binding.aboutContent.cardSocial.websiteLink.setOnClickListener(this)
}
override fun onClick(view: View) {
@ -80,9 +81,10 @@ class AboutFragment : Fragment(R.layout.fragment_about), View.OnClickListener {
R.id.donateLink -> NavigationUtil.goToSupportDevelopment(requireActivity())
R.id.instagramLink -> openUrl(Constants.APP_INSTAGRAM_LINK)
R.id.twitterLink -> openUrl(Constants.APP_TWITTER_LINK)
R.id.changelog -> openUrl(Constants.TELEGRAM_CHANGE_LOG)
R.id.changelog -> NavigationUtil.gotoWhatNews(requireActivity())
R.id.openSource -> NavigationUtil.goToOpenSource(requireActivity())
R.id.bugReportLink -> NavigationUtil.bugReport(requireActivity())
R.id.websiteLink -> openUrl(Constants.WEBSITE)
}
}
@ -99,7 +101,7 @@ class AboutFragment : Fragment(R.layout.fragment_about), View.OnClickListener {
}
private fun shareApp() {
ShareCompat.IntentBuilder.from(requireActivity()).setType("text/plain")
ShareCompat.IntentBuilder(requireActivity()).setType("text/plain")
.setChooserTitle(R.string.share_app)
.setText(String.format(getString(R.string.app_share), requireActivity().packageName))
.startChooser()
@ -107,7 +109,7 @@ class AboutFragment : Fragment(R.layout.fragment_about), View.OnClickListener {
private fun loadContributors() {
val contributorAdapter = ContributorAdapter(emptyList())
recyclerView.apply {
binding.aboutContent.cardCredit.recyclerView.apply {
layoutManager = LinearLayoutManager(requireContext())
itemAnimator = DefaultItemAnimator()
adapter = contributorAdapter
@ -116,4 +118,9 @@ class AboutFragment : Fragment(R.layout.fragment_about), View.OnClickListener {
contributorAdapter.swapData(contributors)
})
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

View File

@ -19,11 +19,12 @@ import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.view.*
import androidx.activity.addCallback
import androidx.appcompat.app.AppCompatActivity
import androidx.core.os.bundleOf
import androidx.core.text.HtmlCompat
import androidx.core.view.ViewCompat
import androidx.lifecycle.Observer
import androidx.core.view.doOnPreDraw
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.FragmentNavigatorExtras
import androidx.navigation.fragment.findNavController
@ -35,17 +36,19 @@ import code.name.monkey.appthemehelper.common.ATHToolbarActivity.getToolbarBackg
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.EXTRA_ALBUM_ID
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.activities.tageditor.AbsTagEditorActivity
import code.name.monkey.retromusic.activities.tageditor.AlbumTagEditorActivity
import code.name.monkey.retromusic.adapter.album.HorizontalAlbumAdapter
import code.name.monkey.retromusic.adapter.song.SimpleSongAdapter
import code.name.monkey.retromusic.databinding.FragmentAlbumDetailsBinding
import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog
import code.name.monkey.retromusic.dialogs.DeleteSongsDialog
import code.name.monkey.retromusic.extensions.*
import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment
import code.name.monkey.retromusic.glide.AlbumGlideRequest
import code.name.monkey.retromusic.glide.ArtistGlideRequest
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.glide.SingleColorTarget
import code.name.monkey.retromusic.helper.MusicPlayerRemote
@ -54,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.ICabHolder
import code.name.monkey.retromusic.model.Album
import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.network.Result
@ -61,14 +65,12 @@ import code.name.monkey.retromusic.network.model.LastFmAlbum
import code.name.monkey.retromusic.repository.RealRepository
import code.name.monkey.retromusic.util.MusicUtil
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.bumptech.glide.Glide
import com.afollestad.materialcab.MaterialCab
import com.google.android.material.transition.MaterialArcMotion
import com.google.android.material.transition.MaterialContainerTransform
import com.google.android.material.transition.MaterialElevationScale
import kotlinx.android.synthetic.main.fragment_album_content.*
import kotlinx.android.synthetic.main.fragment_album_details.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -78,7 +80,10 @@ import org.koin.core.parameter.parametersOf
import java.util.*
class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_details),
IAlbumClickListener {
IAlbumClickListener, ICabHolder {
private var _binding: FragmentAlbumDetailsBinding? = null
private val binding get() = _binding!!
private val arguments by navArgs<AlbumDetailsFragmentArgs>()
private val detailsViewModel by viewModel<AlbumDetailsViewModel> {
@ -87,6 +92,7 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
private lateinit var simpleSongAdapter: SimpleSongAdapter
private lateinit var album: Album
private var albumArtistExists = false
private val savedSortOrder: String
get() = PreferenceUtil.albumDetailSongSortOrder
@ -104,51 +110,70 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentAlbumDetailsBinding.bind(view)
setHasOptionsMenu(true)
mainActivity.setBottomBarVisibility(false)
mainActivity.addMusicServiceEventListener(detailsViewModel)
mainActivity.setSupportActionBar(toolbar)
mainActivity.setSupportActionBar(binding.toolbar)
toolbar.title = " "
ViewCompat.setTransitionName(albumCoverContainer, "album")
binding.toolbar.title = " "
ViewCompat.setTransitionName(binding.albumCoverContainer, arguments.extraAlbumId.toString())
postponeEnterTransition()
detailsViewModel.getAlbum().observe(viewLifecycleOwner, Observer {
startPostponedEnterTransition()
detailsViewModel.getAlbum().observe(viewLifecycleOwner, {
requireView().doOnPreDraw {
startPostponedEnterTransition()
}
albumArtistExists = !it.albumArtist.isNullOrEmpty()
showAlbum(it)
if (albumArtistExists) {
ViewCompat.setTransitionName(binding.artistImage, album.albumArtist)
} else {
ViewCompat.setTransitionName(binding.artistImage, album.artistId.toString())
}
})
setupRecyclerView()
artistImage.setOnClickListener { artistView ->
ViewCompat.setTransitionName(artistView, "artist")
exitTransition = MaterialElevationScale(false).apply {
duration = 300L
binding.artistImage.setOnClickListener { artistView ->
if (albumArtistExists) {
findActivityNavController(R.id.fragment_container)
.navigate(
R.id.albumArtistDetailsFragment,
bundleOf(EXTRA_ARTIST_NAME to album.albumArtist),
null,
FragmentNavigatorExtras(artistView to album.albumArtist.toString())
)
} else {
findActivityNavController(R.id.fragment_container)
.navigate(
R.id.artistDetailsFragment,
bundleOf(EXTRA_ARTIST_ID to album.artistId),
null,
FragmentNavigatorExtras(artistView to album.artistId.toString())
)
}
reenterTransition = MaterialElevationScale(true).apply {
duration = 300L
}
findActivityNavController(R.id.fragment_container)
.navigate(
R.id.artistDetailsFragment,
bundleOf(EXTRA_ARTIST_ID to album.artistId),
null,
FragmentNavigatorExtras(artistView to "artist")
)
}
playAction.setOnClickListener {
binding.fragmentAlbumContent.playAction.setOnClickListener {
MusicPlayerRemote.openQueue(album.songs, 0, true)
}
shuffleAction.setOnClickListener {
binding.fragmentAlbumContent.shuffleAction.setOnClickListener {
MusicPlayerRemote.openAndShuffleQueue(
album.songs,
true
)
}
aboutAlbumText.setOnClickListener {
if (aboutAlbumText.maxLines == 4) {
aboutAlbumText.maxLines = Integer.MAX_VALUE
binding.fragmentAlbumContent.aboutAlbumText.setOnClickListener {
if (binding.fragmentAlbumContent.aboutAlbumText.maxLines == 4) {
binding.fragmentAlbumContent.aboutAlbumText.maxLines = Integer.MAX_VALUE
} else {
aboutAlbumText.maxLines = 4
binding.fragmentAlbumContent.aboutAlbumText.maxLines = 4
}
}
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
if (!handleBackPress()) {
remove()
requireActivity().onBackPressed()
}
}
}
@ -163,9 +188,9 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
requireActivity() as AppCompatActivity,
ArrayList(),
R.layout.item_song,
null
this
)
recyclerView.apply {
binding.fragmentAlbumContent.recyclerView.apply {
layoutManager = LinearLayoutManager(requireContext())
itemAnimator = DefaultItemAnimator()
isNestedScrollingEnabled = false
@ -179,21 +204,21 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
}
this.album = album
albumTitle.text = album.title
binding.albumTitle.text = album.title
val songText = resources.getQuantityString(
R.plurals.albumSongs,
album.songCount,
album.songCount
)
songTitle.text = songText
binding.fragmentAlbumContent.songTitle.text = songText
if (MusicUtil.getYearString(album.year) == "-") {
albumText.text = String.format(
binding.albumText.text = String.format(
"%s • %s",
album.artistName,
if (albumArtistExists) album.albumArtist else album.artistName,
MusicUtil.getReadableDurationString(MusicUtil.getTotalDuration(album.songs))
)
} else {
albumText.text = String.format(
binding.albumText.text = String.format(
"%s • %s • %s",
album.artistName,
MusicUtil.getYearString(album.year),
@ -202,11 +227,18 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
}
loadAlbumCover(album)
simpleSongAdapter.swapDataSet(album.songs)
detailsViewModel.getArtist(album.artistId).observe(viewLifecycleOwner, Observer {
loadArtistImage(it)
})
if (albumArtistExists) {
detailsViewModel.getAlbumArtist(album.albumArtist.toString()).observe(viewLifecycleOwner, {
loadArtistImage(it)
})
} else {
detailsViewModel.getArtist(album.artistId).observe(viewLifecycleOwner, {
loadArtistImage(it)
})
}
detailsViewModel.getAlbumInfo(album).observe(viewLifecycleOwner, Observer { result ->
detailsViewModel.getAlbumInfo(album).observe(viewLifecycleOwner, { result ->
when (result) {
is Result.Loading -> {
println("Loading")
@ -222,41 +254,44 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
}
private fun moreAlbums(albums: List<Album>) {
moreTitle.show()
moreRecyclerView.show()
moreTitle.text = String.format(getString(R.string.label_more_from), album.artistName)
binding.fragmentAlbumContent.moreTitle.show()
binding.fragmentAlbumContent.moreRecyclerView.show()
binding.fragmentAlbumContent.moreTitle.text =
String.format(getString(R.string.label_more_from), album.artistName)
val albumAdapter =
HorizontalAlbumAdapter(requireActivity() as AppCompatActivity, albums, null, this)
moreRecyclerView.layoutManager = GridLayoutManager(
HorizontalAlbumAdapter(requireActivity() as AppCompatActivity, albums, this, this)
binding.fragmentAlbumContent.moreRecyclerView.layoutManager = GridLayoutManager(
requireContext(),
1,
GridLayoutManager.HORIZONTAL,
false
)
moreRecyclerView.adapter = albumAdapter
binding.fragmentAlbumContent.moreRecyclerView.adapter = albumAdapter
}
private fun aboutAlbum(lastFmAlbum: LastFmAlbum) {
if (lastFmAlbum.album != null) {
if (lastFmAlbum.album.wiki != null) {
aboutAlbumText.show()
aboutAlbumTitle.show()
aboutAlbumTitle.text =
binding.fragmentAlbumContent.aboutAlbumText.show()
binding.fragmentAlbumContent.aboutAlbumTitle.show()
binding.fragmentAlbumContent.aboutAlbumTitle.text =
String.format(getString(R.string.about_album_label), lastFmAlbum.album.name)
aboutAlbumText.text = HtmlCompat.fromHtml(
binding.fragmentAlbumContent.aboutAlbumText.text = HtmlCompat.fromHtml(
lastFmAlbum.album.wiki.content,
HtmlCompat.FROM_HTML_MODE_LEGACY
)
}
if (lastFmAlbum.album.listeners.isNotEmpty()) {
listeners.show()
listenersLabel.show()
scrobbles.show()
scrobblesLabel.show()
binding.fragmentAlbumContent.listeners.show()
binding.fragmentAlbumContent.listenersLabel.show()
binding.fragmentAlbumContent.scrobbles.show()
binding.fragmentAlbumContent.scrobblesLabel.show()
listeners.text = RetroUtil.formatValue(lastFmAlbum.album.listeners.toFloat())
scrobbles.text = RetroUtil.formatValue(lastFmAlbum.album.playcount.toFloat())
binding.fragmentAlbumContent.listeners.text =
RetroUtil.formatValue(lastFmAlbum.album.listeners.toFloat())
binding.fragmentAlbumContent.scrobbles.text =
RetroUtil.formatValue(lastFmAlbum.album.playcount.toFloat())
}
}
}
@ -265,24 +300,22 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
detailsViewModel.getMoreAlbums(artist).observe(viewLifecycleOwner, {
moreAlbums(it)
})
ArtistGlideRequest.Builder.from(Glide.with(requireContext()), artist)
.forceDownload(PreferenceUtil.isAllowedToDownloadMetadata())
.generatePalette(requireContext())
.build()
GlideApp.with(requireContext()).asBitmapPalette().artistImageOptions(artist)
//.forceDownload(PreferenceUtil.isAllowedToDownloadMetadata())
.load(RetroGlideExtension.getArtistModel(artist, PreferenceUtil.isAllowedToDownloadMetadata()))
.dontAnimate()
.dontTransform()
.into(object : RetroMusicColoredTarget(artistImage) {
.into(object : RetroMusicColoredTarget(binding.artistImage) {
override fun onColorReady(colors: MediaNotificationProcessor) {
}
})
}
private fun loadAlbumCover(album: Album) {
AlbumGlideRequest.Builder.from(Glide.with(requireContext()), album.safeGetFirstSong())
.checkIgnoreMediaStore()
.generatePalette(requireContext())
.build()
.into(object : SingleColorTarget(image) {
GlideApp.with(requireContext()).asBitmapPalette().albumCoverOptions(album.safeGetFirstSong())
//.checkIgnoreMediaStore()
.load(RetroGlideExtension.getSongModel(album.safeGetFirstSong()))
.into(object : SingleColorTarget(binding.image) {
override fun onColorReady(color: Int) {
setColors(color)
}
@ -290,23 +323,17 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
}
private fun setColors(color: Int) {
shuffleAction?.applyColor(color)
playAction?.applyOutlineColor(color)
binding.fragmentAlbumContent.shuffleAction.applyColor(color)
binding.fragmentAlbumContent.playAction.applyOutlineColor(color)
}
override fun onAlbumClick(albumId: Long, view: View) {
exitTransition = MaterialElevationScale(false).apply {
duration = 300L
}
reenterTransition = MaterialElevationScale(false).apply {
duration = 300L
}
findNavController().navigate(
R.id.albumDetailsFragment,
bundleOf(EXTRA_ALBUM_ID to albumId),
null,
FragmentNavigatorExtras(
view to "album"
view to albumId.toString()
)
)
}
@ -318,9 +345,9 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
setUpSortOrderMenu(sortOrder.subMenu)
ToolbarContentTintHelper.handleOnCreateOptionsMenu(
requireContext(),
toolbar,
binding.toolbar,
menu,
getToolbarBackgroundColor(toolbar)
getToolbarBackgroundColor(binding.toolbar)
)
}
@ -360,7 +387,7 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
intent.putExtra(AbsTagEditorActivity.EXTRA_ID, album.id)
val options = ActivityOptions.makeSceneTransitionAnimation(
requireActivity(),
albumCoverContainer,
binding.albumCoverContainer,
"${getString(R.string.transition_album_art)}_${album.id}"
)
startActivityForResult(
@ -421,6 +448,37 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
simpleSongAdapter.swapDataSet(album.songs)
}
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 {
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
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
companion object {
const val TAG_EDITOR_REQUEST = 9002
}

View File

@ -14,11 +14,7 @@
*/
package code.name.monkey.retromusic.fragments.albums
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.liveData
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.*
import code.name.monkey.retromusic.interfaces.IMusicServiceEventListener
import code.name.monkey.retromusic.model.Album
import code.name.monkey.retromusic.model.Artist
@ -51,6 +47,11 @@ class AlbumDetailsViewModel(
emit(artist)
}
fun getAlbumArtist(artistName: String): LiveData<Artist> = liveData(IO) {
val artist = repository.albumArtistByName(artistName)
emit(artist)
}
fun getAlbumInfo(album: Album): LiveData<Result<LastFmAlbum>> = liveData {
emit(Result.Loading)
emit(repository.albumInfo(album.artistName ?: "-", album.title ?: "-"))
@ -73,4 +74,5 @@ class AlbumDetailsViewModel(
override fun onPlayStateChanged() {}
override fun onRepeatModeChanged() {}
override fun onShuffleModeChanged() {}
override fun onFavoriteStateChanged() {}
}

View File

@ -16,6 +16,7 @@ package code.name.monkey.retromusic.fragments.albums
import android.os.Bundle
import android.view.*
import androidx.activity.addCallback
import androidx.core.os.bundleOf
import androidx.navigation.fragment.FragmentNavigatorExtras
import androidx.navigation.fragment.findNavController
@ -33,7 +34,7 @@ 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.google.android.material.transition.MaterialElevationScale
import com.google.android.gms.cast.framework.CastButtonFactory
class AlbumsFragment : AbsRecyclerViewCustomGridSizeFragment<AlbumAdapter, GridLayoutManager>(),
IAlbumClickListener, ICabHolder {
@ -46,8 +47,17 @@ class AlbumsFragment : AbsRecyclerViewCustomGridSizeFragment<AlbumAdapter, GridL
else
adapter?.swapDataSet(listOf())
})
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
if (!handleBackPress()) {
remove()
requireActivity().onBackPressed()
}
}
}
override val titleRes: Int
get() = R.string.albums
override val emptyMessage: Int
get() = R.string.no_albums
@ -114,20 +124,15 @@ class AlbumsFragment : AbsRecyclerViewCustomGridSizeFragment<AlbumAdapter, GridL
}
override fun onAlbumClick(albumId: Long, view: View) {
exitTransition = MaterialElevationScale(false).apply {
duration = 300L
}
reenterTransition = MaterialElevationScale(true).apply {
duration = 300L
}
findNavController().navigate(
R.id.albumDetailsFragment,
bundleOf(EXTRA_ALBUM_ID to albumId),
null,
FragmentNavigatorExtras(
view to "album"
view to albumId.toString()
)
)
reenterTransition = null
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
@ -140,6 +145,8 @@ class AlbumsFragment : AbsRecyclerViewCustomGridSizeFragment<AlbumAdapter, GridL
val layoutItem = menu.findItem(R.id.action_layout_type)
setupLayoutMenu(layoutItem.subMenu)
setUpSortOrderMenu(menu.findItem(R.id.action_sort_order).subMenu)
//Setting up cast button
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.action_cast)
}
private fun setUpSortOrderMenu(
@ -175,6 +182,13 @@ class AlbumsFragment : AbsRecyclerViewCustomGridSizeFragment<AlbumAdapter, GridL
R.string.sort_order_year
).isChecked =
currentSortOrder.equals(AlbumSortOrder.ALBUM_YEAR)
sortOrderMenu.add(
0,
R.id.action_album_sort_order_num_songs,
4,
R.string.sort_order_num_songs
).isChecked =
currentSortOrder.equals(AlbumSortOrder.ALBUM_NUMBER_OF_SONGS)
sortOrderMenu.setGroupCheckable(0, true, true)
}
@ -251,6 +265,7 @@ class AlbumsFragment : AbsRecyclerViewCustomGridSizeFragment<AlbumAdapter, GridL
R.id.action_album_sort_order_desc -> AlbumSortOrder.ALBUM_Z_A
R.id.action_album_sort_order_artist -> AlbumSortOrder.ALBUM_ARTIST
R.id.action_album_sort_order_year -> AlbumSortOrder.ALBUM_YEAR
R.id.action_album_sort_order_num_songs -> AlbumSortOrder.ALBUM_NUMBER_OF_SONGS
else -> PreferenceUtil.albumSortOrder
}
if (sortOrder != PreferenceUtil.albumSortOrder) {
@ -303,6 +318,21 @@ class AlbumsFragment : AbsRecyclerViewCustomGridSizeFragment<AlbumAdapter, GridL
return false
}
override fun onResume() {
super.onResume()
libraryViewModel.forceReload(ReloadType.Albums)
}
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 {

View File

@ -0,0 +1,342 @@
package code.name.monkey.retromusic.fragments.artists
import android.app.Activity
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.text.Spanned
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.core.text.HtmlCompat
import androidx.core.view.ViewCompat
import androidx.core.view.doOnPreDraw
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.FragmentNavigatorExtras
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import code.name.monkey.retromusic.EXTRA_ALBUM_ID
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.album.HorizontalAlbumAdapter
import code.name.monkey.retromusic.adapter.song.SimpleSongAdapter
import code.name.monkey.retromusic.databinding.FragmentArtistDetailsBinding
import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog
import code.name.monkey.retromusic.extensions.*
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.glide.SingleColorTarget
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.interfaces.IAlbumClickListener
import code.name.monkey.retromusic.interfaces.ICabHolder
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 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.google.android.material.transition.MaterialContainerTransform
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.android.ext.android.get
import java.util.*
import kotlin.collections.ArrayList
abstract class AbsArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_artist_details),
IAlbumClickListener, ICabHolder {
private var _binding: FragmentArtistDetailsBinding? = null
private val binding get() = _binding!!
abstract val detailsViewModel: ArtistDetailsViewModel
abstract val artistId: Long?
abstract val artistName: String?
private lateinit var artist: Artist
private lateinit var songAdapter: SimpleSongAdapter
private lateinit var albumAdapter: HorizontalAlbumAdapter
private var forceDownload: Boolean = false
private var lang: String? = null
private var biography: Spanned? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sharedElementEnterTransition = MaterialContainerTransform().apply {
drawingViewId = R.id.fragment_container
duration = 300L
scrimColor = Color.TRANSPARENT
setAllContainerColors(requireContext().resolveColor(R.attr.colorSurface))
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentArtistDetailsBinding.bind(view)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
setHasOptionsMenu(true)
mainActivity.addMusicServiceEventListener(detailsViewModel)
mainActivity.setSupportActionBar(binding.toolbar)
binding.toolbar.title = null
ViewCompat.setTransitionName(
binding.artistCoverContainer,
(artistId ?: artistName).toString()
)
postponeEnterTransition()
detailsViewModel.getArtist().observe(viewLifecycleOwner, {
requireView().doOnPreDraw {
startPostponedEnterTransition()
}
showArtist(it)
})
setupRecyclerView()
binding.fragmentArtistContent.playAction.apply {
setOnClickListener { MusicPlayerRemote.openQueue(artist.songs, 0, true) }
}
binding.fragmentArtistContent.shuffleAction.apply {
setOnClickListener { MusicPlayerRemote.openAndShuffleQueue(artist.songs, true) }
}
binding.fragmentArtistContent.biographyText.setOnClickListener {
if (binding.fragmentArtistContent.biographyText.maxLines == 4) {
binding.fragmentArtistContent.biographyText.maxLines = Integer.MAX_VALUE
} else {
binding.fragmentArtistContent.biographyText.maxLines = 4
}
}
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
if (!handleBackPress()) {
remove()
requireActivity().onBackPressed()
}
}
}
private fun setupRecyclerView() {
albumAdapter = HorizontalAlbumAdapter(requireActivity(), ArrayList(), this, this)
binding.fragmentArtistContent.albumRecyclerView.apply {
itemAnimator = DefaultItemAnimator()
layoutManager = GridLayoutManager(this.context, 1, GridLayoutManager.HORIZONTAL, false)
adapter = albumAdapter
}
songAdapter = SimpleSongAdapter(requireActivity(), ArrayList(), R.layout.item_song, this)
binding.fragmentArtistContent.recyclerView.apply {
itemAnimator = DefaultItemAnimator()
layoutManager = LinearLayoutManager(this.context)
adapter = songAdapter
}
}
private fun showArtist(artist: Artist) {
this.artist = artist
loadArtistImage(artist)
if (RetroUtil.isAllowedToDownloadMetadata(requireContext())) {
loadBiography(artist.name)
}
binding.artistTitle.text = artist.name
binding.text.text = String.format(
"%s • %s",
MusicUtil.getArtistInfoString(requireContext(), artist),
MusicUtil.getReadableDurationString(MusicUtil.getTotalDuration(artist.songs))
)
val songText = resources.getQuantityString(
R.plurals.albumSongs,
artist.songCount,
artist.songCount
)
val albumText = resources.getQuantityString(
R.plurals.albums,
artist.songCount,
artist.songCount
)
binding.fragmentArtistContent.songTitle.text = songText
binding.fragmentArtistContent.albumTitle.text = albumText
songAdapter.swapDataSet(artist.songs.sortedBy { it.trackNumber })
albumAdapter.swapDataSet(artist.albums)
}
private fun loadBiography(
name: String,
lang: String? = Locale.getDefault().language
) {
biography = null
this.lang = lang
detailsViewModel.getArtistInfo(name, lang, null)
.observe(viewLifecycleOwner, { result ->
when (result) {
is Result.Loading -> println("Loading")
is Result.Error -> println("Error")
is Result.Success -> artistInfo(result.data)
}
})
}
private fun artistInfo(lastFmArtist: LastFmArtist?) {
if (lastFmArtist != null && lastFmArtist.artist != null && lastFmArtist.artist.bio != null) {
val bioContent = lastFmArtist.artist.bio.content
if (bioContent != null && bioContent.trim { it <= ' ' }.isNotEmpty()) {
binding.fragmentArtistContent.biographyText.visibility = View.VISIBLE
binding.fragmentArtistContent.biographyTitle.visibility = View.VISIBLE
biography = HtmlCompat.fromHtml(bioContent, HtmlCompat.FROM_HTML_MODE_LEGACY)
binding.fragmentArtistContent.biographyText.text = biography
if (lastFmArtist.artist.stats.listeners.isNotEmpty()) {
binding.fragmentArtistContent.listeners.show()
binding.fragmentArtistContent.listenersLabel.show()
binding.fragmentArtistContent.scrobbles.show()
binding.fragmentArtistContent.scrobblesLabel.show()
binding.fragmentArtistContent.listeners.text =
RetroUtil.formatValue(lastFmArtist.artist.stats.listeners.toFloat())
binding.fragmentArtistContent.scrobbles.text =
RetroUtil.formatValue(lastFmArtist.artist.stats.playcount.toFloat())
}
}
}
// If the "lang" parameter is set and no biography is given, retry with default language
if (biography == null && lang != null) {
loadBiography(artist.name, null)
}
}
private fun loadArtistImage(artist: Artist) {
GlideApp.with(requireContext()).asBitmapPalette().artistImageOptions(artist)
.load(RetroGlideExtension.getArtistModel(artist))
.dontAnimate()
.into(object : SingleColorTarget(binding.image) {
override fun onColorReady(color: Int) {
setColors(color)
}
})
}
private fun setColors(color: Int) {
if (_binding != null) {
binding.fragmentArtistContent.shuffleAction.applyColor(color)
binding.fragmentArtistContent.playAction.applyOutlineColor(color)
}
}
override fun onAlbumClick(albumId: Long, view: View) {
findNavController().navigate(
R.id.albumDetailsFragment,
bundleOf(EXTRA_ALBUM_ID to albumId),
null,
FragmentNavigatorExtras(
view to albumId.toString()
)
)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return handleSortOrderMenuItem(item)
}
private fun handleSortOrderMenuItem(item: MenuItem): Boolean {
val songs = artist.songs
when (item.itemId) {
android.R.id.home -> findNavController().navigateUp()
R.id.action_play_next -> {
MusicPlayerRemote.playNext(songs)
return true
}
R.id.action_add_to_current_playing -> {
MusicPlayerRemote.enqueue(songs)
return true
}
R.id.action_add_to_playlist -> {
lifecycleScope.launch(Dispatchers.IO) {
val playlists = get<RealRepository>().fetchPlaylists()
withContext(Dispatchers.Main) {
AddToPlaylistDialog.create(playlists, songs)
.show(childFragmentManager, "ADD_PLAYLIST")
}
}
return true
}
R.id.action_set_artist_image -> {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "image/*"
startActivityForResult(
Intent.createChooser(intent, getString(R.string.pick_from_local_storage)),
REQUEST_CODE_SELECT_IMAGE
)
return true
}
R.id.action_reset_artist_image -> {
showToast(resources.getString(R.string.updating))
CustomArtistImageUtil.getInstance(requireContext()).resetCustomArtistImage(artist)
forceDownload = true
return true
}
}
return true
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
REQUEST_CODE_SELECT_IMAGE -> if (resultCode == Activity.RESULT_OK) {
data?.data?.let {
CustomArtistImageUtil.getInstance(requireContext())
.setCustomArtistImage(artist, it)
}
}
else -> if (resultCode == Activity.RESULT_OK) {
println("OK")
}
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.menu_artist_detail, menu)
}
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 {
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
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
companion object {
const val REQUEST_CODE_SELECT_IMAGE = 9002
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright (c) 2020 Hemanth Savarla.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*/
package code.name.monkey.retromusic.fragments.artists
import androidx.navigation.fragment.navArgs
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
class AlbumArtistDetailsFragment : AbsArtistDetailsFragment() {
private val arguments by navArgs<AlbumArtistDetailsFragmentArgs>()
override val detailsViewModel: ArtistDetailsViewModel by viewModel {
parametersOf(null, arguments.extraArtistName)
}
override val artistId: Long?
get() = null
override val artistName: String
get() = arguments.extraArtistName
}

View File

@ -0,0 +1,66 @@
/*
* Copyright (c) 2020 Hemanth Savarla.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY 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

@ -14,298 +14,18 @@
*/
package code.name.monkey.retromusic.fragments.artists
import android.app.Activity
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.text.Spanned
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import androidx.core.os.bundleOf
import androidx.core.text.HtmlCompat
import androidx.core.view.ViewCompat
import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.FragmentNavigatorExtras
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.retromusic.EXTRA_ALBUM_ID
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.album.HorizontalAlbumAdapter
import code.name.monkey.retromusic.adapter.song.SimpleSongAdapter
import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog
import code.name.monkey.retromusic.extensions.*
import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment
import code.name.monkey.retromusic.glide.ArtistGlideRequest
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.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 code.name.monkey.retromusic.util.CustomArtistImageUtil
import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.RetroUtil
import com.bumptech.glide.Glide
import com.google.android.material.transition.MaterialContainerTransform
import com.google.android.material.transition.MaterialElevationScale
import kotlinx.android.synthetic.main.fragment_artist_content.*
import kotlinx.android.synthetic.main.fragment_artist_details.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.android.ext.android.get
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
import java.util.*
import kotlin.collections.ArrayList
class ArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_artist_details),
IAlbumClickListener {
class ArtistDetailsFragment : AbsArtistDetailsFragment() {
private val arguments by navArgs<ArtistDetailsFragmentArgs>()
private val detailsViewModel: ArtistDetailsViewModel by viewModel {
parametersOf(arguments.extraArtistId)
override val detailsViewModel: ArtistDetailsViewModel by viewModel {
parametersOf(arguments.extraArtistId, null)
}
private lateinit var artist: Artist
private lateinit var songAdapter: SimpleSongAdapter
private lateinit var albumAdapter: HorizontalAlbumAdapter
private var forceDownload: Boolean = false
private var lang: String? = null
private var biography: Spanned? = null
override val artistId: Long
get() = arguments.extraArtistId
override val artistName: String?
get() = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sharedElementEnterTransition = MaterialContainerTransform().apply {
drawingViewId = R.id.fragment_container
duration = 300L
scrimColor = Color.TRANSPARENT
setAllContainerColors(requireContext().resolveColor(R.attr.colorSurface))
}
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
setHasOptionsMenu(true)
mainActivity.setBottomBarVisibility(false)
mainActivity.addMusicServiceEventListener(detailsViewModel)
mainActivity.setSupportActionBar(toolbar)
toolbar.title = null
ViewCompat.setTransitionName(artistCoverContainer, "artist")
postponeEnterTransition()
detailsViewModel.getArtist().observe(viewLifecycleOwner, {
startPostponedEnterTransition()
showArtist(it)
})
setupRecyclerView()
playAction.apply {
setOnClickListener { MusicPlayerRemote.openQueue(artist.songs, 0, true) }
}
shuffleAction.apply {
setOnClickListener { MusicPlayerRemote.openAndShuffleQueue(artist.songs, true) }
}
biographyText.setOnClickListener {
if (biographyText.maxLines == 4) {
biographyText.maxLines = Integer.MAX_VALUE
} else {
biographyText.maxLines = 4
}
}
}
private fun setupRecyclerView() {
albumAdapter = HorizontalAlbumAdapter(requireActivity(), ArrayList(), null, this)
albumRecyclerView.apply {
itemAnimator = DefaultItemAnimator()
layoutManager = GridLayoutManager(this.context, 1, GridLayoutManager.HORIZONTAL, false)
adapter = albumAdapter
}
songAdapter = SimpleSongAdapter(requireActivity(), ArrayList(), R.layout.item_song, null)
recyclerView.apply {
itemAnimator = DefaultItemAnimator()
layoutManager = LinearLayoutManager(this.context)
adapter = songAdapter
}
}
private fun showArtist(artist: Artist) {
this.artist = artist
loadArtistImage(artist)
if (RetroUtil.isAllowedToDownloadMetadata(requireContext())) {
loadBiography(artist.name)
}
artistTitle.text = artist.name
text.text = String.format(
"%s • %s",
MusicUtil.getArtistInfoString(requireContext(), artist),
MusicUtil.getReadableDurationString(MusicUtil.getTotalDuration(artist.songs))
)
val songText = resources.getQuantityString(
R.plurals.albumSongs,
artist.songCount,
artist.songCount
)
val albumText = resources.getQuantityString(
R.plurals.albums,
artist.songCount,
artist.songCount
)
songTitle.text = songText
albumTitle.text = albumText
songAdapter.swapDataSet(artist.songs.sortedBy { it.trackNumber })
albumAdapter.swapDataSet(artist.albums)
}
private fun loadBiography(
name: String,
lang: String? = Locale.getDefault().language
) {
biography = null
this.lang = lang
detailsViewModel.getArtistInfo(name, lang, null)
.observe(viewLifecycleOwner, { result ->
when (result) {
is Result.Loading -> println("Loading")
is Result.Error -> println("Error")
is Result.Success -> artistInfo(result.data)
}
})
}
private fun artistInfo(lastFmArtist: LastFmArtist?) {
if (lastFmArtist != null && lastFmArtist.artist != null) {
val bioContent = lastFmArtist.artist.bio.content
if (bioContent != null && bioContent.trim { it <= ' ' }.isNotEmpty()) {
biographyText.visibility = View.VISIBLE
biographyTitle.visibility = View.VISIBLE
biography = HtmlCompat.fromHtml(bioContent, HtmlCompat.FROM_HTML_MODE_LEGACY)
biographyText.text = biography
if (lastFmArtist.artist.stats.listeners.isNotEmpty()) {
listeners.show()
listenersLabel.show()
scrobbles.show()
scrobblesLabel.show()
listeners.text =
RetroUtil.formatValue(lastFmArtist.artist.stats.listeners.toFloat())
scrobbles.text =
RetroUtil.formatValue(lastFmArtist.artist.stats.playcount.toFloat())
}
}
}
// If the "lang" parameter is set and no biography is given, retry with default language
if (biography == null && lang != null) {
loadBiography(artist.name, null)
}
}
private fun loadArtistImage(artist: Artist) {
ArtistGlideRequest.Builder.from(Glide.with(requireContext()), artist)
.generatePalette(requireContext()).build()
.dontAnimate()
.into(object : SingleColorTarget(image) {
override fun onColorReady(color: Int) {
setColors(color)
}
})
}
private fun setColors(color: Int) {
shuffleAction?.applyColor(color)
playAction?.applyOutlineColor(color)
}
override fun onAlbumClick(albumId: Long, view: View) {
exitTransition = MaterialElevationScale(false).apply {
duration = 300L
}
reenterTransition = MaterialElevationScale(false).apply {
duration = 300L
}
findNavController().navigate(
R.id.albumDetailsFragment,
bundleOf(EXTRA_ALBUM_ID to albumId),
null,
FragmentNavigatorExtras(
view to "album"
)
)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return handleSortOrderMenuItem(item)
}
private fun handleSortOrderMenuItem(item: MenuItem): Boolean {
val songs = artist.songs
when (item.itemId) {
android.R.id.home -> findNavController().navigateUp()
R.id.action_play_next -> {
MusicPlayerRemote.playNext(songs)
return true
}
R.id.action_add_to_current_playing -> {
MusicPlayerRemote.enqueue(songs)
return true
}
R.id.action_add_to_playlist -> {
lifecycleScope.launch(Dispatchers.IO) {
val playlists = get<RealRepository>().fetchPlaylists()
withContext(Dispatchers.Main) {
AddToPlaylistDialog.create(playlists, songs)
.show(childFragmentManager, "ADD_PLAYLIST")
}
}
return true
}
R.id.action_set_artist_image -> {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "image/*"
startActivityForResult(
Intent.createChooser(intent, getString(R.string.pick_from_local_storage)),
REQUEST_CODE_SELECT_IMAGE
)
return true
}
R.id.action_reset_artist_image -> {
showToast(resources.getString(R.string.updating))
CustomArtistImageUtil.getInstance(requireContext()).resetCustomArtistImage(artist)
forceDownload = true
return true
}
}
return true
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
REQUEST_CODE_SELECT_IMAGE -> if (resultCode == Activity.RESULT_OK) {
data?.data?.let {
CustomArtistImageUtil.getInstance(requireContext())
.setCustomArtistImage(artist, it)
}
}
else -> if (resultCode == Activity.RESULT_OK) {
println("OK")
}
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.menu_artist_detail, menu)
}
companion object {
const val REQUEST_CODE_SELECT_IMAGE = 9002
}
}

View File

@ -14,11 +14,7 @@
*/
package code.name.monkey.retromusic.fragments.artists
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.liveData
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.*
import code.name.monkey.retromusic.interfaces.IMusicServiceEventListener
import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.network.Result
@ -29,7 +25,8 @@ import kotlinx.coroutines.launch
class ArtistDetailsViewModel(
private val realRepository: RealRepository,
private val artistId: Long
private val artistId: Long?,
private val artistName: String?
) : ViewModel(), IMusicServiceEventListener {
private val artistDetails = MutableLiveData<Artist>()
@ -39,7 +36,9 @@ class ArtistDetailsViewModel(
private fun fetchArtist() {
viewModelScope.launch(IO) {
artistDetails.postValue(realRepository.artistById(artistId))
artistId?.let { artistDetails.postValue(realRepository.artistById(it)) }
artistName?.let { artistDetails.postValue(realRepository.albumArtistByName(it)) }
}
}
@ -56,7 +55,7 @@ class ArtistDetailsViewModel(
}
override fun onMediaStoreChanged() {
fetchArtist()
fetchArtist()
}
override fun onServiceConnected() {}
@ -66,4 +65,5 @@ class ArtistDetailsViewModel(
override fun onPlayStateChanged() {}
override fun onRepeatModeChanged() {}
override fun onShuffleModeChanged() {}
override fun onFavoriteStateChanged() {}
}

View File

@ -16,38 +16,48 @@ package code.name.monkey.retromusic.fragments.artists
import android.os.Bundle
import android.view.*
import androidx.activity.addCallback
import androidx.core.os.bundleOf
import androidx.lifecycle.Observer
import androidx.navigation.fragment.FragmentNavigatorExtras
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.GridLayoutManager
import 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.surfaceColor
import code.name.monkey.retromusic.fragments.ReloadType
import code.name.monkey.retromusic.fragments.base.AbsRecyclerViewCustomGridSizeFragment
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.ICabHolder
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.google.android.material.transition.MaterialElevationScale
import com.google.android.gms.cast.framework.CastButtonFactory
class ArtistsFragment : AbsRecyclerViewCustomGridSizeFragment<ArtistAdapter, GridLayoutManager>(),
IArtistClickListener, ICabHolder {
IArtistClickListener, IAlbumArtistClickListener, ICabHolder {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
libraryViewModel.getArtists().observe(viewLifecycleOwner, Observer {
libraryViewModel.getArtists().observe(viewLifecycleOwner, {
if (it.isNotEmpty())
adapter?.swapDataSet(it)
else
adapter?.swapDataSet(listOf())
})
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
if (!handleBackPress()) {
remove()
requireActivity().onBackPressed()
}
}
}
override val titleRes: Int
get() = R.string.artists
override val emptyMessage: Int
get() = R.string.no_artists
@ -66,7 +76,7 @@ class ArtistsFragment : AbsRecyclerViewCustomGridSizeFragment<ArtistAdapter, Gri
dataSet,
itemLayoutRes(),
this,
this
this, this
)
}
@ -115,18 +125,23 @@ class ArtistsFragment : AbsRecyclerViewCustomGridSizeFragment<ArtistAdapter, Gri
}
override fun onArtist(artistId: Long, view: View) {
exitTransition = MaterialElevationScale(true).apply {
duration = 300L
}
reenterTransition = MaterialElevationScale(false).apply {
duration = 300L
}
findNavController().navigate(
R.id.artistDetailsFragment,
bundleOf(EXTRA_ARTIST_ID to artistId),
null,
FragmentNavigatorExtras(view to "artist")
FragmentNavigatorExtras(view to artistId.toString())
)
reenterTransition = null
}
override fun onAlbumArtist(artistName: String, view: View) {
findNavController().navigate(
R.id.albumArtistDetailsFragment,
bundleOf(EXTRA_ARTIST_NAME to artistName),
null,
FragmentNavigatorExtras(view to artistName)
)
reenterTransition = null
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
@ -139,6 +154,16 @@ class ArtistsFragment : AbsRecyclerViewCustomGridSizeFragment<ArtistAdapter, Gri
val layoutItem = menu.findItem(R.id.action_layout_type)
setupLayoutMenu(layoutItem.subMenu)
setUpSortOrderMenu(menu.findItem(R.id.action_sort_order).subMenu)
setupAlbumArtistMenu(menu)
//Setting up cast button
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.action_cast)
}
private fun setupAlbumArtistMenu(menu: Menu) {
menu.add(0, R.id.action_album_artist, 0, R.string.show_album_artists).apply {
isCheckable = true
isChecked = PreferenceUtil.albumArtistsOnly
}
}
private fun setUpSortOrderMenu(
@ -222,9 +247,23 @@ class ArtistsFragment : AbsRecyclerViewCustomGridSizeFragment<ArtistAdapter, Gri
if (handleSortOrderMenuItem(item)) {
return true
}
if (handleAlbumArtistMenu(item)) {
return true
}
return super.onOptionsItemSelected(item)
}
private fun handleAlbumArtistMenu(item: MenuItem): Boolean {
return if (item.itemId == R.id.action_album_artist) {
PreferenceUtil.albumArtistsOnly = !item.isChecked
item.isChecked = !item.isChecked
libraryViewModel.forceReload(ReloadType.Artists)
true
} else {
false
}
}
private fun handleSortOrderMenuItem(
item: MenuItem
): Boolean {
@ -283,6 +322,16 @@ class ArtistsFragment : AbsRecyclerViewCustomGridSizeFragment<ArtistAdapter, Gri
return false
}
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 {
@ -298,4 +347,9 @@ class ArtistsFragment : AbsRecyclerViewCustomGridSizeFragment<ArtistAdapter, Gri
.start(callback)
return cab as MaterialCab
}
override fun onResume() {
super.onResume()
libraryViewModel.forceReload(ReloadType.Artists)
}
}

View File

@ -27,10 +27,9 @@ import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity
import code.name.monkey.retromusic.interfaces.IMusicServiceEventListener
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.RetroUtil
import org.jaudiotagger.audio.AudioFileIO
import java.io.File
import java.net.URLEncoder
import java.util.*
import org.jaudiotagger.audio.AudioFileIO
/**
* Created by hemanths on 18/08/17.
@ -78,6 +77,9 @@ open class AbsMusicServiceFragment(@LayoutRes layout: Int) : Fragment(layout),
serviceActivity?.removeMusicServiceEventListener(this)
}
override fun onFavoriteStateChanged() {
}
override fun onPlayingMetaChanged() {
}
@ -121,10 +123,10 @@ open class AbsMusicServiceFragment(@LayoutRes layout: Int) : Fragment(layout),
return "-"
}
private fun getMimeType(url: String): String? {
private fun getMimeType(url: String): String {
var type: String? = MimeTypeMap.getFileExtensionFromUrl(
URLEncoder.encode(url, "utf-8")
).toUpperCase(Locale.getDefault())
).uppercase()
if (type == null) {
type = url.substring(url.lastIndexOf(".") + 1)
}

View File

@ -44,7 +44,7 @@ abstract class AbsPlayerControlsFragment(@LayoutRes layout: Int) : AbsMusicServi
abstract fun setColor(color: MediaNotificationProcessor)
fun showBonceAnimation(view: View) {
fun showBounceAnimation(view: View) {
view.apply {
clearAnimation()
scaleX = 0.9f

View File

@ -14,31 +14,41 @@
*/
package code.name.monkey.retromusic.fragments.base
import android.annotation.SuppressLint
import android.app.Activity
import android.content.ContentUris
import android.content.Context
import android.content.Intent
import android.graphics.drawable.AnimatedVectorDrawable
import android.graphics.drawable.Drawable
import android.media.MediaMetadataRetriever
import android.os.Build
import android.os.Bundle
import android.provider.MediaStore
import android.text.TextUtils
import android.view.GestureDetector
import android.view.MenuItem
import android.view.MotionEvent
import android.view.View
import android.widget.RelativeLayout
import android.widget.Toast
import androidx.annotation.LayoutRes
import androidx.appcompat.widget.Toolbar
import androidx.core.os.bundleOf
import androidx.lifecycle.lifecycleScope
import androidx.navigation.findNavController
import androidx.viewpager.widget.ViewPager
import code.name.monkey.retromusic.EXTRA_ALBUM_ID
import code.name.monkey.retromusic.EXTRA_ARTIST_ID
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.MainActivity
import code.name.monkey.retromusic.activities.tageditor.AbsTagEditorActivity
import code.name.monkey.retromusic.activities.tageditor.SongTagEditorActivity
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.dialogs.*
import code.name.monkey.retromusic.extensions.currentFragment
import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.extensions.whichFragment
import code.name.monkey.retromusic.fragments.ReloadType
@ -50,13 +60,14 @@ import code.name.monkey.retromusic.model.lyrics.Lyrics
import code.name.monkey.retromusic.repository.RealRepository
import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.util.*
import kotlinx.android.synthetic.main.shadow_statusbar_toolbar.*
import com.google.android.material.bottomsheet.BottomSheetBehavior
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.android.ext.android.get
import java.io.FileNotFoundException
import kotlin.math.abs
abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragment(layout),
Toolbar.OnMenuItemClickListener, IPaletteColorHolder, PlayerAlbumCoverFragment.Callbacks {
@ -68,6 +79,11 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
): Boolean {
val song = MusicPlayerRemote.currentSong
when (item.itemId) {
R.id.action_toggle_lyrics -> {
PreferenceUtil.showLyrics = !PreferenceUtil.showLyrics
showLyricsIcon(item)
return true
}
R.id.action_toggle_favorite -> {
toggleFavorite(song)
return true
@ -114,6 +130,8 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
return true
}
R.id.action_go_to_album -> {
//Hide Bottom Bar First, else Bottom Sheet doesn't collapse fully
mainActivity.setBottomBarVisibility(false)
mainActivity.collapsePanel()
requireActivity().findNavController(R.id.fragment_container).navigate(
R.id.albumDetailsFragment,
@ -122,11 +140,7 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
return true
}
R.id.action_go_to_artist -> {
mainActivity.collapsePanel()
requireActivity().findNavController(R.id.fragment_container).navigate(
R.id.artistDetailsFragment,
bundleOf(EXTRA_ARTIST_ID to song.artistId)
)
goToArtist(requireActivity())
return true
}
R.id.now_playing -> {
@ -158,7 +172,7 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
val trackUri =
ContentUris.withAppendedId(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
song.id.toLong()
song.id
)
retriever.setDataSource(activity, trackUri)
var genre: String? =
@ -173,6 +187,18 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
return false
}
private fun showLyricsIcon(item: MenuItem) {
val icon =
if (PreferenceUtil.showLyrics) R.drawable.ic_lyrics else R.drawable.ic_lyrics_outline
val drawable: Drawable? = RetroUtil.getTintedVectorDrawable(
requireContext(),
icon,
toolbarIconColor()
)
item.isChecked = PreferenceUtil.showLyrics
item.icon = drawable
}
abstract fun playerToolbar(): Toolbar?
abstract fun onShow()
@ -185,7 +211,6 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
override fun onServiceConnected() {
updateIsFavorite()
updateLyrics()
}
override fun onPlayingMetaChanged() {
@ -193,9 +218,13 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
updateLyrics()
}
override fun onFavoriteStateChanged() {
updateIsFavorite(animate = true)
}
protected open fun toggleFavorite(song: Song) {
lifecycleScope.launch(IO) {
val playlist: PlaylistEntity? = libraryViewModel.favoritePlaylist()
val playlist: PlaylistEntity = libraryViewModel.favoritePlaylist()
if (playlist != null) {
val songEntity = song.toSongEntity(playlist.playListId)
val isFavorite = libraryViewModel.isFavoriteSong(songEntity).isNotEmpty()
@ -210,26 +239,36 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
}
}
fun updateIsFavorite() {
fun updateIsFavorite(animate: Boolean = false) {
lifecycleScope.launch(IO) {
val playlist: PlaylistEntity? = libraryViewModel.favoritePlaylist()
val playlist: PlaylistEntity = libraryViewModel.favoritePlaylist()
if (playlist != null) {
val song: SongEntity =
MusicPlayerRemote.currentSong.toSongEntity(playlist.playListId)
val isFavorite: Boolean = libraryViewModel.isFavoriteSong(song).isNotEmpty()
withContext(Main) {
val icon =
val icon = if (animate) {
if (isFavorite) R.drawable.avd_favorite else R.drawable.avd_unfavorite
} else {
if (isFavorite) R.drawable.ic_favorite else R.drawable.ic_favorite_border
}
val drawable: Drawable? = RetroUtil.getTintedVectorDrawable(
requireContext(),
icon,
toolbarIconColor()
)
if (playerToolbar() != null) {
playerToolbar()?.menu?.findItem(R.id.action_toggle_favorite)
?.setIcon(drawable)?.title =
if (isFavorite) getString(R.string.action_remove_from_favorites)
else getString(R.string.action_add_to_favorites)
playerToolbar()?.menu?.findItem(R.id.action_toggle_favorite)?.apply {
setIcon(drawable)
title =
if (isFavorite) getString(R.string.action_remove_from_favorites)
else getString(R.string.action_add_to_favorites)
getIcon().also {
if (it is AnimatedVectorDrawable) {
it.start()
}
}
}
}
}
}
@ -273,7 +312,50 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
playerAlbumCoverFragment?.setCallbacks(this)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
statusBarShadow?.hide()
view.findViewById<RelativeLayout>(R.id.statusBarShadow)?.hide()
}
@SuppressLint("ClickableViewAccessibility")
override fun onStart() {
super.onStart()
requireView().setOnTouchListener(
SwipeDetector(
requireContext(),
playerAlbumCoverFragment?.viewPager,
requireView()
)
)
}
class SwipeDetector(val context: Context, val viewPager: ViewPager?, val view: View) :
View.OnTouchListener {
private var flingPlayBackController: GestureDetector = GestureDetector(
context,
object : GestureDetector.SimpleOnGestureListener() {
override fun onScroll(
e1: MotionEvent?,
e2: MotionEvent?,
distanceX: Float,
distanceY: Float
): Boolean {
return when {
abs(distanceX) > abs(distanceY) -> {
// Disallow Intercept Touch Event so that parent(BottomSheet) doesn't consume the events
view.parent.requestDisallowInterceptTouchEvent(true)
true
}
else -> {
false
}
}
}
})
@SuppressLint("ClickableViewAccessibility")
override fun onTouch(v: View, event: MotionEvent?): Boolean {
viewPager?.dispatchTouchEvent(event)
return flingPlayBackController.onTouchEvent(event)
}
}
companion object {
@ -290,3 +372,44 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
)
}
}
fun goToArtist(activity: Activity) {
if (activity !is MainActivity) return
val song = MusicPlayerRemote.currentSong
activity.apply {
// Remove exit transition of current fragment so
// it doesn't exit with a weird transition
currentFragment(R.id.fragment_container)?.exitTransition = null
//Hide Bottom Bar First, else Bottom Sheet doesn't collapse fully
setBottomBarVisibility(false)
if (getBottomSheetBehavior().state == BottomSheetBehavior.STATE_EXPANDED) {
collapsePanel()
}
findNavController(R.id.fragment_container).navigate(
R.id.artistDetailsFragment,
bundleOf(EXTRA_ARTIST_ID to song.artistId)
)
}
}
fun goToAlbum(activity: Activity) {
if (activity !is MainActivity) return
val song = MusicPlayerRemote.currentSong
activity.apply {
currentFragment(R.id.fragment_container)?.exitTransition = null
//Hide Bottom Bar First, else Bottom Sheet doesn't collapse fully
setBottomBarVisibility(false)
if (getBottomSheetBehavior().state == BottomSheetBehavior.STATE_EXPANDED) {
collapsePanel()
}
findNavController(R.id.fragment_container).navigate(
R.id.albumDetailsFragment,
bundleOf(EXTRA_ALBUM_ID to song.albumId)
)
}
}

View File

@ -14,12 +14,12 @@
*/
package code.name.monkey.retromusic.fragments.base
import android.os.Bundle
import android.view.View
import androidx.annotation.LayoutRes
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import androidx.transition.TransitionManager
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.util.RetroUtil
import com.google.android.material.transition.MaterialFade
abstract class AbsRecyclerViewCustomGridSizeFragment<A : RecyclerView.Adapter<*>, LM : RecyclerView.LayoutManager> :
AbsRecyclerViewFragment<A, LM>() {
@ -86,6 +86,7 @@ abstract class AbsRecyclerViewCustomGridSizeFragment<A : RecyclerView.Adapter<*>
} else {
saveGridSize(gridSize)
}
recyclerView().isVisible = false
invalidateLayoutManager()
// only recreate the adapter and layout manager if the layout currentLayoutRes has changed
if (oldLayoutRes != itemLayoutRes()) {
@ -93,26 +94,11 @@ abstract class AbsRecyclerViewCustomGridSizeFragment<A : RecyclerView.Adapter<*>
} else {
setGridSize(gridSize)
}
}
protected fun notifyLayoutResChanged(@LayoutRes res: Int) {
this.currentLayoutRes = res
val recyclerView = recyclerView()
applyRecyclerViewPaddingForLayoutRes(recyclerView, currentLayoutRes)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
applyRecyclerViewPaddingForLayoutRes(recyclerView(), currentLayoutRes)
}
private fun applyRecyclerViewPaddingForLayoutRes(recyclerView: RecyclerView, res: Int) {
val padding: Int = if (res == R.layout.item_grid) {
(resources.displayMetrics.density * 2).toInt()
} else {
0
val transition = MaterialFade().apply {
addTarget(recyclerView())
}
//recyclerView.setPadding(padding, padding, padding, padding)
TransitionManager.beginDelayedTransition(getContainer(), transition)
recyclerView().isVisible = true
}
protected abstract fun setGridSize(gridSize: Int)

View File

@ -15,80 +15,73 @@
package code.name.monkey.retromusic.fragments.base
import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.*
import androidx.annotation.NonNull
import androidx.annotation.StringRes
import androidx.core.text.HtmlCompat
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.doOnPreDraw
import androidx.core.view.updatePadding
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.RecyclerView
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.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.helper.MusicPlayerRemote
import code.name.monkey.retromusic.util.DensityUtil
import code.name.monkey.retromusic.util.ThemedFastScroller.create
import com.google.android.material.appbar.AppBarLayout
import kotlinx.android.synthetic.main.fragment_main_recycler.*
import com.google.android.material.transition.MaterialFadeThrough
import com.google.android.material.transition.MaterialSharedAxis
import me.zhanghai.android.fastscroll.FastScroller
import me.zhanghai.android.fastscroll.FastScrollerBuilder
abstract class AbsRecyclerViewFragment<A : RecyclerView.Adapter<*>, LM : RecyclerView.LayoutManager> :
AbsMainActivityFragment(R.layout.fragment_main_recycler),
AppBarLayout.OnOffsetChangedListener {
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
setHasOptionsMenu(true)
}
AbsMainActivityFragment(R.layout.fragment_main_recycler) {
private var _binding: FragmentMainRecyclerBinding? = null
private val binding get() = _binding!!
protected var adapter: A? = null
protected var layoutManager: LM? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentMainRecyclerBinding.bind(view)
enterTransition = MaterialFadeThrough()
exitTransition = MaterialFadeThrough()
postponeEnterTransition()
view.doOnPreDraw { startPostponedEnterTransition() }
mainActivity.setBottomBarVisibility(true)
mainActivity.setSupportActionBar(toolbar)
mainActivity.setSupportActionBar(binding.toolbar)
mainActivity.supportActionBar?.title = null
initLayoutManager()
initAdapter()
setUpRecyclerView()
setupTitle()
setupToolbar()
}
private fun setupTitle() {
toolbar.setNavigationOnClickListener {
private fun setupToolbar() {
binding.toolbar.setNavigationOnClickListener {
exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true).addTarget(requireView())
reenterTransition = 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 appName = HtmlCompat.fromHtml(
"Retro <span style='color:$hexColor';>Music</span>",
HtmlCompat.FROM_HTML_MODE_COMPACT
)
appNameText.text = appName
val appName = resources.getString(titleRes)
binding.appNameText.text = appName
}
abstract val titleRes: Int
private fun setUpRecyclerView() {
recyclerView.apply {
binding.recyclerView.apply {
layoutManager = this@AbsRecyclerViewFragment.layoutManager
adapter = this@AbsRecyclerViewFragment.adapter
val fastScroller = create(this)
create(this)
}
checkForPadding()
}
@ -116,8 +109,8 @@ abstract class AbsRecyclerViewFragment<A : RecyclerView.Adapter<*>, LM : Recycle
}
private fun checkIsEmpty() {
emptyText.setText(emptyMessage)
empty.visibility = if (adapter!!.itemCount == 0) View.VISIBLE else View.GONE
binding.emptyText.setText(emptyMessage)
binding.empty.visibility = if (adapter!!.itemCount == 0) View.VISIBLE else View.GONE
}
private fun checkForPadding() {
@ -125,10 +118,10 @@ abstract class AbsRecyclerViewFragment<A : RecyclerView.Adapter<*>, LM : Recycle
if (itemCount > 0 && MusicPlayerRemote.playingQueue.isNotEmpty()) {
val height = DensityUtil.dip2px(requireContext(), 112f)
recyclerView.updatePadding(0, 0, 0, height)
binding.recyclerView.updatePadding(0, 0, 0, height)
} else {
val height = DensityUtil.dip2px(requireContext(), 56f)
recyclerView.updatePadding(0, 0, 0, height)
binding.recyclerView.updatePadding(0, 0, 0, height)
}
}
@ -141,15 +134,6 @@ abstract class AbsRecyclerViewFragment<A : RecyclerView.Adapter<*>, LM : Recycle
@NonNull
protected abstract fun createAdapter(): A
override fun onOffsetChanged(p0: AppBarLayout?, i: Int) {
/*recyclerView.setPadding(
recyclerView.paddingLeft,
recyclerView.paddingTop,
recyclerView.paddingRight,
i
)*/
}
override fun onQueueChanged() {
super.onQueueChanged()
checkForPadding()
@ -162,22 +146,31 @@ abstract class AbsRecyclerViewFragment<A : RecyclerView.Adapter<*>, LM : Recycle
protected fun invalidateLayoutManager() {
initLayoutManager()
recyclerView.layoutManager = layoutManager
binding.recyclerView.layoutManager = layoutManager
}
protected fun invalidateAdapter() {
initAdapter()
checkIsEmpty()
recyclerView.adapter = adapter
binding.recyclerView.adapter = adapter
}
fun recyclerView(): RecyclerView {
return recyclerView
return binding.recyclerView
}
fun getContainer(): CoordinatorLayout {
return binding.root
}
fun scrollToTop() {
recyclerView().scrollToPosition(0)
binding.appBarLayout.setExpanded(true, true)
}
override fun onPrepareOptionsMenu(menu: Menu) {
super.onPrepareOptionsMenu(menu)
ToolbarContentTintHelper.handleOnPrepareOptionsMenu(requireActivity(), toolbar)
ToolbarContentTintHelper.handleOnPrepareOptionsMenu(requireActivity(), binding.toolbar)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
@ -185,9 +178,9 @@ abstract class AbsRecyclerViewFragment<A : RecyclerView.Adapter<*>, LM : Recycle
inflater.inflate(R.menu.menu_main, menu)
ToolbarContentTintHelper.handleOnCreateOptionsMenu(
requireContext(),
toolbar,
binding.toolbar,
menu,
ATHToolbarActivity.getToolbarBackgroundColor(toolbar)
ATHToolbarActivity.getToolbarBackgroundColor(binding.toolbar)
)
}
@ -209,4 +202,9 @@ abstract class AbsRecyclerViewFragment<A : RecyclerView.Adapter<*>, LM : Recycle
}
return super.onOptionsItemSelected(item)
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

View File

@ -14,13 +14,14 @@
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.text.Spanned;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@ -30,13 +31,11 @@ import android.view.ViewGroup;
import android.view.ViewGroup.MarginLayoutParams;
import android.webkit.MimeTypeMap;
import android.widget.PopupMenu;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import androidx.core.text.HtmlCompat;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.navigation.Navigation;
@ -46,25 +45,36 @@ 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;
@ -76,6 +86,7 @@ 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;
@ -85,15 +96,14 @@ import code.name.monkey.retromusic.views.BreadCrumbLayout;
import code.name.monkey.retromusic.views.ScrollingViewOnApplyWindowInsetsListener;
import me.zhanghai.android.fastscroll.FastScroller;
import static code.name.monkey.appthemehelper.common.ATHToolbarActivity.getToolbarBackgroundColor;
public class FoldersFragment extends AbsMainActivityFragment
implements IMainActivityFragmentCallbacks,
ICabHolder,
BreadCrumbLayout.SelectionCallback,
ICallbacks,
LoaderManager.LoaderCallbacks<List<File>> {
LoaderManager.LoaderCallbacks<List<File>>, StorageClickListener {
private FragmentFolderBinding binding;
public static final String TAG = FoldersFragment.class.getSimpleName();
public static final FileFilter AUDIO_FILE_FILTER =
file ->
@ -106,14 +116,9 @@ public class FoldersFragment extends AbsMainActivityFragment
private static final String CRUMBS = "crumbs";
private static final int LOADER_ID = 5;
private SongFileAdapter adapter;
private Toolbar toolbar;
private TextView appNameText;
private BreadCrumbLayout breadCrumbs;
private StorageAdapter storageAdapter;
private MaterialCab cab;
private View coordinatorLayout;
private View empty;
private TextView emojiText;
private Comparator<File> fileComparator =
private final Comparator<File> fileComparator =
(lhs, rhs) -> {
if (lhs.isDirectory() && !rhs.isDirectory()) {
return -1;
@ -123,7 +128,7 @@ public class FoldersFragment extends AbsMainActivityFragment
return lhs.getName().compareToIgnoreCase(rhs.getName());
}
};
private RecyclerView recyclerView;
private final ArrayList<Storage> storageItems = new ArrayList<>();
public FoldersFragment() {
super(R.layout.fragment_folder);
@ -158,35 +163,43 @@ public class FoldersFragment extends AbsMainActivityFragment
@Override
public View onCreateView(
@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_folder, container, false);
initViews(view);
return view;
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().setBottomBarVisibility(true);
getMainActivity().setSupportActionBar(toolbar);
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() {
toolbar.setNavigationOnClickListener(
v -> Navigation.findNavController(v).navigate(R.id.searchFragment, null, getNavOptions()));
int color = ThemeStore.Companion.accentColor(requireContext());
String hexColor = String.format("#%06X", 0xFFFFFF & color);
Spanned appName =
HtmlCompat.fromHtml(
"Retro <span style='color:" + hexColor + ";'>Music</span>",
HtmlCompat.FROM_HTML_MODE_COMPACT);
appNameText.setText(appName);
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
@ -194,12 +207,14 @@ public class FoldersFragment extends AbsMainActivityFragment
super.onActivityCreated(savedInstanceState);
setHasOptionsMenu(true);
if (savedInstanceState == null) {
switchToFileAdapter();
setCrumb(
new BreadCrumbLayout.Crumb(
FileUtil.safeGetCanonicalFile(PreferenceUtil.INSTANCE.getStartDirectory())),
true);
} else {
breadCrumbs.restoreFromStateWrapper(savedInstanceState.getParcelable(CRUMBS));
binding.breadCrumbs.restoreFromStateWrapper(savedInstanceState.getParcelable(CRUMBS));
LoaderManager.getInstance(this).initLoader(LOADER_ID, null, this);
}
}
@ -213,8 +228,8 @@ public class FoldersFragment extends AbsMainActivityFragment
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
if (breadCrumbs != null) {
outState.putParcelable(CRUMBS, breadCrumbs.getStateWrapper());
if (binding != null) {
outState.putParcelable(CRUMBS, binding.breadCrumbs.getStateWrapper());
}
}
@ -224,8 +239,8 @@ public class FoldersFragment extends AbsMainActivityFragment
cab.finish();
return true;
}
if (breadCrumbs != null && breadCrumbs.popHistory()) {
setCrumb(breadCrumbs.lastHistory(), false);
if (binding.breadCrumbs.popHistory()) {
setCrumb(binding.breadCrumbs.lastHistory(), false);
return true;
}
return false;
@ -268,6 +283,9 @@ public class FoldersFragment extends AbsMainActivityFragment
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(
@ -347,7 +365,7 @@ public class FoldersFragment extends AbsMainActivityFragment
} else {
final File finalFile = file1;
Snackbar.make(
coordinatorLayout,
binding.coordinatorLayout,
Html.fromHtml(
String.format(
getString(R.string.not_listed_in_media_store), file1.getName())),
@ -376,7 +394,7 @@ public class FoldersFragment extends AbsMainActivityFragment
@Override
public void onLoaderReset(@NonNull Loader<List<File>> loader) {
updateAdapter(new LinkedList<File>());
updateAdapter(new LinkedList<>());
}
@Override
@ -393,7 +411,7 @@ public class FoldersFragment extends AbsMainActivityFragment
@Override
public void onPrepareOptionsMenu(@NonNull Menu menu) {
super.onPrepareOptionsMenu(menu);
ToolbarContentTintHelper.handleOnPrepareOptionsMenu(requireActivity(), toolbar);
ToolbarContentTintHelper.handleOnPrepareOptionsMenu(requireActivity(), binding.toolbar);
}
@Override
@ -407,7 +425,7 @@ public class FoldersFragment extends AbsMainActivityFragment
menu.removeItem(R.id.action_layout_type);
menu.removeItem(R.id.action_sort_order);
ToolbarContentTintHelper.handleOnCreateOptionsMenu(
requireContext(), toolbar, menu, getToolbarBackgroundColor(toolbar));
requireContext(), binding.toolbar, menu, getToolbarBackgroundColor(binding.toolbar));
}
@Override
@ -462,26 +480,32 @@ public class FoldersFragment extends AbsMainActivityFragment
private void checkForPadding() {
final int count = adapter.getItemCount();
final MarginLayoutParams params = (MarginLayoutParams) coordinatorLayout.getLayoutParams();
params.bottomMargin =
count > 0 && !MusicPlayerRemote.getPlayingQueue().isEmpty()
? DensityUtil.dip2px(requireContext(), 104f)
: DensityUtil.dip2px(requireContext(), 54f);
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() {
emojiText.setText(getEmojiByUnicode(0x1F631));
if (empty != null) {
empty.setVisibility(
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() {
return breadCrumbs != null && breadCrumbs.size() > 0
? breadCrumbs.getCrumb(breadCrumbs.getActiveIndex())
: null;
if (binding != null) {
return binding.breadCrumbs.size() > 0
? binding.breadCrumbs.getCrumb(binding.breadCrumbs.getActiveIndex())
: null;
}
return null;
}
private String getEmojiByUnicode(int unicode) {
@ -492,21 +516,11 @@ public class FoldersFragment extends AbsMainActivityFragment
return fileComparator;
}
private void initViews(View view) {
coordinatorLayout = view.findViewById(R.id.coordinatorLayout);
recyclerView = view.findViewById(R.id.recyclerView);
breadCrumbs = view.findViewById(R.id.breadCrumbs);
empty = view.findViewById(android.R.id.empty);
emojiText = view.findViewById(R.id.emptyEmoji);
toolbar = view.findViewById(R.id.toolbar);
appNameText = view.findViewById(R.id.appNameText);
}
private void saveScrollPosition() {
BreadCrumbLayout.Crumb crumb = getActiveCrumb();
if (crumb != null) {
crumb.setScrollPosition(
((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition());
((LinearLayoutManager) binding.recyclerView.getLayoutManager()).findFirstVisibleItemPosition());
}
}
@ -529,46 +543,39 @@ public class FoldersFragment extends AbsMainActivityFragment
if (crumb == null) {
return;
}
saveScrollPosition();
breadCrumbs.setActiveOrAdd(crumb, false);
if (addToHistory) {
breadCrumbs.addHistory(crumb);
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);
}
LoaderManager.getInstance(this).restartLoader(LOADER_ID, null, this);
}
private void setUpAdapter() {
adapter =
new SongFileAdapter(getMainActivity(), new LinkedList<>(), R.layout.item_list, this, this);
adapter.registerAdapterDataObserver(
new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
super.onChanged();
checkIsEmpty();
checkForPadding();
}
});
recyclerView.setAdapter(adapter);
checkIsEmpty();
switchToFileAdapter();
}
private void setUpAppbarColor() {
breadCrumbs.setActivatedContentColor(
binding.breadCrumbs.setActivatedContentColor(
ATHUtil.INSTANCE.resolveColor(requireContext(), android.R.attr.textColorPrimary));
breadCrumbs.setDeactivatedContentColor(
binding.breadCrumbs.setDeactivatedContentColor(
ATHUtil.INSTANCE.resolveColor(requireContext(), android.R.attr.textColorSecondary));
}
private void setUpBreadCrumbs() {
breadCrumbs.setCallback(this);
binding.breadCrumbs.setCallback(this);
}
private void setUpRecyclerView() {
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
FastScroller fastScroller = ThemedFastScroller.INSTANCE.create(recyclerView);
recyclerView.setOnApplyWindowInsetsListener(
new ScrollingViewOnApplyWindowInsetsListener(recyclerView, fastScroller));
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) {
@ -580,16 +587,22 @@ public class FoldersFragment extends AbsMainActivityFragment
private void updateAdapter(@NonNull List<File> files) {
adapter.swapDataSet(files);
BreadCrumbLayout.Crumb crumb = getActiveCrumb();
if (crumb != null && recyclerView != null) {
((LinearLayoutManager) recyclerView.getLayoutManager())
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 WeakReference<OnPathsListedCallback> onPathsListedCallbackWeakReference;
private final WeakReference<OnPathsListedCallback> onPathsListedCallbackWeakReference;
public ListPathsAsyncTask(Context context, OnPathsListedCallback callback) {
super(context);
@ -679,7 +692,7 @@ public class FoldersFragment extends AbsMainActivityFragment
private static class AsyncFileLoader extends WrappedAsyncTaskLoader<List<File>> {
private WeakReference<FoldersFragment> fragmentWeakReference;
private final WeakReference<FoldersFragment> fragmentWeakReference;
AsyncFileLoader(FoldersFragment foldersFragment) {
super(foldersFragment.requireActivity());
@ -710,8 +723,8 @@ public class FoldersFragment extends AbsMainActivityFragment
extends ListingFilesDialogAsyncTask<ListSongsAsyncTask.LoadingInfo, Void, List<Song>> {
private final Object extra;
private WeakReference<OnSongsListedCallback> callbackWeakReference;
private WeakReference<Context> contextWeakReference;
private final WeakReference<OnSongsListedCallback> callbackWeakReference;
private final WeakReference<Context> contextWeakReference;
ListSongsAsyncTask(Context context, Object extra, OnSongsListedCallback callback) {
super(context);
@ -823,4 +836,107 @@ public class FoldersFragment extends AbsMainActivityFragment
.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

@ -14,28 +14,26 @@
*/
package code.name.monkey.retromusic.fragments.genres
import android.graphics.Color
import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import androidx.core.view.ViewCompat
import androidx.core.view.doOnPreDraw
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.song.SongAdapter
import code.name.monkey.retromusic.databinding.FragmentPlaylistDetailBinding
import code.name.monkey.retromusic.extensions.dipToPix
import code.name.monkey.retromusic.extensions.resolveColor
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.transition.MaterialArcMotion
import com.google.android.material.transition.MaterialContainerTransform
import kotlinx.android.synthetic.main.fragment_playlist_detail.*
import com.google.android.material.transition.MaterialSharedAxis
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
import java.util.*
@ -47,37 +45,33 @@ class GenreDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playlist_
}
private lateinit var genre: Genre
private lateinit var songAdapter: SongAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sharedElementEnterTransition = MaterialContainerTransform().apply {
drawingViewId = R.id.fragment_container
duration = 300L
scrimColor = Color.TRANSPARENT
setAllContainerColors(requireContext().resolveColor(R.attr.colorSurface))
setPathMotion(MaterialArcMotion())
}
}
private var _binding: FragmentPlaylistDetailBinding? = null
private val binding get() = _binding!!
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true).addTarget(view)
returnTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
_binding = FragmentPlaylistDetailBinding.bind(view)
setHasOptionsMenu(true)
mainActivity.setBottomBarVisibility(false)
mainActivity.addMusicServiceEventListener(detailsViewModel)
mainActivity.setSupportActionBar(toolbar)
ViewCompat.setTransitionName(container, "genre")
mainActivity.setSupportActionBar(binding.toolbar)
ViewCompat.setTransitionName(binding.container, "genre")
genre = arguments.extraGenre
toolbar?.title = arguments.extraGenre.name
binding.toolbar.title = arguments.extraGenre.name
setupRecyclerView()
detailsViewModel.getSongs().observe(viewLifecycleOwner, {
songs(it)
})
postponeEnterTransition()
view.doOnPreDraw {
startPostponedEnterTransition()
}
}
private fun setupRecyclerView() {
songAdapter = SongAdapter(requireActivity(), ArrayList(), R.layout.item_list, null)
recyclerView.apply {
binding.recyclerView.apply {
itemAnimator = DefaultItemAnimator()
layoutManager = LinearLayoutManager(requireContext())
adapter = songAdapter
@ -91,7 +85,7 @@ class GenreDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playlist_
}
fun songs(songs: List<Song>) {
progressIndicator.hide()
binding.progressIndicator.hide()
if (songs.isNotEmpty()) songAdapter.swapDataSet(songs)
else songAdapter.swapDataSet(emptyList())
}
@ -102,13 +96,13 @@ class GenreDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playlist_
private fun checkIsEmpty() {
checkForPadding()
emptyEmoji.text = getEmojiByUnicode(0x1F631)
empty?.visibility = if (songAdapter.itemCount == 0) View.VISIBLE else View.GONE
binding.emptyEmoji.text = getEmojiByUnicode(0x1F631)
binding.empty.visibility = if (songAdapter.itemCount == 0) View.VISIBLE else View.GONE
}
private fun checkForPadding() {
val height = dipToPix(52f).toInt()
recyclerView.setPadding(0, 0, 0, height)
binding.recyclerView.setPadding(0, 0, 0, height)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
@ -119,4 +113,9 @@ class GenreDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playlist_
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return GenreMenuHelper.handleMenuClick(requireActivity(), genre, item)
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

View File

@ -60,4 +60,5 @@ class GenreDetailsViewModel(
override fun onPlayStateChanged() {}
override fun onRepeatModeChanged() {}
override fun onShuffleModeChanged() {}
override fun onFavoriteStateChanged() {}
}

View File

@ -15,25 +15,31 @@
package code.name.monkey.retromusic.fragments.genres
import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import androidx.core.os.bundleOf
import androidx.lifecycle.Observer
import androidx.navigation.fragment.FragmentNavigatorExtras
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.GridLayoutManager
import 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.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 com.google.android.material.transition.MaterialElevationScale
import code.name.monkey.retromusic.util.RetroUtil
import com.google.android.gms.cast.framework.CastButtonFactory
import com.google.android.material.transition.MaterialSharedAxis
class GenresFragment : AbsRecyclerViewFragment<GenreAdapter, LinearLayoutManager>(),
class
GenresFragment : AbsRecyclerViewFragment<GenreAdapter, LinearLayoutManager>(),
IGenreClickListener {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
libraryViewModel.getGenre().observe(viewLifecycleOwner, Observer {
libraryViewModel.getGenre().observe(viewLifecycleOwner, {
if (it.isNotEmpty())
adapter?.swapDataSet(it)
else
@ -42,14 +48,37 @@ class GenresFragment : AbsRecyclerViewFragment<GenreAdapter, LinearLayoutManager
}
override fun createLayoutManager(): LinearLayoutManager {
return LinearLayoutManager(activity)
return if (RetroUtil.isLandscape()) {
GridLayoutManager(activity, 4)
} else {
GridLayoutManager(activity, 2)
}
}
override fun createAdapter(): GenreAdapter {
val dataSet = if (adapter == null) ArrayList() else adapter!!.dataSet
return GenreAdapter(requireActivity(), dataSet, R.layout.item_list_no_image, this)
return GenreAdapter(requireActivity(), dataSet, R.layout.item_genre, this)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
menu.removeItem(R.id.action_grid_size)
menu.removeItem(R.id.action_layout_type)
menu.removeItem(R.id.action_sort_order)
menu.findItem(R.id.action_settings).setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
//Setting up cast button
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.action_cast)
}
override fun onResume() {
super.onResume()
libraryViewModel.forceReload(ReloadType.Genres)
}
override val titleRes: Int
get() = R.string.genres
override val emptyMessage: Int
get() = R.string.no_genres
@ -64,19 +93,13 @@ class GenresFragment : AbsRecyclerViewFragment<GenreAdapter, LinearLayoutManager
}
override fun onClickGenre(genre: Genre, view: View) {
exitTransition = MaterialElevationScale(false).apply {
duration = 300L
}
reenterTransition = MaterialElevationScale(true).apply {
duration = 300L
}
exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true).addTarget(requireView())
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
findNavController().navigate(
R.id.genreDetailsFragment,
bundleOf(EXTRA_GENRE to genre),
null,
FragmentNavigatorExtras(
view to "genre"
)
null
)
}
}

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