mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
chore: add migration script to migrate hive to drift
This commit is contained in:
parent
a3021e4c52
commit
ffb3a3377f
@ -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_deep_linking.dart';
|
||||||
import 'package:spotube/hooks/configurators/use_disable_battery_optimizations.dart';
|
import 'package:spotube/hooks/configurators/use_disable_battery_optimizations.dart';
|
||||||
import 'package:spotube/hooks/configurators/use_get_storage_perms.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/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/bonsoir.dart';
|
||||||
import 'package:spotube/provider/server/server.dart';
|
import 'package:spotube/provider/server/server.dart';
|
||||||
import 'package:spotube/provider/tray_manager/tray_manager.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/logger/logger.dart';
|
||||||
import 'package:spotube/services/wm_tools/wm_tools.dart';
|
import 'package:spotube/services/wm_tools/wm_tools.dart';
|
||||||
import 'package:spotube/themes/theme.dart';
|
import 'package:spotube/themes/theme.dart';
|
||||||
|
import 'package:spotube/utils/migrations/hive.dart';
|
||||||
import 'package:spotube/utils/platform.dart';
|
import 'package:spotube/utils/platform.dart';
|
||||||
import 'package:system_theme/system_theme.dart';
|
import 'package:system_theme/system_theme.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
@ -84,12 +87,23 @@ Future<void> main(List<String> rawArgs) async {
|
|||||||
|
|
||||||
Hive.init(hiveCacheDir);
|
Hive.init(hiveCacheDir);
|
||||||
|
|
||||||
|
final database = AppDatabase();
|
||||||
|
|
||||||
|
await migrateFromHiveToDrift(database);
|
||||||
|
|
||||||
if (kIsDesktop) {
|
if (kIsDesktop) {
|
||||||
await localNotifier.setup(appName: "Spotube");
|
await localNotifier.setup(appName: "Spotube");
|
||||||
await WindowManagerTools.initialize();
|
await WindowManagerTools.initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
runApp(const ProviderScope(child: Spotube()));
|
runApp(
|
||||||
|
ProviderScope(
|
||||||
|
overrides: [
|
||||||
|
databaseProvider.overrideWith((ref) => database),
|
||||||
|
],
|
||||||
|
child: const Spotube(),
|
||||||
|
),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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/albums.dart';
|
||||||
import 'package:spotube/modules/stats/top/artists.dart';
|
import 'package:spotube/modules/stats/top/artists.dart';
|
||||||
import 'package:spotube/modules/stats/top/tracks.dart';
|
import 'package:spotube/modules/stats/top/tracks.dart';
|
||||||
import 'package:spotube/provider/history/state.dart';
|
|
||||||
import 'package:spotube/provider/history/top.dart';
|
import 'package:spotube/provider/history/top.dart';
|
||||||
|
|
||||||
class StatsPageTopSection extends HookConsumerWidget {
|
class StatsPageTopSection extends HookConsumerWidget {
|
||||||
|
@ -3,7 +3,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:spotube/collections/formatters.dart';
|
import 'package:spotube/collections/formatters.dart';
|
||||||
import 'package:spotube/components/titlebar/titlebar.dart';
|
import 'package:spotube/components/titlebar/titlebar.dart';
|
||||||
import 'package:spotube/modules/stats/common/album_item.dart';
|
import 'package:spotube/modules/stats/common/album_item.dart';
|
||||||
import 'package:spotube/provider/history/state.dart';
|
|
||||||
import 'package:spotube/provider/history/top.dart';
|
import 'package:spotube/provider/history/top.dart';
|
||||||
|
|
||||||
class StatsAlbumsPage extends HookConsumerWidget {
|
class StatsAlbumsPage extends HookConsumerWidget {
|
||||||
|
@ -3,7 +3,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:spotube/collections/formatters.dart';
|
import 'package:spotube/collections/formatters.dart';
|
||||||
import 'package:spotube/components/titlebar/titlebar.dart';
|
import 'package:spotube/components/titlebar/titlebar.dart';
|
||||||
import 'package:spotube/modules/stats/common/artist_item.dart';
|
import 'package:spotube/modules/stats/common/artist_item.dart';
|
||||||
import 'package:spotube/provider/history/state.dart';
|
|
||||||
import 'package:spotube/provider/history/top.dart';
|
import 'package:spotube/provider/history/top.dart';
|
||||||
|
|
||||||
class StatsArtistsPage extends HookConsumerWidget {
|
class StatsArtistsPage extends HookConsumerWidget {
|
||||||
|
@ -4,7 +4,7 @@ import 'package:sliver_tools/sliver_tools.dart';
|
|||||||
import 'package:spotube/collections/formatters.dart';
|
import 'package:spotube/collections/formatters.dart';
|
||||||
import 'package:spotube/components/titlebar/titlebar.dart';
|
import 'package:spotube/components/titlebar/titlebar.dart';
|
||||||
import 'package:spotube/modules/stats/common/artist_item.dart';
|
import 'package:spotube/modules/stats/common/artist_item.dart';
|
||||||
import 'package:spotube/provider/history/state.dart';
|
|
||||||
import 'package:spotube/provider/history/top.dart';
|
import 'package:spotube/provider/history/top.dart';
|
||||||
|
|
||||||
class StatsStreamFeesPage extends HookConsumerWidget {
|
class StatsStreamFeesPage extends HookConsumerWidget {
|
||||||
|
@ -4,7 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:spotube/collections/formatters.dart';
|
import 'package:spotube/collections/formatters.dart';
|
||||||
import 'package:spotube/components/titlebar/titlebar.dart';
|
import 'package:spotube/components/titlebar/titlebar.dart';
|
||||||
import 'package:spotube/modules/stats/common/track_item.dart';
|
import 'package:spotube/modules/stats/common/track_item.dart';
|
||||||
import 'package:spotube/provider/history/state.dart';
|
|
||||||
import 'package:spotube/provider/history/top.dart';
|
import 'package:spotube/provider/history/top.dart';
|
||||||
|
|
||||||
class StatsMinutesPage extends HookConsumerWidget {
|
class StatsMinutesPage extends HookConsumerWidget {
|
||||||
|
@ -3,7 +3,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:spotube/collections/formatters.dart';
|
import 'package:spotube/collections/formatters.dart';
|
||||||
import 'package:spotube/components/titlebar/titlebar.dart';
|
import 'package:spotube/components/titlebar/titlebar.dart';
|
||||||
import 'package:spotube/modules/stats/common/playlist_item.dart';
|
import 'package:spotube/modules/stats/common/playlist_item.dart';
|
||||||
import 'package:spotube/provider/history/state.dart';
|
|
||||||
import 'package:spotube/provider/history/top.dart';
|
import 'package:spotube/provider/history/top.dart';
|
||||||
|
|
||||||
class StatsPlaylistsPage extends HookConsumerWidget {
|
class StatsPlaylistsPage extends HookConsumerWidget {
|
||||||
|
@ -4,7 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:spotube/collections/formatters.dart';
|
import 'package:spotube/collections/formatters.dart';
|
||||||
import 'package:spotube/components/titlebar/titlebar.dart';
|
import 'package:spotube/components/titlebar/titlebar.dart';
|
||||||
import 'package:spotube/modules/stats/common/track_item.dart';
|
import 'package:spotube/modules/stats/common/track_item.dart';
|
||||||
import 'package:spotube/provider/history/state.dart';
|
|
||||||
import 'package:spotube/provider/history/top.dart';
|
import 'package:spotube/provider/history/top.dart';
|
||||||
|
|
||||||
class StatsStreamsPage extends HookConsumerWidget {
|
class StatsStreamsPage extends HookConsumerWidget {
|
||||||
|
@ -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<String, dynamic> json) =>
|
|
||||||
_$PlaybackHistoryItemFromJson(json);
|
|
||||||
}
|
|
@ -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>(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<String, dynamic> 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<TResult extends Object?>({
|
|
||||||
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 extends Object?>({
|
|
||||||
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 extends Object?>({
|
|
||||||
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<TResult extends Object?>({
|
|
||||||
required TResult Function(PlaybackHistoryPlaylist value) playlist,
|
|
||||||
required TResult Function(PlaybackHistoryAlbum value) album,
|
|
||||||
required TResult Function(PlaybackHistoryTrack value) track,
|
|
||||||
}) =>
|
|
||||||
throw _privateConstructorUsedError;
|
|
||||||
@optionalTypeArgs
|
|
||||||
TResult? mapOrNull<TResult extends Object?>({
|
|
||||||
TResult? Function(PlaybackHistoryPlaylist value)? playlist,
|
|
||||||
TResult? Function(PlaybackHistoryAlbum value)? album,
|
|
||||||
TResult? Function(PlaybackHistoryTrack value)? track,
|
|
||||||
}) =>
|
|
||||||
throw _privateConstructorUsedError;
|
|
||||||
@optionalTypeArgs
|
|
||||||
TResult maybeMap<TResult extends Object?>({
|
|
||||||
TResult Function(PlaybackHistoryPlaylist value)? playlist,
|
|
||||||
TResult Function(PlaybackHistoryAlbum value)? album,
|
|
||||||
TResult Function(PlaybackHistoryTrack value)? track,
|
|
||||||
required TResult orElse(),
|
|
||||||
}) =>
|
|
||||||
throw _privateConstructorUsedError;
|
|
||||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
|
||||||
@JsonKey(ignore: true)
|
|
||||||
$PlaybackHistoryItemCopyWith<PlaybackHistoryItem> 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<String, dynamic> 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<TResult extends Object?>({
|
|
||||||
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 extends Object?>({
|
|
||||||
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 extends Object?>({
|
|
||||||
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<TResult extends Object?>({
|
|
||||||
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 extends Object?>({
|
|
||||||
TResult? Function(PlaybackHistoryPlaylist value)? playlist,
|
|
||||||
TResult? Function(PlaybackHistoryAlbum value)? album,
|
|
||||||
TResult? Function(PlaybackHistoryTrack value)? track,
|
|
||||||
}) {
|
|
||||||
return playlist?.call(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
@optionalTypeArgs
|
|
||||||
TResult maybeMap<TResult extends Object?>({
|
|
||||||
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<String, dynamic> toJson() {
|
|
||||||
return _$$PlaybackHistoryPlaylistImplToJson(
|
|
||||||
this,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class PlaybackHistoryPlaylist implements PlaybackHistoryItem {
|
|
||||||
factory PlaybackHistoryPlaylist(
|
|
||||||
{required final DateTime date,
|
|
||||||
required final PlaylistSimple playlist}) = _$PlaybackHistoryPlaylistImpl;
|
|
||||||
|
|
||||||
factory PlaybackHistoryPlaylist.fromJson(Map<String, dynamic> 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<String, dynamic> 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<TResult extends Object?>({
|
|
||||||
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 extends Object?>({
|
|
||||||
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 extends Object?>({
|
|
||||||
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<TResult extends Object?>({
|
|
||||||
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 extends Object?>({
|
|
||||||
TResult? Function(PlaybackHistoryPlaylist value)? playlist,
|
|
||||||
TResult? Function(PlaybackHistoryAlbum value)? album,
|
|
||||||
TResult? Function(PlaybackHistoryTrack value)? track,
|
|
||||||
}) {
|
|
||||||
return album?.call(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
@optionalTypeArgs
|
|
||||||
TResult maybeMap<TResult extends Object?>({
|
|
||||||
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<String, dynamic> toJson() {
|
|
||||||
return _$$PlaybackHistoryAlbumImplToJson(
|
|
||||||
this,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class PlaybackHistoryAlbum implements PlaybackHistoryItem {
|
|
||||||
factory PlaybackHistoryAlbum(
|
|
||||||
{required final DateTime date,
|
|
||||||
required final AlbumSimple album}) = _$PlaybackHistoryAlbumImpl;
|
|
||||||
|
|
||||||
factory PlaybackHistoryAlbum.fromJson(Map<String, dynamic> 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<String, dynamic> 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<TResult extends Object?>({
|
|
||||||
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 extends Object?>({
|
|
||||||
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 extends Object?>({
|
|
||||||
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<TResult extends Object?>({
|
|
||||||
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 extends Object?>({
|
|
||||||
TResult? Function(PlaybackHistoryPlaylist value)? playlist,
|
|
||||||
TResult? Function(PlaybackHistoryAlbum value)? album,
|
|
||||||
TResult? Function(PlaybackHistoryTrack value)? track,
|
|
||||||
}) {
|
|
||||||
return track?.call(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
@optionalTypeArgs
|
|
||||||
TResult maybeMap<TResult extends Object?>({
|
|
||||||
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<String, dynamic> toJson() {
|
|
||||||
return _$$PlaybackHistoryTrackImplToJson(
|
|
||||||
this,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class PlaybackHistoryTrack implements PlaybackHistoryItem {
|
|
||||||
factory PlaybackHistoryTrack(
|
|
||||||
{required final DateTime date,
|
|
||||||
required final Track track}) = _$PlaybackHistoryTrackImpl;
|
|
||||||
|
|
||||||
factory PlaybackHistoryTrack.fromJson(Map<String, dynamic> json) =
|
|
||||||
_$PlaybackHistoryTrackImpl.fromJson;
|
|
||||||
|
|
||||||
@override
|
|
||||||
DateTime get date;
|
|
||||||
Track get track;
|
|
||||||
@override
|
|
||||||
@JsonKey(ignore: true)
|
|
||||||
_$$PlaybackHistoryTrackImplCopyWith<_$PlaybackHistoryTrackImpl>
|
|
||||||
get copyWith => throw _privateConstructorUsedError;
|
|
||||||
}
|
|
@ -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<String, dynamic>.from(json['playlist'] as Map)),
|
|
||||||
$type: json['runtimeType'] as String?,
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$$PlaybackHistoryPlaylistImplToJson(
|
|
||||||
_$PlaybackHistoryPlaylistImpl instance) =>
|
|
||||||
<String, dynamic>{
|
|
||||||
'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<String, dynamic>.from(json['album'] as Map)),
|
|
||||||
$type: json['runtimeType'] as String?,
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$$PlaybackHistoryAlbumImplToJson(
|
|
||||||
_$PlaybackHistoryAlbumImpl instance) =>
|
|
||||||
<String, dynamic>{
|
|
||||||
'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<String, dynamic>.from(json['track'] as Map)),
|
|
||||||
$type: json['runtimeType'] as String?,
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$$PlaybackHistoryTrackImplToJson(
|
|
||||||
_$PlaybackHistoryTrackImpl instance) =>
|
|
||||||
<String, dynamic>{
|
|
||||||
'date': instance.date.toIso8601String(),
|
|
||||||
'track': instance.track.toJson(),
|
|
||||||
'runtimeType': instance.$type,
|
|
||||||
};
|
|
@ -6,7 +6,15 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/models/database/database.dart';
|
import 'package:spotube/models/database/database.dart';
|
||||||
import 'package:spotube/provider/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 =
|
final playbackHistoryTopDurationProvider =
|
||||||
StateProvider((ref) => HistoryDuration.days30);
|
StateProvider((ref) => HistoryDuration.days30);
|
||||||
|
@ -10,6 +10,8 @@ abstract class EncryptedKvStoreService {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
static FlutterSecureStorage get storage => _storage;
|
||||||
|
|
||||||
static String? _encryptionKeySync;
|
static String? _encryptionKeySync;
|
||||||
|
|
||||||
static Future<void> initialize() async {
|
static Future<void> initialize() async {
|
||||||
|
@ -82,4 +82,9 @@ abstract class KVStoreService {
|
|||||||
static double get volume => sharedPreferences.getDouble('volume') ?? 1.0;
|
static double get volume => sharedPreferences.getDouble('volume') ?? 1.0;
|
||||||
static Future<void> setVolume(double value) async =>
|
static Future<void> setVolume(double value) async =>
|
||||||
await sharedPreferences.setDouble('volume', value);
|
await sharedPreferences.setDouble('volume', value);
|
||||||
|
|
||||||
|
static bool get hasMigratedToDrift =>
|
||||||
|
sharedPreferences.getBool('hasMigratedToDrift') ?? false;
|
||||||
|
static Future<void> setHasMigratedToDrift(bool value) async =>
|
||||||
|
await sharedPreferences.setBool('hasMigratedToDrift', value);
|
||||||
}
|
}
|
||||||
|
320
lib/utils/migrations/adapters.dart
Normal file
320
lib/utils/migrations/adapters.dart
Normal file
@ -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<String, dynamic> json)
|
||||||
|
: start = json['start'],
|
||||||
|
end = json['end'];
|
||||||
|
|
||||||
|
Map<String, dynamic> 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<String, dynamic> json) =>
|
||||||
|
_$SourceMatchFromJson(json);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => _$SourceMatchToJson(this);
|
||||||
|
|
||||||
|
static String version = 'v1';
|
||||||
|
static final boxName = "oss.krtirtho.spotube.source_matches.$version";
|
||||||
|
|
||||||
|
static LazyBox<SourceMatch> get box => Hive.lazyBox<SourceMatch>(boxName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class AuthenticationCredentials {
|
||||||
|
String cookie;
|
||||||
|
String accessToken;
|
||||||
|
DateTime expiration;
|
||||||
|
|
||||||
|
AuthenticationCredentials({
|
||||||
|
required this.cookie,
|
||||||
|
required this.accessToken,
|
||||||
|
required this.expiration,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory AuthenticationCredentials.fromJson(Map<String, dynamic> json) {
|
||||||
|
return AuthenticationCredentials(
|
||||||
|
cookie: json['cookie'] as String,
|
||||||
|
accessToken: json['accessToken'] as String,
|
||||||
|
expiration: DateTime.parse(json['expiration'] as String),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> 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<String> 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<String, dynamic> json) =>
|
||||||
|
_$UserPreferencesFromJson(json);
|
||||||
|
|
||||||
|
factory UserPreferences.withDefaults() => UserPreferences.fromJson({});
|
||||||
|
|
||||||
|
static SpotubeColor _accentColorSchemeFromJson(Map<String, dynamic> json) {
|
||||||
|
return SpotubeColor.fromString(json["color"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, dynamic>? _accentColorSchemeReadValue(
|
||||||
|
Map<dynamic, dynamic> json, String key) {
|
||||||
|
if (json[key] is String) {
|
||||||
|
return {"color": json[key]};
|
||||||
|
}
|
||||||
|
|
||||||
|
return json[key] as Map<String, dynamic>?;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, dynamic> _accentColorSchemeToJson(SpotubeColor color) {
|
||||||
|
return {"color": color.toString()};
|
||||||
|
}
|
||||||
|
|
||||||
|
static Locale _localeFromJson(Map<String, dynamic> json) {
|
||||||
|
return Locale(json["languageCode"], json["countryCode"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, dynamic> _localeToJson(Locale locale) {
|
||||||
|
return {
|
||||||
|
"languageCode": locale.languageCode,
|
||||||
|
"countryCode": locale.countryCode,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, dynamic>? _localeReadValue(
|
||||||
|
Map<dynamic, dynamic> 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<String, dynamic>?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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<String, dynamic> json)
|
||||||
|
: id = json['id'],
|
||||||
|
name = json['name'],
|
||||||
|
type = BlacklistedType.fromName(json['type']);
|
||||||
|
|
||||||
|
Map<String, dynamic> 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<String, dynamic> json) =>
|
||||||
|
_$PlaybackHistoryItemFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
class PlaybackHistoryState {
|
||||||
|
final List<PlaybackHistoryItem> items;
|
||||||
|
const PlaybackHistoryState({this.items = const []});
|
||||||
|
|
||||||
|
factory PlaybackHistoryState.fromJson(Map<String, dynamic> json) {
|
||||||
|
return PlaybackHistoryState(
|
||||||
|
items: json["items"]
|
||||||
|
?.map(
|
||||||
|
(json) => PlaybackHistoryItem.fromJson(json),
|
||||||
|
)
|
||||||
|
.toList()
|
||||||
|
.cast<PlaybackHistoryItem>() ??
|
||||||
|
<PlaybackHistoryItem>[],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScrobblerState {
|
||||||
|
final String username;
|
||||||
|
final String passwordHash;
|
||||||
|
|
||||||
|
ScrobblerState({
|
||||||
|
required this.username,
|
||||||
|
required this.passwordHash,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory ScrobblerState.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ScrobblerState(
|
||||||
|
username: json["username"],
|
||||||
|
passwordHash: json["passwordHash"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
1380
lib/utils/migrations/adapters.freezed.dart
Normal file
1380
lib/utils/migrations/adapters.freezed.dart
Normal file
File diff suppressed because it is too large
Load Diff
600
lib/utils/migrations/adapters.g.dart
Normal file
600
lib/utils/migrations/adapters.g.dart
Normal file
@ -0,0 +1,600 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'adapters.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// TypeAdapterGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
class SkipSegmentAdapter extends TypeAdapter<SkipSegment> {
|
||||||
|
@override
|
||||||
|
final int typeId = 2;
|
||||||
|
|
||||||
|
@override
|
||||||
|
SkipSegment read(BinaryReader reader) {
|
||||||
|
final numOfFields = reader.readByte();
|
||||||
|
final fields = <int, dynamic>{
|
||||||
|
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<SourceMatch> {
|
||||||
|
@override
|
||||||
|
final int typeId = 6;
|
||||||
|
|
||||||
|
@override
|
||||||
|
SourceMatch read(BinaryReader reader) {
|
||||||
|
final numOfFields = reader.readByte();
|
||||||
|
final fields = <int, dynamic>{
|
||||||
|
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<SourceType> {
|
||||||
|
@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<String, dynamic> _$SourceMatchToJson(SourceMatch instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'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<String, dynamic> _$AuthenticationCredentialsToJson(
|
||||||
|
AuthenticationCredentials instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'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<String, dynamic>),
|
||||||
|
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<String, dynamic>),
|
||||||
|
recommendationMarket:
|
||||||
|
$enumDecodeNullable(_$MarketEnumMap, json['recommendationMarket']) ??
|
||||||
|
Market.US,
|
||||||
|
searchMode:
|
||||||
|
$enumDecodeNullable(_$SearchModeEnumMap, json['searchMode']) ??
|
||||||
|
SearchMode.youtube,
|
||||||
|
downloadLocation: json['downloadLocation'] as String? ?? "",
|
||||||
|
localLibraryLocation: (json['localLibraryLocation'] as List<dynamic>?)
|
||||||
|
?.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<String, dynamic> _$$UserPreferencesImplToJson(
|
||||||
|
_$UserPreferencesImpl instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'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<String, dynamic>.from(json['playlist'] as Map)),
|
||||||
|
$type: json['runtimeType'] as String?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$PlaybackHistoryPlaylistImplToJson(
|
||||||
|
_$PlaybackHistoryPlaylistImpl instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'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<String, dynamic>.from(json['album'] as Map)),
|
||||||
|
$type: json['runtimeType'] as String?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$PlaybackHistoryAlbumImplToJson(
|
||||||
|
_$PlaybackHistoryAlbumImpl instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'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<String, dynamic>.from(json['track'] as Map)),
|
||||||
|
$type: json['runtimeType'] as String?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$PlaybackHistoryTrackImplToJson(
|
||||||
|
_$PlaybackHistoryTrackImpl instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'date': instance.date.toIso8601String(),
|
||||||
|
'track': instance.track.toJson(),
|
||||||
|
'runtimeType': instance.$type,
|
||||||
|
};
|
100
lib/utils/migrations/cache_box.dart
Normal file
100
lib/utils/migrations/cache_box.dart
Normal file
@ -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<T> {
|
||||||
|
static late LazyBox _box;
|
||||||
|
static late LazyBox _encryptedBox;
|
||||||
|
|
||||||
|
final String cacheKey;
|
||||||
|
final bool encrypted;
|
||||||
|
|
||||||
|
final T Function(Map<String, dynamic>) fromJson;
|
||||||
|
|
||||||
|
PersistenceCacheBox(
|
||||||
|
this.cacheKey, {
|
||||||
|
required this.fromJson,
|
||||||
|
this.encrypted = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
static Future<String?> 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<void> 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<void> 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<T?> 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;
|
||||||
|
}
|
||||||
|
}
|
316
lib/utils/migrations/hive.dart
Normal file
316
lib/utils/migrations/hive.dart
Normal file
@ -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<String?> getHiveCacheDir() async =>
|
||||||
|
kIsWeb ? null : (await getApplicationSupportDirectory()).path;
|
||||||
|
|
||||||
|
Future<void> migrateAuthenticationInfo() async {
|
||||||
|
AppLogger.log.i("🔵 Migrating authentication info..");
|
||||||
|
|
||||||
|
final box = PersistenceCacheBox<AuthenticationCredentials>(
|
||||||
|
"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<void> migratePreferences() async {
|
||||||
|
AppLogger.log.i("🔵 Migrating preferences..");
|
||||||
|
final box = PersistenceCacheBox<UserPreferences>(
|
||||||
|
"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<void> 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<void> migrateSourceMatches() async {
|
||||||
|
AppLogger.log.i("🔵 Migrating source matches..");
|
||||||
|
|
||||||
|
Hive.registerAdapter(SourceMatchAdapter());
|
||||||
|
Hive.registerAdapter(SourceTypeAdapter());
|
||||||
|
|
||||||
|
final box = await Hive.openBox<SourceMatch>(
|
||||||
|
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<void> migrateBlacklist() async {
|
||||||
|
AppLogger.log.i("🔵 Migrating blacklist..");
|
||||||
|
|
||||||
|
final box = PersistenceCacheBox<Set<BlacklistedElement>>(
|
||||||
|
"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<void> migrateLastFmCredentials() async {
|
||||||
|
AppLogger.log.i("🔵 Migrating Last.fm credentials..");
|
||||||
|
|
||||||
|
final box = PersistenceCacheBox<ScrobblerState>(
|
||||||
|
"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<void> migratePlaybackHistory() async {
|
||||||
|
AppLogger.log.i("🔵 Migrating playback history..");
|
||||||
|
|
||||||
|
final box = PersistenceCacheBox<PlaybackHistoryState>(
|
||||||
|
"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<void> 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");
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user