From f1c591b716f7a0e57efe83f491aaeac47076e948 Mon Sep 17 00:00:00 2001 From: Konstantinos Paparas Date: Fri, 3 Jan 2025 23:11:57 +0100 Subject: [PATCH] refactor: migrate from legacy libraries Finishes migration from dbflow to room keeping the schema, Also includes the jackson to moshi migration to drop reflection, drops the last remaining usages of rxjava, and finalizes migration from mvp to mvvm. --- app/build.gradle.kts | 62 +- .../com.kelsos.mbrc.data.Database/3.json | 378 ++++++++++++ app/src/main/AndroidManifest.xml | 6 +- app/src/main/java/com/kelsos/mbrc/App.kt | 9 - .../main/java/com/kelsos/mbrc/AppModule.kt | 354 +++++------ .../main/java/com/kelsos/mbrc/BaseActivity.kt | 279 +++++---- .../com/kelsos/mbrc/UpdateRequiredActivity.kt | 6 +- .../com/kelsos/mbrc/annotations/Connection.kt | 13 - .../kelsos/mbrc/annotations/PlayerState.kt | 14 - .../java/com/kelsos/mbrc/annotations/Queue.kt | 17 - .../com/kelsos/mbrc/annotations/Repeat.kt | 13 - .../com/kelsos/mbrc/annotations/Search.kt | 8 - .../kelsos/mbrc/annotations/SocketAction.kt | 27 - .../commands/CancelNotificationCommand.kt | 13 - .../ConnectionStatusChangedCommand.kt | 25 - .../kelsos/mbrc/commands/HandleHandshake.kt | 18 - .../commands/InitiateConnectionCommand.kt | 14 - .../mbrc/commands/KeyVolumeDownCommand.kt | 31 - .../mbrc/commands/KeyVolumeUpCommand.kt | 31 - .../kelsos/mbrc/commands/ProcessUserAction.kt | 20 - .../kelsos/mbrc/commands/ProtocolRequest.kt | 19 - .../commands/ReduceVolumeOnRingCommand.kt | 20 - .../mbrc/commands/RestartConnectionCommand.kt | 14 - .../commands/SocketDataAvailableCommand.kt | 13 - .../mbrc/commands/StartDiscoveryCommand.kt | 13 - .../commands/TerminateConnectionCommand.kt | 18 - .../mbrc/commands/TerminateServiceCommand.kt | 20 - .../mbrc/commands/VersionCheckCommand.kt | 108 ---- .../visual/HandshakeCompletionActions.kt | 55 -- .../visual/NotifyNotAllowedCommand.kt | 25 - .../mbrc/common/data/LocalDataSource.kt | 20 - .../mbrc/common/data/RemoteDataSource.kt | 14 - .../com/kelsos/mbrc/common/data/Repository.kt | 20 +- .../kelsos/mbrc/common/mvp/BasePresenter.kt | 64 -- .../com/kelsos/mbrc/common/mvp/BaseView.kt | 3 - .../com/kelsos/mbrc/common/mvp/Presenter.kt | 9 - .../kelsos/mbrc/common/mvvm/BaseViewModel.kt | 16 + .../com/kelsos/mbrc/common/state/AppState.kt | 11 + .../mbrc/common/state/AppStateManager.kt | 134 +++++ .../mbrc/common/state/ConnectionModel.kt | 47 -- .../mbrc/common/state/ConnectionState.kt | 13 + .../mbrc/common/state/ConnectionStatus.kt | 11 + .../com/kelsos/mbrc/common/state/LfmRating.kt | 21 + .../kelsos/mbrc/common/state/MainDataModel.kt | 92 --- .../mbrc/common/state/NowPlayingTrack.kt | 18 + .../kelsos/mbrc/common/state/PlayerState.kt | 28 + .../kelsos/mbrc/common/state/PlayerStatus.kt | 21 + .../mbrc/common/state/PlayerStatusModel.kt | 13 + .../mbrc/common/state/PlayingPosition.kt | 23 + .../kelsos/mbrc/common/state/PlayingTrack.kt | 49 ++ .../state/PlayingTrackCache.kt} | 62 +- .../com/kelsos/mbrc/common/state/Repeat.kt | 24 + .../kelsos/mbrc/common/state/ShuffleMode.kt | 24 + .../kelsos/mbrc/common/state/TrackRating.kt | 8 + .../kelsos/mbrc/common/ui/CircleImageView.kt | 143 +++-- .../mbrc/common/ui/MultiSwipeRefreshLayout.kt | 4 +- .../utilities/AppCoroutineDispatchers.kt | 12 +- .../kelsos/mbrc/common/utilities/Helpers.kt | 30 + .../mbrc/common/utilities/RemoteUtils.kt | 52 +- .../mbrc/constants/ApplicationEvents.kt | 8 - .../java/com/kelsos/mbrc/constants/Const.kt | 13 - .../mbrc/constants/ProtocolEventType.kt | 12 - .../mbrc/constants/UserInputEventType.kt | 12 - .../main/java/com/kelsos/mbrc/data/Data.kt | 3 - .../java/com/kelsos/mbrc/data/Database.kt | 122 ++-- .../mbrc/data/DeserializationAdapter.kt | 50 ++ .../kelsos/mbrc/data/SerializationAdapter.kt | 17 + .../java/com/kelsos/mbrc/data/UserAction.kt | 15 - .../events/DefaultSettingsChangedEvent.kt | 7 - .../com/kelsos/mbrc/events/MessageEvent.kt | 24 - .../java/com/kelsos/mbrc/events/bus/RxBus.kt | 28 - .../com/kelsos/mbrc/events/bus/RxBusImpl.kt | 78 --- .../events/ui/ConnectionSettingsChanged.kt | 9 - .../events/ui/ConnectionStatusChangeEvent.kt | 13 - .../mbrc/events/ui/CoverChangedEvent.kt | 5 - .../kelsos/mbrc/events/ui/DiscoveryStopped.kt | 7 - .../kelsos/mbrc/events/ui/LfmRatingChanged.kt | 7 - .../events/ui/LibraryRefreshCompleteEvent.kt | 3 - .../mbrc/events/ui/LyricsUpdatedEvent.kt | 5 - .../com/kelsos/mbrc/events/ui/NotifyUser.kt | 20 - .../kelsos/mbrc/events/ui/PlayStateChange.kt | 8 - .../kelsos/mbrc/events/ui/RatingChanged.kt | 5 - .../mbrc/events/ui/RemoteClientMetaData.kt | 9 - .../com/kelsos/mbrc/events/ui/RepeatChange.kt | 7 - .../events/ui/RequestConnectionStateEvent.kt | 3 - .../kelsos/mbrc/events/ui/ScrobbleChange.kt | 5 - .../kelsos/mbrc/events/ui/ShuffleChange.kt | 17 - .../mbrc/events/ui/TrackInfoChangeEvent.kt | 7 - .../com/kelsos/mbrc/events/ui/TrackMoved.kt | 11 - .../com/kelsos/mbrc/events/ui/TrackRemoval.kt | 10 - .../kelsos/mbrc/events/ui/UpdateDuration.kt | 6 - .../com/kelsos/mbrc/events/ui/VolumeChange.kt | 18 - .../com/kelsos/mbrc/extensions/EnableHome.kt | 9 - .../kelsos/mbrc/extensions/FileExtensions.kt | 29 - .../mbrc/extensions/StringExtensions.kt | 3 - .../features/dragsort/OnStartDragListener.kt | 2 + .../dragsort/SimpleItemTouchHelper.kt | 6 +- .../mbrc/features/help/FeedbackFragment.kt | 85 +-- .../mbrc/features/help/FeedbackUiMessage.kt | 16 + .../mbrc/features/help/FeedbackViewModel.kt | 39 ++ .../features/help/HelpFeedbackActivity.kt | 4 +- .../kelsos/mbrc/features/help/HelpFragment.kt | 14 +- .../com/kelsos/mbrc/features/library/Album.kt | 35 -- .../features/library/AlbumEntryAdapter.kt | 121 ---- .../mbrc/features/library/AlbumRepository.kt | 10 - .../features/library/AlbumRepositoryImpl.kt | 63 -- .../features/library/AlbumTracksActivity.kt | 163 ----- .../features/library/AlbumTracksContract.kt | 29 - .../library/AlbumTracksPresenterImpl.kt | 57 -- .../kelsos/mbrc/features/library/Artist.kt | 26 - .../features/library/ArtistAlbumsActivity.kt | 135 ----- .../features/library/ArtistAlbumsContract.kt | 24 - .../library/ArtistAlbumsPresenterImpl.kt | 34 -- .../features/library/ArtistEntryAdapter.kt | 97 --- .../mbrc/features/library/ArtistRepository.kt | 12 - .../features/library/ArtistRepositoryImpl.kt | 49 -- .../features/library/ArtistTabRefreshEvent.kt | 3 - .../features/library/BaseBrowseFragment.kt | 98 +++ .../features/library/BaseDetailsActivity.kt | 97 +++ .../features/library/BaseLibraryViewModel.kt | 25 + .../features/library/BrowseAlbumContract.kt | 28 - .../features/library/BrowseAlbumFragment.kt | 130 ---- .../library/BrowseAlbumPresenterImpl.kt | 63 -- .../features/library/BrowseArtistContract.kt | 27 - .../features/library/BrowseArtistFragment.kt | 122 ---- .../library/BrowseArtistPresenterImpl.kt | 78 --- .../features/library/BrowseGenreContract.kt | 28 - .../features/library/BrowseGenreFragment.kt | 127 ---- .../library/BrowseGenrePresenterImpl.kt | 70 --- .../features/library/BrowseTrackContract.kt | 28 - .../features/library/BrowseTrackFragment.kt | 122 ---- .../library/BrowseTrackPresenterImpl.kt | 74 --- .../mbrc/features/library/CachedAlbumInfo.kt | 6 - .../com/kelsos/mbrc/features/library/Cover.kt | 12 +- .../mbrc/features/library/CoverCache.kt | 146 +++-- .../kelsos/mbrc/features/library/CoverInfo.kt | 14 - .../kelsos/mbrc/features/library/Extras.kt | 29 + .../com/kelsos/mbrc/features/library/Genre.kt | 30 - .../features/library/GenreArtistsActivity.kt | 129 ---- .../features/library/GenreArtistsContract.kt | 24 - .../library/GenreArtistsPresenterImpl.kt | 34 -- .../features/library/GenreEntryAdapter.kt | 95 --- .../mbrc/features/library/GenreRepository.kt | 5 - .../features/library/GenreRepositoryImpl.kt | 41 -- .../com/kelsos/mbrc/features/library/Key.kt | 5 +- .../mbrc/features/library/LibraryActivity.kt | 110 ++-- .../mbrc/features/library/LibraryContract.kt | 30 - .../features/library/LibraryPagerAdapter.kt | 15 +- .../features/library/LibraryPresenterImpl.kt | 82 --- .../features/library/LibrarySyncUseCase.kt | 2 +- .../library/LibrarySyncUseCaseImpl.kt | 12 +- .../mbrc/features/library/LibraryUiEvent.kt | 21 + .../mbrc/features/library/LibraryViewModel.kt | 55 ++ .../features/library/LocalAlbumDataSource.kt | 130 ---- .../features/library/LocalArtistDataSource.kt | 10 - .../library/LocalArtistDataSourceImpl.kt | 102 ---- .../features/library/LocalGenreDataSource.kt | 73 --- .../features/library/LocalTrackDataSource.kt | 178 ------ .../mbrc/features/library/PagePosition.kt | 8 + .../kelsos/mbrc/features/library/PopupMenu.kt | 19 + .../features/library/RemoteAlbumDataSource.kt | 12 - .../library/RemoteArtistDataSource.kt | 12 - .../features/library/RemoteGenreDataSource.kt | 12 - .../features/library/RemoteTrackDataSource.kt | 12 - .../com/kelsos/mbrc/features/library/Track.kt | 51 -- .../features/library/TrackEntryAdapter.kt | 125 ---- .../mbrc/features/library/TrackRepository.kt | 24 - .../features/library/TrackRepositoryImpl.kt | 63 -- .../mbrc/features/library/albums/Actions.kt | 31 + .../mbrc/features/library/albums/Album.kt | 46 ++ .../features/library/albums/AlbumCover.kt | 19 + .../mbrc/features/library/albums/AlbumDao.kt | 66 ++ .../library/albums/AlbumEntryAdapter.kt | 105 ++++ .../library/{ => albums}/AlbumInfo.kt | 6 +- .../library/albums/AlbumRepository.kt | 96 +++ .../features/library/albums/AlbumUiMessage.kt | 15 + .../library/albums/ArtistAlbumsActivity.kt | 82 +++ .../library/albums/ArtistAlbumsViewModel.kt | 30 + .../library/albums/BaseAlbumViewModel.kt | 40 ++ .../library/albums/BrowseAlbumFragment.kt | 73 +++ .../library/albums/BrowseAlbumViewModel.kt | 39 ++ .../mbrc/features/library/albums/Mappers.kt | 25 + .../mbrc/features/library/artists/Actions.kt | 30 + .../mbrc/features/library/artists/Artist.kt | 35 ++ .../features/library/artists/ArtistDao.kt | 54 ++ .../library/artists/ArtistEntryAdapter.kt | 80 +++ .../library/artists/ArtistRepository.kt | 77 +++ .../library/artists/ArtistUiMessage.kt | 15 + .../library/artists/BaseArtistViewModel.kt | 40 ++ .../library/artists/BrowseArtistFragment.kt | 73 +++ .../library/artists/BrowseArtistViewModel.kt | 55 ++ .../library/artists/GenreArtistsActivity.kt | 83 +++ .../library/artists/GenreArtistsViewModel.kt | 31 + .../mbrc/features/library/artists/Mappers.kt | 19 + .../mbrc/features/library/genres/Actions.kt | 30 + .../library/genres/BrowseGenreFragment.kt | 74 +++ .../library/genres/BrowseGenreViewModel.kt | 60 ++ .../mbrc/features/library/genres/Genre.kt | 35 ++ .../mbrc/features/library/genres/GenreDao.kt | 38 ++ .../library/genres/GenreEntryAdapter.kt | 73 +++ .../library/genres/GenreRepository.kt | 77 +++ .../features/library/genres/GenreUiMessage.kt | 15 + .../mbrc/features/library/genres/Mappers.kt | 19 + .../mbrc/features/library/tracks/Actions.kt | 15 + .../library/tracks/AlbumTracksActivity.kt | 121 ++++ .../library/tracks/AlbumTracksViewModel.kt | 45 ++ .../library/tracks/BaseTrackViewModel.kt | 42 ++ .../library/tracks/BrowseTrackFragment.kt | 74 +++ .../library/tracks/BrowseTrackViewModel.kt | 35 ++ .../mbrc/features/library/tracks/Mappers.kt | 37 ++ .../mbrc/features/library/tracks/Track.kt | 74 +++ .../mbrc/features/library/tracks/TrackDao.kt | 96 +++ .../library/tracks/TrackEntryAdapter.kt | 105 ++++ .../library/tracks/TrackRepository.kt | 39 ++ .../library/tracks/TrackRepositoryImpl.kt | 93 +++ .../features/library/tracks/TrackUiMessage.kt | 11 + .../mbrc/features/lyrics/LyricsActivity.kt | 52 +- .../mbrc/features/lyrics/LyricsAdapter.kt | 37 +- .../mbrc/features/lyrics/LyricsContract.kt | 14 - .../mbrc/features/lyrics/LyricsModel.kt | 31 - .../mbrc/features/lyrics/LyricsPayload.kt | 10 +- .../features/lyrics/LyricsPresenterImpl.kt | 50 -- .../mbrc/features/lyrics/LyricsViewModel.kt | 11 + .../features/minicontrol/MiniControlAction.kt | 9 + .../minicontrol/MiniControlContract.kt | 26 - .../minicontrol/MiniControlFragment.kt | 97 ++- .../minicontrol/MiniControlPresenterImpl.kt | 59 -- .../minicontrol/MiniControlViewModel.kt | 51 ++ .../nowplaying/LocalNowPlayingDataSource.kt | 88 --- .../mbrc/features/nowplaying/Mappers.kt | 28 + .../mbrc/features/nowplaying/MoveManager.kt | 12 + .../features/nowplaying/MoveManagerImpl.kt | 31 + .../mbrc/features/nowplaying/NowPlaying.kt | 81 ++- .../features/nowplaying/NowPlayingActivity.kt | 202 ++++--- .../features/nowplaying/NowPlayingAdapter.kt | 207 ++++--- .../features/nowplaying/NowPlayingContract.kt | 38 -- .../mbrc/features/nowplaying/NowPlayingDao.kt | 128 ++++ .../nowplaying/NowPlayingPresenterImpl.kt | 84 --- .../nowplaying/NowPlayingRepository.kt | 102 +++- .../nowplaying/NowPlayingRepositoryImpl.kt | 40 -- .../nowplaying/NowPlayingUiMessages.kt | 11 + .../nowplaying/NowPlayingViewModel.kt | 92 +++ .../nowplaying/RemoteNowPlayingDataSource.kt | 12 - .../mbrc/features/nowplaying/VisibleRange.kt | 13 + .../mbrc/features/output/OutputApiImpl.kt | 4 +- .../mbrc/features/output/OutputResponse.kt | 12 +- .../output/OutputSelectionViewModel.kt | 19 +- .../mbrc/features/player/CoverPayload.kt | 10 +- .../kelsos/mbrc/features/player/IsEmpty.kt | 3 - .../kelsos/mbrc/features/player/LfmStatus.kt | 7 - .../mbrc/features/player/ModelInitializer.kt | 47 -- .../mbrc/features/player/PlayerAction.kt | 29 + .../mbrc/features/player/PlayerActivity.kt | 441 +++++--------- .../mbrc/features/player/PlayerContract.kt | 76 --- .../features/player/PlayerStateSerializer.kt | 26 - .../mbrc/features/player/PlayerUiMessage.kt | 7 + .../mbrc/features/player/PlayerViewModel.kt | 111 ++++ .../player/PlayerViewPresenterImpl.kt | 204 ------- .../features/player/ProgressSeekerHelper.kt | 63 -- .../features/player/RatingDialogFragment.kt | 50 +- .../features/player/RatingDialogViewModel.kt | 40 ++ .../mbrc/features/player/SeekBarListener.kt | 30 + .../mbrc/features/player/SeekBarThrottler.kt | 50 -- .../kelsos/mbrc/features/player/TrackInfo.kt | 43 -- .../playlists/LocalPlaylistDataSource.kt | 74 --- .../kelsos/mbrc/features/playlists/Mappers.kt | 24 + .../mbrc/features/playlists/Playlist.kt | 56 +- .../features/playlists/PlaylistActivity.kt | 122 ++-- .../features/playlists/PlaylistAdapter.kt | 48 +- .../features/playlists/PlaylistContract.kt | 19 - .../mbrc/features/playlists/PlaylistDao.kt | 31 + .../playlists/PlaylistPresenterImpl.kt | 38 -- .../features/playlists/PlaylistRepository.kt | 52 ++ .../playlists/PlaylistRepositoryImpl.kt | 40 -- .../features/playlists/PlaylistViewModel.kt | 72 +++ .../playlists/RemotePlaylistDataSource.kt | 12 - .../kelsos/mbrc/features/queue/AlbumMapper.kt | 6 +- .../mbrc/features/queue/PopupActionHandler.kt | 133 ---- .../com/kelsos/mbrc/features/queue/Queue.kt | 41 ++ .../mbrc/features/queue/QueueHandler.kt | 94 +-- .../mbrc/features/queue/QueuePayload.kt | 14 +- .../mbrc/features/queue/QueueResponse.kt | 6 +- .../features/radio/LocalRadioDataSource.kt | 81 --- .../com/kelsos/mbrc/features/radio/Mappers.kt | 20 + .../mbrc/features/radio/RadioActivity.kt | 97 +-- .../mbrc/features/radio/RadioAdapter.kt | 49 +- .../mbrc/features/radio/RadioContract.kt | 27 - .../mbrc/features/radio/RadioPresenterImpl.kt | 48 -- .../mbrc/features/radio/RadioRepository.kt | 62 ++ .../features/radio/RadioRepositoryImpl.kt | 40 -- .../mbrc/features/radio/RadioStation.kt | 59 +- .../mbrc/features/radio/RadioStationDao.kt | 31 + .../mbrc/features/radio/RadioViewModel.kt | 75 +++ .../features/radio/RemoteRadioDataSource.kt | 12 - .../mbrc/features/settings/CallAction.kt | 28 + .../settings/ClientInformationStore.kt | 29 + .../features/settings/ConnectionAdapter.kt | 87 ++- .../mbrc/features/settings/ConnectionDao.kt | 64 ++ .../settings/ConnectionManagerActivity.kt | 124 ++-- .../settings/ConnectionManagerPresenter.kt | 13 - .../ConnectionManagerPresenterImpl.kt | 64 -- .../settings/ConnectionManagerView.kt | 11 - .../settings/ConnectionManagerViewModel.kt | 77 +++ .../mbrc/features/settings/ConnectionModel.kt | 6 - .../features/settings/ConnectionRepository.kt | 14 +- .../settings/ConnectionRepositoryImpl.kt | 161 +++-- .../features/settings/ConnectionSettings.kt | 57 +- .../features/settings/SettingsActivity.kt | 6 +- .../settings/SettingsDialogFragment.kt | 19 +- .../features/settings/SettingsFragment.kt | 21 +- .../features/settings/SettingsManagerImpl.kt | 65 +- .../mbrc/features/settings/WebViewDialog.kt | 5 +- .../mbrc/features/widgets/BundleData.kt | 10 +- .../features/widgets/RemoteViewsTarget.kt | 12 +- .../mbrc/features/widgets/WidgetBase.kt | 8 +- .../mbrc/features/widgets/WidgetNormal.kt | 4 +- .../mbrc/features/widgets/WidgetSmall.kt | 4 +- .../mbrc/features/widgets/WidgetUpdater.kt | 25 +- .../java/com/kelsos/mbrc/logging/LogHelper.kt | 127 ++-- .../mbrc/networking/ActiveConnection.kt | 5 +- .../com/kelsos/mbrc/networking/ApiBase.kt | 65 +- .../com/kelsos/mbrc/networking/ApiStatus.kt | 6 + .../networking/ClientConnectionUseCase.kt | 18 + .../mbrc/networking/RequestManagerImpl.kt | 103 ++-- .../mbrc/networking/SocketActivityChecker.kt | 76 ++- .../client/ClientConnectionManager.kt | 237 ++++++++ .../networking/client/ConnectivityVerifier.kt | 45 ++ .../client/ConnectivityVerifierImpl.kt | 39 -- .../networking/client/GenericSocketMessage.kt | 12 +- .../mbrc/networking/client/MessageHandler.kt | 168 ++++++ .../mbrc/networking/client/MessageQueue.kt | 25 + .../client/PluginUpdateCheckUseCase.kt | 155 +++++ .../mbrc/networking/client/SocketMessage.kt | 39 +- .../mbrc/networking/client/SocketService.kt | 239 -------- .../mbrc/networking/client/UiMessageQueue.kt | 28 + .../connections/ConnectionMapper.kt | 19 +- .../connections/InetAddressMapper.kt | 4 +- .../networking/discovery/DiscoveryMessage.kt | 31 +- .../networking/discovery/DiscoveryStop.kt | 14 +- .../discovery/RemoteServiceDiscovery.kt | 279 ++++----- .../networking/protocol/CommandFactory.kt | 49 ++ .../protocol/CommandRegistration.kt | 145 ----- .../mbrc/networking/protocol/MessageEvent.kt | 8 + .../protocol/NowPlayingMoveRequest.kt | 10 +- .../protocol/NowPlayingMoveResponse.kt | 14 + .../protocol/NowPlayingTrackRemoveResponse.kt | 12 + .../kelsos/mbrc/networking/protocol/Page.kt | 16 +- .../mbrc/networking/protocol/PageRange.kt | 12 +- .../mbrc/networking/protocol/Position.kt | 12 + .../mbrc/networking/protocol/Protocol.kt | 159 ++++- .../networking/protocol/ProtocolAction.kt | 2 +- .../networking/protocol/ProtocolActions.kt | 567 +++++++++--------- .../networking/protocol/ProtocolHandler.kt | 78 --- .../networking/protocol/ProtocolMessage.kt | 4 +- .../networking/protocol/ProtocolPayload.kt | 18 +- .../networking/protocol/RemoteController.kt | 72 --- .../mbrc/networking/protocol/UserAction.kt | 17 + .../networking/protocol/UserActionUseCase.kt | 49 ++ .../protocol/UserActionUseCaseImpl.kt | 26 + .../protocol/VolumeModifyUseCase.kt | 21 + .../protocol/VolumeModifyUseCaseImpl.kt | 81 +++ .../mbrc/platform/RemoteBroadcastReceiver.kt | 35 +- .../com/kelsos/mbrc/platform/RemoteService.kt | 109 +--- .../AppNotificationManagerImpl.kt | 217 +++++++ .../mediasession/MediaIntentHandler.kt | 27 +- .../platform/mediasession/NotificationData.kt | 11 + .../mediasession/NotificationModel.kt | 13 - .../mediasession/RemoteSessionManager.kt | 159 ++--- .../mediasession/RemoteVolumeProvider.kt | 66 +- .../SessionNotificationManager.kt | 180 ------ .../main/res/layout/activity_album_tracks.xml | 75 ++- .../res/layout/activity_artist_albums.xml | 4 +- .../res/layout/activity_genre_artists.xml | 2 +- app/src/main/res/layout/empty_list.xml | 5 +- .../layout/ui_activity_connection_manager.xml | 8 +- .../main/res/layout/ui_list_track_item.xml | 3 + app/src/main/res/values/strings.xml | 5 + .../library/LibrarySyncUseCaseImplTest.kt | 59 +- .../features/radio/RadioPresenterImplTest.kt | 287 --------- .../settings/ConnectionRepositoryTest.kt | 326 +++++----- .../client/ConnectivityVerifierImplTest.kt | 130 ++-- .../com/kelsos/mbrc/rules/DBFlowTestRule.kt | 32 - .../java/com/kelsos/mbrc/utils/TestModules.kt | 31 + build.gradle.kts | 1 - .../kelsos/mbrc/changelog/ChangelogParser.kt | 4 +- gradle/libs.versions.toml | 73 +-- 386 files changed, 9691 insertions(+), 10138 deletions(-) create mode 100644 app/schemas/com.kelsos.mbrc.data.Database/3.json delete mode 100644 app/src/main/java/com/kelsos/mbrc/annotations/Connection.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/annotations/PlayerState.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/annotations/Queue.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/annotations/Repeat.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/annotations/Search.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/annotations/SocketAction.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/commands/CancelNotificationCommand.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/commands/ConnectionStatusChangedCommand.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/commands/HandleHandshake.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/commands/InitiateConnectionCommand.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/commands/KeyVolumeDownCommand.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/commands/KeyVolumeUpCommand.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/commands/ProcessUserAction.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/commands/ProtocolRequest.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/commands/ReduceVolumeOnRingCommand.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/commands/RestartConnectionCommand.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/commands/SocketDataAvailableCommand.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/commands/StartDiscoveryCommand.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/commands/TerminateConnectionCommand.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/commands/TerminateServiceCommand.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/commands/VersionCheckCommand.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/commands/visual/HandshakeCompletionActions.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/commands/visual/NotifyNotAllowedCommand.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/common/data/LocalDataSource.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/common/data/RemoteDataSource.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/common/mvp/BasePresenter.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/common/mvp/BaseView.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/common/mvp/Presenter.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/common/mvvm/BaseViewModel.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/common/state/AppState.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/common/state/AppStateManager.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/common/state/ConnectionModel.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/common/state/ConnectionState.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/common/state/ConnectionStatus.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/common/state/LfmRating.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/common/state/MainDataModel.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/common/state/NowPlayingTrack.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/common/state/PlayerState.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/common/state/PlayerStatus.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/common/state/PlayerStatusModel.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/common/state/PlayingPosition.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/common/state/PlayingTrack.kt rename app/src/main/java/com/kelsos/mbrc/{features/player/ModelCache.kt => common/state/PlayingTrackCache.kt} (58%) create mode 100644 app/src/main/java/com/kelsos/mbrc/common/state/Repeat.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/common/state/ShuffleMode.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/common/state/TrackRating.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/constants/ApplicationEvents.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/constants/Const.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/constants/ProtocolEventType.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/constants/UserInputEventType.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/data/Data.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/data/DeserializationAdapter.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/data/SerializationAdapter.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/data/UserAction.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/events/DefaultSettingsChangedEvent.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/events/MessageEvent.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/events/bus/RxBus.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/events/bus/RxBusImpl.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/events/ui/ConnectionSettingsChanged.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/events/ui/ConnectionStatusChangeEvent.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/events/ui/CoverChangedEvent.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/events/ui/DiscoveryStopped.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/events/ui/LfmRatingChanged.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/events/ui/LibraryRefreshCompleteEvent.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/events/ui/LyricsUpdatedEvent.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/events/ui/NotifyUser.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/events/ui/PlayStateChange.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/events/ui/RatingChanged.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/events/ui/RemoteClientMetaData.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/events/ui/RepeatChange.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/events/ui/RequestConnectionStateEvent.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/events/ui/ScrobbleChange.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/events/ui/ShuffleChange.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/events/ui/TrackInfoChangeEvent.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/events/ui/TrackMoved.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/events/ui/TrackRemoval.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/events/ui/UpdateDuration.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/events/ui/VolumeChange.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/extensions/EnableHome.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/extensions/FileExtensions.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/extensions/StringExtensions.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/help/FeedbackUiMessage.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/help/FeedbackViewModel.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/Album.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/AlbumEntryAdapter.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/AlbumRepository.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/AlbumRepositoryImpl.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/AlbumTracksActivity.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/AlbumTracksContract.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/AlbumTracksPresenterImpl.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/Artist.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/ArtistAlbumsActivity.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/ArtistAlbumsContract.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/ArtistAlbumsPresenterImpl.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/ArtistEntryAdapter.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/ArtistRepository.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/ArtistRepositoryImpl.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/ArtistTabRefreshEvent.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/BaseBrowseFragment.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/BaseDetailsActivity.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/BaseLibraryViewModel.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/BrowseAlbumContract.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/BrowseAlbumFragment.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/BrowseAlbumPresenterImpl.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/BrowseArtistContract.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/BrowseArtistFragment.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/BrowseArtistPresenterImpl.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/BrowseGenreContract.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/BrowseGenreFragment.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/BrowseGenrePresenterImpl.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/BrowseTrackContract.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/BrowseTrackFragment.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/BrowseTrackPresenterImpl.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/CachedAlbumInfo.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/CoverInfo.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/Extras.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/Genre.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/GenreArtistsActivity.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/GenreArtistsContract.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/GenreArtistsPresenterImpl.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/GenreEntryAdapter.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/GenreRepository.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/GenreRepositoryImpl.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/LibraryContract.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/LibraryPresenterImpl.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/LibraryUiEvent.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/LibraryViewModel.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/LocalAlbumDataSource.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/LocalArtistDataSource.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/LocalArtistDataSourceImpl.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/LocalGenreDataSource.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/LocalTrackDataSource.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/PagePosition.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/PopupMenu.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/RemoteAlbumDataSource.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/RemoteArtistDataSource.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/RemoteGenreDataSource.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/RemoteTrackDataSource.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/Track.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/TrackEntryAdapter.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/TrackRepository.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/TrackRepositoryImpl.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/albums/Actions.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/albums/Album.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/albums/AlbumCover.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/albums/AlbumDao.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/albums/AlbumEntryAdapter.kt rename app/src/main/java/com/kelsos/mbrc/features/library/{ => albums}/AlbumInfo.kt (86%) create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/albums/AlbumRepository.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/albums/AlbumUiMessage.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/albums/ArtistAlbumsActivity.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/albums/ArtistAlbumsViewModel.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/albums/BaseAlbumViewModel.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/albums/BrowseAlbumFragment.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/albums/BrowseAlbumViewModel.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/albums/Mappers.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/artists/Actions.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/artists/Artist.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/artists/ArtistDao.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/artists/ArtistEntryAdapter.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/artists/ArtistRepository.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/artists/ArtistUiMessage.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/artists/BaseArtistViewModel.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/artists/BrowseArtistFragment.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/artists/BrowseArtistViewModel.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/artists/GenreArtistsActivity.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/artists/GenreArtistsViewModel.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/artists/Mappers.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/genres/Actions.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/genres/BrowseGenreFragment.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/genres/BrowseGenreViewModel.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/genres/Genre.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/genres/GenreDao.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/genres/GenreEntryAdapter.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/genres/GenreRepository.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/genres/GenreUiMessage.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/genres/Mappers.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/tracks/Actions.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/tracks/AlbumTracksActivity.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/tracks/AlbumTracksViewModel.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/tracks/BaseTrackViewModel.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/tracks/BrowseTrackFragment.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/tracks/BrowseTrackViewModel.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/tracks/Mappers.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/tracks/Track.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/tracks/TrackDao.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/tracks/TrackEntryAdapter.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/tracks/TrackRepository.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/tracks/TrackRepositoryImpl.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/library/tracks/TrackUiMessage.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/lyrics/LyricsContract.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/lyrics/LyricsModel.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/lyrics/LyricsPresenterImpl.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/lyrics/LyricsViewModel.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/minicontrol/MiniControlAction.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/minicontrol/MiniControlContract.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/minicontrol/MiniControlPresenterImpl.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/minicontrol/MiniControlViewModel.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/nowplaying/LocalNowPlayingDataSource.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/nowplaying/Mappers.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/nowplaying/MoveManager.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/nowplaying/MoveManagerImpl.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/nowplaying/NowPlayingContract.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/nowplaying/NowPlayingDao.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/nowplaying/NowPlayingPresenterImpl.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/nowplaying/NowPlayingRepositoryImpl.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/nowplaying/NowPlayingUiMessages.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/nowplaying/NowPlayingViewModel.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/nowplaying/RemoteNowPlayingDataSource.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/nowplaying/VisibleRange.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/player/IsEmpty.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/player/LfmStatus.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/player/ModelInitializer.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/player/PlayerAction.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/player/PlayerContract.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/player/PlayerStateSerializer.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/player/PlayerUiMessage.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/player/PlayerViewModel.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/player/PlayerViewPresenterImpl.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/player/ProgressSeekerHelper.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/player/RatingDialogViewModel.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/player/SeekBarListener.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/player/SeekBarThrottler.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/player/TrackInfo.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/playlists/LocalPlaylistDataSource.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/playlists/Mappers.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/playlists/PlaylistContract.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/playlists/PlaylistDao.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/playlists/PlaylistPresenterImpl.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/playlists/PlaylistRepositoryImpl.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/playlists/PlaylistViewModel.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/playlists/RemotePlaylistDataSource.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/queue/PopupActionHandler.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/queue/Queue.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/radio/LocalRadioDataSource.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/radio/Mappers.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/radio/RadioContract.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/radio/RadioPresenterImpl.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/radio/RadioRepositoryImpl.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/radio/RadioStationDao.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/radio/RadioViewModel.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/radio/RemoteRadioDataSource.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/settings/CallAction.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/settings/ClientInformationStore.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/settings/ConnectionDao.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/settings/ConnectionManagerPresenter.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/settings/ConnectionManagerPresenterImpl.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/settings/ConnectionManagerView.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/features/settings/ConnectionManagerViewModel.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/features/settings/ConnectionModel.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/networking/ApiStatus.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/networking/ClientConnectionUseCase.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/networking/client/ClientConnectionManager.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/networking/client/ConnectivityVerifierImpl.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/networking/client/MessageHandler.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/networking/client/MessageQueue.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/networking/client/PluginUpdateCheckUseCase.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/networking/client/SocketService.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/networking/client/UiMessageQueue.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/networking/protocol/CommandFactory.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/networking/protocol/CommandRegistration.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/networking/protocol/MessageEvent.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/networking/protocol/NowPlayingMoveResponse.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/networking/protocol/NowPlayingTrackRemoveResponse.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/networking/protocol/Position.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/networking/protocol/ProtocolHandler.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/networking/protocol/RemoteController.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/networking/protocol/UserAction.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/networking/protocol/UserActionUseCase.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/networking/protocol/UserActionUseCaseImpl.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/networking/protocol/VolumeModifyUseCase.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/networking/protocol/VolumeModifyUseCaseImpl.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/platform/mediasession/AppNotificationManagerImpl.kt create mode 100644 app/src/main/java/com/kelsos/mbrc/platform/mediasession/NotificationData.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/platform/mediasession/NotificationModel.kt delete mode 100644 app/src/main/java/com/kelsos/mbrc/platform/mediasession/SessionNotificationManager.kt delete mode 100644 app/src/test/java/com/kelsos/mbrc/features/radio/RadioPresenterImplTest.kt delete mode 100644 app/src/test/java/com/kelsos/mbrc/rules/DBFlowTestRule.kt create mode 100644 app/src/test/java/com/kelsos/mbrc/utils/TestModules.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e71f2f497..7f29552bc 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -11,7 +11,6 @@ plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlinAndroid) alias(libs.plugins.kotlinParcelize) - alias(libs.plugins.kapt) alias(libs.plugins.ksp) alias(libs.plugins.protobuf) alias(libs.plugins.googleServices) apply false @@ -98,6 +97,10 @@ android { buildConfigField("String", "BUILD_TIME", "\"${buildTime()}\"") testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + + ksp { + arg("room.schemaLocation", "$projectDir/schemas") + } } testOptions { @@ -191,6 +194,13 @@ android { } } + +detekt { + source.setFrom(files("src/main/java", "src/main/kotlin")) + config.setFrom(files(rootProject.file("config/detekt/detekt.yml"))) + buildUponDefaultConfig = true +} + val dummyGoogleServicesJson: Configuration by configurations.creating { isCanBeResolved = true isCanBeConsumed = false @@ -205,18 +215,6 @@ dependencies { implementation(projects.changelog) - testImplementation(libs.androidx.arch.core.testing) - testImplementation(libs.androidx.test.core) - testImplementation(libs.androidx.test.runner) - testImplementation(libs.androidx.test.junit) - testImplementation(libs.androidx.test.truth) - testImplementation(libs.bundles.androidx.test.espresso) - testImplementation(libs.truth) - testImplementation(libs.koin.test) - testImplementation(libs.kotlin.coroutines.test) - testImplementation(libs.mockk) - testImplementation(libs.robolectric) - implementation(libs.androidx.annotation) implementation(libs.androidx.appcompat) implementation(libs.androidx.core.ktx) @@ -233,23 +231,34 @@ dependencies { implementation(libs.androidx.legacy.support.v4) implementation(libs.androidx.legacy.support.v13) implementation(libs.bundles.coroutines) + implementation(libs.bundles.androidx.room) implementation(libs.bundles.coil) implementation(libs.bundles.koin) implementation(libs.google.material) implementation(libs.google.protobuf.javalite) + implementation(libs.squareup.moshi.lib) implementation(libs.squareup.okio) + implementation(libs.squareup.okhttp) implementation(libs.timber) - implementation(libs.bundles.dbflow) - implementation(libs.bundles.jackson) - implementation(libs.kotlin.stdlib) - implementation(libs.kotlin.reflect) - implementation(libs.rxandroid) - implementation(libs.rxjava) - implementation(libs.rxkotlin) - implementation(libs.rxrelay) + ksp(libs.androidx.room.compiler) + ksp(libs.squareup.moshi.codegen) - kapt(libs.dbflow.processor) + testImplementation(libs.androidx.arch.core.testing) + testImplementation(libs.androidx.room.testing) + testImplementation(libs.androidx.test.core) + testImplementation(libs.androidx.test.runner) + testImplementation(libs.androidx.test.junit) + testImplementation(libs.androidx.test.truth) + testImplementation(libs.bundles.androidx.test.espresso) + testImplementation(libs.androidx.paging.common.ktx) + testImplementation(libs.androidx.paging.testing) + testImplementation(libs.turbine) + testImplementation(libs.truth) + testImplementation(libs.koin.test) + testImplementation(libs.kotlin.coroutines.test) + testImplementation(libs.mockk) + testImplementation(libs.robolectric) debugImplementation(libs.squareup.leakcanary) debugImplementation(libs.androidx.fragment.testing) @@ -365,7 +374,8 @@ tasks { doLast { if (!project.file("google-services.json").exists()) { throw GradleException( - "You need a google-services.json file to run this project. Please refer to the CONTRIBUTING.md file for details." + "You need a google-services.json file to run this project." + + " Please refer to the CONTRIBUTING.md file for details." ) } } @@ -379,12 +389,6 @@ tasks { } } -kotlin { - sourceSets.all { - languageSettings.enableLanguageFeature("ExplicitBackingFields") - } -} - configurations.all { resolutionStrategy { force("com.google.code.findbugs:jsr305:3.0.2") diff --git a/app/schemas/com.kelsos.mbrc.data.Database/3.json b/app/schemas/com.kelsos.mbrc.data.Database/3.json new file mode 100644 index 000000000..db36f22cb --- /dev/null +++ b/app/schemas/com.kelsos.mbrc.data.Database/3.json @@ -0,0 +1,378 @@ +{ + "formatVersion": 1, + "database": { + "version": 3, + "identityHash": "b169004076611f9b3ad422f67163745c", + "entities": [ + { + "tableName": "genre", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`genre` TEXT, `count` INTEGER, `date_added` INTEGER, `id` INTEGER PRIMARY KEY AUTOINCREMENT)", + "fields": [ + { + "fieldPath": "genre", + "columnName": "genre", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "count", + "columnName": "count", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "dateAdded", + "columnName": "date_added", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "artist", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`artist` TEXT, `count` INTEGER, `date_added` INTEGER, `id` INTEGER PRIMARY KEY AUTOINCREMENT)", + "fields": [ + { + "fieldPath": "artist", + "columnName": "artist", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "count", + "columnName": "count", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "dateAdded", + "columnName": "date_added", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "album", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`artist` TEXT, `album` TEXT, `cover` TEXT, `date_added` INTEGER, `count` INTEGER, `id` INTEGER PRIMARY KEY AUTOINCREMENT)", + "fields": [ + { + "fieldPath": "artist", + "columnName": "artist", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "album", + "columnName": "album", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "cover", + "columnName": "cover", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "dateAdded", + "columnName": "date_added", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "count", + "columnName": "count", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "track", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`artist` TEXT, `title` TEXT, `src` TEXT, `trackno` INTEGER, `disc` INTEGER, `album_artist` TEXT, `album` TEXT, `genre` TEXT, `date_added` INTEGER, `id` INTEGER PRIMARY KEY AUTOINCREMENT)", + "fields": [ + { + "fieldPath": "artist", + "columnName": "artist", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "src", + "columnName": "src", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "trackno", + "columnName": "trackno", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "disc", + "columnName": "disc", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "albumArtist", + "columnName": "album_artist", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "album", + "columnName": "album", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "genre", + "columnName": "genre", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "dateAdded", + "columnName": "date_added", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "now_playing", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`title` TEXT, `artist` TEXT, `path` TEXT, `position` INTEGER, `date_added` INTEGER, `id` INTEGER PRIMARY KEY AUTOINCREMENT)", + "fields": [ + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "artist", + "columnName": "artist", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "dateAdded", + "columnName": "date_added", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "playlists", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT, `url` TEXT, `date_added` INTEGER, `id` INTEGER PRIMARY KEY AUTOINCREMENT)", + "fields": [ + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "dateAdded", + "columnName": "date_added", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "radio_station", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT, `url` TEXT, `date_added` INTEGER, `id` INTEGER PRIMARY KEY AUTOINCREMENT)", + "fields": [ + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "dateAdded", + "columnName": "date_added", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "settings", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`address` TEXT, `port` INTEGER, `name` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT)", + "fields": [ + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "port", + "columnName": "port", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'b169004076611f9b3ad422f67163745c')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8e7ec56eb..bdb498d80 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -53,11 +53,11 @@ android:value=".features.player.PlayerActivity" /> - + - + - + + val threadName = if (threads == 1) "${name}Dispatcher" else "${name}Dispatcher-worker-${threadId++}" + Thread(runnable, threadName) + }.asCoroutineDispatcher() +} + val appModule = module { - singleOf(::RxBusImpl) { bind() } - single { ObjectMapper().registerKotlinModule() } - + single { Moshi.Builder().build() } singleOf(::LibrarySyncUseCaseImpl) { bind() } singleOf(::ApiBase) singleOf(::RequestManagerImpl) { bind() } @@ -186,7 +175,6 @@ val appModule = singleOf(::GenreRepositoryImpl) { bind() } singleOf(::ArtistRepositoryImpl) { bind() } - singleOf(::LocalArtistDataSourceImpl) { bind() } singleOf(::AlbumRepositoryImpl) { bind() } singleOf(::TrackRepositoryImpl) { bind() } @@ -195,21 +183,6 @@ val appModule = singleOf(::RadioRepositoryImpl) { bind() } singleOf(::ConnectionRepositoryImpl) { bind() } - singleOf(::LocalGenreDataSource) - singleOf(::RemoteGenreDataSource) - singleOf(::LocalArtistDataSourceImpl) { bind() } - singleOf(::RemoteArtistDataSource) - singleOf(::LocalAlbumDataSource) - singleOf(::RemoteAlbumDataSource) - singleOf(::LocalTrackDataSource) - singleOf(::RemoteTrackDataSource) - singleOf(::LocalPlaylistDataSource) - singleOf(::RemotePlaylistDataSource) - singleOf(::LocalNowPlayingDataSource) - singleOf(::RemoteNowPlayingDataSource) - singleOf(::LocalRadioDataSource) - singleOf(::RemoteRadioDataSource) - singleOf(::GenreEntryAdapter) singleOf(::ArtistEntryAdapter) @@ -222,26 +195,28 @@ val appModule = singleOf(::RemoteBroadcastReceiver) singleOf(::SettingsManagerImpl) { bind() } singleOf(::ServiceCheckerImpl) { bind() } - singleOf(::ModelCacheImpl) { bind() } - singleOf(::MainDataModel) - singleOf(::ModelInitializer) - singleOf(::ConnectionModel) - singleOf(::LyricsModel) - singleOf(::RemoteController) - singleOf(::SocketService) + singleOf(::PlayingTrackCacheImpl) { bind() } singleOf(::SocketActivityChecker) singleOf(::CoverCache) - singleOf(::ProtocolHandler) - singleOf(::NotificationModel) + singleOf(::AppNotificationManagerImpl) { bind() } + singleOf(::CommandFactoryImpl) { bind() } + singleOf(::UserActionUseCaseImpl) { bind() } + singleOf(::VolumeModifyUseCaseImpl) { bind() } + singleOf(::AppStateManager) + singleOf(::AppState) + singleOf(::ClientConnectionManagerImpl) { bind() } + singleOf(::ClientInformationStoreImpl) { bind() } + singleOf(::ClientConnectionUseCaseImpl) { bind() } + singleOf(::MessageHandlerImpl) { bind() } + singleOf(::MessageQueueImpl) { bind() } + singleOf(::UiMessageQueueImpl) { bind() } + singleOf(::ConnectionState) + singleOf(::SerializationAdapterImpl) { bind() } + singleOf(::DeserializationAdapterImpl) { bind() } + singleOf(::PluginUpdateCheckUseCaseImpl) { bind() } factoryOf(::BasicSettingsHelper) - factoryOf(::ReduceVolumeOnRingCommand) - factoryOf(::HandshakeCompletionActions) - factoryOf(::NotifyNotAllowedCommand) - factoryOf(::ProtocolRequest) - factoryOf(::VersionCheckCommand) - factoryOf(::ProcessUserAction) factoryOf(::UpdateNowPlayingTrack) factoryOf(::UpdateCover) factoryOf(::UpdateRating) @@ -256,131 +231,124 @@ val appModule = factoryOf(::UpdateLyrics) factoryOf(::UpdateNowPlayingTrackMoved) factoryOf(::UpdateNowPlayingTrackRemoval) + factoryOf(::UpdateNowPlayingList) factoryOf(::UpdatePlaybackPositionCommand) factoryOf(::UpdatePluginVersionCommand) factoryOf(::ProtocolPingHandle) factoryOf(::SimpleLogCommand) - factoryOf(::RestartConnectionCommand) - factoryOf(::CancelNotificationCommand) - factoryOf(::SessionNotificationManager) factoryOf(::RemoteSessionManager) factoryOf(::RemoteVolumeProvider) - factoryOf(::InitiateConnectionCommand) - factoryOf(::TerminateConnectionCommand) - factoryOf(::StartDiscoveryCommand) - factoryOf(::RemoteServiceDiscovery) - factoryOf(::KeyVolumeUpCommand) - factoryOf(::KeyVolumeDownCommand) - factoryOf(::SocketDataAvailableCommand) - factoryOf(::ConnectionStatusChangedCommand) - factoryOf(::HandleHandshake) - factoryOf(::TerminateServiceCommand) + factoryOf(::ProtocolVersionUpdate) + singleOf(::RemoteServiceDiscoveryImpl) { bind() } singleOf(::WidgetUpdaterImpl) { bind() } - single(named("main")) { AndroidSchedulers.mainThread() } - single(named("io")) { Schedulers.io() } + val network = createDispatcher(name = "Network", threads = 2) + val database = createDispatcher(name = "Database") + + single { + @Suppress("InjectDispatcher") + object : AppCoroutineDispatchers { + override val main: CoroutineDispatcher = Dispatchers.Main + override val io: CoroutineDispatcher = Dispatchers.IO + override val database: CoroutineDispatcher = database + override val network: CoroutineDispatcher = network + } + } single { - val database = - Executors - .newSingleThreadExecutor { runnable -> - Thread(runnable, "DatabaseDispatcher") - }.asCoroutineDispatcher() - - var threadId = 1 - - val network = - Executors - .newFixedThreadPool(2) { runnable -> - Thread(runnable, "NetworkDispatcher-worker-${threadId++}") - }.asCoroutineDispatcher() - - AppCoroutineDispatchers( - main = Dispatchers.Main, - io = Dispatchers.IO, - database = database, - network = network, - ) + Room + .databaseBuilder(get(), Database::class.java, Database.NAME) + .build() } - - singleOf(::ArtistAlbumsPresenterImpl) { bind() } + single { get().genreDao() } + single { get().artistDao() } + single { get().albumDao() } + single { get().trackDao() } + single { get().nowPlayingDao() } + single { get().playlistDao() } + single { get().radioStationDao() } + single { get().connectionDao() } scope { - scopedOf(::MiniControlPresenterImpl) { bind() } + viewModelOf(::MiniControlViewModel) } scope { - scopedOf(::PlayerViewPresenterImpl) { bind() } - scoped { ProgressSeekerHelper(get(named("main"))) } + viewModelOf(::PlayerViewModel) } scope { - scopedOf(::LyricsPresenterImpl) { bind() } + viewModelOf(::LyricsViewModel) } scope { - scopedOf(::LibraryPresenterImpl) { bind() } scopedOf(::LibrarySearchModel) - scopedOf(::PopupActionHandler) } scope { - scopedOf(::BrowseGenrePresenterImpl) { bind() } + viewModelOf(::BrowseGenreViewModel) scopedOf(::GenreEntryAdapter) } scope { - scopedOf(::BrowseArtistPresenterImpl) { bind() } + viewModelOf(::BrowseArtistViewModel) scopedOf(::ArtistEntryAdapter) } scope { - scopedOf(::BrowseAlbumPresenterImpl) { bind() } + viewModelOf(::BrowseAlbumViewModel) scopedOf(::AlbumEntryAdapter) } scope { - scopedOf(::BrowseTrackPresenterImpl) { bind() } + viewModelOf(::BrowseTrackViewModel) scopedOf(::TrackEntryAdapter) } scope { - scopedOf(::GenreArtistsPresenterImpl) { bind() } + viewModelOf(::GenreArtistsViewModel) scopedOf(::ArtistEntryAdapter) - scopedOf(::PopupActionHandler) } scope { - scopedOf(::ArtistAlbumsPresenterImpl) { bind() } + viewModelOf(::ArtistAlbumsViewModel) scopedOf(::AlbumEntryAdapter) - scopedOf(::PopupActionHandler) } scope { - scopedOf(::AlbumTracksPresenterImpl) { bind() } + viewModelOf(::AlbumTracksViewModel) scopedOf(::TrackEntryAdapter) - scopedOf(::PopupActionHandler) } scope { - scopedOf(::NowPlayingPresenterImpl) { bind() } + viewModelOf(::NowPlayingViewModel) scopedOf(::NowPlayingAdapter) + scopedOf(::MoveManagerImpl) { bind() } } scope { - scopedOf(::PlaylistPresenterImpl) { bind() } + viewModelOf(::PlaylistViewModel) scopedOf(::PlaylistAdapter) } scope { - scopedOf(::ConnectionManagerPresenterImpl) { bind() } + viewModelOf(::ConnectionManagerViewModel) } scope { - scopedOf(::RadioPresenterImpl) { bind() } + viewModelOf(::RadioViewModel) scopedOf(::RadioAdapter) } + scope { + viewModelOf(::RatingDialogViewModel) + } + viewModelOf(::OutputSelectionViewModel) + + scope { + viewModelOf(::FeedbackViewModel) + scopedOf(::LogHelperImpl) { bind() } + } } diff --git a/app/src/main/java/com/kelsos/mbrc/BaseActivity.kt b/app/src/main/java/com/kelsos/mbrc/BaseActivity.kt index 412cb7c3c..e671134f0 100644 --- a/app/src/main/java/com/kelsos/mbrc/BaseActivity.kt +++ b/app/src/main/java/com/kelsos/mbrc/BaseActivity.kt @@ -11,22 +11,21 @@ import android.widget.ImageView import android.widget.TextView import androidx.activity.OnBackPressedCallback import androidx.annotation.ColorRes +import androidx.annotation.LayoutRes import androidx.annotation.StringRes import androidx.appcompat.app.ActionBarDrawerToggle import androidx.core.app.TaskStackBuilder import androidx.core.content.ContextCompat import androidx.core.view.GravityCompat import androidx.drawerlayout.widget.DrawerLayout +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.navigation.NavigationView import com.google.android.material.snackbar.Snackbar -import com.kelsos.mbrc.annotations.Connection -import com.kelsos.mbrc.constants.UserInputEventType -import com.kelsos.mbrc.events.MessageEvent -import com.kelsos.mbrc.events.bus.RxBus -import com.kelsos.mbrc.events.ui.ConnectionStatusChangeEvent -import com.kelsos.mbrc.events.ui.NotifyUser -import com.kelsos.mbrc.events.ui.RequestConnectionStateEvent +import com.kelsos.mbrc.common.state.ConnectionState +import com.kelsos.mbrc.common.state.ConnectionStatus import com.kelsos.mbrc.features.help.HelpFeedbackActivity import com.kelsos.mbrc.features.library.LibraryActivity import com.kelsos.mbrc.features.lyrics.LyricsActivity @@ -36,27 +35,36 @@ import com.kelsos.mbrc.features.player.PlayerActivity import com.kelsos.mbrc.features.playlists.PlaylistActivity import com.kelsos.mbrc.features.radio.RadioActivity import com.kelsos.mbrc.features.settings.SettingsActivity +import com.kelsos.mbrc.networking.ClientConnectionUseCase +import com.kelsos.mbrc.networking.client.UiMessage +import com.kelsos.mbrc.networking.client.UiMessageQueue +import com.kelsos.mbrc.networking.protocol.VolumeModifyUseCase import com.kelsos.mbrc.platform.RemoteService import com.kelsos.mbrc.platform.ServiceChecker +import kotlinx.coroutines.launch import org.koin.android.ext.android.inject import org.koin.androidx.scope.ScopeActivity import timber.log.Timber private const val NAVIGATION_DELAY = 250L -abstract class BaseActivity : - ScopeActivity(), +abstract class BaseActivity( + @LayoutRes val contentLayoutId: Int, +) : ScopeActivity(contentLayoutId), NavigationView.OnNavigationItemSelectedListener { - private val bus: RxBus by inject() private val serviceChecker: ServiceChecker by inject() + private val connectionUseCase: ClientConnectionUseCase by inject() + private val volumeModifyUseCase: VolumeModifyUseCase by inject() + private val connectionState: ConnectionState by inject() + private val uiMessageQueue: UiMessageQueue by inject() private lateinit var toolbar: MaterialToolbar private lateinit var drawer: DrawerLayout lateinit var navigationView: NavigationView - private var connectText: TextView? = null - private var toggle: ActionBarDrawerToggle? = null - private var connect: ImageView? = null + private lateinit var connectText: TextView + private lateinit var toggle: ActionBarDrawerToggle + private lateinit var connect: ImageView protected abstract fun active(): Int @@ -65,29 +73,61 @@ abstract class BaseActivity : private fun onConnectLongClick(): Boolean { Timber.v("Connect long pressed") serviceChecker.startServiceIfNotRunning() - bus.post(MessageEvent(UserInputEventType.RESET_CONNECTION)) + connectionUseCase.connect() return true } protected fun onConnectClick() { Timber.v("Attempting to connect") serviceChecker.startServiceIfNotRunning() - bus.post(MessageEvent(UserInputEventType.START_CONNECTION)) + connectionUseCase.connect() + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setupBackButtonHandler() + + toolbar = findViewById(R.id.toolbar) + drawer = findViewById(R.id.drawer_layout) + navigationView = findViewById(R.id.nav_view) + + setSupportActionBar(toolbar) + + toggle = + ActionBarDrawerToggle(this, drawer, toolbar, R.string.drawer_open, R.string.drawer_close) + drawer.addDrawerListener(toggle) + drawer.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START) + toggle.syncState() + navigationView.setNavigationItemSelectedListener(this) + + val header = navigationView.getHeaderView(0) + connectText = header.findViewById(R.id.nav_connect_text) + connect = header.findViewById(R.id.connect_button) + connect.setOnClickListener { this.onConnectClick() } + connect.setOnLongClickListener { this.onConnectLongClick() } + + supportActionBar?.setDisplayHomeAsUpEnabled(true) + supportActionBar?.setHomeButtonEnabled(true) + navigationView.setCheckedItem(active()) + serviceChecker.startServiceIfNotRunning() + + observeFlows() } override fun onDestroy() { super.onDestroy() - drawer.removeDrawerListener(toggle!!) + drawer.removeDrawerListener(toggle) } override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) - toggle!!.onConfigurationChanged(newConfig) + toggle.onConfigurationChanged(newConfig) } override fun onPostCreate(savedInstanceState: Bundle?) { super.onPostCreate(savedInstanceState) - toggle!!.syncState() + toggle.syncState() } override fun onKeyUp( @@ -100,37 +140,31 @@ abstract class BaseActivity : else -> super.onKeyUp(keyCode, event) } - private fun onConnection(event: ConnectionStatusChangeEvent) { + private fun updateConnectionState(event: ConnectionStatus) { Timber.v("Handling new connection status %s", event.status) @StringRes val resId: Int @ColorRes val colorId: Int - if (event.status == Connection.OFF) { - resId = R.string.drawer_connection_status_off - colorId = R.color.black - } else if (event.status == Connection.ON) { - resId = R.string.drawer_connection_status_on - colorId = R.color.accent - } else if (event.status == Connection.ACTIVE) { - resId = R.string.drawer_connection_status_active - colorId = R.color.power_on - } else { - resId = R.string.drawer_connection_status_off - colorId = R.color.black - } - - connectText!!.setText(resId) - connect!!.setColorFilter(ContextCompat.getColor(this, colorId)) - isConnected = event.status == Connection.ACTIVE - } + when (event) { + ConnectionStatus.Offline -> { + resId = R.string.drawer_connection_status_off + colorId = R.color.black + } - private fun handleUserNotification(event: NotifyUser) { - val message = if (event.isFromResource) getString(event.resId) else event.message + ConnectionStatus.Authenticating -> { + resId = R.string.drawer_connection_status_on + colorId = R.color.accent + } - val focus = currentFocus - if (focus != null) { - Snackbar.make(focus, message, Snackbar.LENGTH_SHORT).show() + ConnectionStatus.Connected -> { + resId = R.string.drawer_connection_status_active + colorId = R.color.power_on + } } + + connectText.setText(resId) + connect.setColorFilter(ContextCompat.getColor(this, colorId)) + isConnected = event == ConnectionStatus.Connected } override fun onKeyDown( @@ -140,12 +174,16 @@ abstract class BaseActivity : val result = when (keyCode) { KeyEvent.KEYCODE_VOLUME_UP -> { - bus.post(MessageEvent(UserInputEventType.KEY_VOLUME_UP)) + lifecycleScope.launch { + volumeModifyUseCase.increment() + } true } KeyEvent.KEYCODE_VOLUME_DOWN -> { - bus.post(MessageEvent(UserInputEventType.KEY_VOLUME_DOWN)) + lifecycleScope.launch { + volumeModifyUseCase.decrement() + } true } @@ -166,31 +204,51 @@ abstract class BaseActivity : return } - if (itemId == R.id.nav_home) { - val upIntent = getParentActivityIntent() - if (upIntent == null || shouldUpRecreateTask(upIntent)) { - createBackStack(Intent(this, PlayerActivity::class.java)) - } else { - navigateUpTo(upIntent) + when (itemId) { + R.id.nav_home -> { + val upIntent = getParentActivityIntent() + if (upIntent == null || shouldUpRecreateTask(upIntent)) { + createBackStack(Intent(this, PlayerActivity::class.java)) + } else { + navigateUpTo(upIntent) + } + } + + R.id.nav_library -> { + createBackStack(Intent(this, LibraryActivity::class.java)) + } + + R.id.nav_now_playing -> { + createBackStack(Intent(this, NowPlayingActivity::class.java)) + } + + R.id.nav_playlists -> { + createBackStack(Intent(this, PlaylistActivity::class.java)) + } + + R.id.nav_radio -> { + createBackStack(Intent(this, RadioActivity::class.java)) + } + + R.id.nav_lyrics -> { + createBackStack(Intent(this, LyricsActivity::class.java)) + } + + R.id.nav_settings -> { + createBackStack(Intent(this, SettingsActivity::class.java)) + } + + R.id.nav_help -> { + createBackStack(Intent(this, HelpFeedbackActivity::class.java)) + } + + R.id.nav_output -> { + OutputSelectionDialog.create(supportFragmentManager).show() + } + + R.id.nav_exit -> { + exitApplication() } - } else if (itemId == R.id.nav_library) { - createBackStack(Intent(this, LibraryActivity::class.java)) - } else if (itemId == R.id.nav_now_playing) { - createBackStack(Intent(this, NowPlayingActivity::class.java)) - } else if (itemId == R.id.nav_playlists) { - createBackStack(Intent(this, PlaylistActivity::class.java)) - } else if (itemId == R.id.nav_radio) { - createBackStack(Intent(this, RadioActivity::class.java)) - } else if (itemId == R.id.nav_lyrics) { - createBackStack(Intent(this, LyricsActivity::class.java)) - } else if (itemId == R.id.nav_settings) { - createBackStack(Intent(this, SettingsActivity::class.java)) - } else if (itemId == R.id.nav_help) { - createBackStack(Intent(this, HelpFeedbackActivity::class.java)) - } else if (itemId == R.id.nav_output) { - OutputSelectionDialog.create(supportFragmentManager).show() - } else if (itemId == R.id.nav_exit) { - exitApplication() } } @@ -221,11 +279,46 @@ abstract class BaseActivity : } } - /** - * Should be called after injections and setContentView. - */ - fun setup() { - Timber.v("Initializing base activity") + private fun observeFlows() { + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + connectionState.connection.collect { event -> + updateConnectionState(event) + } + } + } + + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + uiMessageQueue.messages.collect { + if (it is UiMessage.PluginUpdateRequired) { + showPluginUpdateRequired(it.minimumVersion) + } else if (it is UiMessage.PluginUpdateAvailable) { + showPluginUpdateAvailable() + } + } + } + } + } + + fun showPluginUpdateRequired(minimumRequired: String) { + val intent = Intent(this, UpdateRequiredActivity::class.java) + intent.putExtra(UpdateRequiredActivity.VERSION, minimumRequired) + startActivity(intent) + } + + fun showPluginUpdateAvailable() { + val snackBar = + Snackbar.make( + navigationView, + R.string.main__dialog_plugin_outdated_message, + Snackbar.LENGTH_INDEFINITE, + ) + snackBar.setAction(android.R.string.ok) { snackBar.dismiss() } + snackBar.show() + } + + private fun setupBackButtonHandler() { onBackPressedDispatcher.addCallback( this, object : OnBackPressedCallback(true) { @@ -238,46 +331,6 @@ abstract class BaseActivity : } }, ) - toolbar = findViewById(R.id.toolbar) - drawer = findViewById(R.id.drawer_layout) - navigationView = findViewById(R.id.nav_view) - - setSupportActionBar(toolbar) - - toggle = - ActionBarDrawerToggle(this, drawer, toolbar, R.string.drawer_open, R.string.drawer_close) - drawer.addDrawerListener(toggle!!) - drawer.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START) - toggle!!.syncState() - navigationView.setNavigationItemSelectedListener(this) - - val header = navigationView.getHeaderView(0) - connectText = header.findViewById(R.id.nav_connect_text) - connect = header.findViewById(R.id.connect_button) - connect!!.setOnClickListener { this.onConnectClick() } - connect!!.setOnLongClickListener { this.onConnectLongClick() } - - supportActionBar?.setDisplayHomeAsUpEnabled(true) - supportActionBar?.setHomeButtonEnabled(true) - navigationView.setCheckedItem(active()) - serviceChecker.startServiceIfNotRunning() - } - - override fun onResume() { - super.onResume() - this.bus.register(this, NotifyUser::class.java, { this.handleUserNotification(it) }, true) - this.bus.register( - this, - ConnectionStatusChangeEvent::class.java, - { this.onConnection(it) }, - true, - ) - this.bus.post(RequestConnectionStateEvent()) - } - - override fun onPause() { - super.onPause() - this.bus.unregister(this) } companion object { diff --git a/app/src/main/java/com/kelsos/mbrc/UpdateRequiredActivity.kt b/app/src/main/java/com/kelsos/mbrc/UpdateRequiredActivity.kt index 4e73e4220..006e30806 100644 --- a/app/src/main/java/com/kelsos/mbrc/UpdateRequiredActivity.kt +++ b/app/src/main/java/com/kelsos/mbrc/UpdateRequiredActivity.kt @@ -17,12 +17,12 @@ class UpdateRequiredActivity : AppCompatActivity() { window.sharedElementEnterTransition = MaterialContainerTransform().apply { addTarget(android.R.id.content) - duration = 300L + duration = ENTER_DURATION } window.sharedElementReturnTransition = MaterialContainerTransform().apply { addTarget(android.R.id.content) - duration = 250L + duration = RETURN_DURATION } super.onCreate(savedInstanceState) setContentView(R.layout.activity_update_required) @@ -36,5 +36,7 @@ class UpdateRequiredActivity : AppCompatActivity() { companion object { const val VERSION: String = "version" + const val ENTER_DURATION = 300L + const val RETURN_DURATION = 250L } } diff --git a/app/src/main/java/com/kelsos/mbrc/annotations/Connection.kt b/app/src/main/java/com/kelsos/mbrc/annotations/Connection.kt deleted file mode 100644 index b659462cc..000000000 --- a/app/src/main/java/com/kelsos/mbrc/annotations/Connection.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.kelsos.mbrc.annotations - -import androidx.annotation.IntDef - -object Connection { - const val OFF = 0 - const val ON = 1 - const val ACTIVE = 2 - - @Retention(AnnotationRetention.SOURCE) - @IntDef(OFF, ON, ACTIVE) - annotation class Status -} // no instance diff --git a/app/src/main/java/com/kelsos/mbrc/annotations/PlayerState.kt b/app/src/main/java/com/kelsos/mbrc/annotations/PlayerState.kt deleted file mode 100644 index c022e4271..000000000 --- a/app/src/main/java/com/kelsos/mbrc/annotations/PlayerState.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.kelsos.mbrc.annotations - -import androidx.annotation.StringDef - -object PlayerState { - const val PLAYING = "playing" - const val PAUSED = "paused" - const val STOPPED = "stopped" - const val UNDEFINED = "undefined" - - @Retention(AnnotationRetention.SOURCE) - @StringDef(PAUSED, PLAYING, STOPPED, UNDEFINED) - annotation class State -} // no instance diff --git a/app/src/main/java/com/kelsos/mbrc/annotations/Queue.kt b/app/src/main/java/com/kelsos/mbrc/annotations/Queue.kt deleted file mode 100644 index 7cf089bc5..000000000 --- a/app/src/main/java/com/kelsos/mbrc/annotations/Queue.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.kelsos.mbrc.annotations - -import androidx.annotation.StringDef - -object Queue { - @StringDef(NEXT, LAST, NOW, ADD_ALL, PROFILE) - @Retention(AnnotationRetention.SOURCE) - annotation class Action - - const val PROFILE = "profile" - const val NEXT = "next" - const val LAST = "last" - const val NOW = "now" - const val ADD_ALL = "add-all" - const val PLAY_ALBUM = "play-album" - const val PLAY_ARTIST = "play-artist" -} diff --git a/app/src/main/java/com/kelsos/mbrc/annotations/Repeat.kt b/app/src/main/java/com/kelsos/mbrc/annotations/Repeat.kt deleted file mode 100644 index 52fbb5ff1..000000000 --- a/app/src/main/java/com/kelsos/mbrc/annotations/Repeat.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.kelsos.mbrc.annotations - -import androidx.annotation.StringDef - -object Repeat { - const val ALL = "all" - const val NONE = "none" - const val ONE = "one" - - @StringDef(ALL, NONE, ONE) - @Retention(AnnotationRetention.SOURCE) - annotation class Mode -} diff --git a/app/src/main/java/com/kelsos/mbrc/annotations/Search.kt b/app/src/main/java/com/kelsos/mbrc/annotations/Search.kt deleted file mode 100644 index 5c3f515ab..000000000 --- a/app/src/main/java/com/kelsos/mbrc/annotations/Search.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.kelsos.mbrc.annotations - -object Search { - const val SECTION_GENRE = 0 - const val SECTION_ARTIST = 1 - const val SECTION_ALBUM = 2 - const val SECTION_TRACK = 3 -} diff --git a/app/src/main/java/com/kelsos/mbrc/annotations/SocketAction.kt b/app/src/main/java/com/kelsos/mbrc/annotations/SocketAction.kt deleted file mode 100644 index 5aa21e018..000000000 --- a/app/src/main/java/com/kelsos/mbrc/annotations/SocketAction.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.kelsos.mbrc.annotations - -import androidx.annotation.IntDef - -object SocketAction { - const val RESET = 1 - const val START = 2 - const val RETRY = 3 - const val TERMINATE = 4 - const val STOP = 5 - - @Retention(AnnotationRetention.SOURCE) - @IntDef(RESET, START, RETRY, TERMINATE, STOP) - annotation class Action - - fun name( - @Action action: Int, - ): String = - when (action) { - RESET -> "Reset" - START -> "Start" - RETRY -> "Retry" - TERMINATE -> "Terminate" - STOP -> "Stop" - else -> throw IllegalArgumentException("action $action is not recognised") - } -} diff --git a/app/src/main/java/com/kelsos/mbrc/commands/CancelNotificationCommand.kt b/app/src/main/java/com/kelsos/mbrc/commands/CancelNotificationCommand.kt deleted file mode 100644 index 5cc5648f1..000000000 --- a/app/src/main/java/com/kelsos/mbrc/commands/CancelNotificationCommand.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.kelsos.mbrc.commands - -import com.kelsos.mbrc.networking.protocol.ProtocolAction -import com.kelsos.mbrc.networking.protocol.ProtocolMessage -import com.kelsos.mbrc.platform.mediasession.SessionNotificationManager - -class CancelNotificationCommand( - private val sessionNotificationManager: SessionNotificationManager, -) : ProtocolAction { - override fun execute(message: ProtocolMessage) { - sessionNotificationManager.cancelNotification(SessionNotificationManager.NOW_PLAYING_PLACEHOLDER) - } -} diff --git a/app/src/main/java/com/kelsos/mbrc/commands/ConnectionStatusChangedCommand.kt b/app/src/main/java/com/kelsos/mbrc/commands/ConnectionStatusChangedCommand.kt deleted file mode 100644 index 02a9f8cdb..000000000 --- a/app/src/main/java/com/kelsos/mbrc/commands/ConnectionStatusChangedCommand.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.kelsos.mbrc.commands - -import com.kelsos.mbrc.common.state.ConnectionModel -import com.kelsos.mbrc.networking.client.SocketMessage -import com.kelsos.mbrc.networking.client.SocketService -import com.kelsos.mbrc.networking.protocol.Protocol -import com.kelsos.mbrc.networking.protocol.ProtocolAction -import com.kelsos.mbrc.networking.protocol.ProtocolMessage -import com.kelsos.mbrc.platform.mediasession.SessionNotificationManager - -class ConnectionStatusChangedCommand( - private val model: ConnectionModel, - private val service: SocketService, - private val sessionNotificationManager: SessionNotificationManager, -) : ProtocolAction { - override fun execute(message: ProtocolMessage) { - model.setConnectionState(message.dataString) - - if (model.isConnectionActive) { - service.sendData(SocketMessage.create(Protocol.PLAYER, "Android")) - } else { - sessionNotificationManager.cancelNotification(SessionNotificationManager.NOW_PLAYING_PLACEHOLDER) - } - } -} diff --git a/app/src/main/java/com/kelsos/mbrc/commands/HandleHandshake.kt b/app/src/main/java/com/kelsos/mbrc/commands/HandleHandshake.kt deleted file mode 100644 index 7622dd412..000000000 --- a/app/src/main/java/com/kelsos/mbrc/commands/HandleHandshake.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.kelsos.mbrc.commands - -import com.kelsos.mbrc.common.state.ConnectionModel -import com.kelsos.mbrc.networking.protocol.ProtocolAction -import com.kelsos.mbrc.networking.protocol.ProtocolHandler -import com.kelsos.mbrc.networking.protocol.ProtocolMessage - -class HandleHandshake( - private val handler: ProtocolHandler, - private val model: ConnectionModel, -) : ProtocolAction { - override fun execute(message: ProtocolMessage) { - if (!(message.data as Boolean)) { - handler.resetHandshake() - model.setHandShakeDone(false) - } - } -} diff --git a/app/src/main/java/com/kelsos/mbrc/commands/InitiateConnectionCommand.kt b/app/src/main/java/com/kelsos/mbrc/commands/InitiateConnectionCommand.kt deleted file mode 100644 index 584d64b0a..000000000 --- a/app/src/main/java/com/kelsos/mbrc/commands/InitiateConnectionCommand.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.kelsos.mbrc.commands - -import com.kelsos.mbrc.annotations.SocketAction.START -import com.kelsos.mbrc.networking.client.SocketService -import com.kelsos.mbrc.networking.protocol.ProtocolAction -import com.kelsos.mbrc.networking.protocol.ProtocolMessage - -class InitiateConnectionCommand( - private val socketService: SocketService, -) : ProtocolAction { - override fun execute(message: ProtocolMessage) { - socketService.socketManager(START) - } -} diff --git a/app/src/main/java/com/kelsos/mbrc/commands/KeyVolumeDownCommand.kt b/app/src/main/java/com/kelsos/mbrc/commands/KeyVolumeDownCommand.kt deleted file mode 100644 index 5b3cc7a73..000000000 --- a/app/src/main/java/com/kelsos/mbrc/commands/KeyVolumeDownCommand.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.kelsos.mbrc.commands - -import com.kelsos.mbrc.common.state.MainDataModel -import com.kelsos.mbrc.data.UserAction -import com.kelsos.mbrc.events.MessageEvent -import com.kelsos.mbrc.events.bus.RxBus -import com.kelsos.mbrc.networking.protocol.Protocol -import com.kelsos.mbrc.networking.protocol.ProtocolAction -import com.kelsos.mbrc.networking.protocol.ProtocolMessage - -class KeyVolumeDownCommand( - private val model: MainDataModel, - private val bus: RxBus, -) : ProtocolAction { - override fun execute(message: ProtocolMessage) { - if (model.volume >= 10) { - val mod = model.volume % 10 - val volume: Int - - if (mod == 0) { - volume = model.volume - 10 - } else if (mod < 5) { - volume = model.volume - (10 + mod) - } else { - volume = model.volume - mod - } - - bus.post(MessageEvent.action(UserAction(Protocol.PLAYER_VOLUME, volume))) - } - } -} diff --git a/app/src/main/java/com/kelsos/mbrc/commands/KeyVolumeUpCommand.kt b/app/src/main/java/com/kelsos/mbrc/commands/KeyVolumeUpCommand.kt deleted file mode 100644 index 9ec372b0c..000000000 --- a/app/src/main/java/com/kelsos/mbrc/commands/KeyVolumeUpCommand.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.kelsos.mbrc.commands - -import com.kelsos.mbrc.common.state.MainDataModel -import com.kelsos.mbrc.data.UserAction -import com.kelsos.mbrc.events.MessageEvent -import com.kelsos.mbrc.events.bus.RxBus -import com.kelsos.mbrc.networking.protocol.Protocol -import com.kelsos.mbrc.networking.protocol.ProtocolAction -import com.kelsos.mbrc.networking.protocol.ProtocolMessage - -class KeyVolumeUpCommand( - private val model: MainDataModel, - private val bus: RxBus, -) : ProtocolAction { - override fun execute(message: ProtocolMessage) { - val volume: Int = - if (model.volume <= 90) { - val mod = model.volume % 10 - - when { - mod == 0 -> model.volume + 10 - mod < 5 -> model.volume + (10 - mod) - else -> model.volume + (20 - mod) - } - } else { - 100 - } - - bus.post(MessageEvent.action(UserAction(Protocol.PLAYER_VOLUME, volume))) - } -} diff --git a/app/src/main/java/com/kelsos/mbrc/commands/ProcessUserAction.kt b/app/src/main/java/com/kelsos/mbrc/commands/ProcessUserAction.kt deleted file mode 100644 index a99117302..000000000 --- a/app/src/main/java/com/kelsos/mbrc/commands/ProcessUserAction.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.kelsos.mbrc.commands - -import com.kelsos.mbrc.data.UserAction -import com.kelsos.mbrc.networking.client.SocketMessage -import com.kelsos.mbrc.networking.client.SocketService -import com.kelsos.mbrc.networking.protocol.ProtocolAction -import com.kelsos.mbrc.networking.protocol.ProtocolMessage - -class ProcessUserAction( - private val socket: SocketService, -) : ProtocolAction { - override fun execute(message: ProtocolMessage) { - socket.sendData( - SocketMessage.create( - (message.data as UserAction).context, - (message.data as UserAction).data, - ), - ) - } -} diff --git a/app/src/main/java/com/kelsos/mbrc/commands/ProtocolRequest.kt b/app/src/main/java/com/kelsos/mbrc/commands/ProtocolRequest.kt deleted file mode 100644 index 022415a10..000000000 --- a/app/src/main/java/com/kelsos/mbrc/commands/ProtocolRequest.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.kelsos.mbrc.commands - -import com.kelsos.mbrc.networking.client.SocketMessage -import com.kelsos.mbrc.networking.client.SocketService -import com.kelsos.mbrc.networking.protocol.Protocol -import com.kelsos.mbrc.networking.protocol.ProtocolAction -import com.kelsos.mbrc.networking.protocol.ProtocolMessage -import com.kelsos.mbrc.networking.protocol.ProtocolPayload - -class ProtocolRequest( - private val socket: SocketService, -) : ProtocolAction { - override fun execute(message: ProtocolMessage) { - val payload = ProtocolPayload() - payload.noBroadcast = false - payload.protocolVersion = Protocol.PROTOCOL_VERSION_NUMBER - socket.sendData(SocketMessage.create(Protocol.PROTOCOL_TAG, payload)) - } -} diff --git a/app/src/main/java/com/kelsos/mbrc/commands/ReduceVolumeOnRingCommand.kt b/app/src/main/java/com/kelsos/mbrc/commands/ReduceVolumeOnRingCommand.kt deleted file mode 100644 index 087811f1b..000000000 --- a/app/src/main/java/com/kelsos/mbrc/commands/ReduceVolumeOnRingCommand.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.kelsos.mbrc.commands - -import com.kelsos.mbrc.common.state.MainDataModel -import com.kelsos.mbrc.networking.client.SocketMessage -import com.kelsos.mbrc.networking.client.SocketService -import com.kelsos.mbrc.networking.protocol.Protocol -import com.kelsos.mbrc.networking.protocol.ProtocolAction -import com.kelsos.mbrc.networking.protocol.ProtocolMessage - -class ReduceVolumeOnRingCommand( - private val model: MainDataModel, - private val service: SocketService, -) : ProtocolAction { - override fun execute(message: ProtocolMessage) { - if (model.isMute || model.volume == 0) { - return - } - service.sendData(SocketMessage.create(Protocol.PLAYER_VOLUME, (model.volume * 0.2).toInt())) - } -} diff --git a/app/src/main/java/com/kelsos/mbrc/commands/RestartConnectionCommand.kt b/app/src/main/java/com/kelsos/mbrc/commands/RestartConnectionCommand.kt deleted file mode 100644 index cfde4f318..000000000 --- a/app/src/main/java/com/kelsos/mbrc/commands/RestartConnectionCommand.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.kelsos.mbrc.commands - -import com.kelsos.mbrc.annotations.SocketAction.RESET -import com.kelsos.mbrc.networking.client.SocketService -import com.kelsos.mbrc.networking.protocol.ProtocolAction -import com.kelsos.mbrc.networking.protocol.ProtocolMessage - -class RestartConnectionCommand( - private val socket: SocketService, -) : ProtocolAction { - override fun execute(message: ProtocolMessage) { - socket.socketManager(RESET) - } -} diff --git a/app/src/main/java/com/kelsos/mbrc/commands/SocketDataAvailableCommand.kt b/app/src/main/java/com/kelsos/mbrc/commands/SocketDataAvailableCommand.kt deleted file mode 100644 index 99948733a..000000000 --- a/app/src/main/java/com/kelsos/mbrc/commands/SocketDataAvailableCommand.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.kelsos.mbrc.commands - -import com.kelsos.mbrc.networking.protocol.ProtocolAction -import com.kelsos.mbrc.networking.protocol.ProtocolHandler -import com.kelsos.mbrc.networking.protocol.ProtocolMessage - -class SocketDataAvailableCommand( - private val handler: ProtocolHandler, -) : ProtocolAction { - override fun execute(message: ProtocolMessage) { - handler.preProcessIncoming(message.dataString) - } -} diff --git a/app/src/main/java/com/kelsos/mbrc/commands/StartDiscoveryCommand.kt b/app/src/main/java/com/kelsos/mbrc/commands/StartDiscoveryCommand.kt deleted file mode 100644 index ab3625ba3..000000000 --- a/app/src/main/java/com/kelsos/mbrc/commands/StartDiscoveryCommand.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.kelsos.mbrc.commands - -import com.kelsos.mbrc.networking.discovery.RemoteServiceDiscovery -import com.kelsos.mbrc.networking.protocol.ProtocolAction -import com.kelsos.mbrc.networking.protocol.ProtocolMessage - -class StartDiscoveryCommand( - private val discovery: RemoteServiceDiscovery, -) : ProtocolAction { - override fun execute(message: ProtocolMessage) { - discovery.startDiscovery() - } -} diff --git a/app/src/main/java/com/kelsos/mbrc/commands/TerminateConnectionCommand.kt b/app/src/main/java/com/kelsos/mbrc/commands/TerminateConnectionCommand.kt deleted file mode 100644 index 8c890c834..000000000 --- a/app/src/main/java/com/kelsos/mbrc/commands/TerminateConnectionCommand.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.kelsos.mbrc.commands - -import com.kelsos.mbrc.annotations.SocketAction.TERMINATE -import com.kelsos.mbrc.common.state.ConnectionModel -import com.kelsos.mbrc.networking.client.SocketService -import com.kelsos.mbrc.networking.protocol.ProtocolAction -import com.kelsos.mbrc.networking.protocol.ProtocolMessage - -class TerminateConnectionCommand( - private val service: SocketService, - private val model: ConnectionModel, -) : ProtocolAction { - override fun execute(message: ProtocolMessage) { - model.setHandShakeDone(false) - model.setConnectionState("false") - service.socketManager(TERMINATE) - } -} diff --git a/app/src/main/java/com/kelsos/mbrc/commands/TerminateServiceCommand.kt b/app/src/main/java/com/kelsos/mbrc/commands/TerminateServiceCommand.kt deleted file mode 100644 index e0514715f..000000000 --- a/app/src/main/java/com/kelsos/mbrc/commands/TerminateServiceCommand.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.kelsos.mbrc.commands - -import android.app.Application -import android.content.Intent -import com.kelsos.mbrc.networking.protocol.ProtocolAction -import com.kelsos.mbrc.networking.protocol.ProtocolMessage -import com.kelsos.mbrc.platform.RemoteService - -class TerminateServiceCommand( - private val application: Application, -) : ProtocolAction { - override fun execute(message: ProtocolMessage) { - if (RemoteService.serviceStopping) { - return - } - application.run { - stopService(Intent(this, RemoteService::class.java)) - } - } -} diff --git a/app/src/main/java/com/kelsos/mbrc/commands/VersionCheckCommand.kt b/app/src/main/java/com/kelsos/mbrc/commands/VersionCheckCommand.kt deleted file mode 100644 index 901737299..000000000 --- a/app/src/main/java/com/kelsos/mbrc/commands/VersionCheckCommand.kt +++ /dev/null @@ -1,108 +0,0 @@ -package com.kelsos.mbrc.commands - -import com.fasterxml.jackson.databind.JsonNode -import com.fasterxml.jackson.databind.ObjectMapper -import com.kelsos.mbrc.common.state.MainDataModel -import com.kelsos.mbrc.constants.ProtocolEventType -import com.kelsos.mbrc.events.MessageEvent -import com.kelsos.mbrc.events.bus.RxBus -import com.kelsos.mbrc.features.settings.SettingsManager -import com.kelsos.mbrc.networking.protocol.ProtocolAction -import com.kelsos.mbrc.networking.protocol.ProtocolMessage -import timber.log.Timber -import java.io.IOException -import java.net.URL -import java.time.Instant -import java.time.temporal.ChronoUnit - -class VersionCheckCommand( - private val model: MainDataModel, - private val mapper: ObjectMapper, - private val manager: SettingsManager, - private val bus: RxBus, -) : ProtocolAction { - override fun execute(message: ProtocolMessage) { - val now = Instant.now() - - if (check(MINIMUM_REQUIRED)) { - val next = getNextCheck(true) - if (next.isAfter(now)) { - Timber.d("Next update required check is @ $next") - return - } - bus.post(MessageEvent(ProtocolEventType.PLUGIN_UPDATE_REQUIRED)) - model.minimumRequired = MINIMUM_REQUIRED - model.pluginUpdateRequired = true - manager.setLastUpdated(now, true) - return - } - - if (!manager.isPluginUpdateCheckEnabled()) { - return - } - - val nextCheck = getNextCheck() - - if (nextCheck.isAfter(now)) { - Timber.d("Next update check after @ $nextCheck") - return - } - - val jsonNode: JsonNode - try { - jsonNode = mapper.readValue(URL(CHECK_URL), JsonNode::class.java) - } catch (e1: IOException) { - Timber.d(e1, "While reading json node") - return - } - - val expected = jsonNode.path("tag_name").asText().replace("v", "") - - val found = model.pluginVersion - if (expected != found && check(expected)) { - model.pluginUpdateAvailable = true - bus.post(MessageEvent(ProtocolEventType.PLUGIN_UPDATE_AVAILABLE)) - } - - manager.setLastUpdated(now, false) - Timber.d("Checked for plugin update @ $now. Found: $found expected: $expected") - } - - private fun getNextCheck(required: Boolean = false): Instant { - val lastUpdated = manager.getLastUpdated(required) - val days = if (required) 1L else 2L - return lastUpdated.plus(days, ChronoUnit.DAYS) - } - - private fun check(suggestedVersion: String): Boolean { - val currentVersion = model.pluginVersion.toVersionArray() - val latestVersion = suggestedVersion.toVersionArray() - - var i = 0 - val currentSize = currentVersion.size - val latestSize = latestVersion.size - while (i < currentSize && i < latestSize && currentVersion[i] == latestVersion[i]) { - i++ - } - - if (i < currentSize && i < latestSize) { - val diff = currentVersion[i].compareTo(latestVersion[i]) - return diff < 0 - } - - return false - } - - companion object { - private const val CHECK_URL = - "https://api.github.com/repos/musicbeeremote/plugin/releases/latest" - private const val MINIMUM_REQUIRED = "1.4.0" - } -} - -fun String.toVersionArray(): Array = - split("\\.".toRegex()) - .dropLastWhile(String::isEmpty) - .take(3) - .map { it.toInt() } - .toTypedArray() diff --git a/app/src/main/java/com/kelsos/mbrc/commands/visual/HandshakeCompletionActions.kt b/app/src/main/java/com/kelsos/mbrc/commands/visual/HandshakeCompletionActions.kt deleted file mode 100644 index f2567f4fa..000000000 --- a/app/src/main/java/com/kelsos/mbrc/commands/visual/HandshakeCompletionActions.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.kelsos.mbrc.commands.visual - -import com.kelsos.mbrc.common.state.ConnectionModel -import com.kelsos.mbrc.common.state.MainDataModel -import com.kelsos.mbrc.features.library.LibrarySyncUseCase -import com.kelsos.mbrc.networking.client.SocketMessage -import com.kelsos.mbrc.networking.client.SocketService -import com.kelsos.mbrc.networking.protocol.Protocol -import com.kelsos.mbrc.networking.protocol.ProtocolAction -import com.kelsos.mbrc.networking.protocol.ProtocolMessage -import rx.Observable -import timber.log.Timber -import java.util.concurrent.TimeUnit - -class HandshakeCompletionActions( - private val service: SocketService, - private val model: MainDataModel, - private val connectionModel: ConnectionModel, - private val syncInteractor: LibrarySyncUseCase, -) : ProtocolAction { - override fun execute(message: ProtocolMessage) { - val isComplete = message.data as Boolean - connectionModel.setHandShakeDone(isComplete) - - if (!isComplete) { - return - } - - if (model.pluginProtocol > 2) { - Timber.v("Sending init request") - service.sendData(SocketMessage.create(Protocol.INIT)) - service.sendData(SocketMessage.create(Protocol.PLUGIN_VERSION)) - } else { - Timber.v("Preparing to send requests for state") - - val messages = ArrayList() - messages.add(SocketMessage.create(Protocol.NOW_PLAYING_COVER)) - messages.add(SocketMessage.create(Protocol.PLAYER_STATUS)) - messages.add(SocketMessage.create(Protocol.NOW_PLAYING_TRACK)) - messages.add(SocketMessage.create(Protocol.NOW_PLAYING_LYRICS)) - messages.add(SocketMessage.create(Protocol.NOW_PLAYING_POSITION)) - messages.add(SocketMessage.create(Protocol.PLUGIN_VERSION)) - - val totalMessages = messages.size - Observable - .interval(150, TimeUnit.MILLISECONDS) - .take(totalMessages) - .subscribe({ service.sendData(messages.removeAt(0)) }) { - Timber.v(it, "Failure while sending the init messages") - } - } - - syncInteractor.sync(true) - } -} diff --git a/app/src/main/java/com/kelsos/mbrc/commands/visual/NotifyNotAllowedCommand.kt b/app/src/main/java/com/kelsos/mbrc/commands/visual/NotifyNotAllowedCommand.kt deleted file mode 100644 index bb771654c..000000000 --- a/app/src/main/java/com/kelsos/mbrc/commands/visual/NotifyNotAllowedCommand.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.kelsos.mbrc.commands.visual - -import com.kelsos.mbrc.R -import com.kelsos.mbrc.annotations.SocketAction.STOP -import com.kelsos.mbrc.common.state.ConnectionModel -import com.kelsos.mbrc.events.bus.RxBus -import com.kelsos.mbrc.events.ui.NotifyUser -import com.kelsos.mbrc.networking.client.SocketService -import com.kelsos.mbrc.networking.protocol.ProtocolAction -import com.kelsos.mbrc.networking.protocol.ProtocolHandler -import com.kelsos.mbrc.networking.protocol.ProtocolMessage - -class NotifyNotAllowedCommand( - private val socketService: SocketService, - private val model: ConnectionModel, - private val handler: ProtocolHandler, - private val bus: RxBus, -) : ProtocolAction { - override fun execute(message: ProtocolMessage) { - bus.post(NotifyUser(R.string.notification_not_allowed)) - socketService.socketManager(STOP) - model.setConnectionState("false") - handler.resetHandshake() - } -} diff --git a/app/src/main/java/com/kelsos/mbrc/common/data/LocalDataSource.kt b/app/src/main/java/com/kelsos/mbrc/common/data/LocalDataSource.kt deleted file mode 100644 index ef44fb0f2..000000000 --- a/app/src/main/java/com/kelsos/mbrc/common/data/LocalDataSource.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.kelsos.mbrc.common.data - -import com.kelsos.mbrc.data.Data -import com.raizlabs.android.dbflow.list.FlowCursorList - -interface LocalDataSource { - suspend fun deleteAll() - - suspend fun saveAll(list: List) - - suspend fun loadAllCursor(): FlowCursorList - - suspend fun search(term: String): FlowCursorList - - suspend fun isEmpty(): Boolean - - suspend fun count(): Long - - suspend fun removePreviousEntries(epoch: Long) -} diff --git a/app/src/main/java/com/kelsos/mbrc/common/data/RemoteDataSource.kt b/app/src/main/java/com/kelsos/mbrc/common/data/RemoteDataSource.kt deleted file mode 100644 index 53a2473bd..000000000 --- a/app/src/main/java/com/kelsos/mbrc/common/data/RemoteDataSource.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.kelsos.mbrc.common.data - -import kotlinx.coroutines.flow.Flow - -interface RemoteDataSource { - /** - * Retrieves all the available data from a remote data source - */ - suspend fun fetch(): Flow> - - companion object { - const val LIMIT = 800 - } -} diff --git a/app/src/main/java/com/kelsos/mbrc/common/data/Repository.kt b/app/src/main/java/com/kelsos/mbrc/common/data/Repository.kt index 03ba9084d..5fe9ad361 100644 --- a/app/src/main/java/com/kelsos/mbrc/common/data/Repository.kt +++ b/app/src/main/java/com/kelsos/mbrc/common/data/Repository.kt @@ -1,18 +1,20 @@ package com.kelsos.mbrc.common.data -import com.kelsos.mbrc.data.Data -import com.raizlabs.android.dbflow.list.FlowCursorList +import androidx.paging.PagingData +import kotlinx.coroutines.flow.Flow -interface Repository { - suspend fun getAllCursor(): FlowCursorList +typealias Progress = suspend (current: Int, total: Int) -> Unit - suspend fun getAndSaveRemote(): FlowCursorList +interface Repository { + fun getAll(): Flow> - suspend fun getRemote() + suspend fun getRemote(progress: Progress? = null) - suspend fun search(term: String): FlowCursorList - - suspend fun cacheIsEmpty(): Boolean + fun search(term: String): Flow> suspend fun count(): Long + + suspend fun getById(id: Long): T? } + +suspend fun Repository.cacheIsEmpty(): Boolean = count() == 0L diff --git a/app/src/main/java/com/kelsos/mbrc/common/mvp/BasePresenter.kt b/app/src/main/java/com/kelsos/mbrc/common/mvp/BasePresenter.kt deleted file mode 100644 index 2957284f7..000000000 --- a/app/src/main/java/com/kelsos/mbrc/common/mvp/BasePresenter.kt +++ /dev/null @@ -1,64 +0,0 @@ -package com.kelsos.mbrc.common.mvp - -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.LifecycleRegistry -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.cancelChildren -import rx.Subscription -import rx.subscriptions.CompositeSubscription -import kotlin.coroutines.CoroutineContext - -open class BasePresenter( - private val dispatcher: CoroutineDispatcher = Dispatchers.Main, -) : Presenter, - LifecycleOwner { - var view: T? = null - private set - - private val compositeSubscription = CompositeSubscription() - private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(this) - private lateinit var job: Job - protected lateinit var scope: CoroutineScope - - private val coroutineContext: CoroutineContext - get() = job + dispatcher - - override val isAttached: Boolean - get() = view != null - - override fun attach(view: T) { - this.view = view - job = SupervisorJob() - scope = CoroutineScope(coroutineContext) - lifecycleRegistry.currentState = Lifecycle.State.CREATED - lifecycleRegistry.currentState = Lifecycle.State.STARTED - } - - override fun detach() { - lifecycleRegistry.currentState = Lifecycle.State.DESTROYED - this.view = null - compositeSubscription.clear() - job.cancelChildren() - } - - protected fun addSubscription(subscription: Subscription) { - this.compositeSubscription.add(subscription) - } - - fun checkIfAttached() { - if (!isAttached) { - throw ViewNotAttachedException() - } - } - - protected class ViewNotAttachedException : - RuntimeException("Please call Presenter.attach(BaseView) before calling a method on the presenter") - - override val lifecycle: Lifecycle - get() = lifecycleRegistry -} diff --git a/app/src/main/java/com/kelsos/mbrc/common/mvp/BaseView.kt b/app/src/main/java/com/kelsos/mbrc/common/mvp/BaseView.kt deleted file mode 100644 index b8a327ebb..000000000 --- a/app/src/main/java/com/kelsos/mbrc/common/mvp/BaseView.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.kelsos.mbrc.common.mvp - -interface BaseView diff --git a/app/src/main/java/com/kelsos/mbrc/common/mvp/Presenter.kt b/app/src/main/java/com/kelsos/mbrc/common/mvp/Presenter.kt deleted file mode 100644 index 6f72a76ed..000000000 --- a/app/src/main/java/com/kelsos/mbrc/common/mvp/Presenter.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.kelsos.mbrc.common.mvp - -interface Presenter { - fun attach(view: T) - - fun detach() - - val isAttached: Boolean -} diff --git a/app/src/main/java/com/kelsos/mbrc/common/mvvm/BaseViewModel.kt b/app/src/main/java/com/kelsos/mbrc/common/mvvm/BaseViewModel.kt new file mode 100644 index 000000000..9d0c84ea6 --- /dev/null +++ b/app/src/main/java/com/kelsos/mbrc/common/mvvm/BaseViewModel.kt @@ -0,0 +1,16 @@ +package com.kelsos.mbrc.common.mvvm + +import androidx.lifecycle.ViewModel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow + +interface UiMessageBase + +open class BaseViewModel : ViewModel() { + private val backingEvents = MutableSharedFlow() + val events: Flow = backingEvents + + protected suspend fun emit(uiMessage: T) { + backingEvents.emit(uiMessage) + } +} diff --git a/app/src/main/java/com/kelsos/mbrc/common/state/AppState.kt b/app/src/main/java/com/kelsos/mbrc/common/state/AppState.kt new file mode 100644 index 000000000..47f083d46 --- /dev/null +++ b/app/src/main/java/com/kelsos/mbrc/common/state/AppState.kt @@ -0,0 +1,11 @@ +package com.kelsos.mbrc.common.state + +import kotlinx.coroutines.flow.MutableStateFlow + +class AppState { + val playerStatus = MutableStateFlow(PlayerStatusModel()) + val playingTrack = MutableStateFlow(PlayingTrack()) + val playingTrackRating = MutableStateFlow(TrackRating()) + val playingPosition = MutableStateFlow(PlayingPosition()) + val lyrics = MutableStateFlow(emptyList()) +} diff --git a/app/src/main/java/com/kelsos/mbrc/common/state/AppStateManager.kt b/app/src/main/java/com/kelsos/mbrc/common/state/AppStateManager.kt new file mode 100644 index 000000000..1d47bb564 --- /dev/null +++ b/app/src/main/java/com/kelsos/mbrc/common/state/AppStateManager.kt @@ -0,0 +1,134 @@ +package com.kelsos.mbrc.common.state + +import com.kelsos.mbrc.common.utilities.AppCoroutineDispatchers +import com.kelsos.mbrc.platform.mediasession.AppNotificationManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch +import timber.log.Timber +import java.util.Timer +import kotlin.concurrent.fixedRateTimer + +typealias StateHandler = (Boolean) -> Unit + +@OptIn(FlowPreview::class) +class AppStateManager( + private val appState: AppState, + private val connectionState: ConnectionState, + private val notifications: AppNotificationManager, + private val trackCache: PlayingTrackCache, + private val dispatchers: AppCoroutineDispatchers, +) { + private var isRunning = false + private var stateHandler: StateHandler? = null + private var job = SupervisorJob() + private var scope = CoroutineScope(job + dispatchers.io) + var timer: Timer? = null + + init { + scope.launch { + val track = trackCache.restoreInfo() + appState.playingTrack.emit(track) + } + } + + fun start() { + if (isRunning) { + Timber.v("state manager is already running") + return + } + + isRunning = true + + if (job.isCancelled || job.isCompleted) { + job = SupervisorJob() + scope = CoroutineScope(job + dispatchers.io) + } + + val playingPosition = appState.playingPosition + val debouncedPlayerState = + appState.playerStatus + .map { it.state } + .distinctUntilChanged { old, new -> old == new } + .debounce(PLAYER_STATE_DEBOUNCE_MS) + + scope.launch { + debouncedPlayerState.collect { state -> + stateHandler?.invoke(state == PlayerState.Playing) + val currentPosition = playingPosition.map { it.current }.distinctUntilChanged().first() + notifications.updateState(state, currentPosition) + if (state == PlayerState.Playing) { + startPositionUpdater() + } else { + stopPositionUpdater() + } + } + } + + scope.launch { + appState.playingTrack.collect { playingTrack -> + notifications.updatePlayingTrack(playingTrack) + trackCache.persistInfo(playingTrack) + } + } + + scope.launch { + connectionState.connection.collect { connection -> + notifications.connectionStateChanged(connection == ConnectionStatus.Connected) + } + } + + scope.launch { + playingPosition.collect { playingPosition -> + val playerState = appState.playerStatus.map { it.state }.first() + notifications.updateState(playerState, playingPosition.current) + } + } + } + + fun stop() { + job.cancel() + notifications.cancel() + stopPositionUpdater() + isRunning = false + } + + private fun startPositionUpdater() { + stopPositionUpdater() + timer = + fixedRateTimer("progress", period = UPDATE_PERIOD_MS) { + updatePosition() + } + } + + private fun stopPositionUpdater() { + timer?.cancel() + timer?.purge() + } + + private fun updatePosition() { + scope.launch { + val playingPosition = appState.playingPosition + val position = playingPosition.first() + val current = (position.current + UPDATE_PERIOD_MS).coerceAtMost(position.total) + if (current == position.current) { + return@launch + } + playingPosition.emit(position.copy(current = current)) + } + } + + fun setStateHandler(stateHandler: StateHandler? = null) { + this.stateHandler = stateHandler + } + + companion object { + private const val PLAYER_STATE_DEBOUNCE_MS = 600L + private const val UPDATE_PERIOD_MS = 1000L + } +} diff --git a/app/src/main/java/com/kelsos/mbrc/common/state/ConnectionModel.kt b/app/src/main/java/com/kelsos/mbrc/common/state/ConnectionModel.kt deleted file mode 100644 index d62f41ca6..000000000 --- a/app/src/main/java/com/kelsos/mbrc/common/state/ConnectionModel.kt +++ /dev/null @@ -1,47 +0,0 @@ -package com.kelsos.mbrc.common.state - -import com.kelsos.mbrc.annotations.Connection -import com.kelsos.mbrc.annotations.Connection.Status -import com.kelsos.mbrc.events.bus.RxBus -import com.kelsos.mbrc.events.ui.ConnectionStatusChangeEvent -import com.kelsos.mbrc.events.ui.RequestConnectionStateEvent - -class ConnectionModel( - private val bus: RxBus, -) { - var isConnectionActive: Boolean = false - private set - private var isHandShakeDone: Boolean = false - - init { - isConnectionActive = false - isHandShakeDone = false - this.bus.register(this, RequestConnectionStateEvent::class.java) { notifyState() } - } - - val connection: Int - @Status - get() { - if (isConnectionActive && isHandShakeDone) { - return Connection.ACTIVE - } else if (isConnectionActive) { - return Connection.ON - } - - return Connection.OFF - } - - fun setConnectionState(connectionActive: String) { - this.isConnectionActive = java.lang.Boolean.parseBoolean(connectionActive) - notifyState() - } - - private fun notifyState() { - bus.post(ConnectionStatusChangeEvent.create(connection)) - } - - fun setHandShakeDone(handShakeDone: Boolean) { - this.isHandShakeDone = handShakeDone - notifyState() - } -} diff --git a/app/src/main/java/com/kelsos/mbrc/common/state/ConnectionState.kt b/app/src/main/java/com/kelsos/mbrc/common/state/ConnectionState.kt new file mode 100644 index 000000000..91021c87e --- /dev/null +++ b/app/src/main/java/com/kelsos/mbrc/common/state/ConnectionState.kt @@ -0,0 +1,13 @@ +package com.kelsos.mbrc.common.state + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.firstOrNull + +class ConnectionState { + val connection = MutableStateFlow(ConnectionStatus.Offline) + + suspend fun isConnected(): Boolean { + val connectionStatus = connection.firstOrNull() + return connectionStatus is ConnectionStatus.Connected + } +} diff --git a/app/src/main/java/com/kelsos/mbrc/common/state/ConnectionStatus.kt b/app/src/main/java/com/kelsos/mbrc/common/state/ConnectionStatus.kt new file mode 100644 index 000000000..3e5e6d352 --- /dev/null +++ b/app/src/main/java/com/kelsos/mbrc/common/state/ConnectionStatus.kt @@ -0,0 +1,11 @@ +package com.kelsos.mbrc.common.state + +sealed class ConnectionStatus( + val status: String, +) { + object Offline : ConnectionStatus("Offline") + + object Authenticating : ConnectionStatus("Authenticating") + + object Connected : ConnectionStatus("Connected") +} diff --git a/app/src/main/java/com/kelsos/mbrc/common/state/LfmRating.kt b/app/src/main/java/com/kelsos/mbrc/common/state/LfmRating.kt new file mode 100644 index 000000000..3c7f7abf5 --- /dev/null +++ b/app/src/main/java/com/kelsos/mbrc/common/state/LfmRating.kt @@ -0,0 +1,21 @@ +package com.kelsos.mbrc.common.state + +sealed class LfmRating { + data object Loved : LfmRating() + + data object Banned : LfmRating() + + data object Normal : LfmRating() + + companion object { + private const val LOVE = "Love" + private const val BAN = "Ban" + + fun fromString(value: String?): LfmRating = + when (value) { + LOVE -> Loved + BAN -> Banned + else -> Normal + } + } +} diff --git a/app/src/main/java/com/kelsos/mbrc/common/state/MainDataModel.kt b/app/src/main/java/com/kelsos/mbrc/common/state/MainDataModel.kt deleted file mode 100644 index 9833de47c..000000000 --- a/app/src/main/java/com/kelsos/mbrc/common/state/MainDataModel.kt +++ /dev/null @@ -1,92 +0,0 @@ -package com.kelsos.mbrc.common.state - -import com.kelsos.mbrc.annotations.PlayerState -import com.kelsos.mbrc.annotations.PlayerState.State -import com.kelsos.mbrc.annotations.Repeat -import com.kelsos.mbrc.annotations.Repeat.Mode -import com.kelsos.mbrc.constants.Const -import com.kelsos.mbrc.events.ui.ShuffleChange -import com.kelsos.mbrc.events.ui.ShuffleChange.ShuffleState -import com.kelsos.mbrc.features.player.LfmStatus -import com.kelsos.mbrc.features.player.TrackInfo -import com.kelsos.mbrc.networking.protocol.Protocol - -class MainDataModel { - var pluginUpdateAvailable: Boolean = false - var pluginUpdateRequired: Boolean = false - var minimumRequired: String = "" - var position: Long = 0 - var duration: Long = 0 - var trackInfo: TrackInfo = TrackInfo() - var coverPath: String = "" - var rating: Float = 0f - var volume: Int = 0 - - @ShuffleState - var shuffle: String = ShuffleChange.OFF - var isScrobblingEnabled: Boolean = false - var isMute: Boolean = false - var lfmStatus: LfmStatus = LfmStatus.NORMAL - private set - var apiOutOfDate: Boolean = false - private set - - var pluginVersion: String = "1.0.0" - set(value) { - if (value.isEmpty()) { - return - } - field = value.substring(0, value.lastIndexOf('.')) - } - - var pluginProtocol: Int = 2 - set(value) { - field = value - if (value < Protocol.PROTOCOL_VERSION_NUMBER) { - apiOutOfDate = true - } - } - - @State - var playState: String = PlayerState.UNDEFINED - set(value) { - @State val newState: String = - when { - Const.PLAYING.equals(value, ignoreCase = true) -> PlayerState.PLAYING - Const.STOPPED.equals(value, ignoreCase = true) -> PlayerState.STOPPED - Const.PAUSED.equals(value, ignoreCase = true) -> PlayerState.PAUSED - else -> PlayerState.UNDEFINED - } - field = newState - } - - @Mode - var repeat: String - private set - - init { - repeat = Repeat.NONE - rating = 0f - - lfmStatus = LfmStatus.NORMAL - pluginVersion = Const.EMPTY - } - - fun setLfmRating(rating: String) { - lfmStatus = - when (rating) { - "Love" -> LfmStatus.LOVED - "Ban" -> LfmStatus.BANNED - else -> LfmStatus.NORMAL - } - } - - fun setRepeatState(repeat: String) { - this.repeat = - when { - Protocol.ALL.equals(repeat, ignoreCase = true) -> Repeat.ALL - Protocol.ONE.equals(repeat, ignoreCase = true) -> Repeat.ONE - else -> Repeat.NONE - } - } -} diff --git a/app/src/main/java/com/kelsos/mbrc/common/state/NowPlayingTrack.kt b/app/src/main/java/com/kelsos/mbrc/common/state/NowPlayingTrack.kt new file mode 100644 index 000000000..a747426db --- /dev/null +++ b/app/src/main/java/com/kelsos/mbrc/common/state/NowPlayingTrack.kt @@ -0,0 +1,18 @@ +package com.kelsos.mbrc.common.state + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class NowPlayingTrack( + @Json(name = "artist") + val artist: String, + @Json(name = "album") + val album: String, + @Json(name = "title") + val title: String, + @Json(name = "year") + val year: String, + @Json(name = "path") + val path: String, +) diff --git a/app/src/main/java/com/kelsos/mbrc/common/state/PlayerState.kt b/app/src/main/java/com/kelsos/mbrc/common/state/PlayerState.kt new file mode 100644 index 000000000..763d98b89 --- /dev/null +++ b/app/src/main/java/com/kelsos/mbrc/common/state/PlayerState.kt @@ -0,0 +1,28 @@ +package com.kelsos.mbrc.common.state + +sealed class PlayerState( + val state: String, +) { + object Playing : PlayerState(PLAYING) + + object Paused : PlayerState(PAUSED) + + object Stopped : PlayerState(STOPPED) + + object Undefined : PlayerState(UNDEFINED) + + companion object { + const val PLAYING = "playing" + const val PAUSED = "paused" + const val STOPPED = "stopped" + const val UNDEFINED = "undefined" + + fun fromString(state: String?): PlayerState = + when (state?.lowercase()) { + PLAYING -> Playing + PAUSED -> Paused + STOPPED -> Stopped + else -> Undefined + } + } +} diff --git a/app/src/main/java/com/kelsos/mbrc/common/state/PlayerStatus.kt b/app/src/main/java/com/kelsos/mbrc/common/state/PlayerStatus.kt new file mode 100644 index 000000000..ea0ddaf50 --- /dev/null +++ b/app/src/main/java/com/kelsos/mbrc/common/state/PlayerStatus.kt @@ -0,0 +1,21 @@ +package com.kelsos.mbrc.common.state + +import com.kelsos.mbrc.networking.protocol.Protocol +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class PlayerStatus( + @Json(name = Protocol.PLAYER_MUTE) + val mute: Boolean, + @Json(name = Protocol.PLAYER_STATE) + val playState: String, + @Json(name = Protocol.PLAYER_REPEAT) + val repeat: String, + @Json(name = Protocol.PLAYER_SHUFFLE) + val shuffle: String, + @Json(name = Protocol.PLAYER_SCROBBLE) + val scrobbling: Boolean, + @Json(name = Protocol.PLAYER_VOLUME) + val volume: Int, +) diff --git a/app/src/main/java/com/kelsos/mbrc/common/state/PlayerStatusModel.kt b/app/src/main/java/com/kelsos/mbrc/common/state/PlayerStatusModel.kt new file mode 100644 index 000000000..c0eb91e95 --- /dev/null +++ b/app/src/main/java/com/kelsos/mbrc/common/state/PlayerStatusModel.kt @@ -0,0 +1,13 @@ +package com.kelsos.mbrc.common.state + +import androidx.annotation.IntRange + +data class PlayerStatusModel( + @get:IntRange(from = 0, to = 100) + val volume: Int = 0, + val mute: Boolean = false, + val shuffle: ShuffleMode = ShuffleMode.Off, + val scrobbling: Boolean = false, + val repeat: Repeat = Repeat.None, + val state: PlayerState = PlayerState.Undefined, +) diff --git a/app/src/main/java/com/kelsos/mbrc/common/state/PlayingPosition.kt b/app/src/main/java/com/kelsos/mbrc/common/state/PlayingPosition.kt new file mode 100644 index 000000000..ce7f41005 --- /dev/null +++ b/app/src/main/java/com/kelsos/mbrc/common/state/PlayingPosition.kt @@ -0,0 +1,23 @@ +package com.kelsos.mbrc.common.state + +data class PlayingPosition( + val current: Duration = 0, + val total: Duration = 0, +) { + val totalMinutes get() = total.toMinutes() + val currentMinutes get() = current.toMinutes() + + fun progress(): String = "$currentMinutes / $totalMinutes" +} + +typealias Duration = Long + +private const val SECONDS_IN_MINUTE = 60 +private const val MILLIS_IN_SECONDS = 1000 + +fun Duration.toMinutes(): String { + val inSeconds = this / MILLIS_IN_SECONDS + val minutes = inSeconds / SECONDS_IN_MINUTE + val seconds = inSeconds % SECONDS_IN_MINUTE + return "%02d:%02d".format(minutes, seconds) +} diff --git a/app/src/main/java/com/kelsos/mbrc/common/state/PlayingTrack.kt b/app/src/main/java/com/kelsos/mbrc/common/state/PlayingTrack.kt new file mode 100644 index 000000000..43ac23862 --- /dev/null +++ b/app/src/main/java/com/kelsos/mbrc/common/state/PlayingTrack.kt @@ -0,0 +1,49 @@ +package com.kelsos.mbrc.common.state + +import android.os.Parcel +import android.os.Parcelable + +data class PlayingTrack( + val artist: String = "", + val title: String = "", + val album: String = "", + val year: String = "", + val path: String = "", + val coverUrl: String = "", + val duration: Long = 0, +) : Parcelable { + companion object { + @JvmField + val CREATOR: Parcelable.Creator = + object : Parcelable.Creator { + override fun createFromParcel(source: Parcel): PlayingTrack = PlayingTrack(source) + + override fun newArray(size: Int): Array = arrayOfNulls(size) + } + } + + constructor(source: Parcel) : this( + source.readString().orEmpty(), + source.readString().orEmpty(), + source.readString().orEmpty(), + source.readString().orEmpty(), + source.readString().orEmpty(), + source.readString().orEmpty(), + source.readLong(), + ) + + override fun describeContents() = 0 + + override fun writeToParcel( + dest: Parcel, + flags: Int, + ) { + dest.writeString(artist) + dest.writeString(title) + dest.writeString(album) + dest.writeString(year) + dest.writeString(path) + dest.writeString(coverUrl) + dest.writeLong(duration) + } +} diff --git a/app/src/main/java/com/kelsos/mbrc/features/player/ModelCache.kt b/app/src/main/java/com/kelsos/mbrc/common/state/PlayingTrackCache.kt similarity index 58% rename from app/src/main/java/com/kelsos/mbrc/features/player/ModelCache.kt rename to app/src/main/java/com/kelsos/mbrc/common/state/PlayingTrackCache.kt index 055542c86..174a86d52 100644 --- a/app/src/main/java/com/kelsos/mbrc/features/player/ModelCache.kt +++ b/app/src/main/java/com/kelsos/mbrc/common/state/PlayingTrackCache.kt @@ -1,9 +1,12 @@ -package com.kelsos.mbrc.features.player +package com.kelsos.mbrc.common.state import android.app.Application import android.content.Context +import androidx.datastore.core.CorruptionException import androidx.datastore.core.DataStore +import androidx.datastore.core.Serializer import androidx.datastore.dataStore +import com.google.protobuf.InvalidProtocolBufferException import com.kelsos.mbrc.common.utilities.AppCoroutineDispatchers import com.kelsos.mbrc.store.Store import com.kelsos.mbrc.store.Track @@ -13,39 +16,51 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.withContext import timber.log.Timber import java.io.IOException +import java.io.InputStream +import java.io.OutputStream internal val Context.cacheDataStore: DataStore by dataStore( fileName = "cache_store.db", serializer = PlayerStateSerializer, ) -class ModelCacheImpl( +interface PlayingTrackCache { + suspend fun persistInfo(playingTrack: PlayingTrack) + + suspend fun restoreInfo(): PlayingTrack + + suspend fun persistCover(cover: String) + + suspend fun restoreCover(): String +} + +class PlayingTrackCacheImpl( private val context: Application, private val dispatchers: AppCoroutineDispatchers, -) : ModelCache { +) : PlayingTrackCache { private val storeFlow: Flow = context.cacheDataStore.data .catch { exception -> // dataStore.data throws an IOException when an error is encountered when reading data if (exception is IOException) { - Timber.Forest.e(exception, "Error reading sort order preferences.") + Timber.e(exception, "Error reading sort order preferences.") emit(Store.getDefaultInstance()) } else { throw exception } } - override suspend fun persistInfo(trackInfo: TrackInfo) = + override suspend fun persistInfo(playingTrack: PlayingTrack) { withContext(dispatchers.io) { context.cacheDataStore.updateData { store -> val track = Track .newBuilder() - .setAlbum(trackInfo.album) - .setArtist(trackInfo.artist) - .setPath(trackInfo.path) - .setTitle(trackInfo.title) - .setYear(trackInfo.year) + .setAlbum(playingTrack.album) + .setArtist(playingTrack.artist) + .setPath(playingTrack.path) + .setTitle(playingTrack.title) + .setYear(playingTrack.year) .build() store @@ -55,12 +70,13 @@ class ModelCacheImpl( } return@withContext } + } - override suspend fun restoreInfo(): TrackInfo = + override suspend fun restoreInfo(): PlayingTrack = withContext(dispatchers.io) { val track = storeFlow.first().track - return@withContext TrackInfo( + return@withContext PlayingTrack( track.artist, track.title, track.album, @@ -78,12 +94,22 @@ class ModelCacheImpl( override suspend fun restoreCover(): String = storeFlow.first().cover } -interface ModelCache { - suspend fun persistInfo(trackInfo: TrackInfo) - - suspend fun restoreInfo(): TrackInfo +object PlayerStateSerializer : Serializer { + override suspend fun readFrom(input: InputStream): Store { + try { + return Store.parseFrom(input) + } catch (exception: InvalidProtocolBufferException) { + throw CorruptionException("Cannot read proto.", exception) + } + } - suspend fun persistCover(cover: String) + override suspend fun writeTo( + t: Store, + output: OutputStream, + ) { + t.writeTo(output) + } - suspend fun restoreCover(): String + override val defaultValue: Store + get() = Store.getDefaultInstance() } diff --git a/app/src/main/java/com/kelsos/mbrc/common/state/Repeat.kt b/app/src/main/java/com/kelsos/mbrc/common/state/Repeat.kt new file mode 100644 index 000000000..96c296c6f --- /dev/null +++ b/app/src/main/java/com/kelsos/mbrc/common/state/Repeat.kt @@ -0,0 +1,24 @@ +package com.kelsos.mbrc.common.state + +sealed class Repeat( + val mode: String, +) { + object All : Repeat(ALL) + + object None : Repeat(NONE) + + object One : Repeat(ONE) + + companion object { + const val ALL = "all" + const val NONE = "none" + const val ONE = "one" + + fun fromString(mode: String?): Repeat = + when (mode?.lowercase()) { + ALL -> All + ONE -> One + else -> None + } + } +} diff --git a/app/src/main/java/com/kelsos/mbrc/common/state/ShuffleMode.kt b/app/src/main/java/com/kelsos/mbrc/common/state/ShuffleMode.kt new file mode 100644 index 000000000..7338b702a --- /dev/null +++ b/app/src/main/java/com/kelsos/mbrc/common/state/ShuffleMode.kt @@ -0,0 +1,24 @@ +package com.kelsos.mbrc.common.state + +sealed class ShuffleMode( + val mode: String, +) { + data object Off : ShuffleMode(OFF) + + data object AutoDJ : ShuffleMode(AUTO_DJ) + + data object Shuffle : ShuffleMode(SHUFFLE) + + companion object { + const val OFF = "off" + const val AUTO_DJ = "autodj" + const val SHUFFLE = "shuffle" + + fun fromString(string: String?): ShuffleMode = + when (string?.lowercase()) { + AUTO_DJ -> AutoDJ + SHUFFLE -> Shuffle + else -> Off + } + } +} diff --git a/app/src/main/java/com/kelsos/mbrc/common/state/TrackRating.kt b/app/src/main/java/com/kelsos/mbrc/common/state/TrackRating.kt new file mode 100644 index 000000000..b069bfc6d --- /dev/null +++ b/app/src/main/java/com/kelsos/mbrc/common/state/TrackRating.kt @@ -0,0 +1,8 @@ +package com.kelsos.mbrc.common.state + +data class TrackRating( + val lfmRating: LfmRating = LfmRating.Normal, + val rating: Float = 0f, +) { + fun isFavorite(): Boolean = lfmRating == LfmRating.Loved +} diff --git a/app/src/main/java/com/kelsos/mbrc/common/ui/CircleImageView.kt b/app/src/main/java/com/kelsos/mbrc/common/ui/CircleImageView.kt index 6d993bbd7..a05448df0 100644 --- a/app/src/main/java/com/kelsos/mbrc/common/ui/CircleImageView.kt +++ b/app/src/main/java/com/kelsos/mbrc/common/ui/CircleImageView.kt @@ -36,21 +36,22 @@ import androidx.annotation.DrawableRes import androidx.appcompat.widget.AppCompatImageView import androidx.core.content.ContextCompat import com.kelsos.mbrc.R +import timber.log.Timber class CircleImageView : AppCompatImageView { - private val mDrawableRect = RectF() - private val mBorderRect = RectF() + private val drawableRect = RectF() + private val borderRect = RectF() - private val mShaderMatrix = Matrix() - private val mBitmapPaint = Paint() - private val mBorderPaint = Paint() - private val mFillPaint = Paint() + private val shaderMatrix = Matrix() + private val bitmapPaint = Paint() + private val borderPaint = Paint() + private val fillPaint = Paint() private var mBorderColor = DEFAULT_BORDER_COLOR private var mBorderWidth = DEFAULT_BORDER_WIDTH private var mFillColor = DEFAULT_FILL_COLOR - private var mBitmap: Bitmap? = null + private var bitmap: Bitmap? = null private var mBitmapShader: BitmapShader? = null private var mBitmapWidth: Int = 0 private var mBitmapHeight: Int = 0 @@ -112,15 +113,11 @@ class CircleImageView : AppCompatImageView { override fun getScaleType(): ScaleType = SCALE_TYPE override fun setScaleType(scaleType: ScaleType) { - if (scaleType != SCALE_TYPE) { - throw IllegalArgumentException(String.format("ScaleType %s not supported.", scaleType)) - } + require(scaleType == SCALE_TYPE) { "ScaleType $scaleType not supported." } } override fun setAdjustViewBounds(adjustViewBounds: Boolean) { - if (adjustViewBounds) { - throw IllegalArgumentException("adjustViewBounds not supported.") - } + require(!adjustViewBounds) { "adjustViewBounds not supported." } } override fun onDraw(canvas: Canvas) { @@ -129,26 +126,26 @@ class CircleImageView : AppCompatImageView { return } - if (mBitmap == null) { + if (bitmap == null) { return } if (mFillColor != Color.TRANSPARENT) { canvas.drawCircle( - mDrawableRect.centerX(), - mDrawableRect.centerY(), + drawableRect.centerX(), + drawableRect.centerY(), mDrawableRadius, - mFillPaint, + fillPaint, ) } canvas.drawCircle( - mDrawableRect.centerX(), - mDrawableRect.centerY(), + drawableRect.centerX(), + drawableRect.centerY(), mDrawableRadius, - mBitmapPaint, + bitmapPaint, ) if (mBorderWidth != 0) { - canvas.drawCircle(mBorderRect.centerX(), mBorderRect.centerY(), mBorderRadius, mBorderPaint) + canvas.drawCircle(borderRect.centerX(), borderRect.centerY(), mBorderRadius, borderPaint) } } @@ -172,7 +169,7 @@ class CircleImageView : AppCompatImageView { } mBorderColor = borderColor - mBorderPaint.color = mBorderColor + borderPaint.color = mBorderColor invalidate() } @@ -192,7 +189,7 @@ class CircleImageView : AppCompatImageView { } mFillColor = fillColor - mFillPaint.color = fillColor + fillPaint.color = fillColor invalidate() } @@ -256,10 +253,10 @@ class CircleImageView : AppCompatImageView { invalidate() } - override fun getColorFilter(): ColorFilter = mColorFilter!! + override fun getColorFilter(): ColorFilter = requireNotNull(mColorFilter) private fun applyColorFilter() { - mBitmapPaint.colorFilter = mColorFilter + bitmapPaint.colorFilter = mColorFilter } private fun getBitmapFromDrawable(drawable: Drawable?): Bitmap? { @@ -272,31 +269,28 @@ class CircleImageView : AppCompatImageView { } try { - val bitmap: Bitmap - - if (drawable is ColorDrawable) { - bitmap = - Bitmap.createBitmap(COLORDRAWABLE_DIMENSION, COLORDRAWABLE_DIMENSION, BITMAP_CONFIG) - } else { - bitmap = + val bitmap: Bitmap = + if (drawable is ColorDrawable) { + Bitmap.createBitmap(COLOR_DRAWABLE_DIMENSION, COLOR_DRAWABLE_DIMENSION, BITMAP_CONFIG) + } else { Bitmap.createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight, BITMAP_CONFIG) - } + } val canvas = Canvas(bitmap) drawable.setBounds(0, 0, canvas.width, canvas.height) drawable.draw(canvas) return bitmap - } catch (e: Exception) { - e.printStackTrace() + } catch (e: IllegalArgumentException) { + Timber.e(e) return null } } private fun initializeBitmap() { if (isDisableCircularTransformation) { - mBitmap = null + bitmap = null } else { - mBitmap = getBitmapFromDrawable(drawable) + bitmap = getBitmapFromDrawable(drawable) } setup() } @@ -311,40 +305,38 @@ class CircleImageView : AppCompatImageView { return } - if (mBitmap == null) { + val bmp = bitmap + if (bmp == null) { invalidate() return } - mBitmapShader = BitmapShader(mBitmap!!, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP) + mBitmapShader = BitmapShader(bmp, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP) - mBitmapPaint.isAntiAlias = true - mBitmapPaint.shader = mBitmapShader + bitmapPaint.isAntiAlias = true + bitmapPaint.shader = mBitmapShader - mBorderPaint.style = Paint.Style.STROKE - mBorderPaint.isAntiAlias = true - mBorderPaint.color = mBorderColor - mBorderPaint.strokeWidth = mBorderWidth.toFloat() + borderPaint.style = Paint.Style.STROKE + borderPaint.isAntiAlias = true + borderPaint.color = mBorderColor + borderPaint.strokeWidth = mBorderWidth.toFloat() - mFillPaint.style = Paint.Style.FILL - mFillPaint.isAntiAlias = true - mFillPaint.color = mFillColor + fillPaint.style = Paint.Style.FILL + fillPaint.isAntiAlias = true + fillPaint.color = mFillColor - mBitmapHeight = mBitmap!!.height - mBitmapWidth = mBitmap!!.width + mBitmapHeight = bmp.height + mBitmapWidth = bmp.width - mBorderRect.set(calculateBounds()) + borderRect.set(calculateBounds()) mBorderRadius = - Math.min( - (mBorderRect.height() - mBorderWidth) / 2.0f, - (mBorderRect.width() - mBorderWidth) / 2.0f, - ) + ((borderRect.height() - mBorderWidth) / 2.0f).coerceAtMost((borderRect.width() - mBorderWidth) / 2.0f) - mDrawableRect.set(mBorderRect) + drawableRect.set(borderRect) if (!mBorderOverlay) { - mDrawableRect.inset(mBorderWidth.toFloat(), mBorderWidth.toFloat()) + drawableRect.inset(mBorderWidth.toFloat(), mBorderWidth.toFloat()) } - mDrawableRadius = Math.min(mDrawableRect.height() / 2.0f, mDrawableRect.width() / 2.0f) + mDrawableRadius = (drawableRect.height() / 2.0f).coerceAtMost(drawableRect.width() / 2.0f) applyColorFilter() updateShaderMatrix() @@ -355,7 +347,7 @@ class CircleImageView : AppCompatImageView { val availableWidth = width - paddingLeft - paddingRight val availableHeight = height - paddingTop - paddingBottom - val sideLength = Math.min(availableWidth, availableHeight) + val sideLength = availableWidth.coerceAtMost(availableHeight) val left = paddingLeft + (availableWidth - sideLength) / 2f val top = paddingTop + (availableHeight - sideLength) / 2f @@ -368,34 +360,35 @@ class CircleImageView : AppCompatImageView { var dx = 0f var dy = 0f - mShaderMatrix.set(null) + shaderMatrix.set(null) - if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) { - scale = mDrawableRect.height() / mBitmapHeight.toFloat() - dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f + if (mBitmapWidth * drawableRect.height() > drawableRect.width() * mBitmapHeight) { + scale = drawableRect.height() / mBitmapHeight.toFloat() + dx = (drawableRect.width() - mBitmapWidth * scale) * HALF } else { - scale = mDrawableRect.width() / mBitmapWidth.toFloat() - dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f + scale = drawableRect.width() / mBitmapWidth.toFloat() + dy = (drawableRect.height() - mBitmapHeight * scale) * HALF } - mShaderMatrix.setScale(scale, scale) - mShaderMatrix.postTranslate( - (dx + 0.5f).toInt() + mDrawableRect.left, - (dy + 0.5f).toInt() + mDrawableRect.top, + shaderMatrix.setScale(scale, scale) + shaderMatrix.postTranslate( + (dx + HALF).toInt() + drawableRect.left, + (dy + HALF).toInt() + drawableRect.top, ) - mBitmapShader!!.setLocalMatrix(mShaderMatrix) + requireNotNull(mBitmapShader).setLocalMatrix(shaderMatrix) } companion object { private val SCALE_TYPE = ScaleType.CENTER_CROP private val BITMAP_CONFIG = Bitmap.Config.ARGB_8888 - private val COLORDRAWABLE_DIMENSION = 2 + private const val COLOR_DRAWABLE_DIMENSION = 2 - private val DEFAULT_BORDER_WIDTH = 0 - private val DEFAULT_BORDER_COLOR = Color.BLACK - private val DEFAULT_FILL_COLOR = Color.TRANSPARENT - private val DEFAULT_BORDER_OVERLAY = false + private const val DEFAULT_BORDER_WIDTH = 0 + private const val DEFAULT_BORDER_COLOR = Color.BLACK + private const val DEFAULT_FILL_COLOR = Color.TRANSPARENT + private const val DEFAULT_BORDER_OVERLAY = false + private const val HALF = 0.5f } } diff --git a/app/src/main/java/com/kelsos/mbrc/common/ui/MultiSwipeRefreshLayout.kt b/app/src/main/java/com/kelsos/mbrc/common/ui/MultiSwipeRefreshLayout.kt index 6b16bf646..1c5b06341 100644 --- a/app/src/main/java/com/kelsos/mbrc/common/ui/MultiSwipeRefreshLayout.kt +++ b/app/src/main/java/com/kelsos/mbrc/common/ui/MultiSwipeRefreshLayout.kt @@ -22,7 +22,7 @@ import android.view.View import androidx.swiperefreshlayout.widget.SwipeRefreshLayout /** - * A descendant of [android.support.v4.widget.SwipeRefreshLayout] which supports multiple + * A descendant of [androidx.swiperefreshlayout.widget.SwipeRefreshLayout] which supports multiple * child views triggering a refresh gesture. You set the views which can trigger the gesture via * [.setSwipeableChildren], providing it the child ids. */ @@ -54,7 +54,7 @@ class MultiSwipeRefreshLayout : SwipeRefreshLayout { * we are signifying that the view is in a state where a refresh gesture can start. * - * As [android.support.v4.widget.SwipeRefreshLayout] only supports one direct child by + * As [androidx.swiperefreshlayout.widget.SwipeRefreshLayout] only supports one direct child by * default, we need to manually iterate through our swipeable children to see if any are in a * state to trigger the gesture. If so we return false to start the gesture. */ diff --git a/app/src/main/java/com/kelsos/mbrc/common/utilities/AppCoroutineDispatchers.kt b/app/src/main/java/com/kelsos/mbrc/common/utilities/AppCoroutineDispatchers.kt index f21400f25..c5f347ba3 100644 --- a/app/src/main/java/com/kelsos/mbrc/common/utilities/AppCoroutineDispatchers.kt +++ b/app/src/main/java/com/kelsos/mbrc/common/utilities/AppCoroutineDispatchers.kt @@ -2,9 +2,9 @@ package com.kelsos.mbrc.common.utilities import kotlinx.coroutines.CoroutineDispatcher -data class AppCoroutineDispatchers( - val main: CoroutineDispatcher, - val io: CoroutineDispatcher, - val database: CoroutineDispatcher, - val network: CoroutineDispatcher, -) +interface AppCoroutineDispatchers { + val main: CoroutineDispatcher + val io: CoroutineDispatcher + val database: CoroutineDispatcher + val network: CoroutineDispatcher +} diff --git a/app/src/main/java/com/kelsos/mbrc/common/utilities/Helpers.kt b/app/src/main/java/com/kelsos/mbrc/common/utilities/Helpers.kt index 7fcd5fbc1..f347432e1 100644 --- a/app/src/main/java/com/kelsos/mbrc/common/utilities/Helpers.kt +++ b/app/src/main/java/com/kelsos/mbrc/common/utilities/Helpers.kt @@ -1,5 +1,35 @@ package com.kelsos.mbrc.common.utilities +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import androidx.paging.PagingSource +import androidx.paging.map +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import java.time.Instant + +fun paged( + pagingSourceFactory: () -> PagingSource, + transform: (value: T) -> I, +): Flow> { + val config = + PagingConfig( + enablePlaceholders = true, + pageSize = 60, + maxSize = 200, + ) + return Pager( + config, + pagingSourceFactory = pagingSourceFactory, + ).flow.map { data -> data.map(transform) } +} + +/** + * [Instant.getEpochSecond] for [Instant.now] + */ +fun epoch(): Long = Instant.now().epochSecond + inline fun whenNotNull( p1: T1?, p2: T2?, diff --git a/app/src/main/java/com/kelsos/mbrc/common/utilities/RemoteUtils.kt b/app/src/main/java/com/kelsos/mbrc/common/utilities/RemoteUtils.kt index b7894eec8..48a73257c 100644 --- a/app/src/main/java/com/kelsos/mbrc/common/utilities/RemoteUtils.kt +++ b/app/src/main/java/com/kelsos/mbrc/common/utilities/RemoteUtils.kt @@ -1,48 +1,20 @@ package com.kelsos.mbrc.common.utilities -import android.content.Context -import android.content.pm.PackageManager import android.graphics.Bitmap import android.graphics.BitmapFactory -import androidx.core.content.pm.PackageInfoCompat -import java.security.MessageDigest +import com.kelsos.mbrc.BuildConfig object RemoteUtils { - @Throws(PackageManager.NameNotFoundException::class) - fun Context.getVersion(): String? = packageManager.getPackageInfo(packageName, 0).versionName - - @Throws(PackageManager.NameNotFoundException::class) - fun Context.getVersionCode(): Long = PackageInfoCompat.getLongVersionCode(packageManager.getPackageInfo(packageName, 0)) - - fun coverBitmapSync(coverPath: String): Bitmap? { - return try { - val options = BitmapFactory.Options() - options.inPreferredConfig = Bitmap.Config.RGB_565 - return BitmapFactory.decodeFile(coverPath, options) - } catch (e: Exception) { - null + const val VERSION: String = BuildConfig.VERSION_NAME + const val VERSION_CODE: Int = BuildConfig.VERSION_CODE + + fun loadBitmap(path: String): Result = + runCatching { + BitmapFactory.decodeFile( + path, + BitmapFactory.Options().apply { + inPreferredConfig = Bitmap.Config.RGB_565 + }, + ) } - } - - fun sha1(input: String) = hashString("SHA-1", input) - - private fun hashString( - type: String, - input: String, - ): String { - val hexChars = "0123456789ABCDEF" - val bytes = - MessageDigest - .getInstance(type) - .digest(input.toByteArray()) - val result = StringBuilder(bytes.size * 2) - - bytes.forEach { - val i = it.toInt() - result.append(hexChars[i shr 4 and 0x0f]) - result.append(hexChars[i and 0x0f]) - } - - return result.toString() - } } diff --git a/app/src/main/java/com/kelsos/mbrc/constants/ApplicationEvents.kt b/app/src/main/java/com/kelsos/mbrc/constants/ApplicationEvents.kt deleted file mode 100644 index 74e6bf402..000000000 --- a/app/src/main/java/com/kelsos/mbrc/constants/ApplicationEvents.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.kelsos.mbrc.constants - -object ApplicationEvents { - const val SOCKET_DATA_AVAILABLE = "SocketDataAvailable" - const val SOCKET_STATUS_CHANGED = "SocketStatusChanged" - const val SOCKET_HANDSHAKE_UPDATE = "SocketHandshakeUpdate" - const val TERMINATE_SERVICE = "TerminateService" -} diff --git a/app/src/main/java/com/kelsos/mbrc/constants/Const.kt b/app/src/main/java/com/kelsos/mbrc/constants/Const.kt deleted file mode 100644 index ad050faf6..000000000 --- a/app/src/main/java/com/kelsos/mbrc/constants/Const.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.kelsos.mbrc.constants - -object Const { - const val EMPTY = "" - const val LYRICS_NEWLINE = "\r\n|\n" - const val DATA = "data" - const val TOGGLE = "toggle" - const val PLAYING = "playing" - const val PAUSED = "paused" - const val STOPPED = "stopped" - const val NEWLINE = "\r\n" - const val UTF_8 = "UTF-8" -} diff --git a/app/src/main/java/com/kelsos/mbrc/constants/ProtocolEventType.kt b/app/src/main/java/com/kelsos/mbrc/constants/ProtocolEventType.kt deleted file mode 100644 index ceeec70e1..000000000 --- a/app/src/main/java/com/kelsos/mbrc/constants/ProtocolEventType.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.kelsos.mbrc.constants - -object ProtocolEventType { - const val INITIATE_PROTOCOL_REQUEST = "InitiateProtocolRequest" - const val REDUCE_VOLUME = "ReduceVolume" - const val HANDSHAKE_COMPLETE = "HandshakeComplete" - const val INFORM_CLIENT_NOT_ALLOWED = "InformClientNotAllowed" - const val PLUGIN_UPDATE_AVAILABLE = "PluginUpdateAvailable" - const val PLUGIN_UPDATE_REQUIRED = "PluginUpdateRequired" - const val USER_ACTION = "UserAction" - const val PLUGIN_VERSION_CHECK = "PluginVersionCheck" -} diff --git a/app/src/main/java/com/kelsos/mbrc/constants/UserInputEventType.kt b/app/src/main/java/com/kelsos/mbrc/constants/UserInputEventType.kt deleted file mode 100644 index 7713ad6a3..000000000 --- a/app/src/main/java/com/kelsos/mbrc/constants/UserInputEventType.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.kelsos.mbrc.constants - -object UserInputEventType { - const val START_CONNECTION = "StartConnection" - const val SETTINGS_CHANGED = "SettingsChanged" - const val RESET_CONNECTION = "ResetConnection" - const val CANCEL_NOTIFICATION = "CancelNotification" - const val START_DISCOVERY = "StartDiscovery" - const val KEY_VOLUME_UP = "KeyVolumeUp" - const val KEY_VOLUME_DOWN = "KeyVolumeDown" - const val TERMINATE_CONNECTION = "TerminateConnection" -} diff --git a/app/src/main/java/com/kelsos/mbrc/data/Data.kt b/app/src/main/java/com/kelsos/mbrc/data/Data.kt deleted file mode 100644 index 7979c974b..000000000 --- a/app/src/main/java/com/kelsos/mbrc/data/Data.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.kelsos.mbrc.data - -interface Data diff --git a/app/src/main/java/com/kelsos/mbrc/data/Database.kt b/app/src/main/java/com/kelsos/mbrc/data/Database.kt index 3924d8ba6..0005a79ab 100644 --- a/app/src/main/java/com/kelsos/mbrc/data/Database.kt +++ b/app/src/main/java/com/kelsos/mbrc/data/Database.kt @@ -1,73 +1,57 @@ package com.kelsos.mbrc.data -import com.kelsos.mbrc.features.library.Album -import com.kelsos.mbrc.features.library.Artist -import com.kelsos.mbrc.features.library.Genre -import com.kelsos.mbrc.features.library.Track -import com.kelsos.mbrc.features.nowplaying.NowPlaying -import com.kelsos.mbrc.features.playlists.Playlist -import com.raizlabs.android.dbflow.annotation.Migration -import com.raizlabs.android.dbflow.sql.SQLiteType -import com.raizlabs.android.dbflow.sql.migration.AlterTableMigration -import com.raizlabs.android.dbflow.annotation.Database as Db - -@Db(version = Database.VERSION, name = Database.NAME) -object Database { - const val VERSION = 3 - const val NAME = "cache" - - @Migration(version = 3, database = Database::class) - class Migration3Genre( - table: Class, - ) : AlterTableMigration(table) { - override fun onPreMigrate() { - addColumn(SQLiteType.INTEGER, "date_added") - } - } - - @Migration(version = 3, database = Database::class) - class Migration3Artist( - table: Class, - ) : AlterTableMigration(table) { - override fun onPreMigrate() { - addColumn(SQLiteType.INTEGER, "date_added") - } - } - - @Migration(version = 3, database = Database::class) - class Migration3Album( - table: Class, - ) : AlterTableMigration(table) { - override fun onPreMigrate() { - addColumn(SQLiteType.TEXT, "cover") - addColumn(SQLiteType.INTEGER, "date_added") - } - } - - @Migration(version = 3, database = Database::class) - class Migration3Track( - table: Class, - ) : AlterTableMigration(table) { - override fun onPreMigrate() { - addColumn(SQLiteType.INTEGER, "date_added") - } - } - - @Migration(version = 3, database = Database::class) - class Migration3NowPlaying( - table: Class, - ) : AlterTableMigration(table) { - override fun onPreMigrate() { - addColumn(SQLiteType.INTEGER, "date_added") - } - } - - @Migration(version = 3, database = Database::class) - class Migration3Playlist( - table: Class, - ) : AlterTableMigration(table) { - override fun onPreMigrate() { - addColumn(SQLiteType.INTEGER, "date_added") - } +import androidx.room.Database +import androidx.room.RoomDatabase +import com.kelsos.mbrc.data.Database.Companion.VERSION +import com.kelsos.mbrc.features.library.albums.AlbumDao +import com.kelsos.mbrc.features.library.albums.AlbumEntity +import com.kelsos.mbrc.features.library.artists.ArtistDao +import com.kelsos.mbrc.features.library.artists.ArtistEntity +import com.kelsos.mbrc.features.library.genres.GenreDao +import com.kelsos.mbrc.features.library.genres.GenreEntity +import com.kelsos.mbrc.features.library.tracks.TrackDao +import com.kelsos.mbrc.features.library.tracks.TrackEntity +import com.kelsos.mbrc.features.nowplaying.NowPlayingDao +import com.kelsos.mbrc.features.nowplaying.NowPlayingEntity +import com.kelsos.mbrc.features.playlists.PlaylistDao +import com.kelsos.mbrc.features.playlists.PlaylistEntity +import com.kelsos.mbrc.features.radio.RadioStationDao +import com.kelsos.mbrc.features.radio.RadioStationEntity +import com.kelsos.mbrc.features.settings.ConnectionDao +import com.kelsos.mbrc.features.settings.ConnectionSettingsEntity + +@Database( + entities = [ + GenreEntity::class, + ArtistEntity::class, + AlbumEntity::class, + TrackEntity::class, + NowPlayingEntity::class, + PlaylistEntity::class, + RadioStationEntity::class, + ConnectionSettingsEntity::class, + ], + version = VERSION, +) +abstract class Database : RoomDatabase() { + abstract fun genreDao(): GenreDao + + abstract fun artistDao(): ArtistDao + + abstract fun albumDao(): AlbumDao + + abstract fun trackDao(): TrackDao + + abstract fun nowPlayingDao(): NowPlayingDao + + abstract fun playlistDao(): PlaylistDao + + abstract fun radioStationDao(): RadioStationDao + + abstract fun connectionDao(): ConnectionDao + + companion object { + const val VERSION = 3 + const val NAME = "cache.db" } } diff --git a/app/src/main/java/com/kelsos/mbrc/data/DeserializationAdapter.kt b/app/src/main/java/com/kelsos/mbrc/data/DeserializationAdapter.kt new file mode 100644 index 000000000..70b5dd5a0 --- /dev/null +++ b/app/src/main/java/com/kelsos/mbrc/data/DeserializationAdapter.kt @@ -0,0 +1,50 @@ +package com.kelsos.mbrc.data + +import com.squareup.moshi.Moshi +import java.lang.reflect.ParameterizedType +import kotlin.reflect.KClass + +interface DeserializationAdapter { + fun objectify( + line: String, + kClass: KClass, + ): T where T : Any + + fun objectify( + line: String, + type: ParameterizedType, + ): T where T : Any + + fun convertValue( + data: Any?, + kClass: KClass, + ): T where T : Any +} + +class DeserializationAdapterImpl( + private val moshi: Moshi, +) : DeserializationAdapter { + override fun objectify( + line: String, + type: ParameterizedType, + ): T { + val adapter = moshi.adapter(type) + return checkNotNull(adapter.fromJson(line)) { "what?" } + } + + override fun convertValue( + data: Any?, + kClass: KClass, + ): T { + val adapter = moshi.adapter(kClass.java) + return checkNotNull(adapter.fromJsonValue(data)) { "what?" } + } + + override fun objectify( + line: String, + kClass: KClass, + ): T { + val adapter = moshi.adapter(kClass.java) + return checkNotNull(adapter.fromJson(line)) { "what? " } + } +} diff --git a/app/src/main/java/com/kelsos/mbrc/data/SerializationAdapter.kt b/app/src/main/java/com/kelsos/mbrc/data/SerializationAdapter.kt new file mode 100644 index 000000000..316714161 --- /dev/null +++ b/app/src/main/java/com/kelsos/mbrc/data/SerializationAdapter.kt @@ -0,0 +1,17 @@ +package com.kelsos.mbrc.data + +import com.kelsos.mbrc.networking.client.SocketMessage +import com.squareup.moshi.Moshi + +interface SerializationAdapter { + fun stringify(message: SocketMessage): String +} + +class SerializationAdapterImpl( + private val moshi: Moshi, +) : SerializationAdapter { + override fun stringify(message: SocketMessage): String { + val adapter = moshi.adapter(SocketMessage::class.java) + return adapter.toJson(message) + } +} diff --git a/app/src/main/java/com/kelsos/mbrc/data/UserAction.kt b/app/src/main/java/com/kelsos/mbrc/data/UserAction.kt deleted file mode 100644 index 0ec25690b..000000000 --- a/app/src/main/java/com/kelsos/mbrc/data/UserAction.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.kelsos.mbrc.data - -class UserAction( - val context: String, - val data: Any, -) { - companion object { - fun create(context: String): UserAction = UserAction(context, true) - - fun create( - context: String, - data: Any, - ): UserAction = UserAction(context, data) - } -} diff --git a/app/src/main/java/com/kelsos/mbrc/events/DefaultSettingsChangedEvent.kt b/app/src/main/java/com/kelsos/mbrc/events/DefaultSettingsChangedEvent.kt deleted file mode 100644 index 2ffd7a687..000000000 --- a/app/src/main/java/com/kelsos/mbrc/events/DefaultSettingsChangedEvent.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.kelsos.mbrc.events - -class DefaultSettingsChangedEvent { - companion object { - fun create(): DefaultSettingsChangedEvent = DefaultSettingsChangedEvent() - } -} diff --git a/app/src/main/java/com/kelsos/mbrc/events/MessageEvent.kt b/app/src/main/java/com/kelsos/mbrc/events/MessageEvent.kt deleted file mode 100644 index bf405be54..000000000 --- a/app/src/main/java/com/kelsos/mbrc/events/MessageEvent.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.kelsos.mbrc.events - -import com.fasterxml.jackson.databind.node.TextNode -import com.kelsos.mbrc.constants.ProtocolEventType -import com.kelsos.mbrc.data.UserAction -import com.kelsos.mbrc.networking.protocol.ProtocolMessage - -data class MessageEvent( - override var type: String = "", - override var data: Any = "", -) : ProtocolMessage { - override val dataString: String - get() { - return when (data.javaClass) { - TextNode::class.java -> (data as TextNode).asText() - String::class.java -> data as String - else -> "" - } - } - - companion object { - fun action(data: UserAction): MessageEvent = MessageEvent(ProtocolEventType.USER_ACTION, data) - } -} diff --git a/app/src/main/java/com/kelsos/mbrc/events/bus/RxBus.kt b/app/src/main/java/com/kelsos/mbrc/events/bus/RxBus.kt deleted file mode 100644 index 10908c819..000000000 --- a/app/src/main/java/com/kelsos/mbrc/events/bus/RxBus.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.kelsos.mbrc.events.bus - -import rx.Subscription - -interface RxBus { - fun register( - receiver: Any, - eventClass: Class, - onNext: (T) -> Unit, - ) - - fun register( - receiver: Any, - eventClass: Class, - onNext: (T) -> Unit, - main: Boolean, - ) - - fun unregister(receiver: Any) - - fun register( - eventClass: Class, - onNext: (T) -> Unit, - main: Boolean, - ): Subscription - - fun post(event: Any) -} diff --git a/app/src/main/java/com/kelsos/mbrc/events/bus/RxBusImpl.kt b/app/src/main/java/com/kelsos/mbrc/events/bus/RxBusImpl.kt deleted file mode 100644 index 3173ca7f6..000000000 --- a/app/src/main/java/com/kelsos/mbrc/events/bus/RxBusImpl.kt +++ /dev/null @@ -1,78 +0,0 @@ -package com.kelsos.mbrc.events.bus - -import com.jakewharton.rxrelay.PublishRelay -import rx.Observable -import rx.Subscription -import rx.android.schedulers.AndroidSchedulers -import rx.schedulers.Schedulers -import timber.log.Timber -import java.util.LinkedList - -class RxBusImpl : RxBus { - init { - Timber.v("Injecting RxBus instance %s", this) - } - - private val serializedRelay = PublishRelay.create().toSerialized() - private val activeSubscriptions = HashMap>() - - @Suppress("UNCHECKED_CAST") - override fun register( - receiver: Any, - eventClass: Class, - onNext: (T) -> Unit, - ) { - //noinspection unchecked - val subscription = - serializedRelay - .filter { - it.javaClass == eventClass - }.map { obj -> obj as T } - .subscribe(onNext) - - updateSubscriptions(receiver, subscription) - } - - override fun register( - receiver: Any, - eventClass: Class, - onNext: (T) -> Unit, - main: Boolean, - ) { - val subscription = register(eventClass, onNext, true) - updateSubscriptions(receiver, subscription) - } - - private fun updateSubscriptions( - receiver: Any, - subscription: Subscription, - ) { - val subscriptions: MutableList = - activeSubscriptions[receiver] ?: LinkedList() - subscriptions.add(subscription) - activeSubscriptions[receiver] = subscriptions - } - - override fun unregister(receiver: Any) { - val subscriptions = activeSubscriptions.remove(receiver) - if (subscriptions != null) { - Observable.from(subscriptions).filter { !it.isUnsubscribed }.subscribe { it.unsubscribe() } - } - } - - @Suppress("UNCHECKED_CAST") - override fun register( - eventClass: Class, - onNext: (T) -> Unit, - main: Boolean, - ): Subscription { - //noinspection unchecked - val observable = serializedRelay.filter { it.javaClass == eventClass }.map { obj -> obj as T } - val scheduler = if (main) AndroidSchedulers.mainThread() else Schedulers.immediate() - return observable.onBackpressureBuffer().observeOn(scheduler).subscribe(onNext) - } - - override fun post(event: Any) { - serializedRelay.call(event) - } -} diff --git a/app/src/main/java/com/kelsos/mbrc/events/ui/ConnectionSettingsChanged.kt b/app/src/main/java/com/kelsos/mbrc/events/ui/ConnectionSettingsChanged.kt deleted file mode 100644 index 4bf916223..000000000 --- a/app/src/main/java/com/kelsos/mbrc/events/ui/ConnectionSettingsChanged.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.kelsos.mbrc.events.ui - -class ConnectionSettingsChanged private constructor( - val defaultId: Long, -) { - companion object { - fun newInstance(defaultId: Long): ConnectionSettingsChanged = ConnectionSettingsChanged(defaultId) - } -} diff --git a/app/src/main/java/com/kelsos/mbrc/events/ui/ConnectionStatusChangeEvent.kt b/app/src/main/java/com/kelsos/mbrc/events/ui/ConnectionStatusChangeEvent.kt deleted file mode 100644 index 98ee6b47a..000000000 --- a/app/src/main/java/com/kelsos/mbrc/events/ui/ConnectionStatusChangeEvent.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.kelsos.mbrc.events.ui - -import com.kelsos.mbrc.annotations.Connection.Status - -class ConnectionStatusChangeEvent private constructor( - @Status val status: Int, -) { - companion object { - fun create( - @Status status: Int, - ): ConnectionStatusChangeEvent = ConnectionStatusChangeEvent(status) - } -} diff --git a/app/src/main/java/com/kelsos/mbrc/events/ui/CoverChangedEvent.kt b/app/src/main/java/com/kelsos/mbrc/events/ui/CoverChangedEvent.kt deleted file mode 100644 index 0132c08db..000000000 --- a/app/src/main/java/com/kelsos/mbrc/events/ui/CoverChangedEvent.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.kelsos.mbrc.events.ui - -class CoverChangedEvent( - val path: String = "", -) diff --git a/app/src/main/java/com/kelsos/mbrc/events/ui/DiscoveryStopped.kt b/app/src/main/java/com/kelsos/mbrc/events/ui/DiscoveryStopped.kt deleted file mode 100644 index cb87decd1..000000000 --- a/app/src/main/java/com/kelsos/mbrc/events/ui/DiscoveryStopped.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.kelsos.mbrc.events.ui - -import com.kelsos.mbrc.networking.discovery.DiscoveryStop - -class DiscoveryStopped( - val reason: DiscoveryStop, -) diff --git a/app/src/main/java/com/kelsos/mbrc/events/ui/LfmRatingChanged.kt b/app/src/main/java/com/kelsos/mbrc/events/ui/LfmRatingChanged.kt deleted file mode 100644 index 0e32944c0..000000000 --- a/app/src/main/java/com/kelsos/mbrc/events/ui/LfmRatingChanged.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.kelsos.mbrc.events.ui - -import com.kelsos.mbrc.features.player.LfmStatus - -class LfmRatingChanged( - val status: LfmStatus, -) diff --git a/app/src/main/java/com/kelsos/mbrc/events/ui/LibraryRefreshCompleteEvent.kt b/app/src/main/java/com/kelsos/mbrc/events/ui/LibraryRefreshCompleteEvent.kt deleted file mode 100644 index b5bb6cf6e..000000000 --- a/app/src/main/java/com/kelsos/mbrc/events/ui/LibraryRefreshCompleteEvent.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.kelsos.mbrc.events.ui - -class LibraryRefreshCompleteEvent diff --git a/app/src/main/java/com/kelsos/mbrc/events/ui/LyricsUpdatedEvent.kt b/app/src/main/java/com/kelsos/mbrc/events/ui/LyricsUpdatedEvent.kt deleted file mode 100644 index aab9af1ff..000000000 --- a/app/src/main/java/com/kelsos/mbrc/events/ui/LyricsUpdatedEvent.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.kelsos.mbrc.events.ui - -class LyricsUpdatedEvent( - val lyrics: String, -) diff --git a/app/src/main/java/com/kelsos/mbrc/events/ui/NotifyUser.kt b/app/src/main/java/com/kelsos/mbrc/events/ui/NotifyUser.kt deleted file mode 100644 index fe5514461..000000000 --- a/app/src/main/java/com/kelsos/mbrc/events/ui/NotifyUser.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.kelsos.mbrc.events.ui - -class NotifyUser { - val message: String - val resId: Int - var isFromResource: Boolean = false - private set - - constructor(message: String) { - this.message = message - this.isFromResource = false - this.resId = -1 - } - - constructor(resId: Int) { - this.resId = resId - this.isFromResource = true - this.message = "" - } -} diff --git a/app/src/main/java/com/kelsos/mbrc/events/ui/PlayStateChange.kt b/app/src/main/java/com/kelsos/mbrc/events/ui/PlayStateChange.kt deleted file mode 100644 index fe4feb90c..000000000 --- a/app/src/main/java/com/kelsos/mbrc/events/ui/PlayStateChange.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.kelsos.mbrc.events.ui - -import com.kelsos.mbrc.annotations.PlayerState.State - -data class PlayStateChange( - @State val state: String, - val position: Long, -) diff --git a/app/src/main/java/com/kelsos/mbrc/events/ui/RatingChanged.kt b/app/src/main/java/com/kelsos/mbrc/events/ui/RatingChanged.kt deleted file mode 100644 index a8726cefe..000000000 --- a/app/src/main/java/com/kelsos/mbrc/events/ui/RatingChanged.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.kelsos.mbrc.events.ui - -class RatingChanged( - val rating: Float, -) diff --git a/app/src/main/java/com/kelsos/mbrc/events/ui/RemoteClientMetaData.kt b/app/src/main/java/com/kelsos/mbrc/events/ui/RemoteClientMetaData.kt deleted file mode 100644 index 53e4bef56..000000000 --- a/app/src/main/java/com/kelsos/mbrc/events/ui/RemoteClientMetaData.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.kelsos.mbrc.events.ui - -import com.kelsos.mbrc.features.player.TrackInfo - -data class RemoteClientMetaData( - val trackInfo: TrackInfo, - val coverPath: String = "", - val duration: Long, -) diff --git a/app/src/main/java/com/kelsos/mbrc/events/ui/RepeatChange.kt b/app/src/main/java/com/kelsos/mbrc/events/ui/RepeatChange.kt deleted file mode 100644 index b1d5857de..000000000 --- a/app/src/main/java/com/kelsos/mbrc/events/ui/RepeatChange.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.kelsos.mbrc.events.ui - -import com.kelsos.mbrc.annotations.Repeat.Mode - -class RepeatChange( - @Mode val mode: String, -) diff --git a/app/src/main/java/com/kelsos/mbrc/events/ui/RequestConnectionStateEvent.kt b/app/src/main/java/com/kelsos/mbrc/events/ui/RequestConnectionStateEvent.kt deleted file mode 100644 index 0566fa5aa..000000000 --- a/app/src/main/java/com/kelsos/mbrc/events/ui/RequestConnectionStateEvent.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.kelsos.mbrc.events.ui - -class RequestConnectionStateEvent diff --git a/app/src/main/java/com/kelsos/mbrc/events/ui/ScrobbleChange.kt b/app/src/main/java/com/kelsos/mbrc/events/ui/ScrobbleChange.kt deleted file mode 100644 index 9573ac8ca..000000000 --- a/app/src/main/java/com/kelsos/mbrc/events/ui/ScrobbleChange.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.kelsos.mbrc.events.ui - -class ScrobbleChange( - val isActive: Boolean, -) diff --git a/app/src/main/java/com/kelsos/mbrc/events/ui/ShuffleChange.kt b/app/src/main/java/com/kelsos/mbrc/events/ui/ShuffleChange.kt deleted file mode 100644 index 71473edd4..000000000 --- a/app/src/main/java/com/kelsos/mbrc/events/ui/ShuffleChange.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.kelsos.mbrc.events.ui - -import androidx.annotation.StringDef - -class ShuffleChange( - @ShuffleState val shuffleState: String, -) { - @StringDef(OFF, AUTODJ, SHUFFLE) - @Retention(AnnotationRetention.SOURCE) - annotation class ShuffleState - - companion object { - const val OFF = "off" - const val AUTODJ = "autodj" - const val SHUFFLE = "shuffle" - } -} diff --git a/app/src/main/java/com/kelsos/mbrc/events/ui/TrackInfoChangeEvent.kt b/app/src/main/java/com/kelsos/mbrc/events/ui/TrackInfoChangeEvent.kt deleted file mode 100644 index bf489824e..000000000 --- a/app/src/main/java/com/kelsos/mbrc/events/ui/TrackInfoChangeEvent.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.kelsos.mbrc.events.ui - -import com.kelsos.mbrc.features.player.TrackInfo - -data class TrackInfoChangeEvent( - val trackInfo: TrackInfo, -) diff --git a/app/src/main/java/com/kelsos/mbrc/events/ui/TrackMoved.kt b/app/src/main/java/com/kelsos/mbrc/events/ui/TrackMoved.kt deleted file mode 100644 index 1a22b1f6f..000000000 --- a/app/src/main/java/com/kelsos/mbrc/events/ui/TrackMoved.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.kelsos.mbrc.events.ui - -import com.fasterxml.jackson.databind.node.ObjectNode - -class TrackMoved( - node: ObjectNode, -) { - val isSuccess: Boolean = node.path("success").asBoolean() - val from: Int = node.path("from").asInt() - val to: Int = node.path("to").asInt() -} diff --git a/app/src/main/java/com/kelsos/mbrc/events/ui/TrackRemoval.kt b/app/src/main/java/com/kelsos/mbrc/events/ui/TrackRemoval.kt deleted file mode 100644 index 0bbbc60ca..000000000 --- a/app/src/main/java/com/kelsos/mbrc/events/ui/TrackRemoval.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.kelsos.mbrc.events.ui - -import com.fasterxml.jackson.databind.node.ObjectNode - -class TrackRemoval( - node: ObjectNode, -) { - val index: Int = node.path("index").asInt() - val isSuccess: Boolean = node.path("success").asBoolean() -} diff --git a/app/src/main/java/com/kelsos/mbrc/events/ui/UpdateDuration.kt b/app/src/main/java/com/kelsos/mbrc/events/ui/UpdateDuration.kt deleted file mode 100644 index 0527cbf5f..000000000 --- a/app/src/main/java/com/kelsos/mbrc/events/ui/UpdateDuration.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.kelsos.mbrc.events.ui - -class UpdateDuration( - val position: Int, - val duration: Int, -) diff --git a/app/src/main/java/com/kelsos/mbrc/events/ui/VolumeChange.kt b/app/src/main/java/com/kelsos/mbrc/events/ui/VolumeChange.kt deleted file mode 100644 index 18332e884..000000000 --- a/app/src/main/java/com/kelsos/mbrc/events/ui/VolumeChange.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.kelsos.mbrc.events.ui - -class VolumeChange { - var volume: Int = 0 - private set - var isMute: Boolean = false - private set - - constructor(vol: Int) { - this.volume = vol - this.isMute = false - } - - constructor() { - this.volume = 0 - this.isMute = true - } -} diff --git a/app/src/main/java/com/kelsos/mbrc/extensions/EnableHome.kt b/app/src/main/java/com/kelsos/mbrc/extensions/EnableHome.kt deleted file mode 100644 index 6d987535a..000000000 --- a/app/src/main/java/com/kelsos/mbrc/extensions/EnableHome.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.kelsos.mbrc.extensions - -import androidx.appcompat.app.ActionBar - -fun ActionBar.enableHome(title: String?) { - this.setDisplayHomeAsUpEnabled(true) - this.setDisplayShowHomeEnabled(true) - this.title = title ?: "" -} diff --git a/app/src/main/java/com/kelsos/mbrc/extensions/FileExtensions.kt b/app/src/main/java/com/kelsos/mbrc/extensions/FileExtensions.kt deleted file mode 100644 index f22feca81..000000000 --- a/app/src/main/java/com/kelsos/mbrc/extensions/FileExtensions.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.kelsos.mbrc.extensions - -import java.io.File -import java.io.FileInputStream -import java.security.MessageDigest - -fun File.md5(): String? { - try { - val fin = FileInputStream(this) - val md5er = MessageDigest.getInstance("MD5") - val buffer = ByteArray(1024) - var read: Int - do { - read = fin.read(buffer) - if (read > 0) { - md5er.update(buffer, 0, read) - } - } while (read != -1) - fin.close() - val digest = md5er.digest() ?: return null - var str = "" - digest.indices.forEach { - str += Integer.toString((digest[it].toInt() and 0xff) + 0x100, 16).substring(1) - } - return str - } catch (e: Exception) { - return null - } -} diff --git a/app/src/main/java/com/kelsos/mbrc/extensions/StringExtensions.kt b/app/src/main/java/com/kelsos/mbrc/extensions/StringExtensions.kt deleted file mode 100644 index b42f9bec3..000000000 --- a/app/src/main/java/com/kelsos/mbrc/extensions/StringExtensions.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.kelsos.mbrc.extensions - -fun String.escapeLike(): String = this.replace("%", "_") diff --git a/app/src/main/java/com/kelsos/mbrc/features/dragsort/OnStartDragListener.kt b/app/src/main/java/com/kelsos/mbrc/features/dragsort/OnStartDragListener.kt index cae15f921..d70123739 100644 --- a/app/src/main/java/com/kelsos/mbrc/features/dragsort/OnStartDragListener.kt +++ b/app/src/main/java/com/kelsos/mbrc/features/dragsort/OnStartDragListener.kt @@ -4,4 +4,6 @@ import androidx.recyclerview.widget.RecyclerView interface OnStartDragListener { fun onStartDrag(viewHolder: RecyclerView.ViewHolder) + + fun onDragComplete() } diff --git a/app/src/main/java/com/kelsos/mbrc/features/dragsort/SimpleItemTouchHelper.kt b/app/src/main/java/com/kelsos/mbrc/features/dragsort/SimpleItemTouchHelper.kt index 4fac4bc3a..505543ebb 100644 --- a/app/src/main/java/com/kelsos/mbrc/features/dragsort/SimpleItemTouchHelper.kt +++ b/app/src/main/java/com/kelsos/mbrc/features/dragsort/SimpleItemTouchHelper.kt @@ -64,10 +64,8 @@ class SimpleItemTouchHelper( viewHolder: RecyclerView.ViewHolder?, actionState: Int, ) { - if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) { - if (viewHolder is TouchHelperViewHolder) { - viewHolder.onItemSelected() - } + if (actionState != ItemTouchHelper.ACTION_STATE_IDLE && viewHolder is TouchHelperViewHolder) { + viewHolder.onItemSelected() } super.onSelectedChanged(viewHolder, actionState) diff --git a/app/src/main/java/com/kelsos/mbrc/features/help/FeedbackFragment.kt b/app/src/main/java/com/kelsos/mbrc/features/help/FeedbackFragment.kt index 09f92fc9e..a83b83ac8 100644 --- a/app/src/main/java/com/kelsos/mbrc/features/help/FeedbackFragment.kt +++ b/app/src/main/java/com/kelsos/mbrc/features/help/FeedbackFragment.kt @@ -11,21 +11,46 @@ import android.widget.Button import android.widget.CheckBox import android.widget.EditText import androidx.core.content.FileProvider -import androidx.fragment.app.Fragment +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import com.kelsos.mbrc.BuildConfig.APPLICATION_ID import com.kelsos.mbrc.R -import com.kelsos.mbrc.common.utilities.RemoteUtils.getVersion -import com.kelsos.mbrc.logging.LogHelper -import rx.android.schedulers.AndroidSchedulers -import rx.schedulers.Schedulers +import com.kelsos.mbrc.common.utilities.RemoteUtils.VERSION +import kotlinx.coroutines.launch +import org.koin.androidx.scope.ScopeFragment +import org.koin.androidx.viewmodel.ext.android.viewModel import java.io.File -class FeedbackFragment : Fragment() { +class FeedbackFragment : ScopeFragment() { private lateinit var feedbackEditText: EditText private lateinit var deviceInfo: CheckBox private lateinit var logInfo: CheckBox private lateinit var feedbackButton: Button + private val viewModel: FeedbackViewModel by viewModel() + + private val feedbackText: String + get() { + var feedbackText = feedbackEditText.text.toString().trim { it <= ' ' } + if (deviceInfo.isChecked) { + val device = Build.DEVICE + val manufacturer = Build.MANUFACTURER + val appVersion = VERSION + val androidVersion = Build.VERSION.RELEASE + + feedbackText += + getString( + R.string.feedback_version_info, + manufacturer, + device, + androidVersion, + appVersion, + ) + } + return feedbackText + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -39,55 +64,37 @@ class FeedbackFragment : Fragment() { feedbackButton.setOnClickListener { onFeedbackButtonClicked() } - LogHelper - .logsExist(requireContext()) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe({ - logInfo.isEnabled = true - }) { + lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.events.collect { event -> + when (event) { + is FeedbackUiMessage.UpdateLogsExist -> logInfo.isChecked = event.logsExist + is FeedbackUiMessage.ZipFailed -> openChooser(feedbackText) + is FeedbackUiMessage.ZipSuccess -> openChooser(feedbackText, event.zipFile) + } + } } + } + viewModel.checkIfLogsExist(requireContext().filesDir) return view } private fun onFeedbackButtonClicked() { - var feedbackText = feedbackEditText.text.toString().trim { it <= ' ' } if (TextUtils.isEmpty(feedbackText)) { return } feedbackButton.isEnabled = false - if (deviceInfo.isChecked) { - val device = Build.DEVICE - val manufacturer = Build.MANUFACTURER - val appVersion = requireContext().getVersion() - val androidVersion = Build.VERSION.RELEASE - - feedbackText += - getString( - R.string.feedback_version_info, - manufacturer, - device, - androidVersion, - appVersion, - ) - } - if (!logInfo.isChecked) { openChooser(feedbackText) return } - LogHelper - .zipLogs(requireContext()) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe({ - openChooser(feedbackText, it) - }) { - openChooser(feedbackText) - } + lifecycleScope.launch { + val ctx = requireContext() + viewModel.createZip(ctx.filesDir, ctx.externalCacheDir ?: ctx.cacheDir) + } } private fun openChooser( diff --git a/app/src/main/java/com/kelsos/mbrc/features/help/FeedbackUiMessage.kt b/app/src/main/java/com/kelsos/mbrc/features/help/FeedbackUiMessage.kt new file mode 100644 index 000000000..6fa1e1577 --- /dev/null +++ b/app/src/main/java/com/kelsos/mbrc/features/help/FeedbackUiMessage.kt @@ -0,0 +1,16 @@ +package com.kelsos.mbrc.features.help + +import com.kelsos.mbrc.common.mvvm.UiMessageBase +import java.io.File + +sealed class FeedbackUiMessage : UiMessageBase { + class UpdateLogsExist( + val logsExist: Boolean, + ) : FeedbackUiMessage() + + class ZipSuccess( + val zipFile: File, + ) : FeedbackUiMessage() + + class ZipFailed : FeedbackUiMessage() +} diff --git a/app/src/main/java/com/kelsos/mbrc/features/help/FeedbackViewModel.kt b/app/src/main/java/com/kelsos/mbrc/features/help/FeedbackViewModel.kt new file mode 100644 index 000000000..510347838 --- /dev/null +++ b/app/src/main/java/com/kelsos/mbrc/features/help/FeedbackViewModel.kt @@ -0,0 +1,39 @@ +package com.kelsos.mbrc.features.help + +import androidx.lifecycle.viewModelScope +import com.kelsos.mbrc.common.mvvm.BaseViewModel +import com.kelsos.mbrc.logging.LogHelper +import kotlinx.coroutines.launch +import java.io.File + +class FeedbackViewModel( + private val logHelper: LogHelper, +) : BaseViewModel() { + fun checkIfLogsExist(filesDir: File) { + viewModelScope.launch { + val logsExist = logHelper.logsExist(filesDir = filesDir) + emit(FeedbackUiMessage.UpdateLogsExist(logsExist)) + } + } + + fun createZip( + filesDir: File, + externalCacheDir: File, + ) { + viewModelScope.launch { + val result = + runCatching { + logHelper.zipLogs(filesDir, externalCacheDir) + } + + val message = + if (result.isSuccess) { + FeedbackUiMessage.ZipSuccess(result.getOrThrow()) + } else { + FeedbackUiMessage.ZipFailed() + } + + emit(message) + } + } +} diff --git a/app/src/main/java/com/kelsos/mbrc/features/help/HelpFeedbackActivity.kt b/app/src/main/java/com/kelsos/mbrc/features/help/HelpFeedbackActivity.kt index 5856062c9..07f7253c6 100644 --- a/app/src/main/java/com/kelsos/mbrc/features/help/HelpFeedbackActivity.kt +++ b/app/src/main/java/com/kelsos/mbrc/features/help/HelpFeedbackActivity.kt @@ -32,9 +32,7 @@ class HelpFeedbackActivity : AppCompatActivity() { } pagerAdapter = HelpFeedbackPagerAdapter(this) - viewPager.apply { - adapter = pagerAdapter - } + viewPager.adapter = pagerAdapter TabLayoutMediator(tabLayout, viewPager) { currentTab, currentPosition -> currentTab.text = when (currentPosition) { diff --git a/app/src/main/java/com/kelsos/mbrc/features/help/HelpFragment.kt b/app/src/main/java/com/kelsos/mbrc/features/help/HelpFragment.kt index ef62b5a08..8e96f0ca9 100644 --- a/app/src/main/java/com/kelsos/mbrc/features/help/HelpFragment.kt +++ b/app/src/main/java/com/kelsos/mbrc/features/help/HelpFragment.kt @@ -1,6 +1,5 @@ package com.kelsos.mbrc.features.help -import android.content.pm.PackageManager import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -10,23 +9,14 @@ import android.webkit.WebView import android.webkit.WebViewClient import androidx.fragment.app.Fragment import com.kelsos.mbrc.R -import com.kelsos.mbrc.common.utilities.RemoteUtils.getVersion -import timber.log.Timber +import com.kelsos.mbrc.common.utilities.RemoteUtils class HelpFragment : Fragment() { private lateinit var helpView: WebView override fun onStart() { super.onStart() - val url: String = - try { - String.format("https://mbrc.kelsos.net/help?version=%s", requireContext().getVersion()) - } catch (e: PackageManager.NameNotFoundException) { - Timber.v(e, "Failed to get version") - "https://mbrc.kelsos.net/help" - } - - helpView.loadUrl(url) + helpView.loadUrl("https://mbrc.kelsos.net/help?version=${RemoteUtils.VERSION}") } override fun onCreateView( diff --git a/app/src/main/java/com/kelsos/mbrc/features/library/Album.kt b/app/src/main/java/com/kelsos/mbrc/features/library/Album.kt deleted file mode 100644 index 70d4fe246..000000000 --- a/app/src/main/java/com/kelsos/mbrc/features/library/Album.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.kelsos.mbrc.features.library - -import com.fasterxml.jackson.annotation.JsonIgnore -import com.fasterxml.jackson.annotation.JsonProperty -import com.kelsos.mbrc.common.utilities.RemoteUtils.sha1 -import com.kelsos.mbrc.data.Data -import com.kelsos.mbrc.data.Database -import com.raizlabs.android.dbflow.annotation.Column -import com.raizlabs.android.dbflow.annotation.PrimaryKey -import com.raizlabs.android.dbflow.annotation.Table - -@Table(name = "album", database = Database::class) -data class Album( - @JsonProperty("artist") - @Column - var artist: String? = null, - @JsonProperty("album") - @Column - var album: String? = null, - @JsonProperty("count") - @Column - var count: Int = 0, - @JsonIgnore - @Column - var cover: String? = null, - @JsonIgnore - @Column(name = "date_added") - var dateAdded: Long = 0, - @JsonIgnore - @Column - @PrimaryKey(autoincrement = true) - var id: Long = 0, -) : Data - -fun Album.key(): String = sha1("${artist}_$album") diff --git a/app/src/main/java/com/kelsos/mbrc/features/library/AlbumEntryAdapter.kt b/app/src/main/java/com/kelsos/mbrc/features/library/AlbumEntryAdapter.kt deleted file mode 100644 index 9aeb66b13..000000000 --- a/app/src/main/java/com/kelsos/mbrc/features/library/AlbumEntryAdapter.kt +++ /dev/null @@ -1,121 +0,0 @@ -package com.kelsos.mbrc.features.library - -import android.app.Activity -import android.view.LayoutInflater -import android.view.MenuItem -import android.view.View -import android.view.ViewGroup -import android.widget.LinearLayout -import android.widget.TextView -import androidx.appcompat.widget.PopupMenu -import androidx.core.view.isGone -import androidx.recyclerview.widget.RecyclerView -import coil3.load -import coil3.request.crossfade -import coil3.request.error -import coil3.request.placeholder -import coil3.size.Scale -import com.kelsos.mbrc.R -import com.kelsos.mbrc.common.ui.SquareImageView -import com.raizlabs.android.dbflow.list.FlowCursorList -import java.io.File - -class AlbumEntryAdapter( - context: Activity, -) : RecyclerView.Adapter() { - private val inflater: LayoutInflater = LayoutInflater.from(context) - private var data: FlowCursorList? = null - private var listener: MenuItemSelectedListener? = null - private val cache = File(context.cacheDir, "covers") - - override fun onCreateViewHolder( - parent: ViewGroup, - viewType: Int, - ): ViewHolder { - val view = inflater.inflate(R.layout.ui_list_dual, parent, false) - val holder = ViewHolder(view) - holder.indicator.setOnClickListener { - val popupMenu = PopupMenu(it.context, it) - popupMenu.inflate(R.menu.popup_album) - popupMenu.setOnMenuItemClickListener { menuItem -> - val data = this.data ?: return@setOnMenuItemClickListener false - val position = holder.bindingAdapterPosition.toLong() - val album = data.getItem(position) ?: return@setOnMenuItemClickListener false - listener?.onMenuItemSelected(menuItem, album) - true - } - popupMenu.show() - } - - holder.itemView.setOnClickListener { - val data = this.data ?: return@setOnClickListener - val position = holder.bindingAdapterPosition.toLong() - val album = data.getItem(position) ?: return@setOnClickListener - listener?.onItemClicked(album) - } - return holder - } - - override fun onBindViewHolder( - holder: ViewHolder, - position: Int, - ) { - val data = this.data ?: return - val item = data.getItem(position.toLong()) ?: return - val (artist, album) = item - holder.album.text = if (album.isNullOrBlank()) holder.emptyAlbum else album - holder.artist.text = if (artist.isNullOrBlank()) holder.unknownArtist else artist - - holder.image.load(File(cache, item.key())) { - crossfade(false) - placeholder(R.drawable.ic_image_no_cover) - error(R.drawable.ic_image_no_cover) - size( - holder.itemView.context.resources - .getDimensionPixelSize(R.dimen.list_album_size), - ) - scale(Scale.FILL) - } - } - - fun refresh() { - data?.refresh() - notifyDataSetChanged() - } - - override fun getItemCount(): Int = data?.count?.toInt() ?: 0 - - fun setMenuItemSelectedListener(listener: MenuItemSelectedListener) { - this.listener = listener - } - - interface MenuItemSelectedListener { - fun onMenuItemSelected( - menuItem: MenuItem, - album: Album, - ) - - fun onItemClicked(album: Album) - } - - class ViewHolder( - itemView: View, - ) : RecyclerView.ViewHolder(itemView) { - val artist: TextView = itemView.findViewById(R.id.line_two) - val album: TextView = itemView.findViewById(R.id.line_one) - val image: SquareImageView = itemView.findViewById(R.id.cover) - val indicator: LinearLayout = itemView.findViewById(R.id.ui_item_context_indicator) - - val unknownArtist: String = itemView.context.getString(R.string.unknown_artist) - val emptyAlbum: String = itemView.context.getString(R.string.non_album_tracks) - - init { - image.isGone = false - } - } - - fun update(albums: FlowCursorList) { - data = albums - notifyDataSetChanged() - } -} diff --git a/app/src/main/java/com/kelsos/mbrc/features/library/AlbumRepository.kt b/app/src/main/java/com/kelsos/mbrc/features/library/AlbumRepository.kt deleted file mode 100644 index c52fcda0b..000000000 --- a/app/src/main/java/com/kelsos/mbrc/features/library/AlbumRepository.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.kelsos.mbrc.features.library - -import com.kelsos.mbrc.common.data.Repository -import com.raizlabs.android.dbflow.list.FlowCursorList - -interface AlbumRepository : Repository { - suspend fun getAlbumsByArtist(artist: String): FlowCursorList - - suspend fun updateCovers(updated: List) -} diff --git a/app/src/main/java/com/kelsos/mbrc/features/library/AlbumRepositoryImpl.kt b/app/src/main/java/com/kelsos/mbrc/features/library/AlbumRepositoryImpl.kt deleted file mode 100644 index d742bac60..000000000 --- a/app/src/main/java/com/kelsos/mbrc/features/library/AlbumRepositoryImpl.kt +++ /dev/null @@ -1,63 +0,0 @@ -package com.kelsos.mbrc.features.library - -import com.kelsos.mbrc.common.utilities.AppCoroutineDispatchers -import com.raizlabs.android.dbflow.list.FlowCursorList -import kotlinx.coroutines.flow.onCompletion -import kotlinx.coroutines.withContext -import java.time.Instant - -class AlbumRepositoryImpl( - private val localDataSource: LocalAlbumDataSource, - private val remoteDataSource: RemoteAlbumDataSource, - private val dispatchers: AppCoroutineDispatchers, -) : AlbumRepository { - override suspend fun getAlbumsByArtist(artist: String): FlowCursorList = localDataSource.getAlbumsByArtist(artist) - - override suspend fun getAllCursor(): FlowCursorList = localDataSource.loadAllCursor() - - override suspend fun getAndSaveRemote(): FlowCursorList { - getRemote() - return localDataSource.loadAllCursor() - } - - override suspend fun getRemote() { - val epoch = Instant.now().epochSecond - val default = CachedAlbumInfo(0, null) - val cached = - localDataSource.loadAllCursor().associate { - it.album + it.artist to CachedAlbumInfo(it.id, it?.cover) - } - withContext(dispatchers.io) { - remoteDataSource - .fetch() - .onCompletion { - localDataSource.removePreviousEntries(epoch) - }.collect { albums -> - val list = - albums.map { - it.apply { - dateAdded = epoch - val key = it.album + it.artist - - if (cached.containsKey(key)) { - val cachedAlbum = cached.getOrDefault(key, default) - id = cachedAlbum.id - cover = cachedAlbum.cover - } - } - } - localDataSource.saveAll(list) - } - } - } - - override suspend fun search(term: String): FlowCursorList = localDataSource.search(term) - - override suspend fun cacheIsEmpty(): Boolean = localDataSource.isEmpty() - - override suspend fun count(): Long = localDataSource.count() - - override suspend fun updateCovers(updated: List) { - localDataSource.updateCovers(updated) - } -} diff --git a/app/src/main/java/com/kelsos/mbrc/features/library/AlbumTracksActivity.kt b/app/src/main/java/com/kelsos/mbrc/features/library/AlbumTracksActivity.kt deleted file mode 100644 index 694ba474f..000000000 --- a/app/src/main/java/com/kelsos/mbrc/features/library/AlbumTracksActivity.kt +++ /dev/null @@ -1,163 +0,0 @@ -package com.kelsos.mbrc.features.library - -import android.os.Bundle -import android.view.MenuItem -import android.widget.Button -import android.widget.ImageView -import android.widget.TextView -import androidx.activity.OnBackPressedCallback -import androidx.core.os.BundleCompat -import androidx.core.view.isVisible -import androidx.recyclerview.widget.LinearLayoutManager -import coil3.load -import coil3.request.crossfade -import coil3.request.error -import coil3.request.placeholder -import coil3.size.Scale -import com.google.android.material.snackbar.Snackbar -import com.kelsos.mbrc.R -import com.kelsos.mbrc.common.ui.EmptyRecyclerView -import com.kelsos.mbrc.common.utilities.RemoteUtils.sha1 -import com.kelsos.mbrc.features.queue.PopupActionHandler -import com.raizlabs.android.dbflow.list.FlowCursorList -import org.koin.android.ext.android.inject -import org.koin.androidx.scope.ScopeActivity -import java.io.File - -class AlbumTracksActivity : - ScopeActivity(), - AlbumTracksView, - TrackEntryAdapter.MenuItemSelectedListener { - private val adapter: TrackEntryAdapter by inject() - private val actionHandler: PopupActionHandler by inject() - private val presenter: AlbumTracksPresenter by inject() - - private var album: AlbumInfo? = null - private lateinit var recyclerView: EmptyRecyclerView - - public override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_album_tracks) - onBackPressedDispatcher.addCallback( - this, - object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - finish() - } - }, - ) - - val extras = intent.extras - - if (extras != null) { - album = BundleCompat.getParcelable(extras, ALBUM, AlbumInfo::class.java) - } - - val selectedAlbum = album - if (selectedAlbum == null) { - finish() - return - } - - setSupportActionBar(findViewById(R.id.toolbar)) - val supportActionBar = supportActionBar ?: error("Actionbar should not be null") - supportActionBar.setDisplayHomeAsUpEnabled(true) - supportActionBar.setDisplayShowHomeEnabled(true) - - if (selectedAlbum.album.isEmpty()) { - supportActionBar.setTitle(R.string.non_album_tracks) - } else { - supportActionBar.title = selectedAlbum.album - } - - findViewById(R.id.album_tracks__album).text = selectedAlbum.album - findViewById(R.id.album_tracks__artist).text = selectedAlbum.artist - loadCover(selectedAlbum.artist, selectedAlbum.album) - - presenter.attach(this) - presenter.load(selectedAlbum) - adapter.setMenuItemSelectedListener(this) - recyclerView = findViewById(R.id.list_tracks) - recyclerView.layoutManager = LinearLayoutManager(baseContext) - recyclerView.adapter = adapter - recyclerView.emptyView = findViewById(R.id.empty_view) - val play = findViewById