Merge remote-tracking branch 'retro/dev' into dev
# Conflicts: # .github/ISSUE_TEMPLATE/bug_report.md # README.md # app/build.gradle # app/src/main/AndroidManifest.xml # app/src/main/java/code/name/monkey/retromusic/App.kt # app/src/main/java/code/name/monkey/retromusic/Constants.kt # app/src/main/java/code/name/monkey/retromusic/activities/LyricsActivity.kt # app/src/main/java/code/name/monkey/retromusic/activities/PermissionActivity.kt # app/src/main/java/code/name/monkey/retromusic/activities/PlayingQueueActivity.kt # app/src/main/java/code/name/monkey/retromusic/activities/PurchaseActivity.kt # app/src/main/java/code/name/monkey/retromusic/activities/SettingsActivity.kt # app/src/main/java/code/name/monkey/retromusic/activities/SupportDevelopmentActivity.kt # app/src/main/java/code/name/monkey/retromusic/activities/base/AbsBaseActivity.kt # app/src/main/java/code/name/monkey/retromusic/activities/bugreport/model/DeviceInfo.java # app/src/main/java/code/name/monkey/retromusic/dialogs/CreatePlaylistDialog.kt # app/src/main/java/code/name/monkey/retromusic/extensions/ColorExtensions.kt # app/src/main/java/code/name/monkey/retromusic/extensions/DrawableExtensions.kt # app/src/main/java/code/name/monkey/retromusic/fragments/DetailListFragment.kt # app/src/main/java/code/name/monkey/retromusic/fragments/artists/ArtistDetailsFragment.kt # app/src/main/java/code/name/monkey/retromusic/fragments/base/AbsRecyclerViewFragment.kt # app/src/main/java/code/name/monkey/retromusic/fragments/folder/FoldersFragment.java # app/src/main/java/code/name/monkey/retromusic/fragments/home/HomeFragment.kt # app/src/main/java/code/name/monkey/retromusic/fragments/library/LibraryFragment.kt # app/src/main/java/code/name/monkey/retromusic/fragments/player/full/FullPlaybackControlsFragment.kt # app/src/main/java/code/name/monkey/retromusic/fragments/settings/AbsSettingsFragment.kt # app/src/main/java/code/name/monkey/retromusic/fragments/settings/MainSettingsFragment.kt # app/src/main/java/code/name/monkey/retromusic/model/Artist.kt # app/src/main/java/code/name/monkey/retromusic/model/Song.kt # app/src/main/java/code/name/monkey/retromusic/preferences/AlbumCoverStylePreferenceDialog.kt # app/src/main/java/code/name/monkey/retromusic/preferences/BlacklistPreferenceDialog.kt # app/src/main/java/code/name/monkey/retromusic/preferences/NowPlayingScreenPreferenceDialog.kt # app/src/main/java/code/name/monkey/retromusic/repository/AlbumRepository.kt # app/src/main/java/code/name/monkey/retromusic/service/MusicService.java # app/src/main/java/code/name/monkey/retromusic/util/AppRater.kt # app/src/main/java/code/name/monkey/retromusic/util/NavigationUtil.java # app/src/main/java/code/name/monkey/retromusic/util/PremiumShow.kt # app/src/main/java/code/name/monkey/retromusic/util/RingtoneManager.kt # app/src/main/java/code/name/monkey/retromusic/util/SAFUtil.java # app/src/main/res/layout-sw600dp/item_list_no_image.xml # app/src/main/res/layout/activity_donation.xml # app/src/main/res/layout/activity_pro_version.xml # app/src/main/res/layout/fragment_blur.xml # app/src/main/res/layout/fragment_classic_player.xml # app/src/main/res/layout/fragment_color_player.xml # app/src/main/res/layout/fragment_fit.xml # app/src/main/res/layout/fragment_flat_player.xml # app/src/main/res/layout/fragment_full.xml # app/src/main/res/layout/fragment_gradient_player.xml # app/src/main/res/layout/fragment_home_player.xml # app/src/main/res/layout/fragment_peak_player.xml # app/src/main/res/layout/fragment_plain_player.xml # app/src/main/res/layout/fragment_simple_player.xml # app/src/main/res/layout/fragment_tiny_player.xml # app/src/main/res/layout/item_storage.xml # app/src/main/res/layout/sliding_music_panel_layout.xml # app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml # app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml # app/src/main/res/values-fa-rIR/strings.xml
This commit is contained in:
commit
4df292bddf
925 changed files with 37703 additions and 63683 deletions
|
@ -13,14 +13,19 @@
|
|||
*/
|
||||
package code.name.monkey.retromusic
|
||||
|
||||
import androidx.multidex.MultiDexApplication
|
||||
import android.app.Application
|
||||
import cat.ereza.customactivityoncrash.config.CaocConfig
|
||||
import code.name.monkey.appthemehelper.ThemeStore
|
||||
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||
import code.name.monkey.retromusic.activities.ErrorActivity
|
||||
import code.name.monkey.retromusic.appshortcuts.DynamicShortcutManager
|
||||
import code.name.monkey.retromusic.helper.WallpaperAccentManager
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.core.context.startKoin
|
||||
|
||||
class App : MultiDexApplication() {
|
||||
class App : Application() {
|
||||
|
||||
private val wallpaperAccentManager = WallpaperAccentManager(this)
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
@ -37,9 +42,18 @@ class App : MultiDexApplication() {
|
|||
.coloredNavigationBar(true)
|
||||
.commit()
|
||||
}
|
||||
wallpaperAccentManager.init()
|
||||
|
||||
if (VersionUtils.hasNougatMR())
|
||||
DynamicShortcutManager(this).initDynamicShortcuts()
|
||||
|
||||
// setting Error activity
|
||||
CaocConfig.Builder.create().errorActivity(ErrorActivity::class.java).apply()
|
||||
}
|
||||
|
||||
override fun onTerminate() {
|
||||
super.onTerminate()
|
||||
wallpaperAccentManager.release()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -20,7 +20,8 @@ import android.provider.MediaStore
|
|||
object Constants {
|
||||
const val RATE_ON_GOOGLE_PLAY =
|
||||
"https://play.google.com/store/apps/details?id=code.name.monkey.retromusic"
|
||||
const val TRANSLATE = "https://github.com/h4h13/RetroMusicPlayer"
|
||||
const val TRANSLATE = "https://crowdin.com/project/retromusicplayer"
|
||||
const val WEBSITE = "https://retromusic.app"
|
||||
const val GITHUB_PROJECT = "https://github.com/MuntashirAkon/Metro"
|
||||
const val TELEGRAM_CHANGE_LOG = "https://t.me/retromusiclog"
|
||||
const val USER_PROFILE = "profile.jpg"
|
||||
|
@ -28,30 +29,34 @@ object Constants {
|
|||
const val APP_INSTAGRAM_LINK = "https://www.instagram.com/retromusicapp/"
|
||||
const val APP_TELEGRAM_LINK = "https://t.me/retromusicapp/"
|
||||
const val APP_TWITTER_LINK = "https://twitter.com/retromusicapp"
|
||||
const val FAQ_LINK = "https://github.com/h4h13/RetroMusicPlayer/blob/master/FAQ.md"
|
||||
const val FAQ_LINK = "https://github.com/RetroMusicPlayer/RetroMusicPlayer/blob/master/FAQ.md"
|
||||
const val PINTEREST = "https://in.pinterest.com/retromusicapp/"
|
||||
const val AUDIO_SCROBBLER_URL = "https://ws.audioscrobbler.com/2.0/"
|
||||
|
||||
const val IS_MUSIC =
|
||||
MediaStore.Audio.AudioColumns.IS_MUSIC + "=1" + " AND " + MediaStore.Audio.AudioColumns.TITLE + " != ''"
|
||||
|
||||
const val DATA = "_data"
|
||||
|
||||
@Suppress("Deprecation")
|
||||
val baseProjection = arrayOf(
|
||||
BaseColumns._ID, // 0
|
||||
MediaStore.Audio.AudioColumns.TITLE, // 1
|
||||
MediaStore.Audio.AudioColumns.TRACK, // 2
|
||||
MediaStore.Audio.AudioColumns.YEAR, // 3
|
||||
MediaStore.Audio.AudioColumns.DURATION, // 4
|
||||
MediaStore.Audio.AudioColumns.DATA, // 5
|
||||
DATA, // 5
|
||||
MediaStore.Audio.AudioColumns.DATE_MODIFIED, // 6
|
||||
MediaStore.Audio.AudioColumns.ALBUM_ID, // 7
|
||||
MediaStore.Audio.AudioColumns.ALBUM, // 8
|
||||
MediaStore.Audio.AudioColumns.ARTIST_ID, // 9
|
||||
MediaStore.Audio.AudioColumns.ARTIST, // 10
|
||||
MediaStore.Audio.AudioColumns.COMPOSER, // 11
|
||||
"album_artist" // 12
|
||||
ALBUM_ARTIST // 12
|
||||
)
|
||||
const val NUMBER_OF_TOP_TRACKS = 99
|
||||
}
|
||||
|
||||
const val EXTRA_PLAYLIST_TYPE = "type"
|
||||
const val EXTRA_GENRE = "extra_genre"
|
||||
const val EXTRA_PLAYLIST = "extra_playlist"
|
||||
|
@ -70,7 +75,7 @@ const val NOW_PLAYING_SCREEN_ID = "now_playing_screen_id"
|
|||
const val CAROUSEL_EFFECT = "carousel_effect"
|
||||
const val COLORED_NOTIFICATION = "colored_notification"
|
||||
const val CLASSIC_NOTIFICATION = "classic_notification"
|
||||
const val GAP_LESS_PLAYBACK = "gap_less_playback"
|
||||
const val GAP_LESS_PLAYBACK = "gapless_playback"
|
||||
const val ALBUM_ART_ON_LOCK_SCREEN = "album_art_on_lock_screen"
|
||||
const val BLURRED_ALBUM_ART = "blurred_album_art"
|
||||
const val NEW_BLUR_AMOUNT = "new_blur_amount"
|
||||
|
@ -116,6 +121,8 @@ const val ALBUM_GRID_SIZE_LAND = "album_grid_size_land"
|
|||
const val SONG_GRID_SIZE_LAND = "song_grid_size_land"
|
||||
const val ARTIST_GRID_SIZE = "artist_grid_size"
|
||||
const val ARTIST_GRID_SIZE_LAND = "artist_grid_size_land"
|
||||
const val PLAYLIST_GRID_SIZE = "playlist_grid_size"
|
||||
const val PLAYLIST_GRID_SIZE_LAND = "playlist_grid_size_land"
|
||||
const val COLORED_APP_SHORTCUTS = "colored_app_shortcuts"
|
||||
const val AUDIO_DUCKING = "audio_ducking"
|
||||
const val LAST_ADDED_CUTOFF = "last_added_interval"
|
||||
|
@ -128,7 +135,9 @@ 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 ARTIST_DETAIL_SONG_SORT_ORDER = "artist_detail_song_sort_order"
|
||||
const val LYRICS_OPTIONS = "lyrics_tab_position"
|
||||
const val CHOOSE_EQUALIZER = "choose_equalizer"
|
||||
const val EQUALIZER = "equalizer"
|
||||
|
@ -137,3 +146,25 @@ 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"
|
||||
const val MATERIAL_YOU = "material_you"
|
||||
const val SNOWFALL = "snowfall"
|
||||
const val LYRICS_TYPE = "lyrics_type"
|
||||
const val PLAYBACK_SPEED = "playback_speed"
|
||||
const val PLAYBACK_PITCH = "playback_pitch"
|
||||
const val CUSTOM_FONT = "custom_font"
|
||||
const val APPBAR_MODE = "appbar_mode"
|
||||
const val WALLPAPER_ACCENT = "wallpaper_accent"
|
||||
const val SCREEN_ON_LYRICS = "screen_on_lyrics"
|
||||
const val CIRCLE_PLAY_BUTTON = "circle_play_button"
|
||||
const val SWIPE_ANYWHERE_NOW_PLAYING = "swipe_anywhere_now_playing"
|
||||
const val PAUSE_HISTORY = "pause_history"
|
||||
const val MANAGE_AUDIO_FOCUS = "manage_audio_focus"
|
||||
const val SWIPE_DOWN_DISMISS = "swipe_to_dismiss"
|
|
@ -1,41 +0,0 @@
|
|||
package code.name.monkey.retromusic;
|
||||
|
||||
import android.content.Context;
|
||||
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 java.util.Locale;
|
||||
|
||||
public class LanguageContextWrapper extends ContextWrapper {
|
||||
|
||||
public LanguageContextWrapper(Context base) {
|
||||
super(base);
|
||||
}
|
||||
|
||||
public static LanguageContextWrapper wrap(Context context, Locale newLocale) {
|
||||
Resources res = context.getResources();
|
||||
Configuration configuration = res.getConfiguration();
|
||||
|
||||
if (VersionUtils.INSTANCE.hasNougatMR()) {
|
||||
configuration.setLocale(newLocale);
|
||||
|
||||
LocaleList localeList = new LocaleList(newLocale);
|
||||
LocaleList.setDefault(localeList);
|
||||
configuration.setLocales(localeList);
|
||||
|
||||
context = context.createConfigurationContext(configuration);
|
||||
|
||||
} else if (VersionUtils.INSTANCE.hasLollipop()) {
|
||||
configuration.setLocale(newLocale);
|
||||
context = context.createConfigurationContext(configuration);
|
||||
|
||||
} else {
|
||||
configuration.locale = newLocale;
|
||||
res.updateConfiguration(configuration, res.getDisplayMetrics());
|
||||
}
|
||||
|
||||
return new LanguageContextWrapper(context);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package code.name.monkey.retromusic
|
||||
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.os.LocaleList
|
||||
import code.name.monkey.appthemehelper.util.VersionUtils.hasNougatMR
|
||||
import com.google.android.gms.common.annotation.KeepName
|
||||
import java.util.*
|
||||
|
||||
class LanguageContextWrapper(base: Context?) : ContextWrapper(base) {
|
||||
companion object {
|
||||
@KeepName
|
||||
fun wrap(context: Context?, newLocale: Locale?): LanguageContextWrapper {
|
||||
if (context == null) return LanguageContextWrapper(context)
|
||||
val configuration = context.resources.configuration
|
||||
if (hasNougatMR()) {
|
||||
configuration.setLocale(newLocale)
|
||||
val localeList = LocaleList(newLocale)
|
||||
LocaleList.setDefault(localeList)
|
||||
configuration.setLocales(localeList)
|
||||
} else {
|
||||
configuration.setLocale(newLocale)
|
||||
}
|
||||
return LanguageContextWrapper(context.createConfigurationContext(configuration))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,6 +3,8 @@ 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.cast.RetroWebServer
|
||||
import code.name.monkey.retromusic.db.BlackListStoreDao
|
||||
import code.name.monkey.retromusic.db.BlackListStoreEntity
|
||||
import code.name.monkey.retromusic.db.PlaylistWithSongs
|
||||
|
@ -85,10 +87,26 @@ 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
|
||||
}
|
||||
single {
|
||||
RetroWebServer(get())
|
||||
}
|
||||
}
|
||||
private val dataModule = module {
|
||||
single {
|
||||
|
@ -167,10 +185,11 @@ private val viewModules = module {
|
|||
)
|
||||
}
|
||||
|
||||
viewModel { (artistId: Long) ->
|
||||
viewModel { (artistId: Long?, artistName: String?) ->
|
||||
ArtistDetailsViewModel(
|
||||
get(),
|
||||
artistId
|
||||
artistId,
|
||||
artistName
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -189,4 +208,4 @@ private val viewModules = module {
|
|||
}
|
||||
}
|
||||
|
||||
val appModules = listOf(mainModule, dataModule, viewModules, networkModule, roomModule)
|
||||
val appModules = listOf(mainModule, dataModule, autoModule, viewModules, networkModule, roomModule)
|
|
@ -1,46 +0,0 @@
|
|||
package code.name.monkey.retromusic
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
class PeekingLinearLayoutManager : LinearLayoutManager {
|
||||
@JvmOverloads
|
||||
constructor(
|
||||
context: Context?,
|
||||
@RecyclerView.Orientation orientation: Int = RecyclerView.VERTICAL,
|
||||
reverseLayout: Boolean = false
|
||||
) : super(context, orientation, reverseLayout)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(
|
||||
context,
|
||||
attrs,
|
||||
defStyleAttr,
|
||||
defStyleRes
|
||||
)
|
||||
|
||||
override fun generateDefaultLayoutParams() =
|
||||
scaledLayoutParams(super.generateDefaultLayoutParams())
|
||||
|
||||
override fun generateLayoutParams(lp: ViewGroup.LayoutParams?) =
|
||||
scaledLayoutParams(super.generateLayoutParams(lp))
|
||||
|
||||
override fun generateLayoutParams(c: Context?, attrs: AttributeSet?) =
|
||||
scaledLayoutParams(super.generateLayoutParams(c, attrs))
|
||||
|
||||
private fun scaledLayoutParams(layoutParams: RecyclerView.LayoutParams) =
|
||||
layoutParams.apply {
|
||||
when (orientation) {
|
||||
HORIZONTAL -> width = (horizontalSpace * ratio).toInt()
|
||||
VERTICAL -> height = (verticalSpace * ratio).toInt()
|
||||
}
|
||||
}
|
||||
|
||||
private val horizontalSpace get() = width - paddingStart - paddingEnd
|
||||
|
||||
private val verticalSpace get() = height - paddingTop - paddingBottom
|
||||
|
||||
private val ratio = 0.8f // change to 0.7f for 70%
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
package code.name.monkey.retromusic;
|
||||
|
||||
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> {
|
||||
|
||||
private static final String TAG = "RetroBottomSheetBehavior";
|
||||
|
||||
private boolean allowDragging = true;
|
||||
|
||||
public RetroBottomSheetBehavior() {}
|
||||
|
||||
public RetroBottomSheetBehavior(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public void setAllowDragging(boolean allowDragging) {
|
||||
this.allowDragging = allowDragging;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(
|
||||
@NotNull CoordinatorLayout parent, @NotNull V child, @NotNull MotionEvent event) {
|
||||
if (!allowDragging) {
|
||||
return false;
|
||||
}
|
||||
return super.onInterceptTouchEvent(parent, child, event);
|
||||
}
|
||||
}
|
|
@ -15,32 +15,37 @@
|
|||
package code.name.monkey.retromusic.activities
|
||||
|
||||
import android.animation.ObjectAnimator
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.graphics.PorterDuff
|
||||
import android.os.Bundle
|
||||
import android.view.animation.LinearInterpolator
|
||||
import android.widget.SeekBar
|
||||
import code.name.monkey.appthemehelper.ThemeStore
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
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.db.toSongEntity
|
||||
import code.name.monkey.retromusic.extensions.accentColor
|
||||
import code.name.monkey.retromusic.extensions.drawAboveSystemBars
|
||||
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
|
||||
import code.name.monkey.retromusic.glide.BlurTransformation
|
||||
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
|
||||
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.helper.MusicPlayerRemote
|
||||
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
|
||||
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper.Callback
|
||||
import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler
|
||||
import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener
|
||||
import code.name.monkey.retromusic.model.Song
|
||||
import code.name.monkey.retromusic.repository.RealRepository
|
||||
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
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
|
||||
/**
|
||||
* Created by hemanths on 2020-02-02.
|
||||
|
@ -48,21 +53,24 @@ import kotlinx.coroutines.withContext
|
|||
|
||||
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
|
||||
private val repository: RealRepository by inject()
|
||||
|
||||
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 {
|
||||
lastPlaybackControlsColor = accentColor()
|
||||
binding.close.setOnClickListener {
|
||||
onBackPressed()
|
||||
}
|
||||
binding.repeatButton.drawAboveSystemBars()
|
||||
}
|
||||
|
||||
private fun setUpMusicControllers() {
|
||||
|
@ -75,26 +83,37 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
|
|||
}
|
||||
|
||||
private fun setupFavouriteToggle() {
|
||||
songFavourite.setOnClickListener {
|
||||
MusicUtil.toggleFavorite(
|
||||
this@DriveModeActivity,
|
||||
MusicPlayerRemote.currentSong
|
||||
)
|
||||
binding.songFavourite.setOnClickListener {
|
||||
toggleFavorite(MusicPlayerRemote.currentSong)
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleFavourite() {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val isFavourite =
|
||||
MusicUtil.isFavorite(this@DriveModeActivity, MusicPlayerRemote.currentSong)
|
||||
private fun toggleFavorite(song: Song) {
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
val playlist = repository.favoritePlaylist()
|
||||
val songEntity = song.toSongEntity(playlist.playListId)
|
||||
val isFavorite = repository.isSongFavorite(song.id)
|
||||
if (isFavorite) {
|
||||
repository.removeSongFromPlaylist(songEntity)
|
||||
} else {
|
||||
repository.insertSongs(listOf(song.toSongEntity(playlist.playListId)))
|
||||
}
|
||||
sendBroadcast(Intent(MusicService.FAVORITE_STATE_CHANGED))
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateFavorite() {
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
val isFavorite: Boolean =
|
||||
repository.isSongFavorite(MusicPlayerRemote.currentSong.id)
|
||||
withContext(Dispatchers.Main) {
|
||||
songFavourite.setImageResource(if (isFavourite) R.drawable.ic_favorite else R.drawable.ic_favorite_border)
|
||||
binding.songFavourite.setImageResource(if (isFavorite) 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)
|
||||
|
@ -118,21 +137,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() {
|
||||
|
@ -156,24 +174,24 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
|
|||
updateSong()
|
||||
updateRepeatState()
|
||||
updateShuffleState()
|
||||
toggleFavourite()
|
||||
updateFavorite()
|
||||
}
|
||||
|
||||
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 +201,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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -203,35 +227,36 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
|
|||
override fun onPlayingMetaChanged() {
|
||||
super.onPlayingMetaChanged()
|
||||
updateSong()
|
||||
toggleFavourite()
|
||||
updateFavorite()
|
||||
}
|
||||
|
||||
override fun onFavoriteStateChanged() {
|
||||
super.onFavoriteStateChanged()
|
||||
updateFavorite()
|
||||
}
|
||||
|
||||
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)
|
||||
.load(RetroGlideExtension.getSongModel(song))
|
||||
.songCoverOptions(song)
|
||||
.transform(BlurTransformation.Builder(this).build())
|
||||
.into(object : RetroMusicColoredTarget(image) {
|
||||
override fun onColorReady(colors: MediaNotificationProcessor) {
|
||||
}
|
||||
})
|
||||
.into(binding.image)
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
package code.name.monkey.retromusic.activities
|
||||
|
||||
import android.os.Bundle
|
||||
import android.widget.Button
|
||||
import android.widget.ImageView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import cat.ereza.customactivityoncrash.CustomActivityOnCrash
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.util.FileUtils.createFile
|
||||
import code.name.monkey.retromusic.util.Share.shareFile
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
class ErrorActivity : AppCompatActivity() {
|
||||
private val dayFormat: DateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
|
||||
private val ReportPrefix = "bug_report-"
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.customactivityoncrash_default_error_activity)
|
||||
|
||||
val restartButton =
|
||||
findViewById<Button>(R.id.customactivityoncrash_error_activity_restart_button)
|
||||
|
||||
val config = CustomActivityOnCrash.getConfigFromIntent(intent)
|
||||
if (config == null) {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
restartButton.setText(R.string.customactivityoncrash_error_activity_restart_app)
|
||||
restartButton.setOnClickListener {
|
||||
CustomActivityOnCrash.restartApplication(
|
||||
this@ErrorActivity,
|
||||
config
|
||||
)
|
||||
}
|
||||
val moreInfoButton =
|
||||
findViewById<Button>(R.id.customactivityoncrash_error_activity_more_info_button)
|
||||
|
||||
moreInfoButton.setOnClickListener { //We retrieve all the error data and show it
|
||||
AlertDialog.Builder(this@ErrorActivity)
|
||||
.setTitle(R.string.customactivityoncrash_error_activity_error_details_title)
|
||||
.setMessage(
|
||||
CustomActivityOnCrash.getAllErrorDetailsFromIntent(
|
||||
this@ErrorActivity,
|
||||
intent
|
||||
)
|
||||
)
|
||||
.setPositiveButton(
|
||||
R.string.customactivityoncrash_error_activity_error_details_close,
|
||||
null
|
||||
)
|
||||
.setNeutralButton(
|
||||
R.string.customactivityoncrash_error_activity_error_details_share
|
||||
) { _, _ ->
|
||||
|
||||
val bugReport = createFile(
|
||||
context = this,
|
||||
"Bug Report",
|
||||
"$ReportPrefix${dayFormat.format(Date())}",
|
||||
CustomActivityOnCrash.getAllErrorDetailsFromIntent(
|
||||
this@ErrorActivity,
|
||||
intent
|
||||
), ".txt"
|
||||
)
|
||||
shareFile(this, bugReport)
|
||||
}
|
||||
.show()
|
||||
}
|
||||
val errorActivityDrawableId = config.errorDrawable
|
||||
val errorImageView =
|
||||
findViewById<ImageView>(R.id.customactivityoncrash_error_activity_image)
|
||||
if (errorActivityDrawableId != null) {
|
||||
errorImageView.setImageResource(
|
||||
errorActivityDrawableId
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,103 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
package code.name.monkey.retromusic.activities;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuItem;
|
||||
import android.webkit.WebView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import 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 {
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
setDrawUnderStatusBar();
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_license);
|
||||
setStatusbarColorAuto();
|
||||
setNavigationbarColorAuto();
|
||||
setLightNavigationBar(true);
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
ToolbarContentTintHelper.colorBackButton(toolbar);
|
||||
toolbar.setBackgroundColor(ATHUtil.INSTANCE.resolveColor(this, R.attr.colorSurface));
|
||||
WebView webView = findViewById(R.id.license);
|
||||
try {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
InputStream json = getAssets().open("oldindex.html");
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(json, StandardCharsets.UTF_8));
|
||||
String str;
|
||||
while ((str = in.readLine()) != null) {
|
||||
buf.append(str);
|
||||
}
|
||||
in.close();
|
||||
|
||||
// Inject color values for WebView body background and links
|
||||
final boolean isDark = ATHUtil.INSTANCE.isWindowBackgroundDark(this);
|
||||
final String backgroundColor =
|
||||
colorToCSS(
|
||||
ATHUtil.INSTANCE.resolveColor(
|
||||
this, R.attr.colorSurface, Color.parseColor(isDark ? "#424242" : "#ffffff")));
|
||||
final String contentColor = colorToCSS(Color.parseColor(isDark ? "#ffffff" : "#000000"));
|
||||
final String changeLog =
|
||||
buf.toString()
|
||||
.replace(
|
||||
"{style-placeholder}",
|
||||
String.format(
|
||||
"body { background-color: %s; color: %s; }", backgroundColor, contentColor))
|
||||
.replace("{link-color}", colorToCSS(ThemeStore.Companion.accentColor(this)))
|
||||
.replace(
|
||||
"{link-color-active}",
|
||||
colorToCSS(
|
||||
ColorUtil.INSTANCE.lightenColor(ThemeStore.Companion.accentColor(this))));
|
||||
|
||||
webView.loadData(changeLog, "text/html", "UTF-8");
|
||||
} catch (Throwable e) {
|
||||
webView.loadData(
|
||||
"<h1>Unable to load</h1><p>" + e.getLocalizedMessage() + "</p>", "text/html", "UTF-8");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
onBackPressed();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private String colorToCSS(int color) {
|
||||
return String.format(
|
||||
"rgb(%d, %d, %d)",
|
||||
Color.red(color),
|
||||
Color.green(color),
|
||||
Color.blue(color)); // on API 29, WebView doesn't load with hex colors
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*/
|
||||
package code.name.monkey.retromusic.activities
|
||||
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import code.name.monkey.appthemehelper.util.ATHUtil.isWindowBackgroundDark
|
||||
import code.name.monkey.appthemehelper.util.ColorUtil.lightenColor
|
||||
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
|
||||
import code.name.monkey.retromusic.activities.base.AbsThemeActivity
|
||||
import code.name.monkey.retromusic.databinding.ActivityLicenseBinding
|
||||
import code.name.monkey.retromusic.extensions.accentColor
|
||||
import code.name.monkey.retromusic.extensions.drawAboveSystemBars
|
||||
import code.name.monkey.retromusic.extensions.surfaceColor
|
||||
import java.io.BufferedReader
|
||||
import java.io.InputStreamReader
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
/** Created by hemanths on 2019-09-27. */
|
||||
class LicenseActivity : AbsThemeActivity() {
|
||||
private lateinit var binding: ActivityLicenseBinding
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityLicenseBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
setSupportActionBar(binding.toolbar)
|
||||
ToolbarContentTintHelper.colorBackButton(binding.toolbar)
|
||||
try {
|
||||
val buf = StringBuilder()
|
||||
val json = assets.open("license.html")
|
||||
BufferedReader(InputStreamReader(json, StandardCharsets.UTF_8)).use { br ->
|
||||
var str: String?
|
||||
while (br.readLine().also { str = it } != null) {
|
||||
buf.append(str)
|
||||
}
|
||||
}
|
||||
|
||||
// Inject color values for WebView body background and links
|
||||
val isDark = isWindowBackgroundDark(this)
|
||||
val backgroundColor = colorToCSS(
|
||||
surfaceColor(Color.parseColor(if (isDark) "#424242" else "#ffffff"))
|
||||
)
|
||||
val contentColor = colorToCSS(Color.parseColor(if (isDark) "#ffffff" else "#000000"))
|
||||
val changeLog = buf.toString()
|
||||
.replace(
|
||||
"{style-placeholder}", String.format(
|
||||
"body { background-color: %s; color: %s; }", backgroundColor, contentColor
|
||||
)
|
||||
)
|
||||
.replace("{link-color}", colorToCSS(accentColor()))
|
||||
.replace(
|
||||
"{link-color-active}",
|
||||
colorToCSS(
|
||||
lightenColor(accentColor())
|
||||
)
|
||||
)
|
||||
binding.license.loadData(changeLog, "text/html", "UTF-8")
|
||||
} catch (e: Throwable) {
|
||||
binding.license.loadData(
|
||||
"<h1>Unable to load</h1><p>" + e.localizedMessage + "</p>", "text/html", "UTF-8"
|
||||
)
|
||||
}
|
||||
binding.license.drawAboveSystemBars()
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == android.R.id.home) {
|
||||
onBackPressed()
|
||||
return true
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
private fun colorToCSS(color: Int): String {
|
||||
return String.format(
|
||||
"rgb(%d, %d, %d)",
|
||||
Color.red(color),
|
||||
Color.green(color),
|
||||
Color.blue(color)
|
||||
) // on API 29, WebView doesn't load with hex colors
|
||||
}
|
||||
}
|
|
@ -15,40 +15,38 @@
|
|||
package code.name.monkey.retromusic.activities
|
||||
|
||||
import android.app.KeyguardManager
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.content.getSystemService
|
||||
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.ActivityLockScreenBinding
|
||||
import code.name.monkey.retromusic.extensions.hideStatusBar
|
||||
import code.name.monkey.retromusic.extensions.setTaskDescriptionColorAuto
|
||||
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()
|
||||
setTaskDescriptionColorAuto()
|
||||
setLightNavigationBar(true)
|
||||
|
||||
val config = SlidrConfig.Builder().listener(object : SlidrListener {
|
||||
override fun onSlideStateChanged(state: Int) {
|
||||
|
@ -61,10 +59,10 @@ class LockScreenActivity : AbsMusicServiceActivity() {
|
|||
}
|
||||
|
||||
override fun onSlideClosed(): Boolean {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
if (VersionUtils.hasOreo()) {
|
||||
val keyguardManager =
|
||||
getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
|
||||
keyguardManager.requestDismissKeyguard(this@LockScreenActivity, null)
|
||||
getSystemService<KeyguardManager>()
|
||||
keyguardManager?.requestDismissKeyguard(this@LockScreenActivity, null)
|
||||
}
|
||||
finish()
|
||||
return true
|
||||
|
@ -75,18 +73,19 @@ class LockScreenActivity : AbsMusicServiceActivity() {
|
|||
|
||||
fragment = whichFragment<LockScreenControlsFragment>(R.id.playback_controls_fragment)
|
||||
|
||||
findViewById<View>(R.id.slide).apply {
|
||||
binding.slide.apply {
|
||||
translationY = 100f
|
||||
alpha = 0f
|
||||
ViewCompat.animate(this).translationY(0f).alpha(1f).setDuration(1500).start()
|
||||
animate().translationY(0f).alpha(1f).setDuration(1500).start()
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("Deprecation")
|
||||
private fun lockScreenInit() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||
if (VersionUtils.hasOreoMR1()) {
|
||||
setShowWhenLocked(true)
|
||||
val keyguardManager: KeyguardManager = getSystemService(KeyguardManager::class.java)
|
||||
keyguardManager.requestDismissKeyguard(this, null)
|
||||
val keyguardManager = getSystemService<KeyguardManager>()
|
||||
keyguardManager?.requestDismissKeyguard(this, null)
|
||||
} else {
|
||||
this.window.addFlags(
|
||||
WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or
|
||||
|
@ -107,9 +106,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)
|
||||
}
|
||||
|
|
|
@ -1,153 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.activities
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.WindowManager
|
||||
import androidx.core.view.ViewCompat
|
||||
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.extensions.surfaceColor
|
||||
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.Song
|
||||
import code.name.monkey.retromusic.util.LyricUtil
|
||||
import code.name.monkey.retromusic.util.RetroUtil
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.transition.platform.MaterialContainerTransform
|
||||
import kotlinx.android.synthetic.main.activity_lyrics.*
|
||||
|
||||
class LyricsActivity : AbsMusicServiceActivity(), MusicProgressViewUpdateHelper.Callback {
|
||||
private lateinit var updateHelper: MusicProgressViewUpdateHelper
|
||||
|
||||
private lateinit var song: Song
|
||||
|
||||
private val googleSearchLrcUrl: String
|
||||
get() {
|
||||
var baseUrl = "http://www.google.com/search?"
|
||||
var query = song.title + "+" + song.artistName
|
||||
query = "q=" + query.replace(" ", "+") + " .lrc"
|
||||
baseUrl += query
|
||||
return baseUrl
|
||||
}
|
||||
|
||||
private fun buildContainerTransform(): MaterialContainerTransform {
|
||||
val transform = MaterialContainerTransform()
|
||||
transform.setAllContainerColors(
|
||||
MaterialColors.getColor(findViewById(R.id.container), R.attr.colorSurface)
|
||||
)
|
||||
transform.addTarget(R.id.container)
|
||||
transform.duration = 300
|
||||
return transform
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_lyrics)
|
||||
ViewCompat.setTransitionName(container, "lyrics")
|
||||
setStatusbarColorAuto()
|
||||
setTaskDescriptionColorAuto()
|
||||
setNavigationbarColorAuto()
|
||||
|
||||
setupWakelock()
|
||||
|
||||
toolbar.setBackgroundColor(surfaceColor())
|
||||
ToolbarContentTintHelper.colorBackButton(toolbar)
|
||||
setSupportActionBar(toolbar)
|
||||
|
||||
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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
private fun setupWakelock() {
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
menuInflater.inflate(R.menu.menu_search, menu)
|
||||
return super.onCreateOptionsMenu(menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == android.R.id.home) {
|
||||
finish()
|
||||
return true
|
||||
}
|
||||
if (item.itemId == R.id.action_search) {
|
||||
RetroUtil.openUrl(this, googleSearchLrcUrl)
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
|
@ -20,40 +20,16 @@ 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.TOGGLE_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 code.name.monkey.retromusic.extensions.extra
|
||||
import code.name.monkey.retromusic.extensions.findNavController
|
||||
import androidx.navigation.contains
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
import code.name.monkey.retromusic.*
|
||||
import code.name.monkey.retromusic.activities.base.AbsCastActivity
|
||||
import code.name.monkey.retromusic.databinding.SlidingMusicPanelLayoutBinding
|
||||
import code.name.monkey.retromusic.extensions.*
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||
import code.name.monkey.retromusic.helper.SearchQueryHelper.getSongs
|
||||
import code.name.monkey.retromusic.interfaces.IScrollHelper
|
||||
import code.name.monkey.retromusic.model.CategoryInfo
|
||||
import code.name.monkey.retromusic.model.Song
|
||||
import code.name.monkey.retromusic.repository.PlaylistSongsLoader
|
||||
|
@ -63,22 +39,18 @@ 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()
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
setDrawUnderStatusBar()
|
||||
super.onCreate(savedInstanceState)
|
||||
setStatusbarColorAuto()
|
||||
setNavigationbarColorAuto()
|
||||
setLightNavigationBar(true)
|
||||
setTaskDescriptionColorAuto()
|
||||
hideStatusBar()
|
||||
updateTabs()
|
||||
|
@ -87,6 +59,7 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis
|
|||
if (!hasPermissions()) {
|
||||
findNavController(R.id.fragment_container).navigate(R.id.permissionFragment)
|
||||
}
|
||||
WhatsNewFragment.showChangeLog(this)
|
||||
}
|
||||
|
||||
private fun setupNavigationController() {
|
||||
|
@ -96,23 +69,75 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis
|
|||
|
||||
val categoryInfo: CategoryInfo = PreferenceUtil.libraryCategory.first { it.visible }
|
||||
if (categoryInfo.visible) {
|
||||
navGraph.startDestination = categoryInfo.category.id
|
||||
if (!navGraph.contains(PreferenceUtil.lastTab)) PreferenceUtil.lastTab =
|
||||
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)
|
||||
bottomNavigationView.setupWithNavController(navController)
|
||||
// Scroll Fragment to top
|
||||
bottomNavigationView.setOnItemReselectedListener {
|
||||
currentFragment(R.id.fragment_container).apply {
|
||||
if (this is IScrollHelper) {
|
||||
scrollToTop()
|
||||
}
|
||||
}
|
||||
}
|
||||
navController.addOnDestinationChangedListener { _, destination, _ ->
|
||||
if (destination.id == navGraph.startDestinationId) {
|
||||
currentFragment(R.id.fragment_container)?.enterTransition = null
|
||||
}
|
||||
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, R.id.action_search -> {
|
||||
// Save the last tab
|
||||
if (PreferenceUtil.rememberLastTab) {
|
||||
saveTab(destination.id)
|
||||
}
|
||||
// Show Bottom Navigation Bar
|
||||
setBottomNavVisibility(visible = true, animate = true)
|
||||
}
|
||||
R.id.playing_queue_fragment -> {
|
||||
setBottomNavVisibility(visible = false, hideBottomSheet = true)
|
||||
}
|
||||
else -> setBottomNavVisibility(
|
||||
visible = false,
|
||||
animate = true
|
||||
) // Hide Bottom Navigation Bar
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveTab(id: Int) {
|
||||
PreferenceUtil.lastTab = id
|
||||
}
|
||||
|
||||
override fun onSupportNavigateUp(): Boolean =
|
||||
findNavController(R.id.fragment_container).navigateUp()
|
||||
|
||||
override fun onNewIntent(intent: Intent?) {
|
||||
super.onNewIntent(intent)
|
||||
val expand = intent?.extra<Boolean>(EXPAND_PANEL)?.value ?: false
|
||||
if (expand && PreferenceUtil.isExpandPanel) {
|
||||
fromNotification = true
|
||||
slidingPanel.bringToFront()
|
||||
expandPanel()
|
||||
intent?.removeExtra(EXPAND_PANEL)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
PreferenceUtil.registerOnSharedPreferenceChangedListener(this)
|
||||
val expand = extra<Boolean>(EXPAND_PANEL).value ?: false
|
||||
if (expand && PreferenceUtil.isExpandPanel) {
|
||||
expandPanel()
|
||||
intent.removeExtra(EXPAND_PANEL)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
@ -121,7 +146,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 == TOGGLE_HOME_BANNER || key == TOGGLE_ADD_CONTROLS || key == ALBUM_COVER_STYLE || key == HOME_ARTIST_GRID_STYLE || key == ALBUM_COVER_TRANSFORM || key == DESATURATED_COLOR || key == EXTRA_SONG_INFO || key == TAB_TEXT_MODE || key == LANGUAGE_NAME || key == LIBRARY_CATEGORIES) {
|
||||
if (key == GENERAL_THEME || key == MATERIAL_YOU || key == WALLPAPER_ACCENT || 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 || key == CUSTOM_FONT || key == APPBAR_MODE || key == CIRCLE_PLAY_BUTTON || key == SWIPE_DOWN_DISMISS) {
|
||||
postRecreate()
|
||||
}
|
||||
}
|
||||
|
@ -149,7 +174,7 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis
|
|||
handled = true
|
||||
}
|
||||
if (uri != null && uri.toString().isNotEmpty()) {
|
||||
MusicPlayerRemote.playFromUri(uri)
|
||||
MusicPlayerRemote.playFromUri(this@MainActivity, uri)
|
||||
handled = true
|
||||
} else if (MediaStore.Audio.Playlists.CONTENT_TYPE == mimeType) {
|
||||
val id = parseLongFromIntent(intent, "playlistId", "playlist")
|
||||
|
|
|
@ -14,42 +14,61 @@
|
|||
*/
|
||||
package code.name.monkey.retromusic.activities
|
||||
|
||||
import android.Manifest
|
||||
import android.Manifest.permission.BLUETOOTH_CONNECT
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.ColorStateList
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.text.parseAsHtml
|
||||
import androidx.core.view.isVisible
|
||||
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.extensions.accentBackgroundColor
|
||||
import code.name.monkey.retromusic.extensions.show
|
||||
import code.name.monkey.retromusic.util.RingtoneManager
|
||||
import kotlinx.android.synthetic.main.activity_permission.*
|
||||
import code.name.monkey.retromusic.databinding.ActivityPermissionBinding
|
||||
import code.name.monkey.retromusic.extensions.*
|
||||
|
||||
class PermissionActivity : AbsMusicServiceActivity() {
|
||||
private lateinit var binding: ActivityPermissionBinding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView((R.layout.activity_permission))
|
||||
setStatusbarColorAuto()
|
||||
setNavigationbarColorAuto()
|
||||
setLightNavigationBar(true)
|
||||
binding = ActivityPermissionBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
setStatusBarColorAuto()
|
||||
setTaskDescriptionColorAuto()
|
||||
setupTitle()
|
||||
|
||||
storagePermission.setButtonClick {
|
||||
binding.storagePermission.setButtonClick {
|
||||
requestPermissions()
|
||||
}
|
||||
if (VersionUtils.hasMarshmallow()) audioPermission.show()
|
||||
audioPermission.setButtonClick {
|
||||
if (RingtoneManager.requiresDialog(this@PermissionActivity)) {
|
||||
val intent = Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS)
|
||||
intent.data = Uri.parse("package:" + applicationContext.packageName)
|
||||
startActivity(intent)
|
||||
if (VersionUtils.hasMarshmallow()) {
|
||||
binding.audioPermission.show()
|
||||
binding.audioPermission.setButtonClick {
|
||||
if (!hasAudioPermission()) {
|
||||
val intent = Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS)
|
||||
intent.data = ("package:" + applicationContext.packageName).toUri()
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
finish.accentBackgroundColor()
|
||||
finish.setOnClickListener {
|
||||
|
||||
if (VersionUtils.hasS()) {
|
||||
binding.bluetoothPermission.show()
|
||||
binding.bluetoothPermission.setButtonClick {
|
||||
ActivityCompat.requestPermissions(this,
|
||||
arrayOf(BLUETOOTH_CONNECT),
|
||||
PERMISSION_REQUEST)
|
||||
}
|
||||
}
|
||||
|
||||
binding.finish.accentBackgroundColor()
|
||||
binding.finish.setOnClickListener {
|
||||
if (hasPermissions()) {
|
||||
startActivity(
|
||||
Intent(this, MainActivity::class.java).addFlags(
|
||||
|
@ -63,10 +82,55 @@ class PermissionActivity : AbsMusicServiceActivity() {
|
|||
}
|
||||
|
||||
private fun setupTitle() {
|
||||
val appName = HtmlCompat.fromHtml(
|
||||
"Hello there! <br>Welcome to <b>Metro</b>",
|
||||
HtmlCompat.FROM_HTML_MODE_COMPACT
|
||||
)
|
||||
appNameText.text = appName
|
||||
val appName =
|
||||
getString(R.string.message_welcome,
|
||||
"<b>Metro</b>")
|
||||
.parseAsHtml()
|
||||
binding.appNameText.text = appName
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
binding.finish.isEnabled = hasStoragePermission()
|
||||
if (hasStoragePermission()) {
|
||||
binding.storagePermission.checkImage.isVisible = true
|
||||
binding.storagePermission.checkImage.imageTintList =
|
||||
ColorStateList.valueOf(accentColor())
|
||||
}
|
||||
if (VersionUtils.hasMarshmallow()) {
|
||||
if (hasAudioPermission()) {
|
||||
binding.audioPermission.checkImage.isVisible = true
|
||||
binding.audioPermission.checkImage.imageTintList =
|
||||
ColorStateList.valueOf(accentColor())
|
||||
}
|
||||
}
|
||||
if (VersionUtils.hasS()) {
|
||||
if (hasBluetoothPermission()) {
|
||||
binding.bluetoothPermission.checkImage.isVisible = true
|
||||
binding.bluetoothPermission.checkImage.imageTintList =
|
||||
ColorStateList.valueOf(accentColor())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasStoragePermission(): Boolean {
|
||||
return ActivityCompat.checkSelfPermission(this,
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.S)
|
||||
private fun hasBluetoothPermission(): Boolean {
|
||||
return ActivityCompat.checkSelfPermission(this,
|
||||
BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun hasAudioPermission(): Boolean {
|
||||
return Settings.System.canWrite(this)
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
super.onBackPressed()
|
||||
finishAffinity()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,202 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.activities
|
||||
|
||||
import android.content.res.ColorStateList
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import code.name.monkey.appthemehelper.util.ColorUtil
|
||||
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
||||
import com.h6ah4i.android.widget.advrecyclerview.animator.DraggableItemAnimator
|
||||
import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager
|
||||
import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager
|
||||
import com.h6ah4i.android.widget.advrecyclerview.touchguard.RecyclerViewTouchActionGuardManager
|
||||
import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils
|
||||
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.extensions.accentColor
|
||||
import code.name.monkey.retromusic.extensions.surfaceColor
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
import code.name.monkey.retromusic.util.ThemedFastScroller
|
||||
import kotlinx.android.synthetic.main.activity_playing_queue.*
|
||||
|
||||
open class PlayingQueueActivity : AbsMusicServiceActivity() {
|
||||
|
||||
private var wrappedAdapter: RecyclerView.Adapter<*>? = null
|
||||
private var recyclerViewDragDropManager: RecyclerViewDragDropManager? = null
|
||||
private var recyclerViewSwipeManager: RecyclerViewSwipeManager? = null
|
||||
private var recyclerViewTouchActionGuardManager: RecyclerViewTouchActionGuardManager? = null
|
||||
private var playingQueueAdapter: PlayingQueueAdapter? = null
|
||||
private lateinit var linearLayoutManager: LinearLayoutManager
|
||||
|
||||
private fun getUpNextAndQueueTime(): String {
|
||||
val duration = MusicPlayerRemote.getQueueDurationMillis(MusicPlayerRemote.position)
|
||||
return MusicUtil.buildInfoString(
|
||||
resources.getString(R.string.up_next),
|
||||
MusicUtil.getReadableDurationString(duration)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
setDrawUnderStatusBar()
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_playing_queue)
|
||||
setStatusbarColorAuto()
|
||||
setNavigationbarColorAuto()
|
||||
setTaskDescriptionColorAuto()
|
||||
setLightNavigationBar(true)
|
||||
|
||||
setupToolbar()
|
||||
setUpRecyclerView()
|
||||
|
||||
clearQueue.setOnClickListener {
|
||||
MusicPlayerRemote.clearQueue()
|
||||
}
|
||||
checkForPadding()
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
android.R.id.home -> {
|
||||
onBackPressed()
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setUpRecyclerView() {
|
||||
recyclerViewTouchActionGuardManager = RecyclerViewTouchActionGuardManager()
|
||||
recyclerViewDragDropManager = RecyclerViewDragDropManager()
|
||||
recyclerViewSwipeManager = RecyclerViewSwipeManager()
|
||||
|
||||
val animator = DraggableItemAnimator()
|
||||
animator.supportsChangeAnimations = false
|
||||
|
||||
playingQueueAdapter = PlayingQueueAdapter(
|
||||
this,
|
||||
MusicPlayerRemote.playingQueue.toMutableList(),
|
||||
MusicPlayerRemote.position,
|
||||
R.layout.item_queue
|
||||
)
|
||||
wrappedAdapter = recyclerViewDragDropManager?.createWrappedAdapter(playingQueueAdapter!!)
|
||||
wrappedAdapter = wrappedAdapter?.let { recyclerViewSwipeManager?.createWrappedAdapter(it) }
|
||||
|
||||
linearLayoutManager = LinearLayoutManager(this)
|
||||
|
||||
recyclerView.layoutManager = linearLayoutManager
|
||||
recyclerView.adapter = wrappedAdapter
|
||||
recyclerView.itemAnimator = animator
|
||||
recyclerViewTouchActionGuardManager?.attachRecyclerView(recyclerView)
|
||||
recyclerViewDragDropManager?.attachRecyclerView(recyclerView)
|
||||
recyclerViewSwipeManager?.attachRecyclerView(recyclerView)
|
||||
linearLayoutManager.scrollToPositionWithOffset(MusicPlayerRemote.position + 1, 0)
|
||||
|
||||
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
super.onScrolled(recyclerView, dx, dy)
|
||||
if (dy > 0) {
|
||||
clearQueue.shrink()
|
||||
} else if (dy < 0) {
|
||||
clearQueue.extend()
|
||||
}
|
||||
}
|
||||
})
|
||||
ThemedFastScroller.create(recyclerView)
|
||||
}
|
||||
|
||||
private fun checkForPadding() {
|
||||
}
|
||||
|
||||
override fun onQueueChanged() {
|
||||
if (MusicPlayerRemote.playingQueue.isEmpty()) {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
checkForPadding()
|
||||
updateQueue()
|
||||
updateCurrentSong()
|
||||
}
|
||||
|
||||
override fun onMediaStoreChanged() {
|
||||
updateQueue()
|
||||
updateCurrentSong()
|
||||
}
|
||||
|
||||
private fun updateCurrentSong() {
|
||||
toolbar.subtitle = getUpNextAndQueueTime()
|
||||
}
|
||||
|
||||
override fun onPlayingMetaChanged() {
|
||||
updateQueuePosition()
|
||||
}
|
||||
|
||||
private fun updateQueuePosition() {
|
||||
playingQueueAdapter?.setCurrent(MusicPlayerRemote.position)
|
||||
resetToCurrentPosition()
|
||||
toolbar.subtitle = getUpNextAndQueueTime()
|
||||
}
|
||||
|
||||
private fun updateQueue() {
|
||||
playingQueueAdapter?.swapDataSet(MusicPlayerRemote.playingQueue, MusicPlayerRemote.position)
|
||||
resetToCurrentPosition()
|
||||
}
|
||||
|
||||
private fun resetToCurrentPosition() {
|
||||
recyclerView.stopScroll()
|
||||
linearLayoutManager.scrollToPositionWithOffset(MusicPlayerRemote.position + 1, 0)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
if (recyclerViewDragDropManager != null) {
|
||||
recyclerViewDragDropManager!!.cancelDrag()
|
||||
}
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
if (recyclerViewDragDropManager != null) {
|
||||
recyclerViewDragDropManager!!.release()
|
||||
recyclerViewDragDropManager = null
|
||||
}
|
||||
if (recyclerViewSwipeManager != null) {
|
||||
recyclerViewSwipeManager?.release()
|
||||
recyclerViewSwipeManager = null
|
||||
}
|
||||
if (wrappedAdapter != null) {
|
||||
WrapperAdapterUtils.releaseAll(wrappedAdapter)
|
||||
wrappedAdapter = null
|
||||
}
|
||||
playingQueueAdapter = null
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun setupToolbar() {
|
||||
toolbar.subtitle = getUpNextAndQueueTime()
|
||||
toolbar.setBackgroundColor(surfaceColor())
|
||||
setSupportActionBar(toolbar)
|
||||
clearQueue.backgroundTintList = ColorStateList.valueOf(accentColor())
|
||||
ColorStateList.valueOf(
|
||||
MaterialValueHelper.getPrimaryTextColor(this, ColorUtil.isColorLight(accentColor()))
|
||||
).apply {
|
||||
clearQueue.setTextColor(this)
|
||||
clearQueue.iconTint = this
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,37 +14,44 @@
|
|||
*/
|
||||
package code.name.monkey.retromusic.activities
|
||||
|
||||
import android.Manifest.permission.BLUETOOTH_CONNECT
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavDestination
|
||||
import code.name.monkey.appthemehelper.ThemeStore
|
||||
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||
import com.afollestad.materialdialogs.color.ColorChooserDialog
|
||||
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.extensions.applyToolbar
|
||||
import code.name.monkey.retromusic.extensions.findNavController
|
||||
import kotlinx.android.synthetic.main.activity_settings.*
|
||||
import code.name.monkey.retromusic.databinding.ActivitySettingsBinding
|
||||
import code.name.monkey.retromusic.extensions.*
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.afollestad.materialdialogs.color.ColorCallback
|
||||
|
||||
class SettingsActivity : AbsBaseActivity(), ColorChooserDialog.ColorCallback {
|
||||
class SettingsActivity : AbsBaseActivity(), ColorCallback, OnThemeChangedListener {
|
||||
private lateinit var binding: ActivitySettingsBinding
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
setDrawUnderStatusBar()
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_settings)
|
||||
setStatusbarColorAuto()
|
||||
setNavigationbarColorAuto()
|
||||
setLightNavigationBar(true)
|
||||
val mSavedInstanceState = extra<Bundle>(TAG).value ?: savedInstanceState
|
||||
super.onCreate(mSavedInstanceState)
|
||||
binding = ActivitySettingsBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
setupToolbar()
|
||||
setPermissionDeniedMessage(getString(R.string.permission_bluetooth_denied))
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
setNavigationBarColorPreOreo(surfaceColor())
|
||||
}
|
||||
|
||||
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.collapsingToolbarLayout.title =
|
||||
navController.currentDestination?.let { getStringFromDestination(it) }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,6 +66,7 @@ class SettingsActivity : AbsBaseActivity(), ColorChooserDialog.ColorCallback {
|
|||
R.id.personalizeSettingsFragment -> R.string.personalize
|
||||
R.id.themeSettingsFragment -> R.string.general_settings_title
|
||||
R.id.aboutActivity -> R.string.action_about
|
||||
R.id.backup_fragment -> R.string.backup_restore_title
|
||||
else -> R.id.action_settings
|
||||
}
|
||||
return getString(idRes)
|
||||
|
@ -68,24 +76,47 @@ 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 getPermissionsToRequest(): Array<String> {
|
||||
return if (VersionUtils.hasS()) {
|
||||
arrayOf(BLUETOOTH_CONNECT)
|
||||
} else {
|
||||
arrayOf()
|
||||
}
|
||||
}
|
||||
|
||||
override fun invoke(dialog: MaterialDialog, color: Int) {
|
||||
ThemeStore.editTheme(this).accentColor(color).commit()
|
||||
if (VersionUtils.hasNougatMR())
|
||||
DynamicShortcutManager(this).updateDynamicShortcuts()
|
||||
restart()
|
||||
}
|
||||
|
||||
override fun onThemeValuesChanged() {
|
||||
restart()
|
||||
}
|
||||
|
||||
private fun restart() {
|
||||
val savedInstanceState = Bundle().apply {
|
||||
onSaveInstanceState(this)
|
||||
}
|
||||
finish()
|
||||
val intent = Intent(this, this::class.java).putExtra(TAG, savedInstanceState)
|
||||
startActivity(intent)
|
||||
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val TAG: String = SettingsActivity::class.java.simpleName
|
||||
}
|
||||
}
|
||||
|
||||
interface OnThemeChangedListener {
|
||||
fun onThemeValuesChanged()
|
||||
}
|
||||
|
|
|
@ -18,23 +18,24 @@ import android.content.res.ColorStateList
|
|||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.provider.MediaStore.Images.Media
|
||||
import android.view.MenuItem
|
||||
import androidx.core.net.toUri
|
||||
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.extensions.accentColor
|
||||
import code.name.monkey.retromusic.extensions.setLightStatusBar
|
||||
import code.name.monkey.retromusic.extensions.setStatusBarColor
|
||||
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 +43,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"
|
||||
}
|
||||
|
@ -55,61 +58,60 @@ class ShareInstagramStory : AbsBaseActivity() {
|
|||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
setDrawUnderStatusBar()
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_share_instagram)
|
||||
setStatusbarColor(Color.TRANSPARENT)
|
||||
setNavigationbarColor(Color.BLACK)
|
||||
binding = ActivityShareInstagramBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
setStatusBarColor(Color.TRANSPARENT)
|
||||
|
||||
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)
|
||||
Share.shareStoryToSocial(
|
||||
this@ShareInstagramStory,
|
||||
uri
|
||||
path.toUri()
|
||||
)
|
||||
}
|
||||
}
|
||||
shareButton.setTextColor(
|
||||
binding.shareButton.setTextColor(
|
||||
MaterialValueHelper.getPrimaryTextColor(
|
||||
this,
|
||||
ColorUtil.isColorLight(ThemeStore.accentColor(this))
|
||||
ColorUtil.isColorLight(accentColor())
|
||||
)
|
||||
)
|
||||
shareButton.backgroundTintList = ColorStateList.valueOf(ThemeStore.accentColor(this))
|
||||
binding.shareButton.backgroundTintList =
|
||||
ColorStateList.valueOf(accentColor())
|
||||
}
|
||||
|
||||
private fun setColors(colorLight: Boolean, color: Int) {
|
||||
setLightStatusbar(colorLight)
|
||||
toolbar.setTitleTextColor(
|
||||
setLightStatusBar(colorLight)
|
||||
binding.toolbar.setTitleTextColor(
|
||||
MaterialValueHelper.getPrimaryTextColor(
|
||||
this@ShareInstagramStory,
|
||||
colorLight
|
||||
)
|
||||
)
|
||||
toolbar.navigationIcon?.setTintList(
|
||||
binding.toolbar.navigationIcon?.setTintList(
|
||||
ColorStateList.valueOf(
|
||||
MaterialValueHelper.getPrimaryTextColor(
|
||||
this@ShareInstagramStory,
|
||||
|
@ -117,7 +119,7 @@ class ShareInstagramStory : AbsBaseActivity() {
|
|||
)
|
||||
)
|
||||
)
|
||||
mainContent.background =
|
||||
binding.mainContent.background =
|
||||
GradientDrawable(
|
||||
GradientDrawable.Orientation.TOP_BOTTOM,
|
||||
intArrayOf(color, Color.BLACK)
|
||||
|
|
|
@ -1,229 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.activities
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.view.MenuItem
|
||||
import android.widget.Toast
|
||||
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.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.util.ImageUtil
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.request.RequestListener
|
||||
import com.bumptech.glide.request.target.Target
|
||||
import com.github.dhaval2404.imagepicker.ImagePicker
|
||||
import com.github.dhaval2404.imagepicker.constant.ImageProvider
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import kotlinx.android.synthetic.main.activity_user_info.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class UserInfoActivity : AbsBaseActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_user_info)
|
||||
setStatusbarColorAuto()
|
||||
setNavigationbarColorAuto()
|
||||
setTaskDescriptionColorAuto()
|
||||
setLightNavigationBar(true)
|
||||
applyToolbar(toolbar)
|
||||
|
||||
nameContainer.accentColor()
|
||||
name.setText(PreferenceUtil.userName)
|
||||
|
||||
userImage.setOnClickListener {
|
||||
pickNewPhoto()
|
||||
}
|
||||
|
||||
bannerImage.setOnClickListener {
|
||||
selectBannerImage()
|
||||
}
|
||||
|
||||
next.setOnClickListener {
|
||||
val nameString = 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
|
||||
}
|
||||
PreferenceUtil.userName = nameString
|
||||
setResult(Activity.RESULT_OK)
|
||||
finish()
|
||||
}
|
||||
|
||||
val textColor =
|
||||
MaterialValueHelper.getPrimaryTextColor(this, ColorUtil.isColorLight(accentColor()))
|
||||
next.backgroundTintList = ColorStateList.valueOf(accentColor())
|
||||
next.iconTint = ColorStateList.valueOf(textColor)
|
||||
next.setTextColor(textColor)
|
||||
loadProfile()
|
||||
}
|
||||
|
||||
private fun loadProfile() {
|
||||
bannerImage?.let {
|
||||
ProfileBannerGlideRequest.Builder.from(
|
||||
Glide.with(this),
|
||||
ProfileBannerGlideRequest.getBannerModel()
|
||||
).build().into(it)
|
||||
}
|
||||
UserProfileGlideRequest.Builder.from(
|
||||
Glide.with(this),
|
||||
UserProfileGlideRequest.getUserModel()
|
||||
).build().into(userImage)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == android.R.id.home) {
|
||||
onBackPressed()
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
private fun selectBannerImage() {
|
||||
ImagePicker.with(this)
|
||||
.compress(1440)
|
||||
.provider(ImageProvider.GALLERY)
|
||||
.crop(16f, 9f)
|
||||
.start(PICK_BANNER_REQUEST)
|
||||
}
|
||||
|
||||
private fun pickNewPhoto() {
|
||||
ImagePicker.with(this)
|
||||
.provider(ImageProvider.GALLERY)
|
||||
.cropSquare()
|
||||
.compress(1440)
|
||||
.start(PICK_IMAGE_REQUEST)
|
||||
}
|
||||
|
||||
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
if (resultCode == Activity.RESULT_OK && requestCode == PICK_IMAGE_REQUEST) {
|
||||
val fileUri = data?.data
|
||||
fileUri?.let { setAndSaveUserImage(it) }
|
||||
} else if (resultCode == Activity.RESULT_OK && requestCode == PICK_BANNER_REQUEST) {
|
||||
val fileUri = data?.data
|
||||
fileUri?.let { setAndSaveBannerImage(it) }
|
||||
} else if (resultCode == ImagePicker.RESULT_ERROR) {
|
||||
Toast.makeText(this, ImagePicker.getError(data), Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
Toast.makeText(this, "Task Cancelled", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setAndSaveBannerImage(fileUri: Uri) {
|
||||
Glide.with(this)
|
||||
.load(fileUri)
|
||||
.asBitmap()
|
||||
.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
|
||||
}
|
||||
|
||||
override fun onResourceReady(
|
||||
resource: Bitmap?,
|
||||
model: Any?,
|
||||
target: Target<Bitmap>?,
|
||||
isFromMemoryCache: Boolean,
|
||||
isFirstResource: Boolean
|
||||
): Boolean {
|
||||
resource?.let { saveImage(it, USER_BANNER) }
|
||||
return false
|
||||
}
|
||||
})
|
||||
.into(bannerImage)
|
||||
}
|
||||
|
||||
private fun saveImage(bitmap: Bitmap, fileName: String) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val appDir = applicationContext.filesDir
|
||||
val file = File(appDir, fileName)
|
||||
var successful = false
|
||||
try {
|
||||
val os = BufferedOutputStream(FileOutputStream(file))
|
||||
successful = ImageUtil.resizeBitmap(bitmap, 2048)
|
||||
.compress(Bitmap.CompressFormat.WEBP, 100, os)
|
||||
withContext(Dispatchers.IO) { os.close() }
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
if (successful) {
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(this@UserInfoActivity, "Updated", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setAndSaveUserImage(fileUri: Uri) {
|
||||
Glide.with(this)
|
||||
.load(fileUri)
|
||||
.asBitmap()
|
||||
.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
|
||||
}
|
||||
|
||||
override fun onResourceReady(
|
||||
resource: Bitmap?,
|
||||
model: Any?,
|
||||
target: Target<Bitmap>?,
|
||||
isFromMemoryCache: Boolean,
|
||||
isFirstResource: Boolean
|
||||
): Boolean {
|
||||
resource?.let { saveImage(it, USER_PROFILE) }
|
||||
return false
|
||||
}
|
||||
})
|
||||
.into(userImage)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PICK_IMAGE_REQUEST = 9002
|
||||
private const val PICK_BANNER_REQUEST = 9004
|
||||
}
|
||||
}
|
|
@ -1,111 +0,0 @@
|
|||
package code.name.monkey.retromusic.activities;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import 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 java.io.BufferedReader;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Locale;
|
||||
|
||||
public class WhatsNewActivity extends AbsBaseActivity {
|
||||
|
||||
private static String colorToCSS(int color) {
|
||||
return String.format(
|
||||
Locale.getDefault(),
|
||||
"rgba(%d, %d, %d, %d)",
|
||||
Color.red(color),
|
||||
Color.green(color),
|
||||
Color.blue(color),
|
||||
Color.alpha(color)); // on API 29, WebView doesn't load with hex colors
|
||||
}
|
||||
|
||||
private static void setChangelogRead(@NonNull Context context) {
|
||||
try {
|
||||
PackageInfo pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
|
||||
int currentVersion = pInfo.versionCode;
|
||||
PreferenceUtil.INSTANCE.setLastVersion(currentVersion);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
setDrawUnderStatusBar();
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_whats_new);
|
||||
setStatusbarColorAuto();
|
||||
setNavigationbarColorAuto();
|
||||
setTaskDescriptionColorAuto();
|
||||
|
||||
WebView webView = findViewById(R.id.webView);
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
toolbar.setBackgroundColor(ATHUtil.INSTANCE.resolveColor(this, R.attr.colorSurface));
|
||||
toolbar.setNavigationOnClickListener(v -> onBackPressed());
|
||||
ToolbarContentTintHelper.colorBackButton(toolbar);
|
||||
|
||||
try {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
InputStream json = getAssets().open("retro-changelog.html");
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(json, StandardCharsets.UTF_8));
|
||||
String str;
|
||||
while ((str = in.readLine()) != null) {
|
||||
buf.append(str);
|
||||
}
|
||||
in.close();
|
||||
|
||||
// Inject color values for WebView body background and links
|
||||
final boolean isDark = ATHUtil.INSTANCE.isWindowBackgroundDark(this);
|
||||
final int accentColor = ThemeStore.Companion.accentColor(this);
|
||||
final String backgroundColor =
|
||||
colorToCSS(
|
||||
ATHUtil.INSTANCE.resolveColor(
|
||||
this, R.attr.colorSurface, Color.parseColor(isDark ? "#424242" : "#ffffff")));
|
||||
final String contentColor = colorToCSS(Color.parseColor(isDark ? "#ffffff" : "#000000"));
|
||||
final String textColor = colorToCSS(Color.parseColor(isDark ? "#60FFFFFF" : "#80000000"));
|
||||
final String accentColorString = colorToCSS(ThemeStore.Companion.accentColor(this));
|
||||
final String accentTextColor =
|
||||
colorToCSS(
|
||||
MaterialValueHelper.getPrimaryTextColor(
|
||||
this, ColorUtil.INSTANCE.isColorLight(accentColor)));
|
||||
final String changeLog =
|
||||
buf.toString()
|
||||
.replace(
|
||||
"{style-placeholder}",
|
||||
String.format(
|
||||
"body { background-color: %s; color: %s; } li {color: %s;} .colorHeader {background-color: %s; color: %s;} .tag {color: %s;}",
|
||||
backgroundColor,
|
||||
contentColor,
|
||||
textColor,
|
||||
accentColorString,
|
||||
accentTextColor,
|
||||
accentColorString))
|
||||
.replace("{link-color}", colorToCSS(ThemeStore.Companion.accentColor(this)))
|
||||
.replace(
|
||||
"{link-color-active}",
|
||||
colorToCSS(
|
||||
ColorUtil.INSTANCE.lightenColor(ThemeStore.Companion.accentColor(this))));
|
||||
webView.loadData(changeLog, "text/html", "UTF-8");
|
||||
} catch (Throwable e) {
|
||||
webView.loadData(
|
||||
"<h1>Unable to load</h1><p>" + e.getLocalizedMessage() + "</p>", "text/html", "UTF-8");
|
||||
}
|
||||
setChangelogRead(this);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
package code.name.monkey.retromusic.activities
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.pm.PackageInfoCompat
|
||||
import androidx.core.widget.NestedScrollView
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import code.name.monkey.appthemehelper.util.ATHUtil.isWindowBackgroundDark
|
||||
import code.name.monkey.appthemehelper.util.ColorUtil.isColorLight
|
||||
import code.name.monkey.appthemehelper.util.ColorUtil.lightenColor
|
||||
import code.name.monkey.appthemehelper.util.MaterialValueHelper.getPrimaryTextColor
|
||||
import code.name.monkey.retromusic.BuildConfig
|
||||
import code.name.monkey.retromusic.Constants
|
||||
import code.name.monkey.retromusic.databinding.FragmentWhatsNewBinding
|
||||
import code.name.monkey.retromusic.extensions.accentColor
|
||||
import code.name.monkey.retromusic.extensions.openUrl
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil.lastVersion
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.*
|
||||
|
||||
class WhatsNewFragment : BottomSheetDialogFragment() {
|
||||
private var _binding: FragmentWhatsNewBinding? = null
|
||||
val binding get() = _binding!!
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = FragmentWhatsNewBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
try {
|
||||
val buf = StringBuilder()
|
||||
val stream= requireContext().assets.open("retro-changelog.html")
|
||||
stream.reader(StandardCharsets.UTF_8).buffered().use { br ->
|
||||
var str: String?
|
||||
while (br.readLine().also { str = it } != null) {
|
||||
buf.append(str)
|
||||
}
|
||||
}
|
||||
|
||||
// Inject color values for WebView body background and links
|
||||
val isDark = isWindowBackgroundDark(requireContext())
|
||||
val accentColor = accentColor()
|
||||
binding.webView.setBackgroundColor(0)
|
||||
val contentColor = colorToCSS(Color.parseColor(if (isDark) "#ffffff" else "#000000"))
|
||||
val textColor = colorToCSS(Color.parseColor(if (isDark) "#60FFFFFF" else "#80000000"))
|
||||
val accentColorString = colorToCSS(accentColor())
|
||||
val cardBackgroundColor =
|
||||
colorToCSS(Color.parseColor(if (isDark) "#353535" else "#ffffff"))
|
||||
val accentTextColor = colorToCSS(
|
||||
getPrimaryTextColor(
|
||||
requireContext(), isColorLight(accentColor)
|
||||
)
|
||||
)
|
||||
val changeLog = buf.toString()
|
||||
.replace(
|
||||
"{style-placeholder}",
|
||||
"body { color: $contentColor; } li {color: $textColor;} h3 {color: $accentColorString;} .tag {background-color: $accentColorString; color: $accentTextColor; } div{background-color: $cardBackgroundColor;}"
|
||||
)
|
||||
.replace("{link-color}", colorToCSS(accentColor()))
|
||||
.replace(
|
||||
"{link-color-active}",
|
||||
colorToCSS(
|
||||
lightenColor(accentColor())
|
||||
)
|
||||
)
|
||||
binding.webView.loadData(changeLog, "text/html", "UTF-8")
|
||||
} catch (e: Throwable) {
|
||||
binding.webView.loadData(
|
||||
"<h1>Unable to load</h1><p>" + e.localizedMessage + "</p>", "text/html", "UTF-8"
|
||||
)
|
||||
}
|
||||
setChangelogRead(requireContext())
|
||||
binding.tgFab.setOnClickListener {
|
||||
openUrl(Constants.TELEGRAM_CHANGE_LOG)
|
||||
}
|
||||
binding.tgFab.accentColor()
|
||||
binding.tgFab.shrink()
|
||||
binding.container.setOnScrollChangeListener { _: NestedScrollView?, _: Int, scrollY: Int, _: Int, oldScrollY: Int ->
|
||||
val dy = scrollY - oldScrollY
|
||||
if (dy > 0) {
|
||||
binding.tgFab.shrink()
|
||||
} else if (dy < 0) {
|
||||
binding.tgFab.extend()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
_binding = null
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
const val TAG = "WhatsNewFragment"
|
||||
private fun colorToCSS(color: Int): String {
|
||||
return String.format(
|
||||
Locale.getDefault(),
|
||||
"rgba(%d, %d, %d, %d)",
|
||||
Color.red(color),
|
||||
Color.green(color),
|
||||
Color.blue(color),
|
||||
Color.alpha(color)
|
||||
) // on API 29, WebView doesn't load with hex colors
|
||||
}
|
||||
|
||||
private fun setChangelogRead(context: Context) {
|
||||
try {
|
||||
val pInfo = context.packageManager.getPackageInfo(context.packageName, 0)
|
||||
val currentVersion = PackageInfoCompat.getLongVersionCode(pInfo)
|
||||
lastVersion = currentVersion
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun showChangeLog(activity: FragmentActivity) {
|
||||
val pInfo = activity.packageManager.getPackageInfo(activity.packageName, 0)
|
||||
val currentVersion = PackageInfoCompat.getLongVersionCode(pInfo)
|
||||
if (currentVersion > lastVersion && !BuildConfig.DEBUG) {
|
||||
val changelogBottomSheet = WhatsNewFragment()
|
||||
changelogBottomSheet.show(activity.supportFragmentManager, TAG)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,16 +17,21 @@ package code.name.monkey.retromusic.activities.base
|
|||
import android.Manifest
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Rect
|
||||
import android.media.AudioManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.view.KeyEvent
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.EditText
|
||||
import androidx.core.app.ActivityCompat
|
||||
import code.name.monkey.appthemehelper.ThemeStore
|
||||
import androidx.core.content.getSystemService
|
||||
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.extensions.accentColor
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
|
||||
abstract class AbsBaseActivity : AbsThemeActivity() {
|
||||
|
@ -57,19 +62,12 @@ abstract class AbsBaseActivity : AbsThemeActivity() {
|
|||
permissionDeniedMessage = null
|
||||
}
|
||||
|
||||
override fun onPostCreate(savedInstanceState: Bundle?) {
|
||||
super.onPostCreate(savedInstanceState)
|
||||
if (!hasPermissions()) {
|
||||
// requestPermissions()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
val hasPermissions = hasPermissions()
|
||||
if (hasPermissions != hadPermissions) {
|
||||
hadPermissions = hasPermissions
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (VersionUtils.hasMarshmallow()) {
|
||||
onHasPermissionsChanged(hasPermissions)
|
||||
}
|
||||
}
|
||||
|
@ -92,17 +90,15 @@ abstract class AbsBaseActivity : AbsThemeActivity() {
|
|||
}
|
||||
|
||||
protected open fun requestPermissions() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
requestPermissions(permissions, PERMISSION_REQUEST)
|
||||
}
|
||||
ActivityCompat.requestPermissions(this, permissions, PERMISSION_REQUEST)
|
||||
}
|
||||
|
||||
protected fun hasPermissions(): Boolean {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
for (permission in permissions) {
|
||||
if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
|
||||
return false
|
||||
}
|
||||
for (permission in permissions) {
|
||||
if (ActivityCompat.checkSelfPermission(this,
|
||||
permission) != PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
@ -111,7 +107,7 @@ abstract class AbsBaseActivity : AbsThemeActivity() {
|
|||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<String>,
|
||||
grantResults: IntArray
|
||||
grantResults: IntArray,
|
||||
) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
if (requestCode == PERMISSION_REQUEST) {
|
||||
|
@ -128,24 +124,41 @@ abstract class AbsBaseActivity : AbsThemeActivity() {
|
|||
Snackbar.LENGTH_INDEFINITE
|
||||
)
|
||||
.setAction(R.string.action_grant) { requestPermissions() }
|
||||
.setActionTextColor(ThemeStore.accentColor(this)).show()
|
||||
.setActionTextColor(accentColor()).show()
|
||||
} else if (ActivityCompat.shouldShowRequestPermissionRationale(
|
||||
this@AbsBaseActivity, Manifest.permission.BLUETOOTH_CONNECT
|
||||
)
|
||||
) {
|
||||
// User has deny from permission dialog
|
||||
Snackbar.make(
|
||||
snackBarContainer,
|
||||
R.string.permission_bluetooth_denied,
|
||||
Snackbar.LENGTH_INDEFINITE
|
||||
)
|
||||
.setAction(R.string.action_grant) {
|
||||
ActivityCompat.requestPermissions(this,
|
||||
arrayOf(Manifest.permission.BLUETOOTH_CONNECT),
|
||||
PERMISSION_REQUEST)
|
||||
}
|
||||
.setActionTextColor(accentColor()).show()
|
||||
} else {
|
||||
// User has deny permission and checked never show permission dialog so you can redirect to Application settings page
|
||||
Snackbar.make(
|
||||
snackBarContainer,
|
||||
permissionDeniedMessage!!,
|
||||
Snackbar.LENGTH_INDEFINITE
|
||||
).setAction(R.string.action_settings) {
|
||||
val intent = Intent()
|
||||
intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
|
||||
val uri = Uri.fromParts(
|
||||
"package",
|
||||
this@AbsBaseActivity.packageName,
|
||||
null
|
||||
)
|
||||
intent.data = uri
|
||||
startActivity(intent)
|
||||
}.setActionTextColor(ThemeStore.accentColor(this)).show()
|
||||
)
|
||||
.setAction(R.string.action_settings) {
|
||||
val intent = Intent()
|
||||
intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
|
||||
val uri = Uri.fromParts(
|
||||
"package",
|
||||
this@AbsBaseActivity.packageName,
|
||||
null
|
||||
)
|
||||
intent.data = uri
|
||||
startActivity(intent)
|
||||
}.setActionTextColor(accentColor()).show()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -158,4 +171,23 @@ abstract class AbsBaseActivity : AbsThemeActivity() {
|
|||
companion object {
|
||||
const val PERMISSION_REQUEST = 100
|
||||
}
|
||||
|
||||
// this lets keyboard close when clicked in background
|
||||
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
|
||||
if (event.action == MotionEvent.ACTION_DOWN) {
|
||||
val v = currentFocus
|
||||
if (v is EditText) {
|
||||
val outRect = Rect()
|
||||
v.getGlobalVisibleRect(outRect)
|
||||
if (!outRect.contains(event.rawX.toInt(), event.rawY.toInt())) {
|
||||
v.clearFocus()
|
||||
getSystemService<InputMethodManager>()?.hideSoftInputFromWindow(
|
||||
v.windowToken,
|
||||
0
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.dispatchTouchEvent(event)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
package code.name.monkey.retromusic.activities.base
|
||||
|
||||
import android.os.Bundle
|
||||
import code.name.monkey.retromusic.cast.CastHelper
|
||||
import code.name.monkey.retromusic.cast.RetroSessionManagerListener
|
||||
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.cast.framework.SessionManager
|
||||
import com.google.android.gms.common.ConnectionResult
|
||||
import com.google.android.gms.common.GoogleApiAvailability
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
|
||||
abstract class AbsCastActivity : AbsSlidingMusicPanelActivity() {
|
||||
|
||||
private var mCastSession: CastSession? = null
|
||||
private lateinit var sessionManager: SessionManager
|
||||
private val webServer: RetroWebServer by inject()
|
||||
|
||||
private var playServicesAvailable: Boolean = false
|
||||
|
||||
private val sessionManagerListener by lazy {
|
||||
object : RetroSessionManagerListener {
|
||||
override fun onSessionStarting(castSession: CastSession) {
|
||||
webServer.start()
|
||||
}
|
||||
|
||||
override fun onSessionStarted(castSession: CastSession, p1: String) {
|
||||
invalidateOptionsMenu()
|
||||
mCastSession = castSession
|
||||
loadCastQueue()
|
||||
MusicPlayerRemote.isCasting = true
|
||||
setAllowDragging(false)
|
||||
collapsePanel()
|
||||
}
|
||||
|
||||
override fun onSessionEnding(castSession: CastSession) {
|
||||
MusicPlayerRemote.isCasting = false
|
||||
castSession.remoteMediaClient?.let {
|
||||
val position = it.mediaQueue.indexOfItemWithId(it.currentItem?.itemId ?: 0)
|
||||
val progress = it.approximateStreamPosition
|
||||
MusicPlayerRemote.position = position
|
||||
MusicPlayerRemote.seekTo(progress.toInt())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSessionEnded(castSession: CastSession, p1: Int) {
|
||||
invalidateOptionsMenu()
|
||||
if (mCastSession == castSession) {
|
||||
mCastSession = null
|
||||
}
|
||||
setAllowDragging(true)
|
||||
webServer.stop()
|
||||
}
|
||||
|
||||
override fun onSessionResumed(castSession: CastSession, p1: Boolean) {
|
||||
invalidateOptionsMenu()
|
||||
mCastSession = castSession
|
||||
webServer.start()
|
||||
mCastSession?.remoteMediaClient?.let {
|
||||
loadCastQueue(it.mediaQueue.indexOfItemWithId(it.currentItem?.itemId ?: 0),
|
||||
it.approximateStreamPosition)
|
||||
}
|
||||
|
||||
MusicPlayerRemote.isCasting = true
|
||||
setAllowDragging(false)
|
||||
collapsePanel()
|
||||
}
|
||||
|
||||
override fun onSessionSuspended(castSession: CastSession, p1: Int) {
|
||||
invalidateOptionsMenu()
|
||||
if (mCastSession == castSession) {
|
||||
mCastSession = null
|
||||
}
|
||||
MusicPlayerRemote.isCasting = false
|
||||
setAllowDragging(true)
|
||||
webServer.stop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
sessionManager = CastContext.getSharedInstance(this).sessionManager
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
if (playServicesAvailable) {
|
||||
sessionManager.addSessionManagerListener(
|
||||
sessionManagerListener,
|
||||
CastSession::class.java
|
||||
)
|
||||
if (mCastSession == null) {
|
||||
mCastSession = sessionManager.currentCastSession
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
if (playServicesAvailable) {
|
||||
sessionManager.removeSessionManagerListener(
|
||||
sessionManagerListener,
|
||||
CastSession::class.java
|
||||
)
|
||||
mCastSession = null
|
||||
}
|
||||
}
|
||||
|
||||
fun loadCastQueue(
|
||||
position: Int = MusicPlayerRemote.position,
|
||||
progress: Long = MusicPlayerRemote.songProgressMillis.toLong(),
|
||||
) {
|
||||
mCastSession?.let {
|
||||
if (MusicPlayerRemote.playingQueue.isNotEmpty()) {
|
||||
CastHelper.castQueue(
|
||||
it,
|
||||
MusicPlayerRemote.playingQueue,
|
||||
position,
|
||||
progress
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onQueueChanged() {
|
||||
super.onQueueChanged()
|
||||
if (playServicesAvailable) {
|
||||
loadCastQueue()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,26 +15,28 @@
|
|||
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
|
||||
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.db.toPlayCount
|
||||
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 code.name.monkey.retromusic.service.MusicService.Companion.FAVORITE_STATE_CHANGED
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.MEDIA_STORE_CHANGED
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.META_CHANGED
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.PLAY_STATE_CHANGED
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.QUEUE_CHANGED
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.REPEAT_MODE_CHANGED
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.SHUFFLE_MODE_CHANGED
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.inject
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
abstract class AbsMusicServiceActivity : AbsBaseActivity(), IMusicServiceEventListener {
|
||||
|
||||
|
@ -123,7 +125,10 @@ abstract class AbsMusicServiceActivity : AbsBaseActivity(), IMusicServiceEventLi
|
|||
if (entity != null) {
|
||||
repository.updateHistorySong(MusicPlayerRemote.currentSong)
|
||||
} else {
|
||||
repository.addSongToHistory(MusicPlayerRemote.currentSong)
|
||||
// Check whether pause history option is ON or OFF
|
||||
if (!PreferenceUtil.pauseHistory) {
|
||||
repository.addSongToHistory(MusicPlayerRemote.currentSong)
|
||||
}
|
||||
}
|
||||
val songs = repository.checkSongExistInPlayCount(MusicPlayerRemote.currentSong.id)
|
||||
if (songs.isNotEmpty()) {
|
||||
|
@ -166,6 +171,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)
|
||||
|
@ -178,11 +189,11 @@ abstract class AbsMusicServiceActivity : AbsBaseActivity(), IMusicServiceEventLi
|
|||
}
|
||||
|
||||
override fun getPermissionsToRequest(): Array<String> {
|
||||
return arrayOf(
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||
Manifest.permission.BLUETOOTH
|
||||
)
|
||||
return mutableListOf(Manifest.permission.READ_EXTERNAL_STORAGE).apply {
|
||||
if (!VersionUtils.hasQ()) {
|
||||
add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
}
|
||||
}.toTypedArray()
|
||||
}
|
||||
|
||||
private class MusicStateReceiver(activity: AbsMusicServiceActivity) : BroadcastReceiver() {
|
||||
|
@ -194,7 +205,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()
|
||||
|
|
|
@ -14,27 +14,29 @@
|
|||
*/
|
||||
package code.name.monkey.retromusic.activities.base
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.animation.ArgbEvaluator
|
||||
import android.animation.ValueAnimator
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewTreeObserver
|
||||
import android.view.animation.PathInterpolator
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.animation.doOnEnd
|
||||
import androidx.core.view.*
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.commit
|
||||
import code.name.monkey.appthemehelper.util.ATHUtil
|
||||
import code.name.monkey.appthemehelper.util.ColorUtil
|
||||
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.RetroBottomSheetBehavior
|
||||
import code.name.monkey.retromusic.databinding.SlidingMusicPanelLayoutBinding
|
||||
import code.name.monkey.retromusic.extensions.*
|
||||
import code.name.monkey.retromusic.fragments.LibraryViewModel
|
||||
import code.name.monkey.retromusic.fragments.MiniPlayerFragment
|
||||
import code.name.monkey.retromusic.fragments.NowPlayingScreen
|
||||
import code.name.monkey.retromusic.fragments.NowPlayingScreen.*
|
||||
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
|
||||
import code.name.monkey.retromusic.fragments.other.MiniPlayerFragment
|
||||
import code.name.monkey.retromusic.fragments.player.adaptive.AdaptiveFragment
|
||||
import code.name.monkey.retromusic.fragments.player.blur.BlurPlayerFragment
|
||||
import code.name.monkey.retromusic.fragments.player.card.CardFragment
|
||||
|
@ -47,58 +49,85 @@ import code.name.monkey.retromusic.fragments.player.flat.FlatPlayerFragment
|
|||
import code.name.monkey.retromusic.fragments.player.full.FullPlayerFragment
|
||||
import code.name.monkey.retromusic.fragments.player.gradient.GradientPlayerFragment
|
||||
import code.name.monkey.retromusic.fragments.player.material.MaterialFragment
|
||||
import code.name.monkey.retromusic.fragments.player.md3.MD3PlayerFragment
|
||||
import code.name.monkey.retromusic.fragments.player.normal.PlayerFragment
|
||||
import code.name.monkey.retromusic.fragments.player.peak.PeakPlayerFragment
|
||||
import code.name.monkey.retromusic.fragments.player.peek.PeekPlayerFragment
|
||||
import code.name.monkey.retromusic.fragments.player.plain.PlainPlayerFragment
|
||||
import code.name.monkey.retromusic.fragments.player.simple.SimplePlayerFragment
|
||||
import code.name.monkey.retromusic.fragments.player.tiny.TinyPlayerFragment
|
||||
import code.name.monkey.retromusic.fragments.queue.PlayingQueueFragment
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||
import code.name.monkey.retromusic.model.CategoryInfo
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import code.name.monkey.retromusic.views.BottomNavigationBarTinted
|
||||
import code.name.monkey.retromusic.util.ViewUtil
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior.*
|
||||
import kotlinx.android.synthetic.main.sliding_music_panel_layout.*
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
|
||||
abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
|
||||
companion object {
|
||||
val TAG: String = AbsSlidingMusicPanelActivity::class.java.simpleName
|
||||
}
|
||||
|
||||
var fromNotification = false
|
||||
private var windowInsets: WindowInsetsCompat? = null
|
||||
protected val libraryViewModel by viewModel<LibraryViewModel>()
|
||||
private lateinit var bottomSheetBehavior: RetroBottomSheetBehavior<FrameLayout>
|
||||
private lateinit var bottomSheetBehavior: BottomSheetBehavior<FrameLayout>
|
||||
private var playerFragment: AbsPlayerFragment? = null
|
||||
private var miniPlayerFragment: MiniPlayerFragment? = null
|
||||
private var nowPlayingScreen: NowPlayingScreen? = null
|
||||
private var navigationBarColor: Int = 0
|
||||
private var taskColor: Int = 0
|
||||
private var lightStatusBar: Boolean = false
|
||||
private var lightNavigationBar: Boolean = false
|
||||
private var paletteColor: Int = Color.WHITE
|
||||
protected abstract fun createContentView(): View
|
||||
private var navigationBarColor = 0
|
||||
protected abstract fun createContentView(): SlidingMusicPanelLayoutBinding
|
||||
private val panelState: Int
|
||||
get() = bottomSheetBehavior.state
|
||||
private lateinit var binding: SlidingMusicPanelLayoutBinding
|
||||
private var isInOneTabMode = false
|
||||
|
||||
private var navigationBarColorAnimator: ValueAnimator? = null
|
||||
private val argbEvaluator: ArgbEvaluator = ArgbEvaluator()
|
||||
|
||||
private val bottomSheetCallbackList = object : BottomSheetCallback() {
|
||||
|
||||
override fun onSlide(bottomSheet: View, slideOffset: Float) {
|
||||
setMiniPlayerAlphaProgress(slideOffset)
|
||||
dimBackground.show()
|
||||
dimBackground.alpha = slideOffset
|
||||
navigationBarColorAnimator?.cancel()
|
||||
setNavigationBarColorPreOreo(
|
||||
argbEvaluator.evaluate(
|
||||
slideOffset,
|
||||
surfaceColor(),
|
||||
navigationBarColor
|
||||
) as Int
|
||||
)
|
||||
}
|
||||
|
||||
override fun onStateChanged(bottomSheet: View, newState: Int) {
|
||||
when (newState) {
|
||||
STATE_EXPANDED -> {
|
||||
onPanelExpanded()
|
||||
if (PreferenceUtil.lyricsScreenOn && PreferenceUtil.showLyrics) {
|
||||
keepScreenOn(true)
|
||||
}
|
||||
}
|
||||
STATE_COLLAPSED -> {
|
||||
onPanelCollapsed()
|
||||
dimBackground.hide()
|
||||
if ((PreferenceUtil.lyricsScreenOn && PreferenceUtil.showLyrics) || !PreferenceUtil.isScreenOnEnabled) {
|
||||
keepScreenOn(false)
|
||||
}
|
||||
}
|
||||
STATE_SETTLING, STATE_DRAGGING -> {
|
||||
if (fromNotification) {
|
||||
binding.bottomNavigationView.bringToFront()
|
||||
fromNotification = false
|
||||
}
|
||||
}
|
||||
STATE_HIDDEN -> {
|
||||
MusicPlayerRemote.clearQueue()
|
||||
}
|
||||
|
||||
else -> {
|
||||
println("Do something")
|
||||
println("Do a flip")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -108,23 +137,31 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
|
|||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(createContentView())
|
||||
binding = createContentView()
|
||||
setContentView(binding.root)
|
||||
ViewCompat.setOnApplyWindowInsetsListener(
|
||||
binding.root
|
||||
) { _, insets ->
|
||||
windowInsets = insets
|
||||
insets
|
||||
}
|
||||
chooseFragmentForTheme()
|
||||
setupSlidingUpPanel()
|
||||
setupBottomSheet()
|
||||
updateColor()
|
||||
|
||||
val themeColor = resolveColor(android.R.attr.windowBackground, Color.GRAY)
|
||||
dimBackground.setBackgroundColor(ColorUtil.withAlpha(themeColor, 0.5f))
|
||||
dimBackground.setOnClickListener {
|
||||
println("dimBackground")
|
||||
collapsePanel()
|
||||
if (!PreferenceUtil.materialYou) {
|
||||
binding.slidingPanel.backgroundTintList = ColorStateList.valueOf(darkAccentColor())
|
||||
bottomNavigationView.backgroundTintList = ColorStateList.valueOf(darkAccentColor())
|
||||
}
|
||||
|
||||
navigationBarColor = surfaceColor()
|
||||
}
|
||||
|
||||
private fun setupBottomSheet() {
|
||||
bottomSheetBehavior = from(slidingPanel) as RetroBottomSheetBehavior
|
||||
bottomSheetBehavior = from(binding.slidingPanel)
|
||||
bottomSheetBehavior.addBottomSheetCallback(bottomSheetCallbackList)
|
||||
bottomSheetBehavior.isHideable = PreferenceUtil.swipeDownToDismiss
|
||||
setMiniPlayerAlphaProgress(0F)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
|
@ -142,55 +179,69 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
|
|||
bottomSheetBehavior.removeBottomSheetCallback(bottomSheetCallbackList)
|
||||
}
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
protected fun wrapSlidingMusicPanel(): View {
|
||||
val slidingMusicPanelLayout =
|
||||
layoutInflater.inflate(R.layout.sliding_music_panel_layout, null)
|
||||
val contentContainer: ViewGroup =
|
||||
slidingMusicPanelLayout.findViewById(R.id.mainContentFrame)
|
||||
layoutInflater.inflate(R.layout.activity_main_content, contentContainer)
|
||||
return slidingMusicPanelLayout
|
||||
protected fun wrapSlidingMusicPanel(): SlidingMusicPanelLayoutBinding {
|
||||
return SlidingMusicPanelLayoutBinding.inflate(layoutInflater)
|
||||
}
|
||||
|
||||
fun collapsePanel() {
|
||||
bottomSheetBehavior.state = STATE_COLLAPSED
|
||||
setMiniPlayerAlphaProgress(0f)
|
||||
}
|
||||
|
||||
fun expandPanel() {
|
||||
bottomSheetBehavior.state = STATE_EXPANDED
|
||||
setMiniPlayerAlphaProgress(1f)
|
||||
}
|
||||
|
||||
private fun setMiniPlayerAlphaProgress(progress: Float) {
|
||||
if (progress < 0) return
|
||||
val alpha = 1 - progress
|
||||
miniPlayerFragment?.view?.alpha = alpha
|
||||
miniPlayerFragment?.view?.visibility = if (alpha == 0f) View.GONE else View.VISIBLE
|
||||
bottomNavigationView.translationY = progress * 500
|
||||
bottomNavigationView.alpha = alpha
|
||||
miniPlayerFragment?.view?.alpha = 1 - (progress / 0.2F)
|
||||
miniPlayerFragment?.view?.isGone = alpha == 0f
|
||||
binding.bottomNavigationView.translationY = progress * 500
|
||||
binding.bottomNavigationView.alpha = alpha
|
||||
binding.playerFragmentContainer.alpha = (progress - 0.2F) / 0.2F
|
||||
}
|
||||
|
||||
private fun animateNavigationBarColor(color: Int) {
|
||||
if (VersionUtils.hasOreo()) return
|
||||
navigationBarColorAnimator?.cancel()
|
||||
navigationBarColorAnimator = ValueAnimator
|
||||
.ofArgb(window.navigationBarColor, color).apply {
|
||||
duration = ViewUtil.RETRO_MUSIC_ANIM_TIME.toLong()
|
||||
interpolator = PathInterpolator(0.4f, 0f, 1f, 1f)
|
||||
addUpdateListener { animation: ValueAnimator ->
|
||||
setNavigationBarColorPreOreo(
|
||||
animation.animatedValue as Int
|
||||
)
|
||||
}
|
||||
start()
|
||||
}
|
||||
}
|
||||
|
||||
open fun onPanelCollapsed() {
|
||||
setMiniPlayerAlphaProgress(0F)
|
||||
// restore values
|
||||
super.setLightStatusbar(lightStatusBar)
|
||||
super.setTaskDescriptionColor(taskColor)
|
||||
super.setNavigationbarColor(navigationBarColor)
|
||||
super.setLightNavigationBar(lightNavigationBar)
|
||||
animateNavigationBarColor(surfaceColor())
|
||||
setLightStatusBarAuto()
|
||||
setLightNavigationBarAuto()
|
||||
setTaskDescriptionColor(taskColor)
|
||||
playerFragment?.onHide()
|
||||
}
|
||||
|
||||
open fun onPanelExpanded() {
|
||||
setMiniPlayerAlphaProgress(1F)
|
||||
onPaletteColorChanged()
|
||||
playerFragment?.onShow()
|
||||
}
|
||||
|
||||
private fun setupSlidingUpPanel() {
|
||||
slidingPanel.viewTreeObserver.addOnGlobalLayoutListener(object :
|
||||
binding.slidingPanel.viewTreeObserver.addOnGlobalLayoutListener(object :
|
||||
ViewTreeObserver.OnGlobalLayoutListener {
|
||||
override fun onGlobalLayout() {
|
||||
slidingPanel.viewTreeObserver.removeOnGlobalLayoutListener(this)
|
||||
if (nowPlayingScreen != Peak) {
|
||||
val params = slidingPanel.layoutParams as ViewGroup.LayoutParams
|
||||
params.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
slidingPanel.layoutParams = params
|
||||
binding.slidingPanel.viewTreeObserver.removeOnGlobalLayoutListener(this)
|
||||
if (nowPlayingScreen != Peek) {
|
||||
binding.slidingPanel.updateLayoutParams<ViewGroup.LayoutParams> {
|
||||
height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
}
|
||||
}
|
||||
when (panelState) {
|
||||
STATE_EXPANDED -> onPanelExpanded()
|
||||
|
@ -203,26 +254,30 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
|
|||
})
|
||||
}
|
||||
|
||||
fun getBottomNavigationView(): BottomNavigationBarTinted {
|
||||
return bottomNavigationView
|
||||
}
|
||||
val bottomNavigationView get() = binding.bottomNavigationView
|
||||
|
||||
val slidingPanel get() = binding.slidingPanel
|
||||
|
||||
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)
|
||||
hideBottomBar(false)
|
||||
binding.slidingPanel.viewTreeObserver.removeOnGlobalLayoutListener(this)
|
||||
hideBottomSheet(false)
|
||||
}
|
||||
})
|
||||
} // don't call hideBottomBar(true) here as it causes a bug with the SlidingUpPanelLayout
|
||||
} // don't call hideBottomSheet(true) here as it causes a bug with the SlidingUpPanelLayout
|
||||
}
|
||||
|
||||
override fun onQueueChanged() {
|
||||
super.onQueueChanged()
|
||||
hideBottomBar(MusicPlayerRemote.playingQueue.isEmpty())
|
||||
// Mini player should be hidden in Playing Queue
|
||||
// it may pop up if hideBottomSheet is called
|
||||
if (currentFragment(R.id.fragment_container) !is PlayingQueueFragment) {
|
||||
hideBottomSheet(MusicPlayerRemote.playingQueue.isEmpty())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
|
@ -235,127 +290,153 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
|
|||
collapsePanel()
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun onPaletteColorChanged() {
|
||||
if (panelState == STATE_EXPANDED) {
|
||||
super.setTaskDescriptionColor(paletteColor)
|
||||
val isColorLight = ColorUtil.isColorLight(paletteColor)
|
||||
if (PreferenceUtil.isAdaptiveColor && (nowPlayingScreen == Normal || nowPlayingScreen == Flat)) {
|
||||
super.setLightNavigationBar(true)
|
||||
super.setLightStatusbar(isColorLight)
|
||||
navigationBarColor = surfaceColor()
|
||||
setTaskDescColor(paletteColor)
|
||||
val isColorLight = paletteColor.isColorLight
|
||||
if (PreferenceUtil.isAdaptiveColor && (nowPlayingScreen == Normal || nowPlayingScreen == Flat || nowPlayingScreen == Material)) {
|
||||
setLightNavigationBar(true)
|
||||
setLightStatusBar(isColorLight)
|
||||
} else if (nowPlayingScreen == Card || nowPlayingScreen == Blur || nowPlayingScreen == BlurCard) {
|
||||
super.setLightStatusbar(false)
|
||||
super.setLightNavigationBar(true)
|
||||
super.setNavigationbarColor(Color.BLACK)
|
||||
animateNavigationBarColor(Color.BLACK)
|
||||
navigationBarColor = Color.BLACK
|
||||
setLightStatusBar(false)
|
||||
setLightNavigationBar(true)
|
||||
} else if (nowPlayingScreen == Color || nowPlayingScreen == Tiny || nowPlayingScreen == Gradient) {
|
||||
super.setNavigationbarColor(paletteColor)
|
||||
super.setLightNavigationBar(isColorLight)
|
||||
super.setLightStatusbar(isColorLight)
|
||||
animateNavigationBarColor(paletteColor)
|
||||
navigationBarColor = paletteColor
|
||||
setLightNavigationBar(isColorLight)
|
||||
setLightStatusBar(isColorLight)
|
||||
} else if (nowPlayingScreen == Full) {
|
||||
super.setNavigationbarColor(paletteColor)
|
||||
super.setLightNavigationBar(isColorLight)
|
||||
super.setLightStatusbar(false)
|
||||
animateNavigationBarColor(paletteColor)
|
||||
navigationBarColor = paletteColor
|
||||
setLightNavigationBar(isColorLight)
|
||||
setLightStatusBar(false)
|
||||
} else if (nowPlayingScreen == Classic) {
|
||||
super.setLightStatusbar(false)
|
||||
setLightStatusBar(false)
|
||||
} else if (nowPlayingScreen == Fit) {
|
||||
super.setLightStatusbar(false)
|
||||
} else {
|
||||
super.setLightStatusbar(
|
||||
ColorUtil.isColorLight(
|
||||
ATHUtil.resolveColor(
|
||||
this,
|
||||
android.R.attr.windowBackground
|
||||
)
|
||||
)
|
||||
)
|
||||
super.setLightNavigationBar(true)
|
||||
setLightStatusBar(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun setLightStatusbar(enabled: Boolean) {
|
||||
lightStatusBar = enabled
|
||||
if (panelState == STATE_COLLAPSED) {
|
||||
super.setLightStatusbar(enabled)
|
||||
}
|
||||
}
|
||||
|
||||
override fun setLightNavigationBar(enabled: Boolean) {
|
||||
lightNavigationBar = enabled
|
||||
if (panelState == STATE_COLLAPSED) {
|
||||
super.setLightNavigationBar(enabled)
|
||||
}
|
||||
}
|
||||
|
||||
override fun setNavigationbarColor(color: Int) {
|
||||
navigationBarColor = color
|
||||
if (panelState == STATE_COLLAPSED) {
|
||||
super.setNavigationbarColor(color)
|
||||
}
|
||||
}
|
||||
|
||||
override fun setTaskDescriptionColor(color: Int) {
|
||||
private fun setTaskDescColor(color: Int) {
|
||||
taskColor = color
|
||||
if (panelState == STATE_COLLAPSED) {
|
||||
super.setTaskDescriptionColor(color)
|
||||
setTaskDescriptionColor(color)
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
isInOneTabMode = true
|
||||
binding.bottomNavigationView.hide()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateColor() {
|
||||
libraryViewModel.paletteColor.observe(this, { color ->
|
||||
libraryViewModel.paletteColor.observe(this) { color ->
|
||||
this.paletteColor = color
|
||||
onPaletteColorChanged()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fun setBottomBarVisibility(visible: Boolean) {
|
||||
bottomNavigationView.isVisible = visible
|
||||
hideBottomBar(MusicPlayerRemote.playingQueue.isEmpty())
|
||||
fun setBottomNavVisibility(
|
||||
visible: Boolean,
|
||||
animate: Boolean = false,
|
||||
hideBottomSheet: Boolean = MusicPlayerRemote.playingQueue.isEmpty(),
|
||||
) {
|
||||
if (isInOneTabMode) {
|
||||
hideBottomSheet(
|
||||
hide = hideBottomSheet,
|
||||
animate = animate,
|
||||
isBottomNavVisible = false
|
||||
)
|
||||
return
|
||||
}
|
||||
val mAnimate = animate && bottomSheetBehavior.state == STATE_COLLAPSED
|
||||
if (mAnimate) {
|
||||
if (visible) {
|
||||
binding.bottomNavigationView.bringToFront()
|
||||
binding.bottomNavigationView.show()
|
||||
} else {
|
||||
binding.bottomNavigationView.hide()
|
||||
}
|
||||
} else {
|
||||
binding.bottomNavigationView.isVisible = false
|
||||
if (visible && bottomSheetBehavior.state != STATE_EXPANDED) {
|
||||
binding.bottomNavigationView.bringToFront()
|
||||
}
|
||||
}
|
||||
hideBottomSheet(
|
||||
hide = hideBottomSheet,
|
||||
animate = animate,
|
||||
isBottomNavVisible = visible
|
||||
)
|
||||
}
|
||||
|
||||
private fun hideBottomBar(hide: Boolean) {
|
||||
val heightOfBar = dip(R.dimen.mini_player_height)
|
||||
val heightOfBarWithTabs = heightOfBar * 2
|
||||
val isVisible = bottomNavigationView.isVisible
|
||||
fun hideBottomSheet(
|
||||
hide: Boolean,
|
||||
animate: Boolean = false,
|
||||
isBottomNavVisible: Boolean = bottomNavigationView.isVisible,
|
||||
) {
|
||||
val heightOfBar =
|
||||
windowInsets.safeGetBottomInsets() +
|
||||
if (MusicPlayerRemote.isCasting) dip(R.dimen.cast_mini_player_height) else dip(R.dimen.mini_player_height)
|
||||
val heightOfBarWithTabs = heightOfBar + dip(R.dimen.bottom_nav_height)
|
||||
if (hide) {
|
||||
bottomSheetBehavior.isHideable = true
|
||||
bottomSheetBehavior.peekHeight = 0
|
||||
ViewCompat.setElevation(slidingPanel, 0f)
|
||||
ViewCompat.setElevation(bottomNavigationView, 10f)
|
||||
collapsePanel()
|
||||
bottomSheetBehavior.peekHeight = -windowInsets.safeGetBottomInsets()
|
||||
bottomSheetBehavior.state = STATE_COLLAPSED
|
||||
libraryViewModel.setFabMargin(
|
||||
this,
|
||||
if (isBottomNavVisible) dip(R.dimen.bottom_nav_height) else 0
|
||||
)
|
||||
} else {
|
||||
if (MusicPlayerRemote.playingQueue.isNotEmpty()) {
|
||||
bottomSheetBehavior.isHideable = false
|
||||
ViewCompat.setElevation(slidingPanel, 10f)
|
||||
ViewCompat.setElevation(bottomNavigationView, 10f)
|
||||
if (isVisible) {
|
||||
binding.slidingPanel.elevation = 0F
|
||||
binding.bottomNavigationView.elevation = 5F
|
||||
if (isBottomNavVisible) {
|
||||
println("List")
|
||||
bottomSheetBehavior.peekHeight = heightOfBarWithTabs - 22
|
||||
if (animate) {
|
||||
bottomSheetBehavior.peekHeightAnimate(heightOfBarWithTabs)
|
||||
} else {
|
||||
bottomSheetBehavior.peekHeight = heightOfBarWithTabs
|
||||
}
|
||||
libraryViewModel.setFabMargin(this, dip(R.dimen.mini_player_height_expanded))
|
||||
} else {
|
||||
println("Details")
|
||||
bottomSheetBehavior.peekHeight = heightOfBar
|
||||
if (animate) {
|
||||
bottomSheetBehavior.peekHeightAnimate(heightOfBar).doOnEnd {
|
||||
binding.slidingPanel.bringToFront()
|
||||
}
|
||||
} else {
|
||||
bottomSheetBehavior.peekHeight = heightOfBar
|
||||
binding.slidingPanel.bringToFront()
|
||||
}
|
||||
libraryViewModel.setFabMargin(this, dip(R.dimen.mini_player_height))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setAllowDragging(allowDragging: Boolean) {
|
||||
bottomSheetBehavior.isDraggable = allowDragging
|
||||
hideBottomSheet(false)
|
||||
}
|
||||
|
||||
private fun chooseFragmentForTheme() {
|
||||
nowPlayingScreen = PreferenceUtil.nowPlayingScreen
|
||||
|
||||
|
@ -374,9 +455,10 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
|
|||
Color -> ColorFragment()
|
||||
Gradient -> GradientPlayerFragment()
|
||||
Tiny -> TinyPlayerFragment()
|
||||
Peak -> PeakPlayerFragment()
|
||||
Peek -> PeekPlayerFragment()
|
||||
Circle -> CirclePlayerFragment()
|
||||
Classic -> ClassicPlayerFragment()
|
||||
MD3 -> MD3PlayerFragment()
|
||||
else -> PlayerFragment()
|
||||
} // must implement AbsPlayerFragment
|
||||
supportFragmentManager.commit {
|
||||
|
|
|
@ -15,52 +15,54 @@
|
|||
package code.name.monkey.retromusic.activities.base
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.content.res.Resources
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.KeyEvent
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode
|
||||
import code.name.monkey.appthemehelper.ATH
|
||||
import code.name.monkey.appthemehelper.ThemeStore
|
||||
import androidx.core.os.ConfigurationCompat
|
||||
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
|
||||
import code.name.monkey.retromusic.extensions.*
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import code.name.monkey.retromusic.util.RetroUtil
|
||||
import code.name.monkey.retromusic.util.theme.ThemeManager
|
||||
import code.name.monkey.retromusic.util.theme.getNightMode
|
||||
import code.name.monkey.retromusic.util.theme.getThemeResValue
|
||||
import com.google.android.play.core.splitcompat.SplitCompat
|
||||
import java.util.*
|
||||
|
||||
abstract class AbsThemeActivity : ATHToolbarActivity(), Runnable {
|
||||
|
||||
private val handler = Handler()
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
updateTheme()
|
||||
hideStatusBar()
|
||||
super.onCreate(savedInstanceState)
|
||||
setImmersiveFullscreen()
|
||||
setEdgeToEdgeOrImmersive()
|
||||
registerSystemUiVisibility()
|
||||
toggleScreenOn()
|
||||
MaterialDialogsUtil.updateMaterialDialogsThemeSingleton(this)
|
||||
setLightNavigationBarAuto()
|
||||
setLightStatusBarAuto(surfaceColor())
|
||||
if (VersionUtils.hasQ()) {
|
||||
window.decorView.isForceDarkAllowed = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateTheme() {
|
||||
setTheme(ThemeManager.getThemeResValue(this))
|
||||
setDefaultNightMode(ThemeManager.getNightMode(this))
|
||||
}
|
||||
setTheme(getThemeResValue())
|
||||
if (PreferenceUtil.materialYou) {
|
||||
setDefaultNightMode(getNightMode())
|
||||
}
|
||||
|
||||
private fun toggleScreenOn() {
|
||||
if (PreferenceUtil.isScreenOnEnabled) {
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
} else {
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
if (PreferenceUtil.isCustomFont) {
|
||||
setTheme(R.style.FontThemeOverlay)
|
||||
}
|
||||
if (PreferenceUtil.circlePlayButton) {
|
||||
setTheme(R.style.CircleFABOverlay)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,92 +77,6 @@ abstract class AbsThemeActivity : ATHToolbarActivity(), Runnable {
|
|||
}
|
||||
}
|
||||
|
||||
fun hideStatusBar() {
|
||||
hideStatusBar(PreferenceUtil.isFullScreenMode)
|
||||
}
|
||||
|
||||
private fun hideStatusBar(fullscreen: Boolean) {
|
||||
val statusBar = window.decorView.rootView.findViewById<View>(R.id.status_bar)
|
||||
if (statusBar != null) {
|
||||
statusBar.visibility = if (fullscreen) View.GONE else View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
fun setDrawUnderStatusBar() {
|
||||
RetroUtil.setAllowDrawUnderStatusBar(window)
|
||||
}
|
||||
|
||||
fun setDrawUnderNavigationBar() {
|
||||
RetroUtil.setAllowDrawUnderNavigationBar(window)
|
||||
}
|
||||
|
||||
/**
|
||||
* This will set the color of the view with the id "status_bar" on KitKat and Lollipop. On
|
||||
* Lollipop if no such view is found it will set the statusbar color using the native method.
|
||||
*
|
||||
* @param color the new statusbar color (will be shifted down on Lollipop and above)
|
||||
*/
|
||||
fun setStatusbarColor(color: Int) {
|
||||
val statusBar = window.decorView.rootView.findViewById<View>(R.id.status_bar)
|
||||
if (statusBar != null) {
|
||||
when {
|
||||
VersionUtils.hasMarshmallow() -> statusBar.setBackgroundColor(color)
|
||||
VersionUtils.hasLollipop() -> statusBar.setBackgroundColor(
|
||||
ColorUtil.darkenColor(
|
||||
color
|
||||
)
|
||||
)
|
||||
else -> statusBar.setBackgroundColor(color)
|
||||
}
|
||||
} else {
|
||||
when {
|
||||
VersionUtils.hasMarshmallow() -> window.statusBarColor = color
|
||||
else -> window.statusBarColor = ColorUtil.darkenColor(color)
|
||||
}
|
||||
}
|
||||
setLightStatusbarAuto(ATHUtil.resolveColor(this, R.attr.colorSurface))
|
||||
}
|
||||
|
||||
fun setStatusbarColorAuto() {
|
||||
// we don't want to use statusbar color because we are doing the color darkening on our own to support KitKat
|
||||
setStatusbarColor(ATHUtil.resolveColor(this, R.attr.colorSurface))
|
||||
setLightStatusbarAuto(ATHUtil.resolveColor(this, R.attr.colorSurface))
|
||||
}
|
||||
|
||||
open fun setTaskDescriptionColor(@ColorInt color: Int) {
|
||||
ATH.setTaskDescriptionColor(this, color)
|
||||
}
|
||||
|
||||
fun setTaskDescriptionColorAuto() {
|
||||
setTaskDescriptionColor(ATHUtil.resolveColor(this, R.attr.colorSurface))
|
||||
}
|
||||
|
||||
open fun setNavigationbarColor(color: Int) {
|
||||
if (ThemeStore.coloredNavigationBar(this)) {
|
||||
ATH.setNavigationbarColor(this, color)
|
||||
} else {
|
||||
ATH.setNavigationbarColor(this, Color.BLACK)
|
||||
}
|
||||
}
|
||||
|
||||
fun setNavigationbarColorAuto() {
|
||||
setNavigationbarColor(ATHUtil.resolveColor(this, R.attr.colorSurface))
|
||||
}
|
||||
|
||||
open fun setLightStatusbar(enabled: Boolean) {
|
||||
ATH.setLightStatusbar(this, enabled)
|
||||
}
|
||||
|
||||
fun setLightStatusbarAuto(bgColor: Int) {
|
||||
setLightStatusbar(ColorUtil.isColorLight(bgColor))
|
||||
}
|
||||
|
||||
open fun setLightNavigationBar(enabled: Boolean) {
|
||||
if (!ATHUtil.isWindowBackgroundDark(this) and ThemeStore.coloredNavigationBar(this)) {
|
||||
ATH.setLightNavigationbar(this, enabled)
|
||||
}
|
||||
}
|
||||
|
||||
private fun registerSystemUiVisibility() {
|
||||
val decorView = window.decorView
|
||||
decorView.setOnSystemUiVisibilityChangeListener { visibility ->
|
||||
|
@ -175,19 +91,6 @@ abstract class AbsThemeActivity : ATHToolbarActivity(), Runnable {
|
|||
decorView.setOnSystemUiVisibilityChangeListener(null)
|
||||
}
|
||||
|
||||
private fun setImmersiveFullscreen() {
|
||||
val flags =
|
||||
(View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)
|
||||
|
||||
if (PreferenceUtil.isFullScreenMode) {
|
||||
window.decorView.systemUiVisibility = flags
|
||||
}
|
||||
}
|
||||
|
||||
private fun exitFullscreen() {
|
||||
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
setImmersiveFullscreen()
|
||||
}
|
||||
|
@ -213,8 +116,13 @@ 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))
|
||||
SplitCompat.install(this)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,23 +14,17 @@
|
|||
*/
|
||||
package code.name.monkey.retromusic.activities.bugreport
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Dialog
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.view.MenuItem
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.StringDef
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import code.name.monkey.appthemehelper.ThemeStore
|
||||
import code.name.monkey.appthemehelper.util.ATHUtil
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import code.name.monkey.appthemehelper.util.MaterialUtil
|
||||
import code.name.monkey.appthemehelper.util.TintHelper
|
||||
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
|
||||
|
@ -41,18 +35,21 @@ 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.misc.DialogAsyncTask
|
||||
import code.name.monkey.retromusic.databinding.ActivityBugReportBinding
|
||||
import code.name.monkey.retromusic.extensions.accentColor
|
||||
import code.name.monkey.retromusic.extensions.setTaskDescriptionColorAuto
|
||||
import code.name.monkey.retromusic.extensions.showToast
|
||||
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 kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
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,65 +69,62 @@ 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)
|
||||
setStatusbarColorAuto()
|
||||
setNavigationbarColorAuto()
|
||||
binding = ActivityBugReportBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
setTaskDescriptionColorAuto()
|
||||
|
||||
initViews()
|
||||
|
||||
if (TextUtils.isEmpty(title)) setTitle(R.string.report_an_issue)
|
||||
if (title.isNullOrEmpty()) 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)
|
||||
val accentColor = accentColor()
|
||||
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,75 +132,71 @@ 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()
|
||||
|
||||
val i = Intent(Intent.ACTION_VIEW)
|
||||
i.data = Uri.parse(ISSUE_TRACKER_LINK)
|
||||
i.data = ISSUE_TRACKER_LINK.toUri()
|
||||
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
startActivity(i)
|
||||
}
|
||||
}
|
||||
|
||||
private fun copyDeviceInfoToClipBoard() {
|
||||
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val clipboard = getSystemService<ClipboardManager>()
|
||||
val clip = ClipData.newPlainText(getString(R.string.device_info), deviceInfo?.toMarkdown())
|
||||
clipboard.setPrimaryClip(clip)
|
||||
Toast.makeText(
|
||||
this@BugReportActivity,
|
||||
R.string.copied_device_info_to_clipboard,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
clipboard?.setPrimaryClip(clip)
|
||||
showToast(R.string.copied_device_info_to_clipboard)
|
||||
}
|
||||
|
||||
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 (binding.cardReport.inputUsername.text.isNullOrEmpty()) {
|
||||
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 (binding.cardReport.inputPassword.text.isNullOrEmpty()) {
|
||||
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 (binding.cardReport.inputTitle.text.isNullOrEmpty()) {
|
||||
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 (binding.cardReport.inputDescription.text.isNullOrEmpty()) {
|
||||
setError(binding.cardReport.inputLayoutDescription, R.string.bug_report_no_description)
|
||||
hasErrors = true
|
||||
} else {
|
||||
removeError(inputLayoutDescription)
|
||||
removeError(binding.cardReport.inputLayoutDescription)
|
||||
}
|
||||
|
||||
return !hasErrors
|
||||
|
@ -223,16 +213,16 @@ 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()
|
||||
|
||||
val report = Report(bugTitle, bugDescription, deviceInfo, extraInfo)
|
||||
val target = GithubTarget("h4h13", "RetroMusicPlayer")
|
||||
val target = GithubTarget("RetroMusicPlayer", "RetroMusicPlayer")
|
||||
|
||||
ReportIssueAsyncTask.report(this, report, target, login)
|
||||
reportIssue(report, target, login)
|
||||
}
|
||||
|
||||
private fun onSaveExtraInfo() {}
|
||||
|
@ -244,92 +234,75 @@ open class BugReportActivity : AbsThemeActivity() {
|
|||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
private class ReportIssueAsyncTask private constructor(
|
||||
activity: Activity,
|
||||
private val report: Report,
|
||||
private val target: GithubTarget,
|
||||
private val login: GithubLogin
|
||||
) : DialogAsyncTask<Void, Void, String>(activity) {
|
||||
|
||||
override fun createDialog(context: Context): Dialog {
|
||||
return AlertDialog.Builder(context).show()
|
||||
private fun reportIssue(
|
||||
report: Report,
|
||||
target: GithubTarget,
|
||||
login: GithubLogin
|
||||
) {
|
||||
val client: GitHubClient = if (login.shouldUseApiToken()) {
|
||||
GitHubClient().setOAuth2Token(login.apiToken)
|
||||
} else {
|
||||
GitHubClient().setCredentials(login.username, login.password)
|
||||
}
|
||||
|
||||
@Result
|
||||
override fun doInBackground(vararg params: Void): String {
|
||||
val client: GitHubClient = if (login.shouldUseApiToken()) {
|
||||
GitHubClient().setOAuth2Token(login.apiToken)
|
||||
} else {
|
||||
GitHubClient().setCredentials(login.username, login.password)
|
||||
}
|
||||
val issue = Issue().setTitle(report.title).setBody(report.getDescription())
|
||||
|
||||
val issue = Issue().setTitle(report.title).setBody(report.description)
|
||||
try {
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
val result = try {
|
||||
IssueService(client).createIssue(target.username, target.repository, issue)
|
||||
return RESULT_SUCCESS
|
||||
RESULT_SUCCESS
|
||||
} catch (e: RequestException) {
|
||||
return when (e.status) {
|
||||
when (e.status) {
|
||||
STATUS_BAD_CREDENTIALS -> {
|
||||
if (login.shouldUseApiToken()) RESULT_INVALID_TOKEN else RESULT_BAD_CREDENTIALS
|
||||
}
|
||||
STATUS_ISSUES_NOT_ENABLED -> RESULT_ISSUES_NOT_ENABLED
|
||||
else -> {
|
||||
e.printStackTrace()
|
||||
RESULT_UNKNOWN
|
||||
throw e
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
return RESULT_UNKNOWN
|
||||
RESULT_UNKNOWN
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
val activity = this@BugReportActivity
|
||||
when (result) {
|
||||
RESULT_SUCCESS -> MaterialAlertDialogBuilder(activity)
|
||||
.setTitle(R.string.bug_report_success)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> tryToFinishActivity() }
|
||||
.show()
|
||||
RESULT_BAD_CREDENTIALS -> MaterialAlertDialogBuilder(activity)
|
||||
.setTitle(R.string.bug_report_failed)
|
||||
.setMessage(R.string.bug_report_failed_wrong_credentials)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show()
|
||||
RESULT_INVALID_TOKEN -> MaterialAlertDialogBuilder(activity)
|
||||
.setTitle(R.string.bug_report_failed)
|
||||
.setMessage(R.string.bug_report_failed_invalid_token)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show()
|
||||
RESULT_ISSUES_NOT_ENABLED -> MaterialAlertDialogBuilder(activity)
|
||||
.setTitle(R.string.bug_report_failed)
|
||||
.setMessage(R.string.bug_report_failed_issues_not_available)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show()
|
||||
else -> MaterialAlertDialogBuilder(activity)
|
||||
.setTitle(R.string.bug_report_failed)
|
||||
.setMessage(R.string.bug_report_failed_unknown)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> tryToFinishActivity() }
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> tryToFinishActivity() }
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPostExecute(@Result result: String) {
|
||||
super.onPostExecute(result)
|
||||
|
||||
val context = context ?: return
|
||||
|
||||
when (result) {
|
||||
RESULT_SUCCESS -> tryToFinishActivity()
|
||||
RESULT_BAD_CREDENTIALS -> MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.bug_report_failed)
|
||||
.setMessage(R.string.bug_report_failed_wrong_credentials)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show()
|
||||
RESULT_INVALID_TOKEN -> MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.bug_report_failed)
|
||||
.setMessage(R.string.bug_report_failed_invalid_token)
|
||||
.setPositiveButton(android.R.string.ok, null).show()
|
||||
RESULT_ISSUES_NOT_ENABLED -> MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.bug_report_failed)
|
||||
.setMessage(R.string.bug_report_failed_issues_not_available)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
|
||||
else -> MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.bug_report_failed)
|
||||
.setMessage(R.string.bug_report_failed_unknown)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> tryToFinishActivity() }
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> tryToFinishActivity() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun tryToFinishActivity() {
|
||||
val context = context
|
||||
if (context is Activity && !context.isFinishing) {
|
||||
context.finish()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun report(
|
||||
activity: Activity,
|
||||
report: Report,
|
||||
target: GithubTarget,
|
||||
login: GithubLogin
|
||||
) {
|
||||
ReportIssueAsyncTask(activity, report, target, login).execute()
|
||||
}
|
||||
private fun tryToFinishActivity() {
|
||||
if (!isFinishing) {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -337,6 +310,7 @@ open class BugReportActivity : AbsThemeActivity() {
|
|||
|
||||
private const val STATUS_BAD_CREDENTIALS = 401
|
||||
private const val STATUS_ISSUES_NOT_ENABLED = 410
|
||||
private const val ISSUE_TRACKER_LINK = "https://github.com/h4h13/RetroMusicPlayer"
|
||||
private const val ISSUE_TRACKER_LINK =
|
||||
"https://github.com/RetroMusicPlayer/RetroMusicPlayer"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,205 +0,0 @@
|
|||
package code.name.monkey.retromusic.activities.bugreport.model;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import androidx.annotation.IntRange;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil;
|
||||
|
||||
public class DeviceInfo {
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
@SuppressWarnings("deprecation")
|
||||
private final String[] abis =
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
|
||||
? Build.SUPPORTED_ABIS
|
||||
: new String[] {Build.CPU_ABI, Build.CPU_ABI2};
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
private final String[] abis32Bits =
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? Build.SUPPORTED_32_BIT_ABIS : null;
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
private final String[] abis64Bits =
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? Build.SUPPORTED_64_BIT_ABIS : null;
|
||||
|
||||
private final String baseTheme;
|
||||
|
||||
private final String brand = Build.BRAND;
|
||||
|
||||
private final String buildID = Build.DISPLAY;
|
||||
|
||||
private final String buildVersion = Build.VERSION.INCREMENTAL;
|
||||
|
||||
private final String device = Build.DEVICE;
|
||||
|
||||
private final String hardware = Build.HARDWARE;
|
||||
|
||||
private final boolean isAdaptive;
|
||||
|
||||
private final String manufacturer = Build.MANUFACTURER;
|
||||
|
||||
private final String model = Build.MODEL;
|
||||
|
||||
private final String nowPlayingTheme;
|
||||
|
||||
private final String product = Build.PRODUCT;
|
||||
|
||||
private final String releaseVersion = Build.VERSION.RELEASE;
|
||||
|
||||
@IntRange(from = 0)
|
||||
private final int sdkVersion = Build.VERSION.SDK_INT;
|
||||
|
||||
private final int versionCode;
|
||||
|
||||
private final String versionName;
|
||||
private final String selectedLang;
|
||||
|
||||
public DeviceInfo(@NotNull Context context) {
|
||||
PackageInfo packageInfo;
|
||||
try {
|
||||
packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
packageInfo = null;
|
||||
}
|
||||
if (packageInfo != null) {
|
||||
versionCode = packageInfo.versionCode;
|
||||
versionName = packageInfo.versionName;
|
||||
} else {
|
||||
versionCode = -1;
|
||||
versionName = null;
|
||||
}
|
||||
baseTheme = PreferenceUtil.INSTANCE.getBaseTheme();
|
||||
nowPlayingTheme =
|
||||
context.getString(PreferenceUtil.INSTANCE.getNowPlayingScreen().getTitleRes());
|
||||
isAdaptive = PreferenceUtil.INSTANCE.isAdaptiveColor();
|
||||
selectedLang = PreferenceUtil.INSTANCE.getLanguageCode();
|
||||
}
|
||||
|
||||
public String toMarkdown() {
|
||||
return "Device info:\n"
|
||||
+ "---\n"
|
||||
+ "<table>\n"
|
||||
+ "<tr><td><b>App version</b></td><td>"
|
||||
+ versionName
|
||||
+ "</td></tr>\n"
|
||||
+ "<tr><td>App version code</td><td>"
|
||||
+ versionCode
|
||||
+ "</td></tr>\n"
|
||||
+ "<tr><td>Android build version</td><td>"
|
||||
+ buildVersion
|
||||
+ "</td></tr>\n"
|
||||
+ "<tr><td>Android release version</td><td>"
|
||||
+ releaseVersion
|
||||
+ "</td></tr>\n"
|
||||
+ "<tr><td>Android SDK version</td><td>"
|
||||
+ sdkVersion
|
||||
+ "</td></tr>\n"
|
||||
+ "<tr><td>Android build ID</td><td>"
|
||||
+ buildID
|
||||
+ "</td></tr>\n"
|
||||
+ "<tr><td>Device brand</td><td>"
|
||||
+ brand
|
||||
+ "</td></tr>\n"
|
||||
+ "<tr><td>Device manufacturer</td><td>"
|
||||
+ manufacturer
|
||||
+ "</td></tr>\n"
|
||||
+ "<tr><td>Device name</td><td>"
|
||||
+ device
|
||||
+ "</td></tr>\n"
|
||||
+ "<tr><td>Device model</td><td>"
|
||||
+ model
|
||||
+ "</td></tr>\n"
|
||||
+ "<tr><td>Device product name</td><td>"
|
||||
+ product
|
||||
+ "</td></tr>\n"
|
||||
+ "<tr><td>Device hardware name</td><td>"
|
||||
+ hardware
|
||||
+ "</td></tr>\n"
|
||||
+ "<tr><td>ABIs</td><td>"
|
||||
+ Arrays.toString(abis)
|
||||
+ "</td></tr>\n"
|
||||
+ "<tr><td>ABIs (32bit)</td><td>"
|
||||
+ Arrays.toString(abis32Bits)
|
||||
+ "</td></tr>\n"
|
||||
+ "<tr><td>ABIs (64bit)</td><td>"
|
||||
+ Arrays.toString(abis64Bits)
|
||||
+ "</td></tr>\n"
|
||||
+ "<tr><td>Language</td><td>"
|
||||
+ selectedLang
|
||||
+ "</td></tr>\n"
|
||||
+ "</table>\n";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "App version: "
|
||||
+ versionName
|
||||
+ "\n"
|
||||
+ "App version code: "
|
||||
+ versionCode
|
||||
+ "\n"
|
||||
+ "Android build version: "
|
||||
+ buildVersion
|
||||
+ "\n"
|
||||
+ "Android release version: "
|
||||
+ releaseVersion
|
||||
+ "\n"
|
||||
+ "Android SDK version: "
|
||||
+ sdkVersion
|
||||
+ "\n"
|
||||
+ "Android build ID: "
|
||||
+ buildID
|
||||
+ "\n"
|
||||
+ "Device brand: "
|
||||
+ brand
|
||||
+ "\n"
|
||||
+ "Device manufacturer: "
|
||||
+ manufacturer
|
||||
+ "\n"
|
||||
+ "Device name: "
|
||||
+ device
|
||||
+ "\n"
|
||||
+ "Device model: "
|
||||
+ model
|
||||
+ "\n"
|
||||
+ "Device product name: "
|
||||
+ product
|
||||
+ "\n"
|
||||
+ "Device hardware name: "
|
||||
+ hardware
|
||||
+ "\n"
|
||||
+ "ABIs: "
|
||||
+ Arrays.toString(abis)
|
||||
+ "\n"
|
||||
+ "ABIs (32bit): "
|
||||
+ Arrays.toString(abis32Bits)
|
||||
+ "\n"
|
||||
+ "ABIs (64bit): "
|
||||
+ Arrays.toString(abis64Bits)
|
||||
+ "\n"
|
||||
+ "Base theme: "
|
||||
+ baseTheme
|
||||
+ "\n"
|
||||
+ "Now playing theme: "
|
||||
+ nowPlayingTheme
|
||||
+ "\n"
|
||||
+ "Adaptive: "
|
||||
+ isAdaptive
|
||||
+ "\n"
|
||||
+ "System language: "
|
||||
+ Locale.getDefault().toLanguageTag()
|
||||
+ "\n"
|
||||
+ "In-App Language: "
|
||||
+ selectedLang;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
package code.name.monkey.retromusic.activities.bugreport.model
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import androidx.annotation.IntRange
|
||||
import androidx.core.content.pm.PackageInfoCompat
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil.isAdaptiveColor
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil.languageCode
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil.nowPlayingScreen
|
||||
import java.util.*
|
||||
|
||||
class DeviceInfo(context: Context) {
|
||||
@SuppressLint("NewApi")
|
||||
private val abis = Build.SUPPORTED_ABIS
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
private val abis32Bits = Build.SUPPORTED_32_BIT_ABIS
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
private val abis64Bits = Build.SUPPORTED_64_BIT_ABIS
|
||||
private val baseTheme: String
|
||||
private val brand = Build.BRAND
|
||||
private val buildID = Build.DISPLAY
|
||||
private val buildVersion = Build.VERSION.INCREMENTAL
|
||||
private val device = Build.DEVICE
|
||||
private val hardware = Build.HARDWARE
|
||||
private val isAdaptive: Boolean
|
||||
private val manufacturer = Build.MANUFACTURER
|
||||
private val model = Build.MODEL
|
||||
private val nowPlayingTheme: String
|
||||
private val product = Build.PRODUCT
|
||||
private val releaseVersion = Build.VERSION.RELEASE
|
||||
|
||||
@IntRange(from = 0)
|
||||
private val sdkVersion = Build.VERSION.SDK_INT
|
||||
private var versionCode = 0L
|
||||
private var versionName: String? = null
|
||||
private val selectedLang: String
|
||||
fun toMarkdown(): String {
|
||||
return """
|
||||
Device info:
|
||||
---
|
||||
<table>
|
||||
<tr><td><b>App version</b></td><td>$versionName</td></tr>
|
||||
<tr><td>App version code</td><td>$versionCode</td></tr>
|
||||
<tr><td>Android build version</td><td>$buildVersion</td></tr>
|
||||
<tr><td>Android release version</td><td>$releaseVersion</td></tr>
|
||||
<tr><td>Android SDK version</td><td>$sdkVersion</td></tr>
|
||||
<tr><td>Android build ID</td><td>$buildID</td></tr>
|
||||
<tr><td>Device brand</td><td>$brand</td></tr>
|
||||
<tr><td>Device manufacturer</td><td>$manufacturer</td></tr>
|
||||
<tr><td>Device name</td><td>$device</td></tr>
|
||||
<tr><td>Device model</td><td>$model</td></tr>
|
||||
<tr><td>Device product name</td><td>$product</td></tr>
|
||||
<tr><td>Device hardware name</td><td>$hardware</td></tr>
|
||||
<tr><td>ABIs</td><td>${Arrays.toString(abis)}</td></tr>
|
||||
<tr><td>ABIs (32bit)</td><td>${Arrays.toString(abis32Bits)}</td></tr>
|
||||
<tr><td>ABIs (64bit)</td><td>${Arrays.toString(abis64Bits)}</td></tr>
|
||||
<tr><td>Language</td><td>$selectedLang</td></tr>
|
||||
</table>
|
||||
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return """
|
||||
App version: $versionName
|
||||
App version code: $versionCode
|
||||
Android build version: $buildVersion
|
||||
Android release version: $releaseVersion
|
||||
Android SDK version: $sdkVersion
|
||||
Android build ID: $buildID
|
||||
Device brand: $brand
|
||||
Device manufacturer: $manufacturer
|
||||
Device name: $device
|
||||
Device model: $model
|
||||
Device product name: $product
|
||||
Device hardware name: $hardware
|
||||
ABIs: ${Arrays.toString(abis)}
|
||||
ABIs (32bit): ${Arrays.toString(abis32Bits)}
|
||||
ABIs (64bit): ${Arrays.toString(abis64Bits)}
|
||||
Base theme: $baseTheme
|
||||
Now playing theme: $nowPlayingTheme
|
||||
Adaptive: $isAdaptive
|
||||
System language: ${Locale.getDefault().toLanguageTag()}
|
||||
In-App Language: $selectedLang
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
init {
|
||||
val packageInfo = try {
|
||||
context.packageManager.getPackageInfo(context.packageName, 0)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
null
|
||||
}
|
||||
if (packageInfo != null) {
|
||||
versionCode = PackageInfoCompat.getLongVersionCode(packageInfo)
|
||||
versionName = packageInfo.versionName
|
||||
} else {
|
||||
versionCode = -1
|
||||
versionName = null
|
||||
}
|
||||
baseTheme = PreferenceUtil.baseTheme
|
||||
nowPlayingTheme = context.getString(nowPlayingScreen.titleRes)
|
||||
isAdaptive = isAdaptiveColor
|
||||
selectedLang = languageCode
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
package code.name.monkey.retromusic.activities.bugreport.model;
|
||||
|
||||
import code.name.monkey.retromusic.activities.bugreport.model.github.ExtraInfo;
|
||||
|
||||
public class Report {
|
||||
|
||||
private final String description;
|
||||
|
||||
private final DeviceInfo deviceInfo;
|
||||
|
||||
private final ExtraInfo extraInfo;
|
||||
|
||||
private final String title;
|
||||
|
||||
public Report(String title, String description, DeviceInfo deviceInfo, ExtraInfo extraInfo) {
|
||||
this.title = title;
|
||||
this.description = description;
|
||||
this.deviceInfo = deviceInfo;
|
||||
this.extraInfo = extraInfo;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description
|
||||
+ "\n\n"
|
||||
+ "-\n\n"
|
||||
+ deviceInfo.toMarkdown()
|
||||
+ "\n\n"
|
||||
+ extraInfo.toMarkdown();
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package code.name.monkey.retromusic.activities.bugreport.model
|
||||
|
||||
import code.name.monkey.retromusic.activities.bugreport.model.github.ExtraInfo
|
||||
|
||||
class Report(
|
||||
val title: String,
|
||||
private val description: String,
|
||||
private val deviceInfo: DeviceInfo?,
|
||||
private val extraInfo: ExtraInfo
|
||||
) {
|
||||
fun getDescription(): String {
|
||||
return """
|
||||
$description
|
||||
|
||||
-
|
||||
|
||||
${deviceInfo?.toMarkdown()}
|
||||
|
||||
${extraInfo.toMarkdown()}
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
package code.name.monkey.retromusic.activities.bugreport.model.github;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class ExtraInfo {
|
||||
|
||||
private final Map<String, String> extraInfo = new LinkedHashMap<>();
|
||||
|
||||
public void put(String key, String value) {
|
||||
extraInfo.put(key, value);
|
||||
}
|
||||
|
||||
public void put(String key, boolean value) {
|
||||
extraInfo.put(key, Boolean.toString(value));
|
||||
}
|
||||
|
||||
public void put(String key, double value) {
|
||||
extraInfo.put(key, Double.toString(value));
|
||||
}
|
||||
|
||||
public void put(String key, float value) {
|
||||
extraInfo.put(key, Float.toString(value));
|
||||
}
|
||||
|
||||
public void put(String key, long value) {
|
||||
extraInfo.put(key, Long.toString(value));
|
||||
}
|
||||
|
||||
public void put(String key, int value) {
|
||||
extraInfo.put(key, Integer.toString(value));
|
||||
}
|
||||
|
||||
public void put(String key, Object value) {
|
||||
extraInfo.put(key, String.valueOf(value));
|
||||
}
|
||||
|
||||
public void remove(String key) {
|
||||
extraInfo.remove(key);
|
||||
}
|
||||
|
||||
public String toMarkdown() {
|
||||
if (extraInfo.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
StringBuilder output = new StringBuilder();
|
||||
output.append("Extra info:\n" + "---\n" + "<table>\n");
|
||||
for (String key : extraInfo.keySet()) {
|
||||
output
|
||||
.append("<tr><td>")
|
||||
.append(key)
|
||||
.append("</td><td>")
|
||||
.append(extraInfo.get(key))
|
||||
.append("</td></tr>\n");
|
||||
}
|
||||
output.append("</table>\n");
|
||||
|
||||
return output.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package code.name.monkey.retromusic.activities.bugreport.model.github
|
||||
|
||||
class ExtraInfo {
|
||||
private val extraInfo: MutableMap<String, String> = LinkedHashMap()
|
||||
fun put(key: String, value: String) {
|
||||
extraInfo[key] = value
|
||||
}
|
||||
|
||||
fun put(key: String, value: Boolean) {
|
||||
extraInfo[key] = value.toString()
|
||||
}
|
||||
|
||||
fun put(key: String, value: Double) {
|
||||
extraInfo[key] = value.toString()
|
||||
}
|
||||
|
||||
fun put(key: String, value: Float) {
|
||||
extraInfo[key] = value.toString()
|
||||
}
|
||||
|
||||
fun put(key: String, value: Long) {
|
||||
extraInfo[key] = value.toString()
|
||||
}
|
||||
|
||||
fun put(key: String, value: Int) {
|
||||
extraInfo[key] = value.toString()
|
||||
}
|
||||
|
||||
fun put(key: String, value: Any) {
|
||||
extraInfo[key] = value.toString()
|
||||
}
|
||||
|
||||
fun remove(key: String) {
|
||||
extraInfo.remove(key)
|
||||
}
|
||||
|
||||
fun toMarkdown(): String {
|
||||
if (extraInfo.isEmpty()) {
|
||||
return ""
|
||||
}
|
||||
val output = StringBuilder()
|
||||
output.append(
|
||||
"""
|
||||
Extra info:
|
||||
---
|
||||
<table>
|
||||
|
||||
""".trimIndent()
|
||||
)
|
||||
for (key in extraInfo.keys) {
|
||||
output
|
||||
.append("<tr><td>")
|
||||
.append(key)
|
||||
.append("</td><td>")
|
||||
.append(extraInfo[key])
|
||||
.append("</td></tr>\n")
|
||||
}
|
||||
output.append("</table>\n")
|
||||
return output.toString()
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
package code.name.monkey.retromusic.activities.bugreport.model.github;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
public class GithubLogin {
|
||||
|
||||
private final String apiToken;
|
||||
|
||||
private final String password;
|
||||
|
||||
private final String username;
|
||||
|
||||
public GithubLogin(String username, String password) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.apiToken = null;
|
||||
}
|
||||
|
||||
public GithubLogin(String apiToken) {
|
||||
this.username = null;
|
||||
this.password = null;
|
||||
this.apiToken = apiToken;
|
||||
}
|
||||
|
||||
public String getApiToken() {
|
||||
return apiToken;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public boolean shouldUseApiToken() {
|
||||
return TextUtils.isEmpty(username) || TextUtils.isEmpty(password);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package code.name.monkey.retromusic.activities.bugreport.model.github
|
||||
|
||||
import android.text.TextUtils
|
||||
|
||||
class GithubLogin {
|
||||
val apiToken: String?
|
||||
val password: String?
|
||||
val username: String?
|
||||
|
||||
constructor(username: String?, password: String?) {
|
||||
this.username = username
|
||||
this.password = password
|
||||
apiToken = null
|
||||
}
|
||||
|
||||
constructor(apiToken: String?) {
|
||||
username = null
|
||||
password = null
|
||||
this.apiToken = apiToken
|
||||
}
|
||||
|
||||
fun shouldUseApiToken(): Boolean {
|
||||
return TextUtils.isEmpty(username) || TextUtils.isEmpty(password)
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
package code.name.monkey.retromusic.activities.bugreport.model.github;
|
||||
|
||||
public class GithubTarget {
|
||||
|
||||
private final String repository;
|
||||
|
||||
private final String username;
|
||||
|
||||
public GithubTarget(String username, String repository) {
|
||||
this.username = username;
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public String getRepository() {
|
||||
return repository;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
package code.name.monkey.retromusic.activities.bugreport.model.github
|
||||
|
||||
class GithubTarget(val username: String, val repository: String)
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -21,27 +21,37 @@ import android.graphics.Bitmap
|
|||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.provider.MediaStore
|
||||
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.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.IntentSenderRequest
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import code.name.monkey.appthemehelper.ThemeStore
|
||||
import code.name.monkey.appthemehelper.util.ATHUtil
|
||||
import code.name.monkey.appthemehelper.util.TintHelper
|
||||
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.R.drawable
|
||||
import code.name.monkey.retromusic.activities.base.AbsBaseActivity
|
||||
import code.name.monkey.retromusic.activities.saf.SAFGuideActivity
|
||||
import code.name.monkey.retromusic.extensions.accentColor
|
||||
import code.name.monkey.retromusic.extensions.colorButtons
|
||||
import code.name.monkey.retromusic.extensions.hideSoftKeyboard
|
||||
import code.name.monkey.retromusic.extensions.setTaskDescriptionColorAuto
|
||||
import code.name.monkey.retromusic.model.ArtworkInfo
|
||||
import code.name.monkey.retromusic.model.LoadingInfo
|
||||
import code.name.monkey.retromusic.model.AudioTagInfo
|
||||
import code.name.monkey.retromusic.repository.Repository
|
||||
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 kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jaudiotagger.audio.AudioFile
|
||||
import org.jaudiotagger.audio.AudioFileIO
|
||||
import org.jaudiotagger.tag.FieldKey
|
||||
|
@ -49,20 +59,27 @@ 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
|
||||
protected var id: Long = 0
|
||||
private set
|
||||
private var paletteColorPrimary: Int = 0
|
||||
private var isInNoImageMode: Boolean = false
|
||||
private var songPaths: List<String>? = null
|
||||
private var savedSongPaths: List<String>? = null
|
||||
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!!
|
||||
private var cacheFiles = listOf<File>()
|
||||
|
||||
abstract val bindingInflater: (LayoutInflater) -> VB
|
||||
|
||||
private lateinit var launcher: ActivityResultLauncher<IntentSenderRequest>
|
||||
|
||||
protected abstract fun loadImageFromFile(selectedFile: Uri?)
|
||||
|
||||
protected val show: AlertDialog
|
||||
|
@ -76,7 +93,9 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
|
|||
2 -> deleteImage()
|
||||
}
|
||||
}
|
||||
.setNegativeButton(R.string.action_cancel, null)
|
||||
.show()
|
||||
.colorButtons()
|
||||
|
||||
internal val albumArtist: String?
|
||||
get() {
|
||||
|
@ -158,6 +177,15 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
protected val discNumber: String?
|
||||
get() {
|
||||
return try {
|
||||
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.DISC_NO)
|
||||
} catch (ignored: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
protected val lyrics: String?
|
||||
get() {
|
||||
return try {
|
||||
|
@ -187,9 +215,8 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
|
|||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(contentViewLayout)
|
||||
setStatusbarColorAuto()
|
||||
setNavigationbarColorAuto()
|
||||
_binding = bindingInflater.invoke(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
setTaskDescriptionColorAuto()
|
||||
|
||||
saveFab = findViewById(R.id.saveTags)
|
||||
|
@ -201,6 +228,11 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
|
|||
finish()
|
||||
}
|
||||
setUpViews()
|
||||
launcher = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) {
|
||||
if (it.resultCode == Activity.RESULT_OK) {
|
||||
writeToFiles(getSongUris(), cacheFiles)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setUpViews() {
|
||||
|
@ -217,7 +249,7 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
|
|||
getString(R.string.web_search),
|
||||
getString(R.string.remove_cover)
|
||||
)
|
||||
editorImage?.setOnClickListener { show }
|
||||
editorImage.setOnClickListener { show }
|
||||
}
|
||||
|
||||
private fun startImagePicker() {
|
||||
|
@ -259,6 +291,8 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
|
|||
|
||||
protected abstract fun getSongPaths(): List<String>
|
||||
|
||||
protected abstract fun getSongUris(): List<Uri>
|
||||
|
||||
protected fun searchWebFor(vararg keys: String) {
|
||||
val stringBuilder = StringBuilder()
|
||||
for (key in keys) {
|
||||
|
@ -282,20 +316,6 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
|
|||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
protected fun setNoImageMode() {
|
||||
isInNoImageMode = true
|
||||
imageContainer?.visibility = View.GONE
|
||||
editorImage?.visibility = View.GONE
|
||||
editorImage?.isEnabled = false
|
||||
|
||||
setColors(
|
||||
intent.getIntExtra(
|
||||
EXTRA_PALETTE,
|
||||
ATHUtil.resolveColor(this, R.attr.colorPrimary)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
protected fun dataChanged() {
|
||||
showFab()
|
||||
}
|
||||
|
@ -329,27 +349,57 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
|
|||
fieldKeyValueMap: Map<FieldKey, String>,
|
||||
artworkInfo: ArtworkInfo?
|
||||
) {
|
||||
RetroUtil.hideSoftKeyboard(this)
|
||||
hideSoftKeyboard()
|
||||
|
||||
hideFab()
|
||||
println(fieldKeyValueMap)
|
||||
WriteTagsAsyncTask(this).execute(
|
||||
LoadingInfo(
|
||||
songPaths,
|
||||
fieldKeyValueMap,
|
||||
artworkInfo
|
||||
)
|
||||
)
|
||||
GlobalScope.launch {
|
||||
if (VersionUtils.hasR()) {
|
||||
cacheFiles = TagWriter.writeTagsToFilesR(
|
||||
this@AbsTagEditorActivity, AudioTagInfo(
|
||||
songPaths,
|
||||
fieldKeyValueMap,
|
||||
artworkInfo
|
||||
)
|
||||
)
|
||||
val pendingIntent = MediaStore.createWriteRequest(contentResolver, getSongUris())
|
||||
|
||||
launcher.launch(IntentSenderRequest.Builder(pendingIntent).build())
|
||||
} else {
|
||||
TagWriter.writeTagsToFiles(
|
||||
this@AbsTagEditorActivity, AudioTagInfo(
|
||||
songPaths,
|
||||
fieldKeyValueMap,
|
||||
artworkInfo
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun writeTags(paths: List<String>?) {
|
||||
WriteTagsAsyncTask(this).execute(
|
||||
LoadingInfo(
|
||||
paths,
|
||||
savedTags,
|
||||
savedArtworkInfo
|
||||
)
|
||||
)
|
||||
GlobalScope.launch {
|
||||
if (VersionUtils.hasR()) {
|
||||
cacheFiles = TagWriter.writeTagsToFilesR(
|
||||
this@AbsTagEditorActivity, AudioTagInfo(
|
||||
paths,
|
||||
savedTags,
|
||||
savedArtworkInfo
|
||||
)
|
||||
)
|
||||
val pendingIntent = MediaStore.createWriteRequest(contentResolver, getSongUris())
|
||||
|
||||
launcher.launch(IntentSenderRequest.Builder(pendingIntent).build())
|
||||
} else {
|
||||
TagWriter.writeTagsToFiles(
|
||||
this@AbsTagEditorActivity, AudioTagInfo(
|
||||
paths,
|
||||
savedTags,
|
||||
savedArtworkInfo
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -388,9 +438,30 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun writeToFiles(songUris: List<Uri>, cacheFiles: List<File>) {
|
||||
if (cacheFiles.size == songUris.size) {
|
||||
for (i in cacheFiles.indices) {
|
||||
contentResolver.openOutputStream(songUris[i])?.use { output ->
|
||||
cacheFiles[i].inputStream().use { input ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
TagWriter.scan(this@AbsTagEditorActivity, getSongPaths())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
// Delete Cache Files
|
||||
cacheFiles.forEach { file ->
|
||||
file.delete()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
const val EXTRA_ID = "extra_id"
|
||||
const val EXTRA_PALETTE = "extra_palette"
|
||||
private val TAG = AbsTagEditorActivity::class.java.simpleName
|
||||
|
|
|
@ -22,33 +22,34 @@ import android.graphics.Color
|
|||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
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 androidx.core.widget.doAfterTextChanged
|
||||
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.extensions.appHandleColor
|
||||
import code.name.monkey.retromusic.glide.palette.BitmapPaletteTranscoder
|
||||
import code.name.monkey.retromusic.databinding.ActivityAlbumTagEditorBinding
|
||||
import code.name.monkey.retromusic.extensions.*
|
||||
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.MusicUtil
|
||||
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 com.google.android.material.shape.MaterialShapeDrawable
|
||||
import org.jaudiotagger.tag.FieldKey
|
||||
import java.util.*
|
||||
|
||||
class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
|
||||
class AlbumTagEditorActivity : AbsTagEditorActivity<ActivityAlbumTagEditorBinding>() {
|
||||
|
||||
override val contentViewLayout: Int
|
||||
get() = R.layout.activity_album_tag_editor
|
||||
override val bindingInflater: (LayoutInflater) -> ActivityAlbumTagEditorBinding =
|
||||
ActivityAlbumTagEditorBinding::inflate
|
||||
|
||||
private fun windowEnterTransition() {
|
||||
val slide = Slide()
|
||||
|
@ -60,54 +61,19 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
|
|||
window.enterTransition = slide
|
||||
}
|
||||
|
||||
override fun loadImageFromFile(selectedFile: Uri?) {
|
||||
|
||||
Glide.with(this@AlbumTagEditorActivity).load(selectedFile).asBitmap()
|
||||
.transcode(BitmapPaletteTranscoder(this), BitmapPaletteWrapper::class.java)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE).skipMemoryCache(true)
|
||||
.into(object : SimpleTarget<BitmapPaletteWrapper>() {
|
||||
override fun onResourceReady(
|
||||
resource: BitmapPaletteWrapper?,
|
||||
glideAnimation: GlideAnimation<in BitmapPaletteWrapper>?
|
||||
) {
|
||||
getColor(resource?.palette, Color.TRANSPARENT)
|
||||
albumArtBitmap = resource?.bitmap?.let { ImageUtil.resizeBitmap(it, 2048) }
|
||||
setImageBitmap(
|
||||
albumArtBitmap,
|
||||
getColor(
|
||||
resource?.palette,
|
||||
ATHUtil.resolveColor(
|
||||
this@AlbumTagEditorActivity,
|
||||
R.attr.defaultFooterColor
|
||||
)
|
||||
)
|
||||
)
|
||||
deleteAlbumArt = false
|
||||
dataChanged()
|
||||
setResult(Activity.RESULT_OK)
|
||||
}
|
||||
|
||||
override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) {
|
||||
super.onLoadFailed(e, errorDrawable)
|
||||
Toast.makeText(this@AlbumTagEditorActivity, e.toString(), Toast.LENGTH_LONG)
|
||||
.show()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private var albumArtBitmap: Bitmap? = null
|
||||
private var deleteAlbumArt: Boolean = false
|
||||
|
||||
private fun setupToolbar() {
|
||||
toolbar.setBackgroundColor(ATHUtil.resolveColor(this, R.attr.colorSurface))
|
||||
setSupportActionBar(toolbar)
|
||||
setSupportActionBar(binding.toolbar)
|
||||
binding.appBarLayout?.statusBarForeground =
|
||||
MaterialShapeDrawable.createWithElevationOverlay(this)
|
||||
}
|
||||
|
||||
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 +82,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)
|
||||
binding.yearContainer.setTint(false)
|
||||
binding.genreContainer.setTint(false)
|
||||
binding.albumTitleContainer.setTint(false)
|
||||
binding.albumArtistContainer.setTint(false)
|
||||
|
||||
albumText.appHandleColor().addTextChangedListener(this)
|
||||
albumArtistText.appHandleColor().addTextChangedListener(this)
|
||||
genreTitle.appHandleColor().addTextChangedListener(this)
|
||||
yearTitle.appHandleColor().addTextChangedListener(this)
|
||||
binding.albumText.appHandleColor().doAfterTextChanged { dataChanged() }
|
||||
binding.albumArtistText.appHandleColor().doAfterTextChanged { dataChanged() }
|
||||
binding.genreTitle.appHandleColor().doAfterTextChanged { dataChanged() }
|
||||
binding.yearTitle.appHandleColor().doAfterTextChanged { dataChanged() }
|
||||
}
|
||||
|
||||
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() {
|
||||
|
@ -140,41 +107,68 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
|
|||
bitmap,
|
||||
getColor(
|
||||
generatePalette(bitmap),
|
||||
ATHUtil.resolveColor(this, R.attr.defaultFooterColor)
|
||||
defaultFooterColor()
|
||||
)
|
||||
)
|
||||
deleteAlbumArt = false
|
||||
}
|
||||
|
||||
private fun toastLoadingFailed() {
|
||||
Toast.makeText(
|
||||
this@AlbumTagEditorActivity,
|
||||
R.string.could_not_download_album_cover,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
showToast(R.string.could_not_download_album_cover)
|
||||
}
|
||||
|
||||
override fun searchImageOnWeb() {
|
||||
searchWebFor(albumText.text.toString(), albumArtistText.text.toString())
|
||||
searchWebFor(binding.albumText.text.toString(), binding.albumArtistText.text.toString())
|
||||
}
|
||||
|
||||
override fun deleteImage() {
|
||||
setImageBitmap(
|
||||
BitmapFactory.decodeResource(resources, R.drawable.default_audio_art),
|
||||
ATHUtil.resolveColor(this, R.attr.defaultFooterColor)
|
||||
defaultFooterColor()
|
||||
)
|
||||
deleteAlbumArt = true
|
||||
dataChanged()
|
||||
}
|
||||
|
||||
override fun loadImageFromFile(selectedFile: Uri?) {
|
||||
GlideApp.with(this@AlbumTagEditorActivity).asBitmapPalette().load(selectedFile)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE).skipMemoryCache(true)
|
||||
.into(object : ImageViewTarget<BitmapPaletteWrapper>(binding.editorImage) {
|
||||
override fun onResourceReady(
|
||||
resource: BitmapPaletteWrapper,
|
||||
transition: Transition<in BitmapPaletteWrapper>?
|
||||
) {
|
||||
getColor(resource.palette, Color.TRANSPARENT)
|
||||
albumArtBitmap = resource.bitmap?.let { ImageUtil.resizeBitmap(it, 2048) }
|
||||
setImageBitmap(
|
||||
albumArtBitmap,
|
||||
getColor(
|
||||
resource.palette,
|
||||
defaultFooterColor()
|
||||
)
|
||||
)
|
||||
deleteAlbumArt = false
|
||||
dataChanged()
|
||||
setResult(Activity.RESULT_OK)
|
||||
}
|
||||
|
||||
override fun onLoadFailed(errorDrawable: Drawable?) {
|
||||
super.onLoadFailed(errorDrawable)
|
||||
showToast(R.string.error_load_failed, Toast.LENGTH_LONG)
|
||||
}
|
||||
|
||||
override fun setResource(resource: BitmapPaletteWrapper?) {}
|
||||
})
|
||||
}
|
||||
|
||||
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,
|
||||
|
@ -191,21 +185,29 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
|
|||
.map(Song::data)
|
||||
}
|
||||
|
||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
|
||||
}
|
||||
|
||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||
}
|
||||
|
||||
override fun afterTextChanged(s: Editable) {
|
||||
dataChanged()
|
||||
override fun getSongUris(): List<Uri> = repository.albumById(id).songs.map {
|
||||
MusicUtil.getSongFileUri(it.id)
|
||||
}
|
||||
|
||||
override fun setColors(color: Int) {
|
||||
super.setColors(color)
|
||||
saveFab.backgroundTintList = ColorStateList.valueOf(color)
|
||||
saveFab.backgroundTintList = ColorStateList.valueOf(color)
|
||||
ColorStateList.valueOf(
|
||||
MaterialValueHelper.getPrimaryTextColor(
|
||||
this,
|
||||
color.isColorLight
|
||||
)
|
||||
).also {
|
||||
saveFab.iconTint = it
|
||||
saveFab.setTextColor(it)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override val editorImage: ImageView
|
||||
get() = binding.editorImage
|
||||
|
||||
companion object {
|
||||
|
||||
val TAG: String = AlbumTagEditorActivity::class.java.simpleName
|
||||
|
|
|
@ -14,110 +14,196 @@
|
|||
*/
|
||||
package code.name.monkey.retromusic.activities.tageditor
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import code.name.monkey.appthemehelper.util.ATHUtil
|
||||
import code.name.monkey.appthemehelper.util.MaterialUtil
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.ImageView
|
||||
import android.widget.Toast
|
||||
import androidx.core.widget.doAfterTextChanged
|
||||
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.extensions.appHandleColor
|
||||
import code.name.monkey.retromusic.databinding.ActivitySongTagEditorBinding
|
||||
import code.name.monkey.retromusic.extensions.*
|
||||
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.repository.SongRepository
|
||||
import kotlinx.android.synthetic.main.activity_song_tag_editor.*
|
||||
import code.name.monkey.retromusic.util.ImageUtil
|
||||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
import code.name.monkey.retromusic.util.RetroColorUtil
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.request.target.ImageViewTarget
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
import com.google.android.material.shape.MaterialShapeDrawable
|
||||
import org.jaudiotagger.tag.FieldKey
|
||||
import org.koin.android.ext.android.inject
|
||||
import java.util.*
|
||||
|
||||
class SongTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
|
||||
class SongTagEditorActivity : AbsTagEditorActivity<ActivitySongTagEditorBinding>() {
|
||||
|
||||
override val bindingInflater: (LayoutInflater) -> ActivitySongTagEditorBinding =
|
||||
ActivitySongTagEditorBinding::inflate
|
||||
|
||||
override val contentViewLayout: Int
|
||||
get() = R.layout.activity_song_tag_editor
|
||||
|
||||
private val songRepository by inject<SongRepository>()
|
||||
|
||||
private var albumArtBitmap: Bitmap? = null
|
||||
private var deleteAlbumArt: Boolean = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setNoImageMode()
|
||||
setUpViews()
|
||||
toolbar.setBackgroundColor(ATHUtil.resolveColor(this, R.attr.colorSurface))
|
||||
setSupportActionBar(toolbar)
|
||||
setSupportActionBar(binding.toolbar)
|
||||
binding.appBarLayout?.statusBarForeground =
|
||||
MaterialShapeDrawable.createWithElevationOverlay(this)
|
||||
}
|
||||
|
||||
@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)
|
||||
binding.songTextContainer.setTint(false)
|
||||
binding.composerContainer.setTint(false)
|
||||
binding.albumTextContainer.setTint(false)
|
||||
binding.artistContainer.setTint(false)
|
||||
binding.albumArtistContainer.setTint(false)
|
||||
binding.yearContainer.setTint(false)
|
||||
binding.genreContainer.setTint(false)
|
||||
binding.trackNumberContainer.setTint(false)
|
||||
binding.discNumberContainer.setTint(false)
|
||||
binding.lyricsContainer.setTint(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().doAfterTextChanged { dataChanged() }
|
||||
binding.albumText.appHandleColor().doAfterTextChanged { dataChanged() }
|
||||
binding.albumArtistText.appHandleColor().doAfterTextChanged { dataChanged() }
|
||||
binding.artistText.appHandleColor().doAfterTextChanged { dataChanged() }
|
||||
binding.genreText.appHandleColor().doAfterTextChanged { dataChanged() }
|
||||
binding.yearText.appHandleColor().doAfterTextChanged { dataChanged() }
|
||||
binding.trackNumberText.appHandleColor().doAfterTextChanged { dataChanged() }
|
||||
binding.discNumberText.appHandleColor().doAfterTextChanged { dataChanged() }
|
||||
binding.lyricsText.appHandleColor().doAfterTextChanged { dataChanged() }
|
||||
binding.songComposerText.appHandleColor().doAfterTextChanged { dataChanged() }
|
||||
}
|
||||
|
||||
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.discNumberText.setText(discNumber)
|
||||
binding.lyricsText.setText(lyrics)
|
||||
binding.songComposerText.setText(composer)
|
||||
println(songTitle + songYear)
|
||||
}
|
||||
|
||||
override fun loadCurrentImage() {
|
||||
val bitmap = albumArt
|
||||
setImageBitmap(
|
||||
bitmap,
|
||||
RetroColorUtil.getColor(
|
||||
RetroColorUtil.generatePalette(bitmap),
|
||||
defaultFooterColor()
|
||||
)
|
||||
)
|
||||
deleteAlbumArt = false
|
||||
}
|
||||
|
||||
override fun searchImageOnWeb() {
|
||||
searchWebFor(binding.songText.text.toString(), binding.artistText.text.toString())
|
||||
}
|
||||
|
||||
override fun deleteImage() {
|
||||
setImageBitmap(
|
||||
BitmapFactory.decodeResource(resources, R.drawable.default_audio_art),
|
||||
defaultFooterColor()
|
||||
)
|
||||
deleteAlbumArt = true
|
||||
dataChanged()
|
||||
}
|
||||
|
||||
override fun setColors(color: Int) {
|
||||
super.setColors(color)
|
||||
saveFab.backgroundTintList = ColorStateList.valueOf(color)
|
||||
ColorStateList.valueOf(
|
||||
MaterialValueHelper.getPrimaryTextColor(
|
||||
this,
|
||||
color.isColorLight
|
||||
)
|
||||
).also {
|
||||
saveFab.iconTint = it
|
||||
saveFab.setTextColor(it)
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
writeValuesToFiles(fieldKeyValueMap, null)
|
||||
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.DISC_NO] = binding.discNumberText.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, when {
|
||||
deleteAlbumArt -> ArtworkInfo(id, null)
|
||||
albumArtBitmap == null -> null
|
||||
else -> ArtworkInfo(id, albumArtBitmap!!)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun getSongPaths(): List<String> = listOf(songRepository.song(id).data)
|
||||
|
||||
override fun getSongUris(): List<Uri> = listOf(MusicUtil.getSongFileUri(id))
|
||||
|
||||
override fun loadImageFromFile(selectedFile: Uri?) {
|
||||
}
|
||||
GlideApp.with(this@SongTagEditorActivity).asBitmapPalette().load(selectedFile)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE).skipMemoryCache(true)
|
||||
.into(object : ImageViewTarget<BitmapPaletteWrapper>(binding.editorImage) {
|
||||
override fun onResourceReady(
|
||||
resource: BitmapPaletteWrapper,
|
||||
transition: Transition<in BitmapPaletteWrapper>?
|
||||
) {
|
||||
RetroColorUtil.getColor(resource.palette, Color.TRANSPARENT)
|
||||
albumArtBitmap = resource.bitmap?.let { ImageUtil.resizeBitmap(it, 2048) }
|
||||
setImageBitmap(
|
||||
albumArtBitmap,
|
||||
RetroColorUtil.getColor(
|
||||
resource.palette,
|
||||
defaultFooterColor()
|
||||
)
|
||||
)
|
||||
deleteAlbumArt = false
|
||||
dataChanged()
|
||||
setResult(Activity.RESULT_OK)
|
||||
}
|
||||
|
||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
|
||||
}
|
||||
override fun onLoadFailed(errorDrawable: Drawable?) {
|
||||
super.onLoadFailed(errorDrawable)
|
||||
showToast(R.string.error_load_failed, Toast.LENGTH_LONG)
|
||||
}
|
||||
|
||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||
}
|
||||
|
||||
override fun afterTextChanged(s: Editable) {
|
||||
dataChanged()
|
||||
override fun setResource(resource: BitmapPaletteWrapper?) {}
|
||||
})
|
||||
}
|
||||
|
||||
companion object {
|
||||
val TAG: String = SongTagEditorActivity::class.java.simpleName
|
||||
}
|
||||
|
||||
override val editorImage: ImageView
|
||||
get() = binding.editorImage
|
||||
}
|
||||
|
|
|
@ -0,0 +1,201 @@
|
|||
package code.name.monkey.retromusic.activities.tageditor
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.media.MediaScannerConnection
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import code.name.monkey.retromusic.extensions.showToast
|
||||
import code.name.monkey.retromusic.misc.UpdateToastMediaScannerCompletionListener
|
||||
import code.name.monkey.retromusic.model.AudioTagInfo
|
||||
import code.name.monkey.retromusic.util.MusicUtil.createAlbumArtFile
|
||||
import code.name.monkey.retromusic.util.MusicUtil.deleteAlbumArt
|
||||
import code.name.monkey.retromusic.util.MusicUtil.insertAlbumArt
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jaudiotagger.audio.AudioFileIO
|
||||
import org.jaudiotagger.audio.exceptions.CannotReadException
|
||||
import org.jaudiotagger.audio.exceptions.CannotWriteException
|
||||
import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException
|
||||
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException
|
||||
import org.jaudiotagger.tag.TagException
|
||||
import org.jaudiotagger.tag.images.AndroidArtwork
|
||||
import org.jaudiotagger.tag.images.Artwork
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
class TagWriter {
|
||||
|
||||
companion object {
|
||||
|
||||
suspend fun scan(context: Context, toBeScanned: List<String?>?) {
|
||||
if (toBeScanned == null || toBeScanned.isEmpty()) {
|
||||
Log.i("scan", "scan: Empty")
|
||||
context.showToast( "Scan file from folder")
|
||||
return
|
||||
}
|
||||
MediaScannerConnection.scanFile(
|
||||
context,
|
||||
toBeScanned.toTypedArray(),
|
||||
null,
|
||||
withContext(Dispatchers.Main) {
|
||||
if (context is Activity) UpdateToastMediaScannerCompletionListener(
|
||||
context, toBeScanned
|
||||
) else null
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun writeTagsToFiles(context: Context, info: AudioTagInfo) {
|
||||
withContext(Dispatchers.IO) {
|
||||
runCatching {
|
||||
var artwork: Artwork? = null
|
||||
var albumArtFile: File? = null
|
||||
if (info.artworkInfo?.artwork != null) {
|
||||
try {
|
||||
albumArtFile = createAlbumArtFile(context).canonicalFile
|
||||
info.artworkInfo.artwork.compress(
|
||||
Bitmap.CompressFormat.JPEG,
|
||||
100,
|
||||
albumArtFile.outputStream()
|
||||
)
|
||||
artwork = AndroidArtwork.createArtworkFromFile(albumArtFile)
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
var wroteArtwork = false
|
||||
var deletedArtwork = false
|
||||
for (filePath in info.filePaths!!) {
|
||||
try {
|
||||
val audioFile = AudioFileIO.read(File(filePath))
|
||||
val tag = audioFile.tagOrCreateAndSetDefault
|
||||
if (info.fieldKeyValueMap != null) {
|
||||
for ((key, value) in info.fieldKeyValueMap) {
|
||||
try {
|
||||
tag.setField(key, value)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (info.artworkInfo != null) {
|
||||
if (info.artworkInfo.artwork == null) {
|
||||
tag.deleteArtworkField()
|
||||
deletedArtwork = true
|
||||
} else if (artwork != null) {
|
||||
tag.deleteArtworkField()
|
||||
tag.setField(artwork)
|
||||
wroteArtwork = true
|
||||
}
|
||||
}
|
||||
audioFile.commit()
|
||||
} catch (e: CannotReadException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: CannotWriteException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: TagException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: ReadOnlyFileException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: InvalidAudioFrameException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
if (wroteArtwork) {
|
||||
insertAlbumArt(context, info.artworkInfo!!.albumId, albumArtFile!!.path)
|
||||
} else if (deletedArtwork) {
|
||||
deleteAlbumArt(context, info.artworkInfo!!.albumId)
|
||||
}
|
||||
scan(context, info.filePaths)
|
||||
}.onFailure {
|
||||
it.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
suspend fun writeTagsToFilesR(context: Context, info: AudioTagInfo): List<File> =
|
||||
withContext(Dispatchers.IO) {
|
||||
val cacheFiles = mutableListOf<File>()
|
||||
runCatching {
|
||||
var artwork: Artwork? = null
|
||||
var albumArtFile: File? = null
|
||||
if (info.artworkInfo?.artwork != null) {
|
||||
try {
|
||||
albumArtFile = createAlbumArtFile(context).canonicalFile
|
||||
info.artworkInfo.artwork.compress(
|
||||
Bitmap.CompressFormat.JPEG,
|
||||
100,
|
||||
albumArtFile.outputStream()
|
||||
)
|
||||
artwork = AndroidArtwork.createArtworkFromFile(albumArtFile)
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
var wroteArtwork = false
|
||||
var deletedArtwork = false
|
||||
for (filePath in info.filePaths!!) {
|
||||
try {
|
||||
val originFile = File(filePath)
|
||||
val cacheFile = File(context.cacheDir, originFile.name)
|
||||
cacheFiles.add(cacheFile)
|
||||
originFile.inputStream().use { input ->
|
||||
cacheFile.outputStream().use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
val audioFile = AudioFileIO.read(cacheFile)
|
||||
val tag = audioFile.tagOrCreateAndSetDefault
|
||||
if (info.fieldKeyValueMap != null) {
|
||||
for ((key, value) in info.fieldKeyValueMap) {
|
||||
try {
|
||||
tag.setField(key, value)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (info.artworkInfo != null) {
|
||||
if (info.artworkInfo.artwork == null) {
|
||||
tag.deleteArtworkField()
|
||||
deletedArtwork = true
|
||||
} else if (artwork != null) {
|
||||
tag.deleteArtworkField()
|
||||
tag.setField(artwork)
|
||||
wroteArtwork = true
|
||||
}
|
||||
}
|
||||
audioFile.commit()
|
||||
} catch (e: CannotReadException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: CannotWriteException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: TagException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: ReadOnlyFileException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: InvalidAudioFrameException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
if (wroteArtwork) {
|
||||
insertAlbumArt(context, info.artworkInfo!!.albumId, albumArtFile!!.path)
|
||||
} else if (deletedArtwork) {
|
||||
deleteAlbumArt(context, info.artworkInfo!!.albumId)
|
||||
}
|
||||
}.onFailure {
|
||||
it.printStackTrace()
|
||||
}
|
||||
cacheFiles
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,152 +0,0 @@
|
|||
package code.name.monkey.retromusic.activities.tageditor;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.media.MediaScannerConnection;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
|
||||
import org.jaudiotagger.audio.AudioFile;
|
||||
import org.jaudiotagger.audio.AudioFileIO;
|
||||
import org.jaudiotagger.audio.exceptions.CannotReadException;
|
||||
import org.jaudiotagger.audio.exceptions.CannotWriteException;
|
||||
import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException;
|
||||
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException;
|
||||
import org.jaudiotagger.tag.FieldKey;
|
||||
import org.jaudiotagger.tag.Tag;
|
||||
import org.jaudiotagger.tag.TagException;
|
||||
import org.jaudiotagger.tag.images.Artwork;
|
||||
import org.jaudiotagger.tag.images.ArtworkFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import code.name.monkey.retromusic.R;
|
||||
import code.name.monkey.retromusic.misc.DialogAsyncTask;
|
||||
import code.name.monkey.retromusic.misc.UpdateToastMediaScannerCompletionListener;
|
||||
import code.name.monkey.retromusic.model.LoadingInfo;
|
||||
import code.name.monkey.retromusic.util.MusicUtil;
|
||||
|
||||
public class WriteTagsAsyncTask extends DialogAsyncTask<LoadingInfo, Integer, List<String>> {
|
||||
|
||||
public WriteTagsAsyncTask(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> doInBackground(LoadingInfo... params) {
|
||||
try {
|
||||
LoadingInfo info = params[0];
|
||||
|
||||
Artwork artwork = null;
|
||||
File albumArtFile = null;
|
||||
if (info.getArtworkInfo() != null && info.getArtworkInfo().getArtwork() != null) {
|
||||
try {
|
||||
albumArtFile = MusicUtil.INSTANCE.createAlbumArtFile().getCanonicalFile();
|
||||
info.getArtworkInfo().getArtwork().compress(Bitmap.CompressFormat.PNG, 0, new FileOutputStream(albumArtFile));
|
||||
artwork = ArtworkFactory.createArtworkFromFile(albumArtFile);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
int counter = 0;
|
||||
boolean wroteArtwork = false;
|
||||
boolean deletedArtwork = false;
|
||||
for (String filePath : info.getFilePaths()) {
|
||||
publishProgress(++counter, info.getFilePaths().size());
|
||||
try {
|
||||
AudioFile audioFile = AudioFileIO.read(new File(filePath));
|
||||
Tag tag = audioFile.getTagOrCreateAndSetDefault();
|
||||
|
||||
if (info.getFieldKeyValueMap() != null) {
|
||||
for (Map.Entry<FieldKey, String> entry : info.getFieldKeyValueMap().entrySet()) {
|
||||
try {
|
||||
tag.setField(entry.getKey(), entry.getValue());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (info.getArtworkInfo() != null) {
|
||||
if (info.getArtworkInfo().getArtwork() == null) {
|
||||
tag.deleteArtworkField();
|
||||
deletedArtwork = true;
|
||||
} else if (artwork != null) {
|
||||
tag.deleteArtworkField();
|
||||
tag.setField(artwork);
|
||||
wroteArtwork = true;
|
||||
}
|
||||
}
|
||||
|
||||
audioFile.commit();
|
||||
} catch (@NonNull CannotReadException | IOException | CannotWriteException | TagException | ReadOnlyFileException | InvalidAudioFrameException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
Context context = getContext();
|
||||
if (context != null) {
|
||||
if (wroteArtwork) {
|
||||
MusicUtil.INSTANCE.
|
||||
insertAlbumArt(context, info.getArtworkInfo().getAlbumId(), albumArtFile.getPath());
|
||||
} else if (deletedArtwork) {
|
||||
MusicUtil.INSTANCE.deleteAlbumArt(context, info.getArtworkInfo().getAlbumId());
|
||||
}
|
||||
}
|
||||
|
||||
return info.getFilePaths();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(List<String> toBeScanned) {
|
||||
super.onPostExecute(toBeScanned);
|
||||
scan(toBeScanned);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled(List<String> toBeScanned) {
|
||||
super.onCancelled(toBeScanned);
|
||||
scan(toBeScanned);
|
||||
}
|
||||
|
||||
private void scan(List<String> toBeScanned) {
|
||||
Context context = getContext();
|
||||
if (toBeScanned == null || toBeScanned.isEmpty()) {
|
||||
Log.i("scan", "scan: Empty");
|
||||
Toast.makeText(context, "Scan file from folder", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
MediaScannerConnection.scanFile(context, toBeScanned.toArray(new String[0]), null, context instanceof Activity ? new UpdateToastMediaScannerCompletionListener((Activity) context, toBeScanned) : null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Dialog createDialog(@NonNull Context context) {
|
||||
return new MaterialDialog.Builder(context)
|
||||
.title(R.string.saving_changes)
|
||||
.cancelable(false)
|
||||
.progress(false, 0)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(@NonNull Dialog dialog, Integer... values) {
|
||||
super.onProgressUpdate(dialog, values);
|
||||
((MaterialDialog) dialog).setMaxProgress(values[1]);
|
||||
((MaterialDialog) dialog).setProgress(values[0]);
|
||||
}
|
||||
}
|
|
@ -1,140 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
package code.name.monkey.retromusic.adapter;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
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 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 {
|
||||
|
||||
private List<CategoryInfo> categoryInfos;
|
||||
private ItemTouchHelper touchHelper;
|
||||
|
||||
public CategoryInfoAdapter() {
|
||||
SwipeAndDragHelper swipeAndDragHelper = new SwipeAndDragHelper(this);
|
||||
touchHelper = new ItemTouchHelper(swipeAndDragHelper);
|
||||
}
|
||||
|
||||
public void attachToRecyclerView(RecyclerView recyclerView) {
|
||||
touchHelper.attachToRecyclerView(recyclerView);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public List<CategoryInfo> getCategoryInfos() {
|
||||
return categoryInfos;
|
||||
}
|
||||
|
||||
public void setCategoryInfos(@NonNull List<CategoryInfo> categoryInfos) {
|
||||
this.categoryInfos = categoryInfos;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return categoryInfos.size();
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull CategoryInfoAdapter.ViewHolder holder, int position) {
|
||||
CategoryInfo categoryInfo = categoryInfos.get(position);
|
||||
|
||||
holder.checkBox.setChecked(categoryInfo.isVisible());
|
||||
holder.title.setText(
|
||||
holder.title.getResources().getString(categoryInfo.getCategory().getStringRes()));
|
||||
|
||||
holder.itemView.setOnClickListener(
|
||||
v -> {
|
||||
if (!(categoryInfo.isVisible() && isLastCheckedCategory(categoryInfo))) {
|
||||
categoryInfo.setVisible(!categoryInfo.isVisible());
|
||||
holder.checkBox.setChecked(categoryInfo.isVisible());
|
||||
} else {
|
||||
Toast.makeText(
|
||||
holder.itemView.getContext(),
|
||||
R.string.you_have_to_select_at_least_one_category,
|
||||
Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
}
|
||||
});
|
||||
|
||||
holder.dragView.setOnTouchListener(
|
||||
(view, event) -> {
|
||||
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
|
||||
touchHelper.startDrag(holder);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public CategoryInfoAdapter.ViewHolder onCreateViewHolder(
|
||||
@NonNull ViewGroup parent, int viewType) {
|
||||
View view =
|
||||
LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.preference_dialog_library_categories_listitem, parent, false);
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewMoved(int oldPosition, int newPosition) {
|
||||
CategoryInfo categoryInfo = categoryInfos.get(oldPosition);
|
||||
categoryInfos.remove(oldPosition);
|
||||
categoryInfos.add(newPosition, categoryInfo);
|
||||
notifyItemMoved(oldPosition, newPosition);
|
||||
}
|
||||
|
||||
private boolean isLastCheckedCategory(CategoryInfo categoryInfo) {
|
||||
if (categoryInfo.isVisible()) {
|
||||
for (CategoryInfo c : categoryInfos) {
|
||||
if (c != categoryInfo && c.isVisible()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
private MaterialCheckBox checkBox;
|
||||
private View dragView;
|
||||
private TextView title;
|
||||
|
||||
ViewHolder(View view) {
|
||||
super(view);
|
||||
checkBox = view.findViewById(R.id.checkbox);
|
||||
checkBox.setButtonTintList(
|
||||
ColorStateList.valueOf(ThemeStore.Companion.accentColor(checkBox.getContext())));
|
||||
title = view.findViewById(R.id.title);
|
||||
dragView = view.findViewById(R.id.drag_view);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* 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.adapter
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.res.ColorStateList
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import code.name.monkey.appthemehelper.ThemeStore.Companion.accentColor
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.databinding.PreferenceDialogLibraryCategoriesListitemBinding
|
||||
import code.name.monkey.retromusic.extensions.showToast
|
||||
import code.name.monkey.retromusic.model.CategoryInfo
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import code.name.monkey.retromusic.util.SwipeAndDragHelper
|
||||
import code.name.monkey.retromusic.util.SwipeAndDragHelper.ActionCompletionContract
|
||||
|
||||
class CategoryInfoAdapter : RecyclerView.Adapter<CategoryInfoAdapter.ViewHolder>(),
|
||||
ActionCompletionContract {
|
||||
var categoryInfos: MutableList<CategoryInfo> =
|
||||
PreferenceUtil.libraryCategory.toMutableList()
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
set(value) {
|
||||
field = value
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
private val touchHelper: ItemTouchHelper
|
||||
fun attachToRecyclerView(recyclerView: RecyclerView?) {
|
||||
touchHelper.attachToRecyclerView(recyclerView)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return categoryInfos.size
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val categoryInfo = categoryInfos[position]
|
||||
holder.binding.checkbox.isChecked = categoryInfo.visible
|
||||
holder.binding.title.text =
|
||||
holder.binding.title.resources.getString(categoryInfo.category.stringRes)
|
||||
holder.itemView.setOnClickListener {
|
||||
if (!(categoryInfo.visible && isLastCheckedCategory(categoryInfo))) {
|
||||
categoryInfo.visible = !categoryInfo.visible
|
||||
holder.binding.checkbox.isChecked = categoryInfo.visible
|
||||
} else {
|
||||
holder.itemView.context.showToast(R.string.you_have_to_select_at_least_one_category)
|
||||
}
|
||||
}
|
||||
holder.binding.dragView.setOnTouchListener { _: View?, event: MotionEvent ->
|
||||
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
|
||||
touchHelper.startDrag(holder)
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(
|
||||
parent: ViewGroup, viewType: Int
|
||||
): ViewHolder {
|
||||
return ViewHolder(
|
||||
PreferenceDialogLibraryCategoriesListitemBinding.inflate(
|
||||
LayoutInflater.from(
|
||||
parent.context
|
||||
), parent, false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onViewMoved(oldPosition: Int, newPosition: Int) {
|
||||
val categoryInfo = categoryInfos[oldPosition]
|
||||
categoryInfos.removeAt(oldPosition)
|
||||
categoryInfos.add(newPosition, categoryInfo)
|
||||
notifyItemMoved(oldPosition, newPosition)
|
||||
}
|
||||
|
||||
private fun isLastCheckedCategory(categoryInfo: CategoryInfo): Boolean {
|
||||
if (categoryInfo.visible) {
|
||||
for (c in categoryInfos) {
|
||||
if (c !== categoryInfo && c.visible) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
class ViewHolder(val binding: PreferenceDialogLibraryCategoriesListitemBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
init {
|
||||
binding.checkbox.buttonTintList =
|
||||
ColorStateList.valueOf(accentColor(binding.checkbox.context))
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
val swipeAndDragHelper = SwipeAndDragHelper(this)
|
||||
touchHelper = ItemTouchHelper(swipeAndDragHelper)
|
||||
}
|
||||
}
|
|
@ -14,15 +14,14 @@
|
|||
*/
|
||||
package code.name.monkey.retromusic.adapter
|
||||
|
||||
import android.app.Activity
|
||||
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 code.name.monkey.retromusic.extensions.openUrl
|
||||
import code.name.monkey.retromusic.model.Contributor
|
||||
import code.name.monkey.retromusic.util.RetroUtil.openUrl
|
||||
import code.name.monkey.retromusic.views.RetroShapeableImageView
|
||||
import com.bumptech.glide.Glide
|
||||
|
||||
|
@ -65,7 +64,7 @@ class ContributorAdapter(
|
|||
val contributor = contributors[position]
|
||||
holder.bindData(contributor)
|
||||
holder.itemView.setOnClickListener {
|
||||
openUrl(it?.context as Activity, contributors[position].link)
|
||||
it?.context?.openUrl(contributors[position].link)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,16 +14,22 @@
|
|||
*/
|
||||
package code.name.monkey.retromusic.adapter
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
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.databinding.ItemGenreBinding
|
||||
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.*
|
||||
|
||||
/**
|
||||
|
@ -33,37 +39,72 @@ import java.util.*
|
|||
class GenreAdapter(
|
||||
private val activity: FragmentActivity,
|
||||
var dataSet: List<Genre>,
|
||||
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))
|
||||
return ViewHolder(ItemGenreBinding.inflate(LayoutInflater.from(activity), parent, false))
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val genre = dataSet[position]
|
||||
holder.title?.text = genre.name
|
||||
holder.text?.text = String.format(
|
||||
holder.binding.title.text = genre.name
|
||||
holder.binding.text.text = String.format(
|
||||
Locale.getDefault(),
|
||||
"%d %s",
|
||||
genre.songCount,
|
||||
if (genre.songCount > 1) activity.getString(R.string.songs) else activity.getString(R.string.song)
|
||||
)
|
||||
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.binding.image) {
|
||||
override fun onColorReady(colors: MediaNotificationProcessor) {
|
||||
setColors(holder, colors)
|
||||
}
|
||||
})
|
||||
// Just for a bit of shadow around image
|
||||
holder.binding.image.outlineProvider = ViewOutlineProvider.BOUNDS
|
||||
}
|
||||
|
||||
private fun setColors(holder: ViewHolder, color: MediaNotificationProcessor) {
|
||||
holder.binding.imageContainerCard.setCardBackgroundColor(color.backgroundColor)
|
||||
holder.binding.title.setTextColor(color.primaryTextColor)
|
||||
holder.binding.text.setTextColor(color.secondaryTextColor)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return dataSet.size
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun swapDataSet(list: List<Genre>) {
|
||||
dataSet = list
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) {
|
||||
inner class ViewHolder(val binding: ItemGenreBinding) : RecyclerView.ViewHolder(binding.root),
|
||||
View.OnClickListener {
|
||||
override fun onClick(v: View?) {
|
||||
ViewCompat.setTransitionName(itemView, "genre")
|
||||
listener.onClickGenre(dataSet[layoutPosition], itemView)
|
||||
}
|
||||
|
||||
init {
|
||||
itemView.setOnClickListener(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,35 +14,30 @@
|
|||
*/
|
||||
package code.name.monkey.retromusic.adapter
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
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
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import code.name.monkey.appthemehelper.ThemeStore
|
||||
import code.name.monkey.appthemehelper.util.ColorUtil
|
||||
import code.name.monkey.retromusic.*
|
||||
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.helper.MusicPlayerRemote
|
||||
import code.name.monkey.retromusic.fragments.home.HomeFragment
|
||||
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(
|
||||
private val activity: AppCompatActivity
|
||||
|
@ -60,17 +55,10 @@ class HomeAdapter(
|
|||
LayoutInflater.from(activity).inflate(R.layout.section_recycler_view, parent, false)
|
||||
return when (viewType) {
|
||||
RECENT_ARTISTS, TOP_ARTISTS -> ArtistViewHolder(layout)
|
||||
GENRES -> GenreViewHolder(layout)
|
||||
FAVOURITES -> PlaylistViewHolder(layout)
|
||||
TOP_ALBUMS, RECENT_ALBUMS -> AlbumViewHolder(layout)
|
||||
else -> {
|
||||
SuggestionsViewHolder(
|
||||
LayoutInflater.from(activity).inflate(
|
||||
R.layout.item_suggestions,
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
ArtistViewHolder(layout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -82,6 +70,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 +81,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 +92,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,32 +103,24 @@ 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)
|
||||
)
|
||||
}
|
||||
}
|
||||
SUGGESTIONS -> {
|
||||
val viewHolder = holder as SuggestionsViewHolder
|
||||
viewHolder.bindView(home)
|
||||
}
|
||||
FAVOURITES -> {
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
GENRES -> {
|
||||
val viewHolder = holder as GenreViewHolder
|
||||
viewHolder.bind(home)
|
||||
}
|
||||
PLAYLISTS -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -145,6 +128,7 @@ class HomeAdapter(
|
|||
return list.size
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun swapData(sections: List<Home>) {
|
||||
list = sections
|
||||
notifyDataSetChanged()
|
||||
|
@ -170,36 +154,6 @@ class HomeAdapter(
|
|||
}
|
||||
}
|
||||
|
||||
private inner class SuggestionsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
private val images = listOf(
|
||||
R.id.image1,
|
||||
R.id.image2,
|
||||
R.id.image3,
|
||||
R.id.image4,
|
||||
R.id.image5,
|
||||
R.id.image6,
|
||||
R.id.image7,
|
||||
R.id.image8
|
||||
)
|
||||
|
||||
fun bindView(home: Home) {
|
||||
val color = ThemeStore.accentColor(activity)
|
||||
itemView.findViewById<TextView>(R.id.message).setTextColor(color)
|
||||
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)
|
||||
}
|
||||
SongGlideRequest.Builder.from(Glide.with(activity), home.arrayList[index] as Song)
|
||||
.asBitmap()
|
||||
.build()
|
||||
.into(itemView.findViewById(id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inner class PlaylistViewHolder(view: View) : AbsHomeViewItem(view) {
|
||||
fun bindView(home: Home) {
|
||||
title.setText(home.titleRes)
|
||||
|
@ -207,7 +161,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
|
||||
|
@ -215,23 +169,6 @@ class HomeAdapter(
|
|||
}
|
||||
}
|
||||
|
||||
private inner class GenreViewHolder(itemView: View) : AbsHomeViewItem(itemView) {
|
||||
fun bind(home: Home) {
|
||||
arrow.hide()
|
||||
title.setText(home.titleRes)
|
||||
val genreAdapter = GenreAdapter(
|
||||
activity,
|
||||
home.arrayList as List<Genre>,
|
||||
R.layout.item_grid_genre,
|
||||
this@HomeAdapter
|
||||
)
|
||||
recyclerView.apply {
|
||||
layoutManager = GridLayoutManager(activity, 3, GridLayoutManager.HORIZONTAL, false)
|
||||
adapter = genreAdapter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open class AbsHomeViewItem(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
val recyclerView: RecyclerView = itemView.findViewById(R.id.recyclerView)
|
||||
val title: AppCompatTextView = itemView.findViewById(R.id.title)
|
||||
|
@ -257,7 +194,7 @@ class HomeAdapter(
|
|||
bundleOf(EXTRA_ARTIST_ID to artistId),
|
||||
null,
|
||||
FragmentNavigatorExtras(
|
||||
view to "artist"
|
||||
view to artistId.toString()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -268,7 +205,7 @@ class HomeAdapter(
|
|||
bundleOf(EXTRA_ALBUM_ID to albumId),
|
||||
null,
|
||||
FragmentNavigatorExtras(
|
||||
view to "album"
|
||||
view to albumId.toString()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
*/
|
||||
package code.name.monkey.retromusic.adapter
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
@ -27,17 +28,16 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import code.name.monkey.appthemehelper.ThemeStore
|
||||
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.model.Album
|
||||
import code.name.monkey.retromusic.model.Artist
|
||||
import code.name.monkey.retromusic.model.Genre
|
||||
import code.name.monkey.retromusic.model.Song
|
||||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
import com.bumptech.glide.Glide
|
||||
import java.util.*
|
||||
|
||||
class SearchAdapter(
|
||||
|
@ -45,6 +45,7 @@ class SearchAdapter(
|
|||
private var dataSet: List<Any>
|
||||
) : RecyclerView.Adapter<SearchAdapter.ViewHolder>() {
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun swapDataSet(dataSet: List<Any>) {
|
||||
this.dataSet = dataSet
|
||||
notifyDataSetChanged()
|
||||
|
@ -52,9 +53,9 @@ 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
|
||||
if (dataSet[position] is PlaylistWithSongs) return PLAYLIST
|
||||
return if (dataSet[position] is Song) SONG else HEADER
|
||||
}
|
||||
|
||||
|
@ -66,6 +67,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 +89,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
|
||||
|
@ -109,10 +120,18 @@ class SearchAdapter(
|
|||
)
|
||||
}
|
||||
PLAYLIST -> {
|
||||
val playlist = dataSet[position] as PlaylistEntity
|
||||
holder.title?.text = playlist.playlistName
|
||||
val playlist = dataSet[position] as PlaylistWithSongs
|
||||
holder.title?.text = playlist.playlistEntity.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(
|
||||
RetroGlideExtension.getArtistModel(artist)).into(holder.image!!)
|
||||
}
|
||||
else -> {
|
||||
holder.title?.text = dataSet[position].toString()
|
||||
holder.title?.setTextColor(ThemeStore.accentColor(activity))
|
||||
|
@ -120,16 +139,6 @@ class SearchAdapter(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getSongs(playlist: Playlist): List<Song> {
|
||||
val songs = mutableListOf<Song>()
|
||||
if (playlist is AbsSmartPlaylist) {
|
||||
songs.addAll(playlist.getSongs())
|
||||
} else {
|
||||
songs.addAll(PlaylistSongsLoader.getPlaylistSongList(activity, playlist.id))
|
||||
}
|
||||
return songs
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return dataSet.size
|
||||
}
|
||||
|
@ -140,13 +149,13 @@ class SearchAdapter(
|
|||
imageTextContainer?.isInvisible = true
|
||||
if (itemViewType == SONG) {
|
||||
imageTextContainer?.isGone = true
|
||||
menu?.visibility = View.VISIBLE
|
||||
menu?.isVisible = true
|
||||
menu?.setOnClickListener(object : SongMenuHelper.OnClickSongMenu(activity) {
|
||||
override val song: Song
|
||||
get() = dataSet[layoutPosition] as Song
|
||||
})
|
||||
} else {
|
||||
menu?.visibility = View.GONE
|
||||
menu?.isVisible = false
|
||||
}
|
||||
|
||||
when (itemViewType) {
|
||||
|
@ -154,7 +163,7 @@ class SearchAdapter(
|
|||
ARTIST -> setImageTransitionName(activity.getString(R.string.transition_artist_image))
|
||||
else -> {
|
||||
val container = itemView.findViewById<View>(R.id.imageContainer)
|
||||
container?.visibility = View.GONE
|
||||
container?.isVisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -174,6 +183,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,
|
||||
|
@ -187,9 +202,8 @@ class SearchAdapter(
|
|||
)
|
||||
}
|
||||
SONG -> {
|
||||
val playList = mutableListOf<Song>()
|
||||
playList.add(item as Song)
|
||||
MusicPlayerRemote.openQueue(playList, 0, true)
|
||||
MusicPlayerRemote.playNext(item as Song)
|
||||
MusicPlayerRemote.playNextSong()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -202,5 +216,6 @@ class SearchAdapter(
|
|||
private const val SONG = 3
|
||||
private const val GENRE = 4
|
||||
private const val PLAYLIST = 5
|
||||
private const val ALBUM_ARTIST = 6
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,30 +20,32 @@ import android.view.MenuItem
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.isVisible
|
||||
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.extensions.getTintedDrawable
|
||||
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,
|
||||
override val activity: AppCompatActivity,
|
||||
private var dataSet: List<File>,
|
||||
private val itemLayoutRes: Int,
|
||||
private val iCallbacks: ICallbacks?,
|
||||
iCabHolder: ICabHolder?
|
||||
iCabHolder: ICabHolder?,
|
||||
) : AbsMultiSelectAdapter<SongFileAdapter.ViewHolder, File>(
|
||||
activity, iCabHolder, R.menu.menu_media_selection
|
||||
), PopupTextProvider {
|
||||
|
@ -77,7 +79,7 @@ class SongFileAdapter(
|
|||
if (holder.itemViewType == FILE) {
|
||||
holder.text?.text = getFileText(file)
|
||||
} else {
|
||||
holder.text?.visibility = View.GONE
|
||||
holder.text?.isVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -108,17 +110,15 @@ class SongFileAdapter(
|
|||
)
|
||||
)
|
||||
} else {
|
||||
val error = RetroUtil.getTintedVectorDrawable(
|
||||
activity, R.drawable.ic_file_music, iconColor
|
||||
)
|
||||
Glide.with(activity)
|
||||
val error = activity.getTintedDrawable(R.drawable.ic_file_music, iconColor)
|
||||
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,12 +126,12 @@ class SongFileAdapter(
|
|||
return dataSet.size
|
||||
}
|
||||
|
||||
override fun getIdentifier(position: Int): File? {
|
||||
override fun getIdentifier(position: Int): File {
|
||||
return dataSet[position]
|
||||
}
|
||||
|
||||
override fun getName(`object`: File): String {
|
||||
return getFileTitle(`object`)
|
||||
override fun getName(model: File): String {
|
||||
return getFileTitle(model)
|
||||
}
|
||||
|
||||
override fun onMultipleItemAction(menuItem: MenuItem, selection: List<File>) {
|
||||
|
@ -140,7 +140,7 @@ class SongFileAdapter(
|
|||
}
|
||||
|
||||
override fun getPopupText(position: Int): String {
|
||||
return getSectionName(position)
|
||||
return if (position >= dataSet.lastIndex) "" else getSectionName(position)
|
||||
}
|
||||
|
||||
private fun getSectionName(position: Int): String {
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.adapter
|
||||
|
||||
import android.app.Activity
|
||||
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 code.name.monkey.retromusic.extensions.hide
|
||||
import code.name.monkey.retromusic.model.Contributor
|
||||
import code.name.monkey.retromusic.util.RetroUtil
|
||||
import code.name.monkey.retromusic.views.RetroShapeableImageView
|
||||
|
||||
class TranslatorsAdapter(
|
||||
private var contributors: List<Contributor>
|
||||
) : RecyclerView.Adapter<TranslatorsAdapter.ViewHolder>() {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
return ViewHolder(
|
||||
LayoutInflater.from(parent.context).inflate(
|
||||
R.layout.item_contributor,
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val contributor = contributors[position]
|
||||
holder.bindData(contributor)
|
||||
holder.itemView.setOnClickListener {
|
||||
RetroUtil.openUrl(it?.context as Activity, contributors[position].link)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return contributors.size
|
||||
}
|
||||
|
||||
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
val title: TextView = itemView.findViewById(R.id.title)
|
||||
val text: TextView = itemView.findViewById(R.id.text)
|
||||
val image: RetroShapeableImageView = itemView.findViewById(R.id.icon)
|
||||
|
||||
internal fun bindData(contributor: Contributor) {
|
||||
title.text = contributor.name
|
||||
text.text = contributor.summary
|
||||
image.hide()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,12 +19,13 @@ import android.view.LayoutInflater
|
|||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.isVisible
|
||||
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,11 +36,10 @@ 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(
|
||||
val activity: FragmentActivity,
|
||||
override val activity: FragmentActivity,
|
||||
var dataSet: List<Album>,
|
||||
var itemLayoutRes: Int,
|
||||
iCabHolder: ICabHolder?,
|
||||
|
@ -68,12 +68,18 @@ open class AlbumAdapter(
|
|||
return ViewHolder(view)
|
||||
}
|
||||
|
||||
private fun getAlbumTitle(album: Album): String? {
|
||||
private fun getAlbumTitle(album: Album): String {
|
||||
return album.title
|
||||
}
|
||||
|
||||
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,13 @@ open class AlbumAdapter(
|
|||
holder.itemView.isActivated = isChecked
|
||||
holder.title?.text = getAlbumTitle(album)
|
||||
holder.text?.text = getAlbumText(album)
|
||||
// Check if imageContainer exists so we can have a smooth transition without
|
||||
// CardView clipping, if it doesn't exist in current layout set transition name to image instead.
|
||||
if (holder.imageContainer != null) {
|
||||
holder.imageContainer?.transitionName = album.id.toString()
|
||||
} else {
|
||||
holder.image?.transitionName = album.id.toString()
|
||||
}
|
||||
loadAlbumCover(album, holder)
|
||||
}
|
||||
|
||||
|
@ -92,17 +105,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)
|
||||
|
@ -122,8 +135,8 @@ open class AlbumAdapter(
|
|||
return dataSet[position]
|
||||
}
|
||||
|
||||
override fun getName(album: Album): String {
|
||||
return album.title!!
|
||||
override fun getName(model: Album): String {
|
||||
return model.title
|
||||
}
|
||||
|
||||
override fun onMultipleItemAction(
|
||||
|
@ -150,7 +163,7 @@ open class AlbumAdapter(
|
|||
when (PreferenceUtil.albumSortOrder) {
|
||||
SortOrder.AlbumSortOrder.ALBUM_A_Z, SortOrder.AlbumSortOrder.ALBUM_Z_A -> sectionName =
|
||||
dataSet[position].title
|
||||
SortOrder.AlbumSortOrder.ALBUM_ARTIST -> sectionName = dataSet[position].artistName
|
||||
SortOrder.AlbumSortOrder.ALBUM_ARTIST -> sectionName = dataSet[position].albumArtist
|
||||
SortOrder.AlbumSortOrder.ALBUM_YEAR -> return MusicUtil.getYearString(
|
||||
dataSet[position].year
|
||||
)
|
||||
|
@ -161,8 +174,7 @@ open class AlbumAdapter(
|
|||
inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) {
|
||||
|
||||
init {
|
||||
setImageTransitionName("Album")
|
||||
menu?.visibility = View.GONE
|
||||
menu?.isVisible = false
|
||||
}
|
||||
|
||||
override fun onClick(v: View?) {
|
||||
|
@ -171,16 +183,13 @@ open class AlbumAdapter(
|
|||
toggleChecked(layoutPosition)
|
||||
} else {
|
||||
image?.let {
|
||||
ViewCompat.setTransitionName(it, "album")
|
||||
listener?.onAlbumClick(dataSet[layoutPosition].id, it)
|
||||
listener?.onAlbumClick(dataSet[layoutPosition].id, imageContainer ?: it)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLongClick(v: View?): Boolean {
|
||||
toggleChecked(layoutPosition)
|
||||
return super.onLongClick(v)
|
||||
return toggleChecked(layoutPosition)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,22 +19,24 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.activities.MainActivity
|
||||
import code.name.monkey.retromusic.fragments.AlbumCoverStyle
|
||||
import code.name.monkey.retromusic.fragments.NowPlayingScreen.*
|
||||
import code.name.monkey.retromusic.fragments.base.goToLyrics
|
||||
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.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -83,12 +85,12 @@ class AlbumCoverPagerAdapter(
|
|||
|
||||
class AlbumCoverFragment : Fragment() {
|
||||
|
||||
private lateinit var albumCover: ImageView
|
||||
private var isColorReady: Boolean = false
|
||||
private lateinit var color: MediaNotificationProcessor
|
||||
private lateinit var song: Song
|
||||
private var colorReceiver: ColorReceiver? = null
|
||||
private var request: Int = 0
|
||||
private val mainActivity get() = activity as MainActivity
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
@ -103,10 +105,10 @@ class AlbumCoverPagerAdapter(
|
|||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val view = inflater.inflate(getLayoutWithPlayerTheme(), container, false)
|
||||
ViewCompat.setTransitionName(view, "lyrics")
|
||||
albumCover = view.findViewById(R.id.player_image)
|
||||
view.setOnClickListener {
|
||||
showLyricsDialog()
|
||||
if (mainActivity.getBottomSheetBehavior().state == STATE_EXPANDED) {
|
||||
showLyricsDialog()
|
||||
}
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
@ -122,7 +124,7 @@ class AlbumCoverPagerAdapter(
|
|||
setTitle(song.title)
|
||||
setMessage(if (data.isNullOrEmpty()) "No lyrics found" else data)
|
||||
setNegativeButton(R.string.synced_lyrics) { _, _ ->
|
||||
NavigationUtil.goToLyrics(requireActivity())
|
||||
goToLyrics(requireActivity())
|
||||
}
|
||||
show()
|
||||
}
|
||||
|
@ -133,6 +135,7 @@ class AlbumCoverPagerAdapter(
|
|||
private fun getLayoutWithPlayerTheme(): Int {
|
||||
return when (PreferenceUtil.nowPlayingScreen) {
|
||||
Card, Fit, Tiny, Classic, Gradient, Full -> R.layout.fragment_album_full_cover
|
||||
Peek -> R.layout.fragment_peek_album_cover
|
||||
else -> {
|
||||
if (PreferenceUtil.isCarouselEffect) {
|
||||
R.layout.fragment_album_carousel_cover
|
||||
|
@ -142,7 +145,6 @@ class AlbumCoverPagerAdapter(
|
|||
AlbumCoverStyle.Flat -> R.layout.fragment_album_flat_cover
|
||||
AlbumCoverStyle.Circle -> R.layout.fragment_album_circle_cover
|
||||
AlbumCoverStyle.Card -> R.layout.fragment_album_card_cover
|
||||
AlbumCoverStyle.Material -> R.layout.fragment_album_material_cover
|
||||
AlbumCoverStyle.Full -> R.layout.fragment_album_full_cover
|
||||
AlbumCoverStyle.FullCard -> R.layout.fragment_album_full_card_cover
|
||||
}
|
||||
|
@ -153,7 +155,7 @@ class AlbumCoverPagerAdapter(
|
|||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
loadAlbumCover()
|
||||
loadAlbumCover(albumCover = view.findViewById(R.id.player_image))
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
|
@ -161,10 +163,11 @@ class AlbumCoverPagerAdapter(
|
|||
colorReceiver = null
|
||||
}
|
||||
|
||||
private fun loadAlbumCover() {
|
||||
SongGlideRequest.Builder.from(Glide.with(requireContext()), song)
|
||||
.checkIgnoreMediaStore(requireContext())
|
||||
.generatePalette(requireContext()).build()
|
||||
private fun loadAlbumCover(albumCover: ImageView) {
|
||||
GlideApp.with(this).asBitmapPalette().songCoverOptions(song)
|
||||
//.checkIgnoreMediaStore()
|
||||
.load(RetroGlideExtension.getSongModel(song))
|
||||
.dontAnimate()
|
||||
.into(object : RetroMusicColoredTarget(albumCover) {
|
||||
override fun onColorReady(colors: MediaNotificationProcessor) {
|
||||
setColor(colors)
|
||||
|
@ -200,9 +203,7 @@ class AlbumCoverPagerAdapter(
|
|||
|
||||
fun newInstance(song: Song): AlbumCoverFragment {
|
||||
val frag = AlbumCoverFragment()
|
||||
val args = Bundle()
|
||||
args.putParcelable(SONG_ARG, song)
|
||||
frag.arguments = args
|
||||
frag.arguments = bundleOf(SONG_ARG to song)
|
||||
return frag
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -14,48 +14,55 @@
|
|||
*/
|
||||
package code.name.monkey.retromusic.adapter.artist
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.res.ColorStateList
|
||||
import android.content.res.Resources
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.isVisible
|
||||
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.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
|
||||
|
||||
class ArtistAdapter(
|
||||
val activity: FragmentActivity,
|
||||
override 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 {
|
||||
|
||||
var albumArtistsOnly = false
|
||||
|
||||
init {
|
||||
this.setHasStableIds(true)
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun swapDataSet(dataSet: List<Artist>) {
|
||||
this.dataSet = dataSet
|
||||
notifyDataSetChanged()
|
||||
albumArtistsOnly = PreferenceUtil.albumArtistsOnly
|
||||
}
|
||||
|
||||
override fun getItemId(position: Int): Long {
|
||||
|
@ -82,6 +89,13 @@ class ArtistAdapter(
|
|||
holder.itemView.isActivated = isChecked
|
||||
holder.title?.text = artist.name
|
||||
holder.text?.hide()
|
||||
val transitionName =
|
||||
if (albumArtistsOnly) artist.name else artist.id.toString()
|
||||
if (holder.imageContainer != null) {
|
||||
holder.imageContainer?.transitionName = transitionName
|
||||
} else {
|
||||
holder.image?.transitionName = transitionName
|
||||
}
|
||||
loadArtistImage(artist, holder)
|
||||
}
|
||||
|
||||
|
@ -98,9 +112,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,12 +128,12 @@ class ArtistAdapter(
|
|||
return dataSet.size
|
||||
}
|
||||
|
||||
override fun getIdentifier(position: Int): Artist? {
|
||||
override fun getIdentifier(position: Int): Artist {
|
||||
return dataSet[position]
|
||||
}
|
||||
|
||||
override fun getName(artist: Artist): String {
|
||||
return artist.name
|
||||
override fun getName(model: Artist): String {
|
||||
return model.name
|
||||
}
|
||||
|
||||
override fun onMultipleItemAction(
|
||||
|
@ -146,7 +162,7 @@ class ArtistAdapter(
|
|||
inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) {
|
||||
|
||||
init {
|
||||
menu?.visibility = View.GONE
|
||||
menu?.isVisible = false
|
||||
}
|
||||
|
||||
override fun onClick(v: View?) {
|
||||
|
@ -154,16 +170,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 (albumArtistsOnly && IAlbumArtistClickListener != null) {
|
||||
IAlbumArtistClickListener.onAlbumArtist(artist.name, imageContainer ?: it)
|
||||
} else {
|
||||
IArtistClickListener.onArtist(artist.id, imageContainer ?: it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLongClick(v: View?): Boolean {
|
||||
toggleChecked(layoutPosition)
|
||||
return super.onLongClick(v)
|
||||
return toggleChecked(layoutPosition)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
package code.name.monkey.retromusic.adapter.backup
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.databinding.ItemListBackupBinding
|
||||
import java.io.File
|
||||
|
||||
|
||||
class BackupAdapter(
|
||||
val activity: FragmentActivity,
|
||||
var dataSet: MutableList<File>,
|
||||
val backupClickedListener: BackupClickedListener
|
||||
) : RecyclerView.Adapter<BackupAdapter.ViewHolder>() {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
return ViewHolder(
|
||||
ItemListBackupBinding.inflate(LayoutInflater.from(activity), parent, false)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.binding.title.text = dataSet[position].nameWithoutExtension
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = dataSet.size
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun swapDataset(dataSet: List<File>) {
|
||||
this.dataSet = ArrayList(dataSet)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
inner class ViewHolder(val binding: ItemListBackupBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
init {
|
||||
binding.menu.setOnClickListener { view ->
|
||||
val popupMenu = PopupMenu(activity, view)
|
||||
popupMenu.inflate(R.menu.menu_backup)
|
||||
popupMenu.setOnMenuItemClickListener { menuItem ->
|
||||
return@setOnMenuItemClickListener backupClickedListener.onBackupMenuClicked(
|
||||
dataSet[bindingAdapterPosition],
|
||||
menuItem
|
||||
)
|
||||
}
|
||||
popupMenu.show()
|
||||
}
|
||||
itemView.setOnClickListener {
|
||||
backupClickedListener.onBackupClicked(dataSet[bindingAdapterPosition])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface BackupClickedListener {
|
||||
fun onBackupClicked(file: File)
|
||||
|
||||
fun onBackupMenuClicked(file: File, menuItem: MenuItem): Boolean
|
||||
}
|
||||
}
|
|
@ -1,129 +0,0 @@
|
|||
package code.name.monkey.retromusic.adapter.base;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import androidx.annotation.MenuRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import code.name.monkey.retromusic.R;
|
||||
import code.name.monkey.retromusic.interfaces.ICabHolder;
|
||||
import com.afollestad.materialcab.MaterialCab;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
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 int menuRes;
|
||||
|
||||
public AbsMultiSelectAdapter(
|
||||
@NonNull Context context, @Nullable ICabHolder ICabHolder, @MenuRes int menuRes) {
|
||||
this.ICabHolder = ICabHolder;
|
||||
checked = new ArrayList<>();
|
||||
this.menuRes = menuRes;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCabCreated(MaterialCab materialCab, Menu menu) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCabFinished(MaterialCab materialCab) {
|
||||
clearChecked();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCabItemClicked(MenuItem menuItem) {
|
||||
if (menuItem.getItemId() == R.id.action_multi_select_adapter_check_all) {
|
||||
checkAll();
|
||||
} else {
|
||||
onMultipleItemAction(menuItem, new ArrayList<>(checked));
|
||||
cab.finish();
|
||||
clearChecked();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void checkAll() {
|
||||
if (ICabHolder != null) {
|
||||
checked.clear();
|
||||
for (int i = 0; i < getItemCount(); i++) {
|
||||
I identifier = getIdentifier(i);
|
||||
if (identifier != null) {
|
||||
checked.add(identifier);
|
||||
}
|
||||
}
|
||||
notifyDataSetChanged();
|
||||
updateCab();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected abstract I getIdentifier(int position);
|
||||
|
||||
protected String getName(I object) {
|
||||
return object.toString();
|
||||
}
|
||||
|
||||
protected boolean isChecked(I identifier) {
|
||||
return checked.contains(identifier);
|
||||
}
|
||||
|
||||
protected boolean isInQuickSelectMode() {
|
||||
return cab != null && cab.isActive();
|
||||
}
|
||||
|
||||
protected abstract void onMultipleItemAction(MenuItem menuItem, List<I> selection);
|
||||
|
||||
protected void setMultiSelectMenuRes(@MenuRes int menuRes) {
|
||||
this.menuRes = menuRes;
|
||||
}
|
||||
|
||||
protected boolean toggleChecked(final int position) {
|
||||
if (ICabHolder != null) {
|
||||
I identifier = getIdentifier(position);
|
||||
if (identifier == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!checked.remove(identifier)) {
|
||||
checked.add(identifier);
|
||||
}
|
||||
|
||||
notifyItemChanged(position);
|
||||
updateCab();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void clearChecked() {
|
||||
checked.clear();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private void updateCab() {
|
||||
if (ICabHolder != null) {
|
||||
if (cab == null || !cab.isActive()) {
|
||||
cab = ICabHolder.openCab(menuRes, this);
|
||||
}
|
||||
final int size = checked.size();
|
||||
if (size <= 0) {
|
||||
cab.finish();
|
||||
} else if (size == 1) {
|
||||
cab.setTitle(getName(checked.get(0)));
|
||||
} else {
|
||||
cab.setTitle(context.getString(R.string.x_selected, size));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
package code.name.monkey.retromusic.adapter.base
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.Color
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.annotation.MenuRes
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.extensions.surfaceColor
|
||||
import code.name.monkey.retromusic.interfaces.ICabCallback
|
||||
import code.name.monkey.retromusic.interfaces.ICabHolder
|
||||
import code.name.monkey.retromusic.util.RetroColorUtil
|
||||
import com.afollestad.materialcab.attached.AttachedCab
|
||||
import com.afollestad.materialcab.attached.destroy
|
||||
import com.afollestad.materialcab.attached.isActive
|
||||
|
||||
abstract class AbsMultiSelectAdapter<V : RecyclerView.ViewHolder?, I>(
|
||||
open val activity: FragmentActivity, private val ICabHolder: ICabHolder?, @MenuRes menuRes: Int
|
||||
) : RecyclerView.Adapter<V>(), ICabCallback {
|
||||
private var cab: AttachedCab? = null
|
||||
private val checked: MutableList<I>
|
||||
private var menuRes: Int
|
||||
override fun onCabCreated(cab: AttachedCab, menu: Menu): Boolean {
|
||||
activity.window.statusBarColor =
|
||||
RetroColorUtil.shiftBackgroundColor(activity.surfaceColor())
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onCabFinished(cab: AttachedCab): Boolean {
|
||||
clearChecked()
|
||||
activity.window.statusBarColor = when {
|
||||
VersionUtils.hasMarshmallow() -> Color.TRANSPARENT
|
||||
else -> Color.BLACK
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onCabItemClicked(item: MenuItem): Boolean {
|
||||
if (item.itemId == R.id.action_multi_select_adapter_check_all) {
|
||||
checkAll()
|
||||
} else {
|
||||
onMultipleItemAction(item, ArrayList(checked))
|
||||
cab?.destroy()
|
||||
clearChecked()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun checkAll() {
|
||||
if (ICabHolder != null) {
|
||||
checked.clear()
|
||||
for (i in 0 until itemCount) {
|
||||
val identifier = getIdentifier(i)
|
||||
if (identifier != null) {
|
||||
checked.add(identifier)
|
||||
}
|
||||
}
|
||||
notifyDataSetChanged()
|
||||
updateCab()
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract fun getIdentifier(position: Int): I?
|
||||
|
||||
protected abstract fun getName(model: I): String?
|
||||
|
||||
protected fun isChecked(identifier: I): Boolean {
|
||||
return checked.contains(identifier)
|
||||
}
|
||||
|
||||
protected val isInQuickSelectMode: Boolean
|
||||
get() = cab != null && cab!!.isActive()
|
||||
|
||||
protected abstract fun onMultipleItemAction(menuItem: MenuItem, selection: List<I>)
|
||||
protected fun setMultiSelectMenuRes(@MenuRes menuRes: Int) {
|
||||
this.menuRes = menuRes
|
||||
}
|
||||
|
||||
protected fun toggleChecked(position: Int): Boolean {
|
||||
if (ICabHolder != null) {
|
||||
val identifier = getIdentifier(position) ?: return false
|
||||
if (!checked.remove(identifier)) {
|
||||
checked.add(identifier)
|
||||
}
|
||||
notifyItemChanged(position)
|
||||
updateCab()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun clearChecked() {
|
||||
checked.clear()
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
@SuppressLint("StringFormatInvalid", "StringFormatMatches")
|
||||
private fun updateCab() {
|
||||
if (ICabHolder != null) {
|
||||
if (cab == null || !cab!!.isActive()) {
|
||||
cab = ICabHolder.openCab(menuRes, this)
|
||||
}
|
||||
val size = checked.size
|
||||
when {
|
||||
size <= 0 -> {
|
||||
cab?.destroy()
|
||||
}
|
||||
size == 1 -> {
|
||||
cab?.title(literal = getName(checked[0]))
|
||||
}
|
||||
else -> {
|
||||
cab?.title(literal = activity.getString(R.string.x_selected, size))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
checked = ArrayList()
|
||||
this.menuRes = menuRes
|
||||
}
|
||||
}
|
|
@ -16,19 +16,19 @@ package code.name.monkey.retromusic.adapter.base;
|
|||
|
||||
import android.graphics.Color;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.AppCompatImageView;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import 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 {
|
||||
|
||||
|
@ -41,15 +41,12 @@ public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHold
|
|||
@Nullable
|
||||
public ImageView image;
|
||||
|
||||
@Nullable
|
||||
public ImageView artistImage;
|
||||
|
||||
@Nullable
|
||||
public ImageView playerImage;
|
||||
|
||||
@Nullable
|
||||
public MaterialCardView imageContainerCard;
|
||||
|
||||
@Nullable
|
||||
public FrameLayout imageContainer;
|
||||
|
||||
@Nullable
|
||||
public TextView imageText;
|
||||
|
||||
|
@ -65,10 +62,6 @@ public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHold
|
|||
@Nullable
|
||||
public View paletteColorContainer;
|
||||
|
||||
|
||||
@Nullable
|
||||
public RecyclerView recyclerView;
|
||||
|
||||
@Nullable
|
||||
public TextView text;
|
||||
|
||||
|
@ -88,18 +81,16 @@ public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHold
|
|||
text2 = itemView.findViewById(R.id.text2);
|
||||
|
||||
image = itemView.findViewById(R.id.image);
|
||||
artistImage = itemView.findViewById(R.id.artistImage);
|
||||
playerImage = itemView.findViewById(R.id.player_image);
|
||||
time = itemView.findViewById(R.id.time);
|
||||
|
||||
imageText = itemView.findViewById(R.id.imageText);
|
||||
imageTextContainer = itemView.findViewById(R.id.imageTextContainer);
|
||||
imageContainerCard = itemView.findViewById(R.id.imageContainerCard);
|
||||
imageContainer = itemView.findViewById(R.id.imageContainer);
|
||||
|
||||
menu = itemView.findViewById(R.id.menu);
|
||||
dragView = itemView.findViewById(R.id.drag_view);
|
||||
paletteColorContainer = itemView.findViewById(R.id.paletteColorContainer);
|
||||
recyclerView = itemView.findViewById(R.id.recycler_view);
|
||||
mask = itemView.findViewById(R.id.mask);
|
||||
dummyContainer = itemView.findViewById(R.id.dummy_view);
|
||||
|
||||
|
|
|
@ -15,42 +15,36 @@
|
|||
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
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.setPadding
|
||||
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.extensions.dipToPix
|
||||
import code.name.monkey.retromusic.glide.GlideApp
|
||||
import code.name.monkey.retromusic.glide.playlistPreview.PlaylistPreview
|
||||
import code.name.monkey.retromusic.helper.SortOrder.PlaylistSortOrder
|
||||
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
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import me.zhanghai.android.fastscroll.PopupTextProvider
|
||||
|
||||
class PlaylistAdapter(
|
||||
private val activity: FragmentActivity,
|
||||
private var dataSet: List<PlaylistWithSongs>,
|
||||
override val activity: FragmentActivity,
|
||||
var dataSet: List<PlaylistWithSongs>,
|
||||
private var itemLayoutRes: Int,
|
||||
ICabHolder: ICabHolder?,
|
||||
private val listener: IPlaylistClickListener
|
||||
|
@ -58,7 +52,7 @@ class PlaylistAdapter(
|
|||
activity,
|
||||
ICabHolder,
|
||||
R.menu.menu_playlists_selection
|
||||
) {
|
||||
), PopupTextProvider {
|
||||
|
||||
init {
|
||||
setHasStableIds(true)
|
||||
|
@ -83,44 +77,51 @@ class PlaylistAdapter(
|
|||
}
|
||||
|
||||
private fun getPlaylistTitle(playlist: PlaylistEntity): String {
|
||||
return if (TextUtils.isEmpty(playlist.playlistName)) "-" else playlist.playlistName
|
||||
return playlist.playlistName.ifEmpty { "-" }
|
||||
}
|
||||
|
||||
private fun getPlaylistText(playlist: PlaylistWithSongs): String {
|
||||
return MusicUtil.getPlaylistInfoString(activity, playlist.songs.toSongs())
|
||||
}
|
||||
|
||||
override fun getPopupText(position: Int): String {
|
||||
val sectionName: String = when (PreferenceUtil.playlistSortOrder) {
|
||||
PlaylistSortOrder.PLAYLIST_A_Z, PlaylistSortOrder.PLAYLIST_Z_A -> dataSet[position].playlistEntity.playlistName
|
||||
PlaylistSortOrder.PLAYLIST_SONG_COUNT, PlaylistSortOrder.PLAYLIST_SONG_COUNT_DESC -> dataSet[position].songs.size.toString()
|
||||
else -> {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
return MusicUtil.getSectionName(sectionName)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val playlist = dataSet[position]
|
||||
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)
|
||||
holder.menu?.isGone = isChecked(playlist)
|
||||
GlideApp.with(activity)
|
||||
.load(
|
||||
if (itemLayoutRes == R.layout.item_list) {
|
||||
holder.image?.setPadding(activity.dipToPix(8F).toInt())
|
||||
R.drawable.ic_playlist_play
|
||||
} else 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]
|
||||
}
|
||||
|
||||
override fun getName(playlist: PlaylistWithSongs): String {
|
||||
return playlist.playlistEntity.playlistName
|
||||
override fun getName(model: PlaylistWithSongs): String {
|
||||
return model.playlistEntity.playlistName
|
||||
}
|
||||
|
||||
override fun onMultipleItemAction(menuItem: MenuItem, selection: List<PlaylistWithSongs>) {
|
||||
|
@ -141,18 +142,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)
|
||||
|
@ -172,7 +163,7 @@ class PlaylistAdapter(
|
|||
if (isInQuickSelectMode) {
|
||||
toggleChecked(layoutPosition)
|
||||
} else {
|
||||
ViewCompat.setTransitionName(itemView, "playlist")
|
||||
itemView.transitionName = "playlist"
|
||||
listener.onPlaylistClick(dataSet[layoutPosition], itemView)
|
||||
}
|
||||
}
|
||||
|
@ -183,37 +174,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
|
||||
}
|
||||
|
|
|
@ -16,106 +16,96 @@ package code.name.monkey.retromusic.adapter.song
|
|||
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
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.accentColor
|
||||
import code.name.monkey.retromusic.extensions.accentOutlineColor
|
||||
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()
|
||||
|
||||
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 viewHolder = holder as ViewHolder
|
||||
viewHolder.playAction?.let {
|
||||
it.setOnClickListener {
|
||||
MusicPlayerRemote.openQueue(dataSet, 0, true)
|
||||
}
|
||||
it.accentOutlineColor()
|
||||
}
|
||||
viewHolder.shuffleAction?.let {
|
||||
it.setOnClickListener {
|
||||
MusicPlayerRemote.openAndShuffleQueue(dataSet, true)
|
||||
}
|
||||
it.accentColor()
|
||||
}
|
||||
} 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 +113,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 +124,49 @@ class OrderablePlaylistSongAdapter(
|
|||
return super.onSongMenuItemClick(item)
|
||||
}
|
||||
|
||||
@DraggableItemStateFlags
|
||||
override fun getDragStateFlags(): Int {
|
||||
return mDragStateFlags
|
||||
init {
|
||||
dragView?.isVisible = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun setDragStateFlags(@DraggableItemStateFlags flags: Int) {
|
||||
mDragStateFlags = flags
|
||||
override fun onCheckCanStartDrag(holder: ViewHolder, position: Int, x: Int, y: Int): Boolean {
|
||||
if (dataSet.size == 0 or 1 || isInQuickSelectMode) {
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,11 +16,11 @@ package code.name.monkey.retromusic.adapter.song
|
|||
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
|
||||
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.helper.MusicPlayerRemote
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote.isPlaying
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote.playNextSong
|
||||
|
@ -28,8 +28,6 @@ import code.name.monkey.retromusic.helper.MusicPlayerRemote.removeFromQueue
|
|||
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
|
||||
|
@ -41,7 +39,7 @@ import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultAct
|
|||
import me.zhanghai.android.fastscroll.PopupTextProvider
|
||||
|
||||
class PlayingQueueAdapter(
|
||||
activity: AppCompatActivity,
|
||||
activity: FragmentActivity,
|
||||
dataSet: MutableList<Song>,
|
||||
private var current: Int,
|
||||
itemLayoutRes: Int
|
||||
|
@ -79,14 +77,10 @@ class PlayingQueueAdapter(
|
|||
if (holder.image == null) {
|
||||
return
|
||||
}
|
||||
SongGlideRequest.Builder.from(Glide.with(activity), song)
|
||||
.checkIgnoreMediaStore(activity)
|
||||
.generatePalette(activity).build()
|
||||
.into(object : RetroMusicColoredTarget(holder.image!!) {
|
||||
override fun onColorReady(colors: MediaNotificationProcessor) {
|
||||
//setColors(colors, holder)
|
||||
}
|
||||
})
|
||||
GlideApp.with(activity)
|
||||
.load(RetroGlideExtension.getSongModel(song))
|
||||
.songCoverOptions(song)
|
||||
.into(holder.image!!)
|
||||
}
|
||||
|
||||
fun swapDataSet(dataSet: List<Song>, position: Int) {
|
||||
|
@ -156,7 +150,7 @@ class PlayingQueueAdapter(
|
|||
}
|
||||
|
||||
init {
|
||||
dragView?.visibility = View.VISIBLE
|
||||
dragView?.isVisible = true
|
||||
}
|
||||
|
||||
override fun onSongMenuItemClick(item: MenuItem): Boolean {
|
||||
|
@ -190,7 +184,7 @@ class PlayingQueueAdapter(
|
|||
private const val UP_NEXT = 2
|
||||
}
|
||||
|
||||
override fun onSwipeItem(holder: ViewHolder, position: Int, result: Int): SwipeResultAction? {
|
||||
override fun onSwipeItem(holder: ViewHolder, position: Int, result: Int): SwipeResultAction {
|
||||
return if (result == SwipeableItemConstants.RESULT_CANCELED) {
|
||||
SwipeResultActionDefault()
|
||||
} else {
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.adapter.song
|
||||
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
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.dialogs.RemoveSongFromPlaylistDialog
|
||||
import code.name.monkey.retromusic.interfaces.ICabHolder
|
||||
import code.name.monkey.retromusic.model.Song
|
||||
|
||||
class PlaylistSongAdapter(
|
||||
private val playlist: PlaylistEntity,
|
||||
activity: FragmentActivity,
|
||||
dataSet: MutableList<Song>,
|
||||
itemLayoutRes: Int,
|
||||
iCabHolder: ICabHolder?
|
||||
) : SongAdapter(activity, dataSet, itemLayoutRes, iCabHolder) {
|
||||
|
||||
init {
|
||||
this.setMultiSelectMenuRes(R.menu.menu_cannot_delete_single_songs_playlist_songs_selection)
|
||||
}
|
||||
|
||||
override fun createViewHolder(view: View): SongAdapter.ViewHolder {
|
||||
return ViewHolder(view)
|
||||
}
|
||||
|
||||
open inner class ViewHolder(itemView: View) : SongAdapter.ViewHolder(itemView) {
|
||||
|
||||
override var songMenuRes: Int
|
||||
get() = R.menu.menu_item_playlist_song
|
||||
set(value) {
|
||||
super.songMenuRes = value
|
||||
}
|
||||
|
||||
override fun onSongMenuItemClick(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.action_remove_from_playlist -> {
|
||||
RemoveSongFromPlaylistDialog.create(song.toSongEntity(playlist.playListId))
|
||||
.show(activity.supportFragmentManager, "REMOVE_FROM_PLAYLIST")
|
||||
return true
|
||||
}
|
||||
}
|
||||
return super.onSongMenuItemClick(item)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,14 +15,16 @@
|
|||
package code.name.monkey.retromusic.adapter.song
|
||||
|
||||
import android.view.View
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import code.name.monkey.appthemehelper.ThemeStore
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.extensions.applyColor
|
||||
import code.name.monkey.retromusic.extensions.applyOutlineColor
|
||||
import code.name.monkey.retromusic.extensions.accentColor
|
||||
import code.name.monkey.retromusic.extensions.accentOutlineColor
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||
import code.name.monkey.retromusic.interfaces.ICabHolder
|
||||
import code.name.monkey.retromusic.model.Song
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import code.name.monkey.retromusic.util.RetroUtil
|
||||
import com.google.android.material.button.MaterialButton
|
||||
|
||||
class ShuffleButtonSongAdapter(
|
||||
|
@ -32,28 +34,36 @@ 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)
|
||||
val viewHolder = holder as ViewHolder
|
||||
viewHolder.playAction?.let {
|
||||
it.setOnClickListener {
|
||||
MusicPlayerRemote.openQueue(dataSet, 0, true)
|
||||
}
|
||||
it.applyOutlineColor(color)
|
||||
it.accentOutlineColor()
|
||||
}
|
||||
viewHolder.shuffleAction?.let {
|
||||
it.setOnClickListener {
|
||||
MusicPlayerRemote.openAndShuffleQueue(dataSet, true)
|
||||
}
|
||||
it.applyColor(color)
|
||||
it.accentColor()
|
||||
}
|
||||
} else {
|
||||
super.onBindViewHolder(holder, position - 1)
|
||||
val landscape = RetroUtil.isLandscape
|
||||
if ((PreferenceUtil.songGridSize > 2 && !landscape) || (PreferenceUtil.songGridSizeLand > 5 && landscape)) {
|
||||
holder.menu?.isVisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,4 +79,5 @@ class ShuffleButtonSongAdapter(
|
|||
super.onClick(v)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ import androidx.fragment.app.FragmentActivity
|
|||
import code.name.monkey.retromusic.interfaces.ICabHolder
|
||||
import code.name.monkey.retromusic.model.Song
|
||||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
import java.util.*
|
||||
|
||||
class SimpleSongAdapter(
|
||||
context: FragmentActivity,
|
||||
|
|
|
@ -21,6 +21,7 @@ import android.view.MenuItem
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.navigation.findNavController
|
||||
|
@ -28,21 +29,20 @@ import code.name.monkey.retromusic.EXTRA_ALBUM_ID
|
|||
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.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
|
||||
import code.name.monkey.retromusic.helper.menu.SongsMenuHelper
|
||||
import code.name.monkey.retromusic.interfaces.ICabCallback
|
||||
import code.name.monkey.retromusic.interfaces.ICabHolder
|
||||
import code.name.monkey.retromusic.model.Song
|
||||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import code.name.monkey.retromusic.util.RetroUtil
|
||||
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
|
||||
import com.afollestad.materialcab.MaterialCab
|
||||
import com.bumptech.glide.Glide
|
||||
import me.zhanghai.android.fastscroll.PopupTextProvider
|
||||
|
||||
/**
|
||||
|
@ -50,7 +50,7 @@ import me.zhanghai.android.fastscroll.PopupTextProvider
|
|||
*/
|
||||
|
||||
open class SongAdapter(
|
||||
protected val activity: FragmentActivity,
|
||||
override val activity: FragmentActivity,
|
||||
var dataSet: MutableList<Song>,
|
||||
protected var itemLayoutRes: Int,
|
||||
ICabHolder: ICabHolder?,
|
||||
|
@ -59,7 +59,7 @@ open class SongAdapter(
|
|||
activity,
|
||||
ICabHolder,
|
||||
R.menu.menu_media_selection
|
||||
), MaterialCab.Callback, PopupTextProvider {
|
||||
), ICabCallback, PopupTextProvider {
|
||||
|
||||
private var showSectionName = true
|
||||
|
||||
|
@ -95,15 +95,15 @@ open class SongAdapter(
|
|||
val song = dataSet[position]
|
||||
val isChecked = isChecked(song)
|
||||
holder.itemView.isActivated = isChecked
|
||||
if (isChecked) {
|
||||
holder.menu?.hide()
|
||||
} else {
|
||||
holder.menu?.show()
|
||||
}
|
||||
holder.menu?.isGone = isChecked
|
||||
holder.title?.text = getSongTitle(song)
|
||||
holder.text?.text = getSongText(song)
|
||||
holder.text2?.text = getSongText(song)
|
||||
loadAlbumCover(song, holder)
|
||||
val landscape = RetroUtil.isLandscape
|
||||
if ((PreferenceUtil.songGridSize > 2 && !landscape) || (PreferenceUtil.songGridSizeLand > 5 && landscape)) {
|
||||
holder.menu?.isVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun setColors(color: MediaNotificationProcessor, holder: ViewHolder) {
|
||||
|
@ -111,7 +111,7 @@ open class SongAdapter(
|
|||
holder.title?.setTextColor(color.primaryTextColor)
|
||||
holder.text?.setTextColor(color.secondaryTextColor)
|
||||
holder.paletteColorContainer?.setBackgroundColor(color.backgroundColor)
|
||||
holder.menu?.imageTintList= ColorStateList.valueOf(color.primaryTextColor)
|
||||
holder.menu?.imageTintList = ColorStateList.valueOf(color.primaryTextColor)
|
||||
}
|
||||
holder.mask?.backgroundTintList = ColorStateList.valueOf(color.primaryTextColor)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -150,8 +149,8 @@ open class SongAdapter(
|
|||
return dataSet[position]
|
||||
}
|
||||
|
||||
override fun getName(song: Song): String {
|
||||
return song.title
|
||||
override fun getName(model: Song): String {
|
||||
return model.title
|
||||
}
|
||||
|
||||
override fun onMultipleItemAction(menuItem: MenuItem, selection: List<Song>) {
|
||||
|
@ -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 ""
|
||||
}
|
||||
|
|
|
@ -15,18 +15,16 @@
|
|||
package code.name.monkey.retromusic.appshortcuts
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.Icon
|
||||
import android.graphics.drawable.LayerDrawable
|
||||
import android.os.Build
|
||||
import android.util.TypedValue
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import code.name.monkey.appthemehelper.ThemeStore
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.extensions.getTintedDrawable
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import code.name.monkey.retromusic.util.RetroUtil
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.N_MR1)
|
||||
object AppShortcutIconGenerator {
|
||||
|
@ -63,28 +61,17 @@ object AppShortcutIconGenerator {
|
|||
context: Context,
|
||||
iconId: Int,
|
||||
foregroundColor: Int,
|
||||
backgroundColor: Int
|
||||
backgroundColor: Int,
|
||||
): Icon {
|
||||
// Get and tint foreground and background drawables
|
||||
val vectorDrawable = RetroUtil.getTintedVectorDrawable(context, iconId, foregroundColor)
|
||||
val backgroundDrawable = RetroUtil.getTintedVectorDrawable(
|
||||
context, R.drawable.ic_app_shortcut_background, backgroundColor
|
||||
)
|
||||
val vectorDrawable = context.getTintedDrawable(iconId, foregroundColor)
|
||||
val backgroundDrawable =
|
||||
context.getTintedDrawable(R.drawable.ic_app_shortcut_background, backgroundColor)
|
||||
|
||||
// Squash the two drawables together
|
||||
val layerDrawable = LayerDrawable(arrayOf(backgroundDrawable, vectorDrawable))
|
||||
|
||||
// Return as an Icon
|
||||
return Icon.createWithBitmap(drawableToBitmap(layerDrawable))
|
||||
}
|
||||
|
||||
private fun drawableToBitmap(drawable: Drawable): Bitmap {
|
||||
val bitmap = Bitmap.createBitmap(
|
||||
drawable.intrinsicWidth, drawable.intrinsicHeight, Bitmap.Config.ARGB_8888
|
||||
)
|
||||
val canvas = Canvas(bitmap)
|
||||
drawable.setBounds(0, 0, canvas.width, canvas.height)
|
||||
drawable.draw(canvas)
|
||||
return bitmap
|
||||
return Icon.createWithBitmap(layerDrawable.toBitmap())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ package code.name.monkey.retromusic.appshortcuts
|
|||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.core.os.bundleOf
|
||||
import code.name.monkey.retromusic.appshortcuts.shortcuttype.LastAddedShortcutType
|
||||
import code.name.monkey.retromusic.appshortcuts.shortcuttype.ShuffleAllShortcutType
|
||||
import code.name.monkey.retromusic.appshortcuts.shortcuttype.TopTracksShortcutType
|
||||
|
@ -26,7 +27,11 @@ import code.name.monkey.retromusic.model.smartplaylist.LastAddedPlaylist
|
|||
import code.name.monkey.retromusic.model.smartplaylist.ShuffleAllPlaylist
|
||||
import code.name.monkey.retromusic.model.smartplaylist.TopTracksPlaylist
|
||||
import code.name.monkey.retromusic.service.MusicService
|
||||
import code.name.monkey.retromusic.service.MusicService.*
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_PLAY_PLAYLIST
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.INTENT_EXTRA_PLAYLIST
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.INTENT_EXTRA_SHUFFLE_MODE
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.SHUFFLE_MODE_NONE
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.SHUFFLE_MODE_SHUFFLE
|
||||
|
||||
class AppShortcutLauncherActivity : Activity() {
|
||||
|
||||
|
@ -59,9 +64,10 @@ class AppShortcutLauncherActivity : Activity() {
|
|||
val intent = Intent(this, MusicService::class.java)
|
||||
intent.action = ACTION_PLAY_PLAYLIST
|
||||
|
||||
val bundle = Bundle()
|
||||
bundle.putParcelable(INTENT_EXTRA_PLAYLIST, playlist)
|
||||
bundle.putInt(INTENT_EXTRA_SHUFFLE_MODE, shuffleMode)
|
||||
val bundle = bundleOf(
|
||||
INTENT_EXTRA_PLAYLIST to playlist,
|
||||
INTENT_EXTRA_SHUFFLE_MODE to shuffleMode
|
||||
)
|
||||
|
||||
intent.putExtras(bundle)
|
||||
|
||||
|
|
|
@ -21,14 +21,15 @@ import android.content.pm.ShortcutInfo
|
|||
import android.content.pm.ShortcutManager
|
||||
import android.graphics.drawable.Icon
|
||||
import android.os.Build
|
||||
import androidx.core.content.getSystemService
|
||||
import code.name.monkey.retromusic.appshortcuts.shortcuttype.LastAddedShortcutType
|
||||
import code.name.monkey.retromusic.appshortcuts.shortcuttype.ShuffleAllShortcutType
|
||||
import code.name.monkey.retromusic.appshortcuts.shortcuttype.TopTracksShortcutType
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.N_MR1)
|
||||
class DynamicShortcutManager(private val context: Context) {
|
||||
private val shortcutManager: ShortcutManager =
|
||||
this.context.getSystemService(ShortcutManager::class.java)
|
||||
private val shortcutManager: ShortcutManager? =
|
||||
this.context.getSystemService()
|
||||
|
||||
private val defaultShortcuts: List<ShortcutInfo>
|
||||
get() = listOf(
|
||||
|
@ -39,12 +40,12 @@ class DynamicShortcutManager(private val context: Context) {
|
|||
|
||||
fun initDynamicShortcuts() {
|
||||
// if (shortcutManager.dynamicShortcuts.size == 0) {
|
||||
shortcutManager.dynamicShortcuts = defaultShortcuts
|
||||
shortcutManager?.dynamicShortcuts = defaultShortcuts
|
||||
// }
|
||||
}
|
||||
|
||||
fun updateDynamicShortcuts() {
|
||||
shortcutManager.updateShortcuts(defaultShortcuts)
|
||||
shortcutManager?.updateShortcuts(defaultShortcuts)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -66,7 +67,7 @@ class DynamicShortcutManager(private val context: Context) {
|
|||
}
|
||||
|
||||
fun reportShortcutUsed(context: Context, shortcutId: String) {
|
||||
context.getSystemService(ShortcutManager::class.java).reportShortcutUsed(shortcutId)
|
||||
context.getSystemService<ShortcutManager>()?.reportShortcutUsed(shortcutId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.content.pm.ShortcutInfo
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.core.os.bundleOf
|
||||
import code.name.monkey.retromusic.appshortcuts.AppShortcutLauncherActivity
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.N_MR1)
|
||||
|
@ -36,8 +36,7 @@ abstract class BaseShortcutType(internal var context: Context) {
|
|||
internal fun getPlaySongsIntent(shortcutType: Long): Intent {
|
||||
val intent = Intent(context, AppShortcutLauncherActivity::class.java)
|
||||
intent.action = Intent.ACTION_VIEW
|
||||
val b = Bundle()
|
||||
b.putLong(AppShortcutLauncherActivity.KEY_SHORTCUT_TYPE, shortcutType)
|
||||
val b = bundleOf(AppShortcutLauncherActivity.KEY_SHORTCUT_TYPE to shortcutType)
|
||||
intent.putExtras(b)
|
||||
return intent
|
||||
}
|
||||
|
|
|
@ -42,6 +42,6 @@ class LastAddedShortcutType(context: Context) : BaseShortcutType(context) {
|
|||
companion object {
|
||||
|
||||
val id: String
|
||||
get() = BaseShortcutType.ID_PREFIX + "last_added"
|
||||
get() = ID_PREFIX + "last_added"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,6 +39,6 @@ class TopTracksShortcutType(context: Context) : BaseShortcutType(context) {
|
|||
companion object {
|
||||
|
||||
val id: String
|
||||
get() = BaseShortcutType.ID_PREFIX + "top_tracks"
|
||||
get() = ID_PREFIX + "top_tracks"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,22 +20,27 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.text.TextUtils
|
||||
import android.view.View
|
||||
import android.widget.RemoteViews
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
||||
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||
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.extensions.getTintedDrawable
|
||||
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.service.MusicService.Companion.ACTION_REWIND
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_SKIP
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_TOGGLE_PAUSE
|
||||
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.CustomTarget
|
||||
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
|
||||
|
@ -55,31 +60,24 @@ class AppWidgetBig : BaseAppWidget() {
|
|||
)
|
||||
appWidgetView.setImageViewResource(R.id.image, R.drawable.default_audio_art)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_next, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
context,
|
||||
R.drawable.ic_skip_next,
|
||||
MaterialValueHelper.getPrimaryTextColor(context, false)
|
||||
)!!, 1f
|
||||
)
|
||||
R.id.button_next, context.getTintedDrawable(
|
||||
R.drawable.ic_skip_next,
|
||||
MaterialValueHelper.getPrimaryTextColor(context, false)
|
||||
).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_prev, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
context,
|
||||
R.drawable.ic_skip_previous,
|
||||
MaterialValueHelper.getPrimaryTextColor(context, false)
|
||||
)!!, 1f
|
||||
)
|
||||
R.id.button_prev,
|
||||
context.getTintedDrawable(
|
||||
R.drawable.ic_skip_previous,
|
||||
MaterialValueHelper.getPrimaryTextColor(context, false)
|
||||
).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_toggle_play_pause, BaseAppWidget.Companion.createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
context,
|
||||
R.drawable.ic_play_arrow_white_32dp,
|
||||
MaterialValueHelper.getPrimaryTextColor(context, false)
|
||||
)!!, 1f
|
||||
)
|
||||
R.id.button_toggle_play_pause,
|
||||
context.getTintedDrawable(
|
||||
R.drawable.ic_play_arrow_white_32dp,
|
||||
MaterialValueHelper.getPrimaryTextColor(context, false)
|
||||
).toBitmap()
|
||||
)
|
||||
|
||||
linkButtons(context, appWidgetView)
|
||||
|
@ -98,7 +96,7 @@ class AppWidgetBig : BaseAppWidget() {
|
|||
val song = service.currentSong
|
||||
|
||||
// Set the titles and artwork
|
||||
if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) {
|
||||
if (song.title.isEmpty() && song.artistName.isEmpty()) {
|
||||
appWidgetView.setViewVisibility(
|
||||
R.id.media_titles,
|
||||
View.INVISIBLE
|
||||
|
@ -120,33 +118,27 @@ class AppWidgetBig : BaseAppWidget() {
|
|||
val playPauseRes =
|
||||
if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play_arrow_white_32dp
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_toggle_play_pause, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
service,
|
||||
playPauseRes,
|
||||
primaryColor
|
||||
)!!, 1f
|
||||
)
|
||||
R.id.button_toggle_play_pause,
|
||||
service.getTintedDrawable(
|
||||
playPauseRes,
|
||||
primaryColor
|
||||
).toBitmap()
|
||||
)
|
||||
|
||||
// Set prev/next button drawables
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_next, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
service,
|
||||
R.drawable.ic_skip_next,
|
||||
primaryColor
|
||||
)!!, 1f
|
||||
)
|
||||
R.id.button_next,
|
||||
service.getTintedDrawable(
|
||||
R.drawable.ic_skip_next,
|
||||
primaryColor
|
||||
).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_prev, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
service,
|
||||
R.drawable.ic_skip_previous,
|
||||
primaryColor
|
||||
)!!, 1f
|
||||
)
|
||||
R.id.button_prev,
|
||||
service.getTintedDrawable(
|
||||
R.drawable.ic_skip_previous,
|
||||
primaryColor
|
||||
).toBitmap()
|
||||
)
|
||||
|
||||
// Link actions buttons to intents
|
||||
|
@ -154,27 +146,31 @@ class AppWidgetBig : BaseAppWidget() {
|
|||
|
||||
// Load the album cover async and push the update on completion
|
||||
val p = RetroUtil.getScreenSize(service)
|
||||
val widgetImageSize = Math.min(p.x, p.y)
|
||||
val widgetImageSize = p.x.coerceAtMost(p.y)
|
||||
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()
|
||||
.into(object : SimpleTarget<Bitmap>(widgetImageSize, widgetImageSize) {
|
||||
target = GlideApp.with(appContext)
|
||||
.asBitmap()
|
||||
//.checkIgnoreMediaStore()
|
||||
.load(RetroGlideExtension.getSongModel(song))
|
||||
.into(object : CustomTarget<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)
|
||||
}
|
||||
|
||||
override fun onLoadCleared(placeholder: Drawable?) {}
|
||||
|
||||
private fun update(bitmap: Bitmap?) {
|
||||
if (bitmap == null) {
|
||||
appWidgetView.setImageViewResource(
|
||||
|
@ -199,13 +195,17 @@ class AppWidgetBig : BaseAppWidget() {
|
|||
MainActivity.EXPAND_PANEL,
|
||||
PreferenceUtil.isExpandPanel
|
||||
)
|
||||
var pendingIntent: PendingIntent
|
||||
|
||||
val serviceName = ComponentName(context, MusicService::class.java)
|
||||
|
||||
// Home
|
||||
action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
pendingIntent = PendingIntent.getActivity(context, 0, action, 0)
|
||||
var pendingIntent =
|
||||
PendingIntent.getActivity(
|
||||
context, 0, action, if (VersionUtils.hasMarshmallow())
|
||||
PendingIntent.FLAG_IMMUTABLE
|
||||
else 0
|
||||
)
|
||||
views.setOnClickPendingIntent(R.id.clickable_area, pendingIntent)
|
||||
|
||||
// Previous track
|
||||
|
|
|
@ -20,24 +20,27 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.text.TextUtils
|
||||
import android.view.View
|
||||
import android.widget.RemoteViews
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
||||
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||
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.extensions.getTintedDrawable
|
||||
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.ImageUtil
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_REWIND
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_SKIP
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_TOGGLE_PAUSE
|
||||
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.CustomTarget
|
||||
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
|
||||
|
@ -53,31 +56,25 @@ class AppWidgetCard : BaseAppWidget() {
|
|||
appWidgetView.setImageViewResource(R.id.image, R.drawable.default_audio_art)
|
||||
val secondaryColor = MaterialValueHelper.getSecondaryTextColor(context, true)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_next, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
context,
|
||||
R.drawable.ic_skip_next,
|
||||
secondaryColor
|
||||
)!!, 1f
|
||||
)
|
||||
R.id.button_next,
|
||||
context.getTintedDrawable(
|
||||
R.drawable.ic_skip_next,
|
||||
secondaryColor
|
||||
).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_prev, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
context,
|
||||
R.drawable.ic_skip_previous,
|
||||
secondaryColor
|
||||
)!!, 1f
|
||||
)
|
||||
R.id.button_prev,
|
||||
context.getTintedDrawable(
|
||||
R.drawable.ic_skip_previous,
|
||||
secondaryColor
|
||||
).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_toggle_play_pause, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
context,
|
||||
R.drawable.ic_play_arrow_white_32dp,
|
||||
secondaryColor
|
||||
)!!, 1f
|
||||
)
|
||||
R.id.button_toggle_play_pause,
|
||||
context.getTintedDrawable(
|
||||
R.drawable.ic_play_arrow_white_32dp,
|
||||
secondaryColor
|
||||
).toBitmap()
|
||||
)
|
||||
|
||||
linkButtons(context, appWidgetView)
|
||||
|
@ -94,7 +91,7 @@ class AppWidgetCard : BaseAppWidget() {
|
|||
val song = service.currentSong
|
||||
|
||||
// Set the titles and artwork
|
||||
if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) {
|
||||
if (song.title.isEmpty() && song.artistName.isEmpty()) {
|
||||
appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
|
||||
} else {
|
||||
appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE)
|
||||
|
@ -106,33 +103,27 @@ class AppWidgetCard : BaseAppWidget() {
|
|||
val playPauseRes =
|
||||
if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play_arrow_white_32dp
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_toggle_play_pause, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
service,
|
||||
playPauseRes,
|
||||
MaterialValueHelper.getSecondaryTextColor(service, true)
|
||||
)!!, 1f
|
||||
)
|
||||
R.id.button_toggle_play_pause,
|
||||
service.getTintedDrawable(
|
||||
playPauseRes,
|
||||
MaterialValueHelper.getSecondaryTextColor(service, true)
|
||||
).toBitmap()
|
||||
)
|
||||
|
||||
// Set prev/next button drawables
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_next, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
service,
|
||||
R.drawable.ic_skip_next,
|
||||
MaterialValueHelper.getSecondaryTextColor(service, true)
|
||||
)!!, 1f
|
||||
)
|
||||
R.id.button_next,
|
||||
service.getTintedDrawable(
|
||||
R.drawable.ic_skip_next,
|
||||
MaterialValueHelper.getSecondaryTextColor(service, true)
|
||||
).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_prev, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
service,
|
||||
R.drawable.ic_skip_previous,
|
||||
MaterialValueHelper.getSecondaryTextColor(service, true)
|
||||
)!!, 1f
|
||||
)
|
||||
R.id.button_prev,
|
||||
service.getTintedDrawable(
|
||||
R.drawable.ic_skip_previous,
|
||||
MaterialValueHelper.getSecondaryTextColor(service, true)
|
||||
).toBitmap()
|
||||
)
|
||||
|
||||
// Link actions buttons to intents
|
||||
|
@ -150,14 +141,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()
|
||||
.into(object : SimpleTarget<BitmapPaletteWrapper>(imageSize, imageSize) {
|
||||
target = GlideApp.with(service).asBitmapPalette().songCoverOptions(song)
|
||||
.load(RetroGlideExtension.getSongModel(song))
|
||||
.centerCrop()
|
||||
.into(object : CustomTarget<BitmapPaletteWrapper>(imageSize, imageSize) {
|
||||
override fun onResourceReady(
|
||||
resource: BitmapPaletteWrapper,
|
||||
glideAnimation: GlideAnimation<in BitmapPaletteWrapper>
|
||||
transition: Transition<in BitmapPaletteWrapper>?,
|
||||
) {
|
||||
val palette = resource.palette
|
||||
update(
|
||||
|
@ -171,38 +163,31 @@ 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))
|
||||
}
|
||||
|
||||
override fun onLoadCleared(placeholder: Drawable?) {}
|
||||
|
||||
private fun update(bitmap: Bitmap?, color: Int) {
|
||||
// Set correct drawable for pause state
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_toggle_play_pause, ImageUtil.createBitmap(
|
||||
ImageUtil.getTintedVectorDrawable(
|
||||
service, playPauseRes, color
|
||||
)
|
||||
)
|
||||
R.id.button_toggle_play_pause,
|
||||
service.getTintedDrawable(playPauseRes, color).toBitmap()
|
||||
)
|
||||
|
||||
// Set prev/next button drawables
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_next, ImageUtil.createBitmap(
|
||||
ImageUtil.getTintedVectorDrawable(
|
||||
service, R.drawable.ic_skip_next, color
|
||||
)
|
||||
)
|
||||
R.id.button_next,
|
||||
service.getTintedDrawable(R.drawable.ic_skip_next, color).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_prev, ImageUtil.createBitmap(
|
||||
ImageUtil.getTintedVectorDrawable(
|
||||
service, R.drawable.ic_skip_previous, color
|
||||
)
|
||||
)
|
||||
R.id.button_prev,
|
||||
service.getTintedDrawable(R.drawable.ic_skip_previous, color).toBitmap()
|
||||
)
|
||||
|
||||
val image = getAlbumArtDrawable(service.resources, bitmap)
|
||||
val image = getAlbumArtDrawable(service, bitmap)
|
||||
val roundedBitmap = createRoundedBitmap(
|
||||
image, imageSize, imageSize, cardRadius, 0F, cardRadius, 0F
|
||||
)
|
||||
|
@ -223,13 +208,17 @@ class AppWidgetCard : BaseAppWidget() {
|
|||
MainActivity.EXPAND_PANEL,
|
||||
PreferenceUtil.isExpandPanel
|
||||
)
|
||||
var pendingIntent: PendingIntent
|
||||
|
||||
val serviceName = ComponentName(context, MusicService::class.java)
|
||||
|
||||
// Home
|
||||
action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
pendingIntent = PendingIntent.getActivity(context, 0, action, 0)
|
||||
var pendingIntent =
|
||||
PendingIntent.getActivity(
|
||||
context, 0, action, if (VersionUtils.hasMarshmallow())
|
||||
PendingIntent.FLAG_IMMUTABLE
|
||||
else 0
|
||||
)
|
||||
views.setOnClickPendingIntent(R.id.image, pendingIntent)
|
||||
views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
|
||||
|
||||
|
|
|
@ -0,0 +1,215 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.appwidgets
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.widget.RemoteViews
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
||||
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||
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.extensions.getTintedDrawable
|
||||
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.Companion.ACTION_TOGGLE_PAUSE
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.TOGGLE_FAVORITE
|
||||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import code.name.monkey.retromusic.util.RetroUtil
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import com.bumptech.glide.request.target.CustomTarget
|
||||
import com.bumptech.glide.request.target.Target
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
class AppWidgetCircle : BaseAppWidget() {
|
||||
private var target: Target<BitmapPaletteWrapper>? = null // for cancellation
|
||||
|
||||
/**
|
||||
* Initialize given widgets to default state, where we launch Music on default click and hide
|
||||
* actions if service not running.
|
||||
*/
|
||||
override fun defaultAppWidget(context: Context, appWidgetIds: IntArray) {
|
||||
val appWidgetView = RemoteViews(context.packageName, R.layout.app_widget_circle)
|
||||
|
||||
appWidgetView.setImageViewResource(R.id.image, R.drawable.default_audio_art)
|
||||
val secondaryColor = MaterialValueHelper.getSecondaryTextColor(context, true)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_toggle_play_pause,
|
||||
context.getTintedDrawable(
|
||||
R.drawable.ic_play_arrow,
|
||||
secondaryColor
|
||||
).toBitmap()
|
||||
)
|
||||
|
||||
linkButtons(context, appWidgetView)
|
||||
pushUpdate(context, appWidgetIds, appWidgetView)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update all active widget instances by pushing changes
|
||||
*/
|
||||
override fun performUpdate(service: MusicService, appWidgetIds: IntArray?) {
|
||||
val appWidgetView = RemoteViews(service.packageName, R.layout.app_widget_circle)
|
||||
|
||||
val isPlaying = service.isPlaying
|
||||
val song = service.currentSong
|
||||
|
||||
// Set correct drawable for pause state
|
||||
val playPauseRes =
|
||||
if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play_arrow
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_toggle_play_pause,
|
||||
service.getTintedDrawable(
|
||||
playPauseRes,
|
||||
MaterialValueHelper.getSecondaryTextColor(service, true)
|
||||
).toBitmap()
|
||||
)
|
||||
val isFavorite = runBlocking(Dispatchers.IO) {
|
||||
return@runBlocking MusicUtil.repository.isSongFavorite(song.id)
|
||||
}
|
||||
val favoriteRes =
|
||||
if (isFavorite) R.drawable.ic_favorite else R.drawable.ic_favorite_border
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_toggle_favorite,
|
||||
service.getTintedDrawable(
|
||||
favoriteRes,
|
||||
MaterialValueHelper.getSecondaryTextColor(service, true)
|
||||
).toBitmap()
|
||||
)
|
||||
|
||||
// Link actions buttons to intents
|
||||
linkButtons(service, appWidgetView)
|
||||
|
||||
if (imageSize == 0) {
|
||||
val p = RetroUtil.getScreenSize(service)
|
||||
imageSize = p.x.coerceAtMost(p.y)
|
||||
}
|
||||
|
||||
// Load the album cover async and push the update on completion
|
||||
service.runOnUiThread {
|
||||
if (target != null) {
|
||||
Glide.with(service).clear(target)
|
||||
}
|
||||
target = GlideApp.with(service).asBitmapPalette().songCoverOptions(song)
|
||||
.load(RetroGlideExtension.getSongModel(song))
|
||||
.apply(RequestOptions.circleCropTransform())
|
||||
.into(object : CustomTarget<BitmapPaletteWrapper>(imageSize, imageSize) {
|
||||
override fun onResourceReady(
|
||||
resource: BitmapPaletteWrapper,
|
||||
transition: Transition<in BitmapPaletteWrapper>?,
|
||||
) {
|
||||
val palette = resource.palette
|
||||
update(
|
||||
resource.bitmap, palette.getVibrantColor(
|
||||
palette.getMutedColor(
|
||||
MaterialValueHelper.getSecondaryTextColor(
|
||||
service, true
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onLoadFailed(errorDrawable: Drawable?) {
|
||||
super.onLoadFailed(errorDrawable)
|
||||
update(null, MaterialValueHelper.getSecondaryTextColor(service, true))
|
||||
}
|
||||
|
||||
private fun update(bitmap: Bitmap?, color: Int) {
|
||||
// Set correct drawable for pause state
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_toggle_play_pause,
|
||||
service.getTintedDrawable(
|
||||
playPauseRes, color
|
||||
).toBitmap()
|
||||
)
|
||||
|
||||
// Set favorite button drawables
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_toggle_favorite,
|
||||
service.getTintedDrawable(
|
||||
favoriteRes, color
|
||||
).toBitmap()
|
||||
)
|
||||
if (bitmap != null) {
|
||||
appWidgetView.setImageViewBitmap(R.id.image, bitmap)
|
||||
}
|
||||
|
||||
pushUpdate(service, appWidgetIds, appWidgetView)
|
||||
}
|
||||
|
||||
override fun onLoadCleared(placeholder: Drawable?) {}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Link up various button actions using [PendingIntent].
|
||||
*/
|
||||
private fun linkButtons(context: Context, views: RemoteViews) {
|
||||
val action = Intent(context, MainActivity::class.java)
|
||||
.putExtra(
|
||||
MainActivity.EXPAND_PANEL,
|
||||
PreferenceUtil.isExpandPanel
|
||||
)
|
||||
|
||||
val serviceName = ComponentName(context, MusicService::class.java)
|
||||
|
||||
// Home
|
||||
action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
var pendingIntent =
|
||||
PendingIntent.getActivity(
|
||||
context, 0, action, if (VersionUtils.hasMarshmallow())
|
||||
PendingIntent.FLAG_IMMUTABLE
|
||||
else 0
|
||||
)
|
||||
views.setOnClickPendingIntent(R.id.image, pendingIntent)
|
||||
// Favorite track
|
||||
pendingIntent = buildPendingIntent(context, TOGGLE_FAVORITE, serviceName)
|
||||
views.setOnClickPendingIntent(R.id.button_toggle_favorite, pendingIntent)
|
||||
|
||||
// Play and pause
|
||||
pendingIntent = buildPendingIntent(context, ACTION_TOGGLE_PAUSE, serviceName)
|
||||
views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
const val NAME = "app_widget_circle"
|
||||
|
||||
private var mInstance: AppWidgetCircle? = null
|
||||
private var imageSize = 0
|
||||
|
||||
val instance: AppWidgetCircle
|
||||
@Synchronized get() {
|
||||
if (mInstance == null) {
|
||||
mInstance = AppWidgetCircle()
|
||||
}
|
||||
return mInstance!!
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,24 +21,27 @@ import android.content.Intent
|
|||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.text.TextUtils
|
||||
import android.view.View
|
||||
import android.widget.RemoteViews
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
||||
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||
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.extensions.getTintedDrawable
|
||||
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.ImageUtil
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_REWIND
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_SKIP
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_TOGGLE_PAUSE
|
||||
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.CustomTarget
|
||||
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
|
||||
|
@ -54,33 +57,27 @@ class AppWidgetClassic : BaseAppWidget() {
|
|||
appWidgetView.setImageViewResource(R.id.image, R.drawable.default_audio_art)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_next,
|
||||
createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
context,
|
||||
R.drawable.ic_skip_next,
|
||||
MaterialValueHelper.getSecondaryTextColor(context, true)
|
||||
)!!, 1f
|
||||
)
|
||||
|
||||
context.getTintedDrawable(
|
||||
R.drawable.ic_skip_next,
|
||||
MaterialValueHelper.getSecondaryTextColor(context, true)
|
||||
).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_prev,
|
||||
createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
context,
|
||||
R.drawable.ic_skip_previous,
|
||||
MaterialValueHelper.getSecondaryTextColor(context, true)
|
||||
)!!, 1f
|
||||
)
|
||||
|
||||
context.getTintedDrawable(
|
||||
R.drawable.ic_skip_previous,
|
||||
MaterialValueHelper.getSecondaryTextColor(context, true)
|
||||
).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_toggle_play_pause,
|
||||
createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
context,
|
||||
R.drawable.ic_play_arrow_white_32dp,
|
||||
MaterialValueHelper.getSecondaryTextColor(context, true)
|
||||
)!!, 1f
|
||||
)
|
||||
|
||||
context.getTintedDrawable(
|
||||
R.drawable.ic_play_arrow_white_32dp,
|
||||
MaterialValueHelper.getSecondaryTextColor(context, true)
|
||||
).toBitmap()
|
||||
)
|
||||
|
||||
linkButtons(context, appWidgetView)
|
||||
|
@ -97,7 +94,7 @@ class AppWidgetClassic : BaseAppWidget() {
|
|||
val song = service.currentSong
|
||||
|
||||
// Set the titles and artwork
|
||||
if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) {
|
||||
if (song.title.isEmpty() && song.artistName.isEmpty()) {
|
||||
appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
|
||||
} else {
|
||||
appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE)
|
||||
|
@ -120,14 +117,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()
|
||||
.into(object : SimpleTarget<BitmapPaletteWrapper>(imageSize, imageSize) {
|
||||
target = GlideApp.with(service).asBitmapPalette().songCoverOptions(song)
|
||||
.load(RetroGlideExtension.getSongModel(song))
|
||||
//.checkIgnoreMediaStore()
|
||||
.centerCrop()
|
||||
.into(object : CustomTarget<BitmapPaletteWrapper>(imageSize, imageSize) {
|
||||
override fun onResourceReady(
|
||||
resource: BitmapPaletteWrapper,
|
||||
glideAnimation: GlideAnimation<in BitmapPaletteWrapper>
|
||||
transition: Transition<in BitmapPaletteWrapper>?,
|
||||
) {
|
||||
val palette = resource.palette
|
||||
update(
|
||||
|
@ -143,49 +142,42 @@ 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)
|
||||
}
|
||||
|
||||
override fun onLoadCleared(placeholder: Drawable?) {}
|
||||
|
||||
private fun update(bitmap: Bitmap?, color: Int) {
|
||||
// Set correct drawable for pause state
|
||||
val playPauseRes =
|
||||
if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play_arrow
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_toggle_play_pause,
|
||||
ImageUtil.createBitmap(
|
||||
ImageUtil.getTintedVectorDrawable(
|
||||
service,
|
||||
playPauseRes,
|
||||
color
|
||||
)
|
||||
)
|
||||
service.getTintedDrawable(
|
||||
playPauseRes,
|
||||
color
|
||||
).toBitmap()
|
||||
)
|
||||
|
||||
// Set prev/next button drawables
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_next,
|
||||
ImageUtil.createBitmap(
|
||||
ImageUtil.getTintedVectorDrawable(
|
||||
service,
|
||||
R.drawable.ic_skip_next,
|
||||
color
|
||||
)
|
||||
)
|
||||
service.getTintedDrawable(
|
||||
R.drawable.ic_skip_next,
|
||||
color
|
||||
).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_prev,
|
||||
ImageUtil.createBitmap(
|
||||
ImageUtil.getTintedVectorDrawable(
|
||||
service,
|
||||
R.drawable.ic_skip_previous,
|
||||
color
|
||||
)
|
||||
)
|
||||
service.getTintedDrawable(
|
||||
R.drawable.ic_skip_previous,
|
||||
color
|
||||
).toBitmap()
|
||||
)
|
||||
|
||||
val image = getAlbumArtDrawable(service.resources, bitmap)
|
||||
val image = getAlbumArtDrawable(service, bitmap)
|
||||
val roundedBitmap =
|
||||
createRoundedBitmap(
|
||||
image,
|
||||
|
@ -213,13 +205,16 @@ class AppWidgetClassic : BaseAppWidget() {
|
|||
MainActivity.EXPAND_PANEL,
|
||||
PreferenceUtil.isExpandPanel
|
||||
)
|
||||
var pendingIntent: PendingIntent
|
||||
|
||||
val serviceName = ComponentName(context, MusicService::class.java)
|
||||
|
||||
// Home
|
||||
action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
pendingIntent = PendingIntent.getActivity(context, 0, action, 0)
|
||||
var pendingIntent = PendingIntent.getActivity(
|
||||
context, 0, action, if (VersionUtils.hasMarshmallow())
|
||||
PendingIntent.FLAG_IMMUTABLE
|
||||
else 0
|
||||
)
|
||||
views.setOnClickPendingIntent(R.id.image, pendingIntent)
|
||||
views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
|
||||
|
||||
|
|
|
@ -0,0 +1,261 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.appwidgets
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.view.View
|
||||
import android.widget.RemoteViews
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
||||
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||
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.extensions.getTintedDrawable
|
||||
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.Companion.ACTION_REWIND
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_SKIP
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_TOGGLE_PAUSE
|
||||
import code.name.monkey.retromusic.util.DensityUtil
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.target.CustomTarget
|
||||
import com.bumptech.glide.request.target.Target
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
|
||||
class AppWidgetMD3 : BaseAppWidget() {
|
||||
private var target: Target<BitmapPaletteWrapper>? = null // for cancellation
|
||||
|
||||
/**
|
||||
* Initialize given widgets to default state, where we launch Music on default click and hide
|
||||
* actions if service not running.
|
||||
*/
|
||||
override fun defaultAppWidget(context: Context, appWidgetIds: IntArray) {
|
||||
val appWidgetView = RemoteViews(context.packageName, R.layout.app_widget_md3)
|
||||
|
||||
appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
|
||||
appWidgetView.setImageViewResource(R.id.image, R.drawable.default_audio_art)
|
||||
val secondaryColor = MaterialValueHelper.getSecondaryTextColor(context, true)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_next,
|
||||
context.getTintedDrawable(
|
||||
R.drawable.ic_skip_next,
|
||||
secondaryColor
|
||||
).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_prev,
|
||||
context.getTintedDrawable(
|
||||
R.drawable.ic_skip_previous,
|
||||
secondaryColor
|
||||
).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_toggle_play_pause,
|
||||
context.getTintedDrawable(
|
||||
R.drawable.ic_play_arrow_white_32dp,
|
||||
secondaryColor
|
||||
).toBitmap()
|
||||
)
|
||||
|
||||
linkButtons(context, appWidgetView)
|
||||
pushUpdate(context, appWidgetIds, appWidgetView)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update all active widget instances by pushing changes
|
||||
*/
|
||||
override fun performUpdate(service: MusicService, appWidgetIds: IntArray?) {
|
||||
val appWidgetView = RemoteViews(service.packageName, R.layout.app_widget_md3)
|
||||
|
||||
val isPlaying = service.isPlaying
|
||||
val song = service.currentSong
|
||||
|
||||
// Set the titles and artwork
|
||||
if (song.title.isEmpty() && song.artistName.isEmpty()) {
|
||||
appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
|
||||
} else {
|
||||
appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE)
|
||||
appWidgetView.setTextViewText(R.id.title, song.title)
|
||||
appWidgetView.setTextViewText(R.id.text, getSongArtistAndAlbum(song))
|
||||
}
|
||||
|
||||
// Set correct drawable for pause state
|
||||
val playPauseRes =
|
||||
if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play_arrow_white_32dp
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_toggle_play_pause,
|
||||
service.getTintedDrawable(
|
||||
playPauseRes,
|
||||
MaterialValueHelper.getSecondaryTextColor(service, true)
|
||||
).toBitmap()
|
||||
)
|
||||
|
||||
// Set prev/next button drawables
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_next,
|
||||
service.getTintedDrawable(
|
||||
R.drawable.ic_skip_next,
|
||||
MaterialValueHelper.getSecondaryTextColor(service, true)
|
||||
).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_prev,
|
||||
service.getTintedDrawable(
|
||||
R.drawable.ic_skip_previous,
|
||||
MaterialValueHelper.getSecondaryTextColor(service, true)
|
||||
).toBitmap()
|
||||
)
|
||||
|
||||
// Link actions buttons to intents
|
||||
linkButtons(service, appWidgetView)
|
||||
|
||||
if (imageSize == 0) {
|
||||
imageSize =
|
||||
service.resources.getDimensionPixelSize(R.dimen.app_widget_card_image_size)
|
||||
}
|
||||
if (cardRadius == 0f) {
|
||||
cardRadius =
|
||||
DensityUtil.dip2px(service, 8F).toFloat()
|
||||
}
|
||||
|
||||
// Load the album cover async and push the update on completion
|
||||
service.runOnUiThread {
|
||||
if (target != null) {
|
||||
Glide.with(service).clear(target)
|
||||
}
|
||||
target = GlideApp.with(service).asBitmapPalette().songCoverOptions(song)
|
||||
.load(RetroGlideExtension.getSongModel(song))
|
||||
.centerCrop()
|
||||
.into(object : CustomTarget<BitmapPaletteWrapper>(imageSize, imageSize) {
|
||||
override fun onResourceReady(
|
||||
resource: BitmapPaletteWrapper,
|
||||
transition: Transition<in BitmapPaletteWrapper>?,
|
||||
) {
|
||||
val palette = resource.palette
|
||||
update(
|
||||
resource.bitmap, palette.getVibrantColor(
|
||||
palette.getMutedColor(
|
||||
MaterialValueHelper.getSecondaryTextColor(
|
||||
service, true
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onLoadFailed(errorDrawable: Drawable?) {
|
||||
super.onLoadFailed(errorDrawable)
|
||||
update(null, MaterialValueHelper.getSecondaryTextColor(service, true))
|
||||
}
|
||||
|
||||
override fun onLoadCleared(placeholder: Drawable?) {}
|
||||
|
||||
private fun update(bitmap: Bitmap?, color: Int) {
|
||||
// Set correct drawable for pause state
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_toggle_play_pause,
|
||||
service.getTintedDrawable(playPauseRes, color).toBitmap()
|
||||
)
|
||||
|
||||
// Set prev/next button drawables
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_next,
|
||||
service.getTintedDrawable(R.drawable.ic_skip_next, color).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_prev,
|
||||
service.getTintedDrawable(R.drawable.ic_skip_previous, color).toBitmap()
|
||||
)
|
||||
|
||||
val image = getAlbumArtDrawable(service, bitmap)
|
||||
val roundedBitmap = createRoundedBitmap(
|
||||
image,
|
||||
imageSize,
|
||||
imageSize,
|
||||
cardRadius,
|
||||
cardRadius,
|
||||
cardRadius,
|
||||
cardRadius
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(R.id.image, roundedBitmap)
|
||||
|
||||
pushUpdate(service, appWidgetIds, appWidgetView)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Link up various button actions using [PendingIntent].
|
||||
*/
|
||||
private fun linkButtons(context: Context, views: RemoteViews) {
|
||||
val action = Intent(context, MainActivity::class.java)
|
||||
.putExtra(
|
||||
MainActivity.EXPAND_PANEL,
|
||||
PreferenceUtil.isExpandPanel
|
||||
)
|
||||
|
||||
val serviceName = ComponentName(context, MusicService::class.java)
|
||||
|
||||
// Home
|
||||
action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
var pendingIntent =
|
||||
PendingIntent.getActivity(
|
||||
context, 0, action, if (VersionUtils.hasMarshmallow())
|
||||
PendingIntent.FLAG_IMMUTABLE
|
||||
else 0
|
||||
)
|
||||
views.setOnClickPendingIntent(R.id.image, pendingIntent)
|
||||
views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
|
||||
|
||||
// Previous track
|
||||
pendingIntent = buildPendingIntent(context, ACTION_REWIND, serviceName)
|
||||
views.setOnClickPendingIntent(R.id.button_prev, pendingIntent)
|
||||
|
||||
// Play and pause
|
||||
pendingIntent = buildPendingIntent(context, ACTION_TOGGLE_PAUSE, serviceName)
|
||||
views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent)
|
||||
|
||||
// Next track
|
||||
pendingIntent = buildPendingIntent(context, ACTION_SKIP, serviceName)
|
||||
views.setOnClickPendingIntent(R.id.button_next, pendingIntent)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
const val NAME = "app_widget_md3"
|
||||
|
||||
private var mInstance: AppWidgetMD3? = null
|
||||
private var imageSize = 0
|
||||
private var cardRadius = 0F
|
||||
|
||||
val instance: AppWidgetMD3
|
||||
@Synchronized get() {
|
||||
if (mInstance == null) {
|
||||
mInstance = AppWidgetMD3()
|
||||
}
|
||||
return mInstance!!
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,23 +20,27 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.text.TextUtils
|
||||
import android.view.View
|
||||
import android.widget.RemoteViews
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
||||
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||
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.extensions.getTintedDrawable
|
||||
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.service.MusicService.Companion.ACTION_REWIND
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_SKIP
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_TOGGLE_PAUSE
|
||||
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.CustomTarget
|
||||
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
|
||||
|
@ -52,33 +56,26 @@ class AppWidgetSmall : BaseAppWidget() {
|
|||
appWidgetView.setImageViewResource(R.id.image, R.drawable.default_audio_art)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_next,
|
||||
createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
context,
|
||||
R.drawable.ic_skip_next,
|
||||
MaterialValueHelper.getSecondaryTextColor(context, true)
|
||||
)!!, 1f
|
||||
)
|
||||
context.getTintedDrawable(
|
||||
R.drawable.ic_skip_next,
|
||||
MaterialValueHelper.getSecondaryTextColor(context, true)
|
||||
).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_prev,
|
||||
createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
context,
|
||||
R.drawable.ic_skip_previous,
|
||||
MaterialValueHelper.getSecondaryTextColor(context, true)
|
||||
)!!, 1f
|
||||
)
|
||||
|
||||
context.getTintedDrawable(
|
||||
R.drawable.ic_skip_previous,
|
||||
MaterialValueHelper.getSecondaryTextColor(context, true)
|
||||
).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_toggle_play_pause,
|
||||
createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
context,
|
||||
R.drawable.ic_play_arrow_white_32dp,
|
||||
MaterialValueHelper.getSecondaryTextColor(context, true)
|
||||
)!!, 1f
|
||||
)
|
||||
|
||||
context.getTintedDrawable(
|
||||
R.drawable.ic_play_arrow_white_32dp,
|
||||
MaterialValueHelper.getSecondaryTextColor(context, true)
|
||||
).toBitmap()
|
||||
)
|
||||
|
||||
linkButtons(context, appWidgetView)
|
||||
|
@ -95,10 +92,10 @@ class AppWidgetSmall : BaseAppWidget() {
|
|||
val song = service.currentSong
|
||||
|
||||
// Set the titles and artwork
|
||||
if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) {
|
||||
if (song.title.isEmpty() && song.artistName.isEmpty()) {
|
||||
appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
|
||||
} else {
|
||||
if (TextUtils.isEmpty(song.title) || TextUtils.isEmpty(song.artistName)) {
|
||||
if (song.title.isEmpty() || song.artistName.isEmpty()) {
|
||||
appWidgetView.setTextViewText(R.id.text_separator, "")
|
||||
} else {
|
||||
appWidgetView.setTextViewText(R.id.text_separator, "•")
|
||||
|
@ -123,14 +120,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()
|
||||
.into(object : SimpleTarget<BitmapPaletteWrapper>(imageSize, imageSize) {
|
||||
target = GlideApp.with(service).asBitmapPalette().songCoverOptions(song)
|
||||
//.checkIgnoreMediaStore()
|
||||
.load(RetroGlideExtension.getSongModel(song))
|
||||
.centerCrop()
|
||||
.into(object : CustomTarget<BitmapPaletteWrapper>(imageSize, imageSize) {
|
||||
override fun onResourceReady(
|
||||
resource: BitmapPaletteWrapper,
|
||||
glideAnimation: GlideAnimation<in BitmapPaletteWrapper>
|
||||
transition: Transition<in BitmapPaletteWrapper>?,
|
||||
) {
|
||||
val palette = resource.palette
|
||||
update(
|
||||
|
@ -144,8 +143,12 @@ 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))
|
||||
}
|
||||
|
||||
override fun onLoadCleared(placeholder: Drawable?) {
|
||||
update(null, MaterialValueHelper.getSecondaryTextColor(service, true))
|
||||
}
|
||||
|
||||
|
@ -154,30 +157,21 @@ class AppWidgetSmall : BaseAppWidget() {
|
|||
val playPauseRes = if (isPlaying) R.drawable.ic_pause
|
||||
else R.drawable.ic_play_arrow_white_32dp
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_toggle_play_pause, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
service, playPauseRes, color
|
||||
)!!, 1f
|
||||
)
|
||||
R.id.button_toggle_play_pause,
|
||||
service.getTintedDrawable(playPauseRes, color).toBitmap()
|
||||
)
|
||||
|
||||
// Set prev/next button drawables
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_next, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
service, R.drawable.ic_skip_next, color
|
||||
)!!, 1f
|
||||
)
|
||||
R.id.button_next,
|
||||
service.getTintedDrawable(R.drawable.ic_skip_next, color).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_prev, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
service, R.drawable.ic_skip_previous, color
|
||||
)!!, 1f
|
||||
)
|
||||
R.id.button_prev,
|
||||
service.getTintedDrawable(R.drawable.ic_skip_previous, color).toBitmap()
|
||||
)
|
||||
|
||||
val image = getAlbumArtDrawable(service.resources, bitmap)
|
||||
val image = getAlbumArtDrawable(service, bitmap)
|
||||
val roundedBitmap = createRoundedBitmap(
|
||||
image, imageSize, imageSize, cardRadius, 0f, 0f, 0f
|
||||
)
|
||||
|
@ -198,13 +192,17 @@ class AppWidgetSmall : BaseAppWidget() {
|
|||
MainActivity.EXPAND_PANEL,
|
||||
PreferenceUtil.isExpandPanel
|
||||
)
|
||||
var pendingIntent: PendingIntent
|
||||
|
||||
val serviceName = ComponentName(context, MusicService::class.java)
|
||||
|
||||
// Home
|
||||
action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
pendingIntent = PendingIntent.getActivity(context, 0, action, 0)
|
||||
var pendingIntent =
|
||||
PendingIntent.getActivity(
|
||||
context, 0, action, if (VersionUtils.hasMarshmallow())
|
||||
PendingIntent.FLAG_IMMUTABLE
|
||||
else 0
|
||||
)
|
||||
views.setOnClickPendingIntent(R.id.image, pendingIntent)
|
||||
views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
|
||||
|
||||
|
|
|
@ -18,49 +18,44 @@ import android.app.PendingIntent
|
|||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.text.TextUtils
|
||||
import android.view.View
|
||||
import android.widget.RemoteViews
|
||||
import androidx.core.content.ContextCompat
|
||||
import code.name.monkey.retromusic.App
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||
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.extensions.getTintedDrawable
|
||||
import code.name.monkey.retromusic.service.MusicService
|
||||
import code.name.monkey.retromusic.service.MusicService.*
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_REWIND
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_SKIP
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_TOGGLE_PAUSE
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import code.name.monkey.retromusic.util.RetroUtil
|
||||
|
||||
class AppWidgetText : BaseAppWidget() {
|
||||
override fun defaultAppWidget(context: Context, appWidgetIds: IntArray) {
|
||||
val appWidgetView = RemoteViews(context.packageName, R.layout.app_widget_text)
|
||||
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_next, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
context, R.drawable.ic_skip_next, ContextCompat.getColor(
|
||||
context, R.color.md_white_1000
|
||||
)
|
||||
)!!, 1f
|
||||
)
|
||||
R.id.button_next,
|
||||
context.getTintedDrawable(R.drawable.ic_skip_next, ContextCompat.getColor(
|
||||
context, R.color.md_white_1000
|
||||
)).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_prev, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
context, R.drawable.ic_skip_previous, ContextCompat.getColor(
|
||||
context, R.color.md_white_1000
|
||||
)
|
||||
)!!, 1f
|
||||
R.id.button_prev,
|
||||
context.getTintedDrawable(R.drawable.ic_skip_previous, ContextCompat.getColor(
|
||||
context, R.color.md_white_1000
|
||||
)
|
||||
).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_toggle_play_pause, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
context, R.drawable.ic_play_arrow_white_32dp, ContextCompat.getColor(
|
||||
context, R.color.md_white_1000
|
||||
)
|
||||
)!!, 1f
|
||||
R.id.button_toggle_play_pause,
|
||||
context.getTintedDrawable(R.drawable.ic_play_arrow_white_32dp, ContextCompat.getColor(
|
||||
context, R.color.md_white_1000
|
||||
)
|
||||
).toBitmap()
|
||||
)
|
||||
|
||||
appWidgetView.setTextColor(
|
||||
|
@ -83,13 +78,16 @@ class AppWidgetText : BaseAppWidget() {
|
|||
MainActivity.EXPAND_PANEL,
|
||||
PreferenceUtil.isExpandPanel
|
||||
)
|
||||
var pendingIntent: PendingIntent
|
||||
|
||||
val serviceName = ComponentName(context, MusicService::class.java)
|
||||
|
||||
// Home
|
||||
action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
pendingIntent = PendingIntent.getActivity(context, 0, action, 0)
|
||||
var pendingIntent = PendingIntent.getActivity(
|
||||
context, 0, action, if (VersionUtils.hasMarshmallow())
|
||||
PendingIntent.FLAG_IMMUTABLE
|
||||
else 0
|
||||
)
|
||||
views.setOnClickPendingIntent(R.id.image, pendingIntent)
|
||||
views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
|
||||
|
||||
|
@ -113,7 +111,7 @@ class AppWidgetText : BaseAppWidget() {
|
|||
val song = service.currentSong
|
||||
|
||||
// Set the titles and artwork
|
||||
if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) {
|
||||
if (song.title.isEmpty() && song.artistName.isEmpty()) {
|
||||
appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
|
||||
} else {
|
||||
appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE)
|
||||
|
@ -127,35 +125,29 @@ class AppWidgetText : BaseAppWidget() {
|
|||
val playPauseRes = if (isPlaying) R.drawable.ic_pause
|
||||
else R.drawable.ic_play_arrow_white_32dp
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_toggle_play_pause, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
App.getContext(), playPauseRes, ContextCompat.getColor(
|
||||
App.getContext(), R.color.md_white_1000
|
||||
)
|
||||
)!!, 1f
|
||||
)
|
||||
R.id.button_toggle_play_pause,
|
||||
service.getTintedDrawable(playPauseRes, ContextCompat.getColor(
|
||||
service, R.color.md_white_1000)
|
||||
).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_next, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
App.getContext(),
|
||||
R.drawable.ic_skip_next,
|
||||
ContextCompat.getColor(
|
||||
App.getContext(), R.color.md_white_1000
|
||||
)
|
||||
)!!, 1f
|
||||
)
|
||||
R.id.button_next,
|
||||
service.getTintedDrawable(
|
||||
R.drawable.ic_skip_next,
|
||||
ContextCompat.getColor(
|
||||
service,
|
||||
R.color.md_white_1000
|
||||
)
|
||||
).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_prev, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
App.getContext(),
|
||||
R.drawable.ic_skip_previous,
|
||||
ContextCompat.getColor(
|
||||
App.getContext(), R.color.md_white_1000
|
||||
)
|
||||
)!!, 1f
|
||||
)
|
||||
R.id.button_prev,
|
||||
service.getTintedDrawable(
|
||||
R.drawable.ic_skip_previous,
|
||||
ContextCompat.getColor(
|
||||
service, R.color.md_white_1000
|
||||
)
|
||||
).toBitmap()
|
||||
)
|
||||
|
||||
pushUpdate(service.applicationContext, appWidgetIds, appWidgetView)
|
||||
|
|
|
@ -20,19 +20,20 @@ import android.appwidget.AppWidgetProvider
|
|||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Resources
|
||||
import android.graphics.*
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import android.text.TextUtils
|
||||
import android.widget.RemoteViews
|
||||
import androidx.core.content.ContextCompat
|
||||
import code.name.monkey.retromusic.App
|
||||
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.model.Song
|
||||
import code.name.monkey.retromusic.service.MusicService
|
||||
import code.name.monkey.retromusic.service.MusicService.*
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.APP_WIDGET_UPDATE
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.EXTRA_APP_WIDGET_NAME
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.FAVORITE_STATE_CHANGED
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.META_CHANGED
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.PLAY_STATE_CHANGED
|
||||
|
||||
abstract class BaseAppWidget : AppWidgetProvider() {
|
||||
|
||||
|
@ -57,7 +58,7 @@ abstract class BaseAppWidget : AppWidgetProvider() {
|
|||
*/
|
||||
fun notifyChange(service: MusicService, what: String) {
|
||||
if (hasInstances(service)) {
|
||||
if (META_CHANGED == what || PLAY_STATE_CHANGED == what) {
|
||||
if (META_CHANGED == what || PLAY_STATE_CHANGED == what || FAVORITE_STATE_CHANGED == what) {
|
||||
performUpdate(service, null)
|
||||
}
|
||||
}
|
||||
|
@ -96,10 +97,14 @@ abstract class BaseAppWidget : AppWidgetProvider() {
|
|||
): PendingIntent {
|
||||
val intent = Intent(action)
|
||||
intent.component = serviceName
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
PendingIntent.getForegroundService(context, 0, intent, 0)
|
||||
return if (VersionUtils.hasOreo()) {
|
||||
PendingIntent.getForegroundService(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
|
||||
} else {
|
||||
PendingIntent.getService(context, 0, intent, 0)
|
||||
PendingIntent.getService(
|
||||
context, 0, intent, if (VersionUtils.hasMarshmallow())
|
||||
PendingIntent.FLAG_IMMUTABLE
|
||||
else 0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,18 +112,18 @@ abstract class BaseAppWidget : AppWidgetProvider() {
|
|||
|
||||
abstract fun performUpdate(service: MusicService, appWidgetIds: IntArray?)
|
||||
|
||||
protected fun getAlbumArtDrawable(resources: Resources, bitmap: Bitmap?): Drawable {
|
||||
protected fun getAlbumArtDrawable(context: Context, bitmap: Bitmap?): Drawable {
|
||||
return if (bitmap == null) {
|
||||
ContextCompat.getDrawable(App.getContext(), R.drawable.default_audio_art)!!
|
||||
ContextCompat.getDrawable(context, R.drawable.default_audio_art)!!
|
||||
} else {
|
||||
BitmapDrawable(resources, bitmap)
|
||||
BitmapDrawable(context.resources, bitmap)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun getSongArtistAndAlbum(song: Song): String {
|
||||
val builder = StringBuilder()
|
||||
builder.append(song.artistName)
|
||||
if (!TextUtils.isEmpty(song.artistName) && !TextUtils.isEmpty(song.albumName)) {
|
||||
if (song.artistName.isNotEmpty() && song.albumName.isNotEmpty()) {
|
||||
builder.append(" • ")
|
||||
}
|
||||
builder.append(song.albumName)
|
||||
|
@ -162,18 +167,6 @@ abstract class BaseAppWidget : AppWidgetProvider() {
|
|||
return rounded
|
||||
}
|
||||
|
||||
fun createBitmap(drawable: Drawable, sizeMultiplier: Float): Bitmap {
|
||||
val bitmap = Bitmap.createBitmap(
|
||||
(drawable.intrinsicWidth * sizeMultiplier).toInt(),
|
||||
(drawable.intrinsicHeight * sizeMultiplier).toInt(),
|
||||
Bitmap.Config.ARGB_8888
|
||||
)
|
||||
val c = Canvas(bitmap)
|
||||
drawable.setBounds(0, 0, c.width, c.height)
|
||||
drawable.draw(c)
|
||||
return bitmap
|
||||
}
|
||||
|
||||
protected fun composeRoundedRectPath(
|
||||
rect: RectF,
|
||||
tl: Float,
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* 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__";
|
||||
public static final String RECENT_ROOT = "__RECENT__";
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,283 @@
|
|||
/*
|
||||
* 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.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
|
||||
|
||||
|
||||
/**
|
||||
* 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()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
package code.name.monkey.retromusic.auto
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.support.v4.media.MediaBrowserCompat
|
||||
import android.support.v4.media.MediaDescriptionCompat
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import androidx.core.os.bundleOf
|
||||
|
||||
|
||||
internal object AutoMediaItem {
|
||||
fun with(context: Context): Builder {
|
||||
return Builder(context)
|
||||
}
|
||||
|
||||
internal class Builder(private 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(
|
||||
ResourcesCompat.getDrawable(
|
||||
mContext.resources,
|
||||
iconDrawableId,
|
||||
mContext.theme
|
||||
)?.toBitmap()
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
fun gridLayout(isGrid: Boolean): Builder {
|
||||
|
||||
val hints = bundleOf(
|
||||
CONTENT_STYLE_SUPPORTED to true,
|
||||
CONTENT_STYLE_BROWSABLE_HINT to
|
||||
if (isGrid) CONTENT_STYLE_GRID_ITEM_HINT_VALUE
|
||||
else CONTENT_STYLE_LIST_ITEM_HINT_VALUE,
|
||||
CONTENT_STYLE_PLAYABLE_HINT to
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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 org.json.JSONObject
|
||||
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(),
|
||||
if (position != -1) position else 0,
|
||||
MediaStatus.REPEAT_MODE_REPEAT_OFF,
|
||||
progress,
|
||||
JSONObject()
|
||||
)
|
||||
} 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()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
@file:Suppress("unused")
|
||||
|
||||
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
|
||||
|
||||
|
||||
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): MutableList<SessionProvider>? {
|
||||
return null
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package code.name.monkey.retromusic.cast
|
||||
|
||||
import com.google.android.gms.cast.framework.CastSession
|
||||
import com.google.android.gms.cast.framework.SessionManagerListener
|
||||
|
||||
interface RetroSessionManagerListener : 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(castSession: CastSession) {}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
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"
|
||||
}
|
||||
|
||||
override fun serve(session: IHTTPSession?): Response {
|
||||
if (session?.uri?.contains(PART_COVER_ART) == true) {
|
||||
val albumId = session.parameters?.get(PARAM_ID)?.get(0) ?: 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 (session?.uri?.contains(PART_SONG) == true) {
|
||||
val songId = session.parameters?.get(PARAM_ID)?.get(0) ?: return errorResponse()
|
||||
val songUri = MusicUtil.getSongFileUri(songId.toLong())
|
||||
val songPath = MusicUtil.getSongFilePath(context, songUri)
|
||||
val song = File(songPath)
|
||||
return serveFile(session.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,
|
||||
file.inputStream(), 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)
|
||||
}
|
||||
}
|
|
@ -26,6 +26,8 @@ interface HistoryDao {
|
|||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insertSongInHistory(historyEntity: HistoryEntity)
|
||||
|
||||
@Query("DELETE FROM HistoryEntity WHERE id= :songId")
|
||||
fun deleteSongInHistory(songId: Long)
|
||||
@Query("SELECT * FROM HistoryEntity WHERE id = :songId LIMIT 1")
|
||||
suspend fun isSongPresentInHistory(songId: Long): HistoryEntity?
|
||||
|
||||
|
@ -37,4 +39,7 @@ interface HistoryDao {
|
|||
|
||||
@Query("SELECT * FROM HistoryEntity ORDER BY time_played DESC LIMIT $HISTORY_LIMIT")
|
||||
fun observableHistorySongs(): LiveData<List<HistoryEntity>>
|
||||
|
||||
@Query("DELETE FROM HistoryEntity")
|
||||
suspend fun clearHistory()
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ interface PlaylistDao {
|
|||
suspend fun renamePlaylist(playlistId: Long, name: String)
|
||||
|
||||
@Query("SELECT * FROM PlaylistEntity WHERE playlist_name = :name")
|
||||
fun isPlaylistExists(name: String): List<PlaylistEntity>
|
||||
fun playlist(name: String): List<PlaylistEntity>
|
||||
|
||||
@Query("SELECT * FROM PlaylistEntity")
|
||||
suspend fun playlists(): List<PlaylistEntity>
|
||||
|
@ -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
|
||||
|
@ -64,4 +64,7 @@ interface PlaylistDao {
|
|||
|
||||
@Query("SELECT * FROM SongEntity WHERE playlist_creator_id= :playlistId")
|
||||
fun favoritesSongs(playlistId: Long): List<SongEntity>
|
||||
|
||||
@Query("SELECT EXISTS(SELECT * FROM PlaylistEntity WHERE playlist_id = :playlistId)")
|
||||
fun checkPlaylistExists(playlistId: Long): LiveData<Boolean>
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)])
|
||||
|
|
|
@ -28,12 +28,6 @@ fun List<SongEntity>.toSongs(): List<Song> {
|
|||
}
|
||||
}
|
||||
|
||||
fun List<Song>.toSongs(playlistId: Long): List<SongEntity> {
|
||||
return map {
|
||||
it.toSongEntity(playlistId)
|
||||
}
|
||||
}
|
||||
|
||||
fun Song.toHistoryEntity(timePlayed: Long): HistoryEntity {
|
||||
return HistoryEntity(
|
||||
id = id,
|
||||
|
|
|
@ -16,23 +16,17 @@ package code.name.monkey.retromusic.dialogs
|
|||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.widget.ArrayAdapter
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import code.name.monkey.retromusic.EXTRA_PLAYLISTS
|
||||
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.toSongsEntity
|
||||
import code.name.monkey.retromusic.extensions.colorButtons
|
||||
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.Playlists
|
||||
import code.name.monkey.retromusic.model.Song
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
|
||||
|
||||
class AddToPlaylistDialog : DialogFragment() {
|
||||
|
@ -55,12 +49,6 @@ class AddToPlaylistDialog : DialogFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun playlistAdapter(playlists: List<String>): ArrayAdapter<String> {
|
||||
val adapter = ArrayAdapter<String>(requireContext(), R.layout.item_simple_text, R.id.title)
|
||||
adapter.addAll(playlists)
|
||||
return adapter
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val playlistEntities = extraNotNull<List<PlaylistEntity>>(EXTRA_PLAYLISTS).value
|
||||
val songs = extraNotNull<List<Song>>(EXTRA_SONG).value
|
||||
|
@ -70,21 +58,17 @@ class AddToPlaylistDialog : DialogFragment() {
|
|||
playlistNames.add(entity.playlistName)
|
||||
}
|
||||
return materialDialog(R.string.add_playlist_title)
|
||||
.setAdapter(
|
||||
playlistAdapter(playlistNames)
|
||||
) { dialog, which ->
|
||||
if (which == 0) {
|
||||
.setItems(playlistNames.toTypedArray()) { dialog, which->
|
||||
if (which == 0) {
|
||||
showCreateDialog(songs)
|
||||
} else {
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
val songEntities = songs.toSongsEntity(playlistEntities[which - 1])
|
||||
libraryViewModel.insertSongs(songEntities)
|
||||
libraryViewModel.forceReload(Playlists)
|
||||
}
|
||||
libraryViewModel.addToPlaylist(requireContext(), playlistNames[which], songs)
|
||||
}
|
||||
dialog.dismiss()
|
||||
}
|
||||
.create().colorButtons()
|
||||
.setNegativeButton(R.string.action_cancel, null)
|
||||
.create()
|
||||
.colorButtons()
|
||||
}
|
||||
|
||||
private fun showCreateDialog(songs: List<Song>) {
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
package code.name.monkey.retromusic.dialogs
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.extensions.materialDialog
|
||||
import code.name.monkey.retromusic.util.getExternalStorageDirectory
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.afollestad.materialdialogs.list.listItems
|
||||
import com.afollestad.materialdialogs.list.updateListItems
|
||||
import java.io.File
|
||||
|
||||
class BlacklistFolderChooserDialog : DialogFragment() {
|
||||
private var initialPath: String = 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?.getOrNull(i)?.name
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
private fun listFiles(): Array<File>? {
|
||||
val results = mutableListOf<File>()
|
||||
parentFolder?.listFiles()?.let { files ->
|
||||
files.forEach { file -> if (file.isDirectory) results.add(file) }
|
||||
return results.sortedBy { it.name }.toTypedArray()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
var mSavedInstanceState = savedInstanceState
|
||||
if (VersionUtils.hasMarshmallow()
|
||||
&& ActivityCompat.checkSelfPermission(
|
||||
requireActivity(), Manifest.permission.READ_EXTERNAL_STORAGE
|
||||
)
|
||||
!= PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
return materialDialog().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()
|
||||
.title(text = parentFolder?.absolutePath)
|
||||
.listItems(
|
||||
items = contentsArray.toCharSequence(),
|
||||
waitForPositiveButton = false
|
||||
) { _: MaterialDialog, i: Int, _: CharSequence ->
|
||||
onSelection(i)
|
||||
}
|
||||
.noAutoDismiss()
|
||||
.positiveButton(res = R.string.add_action) {
|
||||
callback?.onFolderSelection(requireContext(), 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?.getOrNull(if (canGoUp) i - 1 else i)
|
||||
canGoUp = true
|
||||
if (parentFolder?.absolutePath == "/storage/emulated") {
|
||||
parentFolder = 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(context: Context, folder: File)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun create(): BlacklistFolderChooserDialog {
|
||||
return BlacklistFolderChooserDialog()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,22 +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 androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import code.name.monkey.retromusic.EXTRA_SONG
|
||||
import code.name.monkey.retromusic.R
|
||||
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.model.Song
|
||||
import kotlinx.android.synthetic.main.dialog_playlist.view.*
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
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 {
|
||||
|
@ -49,25 +50,32 @@ 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
|
||||
) { _, _ ->
|
||||
val playlistName = playlistView.text.toString()
|
||||
if (!TextUtils.isEmpty(playlistName)) {
|
||||
libraryViewModel.addToPlaylist(playlistName, songs)
|
||||
|
||||
libraryViewModel.addToPlaylist(requireContext(), playlistName, songs)
|
||||
} else {
|
||||
playlistContainer.error = "Playlist is can't be empty"
|
||||
playlistContainer.error = "Playlist name can't be empty"
|
||||
}
|
||||
}
|
||||
.setNegativeButton(R.string.action_cancel, null)
|
||||
.create()
|
||||
.colorButtons()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ package code.name.monkey.retromusic.dialogs
|
|||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.core.text.parseAsHtml
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import code.name.monkey.retromusic.EXTRA_PLAYLIST
|
||||
import code.name.monkey.retromusic.R
|
||||
|
@ -55,16 +55,10 @@ class DeletePlaylistDialog : DialogFragment() {
|
|||
//noinspection ConstantConditions
|
||||
if (playlists.size > 1) {
|
||||
title = R.string.delete_playlists_title
|
||||
message = HtmlCompat.fromHtml(
|
||||
String.format(getString(R.string.delete_x_playlists), playlists.size),
|
||||
HtmlCompat.FROM_HTML_MODE_LEGACY
|
||||
)
|
||||
message = String.format(getString(R.string.delete_x_playlists), playlists.size).parseAsHtml()
|
||||
} else {
|
||||
title = R.string.delete_playlist_title
|
||||
message = HtmlCompat.fromHtml(
|
||||
String.format(getString(R.string.delete_playlist_x), playlists[0].playlistName),
|
||||
HtmlCompat.FROM_HTML_MODE_LEGACY
|
||||
)
|
||||
message = String.format(getString(R.string.delete_playlist_x), playlists[0].playlistName).parseAsHtml()
|
||||
}
|
||||
|
||||
return materialDialog(title)
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue