diff --git a/lib/main.dart b/lib/main.dart index 69c89062..cb553115 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -18,7 +18,9 @@ import 'package:spotube/hooks/configurators/use_close_behavior.dart'; import 'package:spotube/hooks/configurators/use_deep_linking.dart'; import 'package:spotube/hooks/configurators/use_disable_battery_optimizations.dart'; import 'package:spotube/hooks/configurators/use_get_storage_perms.dart'; +import 'package:spotube/models/database/database.dart'; import 'package:spotube/provider/audio_player/audio_player_streams.dart'; +import 'package:spotube/provider/database/database.dart'; import 'package:spotube/provider/server/bonsoir.dart'; import 'package:spotube/provider/server/server.dart'; import 'package:spotube/provider/tray_manager/tray_manager.dart'; @@ -33,6 +35,7 @@ import 'package:spotube/services/kv_store/kv_store.dart'; import 'package:spotube/services/logger/logger.dart'; import 'package:spotube/services/wm_tools/wm_tools.dart'; import 'package:spotube/themes/theme.dart'; +import 'package:spotube/utils/migrations/hive.dart'; import 'package:spotube/utils/platform.dart'; import 'package:system_theme/system_theme.dart'; import 'package:path_provider/path_provider.dart'; @@ -84,12 +87,23 @@ Future main(List rawArgs) async { Hive.init(hiveCacheDir); + final database = AppDatabase(); + + await migrateFromHiveToDrift(database); + if (kIsDesktop) { await localNotifier.setup(appName: "Spotube"); await WindowManagerTools.initialize(); } - runApp(const ProviderScope(child: Spotube())); + runApp( + ProviderScope( + overrides: [ + databaseProvider.overrideWith((ref) => database), + ], + child: const Spotube(), + ), + ); }); } diff --git a/lib/modules/stats/top/top.dart b/lib/modules/stats/top/top.dart index 52529f3f..ea52c517 100644 --- a/lib/modules/stats/top/top.dart +++ b/lib/modules/stats/top/top.dart @@ -5,7 +5,7 @@ import 'package:spotube/components/themed_button_tab_bar.dart'; import 'package:spotube/modules/stats/top/albums.dart'; import 'package:spotube/modules/stats/top/artists.dart'; import 'package:spotube/modules/stats/top/tracks.dart'; -import 'package:spotube/provider/history/state.dart'; + import 'package:spotube/provider/history/top.dart'; class StatsPageTopSection extends HookConsumerWidget { diff --git a/lib/pages/stats/albums/albums.dart b/lib/pages/stats/albums/albums.dart index a13e500b..859eaf26 100644 --- a/lib/pages/stats/albums/albums.dart +++ b/lib/pages/stats/albums/albums.dart @@ -3,7 +3,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/collections/formatters.dart'; import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/modules/stats/common/album_item.dart'; -import 'package:spotube/provider/history/state.dart'; + import 'package:spotube/provider/history/top.dart'; class StatsAlbumsPage extends HookConsumerWidget { diff --git a/lib/pages/stats/artists/artists.dart b/lib/pages/stats/artists/artists.dart index 9ebdbe5d..e6dadd95 100644 --- a/lib/pages/stats/artists/artists.dart +++ b/lib/pages/stats/artists/artists.dart @@ -3,7 +3,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/collections/formatters.dart'; import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/modules/stats/common/artist_item.dart'; -import 'package:spotube/provider/history/state.dart'; + import 'package:spotube/provider/history/top.dart'; class StatsArtistsPage extends HookConsumerWidget { diff --git a/lib/pages/stats/fees/fees.dart b/lib/pages/stats/fees/fees.dart index e881ec70..e1d701eb 100644 --- a/lib/pages/stats/fees/fees.dart +++ b/lib/pages/stats/fees/fees.dart @@ -4,7 +4,7 @@ import 'package:sliver_tools/sliver_tools.dart'; import 'package:spotube/collections/formatters.dart'; import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/modules/stats/common/artist_item.dart'; -import 'package:spotube/provider/history/state.dart'; + import 'package:spotube/provider/history/top.dart'; class StatsStreamFeesPage extends HookConsumerWidget { diff --git a/lib/pages/stats/minutes/minutes.dart b/lib/pages/stats/minutes/minutes.dart index 1d6a5844..587e9007 100644 --- a/lib/pages/stats/minutes/minutes.dart +++ b/lib/pages/stats/minutes/minutes.dart @@ -4,7 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/collections/formatters.dart'; import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/modules/stats/common/track_item.dart'; -import 'package:spotube/provider/history/state.dart'; + import 'package:spotube/provider/history/top.dart'; class StatsMinutesPage extends HookConsumerWidget { diff --git a/lib/pages/stats/playlists/playlists.dart b/lib/pages/stats/playlists/playlists.dart index 94f8ce9d..f5ee62d0 100644 --- a/lib/pages/stats/playlists/playlists.dart +++ b/lib/pages/stats/playlists/playlists.dart @@ -3,7 +3,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/collections/formatters.dart'; import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/modules/stats/common/playlist_item.dart'; -import 'package:spotube/provider/history/state.dart'; + import 'package:spotube/provider/history/top.dart'; class StatsPlaylistsPage extends HookConsumerWidget { diff --git a/lib/pages/stats/streams/streams.dart b/lib/pages/stats/streams/streams.dart index 41f2d33a..20e8ff96 100644 --- a/lib/pages/stats/streams/streams.dart +++ b/lib/pages/stats/streams/streams.dart @@ -4,7 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/collections/formatters.dart'; import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/modules/stats/common/track_item.dart'; -import 'package:spotube/provider/history/state.dart'; + import 'package:spotube/provider/history/top.dart'; class StatsStreamsPage extends HookConsumerWidget { diff --git a/lib/provider/history/state.dart b/lib/provider/history/state.dart deleted file mode 100644 index 67658502..00000000 --- a/lib/provider/history/state.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:spotify/spotify.dart'; - -part 'state.freezed.dart'; -part 'state.g.dart'; - -enum HistoryDuration { - allTime, - days7, - days30, - months6, - year, - years2, -} - -@freezed -class PlaybackHistoryItem with _$PlaybackHistoryItem { - factory PlaybackHistoryItem.playlist({ - required DateTime date, - required PlaylistSimple playlist, - }) = PlaybackHistoryPlaylist; - - factory PlaybackHistoryItem.album({ - required DateTime date, - required AlbumSimple album, - }) = PlaybackHistoryAlbum; - - factory PlaybackHistoryItem.track({ - required DateTime date, - required Track track, - }) = PlaybackHistoryTrack; - - factory PlaybackHistoryItem.fromJson(Map json) => - _$PlaybackHistoryItemFromJson(json); -} diff --git a/lib/provider/history/state.freezed.dart b/lib/provider/history/state.freezed.dart deleted file mode 100644 index e2ee9421..00000000 --- a/lib/provider/history/state.freezed.dart +++ /dev/null @@ -1,644 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'state.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(T value) => value; - -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - -PlaybackHistoryItem _$PlaybackHistoryItemFromJson(Map json) { - switch (json['runtimeType']) { - case 'playlist': - return PlaybackHistoryPlaylist.fromJson(json); - case 'album': - return PlaybackHistoryAlbum.fromJson(json); - case 'track': - return PlaybackHistoryTrack.fromJson(json); - - default: - throw CheckedFromJsonException(json, 'runtimeType', 'PlaybackHistoryItem', - 'Invalid union type "${json['runtimeType']}"!'); - } -} - -/// @nodoc -mixin _$PlaybackHistoryItem { - DateTime get date => throw _privateConstructorUsedError; - @optionalTypeArgs - TResult when({ - required TResult Function(DateTime date, PlaylistSimple playlist) playlist, - required TResult Function(DateTime date, AlbumSimple album) album, - required TResult Function(DateTime date, Track track) track, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult? whenOrNull({ - TResult? Function(DateTime date, PlaylistSimple playlist)? playlist, - TResult? Function(DateTime date, AlbumSimple album)? album, - TResult? Function(DateTime date, Track track)? track, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult maybeWhen({ - TResult Function(DateTime date, PlaylistSimple playlist)? playlist, - TResult Function(DateTime date, AlbumSimple album)? album, - TResult Function(DateTime date, Track track)? track, - required TResult orElse(), - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult map({ - required TResult Function(PlaybackHistoryPlaylist value) playlist, - required TResult Function(PlaybackHistoryAlbum value) album, - required TResult Function(PlaybackHistoryTrack value) track, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult? mapOrNull({ - TResult? Function(PlaybackHistoryPlaylist value)? playlist, - TResult? Function(PlaybackHistoryAlbum value)? album, - TResult? Function(PlaybackHistoryTrack value)? track, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult maybeMap({ - TResult Function(PlaybackHistoryPlaylist value)? playlist, - TResult Function(PlaybackHistoryAlbum value)? album, - TResult Function(PlaybackHistoryTrack value)? track, - required TResult orElse(), - }) => - throw _privateConstructorUsedError; - Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) - $PlaybackHistoryItemCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $PlaybackHistoryItemCopyWith<$Res> { - factory $PlaybackHistoryItemCopyWith( - PlaybackHistoryItem value, $Res Function(PlaybackHistoryItem) then) = - _$PlaybackHistoryItemCopyWithImpl<$Res, PlaybackHistoryItem>; - @useResult - $Res call({DateTime date}); -} - -/// @nodoc -class _$PlaybackHistoryItemCopyWithImpl<$Res, $Val extends PlaybackHistoryItem> - implements $PlaybackHistoryItemCopyWith<$Res> { - _$PlaybackHistoryItemCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? date = null, - }) { - return _then(_value.copyWith( - date: null == date - ? _value.date - : date // ignore: cast_nullable_to_non_nullable - as DateTime, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$PlaybackHistoryPlaylistImplCopyWith<$Res> - implements $PlaybackHistoryItemCopyWith<$Res> { - factory _$$PlaybackHistoryPlaylistImplCopyWith( - _$PlaybackHistoryPlaylistImpl value, - $Res Function(_$PlaybackHistoryPlaylistImpl) then) = - __$$PlaybackHistoryPlaylistImplCopyWithImpl<$Res>; - @override - @useResult - $Res call({DateTime date, PlaylistSimple playlist}); -} - -/// @nodoc -class __$$PlaybackHistoryPlaylistImplCopyWithImpl<$Res> - extends _$PlaybackHistoryItemCopyWithImpl<$Res, - _$PlaybackHistoryPlaylistImpl> - implements _$$PlaybackHistoryPlaylistImplCopyWith<$Res> { - __$$PlaybackHistoryPlaylistImplCopyWithImpl( - _$PlaybackHistoryPlaylistImpl _value, - $Res Function(_$PlaybackHistoryPlaylistImpl) _then) - : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? date = null, - Object? playlist = null, - }) { - return _then(_$PlaybackHistoryPlaylistImpl( - date: null == date - ? _value.date - : date // ignore: cast_nullable_to_non_nullable - as DateTime, - playlist: null == playlist - ? _value.playlist - : playlist // ignore: cast_nullable_to_non_nullable - as PlaylistSimple, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$PlaybackHistoryPlaylistImpl implements PlaybackHistoryPlaylist { - _$PlaybackHistoryPlaylistImpl( - {required this.date, required this.playlist, final String? $type}) - : $type = $type ?? 'playlist'; - - factory _$PlaybackHistoryPlaylistImpl.fromJson(Map json) => - _$$PlaybackHistoryPlaylistImplFromJson(json); - - @override - final DateTime date; - @override - final PlaylistSimple playlist; - - @JsonKey(name: 'runtimeType') - final String $type; - - @override - String toString() { - return 'PlaybackHistoryItem.playlist(date: $date, playlist: $playlist)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$PlaybackHistoryPlaylistImpl && - (identical(other.date, date) || other.date == date) && - (identical(other.playlist, playlist) || - other.playlist == playlist)); - } - - @JsonKey(ignore: true) - @override - int get hashCode => Object.hash(runtimeType, date, playlist); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$PlaybackHistoryPlaylistImplCopyWith<_$PlaybackHistoryPlaylistImpl> - get copyWith => __$$PlaybackHistoryPlaylistImplCopyWithImpl< - _$PlaybackHistoryPlaylistImpl>(this, _$identity); - - @override - @optionalTypeArgs - TResult when({ - required TResult Function(DateTime date, PlaylistSimple playlist) playlist, - required TResult Function(DateTime date, AlbumSimple album) album, - required TResult Function(DateTime date, Track track) track, - }) { - return playlist(date, this.playlist); - } - - @override - @optionalTypeArgs - TResult? whenOrNull({ - TResult? Function(DateTime date, PlaylistSimple playlist)? playlist, - TResult? Function(DateTime date, AlbumSimple album)? album, - TResult? Function(DateTime date, Track track)? track, - }) { - return playlist?.call(date, this.playlist); - } - - @override - @optionalTypeArgs - TResult maybeWhen({ - TResult Function(DateTime date, PlaylistSimple playlist)? playlist, - TResult Function(DateTime date, AlbumSimple album)? album, - TResult Function(DateTime date, Track track)? track, - required TResult orElse(), - }) { - if (playlist != null) { - return playlist(date, this.playlist); - } - return orElse(); - } - - @override - @optionalTypeArgs - TResult map({ - required TResult Function(PlaybackHistoryPlaylist value) playlist, - required TResult Function(PlaybackHistoryAlbum value) album, - required TResult Function(PlaybackHistoryTrack value) track, - }) { - return playlist(this); - } - - @override - @optionalTypeArgs - TResult? mapOrNull({ - TResult? Function(PlaybackHistoryPlaylist value)? playlist, - TResult? Function(PlaybackHistoryAlbum value)? album, - TResult? Function(PlaybackHistoryTrack value)? track, - }) { - return playlist?.call(this); - } - - @override - @optionalTypeArgs - TResult maybeMap({ - TResult Function(PlaybackHistoryPlaylist value)? playlist, - TResult Function(PlaybackHistoryAlbum value)? album, - TResult Function(PlaybackHistoryTrack value)? track, - required TResult orElse(), - }) { - if (playlist != null) { - return playlist(this); - } - return orElse(); - } - - @override - Map toJson() { - return _$$PlaybackHistoryPlaylistImplToJson( - this, - ); - } -} - -abstract class PlaybackHistoryPlaylist implements PlaybackHistoryItem { - factory PlaybackHistoryPlaylist( - {required final DateTime date, - required final PlaylistSimple playlist}) = _$PlaybackHistoryPlaylistImpl; - - factory PlaybackHistoryPlaylist.fromJson(Map json) = - _$PlaybackHistoryPlaylistImpl.fromJson; - - @override - DateTime get date; - PlaylistSimple get playlist; - @override - @JsonKey(ignore: true) - _$$PlaybackHistoryPlaylistImplCopyWith<_$PlaybackHistoryPlaylistImpl> - get copyWith => throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class _$$PlaybackHistoryAlbumImplCopyWith<$Res> - implements $PlaybackHistoryItemCopyWith<$Res> { - factory _$$PlaybackHistoryAlbumImplCopyWith(_$PlaybackHistoryAlbumImpl value, - $Res Function(_$PlaybackHistoryAlbumImpl) then) = - __$$PlaybackHistoryAlbumImplCopyWithImpl<$Res>; - @override - @useResult - $Res call({DateTime date, AlbumSimple album}); -} - -/// @nodoc -class __$$PlaybackHistoryAlbumImplCopyWithImpl<$Res> - extends _$PlaybackHistoryItemCopyWithImpl<$Res, _$PlaybackHistoryAlbumImpl> - implements _$$PlaybackHistoryAlbumImplCopyWith<$Res> { - __$$PlaybackHistoryAlbumImplCopyWithImpl(_$PlaybackHistoryAlbumImpl _value, - $Res Function(_$PlaybackHistoryAlbumImpl) _then) - : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? date = null, - Object? album = null, - }) { - return _then(_$PlaybackHistoryAlbumImpl( - date: null == date - ? _value.date - : date // ignore: cast_nullable_to_non_nullable - as DateTime, - album: null == album - ? _value.album - : album // ignore: cast_nullable_to_non_nullable - as AlbumSimple, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$PlaybackHistoryAlbumImpl implements PlaybackHistoryAlbum { - _$PlaybackHistoryAlbumImpl( - {required this.date, required this.album, final String? $type}) - : $type = $type ?? 'album'; - - factory _$PlaybackHistoryAlbumImpl.fromJson(Map json) => - _$$PlaybackHistoryAlbumImplFromJson(json); - - @override - final DateTime date; - @override - final AlbumSimple album; - - @JsonKey(name: 'runtimeType') - final String $type; - - @override - String toString() { - return 'PlaybackHistoryItem.album(date: $date, album: $album)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$PlaybackHistoryAlbumImpl && - (identical(other.date, date) || other.date == date) && - (identical(other.album, album) || other.album == album)); - } - - @JsonKey(ignore: true) - @override - int get hashCode => Object.hash(runtimeType, date, album); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$PlaybackHistoryAlbumImplCopyWith<_$PlaybackHistoryAlbumImpl> - get copyWith => - __$$PlaybackHistoryAlbumImplCopyWithImpl<_$PlaybackHistoryAlbumImpl>( - this, _$identity); - - @override - @optionalTypeArgs - TResult when({ - required TResult Function(DateTime date, PlaylistSimple playlist) playlist, - required TResult Function(DateTime date, AlbumSimple album) album, - required TResult Function(DateTime date, Track track) track, - }) { - return album(date, this.album); - } - - @override - @optionalTypeArgs - TResult? whenOrNull({ - TResult? Function(DateTime date, PlaylistSimple playlist)? playlist, - TResult? Function(DateTime date, AlbumSimple album)? album, - TResult? Function(DateTime date, Track track)? track, - }) { - return album?.call(date, this.album); - } - - @override - @optionalTypeArgs - TResult maybeWhen({ - TResult Function(DateTime date, PlaylistSimple playlist)? playlist, - TResult Function(DateTime date, AlbumSimple album)? album, - TResult Function(DateTime date, Track track)? track, - required TResult orElse(), - }) { - if (album != null) { - return album(date, this.album); - } - return orElse(); - } - - @override - @optionalTypeArgs - TResult map({ - required TResult Function(PlaybackHistoryPlaylist value) playlist, - required TResult Function(PlaybackHistoryAlbum value) album, - required TResult Function(PlaybackHistoryTrack value) track, - }) { - return album(this); - } - - @override - @optionalTypeArgs - TResult? mapOrNull({ - TResult? Function(PlaybackHistoryPlaylist value)? playlist, - TResult? Function(PlaybackHistoryAlbum value)? album, - TResult? Function(PlaybackHistoryTrack value)? track, - }) { - return album?.call(this); - } - - @override - @optionalTypeArgs - TResult maybeMap({ - TResult Function(PlaybackHistoryPlaylist value)? playlist, - TResult Function(PlaybackHistoryAlbum value)? album, - TResult Function(PlaybackHistoryTrack value)? track, - required TResult orElse(), - }) { - if (album != null) { - return album(this); - } - return orElse(); - } - - @override - Map toJson() { - return _$$PlaybackHistoryAlbumImplToJson( - this, - ); - } -} - -abstract class PlaybackHistoryAlbum implements PlaybackHistoryItem { - factory PlaybackHistoryAlbum( - {required final DateTime date, - required final AlbumSimple album}) = _$PlaybackHistoryAlbumImpl; - - factory PlaybackHistoryAlbum.fromJson(Map json) = - _$PlaybackHistoryAlbumImpl.fromJson; - - @override - DateTime get date; - AlbumSimple get album; - @override - @JsonKey(ignore: true) - _$$PlaybackHistoryAlbumImplCopyWith<_$PlaybackHistoryAlbumImpl> - get copyWith => throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class _$$PlaybackHistoryTrackImplCopyWith<$Res> - implements $PlaybackHistoryItemCopyWith<$Res> { - factory _$$PlaybackHistoryTrackImplCopyWith(_$PlaybackHistoryTrackImpl value, - $Res Function(_$PlaybackHistoryTrackImpl) then) = - __$$PlaybackHistoryTrackImplCopyWithImpl<$Res>; - @override - @useResult - $Res call({DateTime date, Track track}); -} - -/// @nodoc -class __$$PlaybackHistoryTrackImplCopyWithImpl<$Res> - extends _$PlaybackHistoryItemCopyWithImpl<$Res, _$PlaybackHistoryTrackImpl> - implements _$$PlaybackHistoryTrackImplCopyWith<$Res> { - __$$PlaybackHistoryTrackImplCopyWithImpl(_$PlaybackHistoryTrackImpl _value, - $Res Function(_$PlaybackHistoryTrackImpl) _then) - : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? date = null, - Object? track = null, - }) { - return _then(_$PlaybackHistoryTrackImpl( - date: null == date - ? _value.date - : date // ignore: cast_nullable_to_non_nullable - as DateTime, - track: null == track - ? _value.track - : track // ignore: cast_nullable_to_non_nullable - as Track, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$PlaybackHistoryTrackImpl implements PlaybackHistoryTrack { - _$PlaybackHistoryTrackImpl( - {required this.date, required this.track, final String? $type}) - : $type = $type ?? 'track'; - - factory _$PlaybackHistoryTrackImpl.fromJson(Map json) => - _$$PlaybackHistoryTrackImplFromJson(json); - - @override - final DateTime date; - @override - final Track track; - - @JsonKey(name: 'runtimeType') - final String $type; - - @override - String toString() { - return 'PlaybackHistoryItem.track(date: $date, track: $track)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$PlaybackHistoryTrackImpl && - (identical(other.date, date) || other.date == date) && - (identical(other.track, track) || other.track == track)); - } - - @JsonKey(ignore: true) - @override - int get hashCode => Object.hash(runtimeType, date, track); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$PlaybackHistoryTrackImplCopyWith<_$PlaybackHistoryTrackImpl> - get copyWith => - __$$PlaybackHistoryTrackImplCopyWithImpl<_$PlaybackHistoryTrackImpl>( - this, _$identity); - - @override - @optionalTypeArgs - TResult when({ - required TResult Function(DateTime date, PlaylistSimple playlist) playlist, - required TResult Function(DateTime date, AlbumSimple album) album, - required TResult Function(DateTime date, Track track) track, - }) { - return track(date, this.track); - } - - @override - @optionalTypeArgs - TResult? whenOrNull({ - TResult? Function(DateTime date, PlaylistSimple playlist)? playlist, - TResult? Function(DateTime date, AlbumSimple album)? album, - TResult? Function(DateTime date, Track track)? track, - }) { - return track?.call(date, this.track); - } - - @override - @optionalTypeArgs - TResult maybeWhen({ - TResult Function(DateTime date, PlaylistSimple playlist)? playlist, - TResult Function(DateTime date, AlbumSimple album)? album, - TResult Function(DateTime date, Track track)? track, - required TResult orElse(), - }) { - if (track != null) { - return track(date, this.track); - } - return orElse(); - } - - @override - @optionalTypeArgs - TResult map({ - required TResult Function(PlaybackHistoryPlaylist value) playlist, - required TResult Function(PlaybackHistoryAlbum value) album, - required TResult Function(PlaybackHistoryTrack value) track, - }) { - return track(this); - } - - @override - @optionalTypeArgs - TResult? mapOrNull({ - TResult? Function(PlaybackHistoryPlaylist value)? playlist, - TResult? Function(PlaybackHistoryAlbum value)? album, - TResult? Function(PlaybackHistoryTrack value)? track, - }) { - return track?.call(this); - } - - @override - @optionalTypeArgs - TResult maybeMap({ - TResult Function(PlaybackHistoryPlaylist value)? playlist, - TResult Function(PlaybackHistoryAlbum value)? album, - TResult Function(PlaybackHistoryTrack value)? track, - required TResult orElse(), - }) { - if (track != null) { - return track(this); - } - return orElse(); - } - - @override - Map toJson() { - return _$$PlaybackHistoryTrackImplToJson( - this, - ); - } -} - -abstract class PlaybackHistoryTrack implements PlaybackHistoryItem { - factory PlaybackHistoryTrack( - {required final DateTime date, - required final Track track}) = _$PlaybackHistoryTrackImpl; - - factory PlaybackHistoryTrack.fromJson(Map json) = - _$PlaybackHistoryTrackImpl.fromJson; - - @override - DateTime get date; - Track get track; - @override - @JsonKey(ignore: true) - _$$PlaybackHistoryTrackImplCopyWith<_$PlaybackHistoryTrackImpl> - get copyWith => throw _privateConstructorUsedError; -} diff --git a/lib/provider/history/state.g.dart b/lib/provider/history/state.g.dart deleted file mode 100644 index dfd01c2c..00000000 --- a/lib/provider/history/state.g.dart +++ /dev/null @@ -1,55 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'state.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -_$PlaybackHistoryPlaylistImpl _$$PlaybackHistoryPlaylistImplFromJson( - Map json) => - _$PlaybackHistoryPlaylistImpl( - date: DateTime.parse(json['date'] as String), - playlist: PlaylistSimple.fromJson( - Map.from(json['playlist'] as Map)), - $type: json['runtimeType'] as String?, - ); - -Map _$$PlaybackHistoryPlaylistImplToJson( - _$PlaybackHistoryPlaylistImpl instance) => - { - 'date': instance.date.toIso8601String(), - 'playlist': instance.playlist.toJson(), - 'runtimeType': instance.$type, - }; - -_$PlaybackHistoryAlbumImpl _$$PlaybackHistoryAlbumImplFromJson(Map json) => - _$PlaybackHistoryAlbumImpl( - date: DateTime.parse(json['date'] as String), - album: - AlbumSimple.fromJson(Map.from(json['album'] as Map)), - $type: json['runtimeType'] as String?, - ); - -Map _$$PlaybackHistoryAlbumImplToJson( - _$PlaybackHistoryAlbumImpl instance) => - { - 'date': instance.date.toIso8601String(), - 'album': instance.album.toJson(), - 'runtimeType': instance.$type, - }; - -_$PlaybackHistoryTrackImpl _$$PlaybackHistoryTrackImplFromJson(Map json) => - _$PlaybackHistoryTrackImpl( - date: DateTime.parse(json['date'] as String), - track: Track.fromJson(Map.from(json['track'] as Map)), - $type: json['runtimeType'] as String?, - ); - -Map _$$PlaybackHistoryTrackImplToJson( - _$PlaybackHistoryTrackImpl instance) => - { - 'date': instance.date.toIso8601String(), - 'track': instance.track.toJson(), - 'runtimeType': instance.$type, - }; diff --git a/lib/provider/history/top.dart b/lib/provider/history/top.dart index aa12c9b3..965fb3ad 100644 --- a/lib/provider/history/top.dart +++ b/lib/provider/history/top.dart @@ -6,7 +6,15 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/models/database/database.dart'; import 'package:spotube/provider/database/database.dart'; -import 'package:spotube/provider/history/state.dart'; + +enum HistoryDuration { + allTime, + days7, + days30, + months6, + year, + years2, +} final playbackHistoryTopDurationProvider = StateProvider((ref) => HistoryDuration.days30); diff --git a/lib/services/kv_store/encrypted_kv_store.dart b/lib/services/kv_store/encrypted_kv_store.dart index ab4a750e..4eca0007 100644 --- a/lib/services/kv_store/encrypted_kv_store.dart +++ b/lib/services/kv_store/encrypted_kv_store.dart @@ -10,6 +10,8 @@ abstract class EncryptedKvStoreService { ), ); + static FlutterSecureStorage get storage => _storage; + static String? _encryptionKeySync; static Future initialize() async { diff --git a/lib/services/kv_store/kv_store.dart b/lib/services/kv_store/kv_store.dart index 2707ea4d..efe83abf 100644 --- a/lib/services/kv_store/kv_store.dart +++ b/lib/services/kv_store/kv_store.dart @@ -82,4 +82,9 @@ abstract class KVStoreService { static double get volume => sharedPreferences.getDouble('volume') ?? 1.0; static Future setVolume(double value) async => await sharedPreferences.setDouble('volume', value); + + static bool get hasMigratedToDrift => + sharedPreferences.getBool('hasMigratedToDrift') ?? false; + static Future setHasMigratedToDrift(bool value) async => + await sharedPreferences.setBool('hasMigratedToDrift', value); } diff --git a/lib/utils/migrations/adapters.dart b/lib/utils/migrations/adapters.dart new file mode 100644 index 00000000..f7f6350b --- /dev/null +++ b/lib/utils/migrations/adapters.dart @@ -0,0 +1,320 @@ +import 'package:hive/hive.dart'; +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:spotify/spotify.dart'; +import 'package:spotube/modules/settings/color_scheme_picker_dialog.dart'; +import 'package:spotube/services/sourced_track/enums.dart'; + +part 'adapters.g.dart'; +part 'adapters.freezed.dart'; + +@HiveType(typeId: 2) +class SkipSegment { + @HiveField(0) + final int start; + @HiveField(1) + final int end; + SkipSegment(this.start, this.end); + + static String version = 'v1'; + static final boxName = "oss.krtirtho.spotube.skip_segments.$version"; + static LazyBox get box => Hive.lazyBox(boxName); + + SkipSegment.fromJson(Map json) + : start = json['start'], + end = json['end']; + + Map toJson() => { + 'start': start, + 'end': end, + }; +} + +@JsonEnum() +@HiveType(typeId: 5) +enum SourceType { + @HiveField(0) + youtube._("YouTube"), + + @HiveField(1) + youtubeMusic._("YouTube Music"), + + @HiveField(2) + jiosaavn._("JioSaavn"); + + final String label; + + const SourceType._(this.label); +} + +@JsonSerializable() +@HiveType(typeId: 6) +class SourceMatch { + @HiveField(0) + String id; + + @HiveField(1) + String sourceId; + + @HiveField(2) + SourceType sourceType; + + @HiveField(3) + DateTime createdAt; + + SourceMatch({ + required this.id, + required this.sourceId, + required this.sourceType, + required this.createdAt, + }); + + factory SourceMatch.fromJson(Map json) => + _$SourceMatchFromJson(json); + + Map toJson() => _$SourceMatchToJson(this); + + static String version = 'v1'; + static final boxName = "oss.krtirtho.spotube.source_matches.$version"; + + static LazyBox get box => Hive.lazyBox(boxName); +} + +@JsonSerializable() +class AuthenticationCredentials { + String cookie; + String accessToken; + DateTime expiration; + + AuthenticationCredentials({ + required this.cookie, + required this.accessToken, + required this.expiration, + }); + + factory AuthenticationCredentials.fromJson(Map json) { + return AuthenticationCredentials( + cookie: json['cookie'] as String, + accessToken: json['accessToken'] as String, + expiration: DateTime.parse(json['expiration'] as String), + ); + } + + Map toJson() { + return { + 'cookie': cookie, + 'accessToken': accessToken, + 'expiration': expiration.toIso8601String(), + }; + } +} + +@JsonEnum() +enum LayoutMode { + compact, + extended, + adaptive, +} + +@JsonEnum() +enum CloseBehavior { + minimizeToTray, + close, +} + +@JsonEnum() +enum AudioSource { + youtube, + piped, + jiosaavn; + + String get label => name[0].toUpperCase() + name.substring(1); +} + +@JsonEnum() +enum MusicCodec { + m4a._("M4a (Best for downloaded music)"), + weba._("WebA (Best for streamed music)\nDoesn't support audio metadata"); + + final String label; + const MusicCodec._(this.label); +} + +@JsonEnum() +enum SearchMode { + youtube._("YouTube"), + youtubeMusic._("YouTube Music"); + + final String label; + + const SearchMode._(this.label); + + factory SearchMode.fromString(String key) { + return SearchMode.values.firstWhere((e) => e.name == key); + } +} + +@freezed +class UserPreferences with _$UserPreferences { + const factory UserPreferences({ + @Default(SourceQualities.high) SourceQualities audioQuality, + @Default(true) bool albumColorSync, + @Default(false) bool amoledDarkTheme, + @Default(true) bool checkUpdate, + @Default(false) bool normalizeAudio, + @Default(false) bool showSystemTrayIcon, + @Default(false) bool skipNonMusic, + @Default(false) bool systemTitleBar, + @Default(CloseBehavior.close) CloseBehavior closeBehavior, + @Default(SpotubeColor(0xFF2196F3, name: "Blue")) + @JsonKey( + fromJson: UserPreferences._accentColorSchemeFromJson, + toJson: UserPreferences._accentColorSchemeToJson, + readValue: UserPreferences._accentColorSchemeReadValue, + ) + SpotubeColor accentColorScheme, + @Default(LayoutMode.adaptive) LayoutMode layoutMode, + @Default(Locale("system", "system")) + @JsonKey( + fromJson: UserPreferences._localeFromJson, + toJson: UserPreferences._localeToJson, + readValue: UserPreferences._localeReadValue, + ) + Locale locale, + @Default(Market.US) Market recommendationMarket, + @Default(SearchMode.youtube) SearchMode searchMode, + @Default("") String downloadLocation, + @Default([]) List localLibraryLocation, + @Default("https://pipedapi.kavin.rocks") String pipedInstance, + @Default(ThemeMode.system) ThemeMode themeMode, + @Default(AudioSource.youtube) AudioSource audioSource, + @Default(SourceCodecs.weba) SourceCodecs streamMusicCodec, + @Default(SourceCodecs.m4a) SourceCodecs downloadMusicCodec, + @Default(true) bool discordPresence, + @Default(true) bool endlessPlayback, + @Default(false) bool enableConnect, + }) = _UserPreferences; + factory UserPreferences.fromJson(Map json) => + _$UserPreferencesFromJson(json); + + factory UserPreferences.withDefaults() => UserPreferences.fromJson({}); + + static SpotubeColor _accentColorSchemeFromJson(Map json) { + return SpotubeColor.fromString(json["color"]); + } + + static Map? _accentColorSchemeReadValue( + Map json, String key) { + if (json[key] is String) { + return {"color": json[key]}; + } + + return json[key] as Map?; + } + + static Map _accentColorSchemeToJson(SpotubeColor color) { + return {"color": color.toString()}; + } + + static Locale _localeFromJson(Map json) { + return Locale(json["languageCode"], json["countryCode"]); + } + + static Map _localeToJson(Locale locale) { + return { + "languageCode": locale.languageCode, + "countryCode": locale.countryCode, + }; + } + + static Map? _localeReadValue( + Map json, String key) { + if (json[key] is String) { + final map = jsonDecode(json[key]); + return { + "languageCode": map["lc"], + "countryCode": map["cc"], + }; + } + + return json[key] as Map?; + } +} + +enum BlacklistedType { + artist, + track; + + static BlacklistedType fromName(String name) => + BlacklistedType.values.firstWhere((e) => e.name == name); +} + +class BlacklistedElement { + final String id; + final String name; + final BlacklistedType type; + + BlacklistedElement.fromJson(Map json) + : id = json['id'], + name = json['name'], + type = BlacklistedType.fromName(json['type']); + + Map toJson() => {'id': id, 'type': type.name, 'name': name}; +} + +@freezed +class PlaybackHistoryItem with _$PlaybackHistoryItem { + factory PlaybackHistoryItem.playlist({ + required DateTime date, + required PlaylistSimple playlist, + }) = PlaybackHistoryPlaylist; + + factory PlaybackHistoryItem.album({ + required DateTime date, + required AlbumSimple album, + }) = PlaybackHistoryAlbum; + + factory PlaybackHistoryItem.track({ + required DateTime date, + required Track track, + }) = PlaybackHistoryTrack; + + factory PlaybackHistoryItem.fromJson(Map json) => + _$PlaybackHistoryItemFromJson(json); +} + +class PlaybackHistoryState { + final List items; + const PlaybackHistoryState({this.items = const []}); + + factory PlaybackHistoryState.fromJson(Map json) { + return PlaybackHistoryState( + items: json["items"] + ?.map( + (json) => PlaybackHistoryItem.fromJson(json), + ) + .toList() + .cast() ?? + [], + ); + } +} + +class ScrobblerState { + final String username; + final String passwordHash; + + ScrobblerState({ + required this.username, + required this.passwordHash, + }); + + factory ScrobblerState.fromJson(Map json) { + return ScrobblerState( + username: json["username"], + passwordHash: json["passwordHash"], + ); + } +} diff --git a/lib/utils/migrations/adapters.freezed.dart b/lib/utils/migrations/adapters.freezed.dart new file mode 100644 index 00000000..339ec0e5 --- /dev/null +++ b/lib/utils/migrations/adapters.freezed.dart @@ -0,0 +1,1380 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'adapters.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +UserPreferences _$UserPreferencesFromJson(Map json) { + return _UserPreferences.fromJson(json); +} + +/// @nodoc +mixin _$UserPreferences { + SourceQualities get audioQuality => throw _privateConstructorUsedError; + bool get albumColorSync => throw _privateConstructorUsedError; + bool get amoledDarkTheme => throw _privateConstructorUsedError; + bool get checkUpdate => throw _privateConstructorUsedError; + bool get normalizeAudio => throw _privateConstructorUsedError; + bool get showSystemTrayIcon => throw _privateConstructorUsedError; + bool get skipNonMusic => throw _privateConstructorUsedError; + bool get systemTitleBar => throw _privateConstructorUsedError; + CloseBehavior get closeBehavior => throw _privateConstructorUsedError; + @JsonKey( + fromJson: UserPreferences._accentColorSchemeFromJson, + toJson: UserPreferences._accentColorSchemeToJson, + readValue: UserPreferences._accentColorSchemeReadValue) + SpotubeColor get accentColorScheme => throw _privateConstructorUsedError; + LayoutMode get layoutMode => throw _privateConstructorUsedError; + @JsonKey( + fromJson: UserPreferences._localeFromJson, + toJson: UserPreferences._localeToJson, + readValue: UserPreferences._localeReadValue) + Locale get locale => throw _privateConstructorUsedError; + Market get recommendationMarket => throw _privateConstructorUsedError; + SearchMode get searchMode => throw _privateConstructorUsedError; + String get downloadLocation => throw _privateConstructorUsedError; + List get localLibraryLocation => throw _privateConstructorUsedError; + String get pipedInstance => throw _privateConstructorUsedError; + ThemeMode get themeMode => throw _privateConstructorUsedError; + AudioSource get audioSource => throw _privateConstructorUsedError; + SourceCodecs get streamMusicCodec => throw _privateConstructorUsedError; + SourceCodecs get downloadMusicCodec => throw _privateConstructorUsedError; + bool get discordPresence => throw _privateConstructorUsedError; + bool get endlessPlayback => throw _privateConstructorUsedError; + bool get enableConnect => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $UserPreferencesCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $UserPreferencesCopyWith<$Res> { + factory $UserPreferencesCopyWith( + UserPreferences value, $Res Function(UserPreferences) then) = + _$UserPreferencesCopyWithImpl<$Res, UserPreferences>; + @useResult + $Res call( + {SourceQualities audioQuality, + bool albumColorSync, + bool amoledDarkTheme, + bool checkUpdate, + bool normalizeAudio, + bool showSystemTrayIcon, + bool skipNonMusic, + bool systemTitleBar, + CloseBehavior closeBehavior, + @JsonKey( + fromJson: UserPreferences._accentColorSchemeFromJson, + toJson: UserPreferences._accentColorSchemeToJson, + readValue: UserPreferences._accentColorSchemeReadValue) + SpotubeColor accentColorScheme, + LayoutMode layoutMode, + @JsonKey( + fromJson: UserPreferences._localeFromJson, + toJson: UserPreferences._localeToJson, + readValue: UserPreferences._localeReadValue) + Locale locale, + Market recommendationMarket, + SearchMode searchMode, + String downloadLocation, + List localLibraryLocation, + String pipedInstance, + ThemeMode themeMode, + AudioSource audioSource, + SourceCodecs streamMusicCodec, + SourceCodecs downloadMusicCodec, + bool discordPresence, + bool endlessPlayback, + bool enableConnect}); +} + +/// @nodoc +class _$UserPreferencesCopyWithImpl<$Res, $Val extends UserPreferences> + implements $UserPreferencesCopyWith<$Res> { + _$UserPreferencesCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? audioQuality = null, + Object? albumColorSync = null, + Object? amoledDarkTheme = null, + Object? checkUpdate = null, + Object? normalizeAudio = null, + Object? showSystemTrayIcon = null, + Object? skipNonMusic = null, + Object? systemTitleBar = null, + Object? closeBehavior = null, + Object? accentColorScheme = null, + Object? layoutMode = null, + Object? locale = null, + Object? recommendationMarket = null, + Object? searchMode = null, + Object? downloadLocation = null, + Object? localLibraryLocation = null, + Object? pipedInstance = null, + Object? themeMode = null, + Object? audioSource = null, + Object? streamMusicCodec = null, + Object? downloadMusicCodec = null, + Object? discordPresence = null, + Object? endlessPlayback = null, + Object? enableConnect = null, + }) { + return _then(_value.copyWith( + audioQuality: null == audioQuality + ? _value.audioQuality + : audioQuality // ignore: cast_nullable_to_non_nullable + as SourceQualities, + albumColorSync: null == albumColorSync + ? _value.albumColorSync + : albumColorSync // ignore: cast_nullable_to_non_nullable + as bool, + amoledDarkTheme: null == amoledDarkTheme + ? _value.amoledDarkTheme + : amoledDarkTheme // ignore: cast_nullable_to_non_nullable + as bool, + checkUpdate: null == checkUpdate + ? _value.checkUpdate + : checkUpdate // ignore: cast_nullable_to_non_nullable + as bool, + normalizeAudio: null == normalizeAudio + ? _value.normalizeAudio + : normalizeAudio // ignore: cast_nullable_to_non_nullable + as bool, + showSystemTrayIcon: null == showSystemTrayIcon + ? _value.showSystemTrayIcon + : showSystemTrayIcon // ignore: cast_nullable_to_non_nullable + as bool, + skipNonMusic: null == skipNonMusic + ? _value.skipNonMusic + : skipNonMusic // ignore: cast_nullable_to_non_nullable + as bool, + systemTitleBar: null == systemTitleBar + ? _value.systemTitleBar + : systemTitleBar // ignore: cast_nullable_to_non_nullable + as bool, + closeBehavior: null == closeBehavior + ? _value.closeBehavior + : closeBehavior // ignore: cast_nullable_to_non_nullable + as CloseBehavior, + accentColorScheme: null == accentColorScheme + ? _value.accentColorScheme + : accentColorScheme // ignore: cast_nullable_to_non_nullable + as SpotubeColor, + layoutMode: null == layoutMode + ? _value.layoutMode + : layoutMode // ignore: cast_nullable_to_non_nullable + as LayoutMode, + locale: null == locale + ? _value.locale + : locale // ignore: cast_nullable_to_non_nullable + as Locale, + recommendationMarket: null == recommendationMarket + ? _value.recommendationMarket + : recommendationMarket // ignore: cast_nullable_to_non_nullable + as Market, + searchMode: null == searchMode + ? _value.searchMode + : searchMode // ignore: cast_nullable_to_non_nullable + as SearchMode, + downloadLocation: null == downloadLocation + ? _value.downloadLocation + : downloadLocation // ignore: cast_nullable_to_non_nullable + as String, + localLibraryLocation: null == localLibraryLocation + ? _value.localLibraryLocation + : localLibraryLocation // ignore: cast_nullable_to_non_nullable + as List, + pipedInstance: null == pipedInstance + ? _value.pipedInstance + : pipedInstance // ignore: cast_nullable_to_non_nullable + as String, + themeMode: null == themeMode + ? _value.themeMode + : themeMode // ignore: cast_nullable_to_non_nullable + as ThemeMode, + audioSource: null == audioSource + ? _value.audioSource + : audioSource // ignore: cast_nullable_to_non_nullable + as AudioSource, + streamMusicCodec: null == streamMusicCodec + ? _value.streamMusicCodec + : streamMusicCodec // ignore: cast_nullable_to_non_nullable + as SourceCodecs, + downloadMusicCodec: null == downloadMusicCodec + ? _value.downloadMusicCodec + : downloadMusicCodec // ignore: cast_nullable_to_non_nullable + as SourceCodecs, + discordPresence: null == discordPresence + ? _value.discordPresence + : discordPresence // ignore: cast_nullable_to_non_nullable + as bool, + endlessPlayback: null == endlessPlayback + ? _value.endlessPlayback + : endlessPlayback // ignore: cast_nullable_to_non_nullable + as bool, + enableConnect: null == enableConnect + ? _value.enableConnect + : enableConnect // ignore: cast_nullable_to_non_nullable + as bool, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$UserPreferencesImplCopyWith<$Res> + implements $UserPreferencesCopyWith<$Res> { + factory _$$UserPreferencesImplCopyWith(_$UserPreferencesImpl value, + $Res Function(_$UserPreferencesImpl) then) = + __$$UserPreferencesImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {SourceQualities audioQuality, + bool albumColorSync, + bool amoledDarkTheme, + bool checkUpdate, + bool normalizeAudio, + bool showSystemTrayIcon, + bool skipNonMusic, + bool systemTitleBar, + CloseBehavior closeBehavior, + @JsonKey( + fromJson: UserPreferences._accentColorSchemeFromJson, + toJson: UserPreferences._accentColorSchemeToJson, + readValue: UserPreferences._accentColorSchemeReadValue) + SpotubeColor accentColorScheme, + LayoutMode layoutMode, + @JsonKey( + fromJson: UserPreferences._localeFromJson, + toJson: UserPreferences._localeToJson, + readValue: UserPreferences._localeReadValue) + Locale locale, + Market recommendationMarket, + SearchMode searchMode, + String downloadLocation, + List localLibraryLocation, + String pipedInstance, + ThemeMode themeMode, + AudioSource audioSource, + SourceCodecs streamMusicCodec, + SourceCodecs downloadMusicCodec, + bool discordPresence, + bool endlessPlayback, + bool enableConnect}); +} + +/// @nodoc +class __$$UserPreferencesImplCopyWithImpl<$Res> + extends _$UserPreferencesCopyWithImpl<$Res, _$UserPreferencesImpl> + implements _$$UserPreferencesImplCopyWith<$Res> { + __$$UserPreferencesImplCopyWithImpl( + _$UserPreferencesImpl _value, $Res Function(_$UserPreferencesImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? audioQuality = null, + Object? albumColorSync = null, + Object? amoledDarkTheme = null, + Object? checkUpdate = null, + Object? normalizeAudio = null, + Object? showSystemTrayIcon = null, + Object? skipNonMusic = null, + Object? systemTitleBar = null, + Object? closeBehavior = null, + Object? accentColorScheme = null, + Object? layoutMode = null, + Object? locale = null, + Object? recommendationMarket = null, + Object? searchMode = null, + Object? downloadLocation = null, + Object? localLibraryLocation = null, + Object? pipedInstance = null, + Object? themeMode = null, + Object? audioSource = null, + Object? streamMusicCodec = null, + Object? downloadMusicCodec = null, + Object? discordPresence = null, + Object? endlessPlayback = null, + Object? enableConnect = null, + }) { + return _then(_$UserPreferencesImpl( + audioQuality: null == audioQuality + ? _value.audioQuality + : audioQuality // ignore: cast_nullable_to_non_nullable + as SourceQualities, + albumColorSync: null == albumColorSync + ? _value.albumColorSync + : albumColorSync // ignore: cast_nullable_to_non_nullable + as bool, + amoledDarkTheme: null == amoledDarkTheme + ? _value.amoledDarkTheme + : amoledDarkTheme // ignore: cast_nullable_to_non_nullable + as bool, + checkUpdate: null == checkUpdate + ? _value.checkUpdate + : checkUpdate // ignore: cast_nullable_to_non_nullable + as bool, + normalizeAudio: null == normalizeAudio + ? _value.normalizeAudio + : normalizeAudio // ignore: cast_nullable_to_non_nullable + as bool, + showSystemTrayIcon: null == showSystemTrayIcon + ? _value.showSystemTrayIcon + : showSystemTrayIcon // ignore: cast_nullable_to_non_nullable + as bool, + skipNonMusic: null == skipNonMusic + ? _value.skipNonMusic + : skipNonMusic // ignore: cast_nullable_to_non_nullable + as bool, + systemTitleBar: null == systemTitleBar + ? _value.systemTitleBar + : systemTitleBar // ignore: cast_nullable_to_non_nullable + as bool, + closeBehavior: null == closeBehavior + ? _value.closeBehavior + : closeBehavior // ignore: cast_nullable_to_non_nullable + as CloseBehavior, + accentColorScheme: null == accentColorScheme + ? _value.accentColorScheme + : accentColorScheme // ignore: cast_nullable_to_non_nullable + as SpotubeColor, + layoutMode: null == layoutMode + ? _value.layoutMode + : layoutMode // ignore: cast_nullable_to_non_nullable + as LayoutMode, + locale: null == locale + ? _value.locale + : locale // ignore: cast_nullable_to_non_nullable + as Locale, + recommendationMarket: null == recommendationMarket + ? _value.recommendationMarket + : recommendationMarket // ignore: cast_nullable_to_non_nullable + as Market, + searchMode: null == searchMode + ? _value.searchMode + : searchMode // ignore: cast_nullable_to_non_nullable + as SearchMode, + downloadLocation: null == downloadLocation + ? _value.downloadLocation + : downloadLocation // ignore: cast_nullable_to_non_nullable + as String, + localLibraryLocation: null == localLibraryLocation + ? _value._localLibraryLocation + : localLibraryLocation // ignore: cast_nullable_to_non_nullable + as List, + pipedInstance: null == pipedInstance + ? _value.pipedInstance + : pipedInstance // ignore: cast_nullable_to_non_nullable + as String, + themeMode: null == themeMode + ? _value.themeMode + : themeMode // ignore: cast_nullable_to_non_nullable + as ThemeMode, + audioSource: null == audioSource + ? _value.audioSource + : audioSource // ignore: cast_nullable_to_non_nullable + as AudioSource, + streamMusicCodec: null == streamMusicCodec + ? _value.streamMusicCodec + : streamMusicCodec // ignore: cast_nullable_to_non_nullable + as SourceCodecs, + downloadMusicCodec: null == downloadMusicCodec + ? _value.downloadMusicCodec + : downloadMusicCodec // ignore: cast_nullable_to_non_nullable + as SourceCodecs, + discordPresence: null == discordPresence + ? _value.discordPresence + : discordPresence // ignore: cast_nullable_to_non_nullable + as bool, + endlessPlayback: null == endlessPlayback + ? _value.endlessPlayback + : endlessPlayback // ignore: cast_nullable_to_non_nullable + as bool, + enableConnect: null == enableConnect + ? _value.enableConnect + : enableConnect // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$UserPreferencesImpl implements _UserPreferences { + const _$UserPreferencesImpl( + {this.audioQuality = SourceQualities.high, + this.albumColorSync = true, + this.amoledDarkTheme = false, + this.checkUpdate = true, + this.normalizeAudio = false, + this.showSystemTrayIcon = false, + this.skipNonMusic = false, + this.systemTitleBar = false, + this.closeBehavior = CloseBehavior.close, + @JsonKey( + fromJson: UserPreferences._accentColorSchemeFromJson, + toJson: UserPreferences._accentColorSchemeToJson, + readValue: UserPreferences._accentColorSchemeReadValue) + this.accentColorScheme = const SpotubeColor(0xFF2196F3, name: "Blue"), + this.layoutMode = LayoutMode.adaptive, + @JsonKey( + fromJson: UserPreferences._localeFromJson, + toJson: UserPreferences._localeToJson, + readValue: UserPreferences._localeReadValue) + this.locale = const Locale("system", "system"), + this.recommendationMarket = Market.US, + this.searchMode = SearchMode.youtube, + this.downloadLocation = "", + final List localLibraryLocation = const [], + this.pipedInstance = "https://pipedapi.kavin.rocks", + this.themeMode = ThemeMode.system, + this.audioSource = AudioSource.youtube, + this.streamMusicCodec = SourceCodecs.weba, + this.downloadMusicCodec = SourceCodecs.m4a, + this.discordPresence = true, + this.endlessPlayback = true, + this.enableConnect = false}) + : _localLibraryLocation = localLibraryLocation; + + factory _$UserPreferencesImpl.fromJson(Map json) => + _$$UserPreferencesImplFromJson(json); + + @override + @JsonKey() + final SourceQualities audioQuality; + @override + @JsonKey() + final bool albumColorSync; + @override + @JsonKey() + final bool amoledDarkTheme; + @override + @JsonKey() + final bool checkUpdate; + @override + @JsonKey() + final bool normalizeAudio; + @override + @JsonKey() + final bool showSystemTrayIcon; + @override + @JsonKey() + final bool skipNonMusic; + @override + @JsonKey() + final bool systemTitleBar; + @override + @JsonKey() + final CloseBehavior closeBehavior; + @override + @JsonKey( + fromJson: UserPreferences._accentColorSchemeFromJson, + toJson: UserPreferences._accentColorSchemeToJson, + readValue: UserPreferences._accentColorSchemeReadValue) + final SpotubeColor accentColorScheme; + @override + @JsonKey() + final LayoutMode layoutMode; + @override + @JsonKey( + fromJson: UserPreferences._localeFromJson, + toJson: UserPreferences._localeToJson, + readValue: UserPreferences._localeReadValue) + final Locale locale; + @override + @JsonKey() + final Market recommendationMarket; + @override + @JsonKey() + final SearchMode searchMode; + @override + @JsonKey() + final String downloadLocation; + final List _localLibraryLocation; + @override + @JsonKey() + List get localLibraryLocation { + if (_localLibraryLocation is EqualUnmodifiableListView) + return _localLibraryLocation; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_localLibraryLocation); + } + + @override + @JsonKey() + final String pipedInstance; + @override + @JsonKey() + final ThemeMode themeMode; + @override + @JsonKey() + final AudioSource audioSource; + @override + @JsonKey() + final SourceCodecs streamMusicCodec; + @override + @JsonKey() + final SourceCodecs downloadMusicCodec; + @override + @JsonKey() + final bool discordPresence; + @override + @JsonKey() + final bool endlessPlayback; + @override + @JsonKey() + final bool enableConnect; + + @override + String toString() { + return 'UserPreferences(audioQuality: $audioQuality, albumColorSync: $albumColorSync, amoledDarkTheme: $amoledDarkTheme, checkUpdate: $checkUpdate, normalizeAudio: $normalizeAudio, showSystemTrayIcon: $showSystemTrayIcon, skipNonMusic: $skipNonMusic, systemTitleBar: $systemTitleBar, closeBehavior: $closeBehavior, accentColorScheme: $accentColorScheme, layoutMode: $layoutMode, locale: $locale, recommendationMarket: $recommendationMarket, searchMode: $searchMode, downloadLocation: $downloadLocation, localLibraryLocation: $localLibraryLocation, pipedInstance: $pipedInstance, themeMode: $themeMode, audioSource: $audioSource, streamMusicCodec: $streamMusicCodec, downloadMusicCodec: $downloadMusicCodec, discordPresence: $discordPresence, endlessPlayback: $endlessPlayback, enableConnect: $enableConnect)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$UserPreferencesImpl && + (identical(other.audioQuality, audioQuality) || + other.audioQuality == audioQuality) && + (identical(other.albumColorSync, albumColorSync) || + other.albumColorSync == albumColorSync) && + (identical(other.amoledDarkTheme, amoledDarkTheme) || + other.amoledDarkTheme == amoledDarkTheme) && + (identical(other.checkUpdate, checkUpdate) || + other.checkUpdate == checkUpdate) && + (identical(other.normalizeAudio, normalizeAudio) || + other.normalizeAudio == normalizeAudio) && + (identical(other.showSystemTrayIcon, showSystemTrayIcon) || + other.showSystemTrayIcon == showSystemTrayIcon) && + (identical(other.skipNonMusic, skipNonMusic) || + other.skipNonMusic == skipNonMusic) && + (identical(other.systemTitleBar, systemTitleBar) || + other.systemTitleBar == systemTitleBar) && + (identical(other.closeBehavior, closeBehavior) || + other.closeBehavior == closeBehavior) && + (identical(other.accentColorScheme, accentColorScheme) || + other.accentColorScheme == accentColorScheme) && + (identical(other.layoutMode, layoutMode) || + other.layoutMode == layoutMode) && + (identical(other.locale, locale) || other.locale == locale) && + (identical(other.recommendationMarket, recommendationMarket) || + other.recommendationMarket == recommendationMarket) && + (identical(other.searchMode, searchMode) || + other.searchMode == searchMode) && + (identical(other.downloadLocation, downloadLocation) || + other.downloadLocation == downloadLocation) && + const DeepCollectionEquality() + .equals(other._localLibraryLocation, _localLibraryLocation) && + (identical(other.pipedInstance, pipedInstance) || + other.pipedInstance == pipedInstance) && + (identical(other.themeMode, themeMode) || + other.themeMode == themeMode) && + (identical(other.audioSource, audioSource) || + other.audioSource == audioSource) && + (identical(other.streamMusicCodec, streamMusicCodec) || + other.streamMusicCodec == streamMusicCodec) && + (identical(other.downloadMusicCodec, downloadMusicCodec) || + other.downloadMusicCodec == downloadMusicCodec) && + (identical(other.discordPresence, discordPresence) || + other.discordPresence == discordPresence) && + (identical(other.endlessPlayback, endlessPlayback) || + other.endlessPlayback == endlessPlayback) && + (identical(other.enableConnect, enableConnect) || + other.enableConnect == enableConnect)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hashAll([ + runtimeType, + audioQuality, + albumColorSync, + amoledDarkTheme, + checkUpdate, + normalizeAudio, + showSystemTrayIcon, + skipNonMusic, + systemTitleBar, + closeBehavior, + accentColorScheme, + layoutMode, + locale, + recommendationMarket, + searchMode, + downloadLocation, + const DeepCollectionEquality().hash(_localLibraryLocation), + pipedInstance, + themeMode, + audioSource, + streamMusicCodec, + downloadMusicCodec, + discordPresence, + endlessPlayback, + enableConnect + ]); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$UserPreferencesImplCopyWith<_$UserPreferencesImpl> get copyWith => + __$$UserPreferencesImplCopyWithImpl<_$UserPreferencesImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$UserPreferencesImplToJson( + this, + ); + } +} + +abstract class _UserPreferences implements UserPreferences { + const factory _UserPreferences( + {final SourceQualities audioQuality, + final bool albumColorSync, + final bool amoledDarkTheme, + final bool checkUpdate, + final bool normalizeAudio, + final bool showSystemTrayIcon, + final bool skipNonMusic, + final bool systemTitleBar, + final CloseBehavior closeBehavior, + @JsonKey( + fromJson: UserPreferences._accentColorSchemeFromJson, + toJson: UserPreferences._accentColorSchemeToJson, + readValue: UserPreferences._accentColorSchemeReadValue) + final SpotubeColor accentColorScheme, + final LayoutMode layoutMode, + @JsonKey( + fromJson: UserPreferences._localeFromJson, + toJson: UserPreferences._localeToJson, + readValue: UserPreferences._localeReadValue) + final Locale locale, + final Market recommendationMarket, + final SearchMode searchMode, + final String downloadLocation, + final List localLibraryLocation, + final String pipedInstance, + final ThemeMode themeMode, + final AudioSource audioSource, + final SourceCodecs streamMusicCodec, + final SourceCodecs downloadMusicCodec, + final bool discordPresence, + final bool endlessPlayback, + final bool enableConnect}) = _$UserPreferencesImpl; + + factory _UserPreferences.fromJson(Map json) = + _$UserPreferencesImpl.fromJson; + + @override + SourceQualities get audioQuality; + @override + bool get albumColorSync; + @override + bool get amoledDarkTheme; + @override + bool get checkUpdate; + @override + bool get normalizeAudio; + @override + bool get showSystemTrayIcon; + @override + bool get skipNonMusic; + @override + bool get systemTitleBar; + @override + CloseBehavior get closeBehavior; + @override + @JsonKey( + fromJson: UserPreferences._accentColorSchemeFromJson, + toJson: UserPreferences._accentColorSchemeToJson, + readValue: UserPreferences._accentColorSchemeReadValue) + SpotubeColor get accentColorScheme; + @override + LayoutMode get layoutMode; + @override + @JsonKey( + fromJson: UserPreferences._localeFromJson, + toJson: UserPreferences._localeToJson, + readValue: UserPreferences._localeReadValue) + Locale get locale; + @override + Market get recommendationMarket; + @override + SearchMode get searchMode; + @override + String get downloadLocation; + @override + List get localLibraryLocation; + @override + String get pipedInstance; + @override + ThemeMode get themeMode; + @override + AudioSource get audioSource; + @override + SourceCodecs get streamMusicCodec; + @override + SourceCodecs get downloadMusicCodec; + @override + bool get discordPresence; + @override + bool get endlessPlayback; + @override + bool get enableConnect; + @override + @JsonKey(ignore: true) + _$$UserPreferencesImplCopyWith<_$UserPreferencesImpl> get copyWith => + throw _privateConstructorUsedError; +} + +PlaybackHistoryItem _$PlaybackHistoryItemFromJson(Map json) { + switch (json['runtimeType']) { + case 'playlist': + return PlaybackHistoryPlaylist.fromJson(json); + case 'album': + return PlaybackHistoryAlbum.fromJson(json); + case 'track': + return PlaybackHistoryTrack.fromJson(json); + + default: + throw CheckedFromJsonException(json, 'runtimeType', 'PlaybackHistoryItem', + 'Invalid union type "${json['runtimeType']}"!'); + } +} + +/// @nodoc +mixin _$PlaybackHistoryItem { + DateTime get date => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult when({ + required TResult Function(DateTime date, PlaylistSimple playlist) playlist, + required TResult Function(DateTime date, AlbumSimple album) album, + required TResult Function(DateTime date, Track track) track, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(DateTime date, PlaylistSimple playlist)? playlist, + TResult? Function(DateTime date, AlbumSimple album)? album, + TResult? Function(DateTime date, Track track)? track, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(DateTime date, PlaylistSimple playlist)? playlist, + TResult Function(DateTime date, AlbumSimple album)? album, + TResult Function(DateTime date, Track track)? track, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(PlaybackHistoryPlaylist value) playlist, + required TResult Function(PlaybackHistoryAlbum value) album, + required TResult Function(PlaybackHistoryTrack value) track, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(PlaybackHistoryPlaylist value)? playlist, + TResult? Function(PlaybackHistoryAlbum value)? album, + TResult? Function(PlaybackHistoryTrack value)? track, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(PlaybackHistoryPlaylist value)? playlist, + TResult Function(PlaybackHistoryAlbum value)? album, + TResult Function(PlaybackHistoryTrack value)? track, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $PlaybackHistoryItemCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $PlaybackHistoryItemCopyWith<$Res> { + factory $PlaybackHistoryItemCopyWith( + PlaybackHistoryItem value, $Res Function(PlaybackHistoryItem) then) = + _$PlaybackHistoryItemCopyWithImpl<$Res, PlaybackHistoryItem>; + @useResult + $Res call({DateTime date}); +} + +/// @nodoc +class _$PlaybackHistoryItemCopyWithImpl<$Res, $Val extends PlaybackHistoryItem> + implements $PlaybackHistoryItemCopyWith<$Res> { + _$PlaybackHistoryItemCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? date = null, + }) { + return _then(_value.copyWith( + date: null == date + ? _value.date + : date // ignore: cast_nullable_to_non_nullable + as DateTime, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$PlaybackHistoryPlaylistImplCopyWith<$Res> + implements $PlaybackHistoryItemCopyWith<$Res> { + factory _$$PlaybackHistoryPlaylistImplCopyWith( + _$PlaybackHistoryPlaylistImpl value, + $Res Function(_$PlaybackHistoryPlaylistImpl) then) = + __$$PlaybackHistoryPlaylistImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({DateTime date, PlaylistSimple playlist}); +} + +/// @nodoc +class __$$PlaybackHistoryPlaylistImplCopyWithImpl<$Res> + extends _$PlaybackHistoryItemCopyWithImpl<$Res, + _$PlaybackHistoryPlaylistImpl> + implements _$$PlaybackHistoryPlaylistImplCopyWith<$Res> { + __$$PlaybackHistoryPlaylistImplCopyWithImpl( + _$PlaybackHistoryPlaylistImpl _value, + $Res Function(_$PlaybackHistoryPlaylistImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? date = null, + Object? playlist = null, + }) { + return _then(_$PlaybackHistoryPlaylistImpl( + date: null == date + ? _value.date + : date // ignore: cast_nullable_to_non_nullable + as DateTime, + playlist: null == playlist + ? _value.playlist + : playlist // ignore: cast_nullable_to_non_nullable + as PlaylistSimple, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$PlaybackHistoryPlaylistImpl implements PlaybackHistoryPlaylist { + _$PlaybackHistoryPlaylistImpl( + {required this.date, required this.playlist, final String? $type}) + : $type = $type ?? 'playlist'; + + factory _$PlaybackHistoryPlaylistImpl.fromJson(Map json) => + _$$PlaybackHistoryPlaylistImplFromJson(json); + + @override + final DateTime date; + @override + final PlaylistSimple playlist; + + @JsonKey(name: 'runtimeType') + final String $type; + + @override + String toString() { + return 'PlaybackHistoryItem.playlist(date: $date, playlist: $playlist)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$PlaybackHistoryPlaylistImpl && + (identical(other.date, date) || other.date == date) && + (identical(other.playlist, playlist) || + other.playlist == playlist)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, date, playlist); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$PlaybackHistoryPlaylistImplCopyWith<_$PlaybackHistoryPlaylistImpl> + get copyWith => __$$PlaybackHistoryPlaylistImplCopyWithImpl< + _$PlaybackHistoryPlaylistImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(DateTime date, PlaylistSimple playlist) playlist, + required TResult Function(DateTime date, AlbumSimple album) album, + required TResult Function(DateTime date, Track track) track, + }) { + return playlist(date, this.playlist); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(DateTime date, PlaylistSimple playlist)? playlist, + TResult? Function(DateTime date, AlbumSimple album)? album, + TResult? Function(DateTime date, Track track)? track, + }) { + return playlist?.call(date, this.playlist); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(DateTime date, PlaylistSimple playlist)? playlist, + TResult Function(DateTime date, AlbumSimple album)? album, + TResult Function(DateTime date, Track track)? track, + required TResult orElse(), + }) { + if (playlist != null) { + return playlist(date, this.playlist); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(PlaybackHistoryPlaylist value) playlist, + required TResult Function(PlaybackHistoryAlbum value) album, + required TResult Function(PlaybackHistoryTrack value) track, + }) { + return playlist(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(PlaybackHistoryPlaylist value)? playlist, + TResult? Function(PlaybackHistoryAlbum value)? album, + TResult? Function(PlaybackHistoryTrack value)? track, + }) { + return playlist?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(PlaybackHistoryPlaylist value)? playlist, + TResult Function(PlaybackHistoryAlbum value)? album, + TResult Function(PlaybackHistoryTrack value)? track, + required TResult orElse(), + }) { + if (playlist != null) { + return playlist(this); + } + return orElse(); + } + + @override + Map toJson() { + return _$$PlaybackHistoryPlaylistImplToJson( + this, + ); + } +} + +abstract class PlaybackHistoryPlaylist implements PlaybackHistoryItem { + factory PlaybackHistoryPlaylist( + {required final DateTime date, + required final PlaylistSimple playlist}) = _$PlaybackHistoryPlaylistImpl; + + factory PlaybackHistoryPlaylist.fromJson(Map json) = + _$PlaybackHistoryPlaylistImpl.fromJson; + + @override + DateTime get date; + PlaylistSimple get playlist; + @override + @JsonKey(ignore: true) + _$$PlaybackHistoryPlaylistImplCopyWith<_$PlaybackHistoryPlaylistImpl> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$PlaybackHistoryAlbumImplCopyWith<$Res> + implements $PlaybackHistoryItemCopyWith<$Res> { + factory _$$PlaybackHistoryAlbumImplCopyWith(_$PlaybackHistoryAlbumImpl value, + $Res Function(_$PlaybackHistoryAlbumImpl) then) = + __$$PlaybackHistoryAlbumImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({DateTime date, AlbumSimple album}); +} + +/// @nodoc +class __$$PlaybackHistoryAlbumImplCopyWithImpl<$Res> + extends _$PlaybackHistoryItemCopyWithImpl<$Res, _$PlaybackHistoryAlbumImpl> + implements _$$PlaybackHistoryAlbumImplCopyWith<$Res> { + __$$PlaybackHistoryAlbumImplCopyWithImpl(_$PlaybackHistoryAlbumImpl _value, + $Res Function(_$PlaybackHistoryAlbumImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? date = null, + Object? album = null, + }) { + return _then(_$PlaybackHistoryAlbumImpl( + date: null == date + ? _value.date + : date // ignore: cast_nullable_to_non_nullable + as DateTime, + album: null == album + ? _value.album + : album // ignore: cast_nullable_to_non_nullable + as AlbumSimple, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$PlaybackHistoryAlbumImpl implements PlaybackHistoryAlbum { + _$PlaybackHistoryAlbumImpl( + {required this.date, required this.album, final String? $type}) + : $type = $type ?? 'album'; + + factory _$PlaybackHistoryAlbumImpl.fromJson(Map json) => + _$$PlaybackHistoryAlbumImplFromJson(json); + + @override + final DateTime date; + @override + final AlbumSimple album; + + @JsonKey(name: 'runtimeType') + final String $type; + + @override + String toString() { + return 'PlaybackHistoryItem.album(date: $date, album: $album)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$PlaybackHistoryAlbumImpl && + (identical(other.date, date) || other.date == date) && + (identical(other.album, album) || other.album == album)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, date, album); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$PlaybackHistoryAlbumImplCopyWith<_$PlaybackHistoryAlbumImpl> + get copyWith => + __$$PlaybackHistoryAlbumImplCopyWithImpl<_$PlaybackHistoryAlbumImpl>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(DateTime date, PlaylistSimple playlist) playlist, + required TResult Function(DateTime date, AlbumSimple album) album, + required TResult Function(DateTime date, Track track) track, + }) { + return album(date, this.album); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(DateTime date, PlaylistSimple playlist)? playlist, + TResult? Function(DateTime date, AlbumSimple album)? album, + TResult? Function(DateTime date, Track track)? track, + }) { + return album?.call(date, this.album); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(DateTime date, PlaylistSimple playlist)? playlist, + TResult Function(DateTime date, AlbumSimple album)? album, + TResult Function(DateTime date, Track track)? track, + required TResult orElse(), + }) { + if (album != null) { + return album(date, this.album); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(PlaybackHistoryPlaylist value) playlist, + required TResult Function(PlaybackHistoryAlbum value) album, + required TResult Function(PlaybackHistoryTrack value) track, + }) { + return album(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(PlaybackHistoryPlaylist value)? playlist, + TResult? Function(PlaybackHistoryAlbum value)? album, + TResult? Function(PlaybackHistoryTrack value)? track, + }) { + return album?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(PlaybackHistoryPlaylist value)? playlist, + TResult Function(PlaybackHistoryAlbum value)? album, + TResult Function(PlaybackHistoryTrack value)? track, + required TResult orElse(), + }) { + if (album != null) { + return album(this); + } + return orElse(); + } + + @override + Map toJson() { + return _$$PlaybackHistoryAlbumImplToJson( + this, + ); + } +} + +abstract class PlaybackHistoryAlbum implements PlaybackHistoryItem { + factory PlaybackHistoryAlbum( + {required final DateTime date, + required final AlbumSimple album}) = _$PlaybackHistoryAlbumImpl; + + factory PlaybackHistoryAlbum.fromJson(Map json) = + _$PlaybackHistoryAlbumImpl.fromJson; + + @override + DateTime get date; + AlbumSimple get album; + @override + @JsonKey(ignore: true) + _$$PlaybackHistoryAlbumImplCopyWith<_$PlaybackHistoryAlbumImpl> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$PlaybackHistoryTrackImplCopyWith<$Res> + implements $PlaybackHistoryItemCopyWith<$Res> { + factory _$$PlaybackHistoryTrackImplCopyWith(_$PlaybackHistoryTrackImpl value, + $Res Function(_$PlaybackHistoryTrackImpl) then) = + __$$PlaybackHistoryTrackImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({DateTime date, Track track}); +} + +/// @nodoc +class __$$PlaybackHistoryTrackImplCopyWithImpl<$Res> + extends _$PlaybackHistoryItemCopyWithImpl<$Res, _$PlaybackHistoryTrackImpl> + implements _$$PlaybackHistoryTrackImplCopyWith<$Res> { + __$$PlaybackHistoryTrackImplCopyWithImpl(_$PlaybackHistoryTrackImpl _value, + $Res Function(_$PlaybackHistoryTrackImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? date = null, + Object? track = null, + }) { + return _then(_$PlaybackHistoryTrackImpl( + date: null == date + ? _value.date + : date // ignore: cast_nullable_to_non_nullable + as DateTime, + track: null == track + ? _value.track + : track // ignore: cast_nullable_to_non_nullable + as Track, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$PlaybackHistoryTrackImpl implements PlaybackHistoryTrack { + _$PlaybackHistoryTrackImpl( + {required this.date, required this.track, final String? $type}) + : $type = $type ?? 'track'; + + factory _$PlaybackHistoryTrackImpl.fromJson(Map json) => + _$$PlaybackHistoryTrackImplFromJson(json); + + @override + final DateTime date; + @override + final Track track; + + @JsonKey(name: 'runtimeType') + final String $type; + + @override + String toString() { + return 'PlaybackHistoryItem.track(date: $date, track: $track)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$PlaybackHistoryTrackImpl && + (identical(other.date, date) || other.date == date) && + (identical(other.track, track) || other.track == track)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, date, track); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$PlaybackHistoryTrackImplCopyWith<_$PlaybackHistoryTrackImpl> + get copyWith => + __$$PlaybackHistoryTrackImplCopyWithImpl<_$PlaybackHistoryTrackImpl>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(DateTime date, PlaylistSimple playlist) playlist, + required TResult Function(DateTime date, AlbumSimple album) album, + required TResult Function(DateTime date, Track track) track, + }) { + return track(date, this.track); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(DateTime date, PlaylistSimple playlist)? playlist, + TResult? Function(DateTime date, AlbumSimple album)? album, + TResult? Function(DateTime date, Track track)? track, + }) { + return track?.call(date, this.track); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(DateTime date, PlaylistSimple playlist)? playlist, + TResult Function(DateTime date, AlbumSimple album)? album, + TResult Function(DateTime date, Track track)? track, + required TResult orElse(), + }) { + if (track != null) { + return track(date, this.track); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(PlaybackHistoryPlaylist value) playlist, + required TResult Function(PlaybackHistoryAlbum value) album, + required TResult Function(PlaybackHistoryTrack value) track, + }) { + return track(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(PlaybackHistoryPlaylist value)? playlist, + TResult? Function(PlaybackHistoryAlbum value)? album, + TResult? Function(PlaybackHistoryTrack value)? track, + }) { + return track?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(PlaybackHistoryPlaylist value)? playlist, + TResult Function(PlaybackHistoryAlbum value)? album, + TResult Function(PlaybackHistoryTrack value)? track, + required TResult orElse(), + }) { + if (track != null) { + return track(this); + } + return orElse(); + } + + @override + Map toJson() { + return _$$PlaybackHistoryTrackImplToJson( + this, + ); + } +} + +abstract class PlaybackHistoryTrack implements PlaybackHistoryItem { + factory PlaybackHistoryTrack( + {required final DateTime date, + required final Track track}) = _$PlaybackHistoryTrackImpl; + + factory PlaybackHistoryTrack.fromJson(Map json) = + _$PlaybackHistoryTrackImpl.fromJson; + + @override + DateTime get date; + Track get track; + @override + @JsonKey(ignore: true) + _$$PlaybackHistoryTrackImplCopyWith<_$PlaybackHistoryTrackImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/utils/migrations/adapters.g.dart b/lib/utils/migrations/adapters.g.dart new file mode 100644 index 00000000..ca95a840 --- /dev/null +++ b/lib/utils/migrations/adapters.g.dart @@ -0,0 +1,600 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'adapters.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class SkipSegmentAdapter extends TypeAdapter { + @override + final int typeId = 2; + + @override + SkipSegment read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return SkipSegment( + fields[0] as int, + fields[1] as int, + ); + } + + @override + void write(BinaryWriter writer, SkipSegment obj) { + writer + ..writeByte(2) + ..writeByte(0) + ..write(obj.start) + ..writeByte(1) + ..write(obj.end); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is SkipSegmentAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} + +class SourceMatchAdapter extends TypeAdapter { + @override + final int typeId = 6; + + @override + SourceMatch read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return SourceMatch( + id: fields[0] as String, + sourceId: fields[1] as String, + sourceType: fields[2] as SourceType, + createdAt: fields[3] as DateTime, + ); + } + + @override + void write(BinaryWriter writer, SourceMatch obj) { + writer + ..writeByte(4) + ..writeByte(0) + ..write(obj.id) + ..writeByte(1) + ..write(obj.sourceId) + ..writeByte(2) + ..write(obj.sourceType) + ..writeByte(3) + ..write(obj.createdAt); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is SourceMatchAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} + +class SourceTypeAdapter extends TypeAdapter { + @override + final int typeId = 5; + + @override + SourceType read(BinaryReader reader) { + switch (reader.readByte()) { + case 0: + return SourceType.youtube; + case 1: + return SourceType.youtubeMusic; + case 2: + return SourceType.jiosaavn; + default: + return SourceType.youtube; + } + } + + @override + void write(BinaryWriter writer, SourceType obj) { + switch (obj) { + case SourceType.youtube: + writer.writeByte(0); + break; + case SourceType.youtubeMusic: + writer.writeByte(1); + break; + case SourceType.jiosaavn: + writer.writeByte(2); + break; + } + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is SourceTypeAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +SourceMatch _$SourceMatchFromJson(Map json) => SourceMatch( + id: json['id'] as String, + sourceId: json['sourceId'] as String, + sourceType: $enumDecode(_$SourceTypeEnumMap, json['sourceType']), + createdAt: DateTime.parse(json['createdAt'] as String), + ); + +Map _$SourceMatchToJson(SourceMatch instance) => + { + 'id': instance.id, + 'sourceId': instance.sourceId, + 'sourceType': _$SourceTypeEnumMap[instance.sourceType]!, + 'createdAt': instance.createdAt.toIso8601String(), + }; + +const _$SourceTypeEnumMap = { + SourceType.youtube: 'youtube', + SourceType.youtubeMusic: 'youtubeMusic', + SourceType.jiosaavn: 'jiosaavn', +}; + +AuthenticationCredentials _$AuthenticationCredentialsFromJson(Map json) => + AuthenticationCredentials( + cookie: json['cookie'] as String, + accessToken: json['accessToken'] as String, + expiration: DateTime.parse(json['expiration'] as String), + ); + +Map _$AuthenticationCredentialsToJson( + AuthenticationCredentials instance) => + { + 'cookie': instance.cookie, + 'accessToken': instance.accessToken, + 'expiration': instance.expiration.toIso8601String(), + }; + +_$UserPreferencesImpl _$$UserPreferencesImplFromJson(Map json) => + _$UserPreferencesImpl( + audioQuality: + $enumDecodeNullable(_$SourceQualitiesEnumMap, json['audioQuality']) ?? + SourceQualities.high, + albumColorSync: json['albumColorSync'] as bool? ?? true, + amoledDarkTheme: json['amoledDarkTheme'] as bool? ?? false, + checkUpdate: json['checkUpdate'] as bool? ?? true, + normalizeAudio: json['normalizeAudio'] as bool? ?? false, + showSystemTrayIcon: json['showSystemTrayIcon'] as bool? ?? false, + skipNonMusic: json['skipNonMusic'] as bool? ?? false, + systemTitleBar: json['systemTitleBar'] as bool? ?? false, + closeBehavior: + $enumDecodeNullable(_$CloseBehaviorEnumMap, json['closeBehavior']) ?? + CloseBehavior.close, + accentColorScheme: UserPreferences._accentColorSchemeReadValue( + json, 'accentColorScheme') == + null + ? const SpotubeColor(0xFF2196F3, name: "Blue") + : UserPreferences._accentColorSchemeFromJson( + UserPreferences._accentColorSchemeReadValue( + json, 'accentColorScheme') as Map), + layoutMode: + $enumDecodeNullable(_$LayoutModeEnumMap, json['layoutMode']) ?? + LayoutMode.adaptive, + locale: UserPreferences._localeReadValue(json, 'locale') == null + ? const Locale("system", "system") + : UserPreferences._localeFromJson( + UserPreferences._localeReadValue(json, 'locale') + as Map), + recommendationMarket: + $enumDecodeNullable(_$MarketEnumMap, json['recommendationMarket']) ?? + Market.US, + searchMode: + $enumDecodeNullable(_$SearchModeEnumMap, json['searchMode']) ?? + SearchMode.youtube, + downloadLocation: json['downloadLocation'] as String? ?? "", + localLibraryLocation: (json['localLibraryLocation'] as List?) + ?.map((e) => e as String) + .toList() ?? + const [], + pipedInstance: + json['pipedInstance'] as String? ?? "https://pipedapi.kavin.rocks", + themeMode: $enumDecodeNullable(_$ThemeModeEnumMap, json['themeMode']) ?? + ThemeMode.system, + audioSource: + $enumDecodeNullable(_$AudioSourceEnumMap, json['audioSource']) ?? + AudioSource.youtube, + streamMusicCodec: $enumDecodeNullable( + _$SourceCodecsEnumMap, json['streamMusicCodec']) ?? + SourceCodecs.weba, + downloadMusicCodec: $enumDecodeNullable( + _$SourceCodecsEnumMap, json['downloadMusicCodec']) ?? + SourceCodecs.m4a, + discordPresence: json['discordPresence'] as bool? ?? true, + endlessPlayback: json['endlessPlayback'] as bool? ?? true, + enableConnect: json['enableConnect'] as bool? ?? false, + ); + +Map _$$UserPreferencesImplToJson( + _$UserPreferencesImpl instance) => + { + 'audioQuality': _$SourceQualitiesEnumMap[instance.audioQuality]!, + 'albumColorSync': instance.albumColorSync, + 'amoledDarkTheme': instance.amoledDarkTheme, + 'checkUpdate': instance.checkUpdate, + 'normalizeAudio': instance.normalizeAudio, + 'showSystemTrayIcon': instance.showSystemTrayIcon, + 'skipNonMusic': instance.skipNonMusic, + 'systemTitleBar': instance.systemTitleBar, + 'closeBehavior': _$CloseBehaviorEnumMap[instance.closeBehavior]!, + 'accentColorScheme': + UserPreferences._accentColorSchemeToJson(instance.accentColorScheme), + 'layoutMode': _$LayoutModeEnumMap[instance.layoutMode]!, + 'locale': UserPreferences._localeToJson(instance.locale), + 'recommendationMarket': _$MarketEnumMap[instance.recommendationMarket]!, + 'searchMode': _$SearchModeEnumMap[instance.searchMode]!, + 'downloadLocation': instance.downloadLocation, + 'localLibraryLocation': instance.localLibraryLocation, + 'pipedInstance': instance.pipedInstance, + 'themeMode': _$ThemeModeEnumMap[instance.themeMode]!, + 'audioSource': _$AudioSourceEnumMap[instance.audioSource]!, + 'streamMusicCodec': _$SourceCodecsEnumMap[instance.streamMusicCodec]!, + 'downloadMusicCodec': _$SourceCodecsEnumMap[instance.downloadMusicCodec]!, + 'discordPresence': instance.discordPresence, + 'endlessPlayback': instance.endlessPlayback, + 'enableConnect': instance.enableConnect, + }; + +const _$SourceQualitiesEnumMap = { + SourceQualities.high: 'high', + SourceQualities.medium: 'medium', + SourceQualities.low: 'low', +}; + +const _$CloseBehaviorEnumMap = { + CloseBehavior.minimizeToTray: 'minimizeToTray', + CloseBehavior.close: 'close', +}; + +const _$LayoutModeEnumMap = { + LayoutMode.compact: 'compact', + LayoutMode.extended: 'extended', + LayoutMode.adaptive: 'adaptive', +}; + +const _$MarketEnumMap = { + Market.AD: 'AD', + Market.AE: 'AE', + Market.AF: 'AF', + Market.AG: 'AG', + Market.AI: 'AI', + Market.AL: 'AL', + Market.AM: 'AM', + Market.AO: 'AO', + Market.AQ: 'AQ', + Market.AR: 'AR', + Market.AS: 'AS', + Market.AT: 'AT', + Market.AU: 'AU', + Market.AW: 'AW', + Market.AX: 'AX', + Market.AZ: 'AZ', + Market.BA: 'BA', + Market.BB: 'BB', + Market.BD: 'BD', + Market.BE: 'BE', + Market.BF: 'BF', + Market.BG: 'BG', + Market.BH: 'BH', + Market.BI: 'BI', + Market.BJ: 'BJ', + Market.BL: 'BL', + Market.BM: 'BM', + Market.BN: 'BN', + Market.BO: 'BO', + Market.BQ: 'BQ', + Market.BR: 'BR', + Market.BS: 'BS', + Market.BT: 'BT', + Market.BV: 'BV', + Market.BW: 'BW', + Market.BY: 'BY', + Market.BZ: 'BZ', + Market.CA: 'CA', + Market.CC: 'CC', + Market.CD: 'CD', + Market.CF: 'CF', + Market.CG: 'CG', + Market.CH: 'CH', + Market.CI: 'CI', + Market.CK: 'CK', + Market.CL: 'CL', + Market.CM: 'CM', + Market.CN: 'CN', + Market.CO: 'CO', + Market.CR: 'CR', + Market.CU: 'CU', + Market.CV: 'CV', + Market.CW: 'CW', + Market.CX: 'CX', + Market.CY: 'CY', + Market.CZ: 'CZ', + Market.DE: 'DE', + Market.DJ: 'DJ', + Market.DK: 'DK', + Market.DM: 'DM', + Market.DO: 'DO', + Market.DZ: 'DZ', + Market.EC: 'EC', + Market.EE: 'EE', + Market.EG: 'EG', + Market.EH: 'EH', + Market.ER: 'ER', + Market.ES: 'ES', + Market.ET: 'ET', + Market.FI: 'FI', + Market.FJ: 'FJ', + Market.FK: 'FK', + Market.FM: 'FM', + Market.FO: 'FO', + Market.FR: 'FR', + Market.GA: 'GA', + Market.GB: 'GB', + Market.GD: 'GD', + Market.GE: 'GE', + Market.GF: 'GF', + Market.GG: 'GG', + Market.GH: 'GH', + Market.GI: 'GI', + Market.GL: 'GL', + Market.GM: 'GM', + Market.GN: 'GN', + Market.GP: 'GP', + Market.GQ: 'GQ', + Market.GR: 'GR', + Market.GS: 'GS', + Market.GT: 'GT', + Market.GU: 'GU', + Market.GW: 'GW', + Market.GY: 'GY', + Market.HK: 'HK', + Market.HM: 'HM', + Market.HN: 'HN', + Market.HR: 'HR', + Market.HT: 'HT', + Market.HU: 'HU', + Market.ID: 'ID', + Market.IE: 'IE', + Market.IL: 'IL', + Market.IM: 'IM', + Market.IN: 'IN', + Market.IO: 'IO', + Market.IQ: 'IQ', + Market.IR: 'IR', + Market.IS: 'IS', + Market.IT: 'IT', + Market.JE: 'JE', + Market.JM: 'JM', + Market.JO: 'JO', + Market.JP: 'JP', + Market.KE: 'KE', + Market.KG: 'KG', + Market.KH: 'KH', + Market.KI: 'KI', + Market.KM: 'KM', + Market.KN: 'KN', + Market.KP: 'KP', + Market.KR: 'KR', + Market.KW: 'KW', + Market.KY: 'KY', + Market.KZ: 'KZ', + Market.LA: 'LA', + Market.LB: 'LB', + Market.LC: 'LC', + Market.LI: 'LI', + Market.LK: 'LK', + Market.LR: 'LR', + Market.LS: 'LS', + Market.LT: 'LT', + Market.LU: 'LU', + Market.LV: 'LV', + Market.LY: 'LY', + Market.MA: 'MA', + Market.MC: 'MC', + Market.MD: 'MD', + Market.ME: 'ME', + Market.MF: 'MF', + Market.MG: 'MG', + Market.MH: 'MH', + Market.MK: 'MK', + Market.ML: 'ML', + Market.MM: 'MM', + Market.MN: 'MN', + Market.MO: 'MO', + Market.MP: 'MP', + Market.MQ: 'MQ', + Market.MR: 'MR', + Market.MS: 'MS', + Market.MT: 'MT', + Market.MU: 'MU', + Market.MV: 'MV', + Market.MW: 'MW', + Market.MX: 'MX', + Market.MY: 'MY', + Market.MZ: 'MZ', + Market.NA: 'NA', + Market.NC: 'NC', + Market.NE: 'NE', + Market.NF: 'NF', + Market.NG: 'NG', + Market.NI: 'NI', + Market.NL: 'NL', + Market.NO: 'NO', + Market.NP: 'NP', + Market.NR: 'NR', + Market.NU: 'NU', + Market.NZ: 'NZ', + Market.OM: 'OM', + Market.PA: 'PA', + Market.PE: 'PE', + Market.PF: 'PF', + Market.PG: 'PG', + Market.PH: 'PH', + Market.PK: 'PK', + Market.PL: 'PL', + Market.PM: 'PM', + Market.PN: 'PN', + Market.PR: 'PR', + Market.PS: 'PS', + Market.PT: 'PT', + Market.PW: 'PW', + Market.PY: 'PY', + Market.QA: 'QA', + Market.RE: 'RE', + Market.RO: 'RO', + Market.RS: 'RS', + Market.RU: 'RU', + Market.RW: 'RW', + Market.SA: 'SA', + Market.SB: 'SB', + Market.SC: 'SC', + Market.SD: 'SD', + Market.SE: 'SE', + Market.SG: 'SG', + Market.SH: 'SH', + Market.SI: 'SI', + Market.SJ: 'SJ', + Market.SK: 'SK', + Market.SL: 'SL', + Market.SM: 'SM', + Market.SN: 'SN', + Market.SO: 'SO', + Market.SR: 'SR', + Market.SS: 'SS', + Market.ST: 'ST', + Market.SV: 'SV', + Market.SX: 'SX', + Market.SY: 'SY', + Market.SZ: 'SZ', + Market.TC: 'TC', + Market.TD: 'TD', + Market.TF: 'TF', + Market.TG: 'TG', + Market.TH: 'TH', + Market.TJ: 'TJ', + Market.TK: 'TK', + Market.TL: 'TL', + Market.TM: 'TM', + Market.TN: 'TN', + Market.TO: 'TO', + Market.TR: 'TR', + Market.TT: 'TT', + Market.TV: 'TV', + Market.TW: 'TW', + Market.TZ: 'TZ', + Market.UA: 'UA', + Market.UG: 'UG', + Market.UM: 'UM', + Market.US: 'US', + Market.UY: 'UY', + Market.UZ: 'UZ', + Market.VA: 'VA', + Market.VC: 'VC', + Market.VE: 'VE', + Market.VG: 'VG', + Market.VI: 'VI', + Market.VN: 'VN', + Market.VU: 'VU', + Market.WF: 'WF', + Market.WS: 'WS', + Market.XK: 'XK', + Market.YE: 'YE', + Market.YT: 'YT', + Market.ZA: 'ZA', + Market.ZM: 'ZM', + Market.ZW: 'ZW', +}; + +const _$SearchModeEnumMap = { + SearchMode.youtube: 'youtube', + SearchMode.youtubeMusic: 'youtubeMusic', +}; + +const _$ThemeModeEnumMap = { + ThemeMode.system: 'system', + ThemeMode.light: 'light', + ThemeMode.dark: 'dark', +}; + +const _$AudioSourceEnumMap = { + AudioSource.youtube: 'youtube', + AudioSource.piped: 'piped', + AudioSource.jiosaavn: 'jiosaavn', +}; + +const _$SourceCodecsEnumMap = { + SourceCodecs.m4a: 'm4a', + SourceCodecs.weba: 'weba', +}; + +_$PlaybackHistoryPlaylistImpl _$$PlaybackHistoryPlaylistImplFromJson( + Map json) => + _$PlaybackHistoryPlaylistImpl( + date: DateTime.parse(json['date'] as String), + playlist: PlaylistSimple.fromJson( + Map.from(json['playlist'] as Map)), + $type: json['runtimeType'] as String?, + ); + +Map _$$PlaybackHistoryPlaylistImplToJson( + _$PlaybackHistoryPlaylistImpl instance) => + { + 'date': instance.date.toIso8601String(), + 'playlist': instance.playlist.toJson(), + 'runtimeType': instance.$type, + }; + +_$PlaybackHistoryAlbumImpl _$$PlaybackHistoryAlbumImplFromJson(Map json) => + _$PlaybackHistoryAlbumImpl( + date: DateTime.parse(json['date'] as String), + album: + AlbumSimple.fromJson(Map.from(json['album'] as Map)), + $type: json['runtimeType'] as String?, + ); + +Map _$$PlaybackHistoryAlbumImplToJson( + _$PlaybackHistoryAlbumImpl instance) => + { + 'date': instance.date.toIso8601String(), + 'album': instance.album.toJson(), + 'runtimeType': instance.$type, + }; + +_$PlaybackHistoryTrackImpl _$$PlaybackHistoryTrackImplFromJson(Map json) => + _$PlaybackHistoryTrackImpl( + date: DateTime.parse(json['date'] as String), + track: Track.fromJson(Map.from(json['track'] as Map)), + $type: json['runtimeType'] as String?, + ); + +Map _$$PlaybackHistoryTrackImplToJson( + _$PlaybackHistoryTrackImpl instance) => + { + 'date': instance.date.toIso8601String(), + 'track': instance.track.toJson(), + 'runtimeType': instance.$type, + }; diff --git a/lib/utils/migrations/cache_box.dart b/lib/utils/migrations/cache_box.dart new file mode 100644 index 00000000..dfe1947b --- /dev/null +++ b/lib/utils/migrations/cache_box.dart @@ -0,0 +1,100 @@ +import 'dart:convert'; + +import 'package:hive/hive.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:spotube/provider/spotify/utils/json_cast.dart'; +import 'package:spotube/services/kv_store/encrypted_kv_store.dart'; +import 'package:spotube/utils/platform.dart'; +import 'package:spotube/utils/primitive_utils.dart'; + +const kKeyBoxName = "spotube_box_name"; +const kNoEncryptionWarningShownKey = "showedNoEncryptionWarning"; +const kIsUsingEncryption = "isUsingEncryption"; +String getBoxKey(String boxName) => "spotube_box_$boxName"; + +class PersistenceCacheBox { + static late LazyBox _box; + static late LazyBox _encryptedBox; + + final String cacheKey; + final bool encrypted; + + final T Function(Map) fromJson; + + PersistenceCacheBox( + this.cacheKey, { + required this.fromJson, + this.encrypted = false, + }); + + static Future read(String key) async { + final localStorage = await SharedPreferences.getInstance(); + if (kIsMacOS || kIsIOS || (kIsLinux && !kIsFlatpak)) { + return localStorage.getString(key); + } + + try { + await localStorage.setBool(kIsUsingEncryption, true); + return await EncryptedKvStoreService.storage.read(key: key); + } catch (e) { + await localStorage.setBool(kIsUsingEncryption, false); + return localStorage.getString(key); + } + } + + static Future write(String key, String value) async { + final localStorage = await SharedPreferences.getInstance(); + if (kIsMacOS || kIsIOS || (kIsLinux && !kIsFlatpak)) { + await localStorage.setString(key, value); + return; + } + + try { + await localStorage.setBool(kIsUsingEncryption, true); + await EncryptedKvStoreService.storage.write(key: key, value: value); + } catch (e) { + await localStorage.setBool(kIsUsingEncryption, false); + await localStorage.setString(key, value); + } + } + + static Future initializeBoxes({required String? path}) async { + String? boxName = await read(kKeyBoxName); + + if (boxName == null) { + boxName = "spotube-${PrimitiveUtils.uuid.v4()}"; + await write(kKeyBoxName, boxName); + } + + String? encryptionKey = await read(getBoxKey(boxName)); + + if (encryptionKey == null) { + encryptionKey = base64Url.encode(Hive.generateSecureKey()); + await write(getBoxKey(boxName), encryptionKey); + } + + _encryptedBox = await Hive.openLazyBox( + boxName, + encryptionCipher: HiveAesCipher(base64Url.decode(encryptionKey)), + ); + + _box = await Hive.openLazyBox( + "spotube_cache", + path: path, + ); + } + + LazyBox get box => encrypted ? _encryptedBox : _box; + + Future getData() async { + final json = await box.get(cacheKey); + + if (json != null || + (json is Map && json.entries.isNotEmpty) || + (json is List && json.isNotEmpty)) { + return fromJson(castNestedJson(json)); + } + + return null; + } +} diff --git a/lib/utils/migrations/hive.dart b/lib/utils/migrations/hive.dart new file mode 100644 index 00000000..e43df1d8 --- /dev/null +++ b/lib/utils/migrations/hive.dart @@ -0,0 +1,316 @@ +import 'package:drift/drift.dart'; +import 'package:flutter/foundation.dart'; +import 'package:hive/hive.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:spotube/models/database/database.dart' + hide + SourceType, + AudioSource, + CloseBehavior, + MusicCodec, + LayoutMode, + SearchMode, + BlacklistedType; +import 'package:spotube/models/database/database.dart' as db; +import 'package:spotube/services/kv_store/kv_store.dart'; +import 'package:spotube/services/logger/logger.dart'; +import 'package:spotube/utils/migrations/adapters.dart'; +import 'package:spotube/utils/migrations/cache_box.dart'; + +late AppDatabase _database; + +Future getHiveCacheDir() async => + kIsWeb ? null : (await getApplicationSupportDirectory()).path; + +Future migrateAuthenticationInfo() async { + AppLogger.log.i("🔵 Migrating authentication info.."); + + final box = PersistenceCacheBox( + "authentication", + encrypted: true, + fromJson: (json) => AuthenticationCredentials.fromJson(json), + ); + + final credentials = await box.getData(); + + if (credentials == null) return; + + await _database.into(_database.authenticationTable).insertOnConflictUpdate( + AuthenticationTableCompanion.insert( + accessToken: DecryptedText(credentials.accessToken), + cookie: DecryptedText(credentials.cookie), + expiration: credentials.expiration, + id: const Value(0), + ), + ); + + AppLogger.log.i("✅ Migrated authentication info"); +} + +Future migratePreferences() async { + AppLogger.log.i("🔵 Migrating preferences.."); + final box = PersistenceCacheBox( + "preferences", + fromJson: (json) => UserPreferences.fromJson(json), + ); + + final preferences = await box.getData(); + + if (preferences == null) return; + + await _database.into(_database.preferencesTable).insertOnConflictUpdate( + PreferencesTableCompanion.insert( + id: const Value(0), + accentColorScheme: Value(preferences.accentColorScheme), + albumColorSync: Value(preferences.albumColorSync), + amoledDarkTheme: Value(preferences.amoledDarkTheme), + audioQuality: Value(preferences.audioQuality), + audioSource: Value( + switch (preferences.audioSource) { + AudioSource.youtube => db.AudioSource.youtube, + AudioSource.piped => db.AudioSource.piped, + AudioSource.jiosaavn => db.AudioSource.jiosaavn, + }, + ), + checkUpdate: Value(preferences.checkUpdate), + closeBehavior: Value( + switch (preferences.closeBehavior) { + CloseBehavior.minimizeToTray => db.CloseBehavior.minimizeToTray, + CloseBehavior.close => db.CloseBehavior.close, + }, + ), + discordPresence: Value(preferences.discordPresence), + downloadLocation: Value(preferences.downloadLocation), + downloadMusicCodec: Value(preferences.downloadMusicCodec), + enableConnect: Value(preferences.enableConnect), + endlessPlayback: Value(preferences.endlessPlayback), + layoutMode: Value( + switch (preferences.layoutMode) { + LayoutMode.adaptive => db.LayoutMode.adaptive, + LayoutMode.compact => db.LayoutMode.compact, + LayoutMode.extended => db.LayoutMode.extended, + }, + ), + localLibraryLocation: Value(preferences.localLibraryLocation), + locale: Value(preferences.locale), + market: Value(preferences.recommendationMarket), + normalizeAudio: Value(preferences.normalizeAudio), + pipedInstance: Value(preferences.pipedInstance), + searchMode: Value( + switch (preferences.searchMode) { + SearchMode.youtube => db.SearchMode.youtube, + SearchMode.youtubeMusic => db.SearchMode.youtubeMusic, + }, + ), + showSystemTrayIcon: Value(preferences.showSystemTrayIcon), + skipNonMusic: Value(preferences.skipNonMusic), + streamMusicCodec: Value(preferences.streamMusicCodec), + systemTitleBar: Value(preferences.systemTitleBar), + themeMode: Value(preferences.themeMode), + ), + ); + + AppLogger.log.i("✅ Migrated preferences"); +} + +Future migrateSkipSegment() async { + AppLogger.log.i("🔵 Migrating skip segments.."); + Hive.registerAdapter(SkipSegmentAdapter()); + + final box = await Hive.openLazyBox( + SkipSegment.boxName, + path: await getHiveCacheDir(), + ); + + final skipSegments = await Future.wait( + box.keys.map( + (key) async => ( + id: key as String, + data: await box.get(key), + ), + ), + ); + + await _database.batch((batch) { + batch.insertAll( + _database.skipSegmentTable, + skipSegments + .where((element) => element.data != null) + .expand((element) => (element.data as List).map( + (segment) => SkipSegmentTableCompanion.insert( + trackId: element.id, + start: segment["start"], + end: segment["end"], + ), + )) + .toList(), + ); + }); + + AppLogger.log.i("✅ Migrated skip segments"); +} + +Future migrateSourceMatches() async { + AppLogger.log.i("🔵 Migrating source matches.."); + + Hive.registerAdapter(SourceMatchAdapter()); + Hive.registerAdapter(SourceTypeAdapter()); + + final box = await Hive.openBox( + SourceMatch.boxName, + path: await getHiveCacheDir(), + ); + + final sourceMatches = + box.keys.map((key) => (data: box.get(key), trackId: key)); + + await _database.batch((batch) { + batch.insertAll( + _database.sourceMatchTable, + sourceMatches + .where((element) => element.data != null) + .map( + (sourceMatch) => SourceMatchTableCompanion.insert( + sourceId: sourceMatch.data!.sourceId, + trackId: sourceMatch.trackId, + sourceType: Value( + switch (sourceMatch.data!.sourceType) { + SourceType.jiosaavn => db.SourceType.jiosaavn, + SourceType.youtube => db.SourceType.youtube, + SourceType.youtubeMusic => db.SourceType.youtubeMusic, + }, + ), + ), + ) + .toList(), + ); + }); + + AppLogger.log.i("✅ Migrated source matches"); +} + +Future migrateBlacklist() async { + AppLogger.log.i("🔵 Migrating blacklist.."); + + final box = PersistenceCacheBox>( + "blacklist", + fromJson: (json) => (json["blacklist"] as List) + .map((e) => BlacklistedElement.fromJson(e)) + .toSet(), + ); + + final data = await box.getData(); + + if (data == null) return; + + await _database.batch((batch) { + batch.insertAll( + _database.blacklistTable, + data.map( + (element) => BlacklistTableCompanion.insert( + name: element.name, + elementId: element.id, + elementType: switch (element.type) { + BlacklistedType.artist => db.BlacklistedType.artist, + BlacklistedType.track => db.BlacklistedType.track, + }, + ), + ), + ); + }); + + AppLogger.log.i("✅ Migrated blacklist"); +} + +Future migrateLastFmCredentials() async { + AppLogger.log.i("🔵 Migrating Last.fm credentials.."); + + final box = PersistenceCacheBox( + "scrobbler", + fromJson: (json) => ScrobblerState.fromJson(json), + encrypted: true, + ); + + final data = await box.getData(); + + if (data == null) return; + + await _database.into(_database.scrobblerTable).insertOnConflictUpdate( + ScrobblerTableCompanion.insert( + id: const Value(0), + passwordHash: DecryptedText(data.passwordHash), + username: data.username, + ), + ); + + AppLogger.log.i("✅ Migrated Last.fm credentials"); +} + +Future migratePlaybackHistory() async { + AppLogger.log.i("🔵 Migrating playback history.."); + + final box = PersistenceCacheBox( + "playback_history", + fromJson: (json) => PlaybackHistoryState.fromJson(json), + ); + + final data = await box.getData(); + + if (data == null) return; + + await _database.batch((batch) { + batch.insertAll( + _database.historyTable, + data.items.map( + (item) => switch (item) { + PlaybackHistoryAlbum() => HistoryTableCompanion.insert( + createdAt: Value(item.date), + itemId: item.album.id!, + data: item.album.toJson(), + type: db.HistoryEntryType.album, + ), + PlaybackHistoryPlaylist() => HistoryTableCompanion.insert( + createdAt: Value(item.date), + itemId: item.playlist.id!, + data: item.playlist.toJson(), + type: db.HistoryEntryType.playlist, + ), + PlaybackHistoryTrack() => HistoryTableCompanion.insert( + createdAt: Value(item.date), + itemId: item.track.id!, + data: item.track.toJson(), + type: db.HistoryEntryType.track, + ), + _ => throw Exception("Unknown history item type"), + }, + ), + ); + }); + + AppLogger.log.i("✅ Migrated playback history"); +} + +Future migrateFromHiveToDrift(AppDatabase database) async { + if (KVStoreService.hasMigratedToDrift) return; + + await PersistenceCacheBox.initializeBoxes( + path: await getHiveCacheDir(), + ); + + _database = database; + + await migrateAuthenticationInfo(); + await migratePreferences(); + + await migrateSkipSegment(); + await migrateSourceMatches(); + + await migrateBlacklist(); + await migratePlaybackHistory(); + + await migrateLastFmCredentials(); + + await KVStoreService.setHasMigratedToDrift(true); + + AppLogger.log.i("🚀 Migrated all data to Drift"); +}