Merge branch 'safeArgs2' into 'develop'

Finish SafeArgs

Closes #510

See merge request ultrasonic/ultrasonic!802
This commit is contained in:
birdbird 2022-08-18 13:20:07 +00:00
commit 1a354765f9
15 changed files with 218 additions and 213 deletions

View File

@ -1,103 +0,0 @@
package org.moire.ultrasonic.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import org.moire.ultrasonic.R;
import org.moire.ultrasonic.domain.Lyrics;
import org.moire.ultrasonic.service.MusicService;
import org.moire.ultrasonic.service.MusicServiceFactory;
import org.moire.ultrasonic.util.BackgroundTask;
import org.moire.ultrasonic.util.CancellationToken;
import org.moire.ultrasonic.util.Constants;
import org.moire.ultrasonic.util.FragmentBackgroundTask;
import org.moire.ultrasonic.util.Util;
import timber.log.Timber;
/**
* Displays the lyrics of a song
*/
public class LyricsFragment extends Fragment {
private TextView artistView;
private TextView titleView;
private TextView textView;
private SwipeRefreshLayout swipe;
private CancellationToken cancellationToken;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
Util.applyTheme(this.getContext());
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.lyrics, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
cancellationToken = new CancellationToken();
Timber.d("Lyrics set title");
FragmentTitle.Companion.setTitle(this, R.string.download_menu_lyrics);
swipe = view.findViewById(R.id.lyrics_refresh);
swipe.setEnabled(false);
artistView = view.findViewById(R.id.lyrics_artist);
titleView = view.findViewById(R.id.lyrics_title);
textView = view.findViewById(R.id.lyrics_text);
load();
}
@Override
public void onDestroyView() {
cancellationToken.cancel();
super.onDestroyView();
}
private void load()
{
BackgroundTask<Lyrics> task = new FragmentBackgroundTask<Lyrics>(getActivity(), true, swipe, cancellationToken)
{
@Override
protected Lyrics doInBackground() throws Throwable
{
Bundle arguments = getArguments();
if (arguments == null) return null;
String artist = arguments.getString(Constants.INTENT_ARTIST);
String title = arguments.getString(Constants.INTENT_TITLE);
MusicService musicService = MusicServiceFactory.getMusicService();
return musicService.getLyrics(artist, title);
}
@Override
protected void done(Lyrics result)
{
if (result != null && result.getArtist() != null)
{
artistView.setText(result.getArtist());
titleView.setText(result.getTitle());
textView.setText(result.getText());
}
else
{
artistView.setText(R.string.lyrics_nomatch);
}
}
};
task.execute();
}
}

View File

@ -49,6 +49,7 @@ import org.moire.ultrasonic.R
import org.moire.ultrasonic.app.UApp import org.moire.ultrasonic.app.UApp
import org.moire.ultrasonic.data.ActiveServerProvider import org.moire.ultrasonic.data.ActiveServerProvider
import org.moire.ultrasonic.data.ServerSettingDao import org.moire.ultrasonic.data.ServerSettingDao
import org.moire.ultrasonic.fragment.MainFragmentDirections
import org.moire.ultrasonic.fragment.OnBackPressedHandler import org.moire.ultrasonic.fragment.OnBackPressedHandler
import org.moire.ultrasonic.model.ServerSettingsModel import org.moire.ultrasonic.model.ServerSettingsModel
import org.moire.ultrasonic.provider.SearchSuggestionProvider import org.moire.ultrasonic.provider.SearchSuggestionProvider
@ -377,10 +378,8 @@ class NavigationActivity : AppCompatActivity() {
) )
suggestions.saveRecentQuery(query, null) suggestions.saveRecentQuery(query, null)
val bundle = Bundle() val action = MainFragmentDirections.toSearchFragment(query, autoPlay)
bundle.putString(Constants.INTENT_QUERY, query) findNavController(R.id.nav_host_fragment).navigate(action)
bundle.putBoolean(Constants.INTENT_AUTOPLAY, autoPlay)
findNavController(R.id.nav_host_fragment).navigate(R.id.searchFragment, bundle)
} }
} }

View File

@ -109,7 +109,7 @@ class FolderSelectorBinder(context: Context) :
menuItem.isChecked = true menuItem.isChecked = true
folderName.text = musicFolderName folderName.text = musicFolderName
RxBus.musicFolderChangedEventPublisher.onNext(selectedFolderId) RxBus.musicFolderChangedEventPublisher.onNext(RxBus.Folder(selectedFolderId))
return true return true
} }

View File

@ -5,7 +5,9 @@ import android.content.Context
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.PopupMenu
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import com.drakeet.multitype.ItemViewBinder import com.drakeet.multitype.ItemViewBinder
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
@ -13,6 +15,7 @@ import org.moire.ultrasonic.R
import org.moire.ultrasonic.domain.Identifiable import org.moire.ultrasonic.domain.Identifiable
import org.moire.ultrasonic.domain.Track import org.moire.ultrasonic.domain.Track
@Suppress("LongParameterList")
class TrackViewBinder( class TrackViewBinder(
val onItemClick: (Track, Int) -> Unit, val onItemClick: (Track, Int) -> Unit,
val onContextMenuClick: ((MenuItem, Track) -> Boolean)? = null, val onContextMenuClick: ((MenuItem, Track) -> Boolean)? = null,
@ -20,13 +23,18 @@ class TrackViewBinder(
val draggable: Boolean, val draggable: Boolean,
context: Context, context: Context,
val lifecycleOwner: LifecycleOwner, val lifecycleOwner: LifecycleOwner,
val createContextMenu: (View, Track) -> PopupMenu = { view, _ ->
Utils.createPopupMenu(
view,
R.menu.context_menu_track
)
}
) : ItemViewBinder<Identifiable, TrackViewHolder>(), KoinComponent { ) : ItemViewBinder<Identifiable, TrackViewHolder>(), KoinComponent {
var startDrag: ((TrackViewHolder) -> Unit)? = null var startDrag: ((TrackViewHolder) -> Unit)? = null
// Set our layout files // Set our layout files
val layout = R.layout.list_item_track val layout = R.layout.list_item_track
private val contextMenuLayout = R.menu.context_menu_track
private val imageHelper: Utils.ImageHelper = Utils.ImageHelper(context) private val imageHelper: Utils.ImageHelper = Utils.ImageHelper(context)
@ -62,7 +70,7 @@ class TrackViewBinder(
holder.itemView.setOnLongClickListener { holder.itemView.setOnLongClickListener {
if (onContextMenuClick != null) { if (onContextMenuClick != null) {
val popup = Utils.createPopupMenu(holder.itemView, contextMenuLayout) val popup = createContextMenu(holder.itemView, track)
popup.setOnMenuItemClickListener { menuItem -> popup.setOnMenuItemClickListener { menuItem ->
onContextMenuClick.invoke(menuItem, track) onContextMenuClick.invoke(menuItem, track)

View File

@ -16,6 +16,7 @@ import android.widget.ImageView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import com.google.android.material.switchmaterial.SwitchMaterial import com.google.android.material.switchmaterial.SwitchMaterial
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import com.skydoves.colorpickerview.ColorPickerDialog import com.skydoves.colorpickerview.ColorPickerDialog
@ -56,9 +57,6 @@ private const val DIALOG_PADDING = 12
* Displays a form where server settings can be created / edited * Displays a form where server settings can be created / edited
*/ */
class EditServerFragment : Fragment(), OnBackPressedHandler { class EditServerFragment : Fragment(), OnBackPressedHandler {
companion object {
const val EDIT_SERVER_INTENT_INDEX = "index"
}
private val serverSettingsModel: ServerSettingsModel by viewModel() private val serverSettingsModel: ServerSettingsModel by viewModel()
private val activeServerProvider: ActiveServerProvider by inject() private val activeServerProvider: ActiveServerProvider by inject()
@ -79,6 +77,8 @@ class EditServerFragment : Fragment(), OnBackPressedHandler {
private var currentColor: Int = 0 private var currentColor: Int = 0
private var selectedColor: Int? = null private var selectedColor: Int? = null
private val navArgs by navArgs<EditServerFragmentArgs>()
@Override @Override
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
Util.applyTheme(this.context) Util.applyTheme(this.context)
@ -107,15 +107,10 @@ class EditServerFragment : Fragment(), OnBackPressedHandler {
saveButton = view.findViewById(R.id.edit_save) saveButton = view.findViewById(R.id.edit_save)
testButton = view.findViewById(R.id.edit_test) testButton = view.findViewById(R.id.edit_test)
val index = arguments?.getInt( if (navArgs.index != -1) {
EDIT_SERVER_INTENT_INDEX,
-1
) ?: -1
if (index != -1) {
// Editing an existing server // Editing an existing server
FragmentTitle.setTitle(this, R.string.server_editor_label) FragmentTitle.setTitle(this, R.string.server_editor_label)
val serverSetting = serverSettingsModel.getServerSetting(index) val serverSetting = serverSettingsModel.getServerSetting(navArgs.index)
serverSetting.observe( serverSetting.observe(
viewLifecycleOwner viewLifecycleOwner
) { t -> ) { t ->

View File

@ -64,7 +64,7 @@ abstract class EntryListFragment<T : GenericEntry> : MultiListFragment<T>() {
RxBus.musicFolderChangedEventObservable.subscribe { RxBus.musicFolderChangedEventObservable.subscribe {
if (!listModel.isOffline()) { if (!listModel.isOffline()) {
val currentSetting = listModel.activeServer val currentSetting = listModel.activeServer
currentSetting.musicFolderId = it currentSetting.musicFolderId = it.id
serverSettingsModel.updateItem(currentSetting) serverSettingsModel.updateItem(currentSetting)
} }
listModel.refresh(refreshListView!!) listModel.refresh(refreshListView!!)

View File

@ -14,8 +14,6 @@ import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.view.ContextMenu
import android.view.ContextMenu.ContextMenuInfo
import android.view.GestureDetector import android.view.GestureDetector
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu import android.view.Menu
@ -26,10 +24,10 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.WindowManager import android.view.WindowManager
import android.view.animation.AnimationUtils import android.view.animation.AnimationUtils
import android.widget.AdapterView.AdapterContextMenuInfo
import android.widget.EditText import android.widget.EditText
import android.widget.ImageView import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.PopupMenu
import android.widget.SeekBar import android.widget.SeekBar
import android.widget.SeekBar.OnSeekBarChangeListener import android.widget.SeekBar.OnSeekBarChangeListener
import android.widget.TextView import android.widget.TextView
@ -71,9 +69,11 @@ import org.koin.core.component.KoinComponent
import org.moire.ultrasonic.R import org.moire.ultrasonic.R
import org.moire.ultrasonic.adapters.BaseAdapter import org.moire.ultrasonic.adapters.BaseAdapter
import org.moire.ultrasonic.adapters.TrackViewBinder import org.moire.ultrasonic.adapters.TrackViewBinder
import org.moire.ultrasonic.api.subsonic.models.AlbumListType
import org.moire.ultrasonic.audiofx.EqualizerController import org.moire.ultrasonic.audiofx.EqualizerController
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline
import org.moire.ultrasonic.domain.Identifiable import org.moire.ultrasonic.domain.Identifiable
import org.moire.ultrasonic.domain.MusicDirectory
import org.moire.ultrasonic.domain.Track import org.moire.ultrasonic.domain.Track
import org.moire.ultrasonic.fragment.FragmentTitle.Companion.setTitle import org.moire.ultrasonic.fragment.FragmentTitle.Companion.setTitle
import org.moire.ultrasonic.service.MediaPlayerController import org.moire.ultrasonic.service.MediaPlayerController
@ -85,7 +85,6 @@ import org.moire.ultrasonic.subsonic.NetworkAndStorageChecker
import org.moire.ultrasonic.subsonic.ShareHandler import org.moire.ultrasonic.subsonic.ShareHandler
import org.moire.ultrasonic.util.CancellationToken import org.moire.ultrasonic.util.CancellationToken
import org.moire.ultrasonic.util.CommunicationError import org.moire.ultrasonic.util.CommunicationError
import org.moire.ultrasonic.util.Constants
import org.moire.ultrasonic.util.Settings import org.moire.ultrasonic.util.Settings
import org.moire.ultrasonic.util.Util import org.moire.ultrasonic.util.Util
import org.moire.ultrasonic.util.toTrack import org.moire.ultrasonic.util.toTrack
@ -346,8 +345,6 @@ class PlayerFragment :
initPlaylistDisplay() initPlaylistDisplay()
registerForContextMenu(playlistView)
EqualizerController.get().observe( EqualizerController.get().observe(
requireActivity() requireActivity()
) { equalizerController -> ) { equalizerController ->
@ -546,55 +543,54 @@ class PlayerFragment :
} }
} }
override fun onCreateContextMenu(menu: ContextMenu, view: View, menuInfo: ContextMenuInfo?) { private fun onCreateContextMenu(view: View, track: Track): PopupMenu {
super.onCreateContextMenu(menu, view, menuInfo) val popup = PopupMenu(view.context, view)
if (view === playlistView) { val inflater: MenuInflater = popup.menuInflater
val info = menuInfo as AdapterContextMenuInfo? inflater.inflate(R.menu.nowplaying_context, popup.menu)
val track = viewAdapter.getCurrentList()[info!!.position] as Track
val menuInflater = requireActivity().menuInflater
menuInflater.inflate(R.menu.nowplaying_context, menu)
if (track.parent == null) { if (track.parent == null) {
val menuItem = menu.findItem(R.id.menu_show_album) val menuItem = popup.menu.findItem(R.id.menu_show_album)
if (menuItem != null) { if (menuItem != null) {
menuItem.isVisible = false menuItem.isVisible = false
} }
} }
if (isOffline() || !Settings.shouldUseId3Tags) { if (isOffline() || !Settings.shouldUseId3Tags) {
menu.findItem(R.id.menu_show_artist)?.isVisible = false popup.menu.findItem(R.id.menu_show_artist)?.isVisible = false
} }
if (isOffline()) { popup.menu.findItem(R.id.menu_lyrics)?.isVisible = !isOffline()
menu.findItem(R.id.menu_lyrics)?.isVisible = false popup.show()
} return popup
}
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
// TODO Why is Track null? return menuItemSelected(item.itemId, currentSong) || super.onOptionsItemSelected(item)
return menuItemSelected(item.itemId, null) || super.onOptionsItemSelected(item) }
private fun onContextMenuItemSelected(
menuItem: MenuItem,
item: MusicDirectory.Child
): Boolean {
if (item !is Track) return false
return menuItemSelected(menuItem.itemId, item)
} }
@Suppress("ComplexMethod", "LongMethod", "ReturnCount") @Suppress("ComplexMethod", "LongMethod", "ReturnCount")
private fun menuItemSelected(menuItemId: Int, track: Track?): Boolean { private fun menuItemSelected(menuItemId: Int, track: Track?): Boolean {
val bundle: Bundle
when (menuItemId) { when (menuItemId) {
R.id.menu_show_artist -> { R.id.menu_show_artist -> {
if (track == null) return false if (track == null) return false
if (Settings.shouldUseId3Tags) { if (Settings.shouldUseId3Tags) {
PlayerFragmentDirections.playerToSelectAlbum( val action = PlayerFragmentDirections.playerToAlbumsList(
type = AlbumListType.BY_ARTIST,
id = track.artistId, id = track.artistId,
name = track.artist, title = track.artist,
parentId = track.artistId, offset = 0,
isArtist = true, size = 1000
) )
bundle = Bundle() findNavController().navigate(action)
Navigation.findNavController(requireView())
.navigate(R.id.playerToSelectAlbum, bundle)
} }
return true return true
} }
@ -614,11 +610,9 @@ class PlayerFragment :
return true return true
} }
R.id.menu_lyrics -> { R.id.menu_lyrics -> {
if (track == null) return false if (track?.artist == null || track.title == null) return false
bundle = Bundle() val action = PlayerFragmentDirections.playerToLyrics(track.artist!!, track.title!!)
bundle.putString(Constants.INTENT_ARTIST, track.artist) Navigation.findNavController(requireView()).navigate(action)
bundle.putString(Constants.INTENT_TITLE, track.title)
Navigation.findNavController(requireView()).navigate(R.id.playerToLyrics, bundle)
return true return true
} }
R.id.menu_remove -> { R.id.menu_remove -> {
@ -672,9 +666,9 @@ class PlayerFragment :
return true return true
} }
R.id.menu_item_star -> { R.id.menu_item_star -> {
if (currentSong == null) return true if (track == null) return true
val isStarred = currentSong!!.starred val isStarred = track.starred
mediaPlayerController.controller?.setRating( mediaPlayerController.controller?.setRating(
HeartRating(!isStarred) HeartRating(!isStarred)
@ -685,10 +679,10 @@ class PlayerFragment :
override fun onSuccess(result: SessionResult?) { override fun onSuccess(result: SessionResult?) {
if (isStarred) { if (isStarred) {
starMenuItem.setIcon(hollowStar) starMenuItem.setIcon(hollowStar)
currentSong!!.starred = false track.starred = false
} else { } else {
starMenuItem.setIcon(fullStar) starMenuItem.setIcon(fullStar)
currentSong!!.starred = true track.starred = true
} }
} }
@ -704,11 +698,11 @@ class PlayerFragment :
return true return true
} }
R.id.menu_item_bookmark_set -> { R.id.menu_item_bookmark_set -> {
if (currentSong == null) return true if (track == null) return true
val songId = currentSong!!.id val songId = track.id
val playerPosition = mediaPlayerController.playerPosition val playerPosition = mediaPlayerController.playerPosition
currentSong!!.bookmarkPosition = playerPosition track.bookmarkPosition = playerPosition
val bookmarkTime = Util.formatTotalDuration(playerPosition.toLong(), true) val bookmarkTime = Util.formatTotalDuration(playerPosition.toLong(), true)
Thread { Thread {
val musicService = getMusicService() val musicService = getMusicService()
@ -726,10 +720,10 @@ class PlayerFragment :
return true return true
} }
R.id.menu_item_bookmark_delete -> { R.id.menu_item_bookmark_delete -> {
if (currentSong == null) return true if (track == null) return true
val bookmarkSongId = currentSong!!.id val bookmarkSongId = track.id
currentSong!!.bookmarkPosition = 0 track.bookmarkPosition = 0
Thread { Thread {
val musicService = getMusicService() val musicService = getMusicService()
try { try {
@ -758,10 +752,10 @@ class PlayerFragment :
return true return true
} }
R.id.menu_item_share_song -> { R.id.menu_item_share_song -> {
if (currentSong == null) return true if (track == null) return true
val tracks: MutableList<Track?> = ArrayList() val tracks: MutableList<Track?> = ArrayList()
tracks.add(currentSong) tracks.add(track)
shareHandler.createShare( shareHandler.createShare(
this, this,
@ -856,6 +850,8 @@ class PlayerFragment :
draggable = true, draggable = true,
context = requireContext(), context = requireContext(),
lifecycleOwner = viewLifecycleOwner, lifecycleOwner = viewLifecycleOwner,
createContextMenu = { view, track -> onCreateContextMenu(view, track) },
onContextMenuClick = { menu, id -> onContextMenuItemSelected(menu, id) },
).apply { ).apply {
this.startDrag = { holder -> this.startDrag = { holder ->
dragTouchHelper.startDrag(holder) dragTouchHelper.startDrag(holder)

View File

@ -19,6 +19,7 @@ import androidx.core.view.isVisible
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
@ -46,7 +47,6 @@ import org.moire.ultrasonic.subsonic.ShareHandler
import org.moire.ultrasonic.subsonic.VideoPlayer.Companion.playVideo import org.moire.ultrasonic.subsonic.VideoPlayer.Companion.playVideo
import org.moire.ultrasonic.util.CancellationToken import org.moire.ultrasonic.util.CancellationToken
import org.moire.ultrasonic.util.CommunicationError import org.moire.ultrasonic.util.CommunicationError
import org.moire.ultrasonic.util.Constants
import org.moire.ultrasonic.util.Settings import org.moire.ultrasonic.util.Settings
import org.moire.ultrasonic.util.Util import org.moire.ultrasonic.util.Util
import org.moire.ultrasonic.util.Util.toast import org.moire.ultrasonic.util.Util.toast
@ -54,8 +54,6 @@ import timber.log.Timber
/** /**
* Initiates a search on the media library and displays the results * Initiates a search on the media library and displays the results
*
* TODO: Move to SafeArgs
*/ */
class SearchFragment : MultiListFragment<Identifiable>(), KoinComponent { class SearchFragment : MultiListFragment<Identifiable>(), KoinComponent {
private var searchResult: SearchResult? = null private var searchResult: SearchResult? = null
@ -69,6 +67,8 @@ class SearchFragment : MultiListFragment<Identifiable>(), KoinComponent {
private var cancellationToken: CancellationToken? = null private var cancellationToken: CancellationToken? = null
private val navArgs by navArgs<SearchFragmentArgs>()
override val listModel: SearchListModel by viewModels() override val listModel: SearchListModel by viewModels()
override val mainLayout: Int = R.layout.search override val mainLayout: Int = R.layout.search
@ -133,14 +133,10 @@ class SearchFragment : MultiListFragment<Identifiable>(), KoinComponent {
MoreButtonBinder() MoreButtonBinder()
) )
// Fragment was started with a query (e.g. from voice search), try to execute search right away // If the fragment was started with a query (e.g. from voice search),
val arguments = arguments // try to execute search right away
if (arguments != null) { if (navArgs.query != null) {
val query = arguments.getString(Constants.INTENT_QUERY) return search(navArgs.query!!, navArgs.autoplay)
val autoPlay = arguments.getBoolean(Constants.INTENT_AUTOPLAY, false)
if (query != null) {
return search(query, autoPlay)
}
} }
} }
@ -156,10 +152,8 @@ class SearchFragment : MultiListFragment<Identifiable>(), KoinComponent {
val searchableInfo = searchManager.getSearchableInfo(requireActivity().componentName) val searchableInfo = searchManager.getSearchableInfo(requireActivity().componentName)
searchView!!.setSearchableInfo(searchableInfo) searchView!!.setSearchableInfo(searchableInfo)
val arguments = arguments val autoPlay = navArgs.autoplay
val autoPlay = arguments != null && val query = navArgs.query
arguments.getBoolean(Constants.INTENT_AUTOPLAY, false)
val query = arguments?.getString(Constants.INTENT_QUERY)
// If started with a query, enter it to the searchView // If started with a query, enter it to the searchView
if (query != null) { if (query != null) {

View File

@ -16,7 +16,6 @@ import org.moire.ultrasonic.adapters.ServerRowAdapter
import org.moire.ultrasonic.data.ActiveServerProvider import org.moire.ultrasonic.data.ActiveServerProvider
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.OFFLINE_DB_ID import org.moire.ultrasonic.data.ActiveServerProvider.Companion.OFFLINE_DB_ID
import org.moire.ultrasonic.data.ServerSetting import org.moire.ultrasonic.data.ServerSetting
import org.moire.ultrasonic.fragment.EditServerFragment.Companion.EDIT_SERVER_INTENT_INDEX
import org.moire.ultrasonic.model.ServerSettingsModel import org.moire.ultrasonic.model.ServerSettingsModel
import org.moire.ultrasonic.service.MediaPlayerController import org.moire.ultrasonic.service.MediaPlayerController
import org.moire.ultrasonic.util.ErrorDialog import org.moire.ultrasonic.util.ErrorDialog
@ -25,8 +24,6 @@ import timber.log.Timber
/** /**
* Displays the list of configured servers, they can be selected or edited * Displays the list of configured servers, they can be selected or edited
*
* TODO: Manage mode is unused. Remove it...
*/ */
class ServerSelectorFragment : Fragment() { class ServerSelectorFragment : Fragment() {
@ -143,8 +140,7 @@ class ServerSelectorFragment : Fragment() {
* Starts the Edit Server Fragment to edit the details of a server * Starts the Edit Server Fragment to edit the details of a server
*/ */
private fun editServerByIndex(index: Int) { private fun editServerByIndex(index: Int) {
val bundle = Bundle() val action = ServerSelectorFragmentDirections.toEditServer(index)
bundle.putInt(EDIT_SERVER_INTENT_INDEX, index) findNavController().navigate(action)
findNavController().navigate(R.id.serverSelectorToEditServer, bundle)
} }
} }

View File

@ -0,0 +1,94 @@
/*
* LyricsFragment.kt
* Copyright (C) 2009-2022 Ultrasonic developers
*
* Distributed under terms of the GNU GPLv3 license.
*/
package org.moire.ultrasonic.fragment.legacy
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.navArgs
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import org.moire.ultrasonic.R
import org.moire.ultrasonic.domain.Lyrics
import org.moire.ultrasonic.fragment.FragmentTitle.Companion.setTitle
import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService
import org.moire.ultrasonic.util.BackgroundTask
import org.moire.ultrasonic.util.CancellationToken
import org.moire.ultrasonic.util.FragmentBackgroundTask
import org.moire.ultrasonic.util.Util.applyTheme
import timber.log.Timber
/**
* Displays the lyrics of a song
*
* TODO: This file has been converted from Java, but not modernized yet.
*/
class LyricsFragment : Fragment() {
private var artistView: TextView? = null
private var titleView: TextView? = null
private var textView: TextView? = null
private var swipe: SwipeRefreshLayout? = null
private var cancellationToken: CancellationToken? = null
private val navArgs by navArgs<LyricsFragmentArgs>()
override fun onCreate(savedInstanceState: Bundle?) {
applyTheme(this.context)
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.lyrics, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
cancellationToken = CancellationToken()
Timber.d("Lyrics set title")
setTitle(this, R.string.download_menu_lyrics)
swipe = view.findViewById(R.id.lyrics_refresh)
swipe?.isEnabled = false
artistView = view.findViewById(R.id.lyrics_artist)
titleView = view.findViewById(R.id.lyrics_title)
textView = view.findViewById(R.id.lyrics_text)
load()
}
override fun onDestroyView() {
cancellationToken!!.cancel()
super.onDestroyView()
}
private fun load() {
val task: BackgroundTask<Lyrics> = object : FragmentBackgroundTask<Lyrics>(
activity, true, swipe, cancellationToken
) {
@Throws(Throwable::class)
override fun doInBackground(): Lyrics {
val musicService = getMusicService()
return musicService.getLyrics(navArgs.artist, navArgs.title)!!
}
override fun done(result: Lyrics) {
if (result.artist != null) {
artistView!!.text = result.artist
titleView!!.text = result.title
textView!!.text = result.text
} else {
artistView!!.setText(R.string.lyrics_nomatch)
}
}
}
task.execute()
}
}

View File

@ -28,9 +28,6 @@ import timber.log.Timber
/** /**
* This class is responsible for handling received events for the Media Player implementation * This class is responsible for handling received events for the Media Player implementation
*
* TODO: Remove this class. Each component should listen to the lifecycleEvents and act on them
* independently
*/ */
class MediaPlayerLifecycleSupport : KoinComponent { class MediaPlayerLifecycleSupport : KoinComponent {
private val playbackStateSerializer by inject<PlaybackStateSerializer>() private val playbackStateSerializer by inject<PlaybackStateSerializer>()

View File

@ -25,9 +25,9 @@ class RxBus {
val themeChangedEventObservable: Observable<Unit> = val themeChangedEventObservable: Observable<Unit> =
themeChangedEventPublisher.observeOn(mainThread()) themeChangedEventPublisher.observeOn(mainThread())
val musicFolderChangedEventPublisher: PublishSubject<String> = val musicFolderChangedEventPublisher: PublishSubject<Folder> =
PublishSubject.create() PublishSubject.create()
val musicFolderChangedEventObservable: Observable<String> = val musicFolderChangedEventObservable: Observable<Folder> =
musicFolderChangedEventPublisher.observeOn(mainThread()) musicFolderChangedEventPublisher.observeOn(mainThread())
val playerStatePublisher: PublishSubject<StateWithTrack> = val playerStatePublisher: PublishSubject<StateWithTrack> =
@ -93,6 +93,10 @@ class RxBus {
val state: DownloadStatus, val state: DownloadStatus,
val progress: Int? val progress: Int?
) )
data class Folder(
val id: String?
)
} }
operator fun CompositeDisposable.plusAssign(disposable: Disposable) { operator fun CompositeDisposable.plusAssign(disposable: Disposable) {

View File

@ -15,14 +15,6 @@ object Constants {
const val REST_PROTOCOL_VERSION = "1.7.0" const val REST_PROTOCOL_VERSION = "1.7.0"
const val REST_CLIENT_ID = "Ultrasonic" const val REST_CLIENT_ID = "Ultrasonic"
// Legacy names for intent extras, in those fragments which don't use SafeArgs yet.
const val INTENT_ARTIST = "subsonic.artist"
const val INTENT_TITLE = "subsonic.title"
const val INTENT_AUTOPLAY = "subsonic.playall"
const val INTENT_QUERY = "subsonic.query"
const val INTENT_ALBUM_LIST_TYPE = "subsonic.albumlisttype"
const val INTENT_SHOW_PLAYER = "subsonic.showplayer"
// Names for Intent Actions // Names for Intent Actions
const val CMD_PROCESS_KEYCODE = "org.moire.ultrasonic.CMD_PROCESS_KEYCODE" const val CMD_PROCESS_KEYCODE = "org.moire.ultrasonic.CMD_PROCESS_KEYCODE"
const val CMD_PLAY = "org.moire.ultrasonic.CMD_PLAY" const val CMD_PLAY = "org.moire.ultrasonic.CMD_PLAY"
@ -32,6 +24,7 @@ object Constants {
const val CMD_STOP = "org.moire.ultrasonic.CMD_STOP" const val CMD_STOP = "org.moire.ultrasonic.CMD_STOP"
const val CMD_PREVIOUS = "org.moire.ultrasonic.CMD_PREVIOUS" const val CMD_PREVIOUS = "org.moire.ultrasonic.CMD_PREVIOUS"
const val CMD_NEXT = "org.moire.ultrasonic.CMD_NEXT" const val CMD_NEXT = "org.moire.ultrasonic.CMD_NEXT"
const val INTENT_SHOW_PLAYER = "org.moire.ultrasonic.SHOW_PLAYER"
// Legacy Preferences keys // Legacy Preferences keys
// Warning: Don't add any new here! // Warning: Don't add any new here!

View File

@ -35,6 +35,11 @@
a:icon="@drawable/ic_menu_remove_all" a:icon="@drawable/ic_menu_remove_all"
app:showAsAction="ifRoom|withText" app:showAsAction="ifRoom|withText"
a:title="@string/download.menu_clear_playlist"/> a:title="@string/download.menu_clear_playlist"/>
<item
a:id="@+id/menu_lyrics"
a:icon="@drawable/ic_library"
app:showAsAction="ifRoom|withText"
a:title="@string/download.menu_lyrics"/>
<item <item
a:id="@+id/menu_item_bookmark_set" a:id="@+id/menu_item_bookmark_set"
a:icon="@drawable/ic_menu_bookmark" a:icon="@drawable/ic_menu_bookmark"

View File

@ -212,6 +212,15 @@
<action <action
android:id="@+id/searchToAlbumsList" android:id="@+id/searchToAlbumsList"
app:destination="@id/albumListFragment" /> app:destination="@id/albumListFragment" />
<argument
android:name="query"
app:argType="string"
app:nullable="true"
android:defaultValue="@null" />
<argument
android:name="autoplay"
app:argType="boolean"
android:defaultValue="false" />
</fragment> </fragment>
<fragment <fragment
android:id="@+id/playlistsFragment" android:id="@+id/playlistsFragment"
@ -263,6 +272,9 @@
<action <action
android:id="@+id/playerToSelectAlbum" android:id="@+id/playerToSelectAlbum"
app:destination="@id/trackCollectionFragment" /> app:destination="@id/trackCollectionFragment" />
<action
android:id="@+id/playerToAlbumsList"
app:destination="@id/albumListFragment" />
<action <action
android:id="@+id/playerToLyrics" android:id="@+id/playerToLyrics"
app:destination="@id/lyricsFragment" /> app:destination="@id/lyricsFragment" />
@ -272,7 +284,14 @@
</fragment> </fragment>
<fragment <fragment
android:id="@+id/lyricsFragment" android:id="@+id/lyricsFragment"
android:name="org.moire.ultrasonic.fragment.LyricsFragment" /> android:name="org.moire.ultrasonic.fragment.legacy.LyricsFragment" >
<argument
android:name="artist"
app:argType="string" />
<argument
android:name="title"
app:argType="string" />
</fragment>
<fragment <fragment
android:id="@+id/equalizerFragment" android:id="@+id/equalizerFragment"
android:name="org.moire.ultrasonic.fragment.EqualizerFragment" /> android:name="org.moire.ultrasonic.fragment.EqualizerFragment" />
@ -280,10 +299,18 @@
android:id="@+id/serverSelectorFragment" android:id="@+id/serverSelectorFragment"
android:name="org.moire.ultrasonic.fragment.ServerSelectorFragment" > android:name="org.moire.ultrasonic.fragment.ServerSelectorFragment" >
<action <action
android:id="@+id/serverSelectorToEditServer" android:id="@+id/toEditServer"
app:destination="@id/editServerFragment" /> app:destination="@id/editServerFragment" />
</fragment> </fragment>
<fragment <fragment
android:id="@+id/editServerFragment" android:id="@+id/editServerFragment"
android:name="org.moire.ultrasonic.fragment.EditServerFragment" /> android:name="org.moire.ultrasonic.fragment.EditServerFragment" >
<argument
android:name="index"
app:argType="integer"
android:defaultValue="-1" />
</fragment>
<action
android:id="@+id/toSearchFragment"
app:destination="@id/searchFragment" />
</navigation> </navigation>