diff --git a/analysis_options.yaml b/analysis_options.yaml index 0dd46902..15283b7e 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -4,7 +4,6 @@ analyzer: exclude: - 'lib/generated/**' - '**.g.dart' - - 'test/**' errors: body_might_complete_normally_nullable: false unused_element: ignore diff --git a/build.yaml b/build.yaml index b39aaf09..86a14a7a 100644 --- a/build.yaml +++ b/build.yaml @@ -13,3 +13,6 @@ targets: generic_argument_factories: false ignore_unannotated: false include_if_null: true + drift_dev: + options: + store_date_time_values_as_text: true diff --git a/lib/db/app_database.dart b/lib/db/app_database.dart new file mode 100644 index 00000000..132d2d9c --- /dev/null +++ b/lib/db/app_database.dart @@ -0,0 +1,33 @@ +import 'dart:io'; + +import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; +import 'package:path/path.dart' as p; + +import '../repository/app_dir.dart'; +import 'dao/key_value_dao.dart'; +import 'enum/key_value_group.dart'; + +part 'app_database.g.dart'; + +@DriftDatabase( + include: {'drift/app.drift'}, + daos: [ + KeyValueDao, + ], +) +class AppDatabase extends _$AppDatabase { + AppDatabase(super.e); + + factory AppDatabase.connect() { + return AppDatabase(LazyDatabase(_openDatabase)); + } + + @override + int get schemaVersion => 1; +} + +Future _openDatabase() async { + final dbFilePath = p.join(appDir.path, 'app.db'); + return NativeDatabase.createInBackground(File(dbFilePath)); +} diff --git a/lib/db/app_database.g.dart b/lib/db/app_database.g.dart new file mode 100644 index 00000000..7bde5ea4 --- /dev/null +++ b/lib/db/app_database.g.dart @@ -0,0 +1,249 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'app_database.dart'; + +// ignore_for_file: type=lint +class KeyValues extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + KeyValues(this.attachedDatabase, [this._alias]); + static const VerificationMeta _keyMeta = const VerificationMeta('key'); + late final GeneratedColumn key = GeneratedColumn( + 'key', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + static const VerificationMeta _groupMeta = const VerificationMeta('group'); + late final GeneratedColumnWithTypeConverter group = + GeneratedColumn('group', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL') + .withConverter(KeyValues.$convertergroup); + static const VerificationMeta _valueMeta = const VerificationMeta('value'); + late final GeneratedColumn value = GeneratedColumn( + 'value', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + @override + List get $columns => [key, group, value]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'key_values'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('key')) { + context.handle( + _keyMeta, key.isAcceptableOrUnknown(data['key']!, _keyMeta)); + } else if (isInserting) { + context.missing(_keyMeta); + } + context.handle(_groupMeta, const VerificationResult.success()); + if (data.containsKey('value')) { + context.handle( + _valueMeta, value.isAcceptableOrUnknown(data['value']!, _valueMeta)); + } else if (isInserting) { + context.missing(_valueMeta); + } + return context; + } + + @override + Set get $primaryKey => {key, group}; + @override + KeyValue map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return KeyValue( + key: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}key'])!, + group: KeyValues.$convertergroup.fromSql(attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}group'])!), + value: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}value'])!, + ); + } + + @override + KeyValues createAlias(String alias) { + return KeyValues(attachedDatabase, alias); + } + + static JsonTypeConverter2 $convertergroup = + const EnumNameConverter(KeyValueGroup.values); + @override + List get customConstraints => const ['PRIMARY KEY("key", "group")']; + @override + bool get dontWriteConstraints => true; +} + +class KeyValue extends DataClass implements Insertable { + final String key; + final KeyValueGroup group; + final String value; + const KeyValue({required this.key, required this.group, required this.value}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['key'] = Variable(key); + { + final converter = KeyValues.$convertergroup; + map['group'] = Variable(converter.toSql(group)); + } + map['value'] = Variable(value); + return map; + } + + KeyValuesCompanion toCompanion(bool nullToAbsent) { + return KeyValuesCompanion( + key: Value(key), + group: Value(group), + value: Value(value), + ); + } + + factory KeyValue.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return KeyValue( + key: serializer.fromJson(json['key']), + group: KeyValues.$convertergroup + .fromJson(serializer.fromJson(json['group'])), + value: serializer.fromJson(json['value']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'key': serializer.toJson(key), + 'group': + serializer.toJson(KeyValues.$convertergroup.toJson(group)), + 'value': serializer.toJson(value), + }; + } + + KeyValue copyWith({String? key, KeyValueGroup? group, String? value}) => + KeyValue( + key: key ?? this.key, + group: group ?? this.group, + value: value ?? this.value, + ); + @override + String toString() { + return (StringBuffer('KeyValue(') + ..write('key: $key, ') + ..write('group: $group, ') + ..write('value: $value') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(key, group, value); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is KeyValue && + other.key == this.key && + other.group == this.group && + other.value == this.value); +} + +class KeyValuesCompanion extends UpdateCompanion { + final Value key; + final Value group; + final Value value; + final Value rowid; + const KeyValuesCompanion({ + this.key = const Value.absent(), + this.group = const Value.absent(), + this.value = const Value.absent(), + this.rowid = const Value.absent(), + }); + KeyValuesCompanion.insert({ + required String key, + required KeyValueGroup group, + required String value, + this.rowid = const Value.absent(), + }) : key = Value(key), + group = Value(group), + value = Value(value); + static Insertable custom({ + Expression? key, + Expression? group, + Expression? value, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (key != null) 'key': key, + if (group != null) 'group': group, + if (value != null) 'value': value, + if (rowid != null) 'rowid': rowid, + }); + } + + KeyValuesCompanion copyWith( + {Value? key, + Value? group, + Value? value, + Value? rowid}) { + return KeyValuesCompanion( + key: key ?? this.key, + group: group ?? this.group, + value: value ?? this.value, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (key.present) { + map['key'] = Variable(key.value); + } + if (group.present) { + final converter = KeyValues.$convertergroup; + + map['group'] = Variable(converter.toSql(group.value)); + } + if (value.present) { + map['value'] = Variable(value.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('KeyValuesCompanion(') + ..write('key: $key, ') + ..write('group: $group, ') + ..write('value: $value, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +abstract class _$AppDatabase extends GeneratedDatabase { + _$AppDatabase(QueryExecutor e) : super(e); + late final KeyValues keyValues = KeyValues(this); + late final KeyValueDao keyValueDao = KeyValueDao(this as AppDatabase); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [keyValues]; + @override + DriftDatabaseOptions get options => + const DriftDatabaseOptions(storeDateTimeAsText: true); +} diff --git a/lib/db/dao/key_value_dao.dart b/lib/db/dao/key_value_dao.dart new file mode 100644 index 00000000..3fac7fee --- /dev/null +++ b/lib/db/dao/key_value_dao.dart @@ -0,0 +1,73 @@ +import 'package:drift/drift.dart'; + +import '../app_database.dart'; +import '../enum/key_value_group.dart'; + +part 'key_value_dao.g.dart'; + +@DriftAccessor( + include: {'../drift/app.drift'}, +) +class KeyValueDao extends DatabaseAccessor + with _$KeyValueDaoMixin { + KeyValueDao(super.attachedDatabase); + + Future getByKey(KeyValueGroup group, String key) { + return (select(keyValues) + ..where((tbl) => tbl.group.equalsValue(group) & tbl.key.equals(key))) + .getSingleOrNull() + .then((value) => value?.value); + } + + Future> getAll(KeyValueGroup group) { + return (select(keyValues)..where((tbl) => tbl.group.equalsValue(group))) + .get() + .then( + (result) => + Map.fromEntries(result.map((e) => MapEntry(e.key, e.value))), + ); + } + + Future set(KeyValueGroup group, String key, String? value) { + if (value != null) { + return into(keyValues).insertOnConflictUpdate( + KeyValuesCompanion.insert( + group: group, + key: key, + value: value, + ), + ); + } else { + return (delete(keyValues) + ..where( + (tbl) => tbl.group.equalsValue(group) & tbl.key.equals(key), + )) + .go(); + } + } + + Future clear(KeyValueGroup group) { + return (delete(keyValues)..where((tbl) => tbl.group.equalsValue(group))) + .go(); + } + + Stream> watchAll(KeyValueGroup group) { + return (select(keyValues)..where((tbl) => tbl.group.equalsValue(group))) + .watch() + .map( + (event) => + Map.fromEntries(event.map((e) => MapEntry(e.key, e.value))), + ); + } + + Stream watchByKey(KeyValueGroup group, String key) { + return (select(keyValues) + ..where((tbl) => tbl.group.equalsValue(group) & tbl.key.equals(key))) + .watchSingleOrNull() + .map((value) => value?.value); + } + + Stream watchTableHasChanged(KeyValueGroup group) { + return db.tableUpdates(TableUpdateQuery.onTable(keyValues)); + } +} diff --git a/lib/db/dao/key_value_dao.g.dart b/lib/db/dao/key_value_dao.g.dart new file mode 100644 index 00000000..471ec49d --- /dev/null +++ b/lib/db/dao/key_value_dao.g.dart @@ -0,0 +1,8 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'key_value_dao.dart'; + +// ignore_for_file: type=lint +mixin _$KeyValueDaoMixin on DatabaseAccessor { + KeyValues get keyValues => attachedDatabase.keyValues; +} diff --git a/lib/db/drift/app.drift b/lib/db/drift/app.drift new file mode 100644 index 00000000..684d2f49 --- /dev/null +++ b/lib/db/drift/app.drift @@ -0,0 +1,9 @@ +import '../enum/key_value_group.dart'; +import '../enum/track_type.dart'; + +CREATE TABLE key_values ( + "key" TEXT NOT NULL, + "group" ENUMNAME(KeyValueGroup) NOT NULL, + "value" TEXT NOT NULL, + PRIMARY KEY("key", "group") +); diff --git a/lib/db/enum/key_value_group.dart b/lib/db/enum/key_value_group.dart new file mode 100644 index 00000000..b1fc49a0 --- /dev/null +++ b/lib/db/enum/key_value_group.dart @@ -0,0 +1,11 @@ +enum KeyValueGroup { + setting, + lyric, + auth, + window, + player, + search, + user, + simpleLazy, + playlistDetail, +} diff --git a/lib/db/enum/track_type.dart b/lib/db/enum/track_type.dart new file mode 100644 index 00000000..c4013aca --- /dev/null +++ b/lib/db/enum/track_type.dart @@ -0,0 +1,7 @@ +enum TrackType { + free, + payAlbum, + vip, + cloud, + noCopyright, +} diff --git a/lib/generated_plugin_registrant.dart b/lib/generated_plugin_registrant.dart deleted file mode 100644 index 757249cc..00000000 --- a/lib/generated_plugin_registrant.dart +++ /dev/null @@ -1,21 +0,0 @@ -// -// Generated file. Do not edit. -// - -// ignore_for_file: directives_ordering -// ignore_for_file: lines_longer_than_80_chars -// ignore_for_file: depend_on_referenced_packages - -import 'package:desktop_drop/desktop_drop_web.dart'; -import 'package:shared_preferences_web/shared_preferences_web.dart'; -import 'package:url_launcher_web/url_launcher_web.dart'; - -import 'package:flutter_web_plugins/flutter_web_plugins.dart'; - -// ignore: public_member_api_docs -void registerPlugins(Registrar registrar) { - DesktopDropWeb.registerWith(registrar); - SharedPreferencesPlugin.registerWith(registrar); - UrlLauncherPlugin.registerWith(registrar); - registrar.registerMessageHandler(); -} diff --git a/lib/main.dart b/lib/main.dart index ee63c09a..65774606 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,24 +3,22 @@ import 'dart:io'; import 'dart:ui'; import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:hive_flutter/adapters.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:mixin_logger/mixin_logger.dart'; import 'package:overlay_support/overlay_support.dart'; import 'package:path/path.dart' as p; -import 'package:shared_preferences/shared_preferences.dart'; import 'package:window_manager/window_manager.dart'; import 'media/tracks/tracks_player_impl_mobile.dart'; import 'navigation/app.dart'; import 'navigation/common/page_splash.dart'; -import 'providers/preference_provider.dart'; +import 'providers/key_value/window_key_value_provider.dart'; import 'providers/repository_provider.dart'; import 'repository.dart'; import 'repository/app_dir.dart'; import 'utils/cache/cached_image.dart'; import 'utils/callback_window_listener.dart'; -import 'utils/hive/duration_adapter.dart'; import 'utils/platform_configuration.dart'; import 'utils/system/system_fonts.dart'; @@ -29,11 +27,8 @@ void main() async { await loadFallbackFonts(); await NetworkRepository.initialize(); await initAppDir(); - final preferences = await SharedPreferences.getInstance(); - unawaited(_initialDesktop(preferences)); initLogger(p.join(appDir.path, 'logs')); registerImageCacheProvider(); - await _initHive(); FlutterError.onError = (details) => e('flutter error: $details'); PlatformDispatcher.instance.onError = (error, stacktrace) { e('uncaught error: $error $stacktrace'); @@ -42,37 +37,41 @@ void main() async { runApp( ProviderScope( overrides: [ - sharedPreferenceProvider.overrideWithValue(preferences), neteaseRepositoryProvider.overrideWithValue(neteaseRepository!), ], - child: PageSplash( - futures: const [], - builder: (BuildContext context, List data) { - return const MyApp(); - }, + child: _WindowInitializationWidget( + child: PageSplash( + futures: const [], + builder: (BuildContext context, List data) { + return const MyApp(); + }, + ), ), ), ); } -Future _initHive() async { - await Hive.initFlutter(p.join(appDir.path, 'hive')); - Hive.registerAdapter(PlaylistDetailAdapter()); - Hive.registerAdapter(TrackTypeAdapter()); - Hive.registerAdapter(TrackAdapter()); - Hive.registerAdapter(ArtistMiniAdapter()); - Hive.registerAdapter(AlbumMiniAdapter()); - Hive.registerAdapter(DurationAdapter()); - Hive.registerAdapter(UserAdapter()); +class _WindowInitializationWidget extends HookConsumerWidget { + const _WindowInitializationWidget({super.key, required this.child}); + + final Widget child; + + @override + Widget build(BuildContext context, WidgetRef ref) { + useMemoized(() async { + await _initialDesktop(ref.read(windowKeyValueProvider)); + }); + return child; + } } -Future _initialDesktop(SharedPreferences preferences) async { +Future _initialDesktop(WindowKeyValue keyValue) async { if (!(Platform.isMacOS || Platform.isLinux || Platform.isWindows)) { return; } await WindowManager.instance.ensureInitialized(); if (Platform.isWindows) { - final size = preferences.getWindowSize(); + final size = await keyValue.getWindowSize(); final windowOptions = WindowOptions( size: size ?? const Size(1080, 720), minimumSize: windowMinSize, @@ -85,7 +84,7 @@ Future _initialDesktop(SharedPreferences preferences) async { await windowManager.focus(); }); } else if (Platform.isLinux) { - final size = preferences.getWindowSize(); + final size = await keyValue.getWindowSize(); await windowManager.setSize(size ?? const Size(1080, 720)); await windowManager.center(); } @@ -95,7 +94,7 @@ Future _initialDesktop(SharedPreferences preferences) async { CallbackWindowListener( onWindowResizeCallback: () async { final size = await windowManager.getSize(); - await preferences.setWindowSize(size); + await keyValue.setWindowSize(size); }, ), ); diff --git a/lib/model/netease_user.dart b/lib/model/netease_user.dart new file mode 100644 index 00000000..a01dac84 --- /dev/null +++ b/lib/model/netease_user.dart @@ -0,0 +1,25 @@ +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; + +import '../repository.dart'; + +part 'netease_user.g.dart'; + +@JsonSerializable() +class NeteaseUser with EquatableMixin { + NeteaseUser({ + required this.user, + required this.loginByQrCode, + }); + + factory NeteaseUser.fromJson(Map json) => + _$NeteaseUserFromJson(json); + + final User user; + final bool loginByQrCode; + + @override + List get props => [user, loginByQrCode]; + + Map toJson() => _$NeteaseUserToJson(this); +} diff --git a/lib/model/netease_user.g.dart b/lib/model/netease_user.g.dart new file mode 100644 index 00000000..d7bad7a4 --- /dev/null +++ b/lib/model/netease_user.g.dart @@ -0,0 +1,18 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'netease_user.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +NeteaseUser _$NeteaseUserFromJson(Map json) => NeteaseUser( + user: User.fromJson(Map.from(json['user'] as Map)), + loginByQrCode: json['loginByQrCode'] as bool, + ); + +Map _$NeteaseUserToJson(NeteaseUser instance) => + { + 'user': instance.user.toJson(), + 'loginByQrCode': instance.loginByQrCode, + }; diff --git a/lib/navigation/app.dart b/lib/navigation/app.dart index 9c7c3536..5b3d7fd1 100644 --- a/lib/navigation/app.dart +++ b/lib/navigation/app.dart @@ -3,8 +3,8 @@ import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../generated/l10n.dart'; +import '../providers/key_value/settings_provider.dart'; import '../providers/navigator_provider.dart'; -import '../providers/settings_provider.dart'; import '../utils/platform_configuration.dart'; import 'common/material/app.dart'; import 'common/material/theme.dart'; @@ -45,7 +45,7 @@ class QuietApp extends ConsumerWidget { theme: theme.light, darkTheme: theme.dark, themeMode: ref.watch( - settingStateProvider.select((value) => value.themeMode), + settingKeyValueProvider.select((value) => value.themeMode), ), home: home, debugShowCheckedModeBanner: false, diff --git a/lib/navigation/common/like_button.dart b/lib/navigation/common/like_button.dart index dfbf9fa3..827a8e9c 100644 --- a/lib/navigation/common/like_button.dart +++ b/lib/navigation/common/like_button.dart @@ -3,8 +3,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../extension.dart'; -import '../../providers/account_provider.dart'; import '../../providers/favorite_tracks_provider.dart'; +import '../../providers/key_value/account_provider.dart'; import '../../providers/player_provider.dart'; import '../../repository.dart'; import 'buttons.dart'; diff --git a/lib/navigation/common/login/login.dart b/lib/navigation/common/login/login.dart index d88c50c8..d5106703 100644 --- a/lib/navigation/common/login/login.dart +++ b/lib/navigation/common/login/login.dart @@ -7,7 +7,7 @@ import 'package:overlay_support/overlay_support.dart'; import 'package:qr_flutter/qr_flutter.dart'; import '../../../extension.dart'; -import '../../../providers/account_provider.dart'; +import '../../../providers/key_value/account_provider.dart'; import '../../../providers/repository_provider.dart'; import '../../../repository/data/login_qr_key_status.dart'; import '../../../utils/hooks.dart'; @@ -107,7 +107,7 @@ class _QrCodeBody extends HookConsumerWidget { try { await showLoaderOverlay( context, - ref.read(userProvider.notifier).loginWithQrKey(), + ref.read(neteaseAccountProvider).loginWithQrKey(), ); onVerified(); } catch (error, stacktrace) { @@ -187,7 +187,7 @@ class LoginPasswordWidget extends HookConsumerWidget { toast(context.strings.pleaseInputPassword); return; } - final account = ref.read(userProvider.notifier); + final account = ref.read(neteaseAccountProvider.notifier); final result = await showLoaderOverlay( context, account.login(phone, password), diff --git a/lib/navigation/common/material/app.dart b/lib/navigation/common/material/app.dart index 75d83a03..7475f827 100644 --- a/lib/navigation/common/material/app.dart +++ b/lib/navigation/common/material/app.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../../../extension.dart'; -import '../../../providers/settings_provider.dart'; +import '../../../providers/key_value/settings_provider.dart'; class CopyRightOverlay extends HookConsumerWidget { const CopyRightOverlay({super.key, this.child}); @@ -23,7 +23,7 @@ class CopyRightOverlay extends HookConsumerWidget { ); return CustomPaint( foregroundPainter: - ref.watch(settingStateProvider.select((value) => value.copyright)) + ref.watch(settingKeyValueProvider.select((value) => value.copyright)) ? null : painter, child: child, diff --git a/lib/navigation/common/navigation_target.dart b/lib/navigation/common/navigation_target.dart index 7d6db61a..0590bcea 100644 --- a/lib/navigation/common/navigation_target.dart +++ b/lib/navigation/common/navigation_target.dart @@ -1,4 +1,4 @@ -import '../../repository.dart'; +import '../../repository/data/playlist_detail.dart'; const kMobileHomeTabs = [ NavigationTargetDiscover, diff --git a/lib/navigation/common/navigator.dart b/lib/navigation/common/navigator.dart index 8131dbd3..bd98b8e0 100644 --- a/lib/navigation/common/navigator.dart +++ b/lib/navigation/common/navigator.dart @@ -9,25 +9,16 @@ class AppNavigator extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final navigatorState = ref.watch(navigatorProvider); - return WillPopScope( - onWillPop: () async { + return Navigator( + pages: List.of(navigatorState.pages), + onPopPage: (route, result) { if (!navigatorState.canBack) { - return true; + return false; } + route.didPop(null); ref.read(navigatorProvider.notifier).back(); - return false; + return true; }, - child: Navigator( - pages: List.of(navigatorState.pages), - onPopPage: (route, result) { - if (!navigatorState.canBack) { - return false; - } - route.didPop(null); - ref.read(navigatorProvider.notifier).back(); - return true; - }, - ), ); } } diff --git a/lib/navigation/common/page_splash.dart b/lib/navigation/common/page_splash.dart index 678005c5..2b9f385f 100644 --- a/lib/navigation/common/page_splash.dart +++ b/lib/navigation/common/page_splash.dart @@ -3,7 +3,8 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../../providers/account_provider.dart'; +import '../../providers/key_value/account_provider.dart'; +import '../../providers/key_value/settings_provider.dart'; ///used to build application widget ///[data] the data initial in [PageSplash] @@ -29,7 +30,8 @@ class _PageSplashState extends ConsumerState { void initState() { super.initState(); final tasks = [ - ref.read(userProvider.notifier).initialize(), + ref.read(authKeyValueProvider).initialized, + ref.read(settingKeyValueProvider).initialized, ]; final start = DateTime.now().millisecondsSinceEpoch; Future.wait([ diff --git a/lib/navigation/common/playlist/track_list_container.dart b/lib/navigation/common/playlist/track_list_container.dart index 1ca355cc..9290c315 100644 --- a/lib/navigation/common/playlist/track_list_container.dart +++ b/lib/navigation/common/playlist/track_list_container.dart @@ -7,11 +7,11 @@ import 'package:mixin_logger/mixin_logger.dart'; import '../../../extension.dart'; import '../../../media/tracks/track_list.dart'; import '../../../media/tracks/tracks_player.dart'; +import '../../../providers/key_value/settings_provider.dart'; import '../../../providers/navigator_provider.dart'; import '../../../providers/player_provider.dart'; import '../../../providers/playlist_detail_provider.dart'; import '../../../providers/repository_provider.dart'; -import '../../../providers/settings_provider.dart'; import '../../../repository.dart'; import '../navigation_target.dart'; @@ -145,7 +145,7 @@ class TrackTileContainer extends ConsumerStatefulWidget { (ref, track) async { final player = ref.read(playerProvider); final skipAccompaniment = - ref.read(settingStateProvider).skipAccompaniment; + ref.read(settingKeyValueProvider).skipAccompaniment; final List tracks; if (skipAccompaniment) { tracks = playlist.tracks @@ -155,7 +155,7 @@ class TrackTileContainer extends ConsumerStatefulWidget { tracks = playlist.tracks; } - if (player.repeatMode == RepeatMode.heart && playlist.isFavorite) { + if (player.repeatMode == RepeatMode.heart && playlist.isMyFavorite) { try { final toPlay = track ?? tracks.firstOrNull; if (toPlay == null) { @@ -186,7 +186,7 @@ class TrackTileContainer extends ConsumerStatefulWidget { id, tracks, track: track, - isUserFavoriteList: playlist.isFavorite, + isUserFavoriteList: playlist.isMyFavorite, rawPlaylistId: playlist.id, ); } diff --git a/lib/navigation/common/settings.dart b/lib/navigation/common/settings.dart index c8e13e05..128cd602 100644 --- a/lib/navigation/common/settings.dart +++ b/lib/navigation/common/settings.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../../extension.dart'; +import '../../extension.dart'; +import '../../providers/key_value/settings_provider.dart'; import '../../providers/navigator_provider.dart'; -import '../../providers/settings_provider.dart'; class ThemeSwitchRadios extends ConsumerWidget { const ThemeSwitchRadios({super.key}); @@ -11,25 +11,25 @@ class ThemeSwitchRadios extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final themeMode = ref.watch( - settingStateProvider.select((value) => value.themeMode), + settingKeyValueProvider.select((value) => value.themeMode), ); - final notifier = ref.read(settingStateProvider.notifier); + final notifier = ref.read(settingKeyValueProvider.notifier); return Column( children: [ RadioListTile( - onChanged: (mode) => notifier.setThemeMode(mode!), + onChanged: (mode) => notifier.themeMode = mode!, groupValue: themeMode, value: ThemeMode.system, title: Text(context.strings.themeAuto), ), RadioListTile( - onChanged: (mode) => notifier.setThemeMode(mode!), + onChanged: (mode) => notifier.themeMode = mode!, groupValue: themeMode, value: ThemeMode.light, title: Text(context.strings.themeLight), ), RadioListTile( - onChanged: (mode) => notifier.setThemeMode(mode!), + onChanged: (mode) => notifier.themeMode = mode!, groupValue: themeMode, value: ThemeMode.dark, title: Text(context.strings.themeDark), @@ -45,11 +45,10 @@ class CopyRightOverlayCheckBox extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { return CheckboxListTile( - value: ref.watch(settingStateProvider.select((value) => value.copyright)), + value: + ref.watch(settingKeyValueProvider.select((value) => value.copyright)), onChanged: (value) { - ref - .read(settingStateProvider.notifier) - .setShowCopyrightOverlay(show: value ?? false); + ref.read(settingKeyValueProvider.notifier).copyright = value ?? false; }, controlAffinity: ListTileControlAffinity.leading, title: Text(context.strings.hideCopyrightOverlay), @@ -101,11 +100,11 @@ class SkipAccompanimentCheckBox extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { return CheckboxListTile( value: ref.watch( - settingStateProvider.select((value) => value.skipAccompaniment), + settingKeyValueProvider.select((value) => value.skipAccompaniment), ), onChanged: (value) => ref - .read(settingStateProvider.notifier) - .setSkipAccompaniment(skip: value ?? false), + .read(settingKeyValueProvider.notifier) + .skipAccompaniment = value ?? false, controlAffinity: ListTileControlAffinity.leading, title: Text(context.strings.skipAccompaniment), ); diff --git a/lib/navigation/desktop/discover/recommend_for_you.dart b/lib/navigation/desktop/discover/recommend_for_you.dart index 88d5983c..0aaaa0c9 100644 --- a/lib/navigation/desktop/discover/recommend_for_you.dart +++ b/lib/navigation/desktop/discover/recommend_for_you.dart @@ -7,9 +7,9 @@ import 'package:overlay_support/overlay_support.dart'; import '../../../extension.dart'; import '../../../media/tracks/track_list.dart'; -import '../../../providers/account_provider.dart'; import '../../../providers/daily_playlist_provider.dart'; import '../../../providers/fm_playlist_provider.dart'; +import '../../../providers/key_value/account_provider.dart'; import '../../../providers/navigator_provider.dart'; import '../../../providers/player_provider.dart'; import '../../../repository/data/track.dart'; diff --git a/lib/navigation/desktop/header_bar.dart b/lib/navigation/desktop/header_bar.dart index 8f96eb8a..d8f28953 100644 --- a/lib/navigation/desktop/header_bar.dart +++ b/lib/navigation/desktop/header_bar.dart @@ -8,7 +8,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:window_manager/window_manager.dart'; import '../../extension.dart'; -import '../../providers/account_provider.dart'; +import '../../providers/key_value/account_provider.dart'; import '../../providers/navigator_provider.dart'; import '../../utils/callback_window_listener.dart'; import '../common/image.dart'; diff --git a/lib/navigation/desktop/navigation_side_bar.dart b/lib/navigation/desktop/navigation_side_bar.dart index 8a5080fe..b6f69cf8 100644 --- a/lib/navigation/desktop/navigation_side_bar.dart +++ b/lib/navigation/desktop/navigation_side_bar.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../../extension.dart'; -import '../../providers/account_provider.dart'; +import '../../providers/key_value/account_provider.dart'; import '../../providers/navigator_provider.dart'; import '../common/navigation_target.dart'; import 'playlist/user_playlists.dart'; diff --git a/lib/navigation/desktop/playlist/page_playlist.dart b/lib/navigation/desktop/playlist/page_playlist.dart index af0bb8eb..7c831621 100644 --- a/lib/navigation/desktop/playlist/page_playlist.dart +++ b/lib/navigation/desktop/playlist/page_playlist.dart @@ -8,7 +8,7 @@ import 'package:overlay_support/overlay_support.dart'; import 'package:stream_transform/stream_transform.dart'; import '../../../extension.dart'; -import '../../../providers/account_provider.dart'; +import '../../../providers/key_value/account_provider.dart'; import '../../../providers/navigator_provider.dart'; import '../../../providers/player_provider.dart'; import '../../../providers/playlist_detail_provider.dart'; diff --git a/lib/navigation/desktop/playlist/user_playlists.dart b/lib/navigation/desktop/playlist/user_playlists.dart index f6ce8e3d..0c1a0b0a 100644 --- a/lib/navigation/desktop/playlist/user_playlists.dart +++ b/lib/navigation/desktop/playlist/user_playlists.dart @@ -8,12 +8,12 @@ import 'package:overlay_support/overlay_support.dart'; import '../../../extension.dart'; import '../../../media/tracks/track_list.dart'; import '../../../media/tracks/tracks_player.dart'; -import '../../../providers/account_provider.dart'; +import '../../../providers/key_value/account_provider.dart'; import '../../../providers/navigator_provider.dart'; import '../../../providers/player_provider.dart'; +import '../../../providers/playlist/user_playlists_provider.dart'; import '../../../providers/repository_provider.dart'; -import '../../../providers/user_playlists_provider.dart'; -import '../../../repository.dart'; +import '../../../repository/data/playlist_detail.dart'; import '../../common/buttons.dart'; import '../../common/navigation_target.dart'; import '../widgets/expansion_panel.dart'; @@ -78,9 +78,9 @@ class _UserPlaylist extends HookWidget { @override Widget build(BuildContext context) { - final created = playlists.where((p) => p.creator.userId == userId).toList(); + final created = playlists.where((p) => p.creatorUserId == userId).toList(); final subscribed = - playlists.where((p) => p.creator.userId != userId).toList(); + playlists.where((p) => p.creatorUserId != userId).toList(); final createdExpanded = useState(true); final subscribedExpanded = useState(true); @@ -194,13 +194,13 @@ class _UserPlaylistItem extends ConsumerWidget { title: Tooltip( message: playlist.name, child: Text( - playlist.isFavorite + playlist.isMyFavorite ? context.strings.myFavoriteMusics : playlist.name, ), ), isSelected: current == playlist.id, - trailing: !playlist.isFavorite + trailing: !playlist.isMyFavorite ? null : AppIconButton( icon: FluentIcons.heart_pulse_20_regular, diff --git a/lib/navigation/desktop/popup/user_info_popup.dart b/lib/navigation/desktop/popup/user_info_popup.dart index b183a87c..eafc10b1 100644 --- a/lib/navigation/desktop/popup/user_info_popup.dart +++ b/lib/navigation/desktop/popup/user_info_popup.dart @@ -4,7 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:overlay_support/overlay_support.dart'; import '../../../extension.dart'; -import '../../../providers/account_provider.dart'; +import '../../../providers/key_value/account_provider.dart'; import '../../common/shape.dart'; import '../widgets/context_menu.dart'; @@ -37,7 +37,7 @@ Future showUserInfoPopup({ title: Text(context.strings.logout), onTap: () { OverlaySupportEntry.of(context)?.dismiss(); - ref.read(userProvider.notifier).logout(); + ref.read(neteaseAccountProvider).logout(); }, icon: const Icon(FluentIcons.power_20_regular), height: 48, diff --git a/lib/navigation/desktop/widgets/highlight_clickable_text.dart b/lib/navigation/desktop/widgets/highlight_clickable_text.dart index 89a141ca..a96aca94 100644 --- a/lib/navigation/desktop/widgets/highlight_clickable_text.dart +++ b/lib/navigation/desktop/widgets/highlight_clickable_text.dart @@ -325,7 +325,7 @@ class _OverflowText extends HookWidget { text: textSpan, textDirection: Directionality.of(context), locale: Localizations.localeOf(context), - textScaleFactor: MediaQuery.textScaleFactorOf(context), + textScaler: MediaQuery.textScalerOf(context), maxLines: maxLines ?? defaultTextStyle.maxLines, textWidthBasis: defaultTextStyle.textWidthBasis, textHeightBehavior: defaultTextStyle.textHeightBehavior, diff --git a/lib/navigation/desktop/widgets/track_tile_normal.dart b/lib/navigation/desktop/widgets/track_tile_normal.dart index 28bbd4ba..1d7baeb5 100644 --- a/lib/navigation/desktop/widgets/track_tile_normal.dart +++ b/lib/navigation/desktop/widgets/track_tile_normal.dart @@ -6,11 +6,11 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:overlay_support/overlay_support.dart'; import '../../../extension.dart'; -import '../../../providers/account_provider.dart'; +import '../../../providers/key_value/account_provider.dart'; import '../../../providers/navigator_provider.dart'; import '../../../providers/player_provider.dart'; +import '../../../providers/playlist/user_playlists_provider.dart'; import '../../../providers/playlist_detail_provider.dart'; -import '../../../providers/user_playlists_provider.dart'; import '../../../repository.dart'; import '../../common/like_button.dart'; import '../../common/navigation_target.dart'; @@ -521,7 +521,7 @@ class _AddToPlaylistSubMenu extends ConsumerWidget { final data = ref.watch( userPlaylistsProvider(userId!).select( (value) => value.whenData( - (value) => value.where((element) => element.creator.userId == userId), + (value) => value.where((element) => element.creatorUserId == userId), ), ), ); diff --git a/lib/navigation/mobile/dialog/add_to_playlist_bottom_sheet.dart b/lib/navigation/mobile/dialog/add_to_playlist_bottom_sheet.dart index df721673..4e135a2d 100644 --- a/lib/navigation/mobile/dialog/add_to_playlist_bottom_sheet.dart +++ b/lib/navigation/mobile/dialog/add_to_playlist_bottom_sheet.dart @@ -3,9 +3,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:overlay_support/overlay_support.dart'; import '../../../extension.dart'; -import '../../../providers/account_provider.dart'; +import '../../../providers/key_value/account_provider.dart'; +import '../../../providers/playlist/user_playlists_provider.dart'; import '../../../providers/playlist_detail_provider.dart'; -import '../../../providers/user_playlists_provider.dart'; import '../../../repository/data/track.dart'; import '../widgets/playlist_tile.dart'; diff --git a/lib/navigation/mobile/dialog/track_menu_bottom_sheet.dart b/lib/navigation/mobile/dialog/track_menu_bottom_sheet.dart index 04345ed0..19c5f552 100644 --- a/lib/navigation/mobile/dialog/track_menu_bottom_sheet.dart +++ b/lib/navigation/mobile/dialog/track_menu_bottom_sheet.dart @@ -4,7 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:overlay_support/overlay_support.dart'; import '../../../extension.dart'; -import '../../../providers/account_provider.dart'; +import '../../../providers/key_value/account_provider.dart'; import '../../../providers/navigator_provider.dart'; import '../../../providers/player_provider.dart'; import '../../../repository.dart'; diff --git a/lib/navigation/mobile/home/_playlists.dart b/lib/navigation/mobile/home/_playlists.dart index 6827661b..32fd0f1a 100644 --- a/lib/navigation/mobile/home/_playlists.dart +++ b/lib/navigation/mobile/home/_playlists.dart @@ -5,8 +5,8 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../../../extension.dart'; -import '../../../providers/account_provider.dart'; -import '../../../providers/user_playlists_provider.dart'; +import '../../../providers/key_value/account_provider.dart'; +import '../../../providers/playlist/user_playlists_provider.dart'; import '../../../repository.dart'; import '../widgets/playlist_tile.dart'; diff --git a/lib/navigation/mobile/home/_preset_grid.dart b/lib/navigation/mobile/home/_preset_grid.dart index f6f26a3c..0ccc343a 100644 --- a/lib/navigation/mobile/home/_preset_grid.dart +++ b/lib/navigation/mobile/home/_preset_grid.dart @@ -3,7 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:overlay_support/overlay_support.dart'; import '../../../extension.dart'; -import '../../../providers/account_provider.dart'; +import '../../../providers/key_value/account_provider.dart'; class PresetGridSection extends ConsumerWidget { const PresetGridSection({super.key}); diff --git a/lib/navigation/mobile/home/_profile.dart b/lib/navigation/mobile/home/_profile.dart index c2d6fc58..73ee975f 100644 --- a/lib/navigation/mobile/home/_profile.dart +++ b/lib/navigation/mobile/home/_profile.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../extension.dart'; -import '../../../providers/account_provider.dart'; +import '../../../providers/key_value/account_provider.dart'; import '../../../providers/navigator_provider.dart'; import '../../../repository.dart'; import '../../common/image.dart'; diff --git a/lib/navigation/mobile/home/main_page_my.dart b/lib/navigation/mobile/home/main_page_my.dart index 45780740..65c0512e 100644 --- a/lib/navigation/mobile/home/main_page_my.dart +++ b/lib/navigation/mobile/home/main_page_my.dart @@ -4,7 +4,7 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../../../extension.dart'; -import '../../../providers/account_provider.dart'; +import '../../../providers/key_value/account_provider.dart'; import '../../../providers/navigator_provider.dart'; import '../../common/buttons.dart'; import '../../common/image.dart'; diff --git a/lib/navigation/mobile/home/page_home.dart b/lib/navigation/mobile/home/page_home.dart index cb177f3f..533447c7 100644 --- a/lib/navigation/mobile/home/page_home.dart +++ b/lib/navigation/mobile/home/page_home.dart @@ -14,11 +14,11 @@ class PageHome extends StatelessWidget { @override Widget build(BuildContext context) { final Widget body; - switch (selectedTab.runtimeType) { - case NavigationTargetLibrary: + switch (selectedTab) { + case NavigationTargetLibrary _: body = const MainPageMy(); break; - case NavigationTargetDiscover: + case NavigationTargetDiscover _: body = const MainPageDiscover(); break; default: diff --git a/lib/navigation/mobile/playlists/page_playlist_detail.dart b/lib/navigation/mobile/playlists/page_playlist_detail.dart index 514464de..961b8d97 100644 --- a/lib/navigation/mobile/playlists/page_playlist_detail.dart +++ b/lib/navigation/mobile/playlists/page_playlist_detail.dart @@ -3,7 +3,7 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../../../extension.dart'; -import '../../../providers/account_provider.dart'; +import '../../../providers/key_value/account_provider.dart'; import '../../../providers/playlist_detail_provider.dart'; import '../../../repository.dart'; import '../../common/playlist/track_list_container.dart'; diff --git a/lib/navigation/mobile/playlists/page_playlist_edit.dart b/lib/navigation/mobile/playlists/page_playlist_edit.dart index 70dcf419..f0ca9cbe 100644 --- a/lib/navigation/mobile/playlists/page_playlist_edit.dart +++ b/lib/navigation/mobile/playlists/page_playlist_edit.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:overlay_support/overlay_support.dart'; -import '../../../repository.dart'; + +import '../../../repository/data/playlist_detail.dart'; import '../../common/image.dart'; ///page for playlist edit diff --git a/lib/navigation/mobile/search/page_search.dart b/lib/navigation/mobile/search/page_search.dart index ed9e0067..958a6bcb 100644 --- a/lib/navigation/mobile/search/page_search.dart +++ b/lib/navigation/mobile/search/page_search.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart' hide SearchBar; import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../../../extension.dart'; +import '../../../providers/key_value/search_history_provider.dart'; import '../../../providers/navigator_provider.dart'; -import '../../../providers/search_history_provider.dart'; import '../../common/navigation_target.dart'; import '../widgets/will_pop_scope.dart'; import 'search_bar.dart'; @@ -126,7 +126,8 @@ class _SearchHistory extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final histories = ref.watch(searchHistoryProvider); + final histories = + ref.watch(searchHistoryProvider.select((value) => value.searchHistory)); if (histories.isEmpty) { return const SizedBox(); } diff --git a/lib/navigation/mobile/settings/page_setting.dart b/lib/navigation/mobile/settings/page_setting.dart index fbd097b6..8b8f4607 100644 --- a/lib/navigation/mobile/settings/page_setting.dart +++ b/lib/navigation/mobile/settings/page_setting.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../extension.dart'; -import '../../../providers/account_provider.dart'; +import '../../../providers/key_value/account_provider.dart'; import '../../common/buttons.dart'; import '../../common/settings.dart'; @@ -138,7 +138,7 @@ class _AccountSettings extends ConsumerWidget { ListTile( title: Text(context.strings.logout), onTap: () { - ref.read(userProvider.notifier).logout(); + ref.read(neteaseAccountProvider).logout(); }, ), ], diff --git a/lib/navigation/mobile/user/page_user_detail.dart b/lib/navigation/mobile/user/page_user_detail.dart index 06724b86..4f7821ee 100644 --- a/lib/navigation/mobile/user/page_user_detail.dart +++ b/lib/navigation/mobile/user/page_user_detail.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:loader/loader.dart'; import 'package:overlay_support/overlay_support.dart'; import '../../../extension.dart'; +import '../../../providers/playlist/user_playlists_provider.dart'; import '../../../providers/user_detail_provider.dart'; import '../../../repository.dart'; import '../../common/image.dart'; @@ -13,9 +13,7 @@ import '../../common/material/tabs.dart'; import '../widgets/playlist_tile.dart'; part 'tab_about.dart'; - part 'tab_events.dart'; - part 'tab_music.dart'; ///用户详情页 diff --git a/lib/navigation/mobile/user/tab_music.dart b/lib/navigation/mobile/user/tab_music.dart index 46ea9148..3fed2725 100644 --- a/lib/navigation/mobile/user/tab_music.dart +++ b/lib/navigation/mobile/user/tab_music.dart @@ -1,15 +1,15 @@ part of 'page_user_detail.dart'; -class TabMusic extends StatefulWidget { +class TabMusic extends ConsumerStatefulWidget { const TabMusic(this.profile, {super.key}); final User profile; @override - State createState() => _TabMusicState(); + ConsumerState createState() => _TabMusicState(); } -class _TabMusicState extends State +class _TabMusicState extends ConsumerState with AutomaticKeepAliveClientMixin { @override bool get wantKeepAlive => true; @@ -17,13 +17,15 @@ class _TabMusicState extends State @override Widget build(BuildContext context) { super.build(context); - return Loader>( - loadTask: () => neteaseRepository!.userPlaylist(widget.profile.userId), - builder: (context, result) { + final playlists = ref.watch(userPlaylistsProvider(widget.profile.userId)); + return playlists.when( + loading: () => const Center(child: CircularProgressIndicator()), + error: (e, _) => Center(child: Text(context.formattedError(e))), + data: (result) { final created = - result.where((p) => p.creator.userId == widget.profile.userId); + result.where((p) => p.creatorUserId == widget.profile.userId); final subscribed = - result.where((p) => p.creator.userId != widget.profile.userId); + result.where((p) => p.creatorUserId != widget.profile.userId); return ListView( children: [ Row( diff --git a/lib/navigation/mobile/welcome/page_welcome.dart b/lib/navigation/mobile/welcome/page_welcome.dart index 3b161ba8..753155c0 100644 --- a/lib/navigation/mobile/welcome/page_welcome.dart +++ b/lib/navigation/mobile/welcome/page_welcome.dart @@ -3,7 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:url_launcher/url_launcher_string.dart'; import '../../../extension.dart'; -import '../../../providers/settings_provider.dart'; +import '../../../providers/key_value/settings_provider.dart'; class PageWelcome extends StatefulWidget { const PageWelcome({super.key}); @@ -57,7 +57,8 @@ class _WelcomeBody extends ConsumerWidget { const SizedBox(height: 8), OutlinedButton( onPressed: () { - ref.read(settingStateProvider.notifier).setSkipWelcomePage(); + ref.read(settingKeyValueProvider.notifier).skipWelcomePage = + true; //remove the all pages // Navigator.pushNamedAndRemoveUntil( // context, diff --git a/lib/providers/account_provider.dart b/lib/providers/account_provider.dart deleted file mode 100644 index 3f61f1df..00000000 --- a/lib/providers/account_provider.dart +++ /dev/null @@ -1,128 +0,0 @@ -import 'dart:async'; - -import 'package:async/async.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; - -import '../repository/data/user.dart'; -import '../repository/netease.dart'; -import 'preference_provider.dart'; -import 'repository_provider.dart'; - -final userProvider = StateNotifierProvider(UserAccount.new); - -final isLoginProvider = Provider((ref) { - return ref.watch(userProvider) != null; -}); - -final userIdProvider = Provider((ref) { - return ref.watch(userProvider)?.userId; -}); - -class UserAccount extends StateNotifier { - UserAccount(this.ref) : super(null) { - _subscription = - ref.read(neteaseRepositoryProvider).onApiUnAuthorized.listen( - (event) { - debugPrint('onApiUnAuthorized'); - logout(); - }, - ); - } - - final Ref ref; - - StreamSubscription? _subscription; - - Future> login(String? phone, String password) async { - final result = await neteaseRepository!.login(phone, password); - if (result.isValue) { - final json = result.asValue!.value; - final userId = json['account']['id'] as int; - try { - await _updateLoginStatus(userId); - } catch (error, stacktrace) { - return Result.error(error, stacktrace); - } - } - return result; - } - - Future _updateLoginStatus( - int userId, { - bool loginByQrKey = false, - }) async { - final userDetailResult = await neteaseRepository!.getUserDetail(userId); - if (userDetailResult.isError) { - final error = userDetailResult.asError!; - debugPrint('error : ${error.error} ${error.stackTrace}'); - throw Exception('can not get user detail.'); - } - await ref.read(sharedPreferenceProvider).setLoginByQrCode(loginByQrKey); - await ref.read(sharedPreferenceProvider).setLoginUser( - userDetailResult.asValue!.value, - ); - state = userDetailResult.asValue!.value; - } - - Future loginWithQrKey() async { - final result = await ref.read(neteaseRepositoryProvider).getLoginStatus(); - final userId = result['account']['id'] as int; - await _updateLoginStatus(userId, loginByQrKey: true); - } - - void logout() { - state = null; - ref.read(sharedPreferenceProvider).setLoginUser(null); - ref.read(neteaseRepositoryProvider).logout(); - } - - Future initialize() async { - final user = await ref.read(sharedPreferenceProvider).getLoginUser(); - if (user != null) { - state = user; - final isLoginViaQrCode = - await ref.read(sharedPreferenceProvider).isLoginByQrCode(); - if (!isLoginViaQrCode) { - //访问api,刷新登陆状态 - await neteaseRepository!.refreshLogin().then( - (login) async { - if (!login || state == null) { - logout(); - } else { - // refresh user - final result = await neteaseRepository!.getUserDetail(userId!); - if (result.isValue) { - state = result.asValue!.value; - await ref.read(sharedPreferenceProvider).setLoginUser(state); - } - } - }, - onError: (e) { - debugPrint('refresh login status failed \n $e'); - }, - ); - } - } - } - - ///当前是否已登录 - bool get isLogin { - return state != null; - } - - ///当前登录用户的id - ///null if not login - int? get userId { - if (!isLogin) { - return null; - } - return state!.userId; - } - - @override - void dispose() { - super.dispose(); - _subscription?.cancel(); - } -} diff --git a/lib/providers/cloud_tracks_provider.dart b/lib/providers/cloud_tracks_provider.dart index 7eeed35f..99e47007 100644 --- a/lib/providers/cloud_tracks_provider.dart +++ b/lib/providers/cloud_tracks_provider.dart @@ -1,13 +1,15 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; + import '../repository.dart'; +import 'key_value/simple_lazy_ley_value_provider.dart'; final cloudTracksProvider = StreamProvider( (ref) async* { const kCacheKey = 'user_cloud_tracks_detail'; - + final keyValue = ref.watch(simpleLazyKeyValueProvider); try { - final data = await neteaseLocalData.get>(kCacheKey); + final data = await keyValue.get>(kCacheKey); if (data != null) { yield CloudTracksDetail.fromJson(data); } @@ -16,6 +18,6 @@ final cloudTracksProvider = StreamProvider( } final details = await neteaseRepository!.getUserCloudTracks(); yield details; - neteaseLocalData[kCacheKey] = details.toJson(); + await keyValue.set(kCacheKey, details.toJson()); }, ); diff --git a/lib/providers/counter_provider.dart b/lib/providers/counter_provider.dart index 21765d1b..7ffc7a71 100644 --- a/lib/providers/counter_provider.dart +++ b/lib/providers/counter_provider.dart @@ -3,22 +3,31 @@ import 'dart:async'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../repository.dart'; +import '../utils/db/db_key_value.dart'; import '../utils/riverpod/cacheable_state_provider.dart'; -import 'account_provider.dart'; +import 'key_value/account_provider.dart'; +import 'key_value/simple_lazy_ley_value_provider.dart'; export 'package:netease_api/netease_api.dart' show MusicCount; final userMusicCountProvider = StateNotifierProvider((ref) { - return MusicCountNotifier(login: ref.watch(isLoginProvider)); + return MusicCountNotifier( + login: ref.watch(isLoginProvider), + keyValue: ref.watch(simpleLazyKeyValueProvider), + ); }); class MusicCountNotifier extends CacheableStateNotifier { - MusicCountNotifier({required this.login}) : super(const MusicCount()); + MusicCountNotifier({ + required this.login, + required this.keyValue, + }) : super(const MusicCount()); static const _cacheKey = 'user_sub_count'; final bool login; + final BaseLazyDbKeyValue keyValue; @override Future load() async { @@ -34,8 +43,7 @@ class MusicCountNotifier extends CacheableStateNotifier { @override Future loadFromCache() async { - final cache = - await neteaseLocalData.get(_cacheKey) as Map?; + final cache = await keyValue.get>(_cacheKey); if (cache == null) { return null; } @@ -44,6 +52,6 @@ class MusicCountNotifier extends CacheableStateNotifier { @override void saveToCache(MusicCount value) { - neteaseLocalData[_cacheKey] = state.toJson(); + keyValue.set(_cacheKey, value); } } diff --git a/lib/providers/daily_playlist_provider.dart b/lib/providers/daily_playlist_provider.dart index 4fe65d19..877f1710 100644 --- a/lib/providers/daily_playlist_provider.dart +++ b/lib/providers/daily_playlist_provider.dart @@ -4,6 +4,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../repository.dart'; +import 'key_value/simple_lazy_ley_value_provider.dart'; extension _DateTime on DateTime { bool isTheSameDay(DateTime other) { @@ -26,6 +27,8 @@ final dailyPlaylistProvider = StreamProvider( Timer? timer; + final keyValue = ref.read(simpleLazyKeyValueProvider); + void scheduleRefresh(VoidCallback refresh) { timer?.cancel(); final nextDay = DateTime.now().nextDay().millisecondsSinceEpoch - @@ -38,14 +41,13 @@ final dailyPlaylistProvider = StreamProvider( final songs = await ret.asFuture; final playlist = DailyPlaylist(date: DateTime.now(), tracks: songs); streamController.add(playlist); - neteaseLocalData[cacheKey] = playlist.toJson(); + await keyValue.set(cacheKey, playlist.toJson()); scheduleRefresh(refresh); } scheduleMicrotask(() async { try { - final cache = - await neteaseLocalData.get>(cacheKey); + final cache = await keyValue.get>(cacheKey); if (cache != null) { final playlist = DailyPlaylist.fromJson(cache); if (playlist.date.isToday()) { diff --git a/lib/providers/database_provider.dart b/lib/providers/database_provider.dart new file mode 100644 index 00000000..2a35a573 --- /dev/null +++ b/lib/providers/database_provider.dart @@ -0,0 +1,8 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../db/app_database.dart'; + +final databaseProvider = Provider((ref) => AppDatabase.connect()); + +final keyValueDaoProvider = + databaseProvider.select((value) => value.keyValueDao); diff --git a/lib/providers/favorite_tracks_provider.dart b/lib/providers/favorite_tracks_provider.dart index ab1f0615..6bb11e31 100644 --- a/lib/providers/favorite_tracks_provider.dart +++ b/lib/providers/favorite_tracks_provider.dart @@ -3,16 +3,23 @@ import 'dart:async'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../repository.dart'; +import '../utils/db/db_key_value.dart'; import '../utils/riverpod/cacheable_state_provider.dart'; -import 'account_provider.dart'; +import 'key_value/account_provider.dart'; +import 'key_value/simple_lazy_ley_value_provider.dart'; final userFavoriteMusicListProvider = StateNotifierProvider>( - (ref) => UserFavoriteMusicListNotifier(ref.watch(userProvider)?.userId), + (ref) => UserFavoriteMusicListNotifier( + ref.watch(userProvider)?.userId, + ref.watch(simpleLazyKeyValueProvider), + ), ); class UserFavoriteMusicListNotifier extends CacheableStateNotifier> { - UserFavoriteMusicListNotifier(this.userId) : super(const []); + UserFavoriteMusicListNotifier(this.userId, this.keyValue) : super(const []); + + final BaseLazyDbKeyValue keyValue; static const _keyLikedSongList = 'likedSongList'; @@ -45,11 +52,11 @@ class UserFavoriteMusicListNotifier extends CacheableStateNotifier> { @override Future?> loadFromCache() async => - (await neteaseLocalData[_keyLikedSongList] as List?)?.cast(); + (await keyValue.get(_keyLikedSongList))?.cast(); @override void saveToCache(List value) { - neteaseLocalData[_keyLikedSongList] = value; + keyValue.set(_keyLikedSongList, value); } } diff --git a/lib/providers/key_value/account_provider.dart b/lib/providers/key_value/account_provider.dart new file mode 100644 index 00000000..9a177b8f --- /dev/null +++ b/lib/providers/key_value/account_provider.dart @@ -0,0 +1,155 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:async/async.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mixin_logger/mixin_logger.dart'; + +import '../../db/enum/key_value_group.dart'; +import '../../model/netease_user.dart'; +import '../../repository/netease.dart'; +import '../../repository/network_repository.dart'; +import '../../utils/db/db_key_value.dart'; +import '../database_provider.dart'; +import '../repository_provider.dart'; + +final neteaseAccountProvider = + ChangeNotifierProvider((ref) { + final authKeyValue = ref.watch(authKeyValueProvider); + final neteaseRepository = ref.watch(neteaseRepositoryProvider); + return NeteaseAccountNotifier( + authKeyValue: authKeyValue, + neteaseRepository: neteaseRepository, + ); +}); + +final userProvider = neteaseAccountProvider.select((value) => value.user?.user); + +final isLoginProvider = neteaseAccountProvider.select((value) => value.isLogin); + +final userIdProvider = neteaseAccountProvider.select((value) => value.userId); + +const _keyNeteaseUser = 'neteaseUser'; + +class AuthKeyValue extends BaseDbKeyValue { + AuthKeyValue({required super.dao}) : super(group: KeyValueGroup.auth); + + NeteaseUser? get neteaseUser { + final json = get>(_keyNeteaseUser); + if (json == null) { + return null; + } + return NeteaseUser.fromJson(json); + } + + set neteaseUser(NeteaseUser? user) { + set(_keyNeteaseUser, jsonEncode(user)); + } +} + +final authKeyValueProvider = Provider((ref) { + final dao = ref.watch(databaseProvider.select((value) => value.keyValueDao)); + return AuthKeyValue(dao: dao); +}); + +class NeteaseAccountNotifier extends ChangeNotifier { + NeteaseAccountNotifier({ + required AuthKeyValue authKeyValue, + required NetworkRepository neteaseRepository, + }) : _authKeyValue = authKeyValue, + _neteaseRepository = neteaseRepository { + _authKeyValue.addListener(notifyListeners); + _initialize(); + _subscription = _neteaseRepository.onApiUnAuthorized.listen((event) { + logout(); + }); + } + + final AuthKeyValue _authKeyValue; + final NetworkRepository _neteaseRepository; + StreamSubscription? _subscription; + + NeteaseUser? get user => _authKeyValue.neteaseUser; + + bool get isLogin => user != null; + + int? get userId => user?.user.userId; + + Future _initialize() async { + await _authKeyValue.initialized; + if (user != null) { + final isLoginViaQrCode = user!.loginByQrCode; + if (!isLoginViaQrCode) { + //访问api,刷新登陆状态 + await _neteaseRepository.refreshLogin().then( + (login) async { + if (!login || user == null) { + await logout(); + } else { + // refresh user + final result = await _neteaseRepository.getUserDetail(userId!); + if (result.isValue) { + _authKeyValue.neteaseUser = NeteaseUser( + user: result.asValue!.value, + loginByQrCode: isLoginViaQrCode, + ); + } + } + }, + onError: (e) { + d('refresh login status failed \n $e'); + }, + ); + } + } + } + + Future logout() async { + _authKeyValue.neteaseUser = null; + await _neteaseRepository.logout(); + } + + Future> login(String? phone, String password) async { + final result = await neteaseRepository!.login(phone, password); + if (result.isValue) { + final json = result.asValue!.value; + final userId = json['account']['id'] as int; + try { + await _updateLoginStatus(userId); + } catch (error, stacktrace) { + return Result.error(error, stacktrace); + } + } + return result; + } + + Future _updateLoginStatus( + int userId, { + bool loginByQrKey = false, + }) async { + final userDetailResult = await _neteaseRepository.getUserDetail(userId); + if (userDetailResult.isError) { + final error = userDetailResult.asError!; + debugPrint('error : ${error.error} ${error.stackTrace}'); + throw Exception('can not get user detail.'); + } + _authKeyValue.neteaseUser = NeteaseUser( + user: userDetailResult.asValue!.value, + loginByQrCode: loginByQrKey, + ); + } + + Future loginWithQrKey() async { + final result = await _neteaseRepository.getLoginStatus(); + final userId = result['account']['id'] as int; + await _updateLoginStatus(userId, loginByQrKey: true); + } + + @override + void dispose() { + _authKeyValue.removeListener(notifyListeners); + _subscription?.cancel(); + super.dispose(); + } +} diff --git a/lib/providers/key_value/player_state_key_value_provider.dart b/lib/providers/key_value/player_state_key_value_provider.dart new file mode 100644 index 00000000..3d6024ca --- /dev/null +++ b/lib/providers/key_value/player_state_key_value_provider.dart @@ -0,0 +1,34 @@ +import 'package:flutter/foundation.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../../db/enum/key_value_group.dart'; +import '../../model/persistence_player_state.dart'; +import '../../utils/db/db_key_value.dart'; +import '../database_provider.dart'; + +final playerKeyValueProvider = Provider( + (ref) { + final dao = ref.watch(keyValueDaoProvider); + return PlayerKeyValue(dao: dao); + }, +); + +const _keyPlayerState = 'player_state'; + +class PlayerKeyValue extends BaseLazyDbKeyValue { + PlayerKeyValue({required super.dao}) : super(group: KeyValueGroup.player); + + Future setPlayerState(PersistencePlayerState state) async { + return set(_keyPlayerState, await compute(convertToString, state)); + } + + Future getPlayerState() async { + final value = await get(_keyPlayerState); + final json = + await compute?>(convertToType, value); + if (json == null) { + return null; + } + return PersistencePlayerState.fromJson(json); + } +} diff --git a/lib/providers/key_value/search_history_provider.dart b/lib/providers/key_value/search_history_provider.dart new file mode 100644 index 00000000..733b596c --- /dev/null +++ b/lib/providers/key_value/search_history_provider.dart @@ -0,0 +1,34 @@ +import 'dart:async'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../db/enum/key_value_group.dart'; +import '../../utils/db/db_key_value.dart'; +import '../database_provider.dart'; + +const String _kKeyHistory = 'key_search_history'; + +final searchHistoryProvider = ChangeNotifierProvider( + (ref) => SearchKeyValue(dao: ref.watch(keyValueDaoProvider)), +); + +class SearchKeyValue extends BaseDbKeyValue { + SearchKeyValue({required super.dao}) : super(group: KeyValueGroup.search); + + List get searchHistory => + get(_kKeyHistory)?.cast() ?? []; + + Future insertSearchHistory(String query) async { + final history = searchHistory.toList(); + history.remove(query); + history.insert(0, query); + while (history.length > 10) { + history.removeLast(); + } + await set(_kKeyHistory, history); + } + + Future clearSearchHistory() async { + await set(_kKeyHistory, null); + } +} diff --git a/lib/providers/key_value/settings_provider.dart b/lib/providers/key_value/settings_provider.dart new file mode 100644 index 00000000..75af3b84 --- /dev/null +++ b/lib/providers/key_value/settings_provider.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../../db/dao/key_value_dao.dart'; +import '../../db/enum/key_value_group.dart'; +import '../../utils/db/db_key_value.dart'; +import '../database_provider.dart'; + +const String _keyThemeMode = 'themeMode'; + +const String _keyCopyright = 'copyright'; + +const String _keySkipWelcomePage = 'skipWelcomePage'; + +const String _keySkipAccompaniment = 'skipAccompaniment'; + +final settingKeyValueProvider = ChangeNotifierProvider( + (ref) { + final dao = + ref.watch(databaseProvider.select((value) => value.keyValueDao)); + return SettingsKeyValue(dao); + }, +); + +class SettingsKeyValue extends BaseDbKeyValue { + SettingsKeyValue(KeyValueDao dao) + : super(group: KeyValueGroup.setting, dao: dao); + + set themeMode(ThemeMode mode) { + set(_keyThemeMode, mode.name); + } + + ThemeMode get themeMode { + final mode = get(_keyThemeMode); + return ThemeMode.values.firstWhere( + (element) => element.name == mode, + orElse: () => ThemeMode.system, + ); + } + + set copyright(bool show) { + set(_keyCopyright, show); + } + + bool get copyright => get(_keyCopyright) ?? false; + + set skipWelcomePage(bool skip) { + set(_keySkipWelcomePage, skip); + } + + bool get skipWelcomePage => get(_keySkipWelcomePage) ?? false; + + set skipAccompaniment(bool skip) { + set(_keySkipWelcomePage, skip); + } + + bool get skipAccompaniment => get(_keySkipAccompaniment) ?? false; +} diff --git a/lib/providers/key_value/simple_lazy_ley_value_provider.dart b/lib/providers/key_value/simple_lazy_ley_value_provider.dart new file mode 100644 index 00000000..e7e11840 --- /dev/null +++ b/lib/providers/key_value/simple_lazy_ley_value_provider.dart @@ -0,0 +1,12 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../db/enum/key_value_group.dart'; +import '../../utils/db/db_key_value.dart'; +import '../database_provider.dart'; + +final simpleLazyKeyValueProvider = Provider( + (ref) => BaseLazyDbKeyValue( + group: KeyValueGroup.simpleLazy, + dao: ref.watch(keyValueDaoProvider), + ), +); diff --git a/lib/providers/key_value/window_key_value_provider.dart b/lib/providers/key_value/window_key_value_provider.dart new file mode 100644 index 00000000..44edec26 --- /dev/null +++ b/lib/providers/key_value/window_key_value_provider.dart @@ -0,0 +1,40 @@ +import 'dart:ui'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../db/enum/key_value_group.dart'; +import '../../utils/db/db_key_value.dart'; +import '../database_provider.dart'; + +const _keyWindowHeight = 'window_height'; +const _keyWindowWidth = 'window_width'; + +final windowKeyValueProvider = Provider( + (ref) { + final dao = ref.watch(keyValueDaoProvider); + return WindowKeyValue(dao: dao); + }, +); + +class WindowKeyValue extends BaseLazyDbKeyValue { + WindowKeyValue({required super.dao}) : super(group: KeyValueGroup.window); + + Future getWindowSize() async { + final width = await get(_keyWindowWidth); + final height = await get(_keyWindowHeight); + if (width == null || height == null) { + return null; + } + return Size(width, height); + } + + Future setWindowSize(Size? value) async { + if (value == null) { + await set(_keyWindowWidth, null); + await set(_keyWindowHeight, null); + } else { + await set(_keyWindowWidth, value.width); + await set(_keyWindowHeight, value.height); + } + } +} diff --git a/lib/providers/lyric_provider.dart b/lib/providers/lyric_provider.dart index 2cf79a5a..c7b56870 100644 --- a/lib/providers/lyric_provider.dart +++ b/lib/providers/lyric_provider.dart @@ -1,13 +1,40 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../db/enum/key_value_group.dart'; import '../navigation/common/player/lyric.dart'; import '../repository.dart'; +import '../utils/db/db_key_value.dart'; +import 'database_provider.dart'; + +final _lyricKeyValueProvider = Provider( + (ref) { + final dao = ref.watch(keyValueDaoProvider); + return BaseLazyDbKeyValue(group: KeyValueGroup.lyric, dao: dao); + }, +); + +extension _LyricKeyValue on BaseLazyDbKeyValue { + Future getLyric(int id) => getWithConverter( + id.toString(), + LyricContent.from, + ); + + Future setLyric(int id, String lyric) async { + await set(id.toString(), lyric); + } +} final lyricProvider = - FutureProvider.family((ref, id) async { + FutureProvider.family.autoDispose((ref, id) async { + final keyValue = ref.watch(_lyricKeyValueProvider); + final cache = await keyValue.getLyric(id); + if (cache != null) { + return cache; + } final lyric = await neteaseRepository!.lyric(id); if (lyric == null) { return null; } + await keyValue.setLyric(id, lyric); return LyricContent.from(lyric); }); diff --git a/lib/providers/play_history_provider.dart b/lib/providers/play_history_provider.dart index bb5cefb7..aab481ab 100644 --- a/lib/providers/play_history_provider.dart +++ b/lib/providers/play_history_provider.dart @@ -4,25 +4,34 @@ import 'package:flutter/material.dart'; import 'package:riverpod/riverpod.dart'; import '../repository/data/track.dart'; -import '../repository/local_cache_data.dart'; +import '../utils/db/db_key_value.dart'; +import 'key_value/simple_lazy_ley_value_provider.dart'; final playHistoryProvider = StateNotifierProvider>( - (ref) => PlayHistoryStateNotifier(), + (ref) => PlayHistoryStateNotifier(ref.watch(simpleLazyKeyValueProvider)), ); +const _keyPlayHistory = 'play_history'; + class PlayHistoryStateNotifier extends StateNotifier> { - PlayHistoryStateNotifier() : super(const []) { + PlayHistoryStateNotifier(this.keyValue) : super(const []) { _initializeLoad(); } + final BaseLazyDbKeyValue keyValue; + final List _data = []; final _initializeCompleter = Completer(); Future _initializeLoad() async { try { - _data.addAll(await neteaseLocalData.getPlayHistory()); + final cache = + await keyValue.get>>(_keyPlayHistory); + if (cache != null) { + _data.addAll(cache.map(Track.fromJson)); + } state = _data.toList(); } catch (error, stackTrace) { debugPrint( @@ -38,18 +47,18 @@ class PlayHistoryStateNotifier extends StateNotifier> { _data.removeWhere((element) => element.id == track.id); _data.insert(0, track); state = _data.toList(); - await neteaseLocalData.updatePlayHistory(_data); + await keyValue.set(_keyPlayHistory, _data); } void remove(Track track) { _data.removeWhere((element) => element.id == track.id); state = _data.toList(); - neteaseLocalData.updatePlayHistory(_data); + keyValue.set(_keyPlayHistory, _data); } void clear() { _data.clear(); state = _data.toList(); - neteaseLocalData.updatePlayHistory(_data); + keyValue.set(_keyPlayHistory, _data); } } diff --git a/lib/providers/play_records_provider.dart b/lib/providers/play_records_provider.dart index b21c32bc..54ac0d68 100644 --- a/lib/providers/play_records_provider.dart +++ b/lib/providers/play_records_provider.dart @@ -3,7 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../extension.dart'; import '../repository.dart'; import '../utils/exceptions.dart'; -import 'account_provider.dart'; +import 'key_value/account_provider.dart'; final allPlayRecordsProvider = FutureProvider>((ref) async { final userId = ref.watch(userIdProvider); diff --git a/lib/providers/player_provider.dart b/lib/providers/player_provider.dart index 3b4a88d5..76b30a81 100644 --- a/lib/providers/player_provider.dart +++ b/lib/providers/player_provider.dart @@ -4,8 +4,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../media/tracks/tracks_player.dart'; import '../model/persistence_player_state.dart'; +import 'key_value/player_state_key_value_provider.dart'; import 'play_history_provider.dart'; -import 'preference_provider.dart'; final playerStateProvider = StateNotifierProvider( @@ -13,7 +13,7 @@ final playerStateProvider = final player = TracksPlayer.platform(); scheduleMicrotask(() async { - final state = await ref.read(sharedPreferenceProvider).getPlayerState(); + final state = await ref.read(playerKeyValueProvider).getPlayerState(); if (state != null) { player.restoreFromPersistence(state); } @@ -30,7 +30,7 @@ final playerStateProvider = return; } lastState = newState; - ref.read(sharedPreferenceProvider).setPlayerState(newState); + ref.read(playerKeyValueProvider).setPlayerState(newState); }, fireImmediately: false, ); diff --git a/lib/providers/user_playlists_provider.dart b/lib/providers/playlist/user_playlists_provider.dart similarity index 98% rename from lib/providers/user_playlists_provider.dart rename to lib/providers/playlist/user_playlists_provider.dart index adb87429..6cc599cd 100644 --- a/lib/providers/user_playlists_provider.dart +++ b/lib/providers/playlist/user_playlists_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../repository.dart'; +import '../../repository.dart'; final userPlaylistsProvider = FutureProvider.family, int>( (ref, userId) async { diff --git a/lib/providers/playlist_detail_provider.dart b/lib/providers/playlist_detail_provider.dart index 8440fbcc..fab6bd97 100644 --- a/lib/providers/playlist_detail_provider.dart +++ b/lib/providers/playlist_detail_provider.dart @@ -1,16 +1,17 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; -import 'package:hive/hive.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:mixin_logger/mixin_logger.dart'; +import '../db/enum/key_value_group.dart'; import '../repository/data/playlist_detail.dart'; import '../repository/data/track.dart'; import '../repository/netease.dart'; import '../repository/network_repository.dart'; -import '../utils/hive/hive_util.dart'; -import 'account_provider.dart'; +import '../utils/db/db_key_value.dart'; +import 'database_provider.dart'; +import 'key_value/account_provider.dart'; final playlistDetailProvider = StateNotifierProvider.family< PlaylistDetailStateNotifier, AsyncValue, int>( @@ -20,6 +21,27 @@ final playlistDetailProvider = StateNotifierProvider.family< ), ); +extension _PlaylistKeyValue on BaseLazyDbKeyValue { + Future getPlaylistDetail(int playlistId) async { + final json = await get>('playlist_detail_$playlistId'); + if (json == null) { + return null; + } + return PlaylistDetail.fromJson(json); + } + + Future setPlaylistDetail(PlaylistDetail playlistDetail) async { + await set('playlist_detail_${playlistDetail.id}', playlistDetail.toJson()); + } +} + +final _playlistDetailKeyValueProvider = Provider( + (ref) => BaseLazyDbKeyValue( + group: KeyValueGroup.playlistDetail, + dao: ref.watch(keyValueDaoProvider), + ), +); + class PlaylistDetailStateNotifier extends StateNotifier> { PlaylistDetailStateNotifier({ @@ -31,17 +53,17 @@ class PlaylistDetailStateNotifier final int playlistId; final Ref ref; - late Box _playlistDetailBox; PlaylistDetail? _playlistDetail; + BaseLazyDbKeyValue get _playlistDetailKeyValue => + ref.read(_playlistDetailKeyValueProvider); + final _initializeCompleter = Completer(); Future _initializeLoad() async { assert(state is AsyncLoading, 'state is not AsyncLoading'); - _playlistDetailBox = - await Hive.openBoxSafe('playlistDetail'); - final local = _playlistDetailBox.get(playlistId.toString()); + final local = await _playlistDetailKeyValue.getPlaylistDetail(playlistId); if (local != null) { _playlistDetail = local; state = AsyncValue.data(local); @@ -78,7 +100,7 @@ class PlaylistDetailStateNotifier } _playlistDetail = data; state = AsyncValue.data(data); - await _playlistDetailBox.put(playlistId.toString(), data); + await _playlistDetailKeyValue.setPlaylistDetail(data); } catch (error, stacktrace) { debugPrint('error: $error ,$stacktrace'); if (state is! AsyncData) { @@ -119,7 +141,7 @@ class PlaylistDetailStateNotifier trackIds: [...ids, ..._playlistDetail!.trackIds], ); _playlistDetail = detail; - await _playlistDetailBox.put(playlistId.toString(), detail); + await _playlistDetailKeyValue.setPlaylistDetail(detail); state = AsyncValue.data(detail); } @@ -143,7 +165,7 @@ class PlaylistDetailStateNotifier trackIds: _playlistDetail!.trackIds.where((t) => t != track.id).toList(), ); _playlistDetail = detail; - await _playlistDetailBox.put(playlistId.toString(), detail); + await _playlistDetailKeyValue.setPlaylistDetail(detail); state = AsyncValue.data(detail); } } diff --git a/lib/providers/preference_provider.dart b/lib/providers/preference_provider.dart deleted file mode 100644 index 0c3d97df..00000000 --- a/lib/providers/preference_provider.dart +++ /dev/null @@ -1,81 +0,0 @@ -import 'dart:convert'; -import 'dart:ui'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -import '../model/persistence_player_state.dart'; -import '../repository.dart'; - -final sharedPreferenceProvider = Provider( - (ref) => throw UnimplementedError('init with override'), -); - -extension SharedPreferenceProviderExtension on SharedPreferences { - Size? getWindowSize() { - final width = getDouble('window_width') ?? 0; - final height = getDouble('window_height') ?? 0; - if (width == 0 || height == 0) { - return null; - } - return Size(width, height); - } - - Future setWindowSize(Size size) async { - await Future.wait([ - setDouble('window_width', size.width), - setDouble('window_height', size.height), - ]); - } - - Future getPlayerState() async { - final json = getString('player_state'); - if (json == null) { - return null; - } - try { - final map = await compute(jsonDecode, json); - return PersistencePlayerState.fromJson(map); - } catch (error, stacktrace) { - debugPrint('getPlayerState error: $error\n$stacktrace'); - return null; - } - } - - Future setPlayerState(PersistencePlayerState state) async { - final json = await compute(jsonEncode, state.toJson()); - await setString('player_state', json); - } - - Future isLoginByQrCode() async { - return getBool('login_by_qr_code') ?? false; - } - - // ignore: avoid_positional_boolean_parameters - Future setLoginByQrCode(bool value) async { - await setBool('login_by_qr_code', value); - } - - Future getLoginUser() async { - final json = getString('login_user'); - if (json == null) { - return null; - } - try { - final map = await compute(jsonDecode, json); - return User.fromJson(map); - } catch (error, stacktrace) { - debugPrint('getLoginUser error: $error\n$stacktrace'); - return null; - } - } - - Future setLoginUser(User? user) async { - if (user == null) { - await remove('login_user'); - } else { - await setString('login_user', jsonEncode(user.toJson())); - } - } -} diff --git a/lib/providers/search_history_provider.dart b/lib/providers/search_history_provider.dart deleted file mode 100644 index 8003d3c6..00000000 --- a/lib/providers/search_history_provider.dart +++ /dev/null @@ -1,47 +0,0 @@ -import 'dart:async'; - -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -const String _kKeyHistory = 'key_search_history'; - -final searchHistoryProvider = - StateNotifierProvider>( - (ref) => SearchHistoryNotifier(), -); - -class SearchHistoryNotifier extends StateNotifier> { - SearchHistoryNotifier() : super([]) { - scheduleMicrotask(() async { - try { - final preferences = await SharedPreferences.getInstance(); - state = preferences.getStringList(_kKeyHistory) ?? []; - } finally { - _initCompleter.complete(); - } - }); - } - - final _initCompleter = Completer(); - - Future clearSearchHistory() async { - await _initCompleter.future; - state = const []; - final preference = await SharedPreferences.getInstance(); - await preference.remove(_kKeyHistory); - } - - Future insertSearchHistory(String query) async { - await _initCompleter.future; - final history = state.toList(); - history.remove(query); - history.insert(0, query); - while (history.length > 10) { - history.removeLast(); - } - state = history; - - final preference = await SharedPreferences.getInstance(); - await preference.setStringList(_kKeyHistory, state); - } -} diff --git a/lib/providers/settings_provider.dart b/lib/providers/settings_provider.dart deleted file mode 100644 index 5a2904ff..00000000 --- a/lib/providers/settings_provider.dart +++ /dev/null @@ -1,94 +0,0 @@ -import 'package:equatable/equatable.dart'; -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -import 'preference_provider.dart'; - -const String _prefix = 'quiet:settings:'; - -const String _keyThemeMode = '$_prefix:themeMode'; - -const String _keyCopyright = '$_prefix:copyright'; - -const String _keySkipWelcomePage = '$_prefix:skipWelcomePage'; - -final settingStateProvider = StateNotifierProvider( - (ref) { - return Settings(ref.read(sharedPreferenceProvider)); - }, -); - -class SettingState with EquatableMixin { - const SettingState({ - required this.themeMode, - required this.skipWelcomePage, - required this.copyright, - required this.skipAccompaniment, - }); - - factory SettingState.fromPreference(SharedPreferences preference) { - final mode = preference.getInt(_keyThemeMode) ?? 0; - assert(mode >= 0 && mode < ThemeMode.values.length, 'invalid theme mode'); - return SettingState( - themeMode: ThemeMode.values[mode.clamp(0, ThemeMode.values.length - 1)], - skipWelcomePage: preference.getBool(_keySkipWelcomePage) ?? false, - copyright: preference.getBool(_keyCopyright) ?? false, - skipAccompaniment: - preference.getBool('$_prefix:skipAccompaniment') ?? false, - ); - } - - final ThemeMode themeMode; - final bool skipWelcomePage; - final bool copyright; - final bool skipAccompaniment; - - @override - List get props => [ - themeMode, - skipWelcomePage, - copyright, - skipAccompaniment, - ]; - - SettingState copyWith({ - ThemeMode? themeMode, - bool? skipWelcomePage, - bool? copyright, - bool? skipAccompaniment, - }) => - SettingState( - themeMode: themeMode ?? this.themeMode, - skipWelcomePage: skipWelcomePage ?? this.skipWelcomePage, - copyright: copyright ?? this.copyright, - skipAccompaniment: skipAccompaniment ?? this.skipAccompaniment, - ); -} - -class Settings extends StateNotifier { - Settings(this._preferences) - : super(SettingState.fromPreference(_preferences)); - - final SharedPreferences _preferences; - - void setThemeMode(ThemeMode themeMode) { - _preferences.setInt(_keyThemeMode, themeMode.index); - state = state.copyWith(themeMode: themeMode); - } - - void setShowCopyrightOverlay({required bool show}) { - _preferences.setBool(_keyCopyright, show); - state = state.copyWith(copyright: show); - } - - void setSkipWelcomePage() { - _preferences.setBool(_keySkipWelcomePage, true); - state = state.copyWith(skipWelcomePage: true); - } - - void setSkipAccompaniment({required bool skip}) { - _preferences.setBool('$_prefix:skipAccompaniment', skip); - state = state.copyWith(skipAccompaniment: skip); - } -} diff --git a/lib/providers/user_detail_provider.dart b/lib/providers/user_detail_provider.dart index 8c5291f3..e41fa709 100644 --- a/lib/providers/user_detail_provider.dart +++ b/lib/providers/user_detail_provider.dart @@ -1,26 +1,46 @@ -import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mixin_logger/mixin_logger.dart'; +import '../db/enum/key_value_group.dart'; import '../repository.dart'; +import '../utils/db/db_key_value.dart'; +import 'database_provider.dart'; + +final _userKeyValueProvider = Provider( + (ref) => BaseLazyDbKeyValue( + group: KeyValueGroup.user, + dao: ref.watch(keyValueDaoProvider), + ), +); + +extension _UserBaseLazyDbKeyValue on BaseLazyDbKeyValue { + Future getUser(int userId) async { + final json = await get>('user_detail_$userId'); + if (json == null) { + return null; + } + return User.fromJson(json); + } + + Future setUser(User user) async { + await set('user_detail_${user.userId}', user.toJson()); + } +} final userDetailProvider = StreamProvider.family( (ref, userId) async* { - final cacheKey = 'user_detail_$userId'; - + final keyValue = ref.watch(_userKeyValueProvider); try { - final cache = await neteaseLocalData.get>(cacheKey); + final cache = await keyValue.getUser(userId); if (cache != null) { - yield User.fromJson(cache); + yield cache; } } catch (error, stack) { - debugPrint('$error, $stack'); - // clear cache if error occurs - neteaseLocalData[cacheKey] = null; + e('$error, $stack'); } - final result = await neteaseRepository!.getUserDetail(userId); final user = await result.asFuture; yield user; - neteaseLocalData[cacheKey] = user.toJson(); + await keyValue.setUser(user); }, ); diff --git a/lib/repository.dart b/lib/repository.dart index 319cd789..4ad1be08 100644 --- a/lib/repository.dart +++ b/lib/repository.dart @@ -7,7 +7,6 @@ export 'repository/data/playlist_detail.dart'; export 'repository/data/recommended_playlist.dart'; export 'repository/data/track.dart'; export 'repository/data/user.dart'; -export 'repository/local_cache_data.dart'; export 'repository/netease.dart'; export 'repository/network_repository.dart'; diff --git a/lib/repository/data/playlist_detail.dart b/lib/repository/data/playlist_detail.dart index a51e725b..049b8ced 100644 --- a/lib/repository/data/playlist_detail.dart +++ b/lib/repository/data/playlist_detail.dart @@ -1,14 +1,12 @@ import 'package:equatable/equatable.dart'; -import 'package:hive/hive.dart'; import 'package:json_annotation/json_annotation.dart'; -import 'track.dart'; +import 'track.dart'; import 'user.dart'; part 'playlist_detail.g.dart'; @JsonSerializable() -@HiveType(typeId: 1) class PlaylistDetail with EquatableMixin { PlaylistDetail({ required this.id, @@ -26,59 +24,45 @@ class PlaylistDetail with EquatableMixin { required this.commentCount, required this.trackIds, required this.createTime, - required this.isFavorite, + required this.isMyFavorite, }); factory PlaylistDetail.fromJson(Map json) => _$PlaylistDetailFromJson(json); - @HiveField(0) final int id; - @HiveField(1) final List tracks; - @HiveField(2) final User creator; - @HiveField(3) + int get creatorUserId => creator.userId; + final String coverUrl; - @HiveField(4) final int trackCount; - @HiveField(5) final bool subscribed; - @HiveField(6) final int subscribedCount; - @HiveField(7) final int shareCount; - @HiveField(8) final int playCount; - @HiveField(9) final int trackUpdateTime; - @HiveField(10) final String name; - @HiveField(11) final String description; - @HiveField(12) final int commentCount; - @HiveField(13) final List trackIds; - @HiveField(14) final DateTime createTime; - @HiveField(15) - final bool isFavorite; + final bool isMyFavorite; @override List get props => [ @@ -97,7 +81,7 @@ class PlaylistDetail with EquatableMixin { commentCount, trackIds, createTime, - isFavorite, + isMyFavorite, ]; Map toJson() => _$PlaylistDetailToJson(this); @@ -135,7 +119,7 @@ class PlaylistDetail with EquatableMixin { commentCount: commentCount ?? this.commentCount, trackIds: trackIds ?? this.trackIds, createTime: createTime ?? this.createTime, - isFavorite: isFavorite ?? this.isFavorite, + isMyFavorite: isFavorite ?? isMyFavorite, ); } } diff --git a/lib/repository/data/playlist_detail.g.dart b/lib/repository/data/playlist_detail.g.dart index 4bf7354c..dfe74ac1 100644 --- a/lib/repository/data/playlist_detail.g.dart +++ b/lib/repository/data/playlist_detail.g.dart @@ -2,89 +2,6 @@ part of 'playlist_detail.dart'; -// ************************************************************************** -// TypeAdapterGenerator -// ************************************************************************** - -class PlaylistDetailAdapter extends TypeAdapter { - @override - final int typeId = 1; - - @override - PlaylistDetail read(BinaryReader reader) { - final numOfFields = reader.readByte(); - final fields = { - for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), - }; - return PlaylistDetail( - id: fields[0] as int, - tracks: (fields[1] as List).cast(), - creator: fields[2] as User, - coverUrl: fields[3] as String, - trackCount: fields[4] as int, - subscribed: fields[5] as bool, - subscribedCount: fields[6] as int, - shareCount: fields[7] as int, - playCount: fields[8] as int, - trackUpdateTime: fields[9] as int, - name: fields[10] as String, - description: fields[11] as String, - commentCount: fields[12] as int, - trackIds: (fields[13] as List).cast(), - createTime: fields[14] as DateTime, - isFavorite: fields[15] as bool, - ); - } - - @override - void write(BinaryWriter writer, PlaylistDetail obj) { - writer - ..writeByte(16) - ..writeByte(0) - ..write(obj.id) - ..writeByte(1) - ..write(obj.tracks) - ..writeByte(2) - ..write(obj.creator) - ..writeByte(3) - ..write(obj.coverUrl) - ..writeByte(4) - ..write(obj.trackCount) - ..writeByte(5) - ..write(obj.subscribed) - ..writeByte(6) - ..write(obj.subscribedCount) - ..writeByte(7) - ..write(obj.shareCount) - ..writeByte(8) - ..write(obj.playCount) - ..writeByte(9) - ..write(obj.trackUpdateTime) - ..writeByte(10) - ..write(obj.name) - ..writeByte(11) - ..write(obj.description) - ..writeByte(12) - ..write(obj.commentCount) - ..writeByte(13) - ..write(obj.trackIds) - ..writeByte(14) - ..write(obj.createTime) - ..writeByte(15) - ..write(obj.isFavorite); - } - - @override - int get hashCode => typeId.hashCode; - - @override - bool operator ==(Object other) => - identical(this, other) || - other is PlaylistDetailAdapter && - runtimeType == other.runtimeType && - typeId == other.typeId; -} - // ************************************************************************** // JsonSerializableGenerator // ************************************************************************** @@ -108,7 +25,7 @@ PlaylistDetail _$PlaylistDetailFromJson(Map json) => PlaylistDetail( trackIds: (json['trackIds'] as List).map((e) => e as int).toList(), createTime: DateTime.parse(json['createTime'] as String), - isFavorite: json['isFavorite'] as bool, + isMyFavorite: json['isMyFavorite'] as bool, ); Map _$PlaylistDetailToJson(PlaylistDetail instance) => @@ -128,5 +45,5 @@ Map _$PlaylistDetailToJson(PlaylistDetail instance) => 'commentCount': instance.commentCount, 'trackIds': instance.trackIds, 'createTime': instance.createTime.toIso8601String(), - 'isFavorite': instance.isFavorite, + 'isMyFavorite': instance.isMyFavorite, }; diff --git a/lib/repository/data/track.dart b/lib/repository/data/track.dart index 3e484a16..015b4e04 100644 --- a/lib/repository/data/track.dart +++ b/lib/repository/data/track.dart @@ -1,27 +1,19 @@ import 'package:equatable/equatable.dart'; -import 'package:hive/hive.dart'; import 'package:json_annotation/json_annotation.dart'; part 'track.g.dart'; typedef Music = Track; -@HiveType(typeId: 3) enum TrackType { - @HiveField(0) free, - @HiveField(1) payAlbum, - @HiveField(2) vip, - @HiveField(3) cloud, - @HiveField(4) noCopyright, } @JsonSerializable() -@HiveType(typeId: 2) class Track with EquatableMixin { Track({ required this.id, @@ -37,31 +29,22 @@ class Track with EquatableMixin { factory Track.fromJson(Map json) => _$TrackFromJson(json); - @HiveField(0) final int id; - @HiveField(1) final String? uri; - @HiveField(2) final String name; - @HiveField(3) final List artists; - @HiveField(4) final AlbumMini? album; - @HiveField(5) final String? imageUrl; - @HiveField(6) final Duration duration; - @HiveField(7) final TrackType type; - @HiveField(8, defaultValue: false) final bool isRecommend; String get displaySubtitle { @@ -90,7 +73,6 @@ class Track with EquatableMixin { } @JsonSerializable() -@HiveType(typeId: 4) class ArtistMini with EquatableMixin { ArtistMini({ required this.id, @@ -102,13 +84,10 @@ class ArtistMini with EquatableMixin { _$ArtistMiniFromJson(json); @JsonKey(name: 'id') - @HiveField(0) final int id; @JsonKey(name: 'name') - @HiveField(1) final String name; @JsonKey(name: 'imageUrl') - @HiveField(2) final String? imageUrl; @override @@ -118,7 +97,6 @@ class ArtistMini with EquatableMixin { } @JsonSerializable() -@HiveType(typeId: 5) class AlbumMini with EquatableMixin { AlbumMini({ required this.id, @@ -130,15 +108,12 @@ class AlbumMini with EquatableMixin { _$AlbumMiniFromJson(json); @JsonKey(name: 'id') - @HiveField(0) final int id; @JsonKey(name: 'picUrl') - @HiveField(1) final String? picUri; @JsonKey(name: 'name') - @HiveField(2) final String name; @override diff --git a/lib/repository/data/track.g.dart b/lib/repository/data/track.g.dart index a303eedc..7b38413c 100644 --- a/lib/repository/data/track.g.dart +++ b/lib/repository/data/track.g.dart @@ -2,202 +2,6 @@ part of 'track.dart'; -// ************************************************************************** -// TypeAdapterGenerator -// ************************************************************************** - -class TrackAdapter extends TypeAdapter { - @override - final int typeId = 2; - - @override - Track read(BinaryReader reader) { - final numOfFields = reader.readByte(); - final fields = { - for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), - }; - return Track( - id: fields[0] as int, - uri: fields[1] as String?, - name: fields[2] as String, - artists: (fields[3] as List).cast(), - album: fields[4] as AlbumMini?, - imageUrl: fields[5] as String?, - duration: fields[6] as Duration, - type: fields[7] as TrackType, - isRecommend: fields[8] == null ? false : fields[8] as bool, - ); - } - - @override - void write(BinaryWriter writer, Track obj) { - writer - ..writeByte(9) - ..writeByte(0) - ..write(obj.id) - ..writeByte(1) - ..write(obj.uri) - ..writeByte(2) - ..write(obj.name) - ..writeByte(3) - ..write(obj.artists) - ..writeByte(4) - ..write(obj.album) - ..writeByte(5) - ..write(obj.imageUrl) - ..writeByte(6) - ..write(obj.duration) - ..writeByte(7) - ..write(obj.type) - ..writeByte(8) - ..write(obj.isRecommend); - } - - @override - int get hashCode => typeId.hashCode; - - @override - bool operator ==(Object other) => - identical(this, other) || - other is TrackAdapter && - runtimeType == other.runtimeType && - typeId == other.typeId; -} - -class ArtistMiniAdapter extends TypeAdapter { - @override - final int typeId = 4; - - @override - ArtistMini read(BinaryReader reader) { - final numOfFields = reader.readByte(); - final fields = { - for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), - }; - return ArtistMini( - id: fields[0] as int, - name: fields[1] as String, - imageUrl: fields[2] as String?, - ); - } - - @override - void write(BinaryWriter writer, ArtistMini obj) { - writer - ..writeByte(3) - ..writeByte(0) - ..write(obj.id) - ..writeByte(1) - ..write(obj.name) - ..writeByte(2) - ..write(obj.imageUrl); - } - - @override - int get hashCode => typeId.hashCode; - - @override - bool operator ==(Object other) => - identical(this, other) || - other is ArtistMiniAdapter && - runtimeType == other.runtimeType && - typeId == other.typeId; -} - -class AlbumMiniAdapter extends TypeAdapter { - @override - final int typeId = 5; - - @override - AlbumMini read(BinaryReader reader) { - final numOfFields = reader.readByte(); - final fields = { - for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), - }; - return AlbumMini( - id: fields[0] as int, - picUri: fields[1] as String?, - name: fields[2] as String, - ); - } - - @override - void write(BinaryWriter writer, AlbumMini obj) { - writer - ..writeByte(3) - ..writeByte(0) - ..write(obj.id) - ..writeByte(1) - ..write(obj.picUri) - ..writeByte(2) - ..write(obj.name); - } - - @override - int get hashCode => typeId.hashCode; - - @override - bool operator ==(Object other) => - identical(this, other) || - other is AlbumMiniAdapter && - runtimeType == other.runtimeType && - typeId == other.typeId; -} - -class TrackTypeAdapter extends TypeAdapter { - @override - final int typeId = 3; - - @override - TrackType read(BinaryReader reader) { - switch (reader.readByte()) { - case 0: - return TrackType.free; - case 1: - return TrackType.payAlbum; - case 2: - return TrackType.vip; - case 3: - return TrackType.cloud; - case 4: - return TrackType.noCopyright; - default: - return TrackType.free; - } - } - - @override - void write(BinaryWriter writer, TrackType obj) { - switch (obj) { - case TrackType.free: - writer.writeByte(0); - break; - case TrackType.payAlbum: - writer.writeByte(1); - break; - case TrackType.vip: - writer.writeByte(2); - break; - case TrackType.cloud: - writer.writeByte(3); - break; - case TrackType.noCopyright: - writer.writeByte(4); - break; - } - } - - @override - int get hashCode => typeId.hashCode; - - @override - bool operator ==(Object other) => - identical(this, other) || - other is TrackTypeAdapter && - runtimeType == other.runtimeType && - typeId == other.typeId; -} - // ************************************************************************** // JsonSerializableGenerator // ************************************************************************** diff --git a/lib/repository/data/user.dart b/lib/repository/data/user.dart index 4208566e..ce4a23f8 100644 --- a/lib/repository/data/user.dart +++ b/lib/repository/data/user.dart @@ -1,11 +1,9 @@ import 'package:equatable/equatable.dart'; -import 'package:hive/hive.dart'; import 'package:json_annotation/json_annotation.dart'; part 'user.g.dart'; @JsonSerializable() -@HiveType(typeId: 7) class User with EquatableMixin { User({ required this.userId, @@ -28,52 +26,36 @@ class User with EquatableMixin { factory User.fromJson(Map json) => _$UserFromJson(json); - @HiveField(0) final int userId; - @HiveField(1) final String avatarUrl; - @HiveField(2) final String backgroundUrl; - @HiveField(3) final int vipType; - @HiveField(4) final int createTime; - @HiveField(5) final String nickname; - @HiveField(6) final bool followed; - @HiveField(7) final String description; - @HiveField(8) final String detailDescription; - @HiveField(9) final int followedUsers; - @HiveField(10) final int followers; - @HiveField(11) final int allSubscribedCount; - @HiveField(12) final int playlistBeSubscribedCount; - @HiveField(13) final int playlistCount; - @HiveField(14) final int eventCount; - @HiveField(15) final int level; @override diff --git a/lib/repository/data/user.g.dart b/lib/repository/data/user.g.dart index 52275e78..8c15bd7a 100644 --- a/lib/repository/data/user.g.dart +++ b/lib/repository/data/user.g.dart @@ -2,89 +2,6 @@ part of 'user.dart'; -// ************************************************************************** -// TypeAdapterGenerator -// ************************************************************************** - -class UserAdapter extends TypeAdapter { - @override - final int typeId = 7; - - @override - User read(BinaryReader reader) { - final numOfFields = reader.readByte(); - final fields = { - for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), - }; - return User( - userId: fields[0] as int, - avatarUrl: fields[1] as String, - backgroundUrl: fields[2] as String, - vipType: fields[3] as int, - createTime: fields[4] as int, - nickname: fields[5] as String, - followed: fields[6] as bool, - description: fields[7] as String, - detailDescription: fields[8] as String, - followedUsers: fields[9] as int, - followers: fields[10] as int, - allSubscribedCount: fields[11] as int, - playlistBeSubscribedCount: fields[12] as int, - playlistCount: fields[13] as int, - level: fields[15] as int, - eventCount: fields[14] as int, - ); - } - - @override - void write(BinaryWriter writer, User obj) { - writer - ..writeByte(16) - ..writeByte(0) - ..write(obj.userId) - ..writeByte(1) - ..write(obj.avatarUrl) - ..writeByte(2) - ..write(obj.backgroundUrl) - ..writeByte(3) - ..write(obj.vipType) - ..writeByte(4) - ..write(obj.createTime) - ..writeByte(5) - ..write(obj.nickname) - ..writeByte(6) - ..write(obj.followed) - ..writeByte(7) - ..write(obj.description) - ..writeByte(8) - ..write(obj.detailDescription) - ..writeByte(9) - ..write(obj.followedUsers) - ..writeByte(10) - ..write(obj.followers) - ..writeByte(11) - ..write(obj.allSubscribedCount) - ..writeByte(12) - ..write(obj.playlistBeSubscribedCount) - ..writeByte(13) - ..write(obj.playlistCount) - ..writeByte(14) - ..write(obj.eventCount) - ..writeByte(15) - ..write(obj.level); - } - - @override - int get hashCode => typeId.hashCode; - - @override - bool operator ==(Object other) => - identical(this, other) || - other is UserAdapter && - runtimeType == other.runtimeType && - typeId == other.typeId; -} - // ************************************************************************** // JsonSerializableGenerator // ************************************************************************** diff --git a/lib/repository/local_cache_data.dart b/lib/repository/local_cache_data.dart deleted file mode 100644 index 664d32f2..00000000 --- a/lib/repository/local_cache_data.dart +++ /dev/null @@ -1,78 +0,0 @@ -import 'dart:async'; - -import 'package:hive/hive.dart'; -import 'package:mixin_logger/mixin_logger.dart'; - -import '../utils/hive/hive_util.dart'; -import 'data/playlist_detail.dart'; -import 'data/track.dart'; - -LocalData neteaseLocalData = LocalData._(); - -const String _playHistoryKey = 'play_history'; - -class LocalData { - LocalData._(); - - final _box = Hive.openBoxSafe('local_data'); - - FutureOr operator [](dynamic key) async { - return get(key); - } - - void operator []=(dynamic key, dynamic value) { - _put(value, key); - } - - Future get(dynamic key) async { - final box = await _box; - try { - return box.get(key) as T?; - } catch (error, stackTrace) { - e('get $key error: $error\n$stackTrace'); - } - } - - Future _put(dynamic value, [dynamic key]) async { - final box = await _box; - try { - await box.put(key, value); - } catch (error, stacktrace) { - e('LocalData put error: $error\n$stacktrace'); - } - } - - Future getPlaylistDetail(int playlistId) async { - final data = await get>('playlist_detail_$playlistId'); - if (data == null) { - return null; - } - try { - return PlaylistDetail.fromJson(data); - } catch (e) { - return null; - } - } - - //TODO 添加分页加载逻辑 - Future updatePlaylistDetail(PlaylistDetail playlistDetail) { - return _put( - playlistDetail.toJson(), - 'playlist_detail_${playlistDetail.id}', - ); - } - - Future> getPlayHistory() async { - final data = await get>>(_playHistoryKey); - if (data == null) { - return const []; - } - final result = - data.cast>().map(Track.fromJson).toList(); - return result; - } - - Future updatePlayHistory(List list) { - return _put(list.map((t) => t.toJson()).toList(), _playHistoryKey); - } -} diff --git a/lib/repository/network_repository.dart b/lib/repository/network_repository.dart index a82cbc1a..b8f01427 100644 --- a/lib/repository/network_repository.dart +++ b/lib/repository/network_repository.dart @@ -9,7 +9,6 @@ import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; import '../repository.dart'; -import '../utils/cache/key_value_cache.dart'; import 'data/login_qr_key_status.dart'; import 'data/search_result.dart'; @@ -24,8 +23,7 @@ export 'package:netease_api/netease_api.dart' PlayRecordType; class NetworkRepository { - NetworkRepository(String cookiePath, this.cachePath) - : _lyricCache = _LyricCache(p.join(cachePath, 'lyrics')) { + NetworkRepository(String cookiePath, this.cachePath) { _repository = api.Repository( cookiePath, onError: (error) { @@ -54,23 +52,13 @@ class NetworkRepository { final String cachePath; - final _LyricCache _lyricCache; - final _onApiUnAuthorized = StreamController.broadcast(); Stream get onApiUnAuthorized => _onApiUnAuthorized.stream; /// Fetch lyric by track id Future lyric(int id) async { - final key = CacheKey.fromString(id.toString()); - final lyric = await _lyricCache.get(key); - if (lyric != null) { - return lyric; - } final lyricString = await _repository.lyric(id); - if (lyricString != null) { - await _lyricCache.update(key, lyricString); - } return lyricString; } @@ -537,7 +525,7 @@ extension _PlayListMapper on api.Playlist { trackUpdateTime: trackUpdateTime, trackIds: trackIds.map((e) => e.id).toList(), createTime: DateTime.fromMillisecondsSinceEpoch(createTime), - isFavorite: specialType == 5, + isMyFavorite: specialType == 5, ); } } @@ -689,36 +677,3 @@ extension _UserDetailMapper on api.UserDetail { ); } } - -class _LyricCache implements Cache { - _LyricCache(String dir) - : provider = - FileCacheProvider(dir, maxSize: 20 * 1024 * 1024 /* 20 Mb */); - - final FileCacheProvider provider; - - @override - Future get(CacheKey key) async { - final file = provider.getFile(key); - if (await file.exists()) { - provider.touchFile(file); - return file.readAsStringSync(); - } - return null; - } - - @override - Future update(CacheKey key, String? t) async { - var file = provider.getFile(key); - if (await file.exists()) { - await file.delete(); - } - file = await file.create(recursive: true); - await file.writeAsString(t!); - try { - return await file.exists(); - } finally { - provider.checkSize(); - } - } -} diff --git a/lib/utils/db/db_key_value.dart b/lib/utils/db/db_key_value.dart new file mode 100644 index 00000000..dc2dc49d --- /dev/null +++ b/lib/utils/db/db_key_value.dart @@ -0,0 +1,164 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:mixin_logger/mixin_logger.dart'; + +import '../../db/dao/key_value_dao.dart'; +import '../../db/enum/key_value_group.dart'; + +class BaseLazyDbKeyValue { + BaseLazyDbKeyValue({ + required this.group, + required this.dao, + }); + + final KeyValueGroup group; + final KeyValueDao dao; + + Stream watch(String key) => + dao.watchByKey(group, key).map(convertToType); + + Future get(String key) async { + return getWithConverter(key, convertToType); + } + + Future getWithConverter( + String key, + T? Function(String value) converter, + ) async { + final value = await dao.getByKey(group, key); + if (value == null) { + return null; + } + return converter(value); + } + + Future set(String key, T? value) async { + await dao.set(group, key, convertToString(value)); + } + + Future clear() async { + await dao.clear(group); + } + + Future?> getMap(String key) async { + final value = await dao.getByKey(group, key); + if (value == null) { + return null; + } + try { + final map = jsonDecode(value) as Map; + return map.cast(); + } catch (error, stacktrace) { + e('getMap $key error: $error, $stacktrace'); + return null; + } + } + + Future?> getList(String key) async { + final value = await dao.getByKey(group, key); + if (value == null) { + return null; + } + try { + final list = jsonDecode(value) as List; + return list.cast(); + } catch (error, stacktrace) { + e('getList $key error: $error, $stacktrace'); + return null; + } + } +} + +class BaseDbKeyValue extends ChangeNotifier { + BaseDbKeyValue({required this.group, required this.dao}) { + _loadProperties().whenComplete(_initCompleter.complete); + _subscription = dao.watchTableHasChanged(group).listen((event) { + _loadProperties(); + }); + } + + final KeyValueGroup group; + final KeyValueDao dao; + + final Map _data = {}; + + final Completer _initCompleter = Completer(); + StreamSubscription? _subscription; + + Future get initialized => _initCompleter.future; + + Future _loadProperties() async { + final properties = await dao.getAll(group); + _data + ..clear() + ..addAll(properties); + notifyListeners(); + } + + T? get(String key) => convertToType(_data[key]); + + Future set(String key, T? value) { + final ret = convertToString(value); + if (ret == null) { + _data.remove(key); + } else { + _data[key] = ret; + } + return dao.set(group, key, ret); + } + + Future clear() { + _data.clear(); + return dao.clear(group); + } + + @override + void dispose() { + super.dispose(); + _subscription?.cancel(); + } +} + +String? convertToString(dynamic value) { + if (value == null) { + return null; + } + if (value is String) { + return value; + } + return jsonEncode(value); +} + +T? convertToType(String? value) { + if (value == null) { + return null; + } + try { + switch (T) { + case const (String): + return value as T; + case const (int): + return int.parse(value) as T; + case const (double): + return double.parse(value) as T; + case const (bool): + return (value == 'true') as T; + case const (Map): + case const (Map): + return jsonDecode(value) as T; + case const (List): + return jsonDecode(value) as T; + case const (List>): + return (jsonDecode(value) as List).cast>() as T; + case const (dynamic): + return value as T; + default: + throw ArgumentError('unsupported type $T'); + } + } catch (error, stacktrace) { + e('failed to convert $value to type $T : $error, \n$stacktrace'); + return null; + } +} diff --git a/lib/utils/hive/duration_adapter.dart b/lib/utils/hive/duration_adapter.dart deleted file mode 100644 index c030f306..00000000 --- a/lib/utils/hive/duration_adapter.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:hive/hive.dart'; - -class DurationAdapter extends TypeAdapter { - @override - final typeId = 6; - - @override - Duration read(BinaryReader reader) { - return Duration(milliseconds: reader.readInt()); - } - - @override - void write(BinaryWriter writer, Duration obj) { - writer.writeInt(obj.inMilliseconds); - } -} diff --git a/lib/utils/hive/hive_util.dart b/lib/utils/hive/hive_util.dart deleted file mode 100644 index 038c613e..00000000 --- a/lib/utils/hive/hive_util.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:hive/hive.dart'; - -extension HiveExt on HiveInterface { - Future> openBoxSafe(String name) async { - try { - return await openBox( - name, - compactionStrategy: (entries, deleted) => false, - ); - } catch (error) { - await Hive.deleteBoxFromDisk(name); - return openBox(name); - } - } -} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 88930c4e..c17da1ea 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -9,7 +9,6 @@ import desktop_drop import lychee_player import path_provider_foundation import screen_retriever -import shared_preferences_foundation import sqlite3_flutter_libs import url_launcher_macos import window_manager @@ -19,7 +18,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { LycheePlayerPlugin.register(with: registry.registrar(forPlugin: "LycheePlayerPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin")) - SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) diff --git a/macos/Podfile.lock b/macos/Podfile.lock index e728050a..98bcc616 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -13,9 +13,6 @@ PODS: - FlutterMacOS - screen_retriever (0.0.1): - FlutterMacOS - - shared_preferences_foundation (0.0.1): - - Flutter - - FlutterMacOS - sqlite3 (3.44.0): - sqlite3/common (= 3.44.0) - sqlite3/common (3.44.0) @@ -43,7 +40,6 @@ DEPENDENCIES: - mixin_logger (from `Flutter/ephemeral/.symlinks/plugins/mixin_logger/macos`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - screen_retriever (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos`) - - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) - sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/macos`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) - window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`) @@ -66,8 +62,6 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin screen_retriever: :path: Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos - shared_preferences_foundation: - :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin sqlite3_flutter_libs: :path: Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/macos url_launcher_macos: @@ -83,7 +77,6 @@ SPEC CHECKSUMS: mixin_logger: 011219cd258a9229c11e97e1b637e1c223330462 path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38 - shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 sqlite3: 6e2d4a4879854d0ec86b476bf3c3e30870bac273 sqlite3_flutter_libs: a25f3a0f522fdcd8fef6a4a50a3d681dd43d8dea url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95 diff --git a/netease_api/lib/src/repository.dart b/netease_api/lib/src/repository.dart index f7409c4a..65f4a8cd 100644 --- a/netease_api/lib/src/repository.dart +++ b/netease_api/lib/src/repository.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:convert'; import 'dart:io'; import 'package:async/async.dart' show Result, ErrorResult; @@ -567,8 +566,8 @@ class Repository { } assert( () { - debugPrint('api request: $path $param'); - debugPrint('api response: ${result.status} ${jsonEncode(result.body)}'); + // debugPrint('api request: $path $param'); + // debugPrint('api response: ${result.status} ${jsonEncode(result.body)}'); return true; }(), ); diff --git a/pubspec.lock b/pubspec.lock index 5f79f320..54e4ccd2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -181,10 +181,10 @@ packages: dependency: "direct main" description: name: collection - sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.2" + version: "1.18.0" convert: dependency: transitive description: @@ -342,10 +342,10 @@ packages: dependency: "direct main" description: name: fluentui_system_icons - sha256: "597e1411b2506ccd879bc477bd5e8dc13bddade4c36410e136d4f0152c92f48f" + sha256: "3148313229de2f96c718c12ebaa8496ab5c84574133db728443e4286ac4654ef" url: "https://pub.dev" source: hosted - version: "1.1.221" + version: "1.1.222" flutter: dependency: "direct main" description: flutter @@ -376,10 +376,10 @@ packages: dependency: "direct main" description: name: flutter_riverpod - sha256: "305203d1578f6857675f9730568548b03900ce53afd319f4aa9d2fa943334dbe" + sha256: "49b55e8a467229eedb801bd0864baa7f891c39cc00f790189ba8479e9ad0fa06" url: "https://pub.dev" source: hosted - version: "2.4.5" + version: "2.4.6" flutter_test: dependency: "direct dev" description: flutter @@ -423,21 +423,13 @@ packages: source: hosted version: "0.3.1" hive: - dependency: "direct main" + dependency: transitive description: name: hive sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" url: "https://pub.dev" source: hosted version: "2.2.3" - hive_flutter: - dependency: "direct main" - description: - name: hive_flutter - sha256: dca1da446b1d808a51689fb5d0c6c9510c0a2ba01e22805d492c73b68e33eecc - url: "https://pub.dev" - source: hosted - version: "1.1.0" hive_generator: dependency: "direct dev" description: @@ -450,10 +442,10 @@ packages: dependency: "direct main" description: name: hooks_riverpod - sha256: "2827136ecc0c2abffc3a58e575db6d5b84d159977fa1edc223c97bf566aa8c73" + sha256: "4fee3c75360e353128149df23ed93414477082bb130f9e23723b1a79a54bc6d0" url: "https://pub.dev" source: hosted - version: "2.4.5" + version: "2.4.6" http_multi_server: dependency: transitive description: @@ -571,10 +563,10 @@ packages: dependency: "direct main" description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" mime: dependency: transitive description: @@ -716,10 +708,10 @@ packages: dependency: transitive description: name: plugin_platform_interface - sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d + sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8 url: "https://pub.dev" source: hosted - version: "2.1.6" + version: "2.1.7" pointycastle: dependency: transitive description: @@ -780,10 +772,10 @@ packages: dependency: "direct main" description: name: riverpod - sha256: "2e84315036e64c59affaff7443dea51247bc2fe704461a32f26a27986fb63d55" + sha256: "01fa385aa5d6db42fd602d8c400c28ae1c83d1fd6fbae1cbf0f4c78bac58d4b2" url: "https://pub.dev" source: hosted - version: "2.4.5" + version: "2.4.6" scoped_model: dependency: "direct main" description: @@ -808,62 +800,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.3.8" - shared_preferences: - dependency: "direct main" - description: - name: shared_preferences - sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" - url: "https://pub.dev" - source: hosted - version: "2.2.2" - shared_preferences_android: - dependency: transitive - description: - name: shared_preferences_android - sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06" - url: "https://pub.dev" - source: hosted - version: "2.2.1" - shared_preferences_foundation: - dependency: transitive - description: - name: shared_preferences_foundation - sha256: "7bf53a9f2d007329ee6f3df7268fd498f8373602f943c975598bbb34649b62a7" - url: "https://pub.dev" - source: hosted - version: "2.3.4" - shared_preferences_linux: - dependency: transitive - description: - name: shared_preferences_linux - sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa" - url: "https://pub.dev" - source: hosted - version: "2.3.2" - shared_preferences_platform_interface: - dependency: transitive - description: - name: shared_preferences_platform_interface - sha256: d4ec5fc9ebb2f2e056c617112aa75dcf92fc2e4faaf2ae999caa297473f75d8a - url: "https://pub.dev" - source: hosted - version: "2.3.1" - shared_preferences_web: - dependency: transitive - description: - name: shared_preferences_web - sha256: d762709c2bbe80626ecc819143013cc820fa49ca5e363620ee20a8b15a3e3daf - url: "https://pub.dev" - source: hosted - version: "2.2.1" - shared_preferences_windows: - dependency: transitive - description: - name: shared_preferences_windows - sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59" - url: "https://pub.dev" - source: hosted - version: "2.3.2" shelf: dependency: transitive description: @@ -937,10 +873,10 @@ packages: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" state_notifier: dependency: transitive description: @@ -953,10 +889,10 @@ packages: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" stream_transform: dependency: "direct main" description: @@ -1001,10 +937,10 @@ packages: dependency: transitive description: name: test_api - sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.6.1" timing: dependency: transitive description: @@ -1114,10 +1050,10 @@ packages: dependency: transitive description: name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 url: "https://pub.dev" source: hosted - version: "0.1.4-beta" + version: "0.3.0" web_socket_channel: dependency: transitive description: @@ -1130,10 +1066,10 @@ packages: dependency: transitive description: name: win32 - sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3" + sha256: "7c99c0e1e2fa190b48d25c81ca5e42036d5cac81430ef249027d97b0935c553f" url: "https://pub.dev" source: hosted - version: "5.0.9" + version: "5.1.0" window_manager: dependency: "direct main" description: @@ -1167,5 +1103,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.1.0 <4.0.0" - flutter: ">=3.13.0" + dart: ">=3.2.0 <4.0.0" + flutter: ">=3.16.0" diff --git a/pubspec.yaml b/pubspec.yaml index 75ef082a..225c259a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,8 +5,8 @@ version: 0.4.0 publish_to: none environment: - sdk: '>=3.0.0 <4.0.0' - flutter: ^3.10.0 + sdk: '>=3.2.0 <4.0.0' + flutter: ^3.16.0 dependencies: flutter: @@ -16,7 +16,6 @@ dependencies: cupertino_icons: ^1.0.6 json_annotation: ^4.3.0 path_provider: ^2.1.1 - shared_preferences: ^2.2.1 url_launcher: ^6.1.14 intl: ^0.18.0 scoped_model: ^2.0.0 @@ -31,8 +30,6 @@ dependencies: url: https://github.com/boyan01/flutter-music-player.git ref: f7a97ae6b892c133c63a10e5f8879a611520d3c7 scrollable_positioned_list: ^0.3.5 - hive: ^2.2.3 - hive_flutter: ^1.1.0 flutter_hooks: ^0.20.3 equatable: ^2.0.3 netease_api: diff --git a/test/navigation_target_test.dart b/test/navigation_target_test.dart index d0012f5a..bc5d620d 100644 --- a/test/navigation_target_test.dart +++ b/test/navigation_target_test.dart @@ -1,6 +1,4 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:flutter_test/flutter_test.dart'; - import 'package:quiet/navigation/common/navigation_target.dart'; void main() { diff --git a/test/utils/db/db_key_value_test.dart b/test/utils/db/db_key_value_test.dart new file mode 100644 index 00000000..f13989c2 --- /dev/null +++ b/test/utils/db/db_key_value_test.dart @@ -0,0 +1,79 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:quiet/utils/db/db_key_value.dart'; + +void main() { + test('convert to type', () { + expect(convertToType('123'), 123); + expect(convertToType>('{}'), {}); + expect(convertToType('{}'), {}); + expect(convertToType>('{"a":1}'), {'a': 1}); + expect(convertToType>>('[{"a":1}]'), [ + {'a': 1}, + ]); + expect(convertToType>('[{"a":1}]'), [ + {'a': 1}, + ]); + }); + + test('convert to string', () { + expect(convertToString(123), '123'); + expect(convertToString({}), '{}'); + expect(convertToString({}), '{}'); + expect(convertToString({'a': 1}), '{"a":1}'); + expect( + convertToString([ + {'a': 1}, + ]), + '[{"a":1}]', + ); + expect( + convertToString([ + {'a': 1}, + ]), + '[{"a":1}]', + ); + expect(convertToString('abc'), 'abc'); + }); + + test('test key value', () async { + expect( + convertToType(convertToString(true)), + true, + ); + expect( + convertToType(convertToString(false)), + false, + ); + expect( + convertToType(convertToString(null)), + null, + ); + expect( + convertToType(convertToString('true')), + 'true', + ); + expect(convertToType(convertToString(1)), 1); + expect( + convertToType(convertToString(1.0)), + 1.0, + ); + expect( + convertToType>( + convertToString({'a': 1}), + ), + {'a': 1}, + ); + expect( + convertToType>>( + convertToString( + [ + {'a': 1}, + ], + ), + ), + [ + {'a': 1}, + ], + ); + }); +} diff --git a/test/widget_test_context.dart b/test/widget_test_context.dart deleted file mode 100644 index 70ac14d0..00000000 --- a/test/widget_test_context.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:music_player/music_player.dart'; -import 'package:quiet/component/player/player.dart'; - -///配置一些通用用于测试的Widget上下文 -class TestContext extends StatefulWidget { - const TestContext({Key? key, this.child}) : super(key: key); - final Widget? child; - - @override - _TestContextState createState() => _TestContextState(); -} - -class _TestContextState extends State { - @override - void initState() { - super.initState(); - } - - @override - Widget build(BuildContext context) { - return MaterialApp( - home: Material( - child: ScopedModel( - model: _TestQuietModel(), - child: DisableBottomController(child: widget.child), - )), - ); - } -} - -class _TestQuietModel extends Model implements QuietModel { - //TODO test - @override - MusicPlayer player = MusicPlayer(); -}