diff --git a/lib/bloc/deeplink_bloc.dart b/lib/bloc/deeplink_bloc.dart index 5870c51..50b2b88 100644 --- a/lib/bloc/deeplink_bloc.dart +++ b/lib/bloc/deeplink_bloc.dart @@ -4,7 +4,6 @@ import 'package:flutter/foundation.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:uni_links/uni_links.dart'; -import 'package:universal_io/io.dart'; import '../common/platform.dart'; import '../model/common_model.dart'; @@ -60,7 +59,7 @@ class DeepLinkBloc extends HydratedBloc { process(initialUri); _sub = uriLinkStream.listen(process); while (_sub != null) { - sleep(const Duration(seconds: 1)); + await Future.delayed(const Duration(seconds: 1)); } } catch (e) { debugPrint('DeepLinkBloc: $e'); diff --git a/lib/bloc/yesod/yesod_bloc.dart b/lib/bloc/yesod/yesod_bloc.dart index 334aa7b..b7d2a6a 100644 --- a/lib/bloc/yesod/yesod_bloc.dart +++ b/lib/bloc/yesod/yesod_bloc.dart @@ -19,9 +19,9 @@ part 'yesod_state.dart'; class YesodBloc extends Bloc { final ApiHelper _api; - final YesodRepo _repo; + final YesodRepo repo; - YesodBloc(this._api, this._repo) : super(YesodState()) { + YesodBloc(this._api, this.repo) : super(YesodState()) { on((event, emit) async { if (state.feedConfigs == null) { add(YesodConfigLoadEvent()); @@ -176,21 +176,11 @@ class YesodBloc extends Bloc { )); }, transformer: droppable()); - on((event, emit) async { - emit(state.copyWith( - feedConfigEditIndex: event.index, - )); - }, transformer: restartable()); - on((event, emit) async { emit(YesodConfigEditState( state, EventStatus.processing, )); - final index = state.feedConfigEditIndex; - if (index == null) { - return; - } final resp = await _api.doRequest( (client) => client.updateFeedConfig, UpdateFeedConfigRequest( @@ -206,10 +196,7 @@ class YesodBloc extends Bloc { return; } final configs = state.feedConfigs ?? []; - configs[index] = ListFeedConfigsResponse_FeedWithConfig( - config: event.config, - feed: configs[index].feed, - ); + add(YesodConfigLoadEvent()); emit(YesodConfigEditState( state.copyWith(feedConfigs: configs), EventStatus.success, @@ -229,6 +216,7 @@ class YesodBloc extends Bloc { : state, EventStatus.processing, )); + final listConfig = repo.getFeedItemListConfig(); final resp = await _api.doRequest( (client) => client.listFeedItems, ListFeedItemsRequest( @@ -236,8 +224,9 @@ class YesodBloc extends Bloc { pageSize: Int64(pageSize), pageNum: Int64(pageNum), ), - feedIdFilter: state.feedItemFilter?.feedIdFilter, - categoryFilter: state.feedItemFilter?.categoryFilter, + feedIdFilter: listConfig.feedIdFilter + ?.map((e) => InternalID(id: Int64.parseInt(e))), + categoryFilter: listConfig.categoryFilter, ), ); if (resp.status != ApiStatus.success) { @@ -260,23 +249,12 @@ class YesodBloc extends Bloc { )); }, transformer: droppable()); - on((event, emit) async { - emit(state.copyWith(feedItemFilter: event.filter)); - add(YesodFeedItemDigestsLoadEvent(1, refresh: true)); - }, transformer: restartable()); - on((event, emit) async { emit(YesodFeedItemLoadState( state, EventStatus.processing, )); - if (_repo.existFeedItem(event.id)) { - state.feedItems ??= {}; - final item = _repo.getFeedItem(event.id); - if (item != null) { - state.feedItems![event.id] = item; - } - } else { + if (!repo.existFeedItem(event.id)) { final resp = await _api.doRequest( (client) => client.getFeedItem, GetFeedItemRequest( @@ -292,15 +270,13 @@ class YesodBloc extends Bloc { return; } else { final item = resp.getData().item; - await _repo.setFeedItem(event.id, item); - state.feedItems ??= {}; - state.feedItems![event.id] = item; - emit(YesodFeedItemLoadState( - state, - EventStatus.success, - )); + await repo.setFeedItem(event.id, item); } } + emit(YesodFeedItemLoadState( + state, + EventStatus.success, + )); }, transformer: droppable()); on((event, emit) async { @@ -326,10 +302,6 @@ class YesodBloc extends Bloc { )); }, transformer: droppable()); - on((event, emit) async { - emit(state.copyWith(feedListType: event.type)); - }); - on((event, emit) async { await _api.doRequest( (client) => client.readFeedItem, @@ -339,10 +311,10 @@ class YesodBloc extends Bloc { } int cacheSize() { - return _repo.cacheSize(); + return repo.cacheSize(); } Future clearCache() async { - await _repo.clearCache(); + await repo.clearCache(); } } diff --git a/lib/bloc/yesod/yesod_event.dart b/lib/bloc/yesod/yesod_event.dart index 75c77fe..61f5d08 100644 --- a/lib/bloc/yesod/yesod_event.dart +++ b/lib/bloc/yesod/yesod_event.dart @@ -19,12 +19,6 @@ final class YesodConfigAddEvent extends YesodEvent { YesodConfigAddEvent(this.config); } -final class YesodSetConfigEditIndexEvent extends YesodEvent { - final int index; - - YesodSetConfigEditIndexEvent(this.index); -} - final class YesodConfigEditEvent extends YesodEvent { final FeedConfig config; @@ -38,12 +32,6 @@ final class YesodFeedItemDigestsLoadEvent extends YesodEvent { YesodFeedItemDigestsLoadEvent(this.pageNum, {this.refresh}); } -final class YesodFeedItemDigestsSetFilterEvent extends YesodEvent { - final YesodFeedItemFilter filter; - - YesodFeedItemDigestsSetFilterEvent(this.filter); -} - final class YesodFeedItemLoadEvent extends YesodEvent { final InternalID id; @@ -52,12 +40,6 @@ final class YesodFeedItemLoadEvent extends YesodEvent { final class YesodFeedCategoriesLoadEvent extends YesodEvent {} -final class YesodFeedListTypeSetEvent extends YesodEvent { - final FeedListType type; - - YesodFeedListTypeSetEvent(this.type); -} - final class YesodFeedItemReadEvent extends YesodEvent { final InternalID id; diff --git a/lib/bloc/yesod/yesod_state.dart b/lib/bloc/yesod/yesod_state.dart index 8047d2d..49f5f53 100644 --- a/lib/bloc/yesod/yesod_state.dart +++ b/lib/bloc/yesod/yesod_state.dart @@ -5,61 +5,36 @@ class YesodState { late List? feedConfigs; late List? feedItemDigests; late List? feedCategories; - late Map? feedItems; // Data for UI - late int? feedConfigEditIndex; late RssPostItem? feedPreview; - late InternalID? selectedFeedItemID; - late YesodFeedItemFilter? feedItemFilter; - late FeedListType? feedListType; YesodState({ this.feedConfigs, - this.feedConfigEditIndex, this.feedPreview, this.feedItemDigests, - this.feedItems, - this.selectedFeedItemID, - this.feedItemFilter, this.feedCategories, - this.feedListType, }); YesodState copyWith({ List? feedConfigs, - int? feedConfigEditIndex, RssPostItem? feedPreview, List? feedItemDigests, - Map? feedItems, - InternalID? selectedFeedItemID, - YesodFeedItemFilter? feedItemFilter, List? feedCategories, - FeedListType? feedListType, }) { return YesodState( feedConfigs: feedConfigs ?? this.feedConfigs, - feedConfigEditIndex: feedConfigEditIndex ?? this.feedConfigEditIndex, feedPreview: feedPreview ?? this.feedPreview, feedItemDigests: feedItemDigests ?? this.feedItemDigests, - feedItems: feedItems ?? this.feedItems, - selectedFeedItemID: selectedFeedItemID ?? this.selectedFeedItemID, - feedItemFilter: feedItemFilter ?? this.feedItemFilter, feedCategories: feedCategories ?? this.feedCategories, - feedListType: feedListType ?? this.feedListType, ); } void _from(YesodState other) { feedConfigs = other.feedConfigs; - feedConfigEditIndex = other.feedConfigEditIndex; feedPreview = other.feedPreview; feedItemDigests = other.feedItemDigests; - feedItems = other.feedItems; - selectedFeedItemID = other.selectedFeedItemID; - feedItemFilter = other.feedItemFilter; feedCategories = other.feedCategories; - feedListType = other.feedListType; } } @@ -148,21 +123,3 @@ class YesodFeedCategoriesLoadState extends YesodState with EventStatusMixin { @override final EventStatus statusCode; } - -class YesodFeedItemFilter { - final Iterable? feedIdFilter; - final Iterable? authorIdFilter; - final Iterable? publishPlatformFilter; - final Iterable? categoryFilter; - final TimeRange? publishTimeRange; - final bool? hideRead; - - YesodFeedItemFilter({ - this.feedIdFilter, - this.authorIdFilter, - this.publishPlatformFilter, - this.categoryFilter, - this.publishTimeRange, - this.hideRead, - }); -} diff --git a/lib/init.dart b/lib/init.dart index 9411e16..f0570d4 100644 --- a/lib/init.dart +++ b/lib/init.dart @@ -62,12 +62,12 @@ Future init() async { if (kDebugMode) { Bloc.observer = SimpleBlocObserver(); } - final deviceInfo = await _genClientDeviceInfo(); - final clientSettingBloc = ClientSettingBloc(common); - final deepLinkBloc = DeepLinkBloc(initialUri); HydratedBloc.storage = await HydratedStorage.build( storageDirectory: dataPath == null ? Directory('') : Directory(dataPath), ); + final deviceInfo = await _genClientDeviceInfo(); + final clientSettingBloc = ClientSettingBloc(common); + final deepLinkBloc = DeepLinkBloc(initialUri); final mainBloc = MainBloc(api, common, clientSettingBloc, deepLinkBloc, packageInfo, deviceInfo, dataPath); diff --git a/lib/model/yesod_model.dart b/lib/model/yesod_model.dart index e1e219d..dd96a95 100644 --- a/lib/model/yesod_model.dart +++ b/lib/model/yesod_model.dart @@ -1,4 +1,24 @@ -enum FeedListType { +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'yesod_model.freezed.dart'; +part 'yesod_model.g.dart'; + +@freezed +class YesodFeedItemListConfig with _$YesodFeedItemListConfig { + const factory YesodFeedItemListConfig({ + Iterable? feedIdFilter, + Iterable? authorIdFilter, + Iterable? publishPlatformFilter, + Iterable? categoryFilter, + bool? hideRead, + FeedItemListType? listType, + }) = _YesodFeedItemListConfig; + + factory YesodFeedItemListConfig.fromJson(Map json) => + _$YesodFeedItemListConfigFromJson(json); +} + +enum FeedItemListType { table, magazine, card, diff --git a/lib/model/yesod_model.freezed.dart b/lib/model/yesod_model.freezed.dart new file mode 100644 index 0000000..9d535df --- /dev/null +++ b/lib/model/yesod_model.freezed.dart @@ -0,0 +1,275 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'yesod_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +YesodFeedItemListConfig _$YesodFeedItemListConfigFromJson( + Map json) { + return _YesodFeedItemListConfig.fromJson(json); +} + +/// @nodoc +mixin _$YesodFeedItemListConfig { + Iterable? get feedIdFilter => throw _privateConstructorUsedError; + Iterable? get authorIdFilter => throw _privateConstructorUsedError; + Iterable? get publishPlatformFilter => + throw _privateConstructorUsedError; + Iterable? get categoryFilter => throw _privateConstructorUsedError; + bool? get hideRead => throw _privateConstructorUsedError; + FeedItemListType? get listType => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $YesodFeedItemListConfigCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $YesodFeedItemListConfigCopyWith<$Res> { + factory $YesodFeedItemListConfigCopyWith(YesodFeedItemListConfig value, + $Res Function(YesodFeedItemListConfig) then) = + _$YesodFeedItemListConfigCopyWithImpl<$Res, YesodFeedItemListConfig>; + @useResult + $Res call( + {Iterable? feedIdFilter, + Iterable? authorIdFilter, + Iterable? publishPlatformFilter, + Iterable? categoryFilter, + bool? hideRead, + FeedItemListType? listType}); +} + +/// @nodoc +class _$YesodFeedItemListConfigCopyWithImpl<$Res, + $Val extends YesodFeedItemListConfig> + implements $YesodFeedItemListConfigCopyWith<$Res> { + _$YesodFeedItemListConfigCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? feedIdFilter = freezed, + Object? authorIdFilter = freezed, + Object? publishPlatformFilter = freezed, + Object? categoryFilter = freezed, + Object? hideRead = freezed, + Object? listType = freezed, + }) { + return _then(_value.copyWith( + feedIdFilter: freezed == feedIdFilter + ? _value.feedIdFilter + : feedIdFilter // ignore: cast_nullable_to_non_nullable + as Iterable?, + authorIdFilter: freezed == authorIdFilter + ? _value.authorIdFilter + : authorIdFilter // ignore: cast_nullable_to_non_nullable + as Iterable?, + publishPlatformFilter: freezed == publishPlatformFilter + ? _value.publishPlatformFilter + : publishPlatformFilter // ignore: cast_nullable_to_non_nullable + as Iterable?, + categoryFilter: freezed == categoryFilter + ? _value.categoryFilter + : categoryFilter // ignore: cast_nullable_to_non_nullable + as Iterable?, + hideRead: freezed == hideRead + ? _value.hideRead + : hideRead // ignore: cast_nullable_to_non_nullable + as bool?, + listType: freezed == listType + ? _value.listType + : listType // ignore: cast_nullable_to_non_nullable + as FeedItemListType?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$YesodFeedItemListConfigImplCopyWith<$Res> + implements $YesodFeedItemListConfigCopyWith<$Res> { + factory _$$YesodFeedItemListConfigImplCopyWith( + _$YesodFeedItemListConfigImpl value, + $Res Function(_$YesodFeedItemListConfigImpl) then) = + __$$YesodFeedItemListConfigImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {Iterable? feedIdFilter, + Iterable? authorIdFilter, + Iterable? publishPlatformFilter, + Iterable? categoryFilter, + bool? hideRead, + FeedItemListType? listType}); +} + +/// @nodoc +class __$$YesodFeedItemListConfigImplCopyWithImpl<$Res> + extends _$YesodFeedItemListConfigCopyWithImpl<$Res, + _$YesodFeedItemListConfigImpl> + implements _$$YesodFeedItemListConfigImplCopyWith<$Res> { + __$$YesodFeedItemListConfigImplCopyWithImpl( + _$YesodFeedItemListConfigImpl _value, + $Res Function(_$YesodFeedItemListConfigImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? feedIdFilter = freezed, + Object? authorIdFilter = freezed, + Object? publishPlatformFilter = freezed, + Object? categoryFilter = freezed, + Object? hideRead = freezed, + Object? listType = freezed, + }) { + return _then(_$YesodFeedItemListConfigImpl( + feedIdFilter: freezed == feedIdFilter + ? _value.feedIdFilter + : feedIdFilter // ignore: cast_nullable_to_non_nullable + as Iterable?, + authorIdFilter: freezed == authorIdFilter + ? _value.authorIdFilter + : authorIdFilter // ignore: cast_nullable_to_non_nullable + as Iterable?, + publishPlatformFilter: freezed == publishPlatformFilter + ? _value.publishPlatformFilter + : publishPlatformFilter // ignore: cast_nullable_to_non_nullable + as Iterable?, + categoryFilter: freezed == categoryFilter + ? _value.categoryFilter + : categoryFilter // ignore: cast_nullable_to_non_nullable + as Iterable?, + hideRead: freezed == hideRead + ? _value.hideRead + : hideRead // ignore: cast_nullable_to_non_nullable + as bool?, + listType: freezed == listType + ? _value.listType + : listType // ignore: cast_nullable_to_non_nullable + as FeedItemListType?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$YesodFeedItemListConfigImpl implements _YesodFeedItemListConfig { + const _$YesodFeedItemListConfigImpl( + {this.feedIdFilter, + this.authorIdFilter, + this.publishPlatformFilter, + this.categoryFilter, + this.hideRead, + this.listType}); + + factory _$YesodFeedItemListConfigImpl.fromJson(Map json) => + _$$YesodFeedItemListConfigImplFromJson(json); + + @override + final Iterable? feedIdFilter; + @override + final Iterable? authorIdFilter; + @override + final Iterable? publishPlatformFilter; + @override + final Iterable? categoryFilter; + @override + final bool? hideRead; + @override + final FeedItemListType? listType; + + @override + String toString() { + return 'YesodFeedItemListConfig(feedIdFilter: $feedIdFilter, authorIdFilter: $authorIdFilter, publishPlatformFilter: $publishPlatformFilter, categoryFilter: $categoryFilter, hideRead: $hideRead, listType: $listType)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$YesodFeedItemListConfigImpl && + const DeepCollectionEquality() + .equals(other.feedIdFilter, feedIdFilter) && + const DeepCollectionEquality() + .equals(other.authorIdFilter, authorIdFilter) && + const DeepCollectionEquality() + .equals(other.publishPlatformFilter, publishPlatformFilter) && + const DeepCollectionEquality() + .equals(other.categoryFilter, categoryFilter) && + (identical(other.hideRead, hideRead) || + other.hideRead == hideRead) && + (identical(other.listType, listType) || + other.listType == listType)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(feedIdFilter), + const DeepCollectionEquality().hash(authorIdFilter), + const DeepCollectionEquality().hash(publishPlatformFilter), + const DeepCollectionEquality().hash(categoryFilter), + hideRead, + listType); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$YesodFeedItemListConfigImplCopyWith<_$YesodFeedItemListConfigImpl> + get copyWith => __$$YesodFeedItemListConfigImplCopyWithImpl< + _$YesodFeedItemListConfigImpl>(this, _$identity); + + @override + Map toJson() { + return _$$YesodFeedItemListConfigImplToJson( + this, + ); + } +} + +abstract class _YesodFeedItemListConfig implements YesodFeedItemListConfig { + const factory _YesodFeedItemListConfig( + {final Iterable? feedIdFilter, + final Iterable? authorIdFilter, + final Iterable? publishPlatformFilter, + final Iterable? categoryFilter, + final bool? hideRead, + final FeedItemListType? listType}) = _$YesodFeedItemListConfigImpl; + + factory _YesodFeedItemListConfig.fromJson(Map json) = + _$YesodFeedItemListConfigImpl.fromJson; + + @override + Iterable? get feedIdFilter; + @override + Iterable? get authorIdFilter; + @override + Iterable? get publishPlatformFilter; + @override + Iterable? get categoryFilter; + @override + bool? get hideRead; + @override + FeedItemListType? get listType; + @override + @JsonKey(ignore: true) + _$$YesodFeedItemListConfigImplCopyWith<_$YesodFeedItemListConfigImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/model/yesod_model.g.dart b/lib/model/yesod_model.g.dart new file mode 100644 index 0000000..33bffb5 --- /dev/null +++ b/lib/model/yesod_model.g.dart @@ -0,0 +1,40 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'yesod_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$YesodFeedItemListConfigImpl _$$YesodFeedItemListConfigImplFromJson( + Map json) => + _$YesodFeedItemListConfigImpl( + feedIdFilter: + (json['feedIdFilter'] as List?)?.map((e) => e as String), + authorIdFilter: + (json['authorIdFilter'] as List?)?.map((e) => e as String), + publishPlatformFilter: (json['publishPlatformFilter'] as List?) + ?.map((e) => e as String), + categoryFilter: + (json['categoryFilter'] as List?)?.map((e) => e as String), + hideRead: json['hideRead'] as bool?, + listType: + $enumDecodeNullable(_$FeedItemListTypeEnumMap, json['listType']), + ); + +Map _$$YesodFeedItemListConfigImplToJson( + _$YesodFeedItemListConfigImpl instance) => + { + 'feedIdFilter': instance.feedIdFilter?.toList(), + 'authorIdFilter': instance.authorIdFilter?.toList(), + 'publishPlatformFilter': instance.publishPlatformFilter?.toList(), + 'categoryFilter': instance.categoryFilter?.toList(), + 'hideRead': instance.hideRead, + 'listType': _$FeedItemListTypeEnumMap[instance.listType], + }; + +const _$FeedItemListTypeEnumMap = { + FeedItemListType.table: 'table', + FeedItemListType.magazine: 'magazine', + FeedItemListType.card: 'card', +}; diff --git a/lib/repo/local/yesod.dart b/lib/repo/local/yesod.dart index f5ecfb5..71f9f95 100644 --- a/lib/repo/local/yesod.dart +++ b/lib/repo/local/yesod.dart @@ -1,31 +1,75 @@ +import 'dart:convert'; + import 'package:hive/hive.dart'; import 'package:sentry_hive/sentry_hive.dart'; import 'package:tuihub_protos/librarian/v1/common.pb.dart'; import 'package:universal_io/io.dart'; +import '../../model/yesod_model.dart'; + +const _yesodCommonBoxFile = 'yesod_common'; const _feedItemCacheBoxFile = 'yesod_feed_item_cache'; +const _defaultFeedItemListConfigKey = 'listConfig:default'; + class YesodRepo { - late Box _feedItemCacheBox; + late Box _yesodCommonBox; + late LazyBox _feedItemCacheBox; - YesodRepo._init(Box box) { - _feedItemCacheBox = box; + YesodRepo._init( + Box yesodCommonBox, + LazyBox feedItemCacheBox, + ) { + _yesodCommonBox = yesodCommonBox; + _feedItemCacheBox = feedItemCacheBox; } static Future init(String? path) async { - final box = - await SentryHive.openBox(_feedItemCacheBoxFile, path: path); - return YesodRepo._init(box); + final feedItemListConfigBox = + await SentryHive.openBox(_yesodCommonBoxFile, path: path); + final feedItemCacheBox = + await SentryHive.openLazyBox(_feedItemCacheBoxFile, path: path); + return YesodRepo._init( + feedItemListConfigBox, + feedItemCacheBox, + ); + } + + Future dispose() async { + await _yesodCommonBox.close(); + await _feedItemCacheBox.close(); + } + + Future setFeedItemListConfig(YesodFeedItemListConfig data) { + return _yesodCommonBox.put( + _defaultFeedItemListConfigKey, jsonEncode(data.toJson())); + } + + YesodFeedItemListConfig getFeedItemListConfig() { + try { + final data = _yesodCommonBox.get(_defaultFeedItemListConfigKey); + if (data != null) { + return YesodFeedItemListConfig.fromJson( + jsonDecode(data) as Map); + } + } catch (e) { + // ignore + } + return const YesodFeedItemListConfig(); } Future setFeedItem(InternalID id, FeedItem data) { return _feedItemCacheBox.put(id.toString(), data.writeToJson()); } - FeedItem? getFeedItem(InternalID id) { - final data = _feedItemCacheBox.get(id.toString()); - if (data != null) { - return FeedItem.fromJson(data); + Future getFeedItem(InternalID id) async { + try { + final data = await _feedItemCacheBox.get(id.toString()); + if (data != null) { + return FeedItem.fromJson(data); + } + } catch (e) { + // ignore } return null; } diff --git a/lib/route.dart b/lib/route.dart index a51537c..cdf56d2 100644 --- a/lib/route.dart +++ b/lib/route.dart @@ -113,8 +113,8 @@ class AppRoutes { AppRoutes._('$_yesod/${_YesodFunctions.timeline}'); static const AppRoutes yesodConfig = AppRoutes._('$_yesod/${_YesodFunctions.config}'); - static AppRoutes yesodConfigEdit() => AppRoutes._( - '$yesodConfig?action=${_YesodActions.configEdit}', + static AppRoutes yesodConfigEdit(int id) => AppRoutes._( + '$yesodConfig?action=${_YesodActions.configEdit}&id=$id', isAction: true, ); static AppRoutes yesodConfigAdd() => AppRoutes._( @@ -395,12 +395,14 @@ GoRouter getRouter(MainBloc mainBloc, ApiHelper apiHelper) { _YesodFunctions.recent; var action = state.uri.queryParameters['action'] ?? _YesodActions.configAdd; + final idStr = state.uri.queryParameters['id']; + final id = idStr != null ? int.tryParse(idStr) : null; if (function == _YesodFunctions.recent) { action = _YesodActions.recentFilter; } final yesodActions = { - _YesodActions.configEdit: const YesodConfigEditPanel(), + _YesodActions.configEdit: YesodConfigEditPanel(index: id), _YesodActions.configAdd: const YesodConfigAddPanel(), _YesodActions.recentFilter: const YesodRecentSettingPanel(), }; diff --git a/lib/view/pages/yesod/yesod_config_add_panel.dart b/lib/view/pages/yesod/yesod_config_add_panel.dart index 8946932..21075d9 100644 --- a/lib/view/pages/yesod/yesod_config_add_panel.dart +++ b/lib/view/pages/yesod/yesod_config_add_panel.dart @@ -63,7 +63,7 @@ class YesodConfigAddPanel extends StatelessWidget { description: feedPreview!.description, title: feedPreview!.title ?? '', callback: () {}, - listType: FeedListType.card, + listType: FeedItemListType.card, ), Text(state is YesodConfigPreviewState && state.failed ? state.msg ?? '' diff --git a/lib/view/pages/yesod/yesod_config_edit_panel.dart b/lib/view/pages/yesod/yesod_config_edit_panel.dart index 2380286..d677c0f 100644 --- a/lib/view/pages/yesod/yesod_config_edit_panel.dart +++ b/lib/view/pages/yesod/yesod_config_edit_panel.dart @@ -12,10 +12,12 @@ import '../../form/input_formatters.dart'; import '../../specialized/right_panel_form.dart'; class YesodConfigEditPanel extends StatelessWidget { - const YesodConfigEditPanel({super.key}); + const YesodConfigEditPanel({super.key, required this.index}); + + final int? index; void close(BuildContext context) { - AppRoutes.yesodConfigEdit().pop(context); + AppRoutes.yesodConfigEdit(0).pop(context); } @override @@ -27,13 +29,10 @@ class YesodConfigEditPanel extends StatelessWidget { close(context); } }, - buildWhen: (previous, current) => - previous.feedConfigEditIndex != current.feedConfigEditIndex, builder: (context, state) { - final config = - state.feedConfigEditIndex != null && state.feedConfigs != null - ? state.feedConfigs![state.feedConfigEditIndex!].config - : FeedConfig(); + final config = index != null && state.feedConfigs != null + ? state.feedConfigs![index!].config + : FeedConfig(); var name = config.name; var feedUrl = config.feedUrl; var feedEnabled = diff --git a/lib/view/pages/yesod/yesod_config_page.dart b/lib/view/pages/yesod/yesod_config_page.dart index 4efbd24..cf447b1 100644 --- a/lib/view/pages/yesod/yesod_config_page.dart +++ b/lib/view/pages/yesod/yesod_config_page.dart @@ -31,10 +31,7 @@ class YesodConfigPage extends StatelessWidget { final item = listData.elementAt(index); void openEditPage() { - context - .read() - .add(YesodSetConfigEditIndexEvent(index)); - AppRoutes.yesodConfigEdit().go(context); + AppRoutes.yesodConfigEdit(index).go(context); } return SelectionArea( diff --git a/lib/view/pages/yesod/yesod_detail_page.dart b/lib/view/pages/yesod/yesod_detail_page.dart index 6baeaff..72b5e11 100644 --- a/lib/view/pages/yesod/yesod_detail_page.dart +++ b/lib/view/pages/yesod/yesod_detail_page.dart @@ -16,85 +16,93 @@ class YesodDetailPage extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocBuilder(builder: (context, state) { - if (state.feedItems == null || state.feedItems![itemId] == null) { - BlocProvider.of(context).add(YesodFeedItemLoadEvent(itemId)); - } - final item = state.feedItems?[itemId]; - return DecoratedBox( - decoration: BoxDecoration( - color: Theme.of(context).cardColor, - ), - child: Column( - mainAxisSize: MainAxisSize.max, - children: [ - ClipRRect( - borderRadius: SpacingHelper.defaultBorderRadius, - child: AppBar( - title: Text(item != null ? item.title : '加载中...'), - ), + return BlocBuilder(builder: (context, state) { + return FutureBuilder( + future: context.read().repo.getFeedItem(itemId), + builder: (context, snapshot) { + final item = snapshot.data; + if (item == null) { + context.read().add(YesodFeedItemLoadEvent(itemId)); + } + return DecoratedBox( + decoration: BoxDecoration( + color: Theme.of(context).cardColor, ), - if (item != null) - Expanded( - child: DynMouseScroll( - builder: (context, controller, physics) { - return SingleChildScrollView( - controller: controller, - physics: physics, - child: BootstrapContainer( - children: [ - BootstrapColumn( - xxs: 12, - sm: 10, - md: 8, - child: Container( - padding: const EdgeInsets.all(16), - alignment: Alignment.topCenter, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox( - height: 16, - ), - Row( + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + ClipRRect( + borderRadius: SpacingHelper.defaultBorderRadius, + child: AppBar( + title: Text(item != null ? item.title : '加载中...'), + ), + ), + if (item != null) + Expanded( + child: DynMouseScroll( + builder: (context, controller, physics) { + return SingleChildScrollView( + controller: controller, + physics: physics, + child: BootstrapContainer( + children: [ + BootstrapColumn( + xxs: 12, + sm: 10, + md: 8, + child: Container( + padding: const EdgeInsets.all(16), + alignment: Alignment.topCenter, + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, children: [ - const Text('作者:'), - for (final author in item.authors) - Text(author.name), + const SizedBox( + height: 16, + ), + Row( + children: [ + const Text('作者:'), + for (final author in item.authors) + Text(author.name), + ], + ), + Text( + '发布时间:${DurationHelper.recentString(item.publishedParsed.toDateTime())}'), + if (item.updatedParsed + .toDateTime() + .isAfter(item.publishedParsed + .toDateTime())) + Text( + '更新时间:${DurationHelper.recentString(item.updatedParsed.toDateTime())}'), + const SizedBox( + height: 16, + ), + HtmlWidget( + item.content.isNotEmpty + ? item.content + : item.description, + renderMode: RenderMode.column, + enableCaching: true, + ), ], ), - Text( - '发布时间:${DurationHelper.recentString(item.publishedParsed.toDateTime())}'), - if (item.updatedParsed.toDateTime().isAfter( - item.publishedParsed.toDateTime())) - Text( - '更新时间:${DurationHelper.recentString(item.updatedParsed.toDateTime())}'), - const SizedBox( - height: 16, - ), - HtmlWidget( - item.content.isNotEmpty - ? item.content - : item.description, - renderMode: RenderMode.column, - enableCaching: true, - ), - ], + ), ), - ), + ], ), - ], - ), - ); - }, - ), - ) - else - const Center( - child: CircularProgressIndicator(), - ), - ], - ), + ); + }, + ), + ) + else + const Center( + child: CircularProgressIndicator(), + ), + ], + ), + ); + }, ); }); } diff --git a/lib/view/pages/yesod/yesod_preview_card.dart b/lib/view/pages/yesod/yesod_preview_card.dart index 1f2a1f6..0fb958f 100644 --- a/lib/view/pages/yesod/yesod_preview_card.dart +++ b/lib/view/pages/yesod/yesod_preview_card.dart @@ -26,7 +26,7 @@ class YesodPreviewCard extends StatelessWidget { final String title; final String? description; final void Function() callback; - final FeedListType listType; + final FeedItemListType listType; final BorderRadius? cardBorderRadius; @override @@ -40,7 +40,8 @@ class YesodPreviewCard extends StatelessWidget { const cardPaddingH = 16.0; const imgPadding = 8; const iconSize = 18.0; - final double leftImageSize = listType == FeedListType.magazine ? 128 : 0; + final double leftImageSize = + listType == FeedItemListType.magazine ? 128 : 0; final double bottomImageSize = constraints.biggest.width < 406 ? (constraints.biggest.width - 2 * imgPadding - 2 * cardPaddingH) / 3 : 130; @@ -58,7 +59,7 @@ class YesodPreviewCard extends StatelessWidget { padding: const EdgeInsets.fromLTRB( cardPaddingH, cardPaddingV, cardPaddingH, cardPaddingV), child: Row(children: [ - if (listType == FeedListType.magazine) + if (listType == FeedItemListType.magazine) Container( width: leftImageSize, padding: const EdgeInsets.only(right: cardPaddingV), @@ -79,7 +80,7 @@ class YesodPreviewCard extends StatelessWidget { width: constraints.biggest.width - 2 * cardPaddingH - leftImageSize, - constraints: listType == FeedListType.magazine + constraints: listType == FeedItemListType.magazine ? BoxConstraints( minHeight: leftImageSize, ) @@ -87,10 +88,10 @@ class YesodPreviewCard extends StatelessWidget { child: ClipRect( child: Flex( mainAxisSize: MainAxisSize.min, - crossAxisAlignment: listType == FeedListType.table + crossAxisAlignment: listType == FeedItemListType.table ? CrossAxisAlignment.center : CrossAxisAlignment.start, - direction: listType == FeedListType.table + direction: listType == FeedItemListType.table ? Axis.horizontal : Axis.vertical, children: [ @@ -142,7 +143,7 @@ class YesodPreviewCard extends StatelessWidget { ), ), ), - if (listType == FeedListType.card && + if (listType == FeedItemListType.card && images != null && images!.isNotEmpty) ConstrainedBox( diff --git a/lib/view/pages/yesod/yesod_recent_page.dart b/lib/view/pages/yesod/yesod_recent_page.dart index 0738104..060c052 100644 --- a/lib/view/pages/yesod/yesod_recent_page.dart +++ b/lib/view/pages/yesod/yesod_recent_page.dart @@ -37,6 +37,7 @@ class YesodRecentPage extends StatelessWidget { maxPageNum = state.maxPage; } final items = state.feedItemDigests ?? []; + final listConfig = context.read().repo.getFeedItemListConfig(); return DecoratedBox( decoration: BoxDecoration( @@ -85,7 +86,7 @@ class YesodRecentPage extends StatelessWidget { cacheExtent: MediaQuery.sizeOf(context).height * 2, itemBuilder: (context, index) { if (index < items.length) { - if ((state.feedItemFilter?.hideRead ?? false) && + if ((listConfig.hideRead ?? false) && items[index].readCount > 0) { return const SizedBox(); } @@ -139,8 +140,8 @@ class YesodRecentPage extends StatelessWidget { iconUrl: item.feedAvatarUrl, images: item.imageUrls, description: item.shortDescription, - listType: state.feedListType ?? - FeedListType.card, + listType: listConfig.listType ?? + FeedItemListType.card, cardBorderRadius: filled ? BorderRadius.zero : null, diff --git a/lib/view/pages/yesod/yesod_recent_setting_panel.dart b/lib/view/pages/yesod/yesod_recent_setting_panel.dart index e00ba34..b6fcd9f 100644 --- a/lib/view/pages/yesod/yesod_recent_setting_panel.dart +++ b/lib/view/pages/yesod/yesod_recent_setting_panel.dart @@ -4,7 +4,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:multi_select_flutter/dialog/multi_select_dialog_field.dart'; import 'package:multi_select_flutter/util/multi_select_item.dart'; import 'package:tuihub_protos/librarian/sephirah/v1/yesod.pb.dart'; -import 'package:tuihub_protos/librarian/v1/common.pb.dart'; import '../../../bloc/yesod/yesod_bloc.dart'; import '../../../model/yesod_model.dart'; @@ -13,25 +12,38 @@ import '../../form/form_field.dart'; import '../../helper/spacing.dart'; import '../../specialized/right_panel_form.dart'; -class YesodRecentSettingPanel extends StatelessWidget { +class YesodRecentSettingPanel extends StatefulWidget { const YesodRecentSettingPanel({super.key}); + @override + State createState() => + YesodRecentSettingPanelState(); +} + +class YesodRecentSettingPanelState extends State { + bool initialized = false; + List feedIDFilter = []; + List categoryFilter = []; + bool hideRead = false; + FeedItemListType listType = FeedItemListType.card; + void close(BuildContext context) { AppRoutes.yesodRecentFilter().pop(context); } @override Widget build(BuildContext context) { - List feedIDFilter = []; - List categoryFilter = []; - bool hideRead; - return BlocBuilder( builder: (context, state) { - feedIDFilter = state.feedItemFilter?.feedIdFilter?.toList() ?? []; - categoryFilter = state.feedItemFilter?.categoryFilter?.toList() ?? []; - final listType = state.feedListType ?? FeedListType.card; - hideRead = state.feedItemFilter?.hideRead ?? false; + if (!initialized) { + final listConfig = + context.read().repo.getFeedItemListConfig(); + feedIDFilter = listConfig.feedIdFilter?.toList() ?? []; + categoryFilter = listConfig.categoryFilter?.toList() ?? []; + listType = listConfig.listType ?? FeedItemListType.card; + hideRead = listConfig.hideRead ?? false; + initialized = true; + } return RightPanelForm( title: const Text('设置'), @@ -44,9 +56,9 @@ class YesodRecentSettingPanel extends StatelessWidget { const SizedBox( height: 16, ), - AnimatedToggleSwitch.size( + AnimatedToggleSwitch.size( current: listType, - values: FeedListType.values, + values: FeedItemListType.values, iconOpacity: 1.0, selectedIconScale: 1.0, indicatorSize: const Size.fromWidth(100), @@ -62,9 +74,9 @@ class YesodRecentSettingPanel extends StatelessWidget { local.animationValue)))); }, onChanged: (value) { - context.read().add( - YesodFeedListTypeSetEvent(value), - ); + setState(() { + listType = value; + }); }, ), const SizedBox( @@ -94,7 +106,7 @@ class YesodRecentSettingPanel extends StatelessWidget { for (final ListFeedConfigsResponse_FeedWithConfig config in state.feedConfigs ?? []) MultiSelectItem( - config.config.id, + config.config.id.id.toString(), config.feed.title.isNotEmpty ? config.feed.title : config.config.feedUrl), @@ -128,18 +140,16 @@ class YesodRecentSettingPanel extends StatelessWidget { ), ), ], - onSubmit: () { - context.read().add( - YesodFeedItemDigestsSetFilterEvent( - YesodFeedItemFilter( - feedIdFilter: feedIDFilter, - categoryFilter: categoryFilter, - hideRead: hideRead, - ), - ), - ); - close(context); - }, + onSubmit: () async => context + .read() + .repo + .setFeedItemListConfig(YesodFeedItemListConfig( + feedIdFilter: feedIDFilter, + categoryFilter: categoryFilter, + hideRead: hideRead, + listType: listType, + )) + .then((value) => close(context)), close: () => close(context), ); },