diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0dd89ff5aca500f8de1f37063ff2844912b06b13..0b7d32bd08fccfd5963e6d16dfec54d92ed59a7e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -7,6 +7,7 @@ plugins { id("kotlin-android") id("androidx.navigation.safeargs.kotlin") id("kotlin-parcelize") + id("kotlin-kapt") id("org.jlleitschuh.gradle.ktlint") version "11.2.0" id("com.gladed.androidgitversion") version "0.4.14" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 62f7deb597815e258a147a7679a1a54c50835cb9..9bfd708cfac8267d544607fb306676897c38a824 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -22,13 +22,11 @@ android:name=".activities.SplashActivity" android:launchMode="singleInstance" android:noHistory="true" - android:screenOrientation="portrait" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.VIEW" /> - <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> @@ -40,9 +38,7 @@ android:launchMode="singleInstance" android:screenOrientation="portrait" /> - <activity - android:name=".activities.MainActivity" - android:screenOrientation="portrait" /> + <activity android:name=".activities.MainActivity" /> <activity android:name=".activities.DownloadsActivity" diff --git a/app/src/main/java/audio/funkwhale/ffa/activities/MainActivity.kt b/app/src/main/java/audio/funkwhale/ffa/activities/MainActivity.kt index d8c916f476f357fa715f6ec1149ca163af44dd33..dbe57fbf42dd96ae6d6af4e838cb391815c61a94 100644 --- a/app/src/main/java/audio/funkwhale/ffa/activities/MainActivity.kt +++ b/app/src/main/java/audio/funkwhale/ffa/activities/MainActivity.kt @@ -34,6 +34,7 @@ import audio.funkwhale.ffa.databinding.ActivityMainBinding import audio.funkwhale.ffa.fragments.AddToPlaylistDialog import audio.funkwhale.ffa.fragments.BrowseFragmentDirections import audio.funkwhale.ffa.fragments.LandscapeQueueFragment +import audio.funkwhale.ffa.fragments.NowPlayingFragment import audio.funkwhale.ffa.fragments.QueueFragment import audio.funkwhale.ffa.fragments.TrackInfoDetailsFragment import audio.funkwhale.ffa.model.Track @@ -67,6 +68,9 @@ import com.github.kittinunf.fuel.Fuel import com.github.kittinunf.fuel.coroutines.awaitStringResponse import com.google.android.exoplayer2.Player import com.google.android.exoplayer2.offline.DownloadService +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED +import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED import com.google.gson.Gson import com.preference.PowerPreference import jp.wasabeef.picasso.transformations.RoundedCornersTransformation @@ -82,7 +86,6 @@ class MainActivity : AppCompatActivity() { LOGOUT(1001) } - private val favoriteRepository = FavoritesRepository(this) private val favoritedRepository = FavoritedRepository(this) private var menu: Menu? = null @@ -104,8 +107,8 @@ class MainActivity : AppCompatActivity() { setSupportActionBar(binding.appbar) onBackPressedDispatcher.addCallback(this) { - if (binding.nowPlaying.isOpened()) { - binding.nowPlaying.close() + if (binding.nowPlayingBottomSheet.isOpen) { + binding.nowPlayingBottomSheet.close() } else { navigation.navigateUp() } @@ -121,66 +124,16 @@ class MainActivity : AppCompatActivity() { override fun onResume() { super.onResume() - findViewById<DisableableFrameLayout?>(R.id.container)?.apply { - setShouldRegisterTouch { - if (binding.nowPlaying.isOpened()) { - binding.nowPlaying.close() - false - } else { - true - } - } - } - - favoritedRepository.update(this, lifecycleScope) - - startService(Intent(this, PlayerService::class.java)) - DownloadService.start(this, PinService::class.java) - - CommandBus.send(Command.RefreshService) - - lifecycleScope.launch(IO) { - Userinfo.get(this@MainActivity, oAuth) - } - - with(binding) { + binding.nowPlaying.getFragment<NowPlayingFragment>().apply { + favoritedRepository.update(requireContext(), lifecycleScope) - nowPlayingContainer?.nowPlayingToggle?.setOnClickListener { - CommandBus.send(Command.ToggleState) - } + startService(Intent(requireContext(), PlayerService::class.java)) + DownloadService.start(requireContext(), PinService::class.java) - nowPlayingContainer?.nowPlayingNext?.setOnClickListener { - CommandBus.send(Command.NextTrack) - } + CommandBus.send(Command.RefreshService) - nowPlayingContainer?.nowPlayingDetailsPrevious?.setOnClickListener { - CommandBus.send(Command.PreviousTrack) - } - - nowPlayingContainer?.nowPlayingDetailsNext?.setOnClickListener { - CommandBus.send(Command.NextTrack) - } - - nowPlayingContainer?.nowPlayingDetailsToggle?.setOnClickListener { - CommandBus.send(Command.ToggleState) - } - - binding.nowPlayingContainer?.nowPlayingDetailsProgress?.setOnSeekBarChangeListener( - object : SeekBar.OnSeekBarChangeListener { - override fun onStopTrackingTouch(view: SeekBar?) {} - - override fun onStartTrackingTouch(view: SeekBar?) {} - - override fun onProgressChanged(view: SeekBar?, progress: Int, fromUser: Boolean) { - if (fromUser) { - CommandBus.send(Command.Seek(progress)) - } - } - }) - - landscapeQueue?.let { - supportFragmentManager.beginTransaction() - .replace(R.id.landscape_queue, LandscapeQueueFragment()).commit() + lifecycleScope.launch(IO) { + Userinfo.get(this@MainActivity, oAuth) } } } @@ -223,7 +176,7 @@ class MainActivity : AppCompatActivity() { override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { android.R.id.home -> { - binding.nowPlaying.close() + binding.nowPlayingBottomSheet.close() navigation.popBackStack(R.id.browseFragment, false) } @@ -298,70 +251,22 @@ class MainActivity : AppCompatActivity() { private fun watchEventBus() { lifecycleScope.launch(Main) { EventBus.get().collect { event -> - if (event is Event.LogOut) { - FFA.get().deleteAllData(this@MainActivity) - startActivity( - Intent(this@MainActivity, LoginActivity::class.java).apply { - flags = Intent.FLAG_ACTIVITY_NO_HISTORY - } - ) - - finish() - } else if (event is Event.PlaybackError) { - toast(event.message) - } else if (event is Event.Buffering) { - when (event.value) { - true -> binding.nowPlayingContainer?.nowPlayingBuffering?.visibility = View.VISIBLE - false -> binding.nowPlayingContainer?.nowPlayingBuffering?.visibility = View.GONE - } - } else if (event is Event.PlaybackStopped) { - if (binding.nowPlaying.visibility == View.VISIBLE) { - (binding.navHostFragment.layoutParams as? ViewGroup.MarginLayoutParams)?.let { - it.bottomMargin = it.bottomMargin / 2 - } - - binding.landscapeQueue?.let { landscape_queue -> - (landscape_queue.layoutParams as? ViewGroup.MarginLayoutParams)?.let { - it.bottomMargin = it.bottomMargin / 2 + when(event) { + is Event.LogOut -> logout() + is Event.PlaybackError -> toast(event.message) + is Event.PlaybackStopped -> binding.nowPlayingBottomSheet.hide() + is Event.TrackFinished -> incrementListenCount(event.track) + is Event.QueueChanged -> { + if(binding.nowPlayingBottomSheet.isHidden) binding.nowPlayingBottomSheet.show() + findViewById<View>(R.id.nav_queue)?.let { view -> + ObjectAnimator.ofFloat(view, View.ROTATION, 0f, 360f).let { + it.duration = 500 + it.interpolator = AccelerateDecelerateInterpolator() + it.start() } } - - binding.nowPlaying.animate() - .alpha(0.0f) - .setDuration(400) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animator: Animator) { - binding.nowPlaying.visibility = View.GONE - } - }) - .start() - } - } else if (event is Event.TrackFinished) { - incrementListenCount(event.track) - } else if (event is Event.StateChanged) { - when (event.playing) { - true -> { - binding.nowPlayingContainer?.nowPlayingToggle?.icon = - AppCompatResources.getDrawable(this@MainActivity, R.drawable.pause) - binding.nowPlayingContainer?.nowPlayingDetailsToggle?.icon = - AppCompatResources.getDrawable(this@MainActivity, R.drawable.pause) - } - - false -> { - binding.nowPlayingContainer?.nowPlayingToggle?.icon = - AppCompatResources.getDrawable(this@MainActivity, R.drawable.play) - binding.nowPlayingContainer?.nowPlayingDetailsToggle?.icon = - AppCompatResources.getDrawable(this@MainActivity, R.drawable.play) - } - } - } else if (event is Event.QueueChanged) { - findViewById<View>(R.id.nav_queue)?.let { view -> - ObjectAnimator.ofFloat(view, View.ROTATION, 0f, 360f).let { - it.duration = 500 - it.interpolator = AccelerateDecelerateInterpolator() - it.start() - } } + else -> {} } } } @@ -402,24 +307,6 @@ class MainActivity : AppCompatActivity() { } } } - - lifecycleScope.launch(Main) { - ProgressBus.get().collect { (current, duration, percent) -> - binding.nowPlayingContainer?.nowPlayingProgress?.progress = percent - binding.nowPlayingContainer?.nowPlayingDetailsProgress?.progress = percent - - val currentMins = (current / 1000) / 60 - val currentSecs = (current / 1000) % 60 - - val durationMins = duration / 60 - val durationSecs = duration % 60 - - binding.nowPlayingContainer?.nowPlayingDetailsProgressCurrent?.text = - "%02d:%02d".format(currentMins, currentSecs) - binding.nowPlayingContainer?.nowPlayingDetailsProgressDuration?.text = - "%02d:%02d".format(durationMins, durationSecs) - } - } } private fun refreshCurrentTrack(track: Track?) { @@ -444,175 +331,6 @@ class MainActivity : AppCompatActivity() { } } } - - binding.nowPlayingContainer?.nowPlayingTitle?.text = track.title - binding.nowPlayingContainer?.nowPlayingAlbum?.text = track.artist.name - - binding.nowPlayingContainer?.nowPlayingDetailsTitle?.text = track.title - binding.nowPlayingContainer?.nowPlayingDetailsArtist?.text = track.artist.name - - val lic = this.layoutInflater.context - - CoverArt.withContext(lic, maybeNormalizeUrl(track.cover())) - .fit() - .centerCrop() - .into(binding.nowPlayingContainer?.nowPlayingCover) - - binding.nowPlayingContainer?.nowPlayingDetailsCover?.let { nowPlayingDetailsCover -> - CoverArt.withContext(lic, maybeNormalizeUrl(track.cover())) - .fit() - .centerCrop() - .transform(RoundedCornersTransformation(16, 0)) - .into(nowPlayingDetailsCover) - } - - if (binding.nowPlayingContainer?.nowPlayingCover == null) { - lifecycleScope.launch(Default) { - val width = DisplayMetrics().apply { - windowManager.defaultDisplay.getMetrics(this) - }.widthPixels - - val backgroundCover = CoverArt.withContext(lic, maybeNormalizeUrl(track.cover())) - .get() - .run { Bitmap.createScaledBitmap(this, width, width, false).toDrawable(resources) } - .apply { - alpha = 20 - gravity = Gravity.CENTER - } - - withContext(Main) { - binding.nowPlayingContainer?.nowPlayingDetails?.background = backgroundCover - } - } - } - - binding.nowPlayingContainer?.nowPlayingDetailsRepeat?.let { now_playing_details_repeat -> - changeRepeatMode(FFACache.getLine(this@MainActivity, "repeat")?.toInt() ?: 0) - - now_playing_details_repeat.setOnClickListener { - val current = FFACache.getLine(this@MainActivity, "repeat")?.toInt() ?: 0 - - changeRepeatMode((current + 1) % 3) - } - } - - binding.nowPlayingContainer?.nowPlayingDetailsInfo?.let { nowPlayingDetailsInfo -> - nowPlayingDetailsInfo.setOnClickListener { - PopupMenu( - this@MainActivity, - nowPlayingDetailsInfo, - Gravity.START, - R.attr.actionOverflowMenuStyle, - 0 - ).apply { - inflate(R.menu.track_info) - - setOnMenuItemClickListener { - when (it.itemId) { - R.id.track_info_artist -> BrowseFragmentDirections.browseToAlbums( - track.artist, - track.album?.cover() - ) - R.id.track_info_album -> track.album?.let(BrowseFragmentDirections::browseToTracks) - R.id.track_info_details -> TrackInfoDetailsFragment.new(track) - .show(supportFragmentManager, "dialog") - } - - binding.nowPlaying.close() - - true - } - - show() - } - } - } - - binding.nowPlayingContainer?.nowPlayingDetailsFavorite?.let { now_playing_details_favorite -> - favoritedRepository.fetch().untilNetwork(lifecycleScope, IO) { favorites, _, _, _ -> - lifecycleScope.launch(Main) { - track.favorite = favorites.contains(track.id) - - when (track.favorite) { - true -> now_playing_details_favorite.setColorFilter(getColor(R.color.colorFavorite)) - false -> now_playing_details_favorite.setColorFilter(getColor(R.color.controlForeground)) - } - } - } - - now_playing_details_favorite.setOnClickListener { - when (track.favorite) { - true -> { - favoriteRepository.deleteFavorite(track.id) - now_playing_details_favorite.setColorFilter(getColor(R.color.controlForeground)) - } - - false -> { - favoriteRepository.addFavorite(track.id) - now_playing_details_favorite.setColorFilter(getColor(R.color.colorFavorite)) - } - } - - track.favorite = !track.favorite - - favoriteRepository.fetch(Repository.Origin.Network.origin) - } - - binding.nowPlayingContainer?.nowPlayingDetailsAddToPlaylist?.setOnClickListener { - CommandBus.send(Command.AddToPlaylist(listOf(track))) - } - } - } - } - - private fun changeRepeatMode(index: Int) { - when (index) { - // From no repeat to repeat all - 0 -> { - FFACache.set(this@MainActivity, "repeat", "0") - - binding.nowPlayingContainer?.nowPlayingDetailsRepeat?.setImageResource(R.drawable.repeat) - binding.nowPlayingContainer?.nowPlayingDetailsRepeat?.setColorFilter( - ContextCompat.getColor( - this, - R.color.controlForeground - ) - ) - binding.nowPlayingContainer?.nowPlayingDetailsRepeat?.alpha = 0.2f - - CommandBus.send(Command.SetRepeatMode(Player.REPEAT_MODE_OFF)) - } - - // From repeat all to repeat one - 1 -> { - FFACache.set(this@MainActivity, "repeat", "1") - - binding.nowPlayingContainer?.nowPlayingDetailsRepeat?.setImageResource(R.drawable.repeat) - binding.nowPlayingContainer?.nowPlayingDetailsRepeat?.setColorFilter( - ContextCompat.getColor( - this, - R.color.controlForeground - ) - ) - binding.nowPlayingContainer?.nowPlayingDetailsRepeat?.alpha = 1.0f - - CommandBus.send(Command.SetRepeatMode(Player.REPEAT_MODE_ALL)) - } - - // From repeat one to no repeat - 2 -> { - FFACache.set(this@MainActivity, "repeat", "2") - binding.nowPlayingContainer?.nowPlayingDetailsRepeat?.setImageResource(R.drawable.repeat_one) - binding.nowPlayingContainer?.nowPlayingDetailsRepeat?.setColorFilter( - ContextCompat.getColor( - this, - R.color.controlForeground - ) - ) - binding.nowPlayingContainer?.nowPlayingDetailsRepeat?.alpha = 1.0f - - CommandBus.send(Command.SetRepeatMode(Player.REPEAT_MODE_ONE)) - } } } @@ -633,4 +351,15 @@ class MainActivity : AppCompatActivity() { } } } + + private fun logout() { + FFA.get().deleteAllData(this@MainActivity) + startActivity( + Intent(this@MainActivity, LoginActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NO_HISTORY + } + ) + + finish() + } } diff --git a/app/src/main/java/audio/funkwhale/ffa/adapters/AlbumsGridAdapter.kt b/app/src/main/java/audio/funkwhale/ffa/adapters/AlbumsGridAdapter.kt index 2e001745c9608f1f63d33061168c5b0176d43f6f..e3766172dffdaed430c46317810d6c31cb4bf7cf 100644 --- a/app/src/main/java/audio/funkwhale/ffa/adapters/AlbumsGridAdapter.kt +++ b/app/src/main/java/audio/funkwhale/ffa/adapters/AlbumsGridAdapter.kt @@ -41,7 +41,6 @@ class AlbumsGridAdapter( CoverArt.withContext(layoutInflater.context, maybeNormalizeUrl(album.cover())) .fit() - .placeholder(R.drawable.cover) .transform(RoundedCornersTransformation(16, 0)) .into(holder.cover) diff --git a/app/src/main/java/audio/funkwhale/ffa/adapters/BrowseTabsAdapter.kt b/app/src/main/java/audio/funkwhale/ffa/adapters/BrowseTabsAdapter.kt index b8cb5ba97e8a4e7dc3c432f091f62aa799e2db20..65caed3588f5e0e61460da81ec89419712ba6665 100644 --- a/app/src/main/java/audio/funkwhale/ffa/adapters/BrowseTabsAdapter.kt +++ b/app/src/main/java/audio/funkwhale/ffa/adapters/BrowseTabsAdapter.kt @@ -9,29 +9,16 @@ import audio.funkwhale.ffa.fragments.FavoritesFragment import audio.funkwhale.ffa.fragments.PlaylistsFragment import audio.funkwhale.ffa.fragments.RadiosFragment -class BrowseTabsAdapter(val context: Fragment) : - FragmentStateAdapter(context) { - var tabs = mutableListOf<Fragment>() - +class BrowseTabsAdapter(val context: Fragment) : FragmentStateAdapter(context) { override fun getItemCount() = 5 - override fun createFragment(position: Int): Fragment { - tabs.getOrNull(position)?.let { - return it - } - - val fragment = when (position) { - 0 -> ArtistsFragment() - 1 -> AlbumsGridFragment() - 2 -> PlaylistsFragment() - 3 -> RadiosFragment() - 4 -> FavoritesFragment() - else -> ArtistsFragment() - } - - tabs.add(position, fragment) - - return fragment + override fun createFragment(position: Int): Fragment = when (position) { + 0 -> ArtistsFragment() + 1 -> AlbumsGridFragment() + 2 -> PlaylistsFragment() + 3 -> RadiosFragment() + 4 -> FavoritesFragment() + else -> ArtistsFragment() } fun tabText(position: Int): String { diff --git a/app/src/main/java/audio/funkwhale/ffa/fragments/NowPlayingFragment.kt b/app/src/main/java/audio/funkwhale/ffa/fragments/NowPlayingFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..539d6526bcfa9aa3862850fffc3bfc61e9457e2f --- /dev/null +++ b/app/src/main/java/audio/funkwhale/ffa/fragments/NowPlayingFragment.kt @@ -0,0 +1,250 @@ +package audio.funkwhale.ffa.fragments + +import android.os.Bundle +import android.view.Gravity +import android.view.View +import android.widget.Button +import android.widget.SeekBar +import android.widget.SeekBar.OnSeekBarChangeListener +import androidx.appcompat.widget.AppCompatImageView +import androidx.appcompat.widget.PopupMenu +import androidx.customview.widget.Openable +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.distinctUntilChanged +import androidx.lifecycle.lifecycleScope +import androidx.navigation.fragment.findNavController +import audio.funkwhale.ffa.MainNavDirections +import audio.funkwhale.ffa.R +import audio.funkwhale.ffa.databinding.FragmentNowPlayingBinding +import audio.funkwhale.ffa.model.Track +import audio.funkwhale.ffa.repositories.FavoritedRepository +import audio.funkwhale.ffa.repositories.FavoritesRepository +import audio.funkwhale.ffa.repositories.Repository +import audio.funkwhale.ffa.utils.* +import audio.funkwhale.ffa.viewmodel.NowPlayingViewModel +import audio.funkwhale.ffa.views.NowPlayingBottomSheet +import jp.wasabeef.picasso.transformations.RoundedCornersTransformation +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class NowPlayingFragment : Fragment(R.layout.fragment_now_playing) { + private val binding by lazy { FragmentNowPlayingBinding.bind(requireView()) } + private val viewModel by viewModels<NowPlayingViewModel>() + private val favoriteRepository by lazy { FavoritesRepository(requireContext()) } + private val favoritedRepository by lazy { FavoritedRepository(requireContext()) } + + private val bottomSheet: BottomSheetIneractable? by lazy { + var view = this.view?.parent + while (view != null) { + if(view is BottomSheetIneractable) return@lazy view + view = view.parent + } + null + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + binding.lifecycleOwner = viewLifecycleOwner + + viewModel.currentTrack.distinctUntilChanged().observe(viewLifecycleOwner, ::onTrackChange) + + with(binding.controls) { + currentTrackTitle = viewModel.currentTrackTitle + currentTrackArtist = viewModel.currentTrackArtist + isCurrentTrackFavorite = viewModel.isCurrentTrackFavorite + repeatModeResource = viewModel.repeatModeResource + repeatModeAlpha = viewModel.repeatModeAlpha + currentProgressText = viewModel.currentProgressText + currentDurationText = viewModel.currentDurationText + isPlaying = viewModel.isPlaying + progress = viewModel.progress + + nowPlayingDetailsPrevious.setOnClickListener { + CommandBus.send(Command.PreviousTrack) + } + + nowPlayingDetailsNext.setOnClickListener { + CommandBus.send(Command.NextTrack) + } + + nowPlayingDetailsToggle.setOnClickListener { + CommandBus.send(Command.ToggleState) + } + + nowPlayingDetailsRepeat.setOnClickListener { toggleRepeatMode() } + nowPlayingDetailsProgress.setOnSeekBarChangeListener(OnSeekBarChanged()) + nowPlayingDetailsFavorite.setOnClickListener { onFavorite() } + nowPlayingDetailsAddToPlaylist.setOnClickListener { onAddToPlaylist() } + } + + with(binding.header) { + isBuffering = viewModel.isBuffering + isPlaying = viewModel.isPlaying + progress = viewModel.progress + currentTrackTitle = viewModel.currentTrackTitle + currentTrackArtist = viewModel.currentTrackArtist + + + nowPlayingNext.setOnClickListener { + CommandBus.send(Command.NextTrack) + } + + nowPlayingToggle.setOnClickListener { + CommandBus.send(Command.ToggleState) + } + } + + binding.nowPlayingDetailsInfo.setOnClickListener { openInfoMenu() } + + lifecycleScope.launch(Dispatchers.Main) { + CommandBus.get().collect { onCommand(it) } + } + + lifecycleScope.launch(Dispatchers.Main) { + EventBus.get().collect { onEvent(it) } + } + + lifecycleScope.launch(Dispatchers.Main) { + ProgressBus.get().collect { onProgress(it) } + } + } + + + private fun toggleRepeatMode() { + val cachedRepeatMode = FFACache.getLine(requireContext(), "repeat").toIntOrElse(0) + val iteratedRepeatMode = (cachedRepeatMode + 1) % 3 + FFACache.set(requireContext(), "repeat", "$iteratedRepeatMode") + CommandBus.send(Command.SetRepeatMode(iteratedRepeatMode)) + } + + private fun onAddToPlaylist() { + val currentTrack = viewModel.currentTrack.value ?: return + CommandBus.send(Command.AddToPlaylist(listOf(currentTrack))) + } + + private fun onCommand(command: Command) = when (command) { + is Command.RefreshTrack -> refreshCurrentTrack(command.track) + is Command.SetRepeatMode -> viewModel.repeatMode.postValue(command.mode) + else -> {} + } + + private fun onEvent(event: Event): Unit = when (event) { + is Event.Buffering -> viewModel.isBuffering.postValue(event.value) + is Event.StateChanged -> viewModel.isPlaying.postValue(event.playing) + else -> {} + } + + private fun onFavorite() { + val currentTrack = viewModel.currentTrack.value ?: return + + if (currentTrack.favorite) favoriteRepository.deleteFavorite(currentTrack.id) + else favoriteRepository.addFavorite(currentTrack.id) + + currentTrack.favorite = !currentTrack.favorite + // Trigger UI refresh + viewModel.currentTrack.postValue(viewModel.currentTrack.value) + + favoritedRepository.fetch(Repository.Origin.Network.origin) + } + + private fun onProgress(state: Triple<Int, Int, Int>) { + val (current, duration, percent) = state + + val currentMins = (current / 1000) / 60 + val currentSecs = (current / 1000) % 60 + + val durationMins = duration / 60 + val durationSecs = duration % 60 + + viewModel.progress.postValue(percent) + viewModel.currentProgressText.postValue("%02d:%02d".format(currentMins, currentSecs)) + viewModel.currentDurationText.postValue("%02d:%02d".format(durationMins, durationSecs)) + } + + private fun onTrackChange(track: Track?) { + if (track == null) { + binding.header.nowPlayingCover.setImageResource(R.drawable.cover) + binding.nowPlayingDetailCover.setImageResource(R.drawable.cover) + return + } + + CoverArt.withContext(requireContext(), maybeNormalizeUrl(track.album?.cover())) + .fit() + .centerCrop() + .into(binding.nowPlayingDetailCover) + + CoverArt.withContext(requireContext(), maybeNormalizeUrl(track.album?.cover())) + .fit() + .centerCrop() + .transform(RoundedCornersTransformation(16, 0)) + .into(binding.header.nowPlayingCover) + } + + private fun openInfoMenu() { + val currentTrack = viewModel.currentTrack.value ?: return + + PopupMenu( + requireContext(), + binding.nowPlayingDetailsInfo, + Gravity.START, + R.attr.actionOverflowMenuStyle, + 0 + ).apply { + inflate(R.menu.track_info) + + setOnMenuItemClickListener { + bottomSheet?.close() + + when (it.itemId) { + R.id.track_info_artist -> findNavController().navigate( + MainNavDirections.globalBrowseToAlbums( + currentTrack.artist, + currentTrack.album?.cover() + ) + ) + R.id.track_info_album -> currentTrack.album?.let { album -> + findNavController().navigate(MainNavDirections.globalBrowseTracks(album)) + } + R.id.track_info_details -> TrackInfoDetailsFragment.new(currentTrack).show( + requireActivity().supportFragmentManager, "dialog" + ) + } + + true + } + + show() + } + } + + private fun refreshCurrentTrack(track: Track?) { + viewModel.currentTrack.postValue(track) + + val cachedRepeatMode = FFACache.getLine(requireContext(), "repeat").toIntOrElse(0) + viewModel.repeatMode.postValue(cachedRepeatMode % 3) + + // At this point, a non-null track is required + + if (track == null) return + + favoritedRepository.fetch().untilNetwork(lifecycleScope, Dispatchers.IO) { favorites, _, _, _ -> + lifecycleScope.launch(Dispatchers.Main) { + track.favorite = favorites.contains(track.id) + // Trigger UI refresh + viewModel.currentTrack.postValue(viewModel.currentTrack.value) + } + } + } + + inner class OnSeekBarChanged : OnSeekBarChangeListener { + override fun onStopTrackingTouch(view: SeekBar?) {} + + override fun onStartTrackingTouch(view: SeekBar?) {} + + override fun onProgressChanged(view: SeekBar?, progress: Int, fromUser: Boolean) { + if (fromUser) { + CommandBus.send(Command.Seek(progress)) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/audio/funkwhale/ffa/utils/BottomSheetIneractable.kt b/app/src/main/java/audio/funkwhale/ffa/utils/BottomSheetIneractable.kt new file mode 100644 index 0000000000000000000000000000000000000000..f8645914b494f1af3778f57d71fc73c89af5614b --- /dev/null +++ b/app/src/main/java/audio/funkwhale/ffa/utils/BottomSheetIneractable.kt @@ -0,0 +1,10 @@ +package audio.funkwhale.ffa.utils + +import androidx.customview.widget.Openable + +interface BottomSheetIneractable: Openable { + val isHidden: Boolean + fun show() + fun hide() + fun toggle() +} \ No newline at end of file diff --git a/app/src/main/java/audio/funkwhale/ffa/utils/CoverArt.kt b/app/src/main/java/audio/funkwhale/ffa/utils/CoverArt.kt index 3cd73d932995128a2b121e9302d1ccb324825309..85b137f60fb5ab84f49da744ad5a5a93febdd002 100644 --- a/app/src/main/java/audio/funkwhale/ffa/utils/CoverArt.kt +++ b/app/src/main/java/audio/funkwhale/ffa/utils/CoverArt.kt @@ -2,7 +2,9 @@ package audio.funkwhale.ffa.utils import android.content.Context import android.net.Uri +import android.transition.CircularPropagation import android.util.Log +import androidx.swiperefreshlayout.widget.CircularProgressDrawable import audio.funkwhale.ffa.BuildConfig import audio.funkwhale.ffa.R import com.squareup.picasso.Downloader @@ -252,9 +254,10 @@ open class CoverArt private constructor() { * The primary entrypoint for the codebase. */ fun withContext(context: Context, url: String?): RequestCreator { - return buildPicasso(context) - .load(url) - .placeholder(R.drawable.cover) + val request = buildPicasso(context).load(url) + if(url == null) request.placeholder(R.drawable.cover) + else request.placeholder(CircularProgressDrawable(context)) + return request.error(R.drawable.cover) } } } diff --git a/app/src/main/java/audio/funkwhale/ffa/utils/Extensions.kt b/app/src/main/java/audio/funkwhale/ffa/utils/Extensions.kt index 9a81a6d062902636b9f7d62a93406abf38dfcf08..2e5371203bbfc06b43474cf75291219c0ed44377 100644 --- a/app/src/main/java/audio/funkwhale/ffa/utils/Extensions.kt +++ b/app/src/main/java/audio/funkwhale/ffa/utils/Extensions.kt @@ -149,3 +149,5 @@ inline fun <T, U, V, W, R> LiveData<T>.mergeWith( } } } + +public fun String?.toIntOrElse(default: Int): Int = this?.toIntOrNull(radix = 10) ?: default \ No newline at end of file diff --git a/app/src/main/java/audio/funkwhale/ffa/utils/ViewBindings.kt b/app/src/main/java/audio/funkwhale/ffa/utils/ViewBindings.kt new file mode 100644 index 0000000000000000000000000000000000000000..6a12db73f42ba9d37a42d433818a3b2318997614 --- /dev/null +++ b/app/src/main/java/audio/funkwhale/ffa/utils/ViewBindings.kt @@ -0,0 +1,25 @@ +package audio.funkwhale.ffa.utils + +import android.graphics.Bitmap +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.Drawable +import android.util.Log +import android.widget.ImageButton +import androidx.annotation.ColorRes +import androidx.appcompat.widget.AppCompatImageView +import androidx.databinding.BindingAdapter + + +@BindingAdapter("srcCompat") +fun setImageViewResource(imageView: AppCompatImageView, resource: Any?) = when (resource) { + is Bitmap -> imageView.setImageBitmap(resource) + is Int -> imageView.setImageResource(resource) + is Drawable -> imageView.setImageDrawable(resource) + else -> imageView.setImageDrawable(ColorDrawable(Color.TRANSPARENT)) +} + +@BindingAdapter("tint") +fun setTint(imageView: ImageButton, @ColorRes resource: Int) = resource.let { + imageView.setColorFilter(resource) +} \ No newline at end of file diff --git a/app/src/main/java/audio/funkwhale/ffa/viewmodel/NowPlayingViewModel.kt b/app/src/main/java/audio/funkwhale/ffa/viewmodel/NowPlayingViewModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..4a972aee179d53d6ea6888223f79e143a4c6a4b9 --- /dev/null +++ b/app/src/main/java/audio/funkwhale/ffa/viewmodel/NowPlayingViewModel.kt @@ -0,0 +1,56 @@ +package audio.funkwhale.ffa.viewmodel + +import android.app.Application +import android.content.Context +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import androidx.appcompat.content.res.AppCompatResources +import androidx.core.graphics.drawable.toDrawable +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.distinctUntilChanged +import androidx.lifecycle.map +import audio.funkwhale.ffa.FFA +import audio.funkwhale.ffa.R +import audio.funkwhale.ffa.model.Track +import audio.funkwhale.ffa.utils.CoverArt +import audio.funkwhale.ffa.utils.maybeNormalizeUrl +import com.google.android.exoplayer2.Player +import com.squareup.picasso.Picasso +import com.squareup.picasso.Target + +class NowPlayingViewModel(app: Application) : AndroidViewModel(app) { + val isBuffering = MutableLiveData(false) + val isPlaying = MutableLiveData(false) + val repeatMode = MutableLiveData(0) + val progress = MutableLiveData(0) + val currentTrack = MutableLiveData<Track?>(null) + val currentProgressText = MutableLiveData("") + val currentDurationText = MutableLiveData("") + + // Calling distinctUntilChanged() prevents triggering an event when the track hasn't changed + val currentTrackTitle = currentTrack.distinctUntilChanged().map { it?.title ?: "" } + val currentTrackArtist = currentTrack.distinctUntilChanged().map { it?.artist?.name ?: "" } + // Not calling distinctUntilChanged() here as we need to process every event + val isCurrentTrackFavorite = currentTrack.map { + it?.favorite ?: false + } + + val repeatModeResource = repeatMode.distinctUntilChanged().map { + when (it) { + Player.REPEAT_MODE_ONE -> AppCompatResources.getDrawable(context, R.drawable.repeat_one) + else -> AppCompatResources.getDrawable(context, R.drawable.repeat) + } + } + + val repeatModeAlpha = repeatMode.distinctUntilChanged().map { + when (it) { + Player.REPEAT_MODE_OFF -> 0.2f + else -> 1f + } + } + + private val context: Context + get() = getApplication<FFA>().applicationContext +} diff --git a/app/src/main/java/audio/funkwhale/ffa/views/NowPlayingBottomSheet.kt b/app/src/main/java/audio/funkwhale/ffa/views/NowPlayingBottomSheet.kt new file mode 100644 index 0000000000000000000000000000000000000000..b0e079fb14e410706d784ec9e286026e8c9d8aa9 --- /dev/null +++ b/app/src/main/java/audio/funkwhale/ffa/views/NowPlayingBottomSheet.kt @@ -0,0 +1,67 @@ +package audio.funkwhale.ffa.views + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import android.view.ViewGroup +import androidx.coordinatorlayout.widget.CoordinatorLayout +import audio.funkwhale.ffa.R +import audio.funkwhale.ffa.utils.BottomSheetIneractable +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.card.MaterialCardView + +class NowPlayingBottomSheet @JvmOverloads constructor ( + context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 +) : MaterialCardView(context, attrs), BottomSheetIneractable { + val behavior = BottomSheetBehavior<NowPlayingBottomSheet>(context, attrs) + + private val targetHeaderId: Int + + + init { + context.theme.obtainStyledAttributes(attrs, R.styleable.NowPlaying, defStyleAttr, 0).use { + targetHeaderId = it.getResourceId(R.styleable.NowPlaying_target_header, NO_ID) + } + } + + override fun setLayoutParams(params: ViewGroup.LayoutParams?) { + super.setLayoutParams(params) + (params as CoordinatorLayout.LayoutParams).behavior = behavior + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + findViewById<View>(targetHeaderId)?.apply { + behavior.setPeekHeight(this.measuredHeight, false) + this.setOnClickListener { this@NowPlayingBottomSheet.toggle() } + } ?: hide() + } + + // Bottom sheet interactions + override val isHidden: Boolean get() = behavior.state == BottomSheetBehavior.STATE_HIDDEN + + override fun isOpen(): Boolean = behavior.state == BottomSheetBehavior.STATE_EXPANDED + + override fun open() { + behavior.state = BottomSheetBehavior.STATE_EXPANDED + } + + override fun close() { + behavior.state = BottomSheetBehavior.STATE_COLLAPSED + } + + override fun show() { + behavior.isHideable = false + close() + } + + override fun hide() { + behavior.isHideable = true + behavior.state = BottomSheetBehavior.STATE_HIDDEN + } + + override fun toggle() { + if (isHidden) return + if (isOpen) close() else open() + } +} diff --git a/app/src/main/java/audio/funkwhale/ffa/views/NowPlayingView.kt b/app/src/main/java/audio/funkwhale/ffa/views/NowPlayingView.kt deleted file mode 100644 index 7ad73746cc1bf461c4776b206cebdac4283b8b64..0000000000000000000000000000000000000000 --- a/app/src/main/java/audio/funkwhale/ffa/views/NowPlayingView.kt +++ /dev/null @@ -1,255 +0,0 @@ -package audio.funkwhale.ffa.views - -import android.animation.ValueAnimator -import android.content.Context -import android.util.AttributeSet -import android.util.TypedValue -import android.view.GestureDetector -import android.view.LayoutInflater -import android.view.MotionEvent -import android.view.View -import android.view.ViewTreeObserver -import android.view.animation.DecelerateInterpolator -import audio.funkwhale.ffa.R -import audio.funkwhale.ffa.databinding.PartialNowPlayingBinding -import com.google.android.material.card.MaterialCardView -import kotlin.math.abs -import kotlin.math.min - -class NowPlayingView : MaterialCardView { - val activity: Context - var gestureDetector: GestureDetector? = null - var gestureDetectorCallback: OnGestureDetection? = null - - private val binding = - PartialNowPlayingBinding.inflate(LayoutInflater.from(context), this, true) - - constructor(context: Context) : super(context) { - activity = context - } - - constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { - activity = context - } - - constructor(context: Context, attrs: AttributeSet?, style: Int) : super(context, attrs, style) { - activity = context - } - - override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec) - - binding.nowPlayingRoot.measure( - widthMeasureSpec, - MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.UNSPECIFIED) - ) - } - - override fun onVisibilityChanged(changedView: View, visibility: Int) { - super.onVisibilityChanged(changedView, visibility) - - if (visibility == View.VISIBLE && gestureDetector == null) { - viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { - override fun onGlobalLayout() { - gestureDetectorCallback = OnGestureDetection() - gestureDetector = GestureDetector(context, gestureDetectorCallback!!) - - setOnTouchListener { _, motionEvent -> - val ret = gestureDetector?.onTouchEvent(motionEvent) ?: false - - if (motionEvent.actionMasked == MotionEvent.ACTION_UP) { - if (gestureDetectorCallback?.isScrolling == true) { - gestureDetectorCallback?.onUp() - } - } - performClick() - ret - } - - viewTreeObserver.removeOnGlobalLayoutListener(this) - } - }) - } - } - - fun isOpened(): Boolean = gestureDetectorCallback?.isOpened() ?: false - - fun close() { - gestureDetectorCallback?.close() - } - - inner class OnGestureDetection : GestureDetector.SimpleOnGestureListener() { - private var maxHeight = 0 - private var minHeight = 0 - private var maxMargin = 0 - - private var initialTouchY = 0f - private var lastTouchY = 0f - - var isScrolling = false - private var flingAnimator: ValueAnimator? = null - - init { - (layoutParams as? MarginLayoutParams)?.let { - maxMargin = it.marginStart - } - - minHeight = TypedValue().let { - activity.theme.resolveAttribute(R.attr.actionBarSize, it, true) - - TypedValue.complexToDimensionPixelSize(it.data, resources.displayMetrics) - } - - maxHeight = binding.nowPlayingDetails.measuredHeight + (2 * maxMargin) - } - - override fun onDown(e: MotionEvent): Boolean { - initialTouchY = e.rawY - lastTouchY = e.rawY - - return true - } - - fun onUp(): Boolean { - isScrolling = false - - layoutParams.let { - val offsetToMax = maxHeight - height - val offsetToMin = height - minHeight - - flingAnimator = - if (offsetToMin < offsetToMax) ValueAnimator.ofInt(it.height, minHeight) - else ValueAnimator.ofInt(it.height, maxHeight) - - animateFling(500) - - return true - } - } - - override fun onFling( - firstMotionEvent: MotionEvent, - secondMotionEvent: MotionEvent, - velocityX: Float, - velocityY: Float - ): Boolean { - isScrolling = false - - layoutParams.let { - val diff = - if (velocityY < 0) maxHeight - it.height - else it.height - minHeight - - flingAnimator = - if (velocityY < 0) ValueAnimator.ofInt(it.height, maxHeight) - else ValueAnimator.ofInt(it.height, minHeight) - - animateFling(min(abs((diff.toFloat() / velocityY * 1000).toLong()), 600)) - } - - return true - } - - override fun onScroll( - firstMotionEvent: MotionEvent, - secondMotionEvent: MotionEvent, - distanceX: Float, - distanceY: Float - ): Boolean { - isScrolling = true - - layoutParams.let { - val newHeight = it.height + lastTouchY - secondMotionEvent.rawY - val progress = (newHeight - minHeight) / (maxHeight - minHeight) - val newMargin = maxMargin - (maxMargin * progress) - - (layoutParams as? MarginLayoutParams)?.let { params -> - params.marginStart = newMargin.toInt() - params.marginEnd = newMargin.toInt() - params.bottomMargin = newMargin.toInt() - } - - layoutParams = layoutParams.apply { - when { - newHeight <= minHeight -> { - height = minHeight - return true - } - newHeight >= maxHeight -> { - height = maxHeight - return true - } - else -> height = newHeight.toInt() - } - } - - binding.summary.alpha = 1f - progress - - binding.summary.layoutParams = binding.summary.layoutParams.apply { - height = (minHeight * (1f - progress)).toInt() - } - } - - lastTouchY = secondMotionEvent.rawY - - return true - } - - override fun onSingleTapUp(e: MotionEvent): Boolean { - layoutParams.let { - if (height != minHeight) return true - - flingAnimator = ValueAnimator.ofInt(it.height, maxHeight) - - animateFling(300) - } - - return true - } - - fun isOpened(): Boolean = layoutParams.height == maxHeight - - fun close(): Boolean { - layoutParams.let { - if (it.height == minHeight) return true - - flingAnimator = ValueAnimator.ofInt(it.height, minHeight) - - animateFling(300) - } - - return true - } - - private fun animateFling(dur: Long) { - flingAnimator?.apply { - duration = dur - interpolator = DecelerateInterpolator() - - addUpdateListener { valueAnimator -> - layoutParams = layoutParams.apply { - val newHeight = valueAnimator.animatedValue as Int - val progress = (newHeight.toFloat() - minHeight) / (maxHeight - minHeight) - val newMargin = maxMargin - (maxMargin * progress) - - (layoutParams as? MarginLayoutParams)?.let { - it.marginStart = newMargin.toInt() - it.marginEnd = newMargin.toInt() - it.bottomMargin = newMargin.toInt() - } - - height = newHeight - - binding.summary.alpha = 1f - progress - - binding.summary.layoutParams = binding.summary.layoutParams.apply { - height = (minHeight * (1f - progress)).toInt() - } - } - } - - start() - } - } - } -} diff --git a/app/src/main/java/audio/funkwhale/ffa/views/SquareImageView.kt b/app/src/main/java/audio/funkwhale/ffa/views/SquareImageView.kt index 586131ab689822d55bc2d418cda6c4325dc144e1..e148e105a7ded4cdbbb092087256307eed9e9dd6 100644 --- a/app/src/main/java/audio/funkwhale/ffa/views/SquareImageView.kt +++ b/app/src/main/java/audio/funkwhale/ffa/views/SquareImageView.kt @@ -4,7 +4,7 @@ import android.content.Context import android.util.AttributeSet import androidx.appcompat.widget.AppCompatImageView -class SquareImageView : AppCompatImageView { +open class SquareImageView : AppCompatImageView { constructor(context: Context) : super(context) constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) constructor(context: Context, attrs: AttributeSet?, style: Int) : super(context, attrs, style) @@ -12,6 +12,8 @@ class SquareImageView : AppCompatImageView { override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) - setMeasuredDimension(measuredWidth, measuredWidth) + val dimension = if(measuredWidth == 0 && measuredHeight > 0) measuredHeight else measuredWidth + + setMeasuredDimension(dimension, dimension) } } diff --git a/app/src/main/res/layout-land/activity_main.xml b/app/src/main/res/layout-land/activity_main.xml index 4834d5c5c8804a1ebfe6797f93b5853cae18dbe8..30c86da0500d767c45d897919dbce0d5cc5f8434 100644 --- a/app/src/main/res/layout-land/activity_main.xml +++ b/app/src/main/res/layout-land/activity_main.xml @@ -1,64 +1,82 @@ <?xml version="1.0" encoding="utf-8"?> -<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" +<androidx.constraintlayout.widget.ConstraintLayout + xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> - <LinearLayout + <androidx.coordinatorlayout.widget.CoordinatorLayout android:layout_width="match_parent" - android:layout_height="match_parent" - android:baselineAligned="false" - android:orientation="horizontal"> + android:layout_height="0dp" + android:background="@color/surface" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_weight="10"> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:baselineAligned="false" + android:orientation="horizontal"> - <androidx.fragment.app.FragmentContainerView + <androidx.fragment.app.FragmentContainerView android:id="@+id/nav_host_fragment" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" - android:layout_marginBottom="?attr/actionBarSize" app:defaultNavHost="true" + app:layout_behavior="@string/appbar_scrolling_view_behavior" app:navGraph="@navigation/main_nav" - app:layout_behavior="@string/appbar_scrolling_view_behavior" /> - - <FrameLayout - android:id="@+id/landscape_queue" - android:layout_width="0dp" - android:layout_height="match_parent" - android:layout_marginBottom="?attr/actionBarSize" - android:layout_weight="1" - app:layout_behavior="@string/appbar_scrolling_view_behavior" /> + tools:layout="@layout/fragment_artists" /> - </LinearLayout> + <androidx.fragment.app.FragmentContainerView + android:id="@+id/landscape_queue" + android:name="audio.funkwhale.ffa.fragments.LandscapeQueueFragment" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + app:layout_behavior="@string/appbar_scrolling_view_behavior" + tools:layout="@layout/partial_queue" /> + </LinearLayout> + </androidx.coordinatorlayout.widget.CoordinatorLayout> - <audio.funkwhale.ffa.views.NowPlayingView - android:id="@+id/now_playing" + <androidx.coordinatorlayout.widget.CoordinatorLayout android:layout_width="match_parent" - android:layout_height="?attr/actionBarSize" - android:layout_gravity="bottom" - android:layout_margin="8dp" - android:alpha="0" - android:visibility="gone" - app:cardCornerRadius="8dp" - app:cardElevation="12dp" - app:layout_dodgeInsetEdges="bottom" - tools:alpha="1" - tools:visibility="visible"> - - <include layout="@layout/partial_now_playing" /> - - </audio.funkwhale.ffa.views.NowPlayingView> + android:layout_height="0dp" + app:layout_constraintBottom_toTopOf="@id/appbar_wrapper" + app:layout_constraintTop_toTopOf="parent"> + <audio.funkwhale.ffa.views.NowPlayingBottomSheet + android:id="@+id/now_playing_bottom_sheet" + style="?attr/bottomSheetStyle" + android:layout_width="match_parent" + android:layout_height="match_parent" + app:cardCornerRadius="3dp" + app:cardElevation="12dp" + app:target_header="@id/header"> + <androidx.fragment.app.FragmentContainerView + android:id="@+id/now_playing" + android:name="audio.funkwhale.ffa.fragments.NowPlayingFragment" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:layout="@layout/fragment_now_playing" /> + </audio.funkwhale.ffa.views.NowPlayingBottomSheet> + </androidx.coordinatorlayout.widget.CoordinatorLayout> - <com.google.android.material.bottomappbar.BottomAppBar - android:id="@+id/appbar" + <androidx.coordinatorlayout.widget.CoordinatorLayout + android:id="@+id/appbar_wrapper" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_gravity="bottom" - android:theme="@style/AppTheme.AppBar" - app:backgroundTint="@color/colorPrimaryDark" - app:layout_insetEdge="bottom" - app:navigationIcon="@drawable/funkwhaleshape" - tools:menu="@menu/toolbar" /> + app:layout_constraintBottom_toBottomOf="parent"> -</androidx.coordinatorlayout.widget.CoordinatorLayout> + <com.google.android.material.bottomappbar.BottomAppBar + android:id="@+id/appbar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="bottom" + android:theme="@style/AppTheme.AppBar" + app:backgroundTint="@color/elevatedSurface" + app:layout_insetEdge="bottom" + app:navigationIcon="@drawable/funkwhaleshape" + tools:menu="@menu/toolbar" /> + </androidx.coordinatorlayout.widget.CoordinatorLayout> +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/app/src/main/res/layout-land/fragment_now_playing.xml b/app/src/main/res/layout-land/fragment_now_playing.xml new file mode 100644 index 0000000000000000000000000000000000000000..4480aaf4a9d70972ac4dff32b225e7d987639b9a --- /dev/null +++ b/app/src/main/res/layout-land/fragment_now_playing.xml @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="utf-8"?> +<layout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/now_playing_root" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/elevatedSurface"> + + <include + android:id="@+id/header" + layout="@layout/partial_now_playing_header" /> + + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/cover_container" + android:layout_width="wrap_content" + android:layout_height="0dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/header"> + + <androidx.appcompat.widget.AppCompatImageView + android:id="@+id/now_playing_detail_cover" + android:layout_width="0dp" + android:layout_height="match_parent" + android:scaleType="fitCenter" + app:layout_constraintDimensionRatio="H,1:1" + app:layout_constraintStart_toStartOf="parent" + app:srcCompat="@drawable/cover" + tools:src="@tools:sample/avatars" /> + + <ImageButton + android:id="@+id/now_playing_details_info" + style="@style/IconButton" + android:layout_width="32dp" + android:layout_height="32dp" + android:layout_gravity="top|end" + android:layout_margin="8dp" + android:background="@drawable/circle" + android:contentDescription="@string/alt_track_info" + android:src="@drawable/more" + app:layout_constraintEnd_toEndOf="@id/now_playing_detail_cover" + app:layout_constraintTop_toTopOf="@id/now_playing_detail_cover" + app:tint="@color/controlForeground" /> + </androidx.constraintlayout.widget.ConstraintLayout> + + + <include + android:id="@+id/controls" + layout="@layout/partial_now_playing_controls" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_marginStart="4dp" + android:layout_marginEnd="4dp" + app:layout_constraintTop_toBottomOf="@id/header" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@id/cover_container" + /> + + </androidx.constraintlayout.widget.ConstraintLayout> +</layout> \ No newline at end of file diff --git a/app/src/main/res/layout-land/partial_now_playing.xml b/app/src/main/res/layout-land/partial_now_playing.xml deleted file mode 100644 index 03e622910e66c82b54eaeb5c5a8bf23bb346be90..0000000000000000000000000000000000000000 --- a/app/src/main/res/layout-land/partial_now_playing.xml +++ /dev/null @@ -1,251 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/tools" - android:id="@+id/now_playing_root" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="@color/elevatedSurface" - android:orientation="vertical"> - - <LinearLayout - android:id="@+id/summary" - android:layout_width="match_parent" - android:layout_height="?attr/actionBarSize" - android:orientation="vertical"> - - <ProgressBar - android:id="@+id/now_playing_progress" - style="@android:style/Widget.Material.ProgressBar.Horizontal" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="-6dp" - android:layout_marginBottom="-6dp" - android:progress="40" - android:progressTint="@color/colorPrimary" /> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:gravity="center_vertical" - android:orientation="horizontal"> - - <FrameLayout - android:layout_width="?attr/actionBarSize" - android:layout_height="?attr/actionBarSize" - android:layout_marginEnd="16dp"> - - <audio.funkwhale.ffa.views.SquareImageView - android:id="@+id/now_playing_cover" - android:layout_width="?attr/actionBarSize" - android:layout_height="?attr/actionBarSize" - tools:src="@tools:sample/avatars" /> - - <ProgressBar - android:id="@+id/now_playing_buffering" - android:layout_width="?attr/actionBarSize" - android:layout_height="?attr/actionBarSize" - android:indeterminate="true" - android:indeterminateTint="@color/controlForeground" - android:visibility="gone" /> - - </FrameLayout> - - <LinearLayout - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_weight="2" - android:orientation="vertical"> - - <TextView - android:id="@+id/now_playing_title" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:textColor="@color/itemTitle" - tools:text="Supermassive Black Hole" /> - - <TextView - android:id="@+id/now_playing_album" - android:layout_width="match_parent" - android:layout_height="wrap_content" - tools:text="Muse" /> - - </LinearLayout> - - <com.google.android.material.button.MaterialButton - android:id="@+id/now_playing_toggle" - style="@style/AppTheme.OutlinedButton" - android:layout_width="?attr/actionBarSize" - android:layout_height="match_parent" - android:layout_marginEnd="16dp" - app:icon="@drawable/play" /> - - <ImageButton - android:id="@+id/now_playing_next" - style="@style/IconButton" - android:layout_width="32dp" - android:layout_height="32dp" - android:layout_marginEnd="16dp" - android:contentDescription="@string/control_next" - android:src="@drawable/next" /> - - </LinearLayout> - - </LinearLayout> - - <LinearLayout - android:id="@+id/now_playing_details" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:paddingStart="32dp" - android:paddingTop="16dp" - android:paddingEnd="32dp"> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal"> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_weight="1" - android:orientation="vertical"> - - <TextView - android:id="@+id/now_playing_details_title" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:textColor="@color/itemTitle" - android:textSize="18sp" - tools:text="Supermassive Black Hole" /> - - <TextView - android:id="@+id/now_playing_details_artist" - android:layout_width="match_parent" - android:layout_height="wrap_content" - tools:text="Muse" /> - - </LinearLayout> - - <ImageButton - android:id="@+id/now_playing_details_add_to_playlist" - style="@style/IconButton" - android:layout_width="24dp" - android:layout_height="24dp" - android:layout_margin="8dp" - android:contentDescription="@string/alt_album_cover" - android:src="@drawable/add_to_playlist" /> - - <ImageButton - android:id="@+id/now_playing_details_favorite" - style="@style/IconButton" - android:layout_width="24dp" - android:layout_height="24dp" - android:layout_margin="8dp" - android:contentDescription="@string/alt_album_cover" - android:src="@drawable/favorite" /> - - <ImageButton - android:id="@+id/now_playing_details_info" - style="@style/IconButton" - android:layout_width="24dp" - android:layout_height="24dp" - android:layout_margin="8dp" - android:contentDescription="@string/alt_track_info" - android:src="@drawable/more" - app:tint="@color/controlForeground" /> - - </LinearLayout> - - <SeekBar - android:id="@+id/now_playing_details_progress" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="16dp" - android:max="100" - android:progressBackgroundTint="#cacaca" - android:progressTint="@color/controlForeground" - android:thumbOffset="3dp" - android:paddingStart="0dp" - android:paddingEnd="0dp" - android:thumbTint="@color/controlForeground" /> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal"> - - <TextView - android:id="@+id/now_playing_details_progress_current" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_weight="1" /> - - <TextView - android:id="@+id/now_playing_details_progress_duration" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_weight="1" - android:textAlignment="textEnd" /> - - </LinearLayout> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="64dp" - android:layout_marginBottom="8dp" - android:gravity="center" - android:orientation="horizontal"> - - <ImageButton - android:id="@+id/now_playing_details_previous" - style="@style/IconButton" - android:layout_width="48dp" - android:layout_height="48dp" - android:layout_marginEnd="16dp" - android:contentDescription="@string/control_previous" - android:src="@drawable/previous" /> - - <com.google.android.material.button.MaterialButton - android:id="@+id/now_playing_details_toggle" - style="@style/AppTheme.OutlinedButton" - android:layout_width="64dp" - android:layout_height="64dp" - app:cornerRadius="64dp" - app:icon="@drawable/play" - app:iconSize="32dp" /> - - <ImageButton - android:id="@+id/now_playing_details_next" - style="@style/IconButton" - android:layout_width="48dp" - android:layout_height="48dp" - android:layout_marginStart="16dp" - android:contentDescription="@string/control_next" - android:src="@drawable/next" /> - - </LinearLayout> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginBottom="16dp" - android:gravity="center" - android:orientation="horizontal"> - - <ImageButton - android:id="@+id/now_playing_details_repeat" - style="@style/IconButton" - android:layout_width="28dp" - android:layout_height="28dp" - android:contentDescription="@string/control_next" - android:src="@drawable/repeat" /> - - </LinearLayout> - - </LinearLayout> - -</LinearLayout> diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 246e22ade95276cadb9d1423ec380f76199d19dc..a732247f2db0ec04b396f041bf4b645df41d7df1 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,50 +1,65 @@ <?xml version="1.0" encoding="utf-8"?> -<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="@color/surface"> +<androidx.constraintlayout.widget.ConstraintLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <androidx.coordinatorlayout.widget.CoordinatorLayout + android:layout_width="match_parent" + android:layout_height="0dp" + android:background="@color/surface" + app:layout_constraintVertical_weight="10" + app:layout_constraintTop_toTopOf="parent"> <androidx.fragment.app.FragmentContainerView - android:id="@+id/nav_host_fragment" - android:name="androidx.navigation.fragment.NavHostFragment" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_marginBottom="?attr/actionBarSize" - app:defaultNavHost="true" - app:navGraph="@navigation/main_nav" - app:layout_behavior="@string/appbar_scrolling_view_behavior" /> + android:id="@+id/nav_host_fragment" + android:name="androidx.navigation.fragment.NavHostFragment" + android:layout_width="match_parent" + android:layout_height="match_parent" + app:defaultNavHost="true" + app:layout_behavior="@string/appbar_scrolling_view_behavior" + app:navGraph="@navigation/main_nav" + tools:layout="@layout/fragment_artists" /> + </androidx.coordinatorlayout.widget.CoordinatorLayout> - <audio.funkwhale.ffa.views.NowPlayingView + <androidx.coordinatorlayout.widget.CoordinatorLayout + android:layout_width="match_parent" + android:layout_height="0dp" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toTopOf="@id/appbar_wrapper"> + <audio.funkwhale.ffa.views.NowPlayingBottomSheet + android:id="@+id/now_playing_bottom_sheet" + style="?attr/bottomSheetStyle" + android:layout_width="match_parent" + android:layout_height="match_parent" + app:cardCornerRadius="3dp" + app:cardElevation="12dp" + app:target_header="@id/header"> + <androidx.fragment.app.FragmentContainerView android:id="@+id/now_playing" android:layout_width="match_parent" - android:layout_height="?attr/actionBarSize" - android:layout_gravity="bottom" - android:layout_margin="8dp" - android:alpha="0" - android:visibility="gone" - app:cardCornerRadius="3dp" - app:cardElevation="12dp" - app:layout_dodgeInsetEdges="bottom" - tools:alpha="1" - tools:visibility="visible"> - - <include - android:id="@+id/now_playing_container" - layout="@layout/partial_now_playing" /> + android:layout_height="match_parent" + android:name="audio.funkwhale.ffa.fragments.NowPlayingFragment" + tools:layout="@layout/fragment_now_playing"/> + </audio.funkwhale.ffa.views.NowPlayingBottomSheet> + </androidx.coordinatorlayout.widget.CoordinatorLayout> - </audio.funkwhale.ffa.views.NowPlayingView> + <androidx.coordinatorlayout.widget.CoordinatorLayout + android:id="@+id/appbar_wrapper" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:layout_constraintBottom_toBottomOf="parent"> <com.google.android.material.bottomappbar.BottomAppBar - android:id="@+id/appbar" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="bottom" - android:theme="@style/AppTheme.AppBar" - app:backgroundTint="@color/elevatedSurface" - app:layout_insetEdge="bottom" - app:navigationIcon="@drawable/funkwhaleshape" - tools:menu="@menu/toolbar" /> - -</androidx.coordinatorlayout.widget.CoordinatorLayout> + android:id="@+id/appbar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="bottom" + android:theme="@style/AppTheme.AppBar" + app:backgroundTint="@color/elevatedSurface" + app:layout_insetEdge="bottom" + app:navigationIcon="@drawable/funkwhaleshape" + tools:menu="@menu/toolbar" /> + </androidx.coordinatorlayout.widget.CoordinatorLayout> +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/app/src/main/res/layout/fragment_now_playing.xml b/app/src/main/res/layout/fragment_now_playing.xml new file mode 100644 index 0000000000000000000000000000000000000000..e22e1d0f45ab54f17b80fd412d5e6c4198fc3083 --- /dev/null +++ b/app/src/main/res/layout/fragment_now_playing.xml @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="utf-8"?> +<layout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/now_playing_root" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/elevatedSurface"> + + <include + android:id="@+id/header" + layout="@layout/partial_now_playing_header" /> + + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/cover_container" + android:layout_width="match_parent" + android:layout_height="0dp" + android:padding="8dp" + app:layout_constraintTop_toBottomOf="@id/header" + app:layout_constraintBottom_toTopOf="@id/controls"> + + <androidx.appcompat.widget.AppCompatImageView + android:id="@+id/now_playing_detail_cover" + android:layout_width="match_parent" + android:layout_height="0dp" + android:scaleType="centerCrop" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:srcCompat="@drawable/cover" + tools:src="@tools:sample/avatars" /> + + <ImageButton + android:id="@+id/now_playing_details_info" + android:layout_width="32dp" + android:layout_height="32dp" + android:layout_margin="8dp" + app:layout_constraintEnd_toEndOf="@id/now_playing_detail_cover" + app:layout_constraintTop_toTopOf="@id/now_playing_detail_cover" + style="@style/IconButton" + android:layout_gravity="top|end" + android:background="@drawable/circle" + android:contentDescription="@string/alt_track_info" + android:src="@drawable/more" + app:tint="@color/controlForeground" /> + </androidx.constraintlayout.widget.ConstraintLayout> + + <include + android:id="@+id/controls" + layout="@layout/partial_now_playing_controls" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="8dp" + app:layout_constraintBottom_toBottomOf="parent" + /> + </androidx.constraintlayout.widget.ConstraintLayout> +</layout> \ No newline at end of file diff --git a/app/src/main/res/layout/partial_now_playing.xml b/app/src/main/res/layout/partial_now_playing.xml deleted file mode 100644 index 560b7983e584a3a774f3ec4eddba4ee988042740..0000000000000000000000000000000000000000 --- a/app/src/main/res/layout/partial_now_playing.xml +++ /dev/null @@ -1,283 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/tools" - android:id="@+id/now_playing_root" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="@color/elevatedSurface" - android:orientation="vertical"> - - <LinearLayout - android:id="@+id/summary" - android:layout_width="match_parent" - android:layout_height="?attr/actionBarSize" - android:orientation="vertical"> - - <ProgressBar - android:id="@+id/now_playing_progress" - style="@android:style/Widget.Material.ProgressBar.Horizontal" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="-6dp" - android:layout_marginBottom="-6dp" - android:progress="40" - android:progressTint="@color/colorPrimaryDark" /> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:gravity="center_vertical" - android:orientation="horizontal"> - - <FrameLayout - android:layout_width="?attr/actionBarSize" - android:layout_height="?attr/actionBarSize" - android:layout_marginEnd="16dp"> - - <audio.funkwhale.ffa.views.SquareImageView - android:id="@+id/now_playing_cover" - android:layout_width="?attr/actionBarSize" - android:layout_height="?attr/actionBarSize" - tools:src="@tools:sample/avatars" /> - - <ProgressBar - android:id="@+id/now_playing_buffering" - android:layout_width="?attr/actionBarSize" - android:layout_height="?attr/actionBarSize" - android:indeterminate="true" - android:indeterminateTint="@color/controlForeground" - android:visibility="gone" /> - - </FrameLayout> - - <LinearLayout - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginEnd="8dp" - android:layout_weight="2" - android:orientation="vertical"> - - <TextView - android:id="@+id/now_playing_title" - style="@style/AppTheme.ItemTitle" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:ellipsize="end" - android:lines="1" - tools:text="Supermassive Black Hole" /> - - <TextView - android:id="@+id/now_playing_album" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:ellipsize="end" - android:lines="1" - tools:text="Muse" /> - - </LinearLayout> - - <com.google.android.material.button.MaterialButton - android:id="@+id/now_playing_toggle" - style="@style/AppTheme.OutlinedButton" - android:layout_width="?attr/actionBarSize" - android:layout_height="match_parent" - android:layout_marginEnd="16dp" - app:icon="@drawable/play" /> - - <ImageButton - android:id="@+id/now_playing_next" - style="@style/IconButton" - android:layout_width="32dp" - android:layout_height="32dp" - android:layout_marginEnd="16dp" - android:contentDescription="@string/control_next" - android:src="@drawable/next" /> - - </LinearLayout> - - </LinearLayout> - - <LinearLayout - android:id="@+id/now_playing_details" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical"> - - <FrameLayout - android:layout_width="match_parent" - android:layout_height="0dp" - android:layout_weight="1" - android:padding="8dp"> - - <audio.funkwhale.ffa.views.SquareImageView - android:id="@+id/now_playing_details_cover" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_gravity="center" - android:adjustViewBounds="true" - android:src="@drawable/funkwhaleshape" - tools:src="@tools:sample/avatars" /> - - <ImageButton - android:id="@+id/now_playing_details_info" - style="@style/IconButton" - android:layout_width="32dp" - android:layout_height="32dp" - android:layout_gravity="top|end" - android:layout_margin="8dp" - android:background="@drawable/circle" - android:contentDescription="@string/alt_track_info" - android:src="@drawable/more" - app:tint="@color/controlForeground" /> - - </FrameLayout> - - <LinearLayout - android:id="@+id/now_playing_details_controls" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginStart="32dp" - android:layout_marginEnd="32dp" - android:orientation="vertical" - android:paddingTop="16dp"> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal"> - - <LinearLayout - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:orientation="vertical"> - - <TextView - android:id="@+id/now_playing_details_title" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:textColor="@color/itemTitle" - android:textSize="18sp" - tools:text="Supermassive Black Hole" /> - - <TextView - android:id="@+id/now_playing_details_artist" - android:layout_width="match_parent" - android:layout_height="wrap_content" - tools:text="Muse" /> - - </LinearLayout> - - <ImageButton - android:id="@+id/now_playing_details_add_to_playlist" - style="@style/IconButton" - android:layout_width="24dp" - android:layout_height="24dp" - android:layout_margin="8dp" - android:contentDescription="@string/playlist_add_to" - android:src="@drawable/add_to_playlist" /> - - <ImageButton - android:id="@+id/now_playing_details_favorite" - style="@style/IconButton" - android:layout_width="24dp" - android:layout_height="24dp" - android:layout_margin="8dp" - android:contentDescription="@string/alt_album_cover" - android:src="@drawable/favorite" /> - - </LinearLayout> - - <SeekBar - android:id="@+id/now_playing_details_progress" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="16dp" - android:max="100" - android:paddingStart="0dp" - android:paddingEnd="0dp" - android:progressBackgroundTint="#cacaca" - android:progressTint="@color/controlForeground" - android:thumbOffset="3dp" - android:thumbTint="@color/controlForeground" /> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal"> - - <TextView - android:id="@+id/now_playing_details_progress_current" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_weight="1" /> - - <TextView - android:id="@+id/now_playing_details_progress_duration" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_weight="1" - android:textAlignment="textEnd" /> - - </LinearLayout> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="64dp" - android:layout_marginBottom="8dp" - android:gravity="center" - android:orientation="horizontal"> - - <ImageButton - android:id="@+id/now_playing_details_previous" - style="@style/IconButton" - android:layout_width="32dp" - android:layout_height="32dp" - android:layout_marginEnd="16dp" - android:contentDescription="@string/control_previous" - android:src="@drawable/previous" /> - - <com.google.android.material.button.MaterialButton - android:id="@+id/now_playing_details_toggle" - style="@style/AppTheme.OutlinedButton" - android:layout_width="64dp" - android:layout_height="64dp" - app:cornerRadius="64dp" - app:icon="@drawable/play" - app:iconSize="32dp" /> - - <ImageButton - android:id="@+id/now_playing_details_next" - style="@style/IconButton" - android:layout_width="32dp" - android:layout_height="32dp" - android:layout_marginStart="16dp" - android:contentDescription="@string/control_next" - android:src="@drawable/next" /> - - </LinearLayout> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginBottom="16dp" - android:gravity="center" - android:orientation="horizontal"> - - <ImageButton - android:id="@+id/now_playing_details_repeat" - style="@style/IconButton" - android:layout_width="28dp" - android:layout_height="28dp" - android:contentDescription="@string/control_next" - android:src="@drawable/repeat" /> - - </LinearLayout> - - </LinearLayout> - - </LinearLayout> - -</LinearLayout> diff --git a/app/src/main/res/layout/partial_now_playing_controls.xml b/app/src/main/res/layout/partial_now_playing_controls.xml new file mode 100644 index 0000000000000000000000000000000000000000..4196c71f639e88233a41b373b63cc6b5182fde57 --- /dev/null +++ b/app/src/main/res/layout/partial_now_playing_controls.xml @@ -0,0 +1,159 @@ +<?xml version="1.0" encoding="utf-8"?> +<layout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> + <data> + <import type="androidx.lifecycle.LiveData" /> + <import type="android.graphics.drawable.Drawable" /> + <variable name="currentTrackTitle" type="LiveData<String>" /> + <variable name="currentTrackArtist" type="LiveData<String>" /> + <variable name="isCurrentTrackFavorite" type="LiveData<Boolean>" /> + <variable name="repeatModeResource" type="LiveData<Drawable>" /> + <variable name="repeatModeAlpha" type="LiveData<Float>" /> + <variable name="currentProgressText" type="LiveData<String>" /> + <variable name="currentDurationText" type="LiveData<String>" /> + <variable name="isPlaying" type="LiveData<Boolean>" /> + <variable name="progress" type="LiveData<Integer>" /> + </data> + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="match_parent"> + <TextView + android:id="@+id/current_playing_details_title" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:paddingTop="2dp" + android:paddingBottom="2dp" + android:text="@{currentTrackTitle}" + android:textColor="@color/itemTitle" + android:textSize="18sp" + app:layout_constraintEnd_toStartOf="@+id/now_playing_details_add_to_playlist" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" tools:text="Supermassive Black Hole" /> + + <TextView + android:id="@+id/current_playing_details_artist" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:paddingTop="2dp" + android:paddingBottom="2dp" + android:text="@{currentTrackArtist}" + app:layout_constraintEnd_toStartOf="@+id/now_playing_details_add_to_playlist" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/current_playing_details_title" + tools:text="Muse" /> + + <ImageButton + android:id="@+id/now_playing_details_add_to_playlist" + style="@style/IconButton" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_margin="8dp" + android:contentDescription="@string/playlist_add_to" + android:src="@drawable/add_to_playlist" + app:layout_constraintBottom_toBottomOf="@+id/current_playing_details_artist" + + app:layout_constraintEnd_toStartOf="@+id/now_playing_details_favorite" + app:layout_constraintTop_toTopOf="@+id/current_playing_details_title" /> + + <ImageButton + android:id="@+id/now_playing_details_favorite" + style="@style/IconButton" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_margin="8dp" + android:contentDescription="@string/control_add_to_favorties" + android:src="@drawable/favorite" + app:layout_constraintBottom_toBottomOf="@+id/current_playing_details_artist" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="@+id/current_playing_details_title" + app:tint="@{isCurrentTrackFavorite ? @color/colorFavorite : @color/controlForeground, default=@color/controlForeground}" /> + + + <TextView + android:id="@+id/now_playing_details_progress_current" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text='@{currentProgressText, default="5:04"}' + app:layout_constraintBottom_toBottomOf="@+id/now_playing_details_progress" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="@+id/now_playing_details_progress" /> + + <SeekBar + android:id="@+id/now_playing_details_progress" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:max="100" + android:layout_margin="8dp" + android:progress="@{progress, default=40}" + android:progressBackgroundTint="#cacaca" + android:progressTint="@color/controlForeground" + android:thumbOffset="3dp" + android:thumbTint="@color/controlForeground" + app:layout_constraintEnd_toStartOf="@+id/now_playing_details_progress_duration" + app:layout_constraintStart_toEndOf="@+id/now_playing_details_progress_current" + app:layout_constraintTop_toBottomOf="@+id/current_playing_details_artist" /> + + <TextView + android:id="@+id/now_playing_details_progress_duration" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text='@{currentDurationText, default="5:04"}' + android:textAlignment="textEnd" + app:layout_constraintBottom_toBottomOf="@+id/now_playing_details_progress" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="@+id/now_playing_details_progress" /> + + <ImageButton + android:id="@+id/now_playing_details_previous" + style="@style/IconButton" + android:layout_width="32dp" + android:layout_height="32dp" + android:layout_margin="8dp" + android:contentDescription="@string/control_previous" + android:src="@drawable/previous" + app:layout_constraintBottom_toBottomOf="@+id/now_playing_details_toggle" + app:layout_constraintEnd_toStartOf="@+id/now_playing_details_toggle" + app:layout_constraintTop_toBottomOf="@+id/now_playing_details_progress" /> + + <com.google.android.material.button.MaterialButton + android:id="@+id/now_playing_details_toggle" + style="@style/AppTheme.OutlinedButton" + android:layout_width="64dp" + android:layout_height="64dp" + android:layout_margin="8dp" + app:cornerRadius="64dp" + app:icon="@{isPlaying ? @drawable/pause : @drawable/play, default=@drawable/play}" + app:iconSize="32dp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/now_playing_details_progress" /> + + <ImageButton + android:id="@+id/now_playing_details_next" + style="@style/IconButton" + android:layout_width="32dp" + android:layout_height="32dp" + android:layout_margin="8dp" + android:contentDescription="@string/control_next" + android:src="@drawable/next" + app:layout_constraintBottom_toBottomOf="@+id/now_playing_details_toggle" + app:layout_constraintStart_toEndOf="@+id/now_playing_details_toggle" + app:layout_constraintTop_toBottomOf="@+id/now_playing_details_progress" /> + + <ImageButton + android:id="@+id/now_playing_details_repeat" + style="@style/IconButton" + android:layout_width="32dp" + android:layout_height="32dp" + android:layout_margin="8dp" + android:alpha="@{repeatModeAlpha, default=1}" + android:contentDescription="@string/control_repeat_mode" + android:src="@{repeatModeResource, default=@drawable/repeat}" + app:layout_constraintBottom_toBottomOf="@+id/now_playing_details_toggle" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@+id/now_playing_details_progress" + app:tint="@color/controlForeground" /> + </androidx.constraintlayout.widget.ConstraintLayout> +</layout> \ No newline at end of file diff --git a/app/src/main/res/layout/partial_now_playing_header.xml b/app/src/main/res/layout/partial_now_playing_header.xml new file mode 100644 index 0000000000000000000000000000000000000000..960c43f82d07f0236b9b31dfd55b61cd83270cce --- /dev/null +++ b/app/src/main/res/layout/partial_now_playing_header.xml @@ -0,0 +1,106 @@ +<?xml version="1.0" encoding="utf-8"?> +<layout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + xmlns:app="http://schemas.android.com/apk/res-auto"> + <data> + <import type="androidx.lifecycle.LiveData" /> + <import type="android.view.View" /> + <import type="android.graphics.drawable.Drawable" /> + <variable name="isBuffering" type="LiveData<Boolean>" /> + <variable name="isPlaying" type="LiveData<Boolean>" /> + <variable name="progress" type="LiveData<Integer>" /> + <variable name="currentTrackTitle" type="LiveData<String>" /> + <variable name="currentTrackArtist" type="LiveData<String>" /> + </data> + + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="?attr/actionBarSize"> + + <com.google.android.material.progressindicator.LinearProgressIndicator + android:id="@+id/now_playing_progress" + android:layout_width="0dp" + android:layout_height="wrap_content" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + android:progress="@{progress, default=40}" + android:progressTint="@color/colorPrimaryDark" /> + + <audio.funkwhale.ffa.views.SquareImageView + android:id="@+id/now_playing_cover" + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/now_playing_progress" + app:layout_constraintBottom_toBottomOf="parent" + app:srcCompat="@drawable/cover" + tools:src="@tools:sample/avatars" /> + + <ProgressBar + android:id="@+id/now_playing_buffering" + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintStart_toStartOf="@id/now_playing_cover" + app:layout_constraintTop_toTopOf="@id/now_playing_cover" + app:layout_constraintBottom_toBottomOf="@id/now_playing_cover" + app:layout_constraintEnd_toEndOf="@id/now_playing_cover" + android:indeterminate="true" + android:indeterminateTint="@color/controlForeground" + android:visibility="@{isBuffering ? View.VISIBLE : View.GONE, default=gone}" /> + + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/header_controls" + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintHorizontal_weight="10" + app:layout_constraintStart_toEndOf="@id/now_playing_cover" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@id/now_playing_progress" + app:layout_constraintBottom_toBottomOf="parent" + android:padding="4dp"> + + <TextView + android:layout_width="0dp" + android:layout_height="wrap_content" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toStartOf="@id/now_playing_toggle" + style="@style/AppTheme.ItemTitle" + android:text="@{currentTrackTitle}" + android:ellipsize="end" + android:lines="1" + tools:text="Supermassive Black Hole" /> + + <TextView + android:layout_width="0dp" + android:layout_height="wrap_content" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toStartOf="@id/now_playing_toggle" + android:ellipsize="end" + android:lines="1" + android:text="@{currentTrackArtist}" + tools:text="Muse" /> + + <com.google.android.material.button.MaterialButton + android:id="@+id/now_playing_toggle" + style="@style/AppTheme.OutlinedButton" + android:layout_width="?attr/actionBarSize" + android:layout_height="match_parent" + app:layout_constraintEnd_toStartOf="@id/now_playing_next" + android:layout_marginEnd="16dp" + app:icon="@{isPlaying ? @drawable/pause : @drawable/play, default=@drawable/play}" /> + + <ImageButton + android:id="@+id/now_playing_next" + android:layout_width="32dp" + android:layout_height="match_parent" + app:layout_constraintEnd_toEndOf="parent" + style="@style/IconButton" + android:contentDescription="@string/control_next" + android:src="@drawable/next" /> + </androidx.constraintlayout.widget.ConstraintLayout> + </androidx.constraintlayout.widget.ConstraintLayout> +</layout> \ No newline at end of file diff --git a/app/src/main/res/navigation/main_nav.xml b/app/src/main/res/navigation/main_nav.xml index 6d7915004c20971d1717dcf2484524cbd35efa49..1869352e282f7ac05fac7a7c0fa77ed8a7628659 100644 --- a/app/src/main/res/navigation/main_nav.xml +++ b/app/src/main/res/navigation/main_nav.xml @@ -1,103 +1,119 @@ <?xml version="1.0" encoding="utf-8"?> <navigation xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/tools" - android:id="@+id/main_nav" - app:startDestination="@id/browseFragment"> + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/main_nav" + app:startDestination="@id/browseFragment"> - <fragment - android:id="@+id/browseFragment" - android:name="audio.funkwhale.ffa.fragments.BrowseFragment" - android:label="BrowseFragment"> - <action - android:id="@+id/browseToSearch" - app:destination="@id/searchFragment" - app:enterAnim="@anim/slide_up" - app:exitAnim="@anim/delayed_fade_out" - app:popEnterAnim="@anim/none" - app:popExitAnim="@anim/slide_down" /> - <action - android:id="@+id/browseToAlbums" - app:destination="@id/albumsFragment" - app:enterAnim="@anim/slide_up" - app:exitAnim="@anim/delayed_fade_out" - app:popEnterAnim="@anim/none" - app:popExitAnim="@anim/slide_down" /> - <action - android:id="@+id/browseToTracks" - app:destination="@id/tracksFragment" - app:enterAnim="@anim/slide_up" - app:exitAnim="@anim/delayed_fade_out" - app:popEnterAnim="@anim/none" - app:popExitAnim="@anim/slide_down" /> - <action - android:id="@+id/browseToArtists" - app:destination="@id/artistsFragment" - app:enterAnim="@anim/slide_up" - app:exitAnim="@anim/delayed_fade_out" - app:popEnterAnim="@anim/none" - app:popExitAnim="@anim/slide_down" /> - <action - android:id="@+id/browseToPlaylistTracks" - app:destination="@id/playlistTracksFragment" - app:enterAnim="@anim/slide_up" - app:exitAnim="@anim/delayed_fade_out" - app:popEnterAnim="@anim/none" - app:popExitAnim="@anim/slide_down" /> - </fragment> - <fragment - android:id="@+id/playlistTracksFragment" - android:name="audio.funkwhale.ffa.fragments.PlaylistTracksFragment" - android:label="PlaylistTracksFragment" > - <argument - android:name="playlist" - app:argType="audio.funkwhale.ffa.model.Playlist" /> - </fragment> - <fragment - android:id="@+id/tracksFragment" - android:name="audio.funkwhale.ffa.fragments.TracksFragment" - android:label="TracksFragment" > - <argument - android:name="album" - app:argType="audio.funkwhale.ffa.model.Album" /> - </fragment> - <fragment - android:id="@+id/albumsFragment" - android:name="audio.funkwhale.ffa.fragments.AlbumsFragment" - android:label="AlbumsFragment" > - <argument - android:name="artist" - app:argType="audio.funkwhale.ffa.model.Artist" /> - <argument - android:name="cover" - app:argType="string" - app:nullable="true" - android:defaultValue="@null" /> - <action - android:id="@+id/albumsToTracks" - app:destination="@id/tracksFragment" /> - </fragment> - <fragment - android:id="@+id/searchFragment" - android:name="audio.funkwhale.ffa.fragments.SearchFragment" - android:label="SearchFragment" > - <action - android:id="@+id/searchToAlbums" - app:destination="@id/albumsFragment" - app:enterAnim="@anim/slide_up" - app:exitAnim="@anim/delayed_fade_out" - app:popEnterAnim="@anim/none" - app:popExitAnim="@anim/slide_down" /> - <action - android:id="@+id/searchToTracks" - app:destination="@id/tracksFragment" - app:enterAnim="@anim/slide_up" - app:exitAnim="@anim/delayed_fade_out" - app:popEnterAnim="@anim/none" - app:popExitAnim="@anim/slide_down" /> - </fragment> - <fragment - android:id="@+id/artistsFragment" - android:name="audio.funkwhale.ffa.fragments.ArtistsFragment" - android:label="ArtistsFragment" /> + <fragment + android:id="@+id/browseFragment" + android:name="audio.funkwhale.ffa.fragments.BrowseFragment" + android:label="BrowseFragment"> + <action + android:id="@+id/browseToSearch" + app:destination="@id/searchFragment" + app:enterAnim="@anim/slide_up" + app:exitAnim="@anim/delayed_fade_out" + app:popEnterAnim="@anim/none" + app:popExitAnim="@anim/slide_down" /> + <action + android:id="@+id/browseToAlbums" + app:destination="@id/albumsFragment" + app:enterAnim="@anim/slide_up" + app:exitAnim="@anim/delayed_fade_out" + app:popEnterAnim="@anim/none" + app:popExitAnim="@anim/slide_down" /> + <action + android:id="@+id/browseToTracks" + app:destination="@id/tracksFragment" + app:enterAnim="@anim/slide_up" + app:exitAnim="@anim/delayed_fade_out" + app:popEnterAnim="@anim/none" + app:popExitAnim="@anim/slide_down" /> + <action + android:id="@+id/browseToArtists" + app:destination="@id/artistsFragment" + app:enterAnim="@anim/slide_up" + app:exitAnim="@anim/delayed_fade_out" + app:popEnterAnim="@anim/none" + app:popExitAnim="@anim/slide_down" /> + <action + android:id="@+id/browseToPlaylistTracks" + app:destination="@id/playlistTracksFragment" + app:enterAnim="@anim/slide_up" + app:exitAnim="@anim/delayed_fade_out" + app:popEnterAnim="@anim/none" + app:popExitAnim="@anim/slide_down" /> + </fragment> + <fragment + android:id="@+id/playlistTracksFragment" + android:name="audio.funkwhale.ffa.fragments.PlaylistTracksFragment" + android:label="PlaylistTracksFragment"> + <argument + android:name="playlist" + app:argType="audio.funkwhale.ffa.model.Playlist" /> + </fragment> + <fragment + android:id="@+id/tracksFragment" + android:name="audio.funkwhale.ffa.fragments.TracksFragment" + android:label="TracksFragment"> + <argument + android:name="album" + app:argType="audio.funkwhale.ffa.model.Album" /> + </fragment> + <fragment + android:id="@+id/albumsFragment" + android:name="audio.funkwhale.ffa.fragments.AlbumsFragment" + android:label="AlbumsFragment"> + <argument + android:name="artist" + app:argType="audio.funkwhale.ffa.model.Artist" /> + <argument + android:name="cover" + android:defaultValue="@null" + app:argType="string" + app:nullable="true" /> + <action + android:id="@+id/albumsToTracks" + app:destination="@id/tracksFragment" /> + </fragment> + <fragment + android:id="@+id/searchFragment" + android:name="audio.funkwhale.ffa.fragments.SearchFragment" + android:label="SearchFragment"> + <action + android:id="@+id/searchToAlbums" + app:destination="@id/albumsFragment" + app:enterAnim="@anim/slide_up" + app:exitAnim="@anim/delayed_fade_out" + app:popEnterAnim="@anim/none" + app:popExitAnim="@anim/slide_down" /> + <action + android:id="@+id/searchToTracks" + app:destination="@id/tracksFragment" + app:enterAnim="@anim/slide_up" + app:exitAnim="@anim/delayed_fade_out" + app:popEnterAnim="@anim/none" + app:popExitAnim="@anim/slide_down" /> + </fragment> + <fragment + android:id="@+id/artistsFragment" + android:name="audio.funkwhale.ffa.fragments.ArtistsFragment" + android:label="ArtistsFragment" /> + <action + android:id="@+id/globalBrowseToAlbums" + app:destination="@id/albumsFragment" + app:enterAnim="@anim/slide_up" + app:exitAnim="@anim/delayed_fade_out" + app:popEnterAnim="@anim/none" + app:popExitAnim="@anim/slide_down" + /> + <action + android:id="@+id/globalBrowseTracks" + app:destination="@id/tracksFragment" + app:enterAnim="@anim/slide_up" + app:exitAnim="@anim/delayed_fade_out" + app:popEnterAnim="@anim/none" + app:popExitAnim="@anim/slide_down" + /> </navigation> \ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml new file mode 100644 index 0000000000000000000000000000000000000000..2a5fe063083a40cdb81e723b676b5cc9463437ee --- /dev/null +++ b/app/src/main/res/values/attrs.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <declare-styleable name="NowPlaying"> + <attr name="target_header" format="reference" /> + </declare-styleable> +</resources> \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b99a05d354672f0de0764e317d61d36749c33ede..a6c42cc699d57cf6723ad8f19fc2695dc30e6b47 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -76,6 +76,8 @@ <string name="control_toggle">Toggle playback</string> <string name="control_previous">Previous track</string> <string name="control_next">Next track</string> + <string name="control_repeat_mode">Repeat mode</string> + <string name="control_add_to_favorties">Add to favorties</string> <string name="error_playback">This track could not be played</string> <plurals name="album_count"> <item quantity="one">%d album</item>