Skip to content

Commit b5cf7d9

Browse files
committed
refactor(bookmark): 引入书签显示模型整合阅读统计信息
Refactors bookmark-related features to encapsulate bookmarks and reading statistics within a BookmarkDisplayModel.
1 parent a8876d0 commit b5cf7d9

File tree

10 files changed

+336
-139
lines changed

10 files changed

+336
-139
lines changed

.trae/rules/project_rules.md

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -162,14 +162,14 @@ ReadeckApp 中的 Repository 必须使用正确的通知机制:
162162
```dart
163163
class ExampleRepository {
164164
final StreamController<void> _dataChangedController = StreamController<void>.broadcast();
165-
165+
166166
Stream<void> get dataChanged => _dataChangedController.stream;
167-
167+
168168
Future<void> saveData() async {
169169
// 保存数据逻辑
170170
_dataChangedController.add(null); // 通知数据变更
171171
}
172-
172+
173173
void dispose() {
174174
_dataChangedController.close();
175175
}
@@ -521,8 +521,6 @@ test_resources/ # 测试资源
521521

522522
### ReadeckApp 代码示例
523523

524-
---
525-
526524
## 错误处理和错误页面规范
527525

528526
### 统一错误页面组件
@@ -595,3 +593,25 @@ ReadeckApp 项目严格遵循 Flutter 官方推荐的架构模式,结合以下
595593
- **清晰的代码结构**
596594

597595
通过遵循本规范,ReadeckApp 项目将具备良好的可维护性、可测试性和可扩展性,为后续开发和维护提供坚实的基础。
596+
597+
## Development Workflow
598+
599+
### Commit Messages
600+
601+
This project uses [semantic-release](https://github.com/semantic-release/semantic-release) to automate version management and package publishing. Therefore, all commit messages must follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/).
602+
603+
- **chore**: For commits that do not modify the source code or test files (e.g., changes to build scripts, documentation, or project configuration).
604+
- **feat**: For new features.
605+
- **fix**: For bug fixes.
606+
- **refactor**: For code refactoring that does not change external behavior.
607+
- **docs**: For documentation changes.
608+
- **style**: For code style changes (e.g., formatting).
609+
- **test**: For adding or modifying tests.
610+
- **perf**: For performance improvements.
611+
- **ci**: For changes to CI configuration files and scripts.
612+
- **build**: For changes that affect the build system or external dependencies.
613+
- **revert**: For reverting a previous commit.
614+
615+
### Test-Driven Development (TDD)
616+
617+
This project follows a Test-Driven Development (TDD) approach. All new features or bug fixes should start with writing a failing unit test that describes the desired functionality or reproduces the bug. Only after the test is written should the implementation code be written to make the test pass.

GEMINI.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@.trae/rules/project_rules.md

lib/config/dependencies.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,12 @@ List<SingleChildWidget> providers(String host, String token) {
4242
Provider(
4343
create: (context) => ArticleRepository(
4444
context.read(), context.read(), context.read(), context.read())),
45-
Provider(create: (context) => BookmarkRepository(context.read())),
45+
Provider(create: (context) => ReadingStatsRepository(context.read())),
46+
Provider(
47+
create: (context) =>
48+
BookmarkRepository(context.read(), context.read())),
4649
Provider(create: (context) => DailyReadHistoryRepository(context.read())),
4750
Provider(create: (context) => LabelRepository(context.read())),
48-
Provider(create: (context) => ReadingStatsRepository(context.read())),
4951
Provider(
5052
create: (context) =>
5153
BookmarkOperationUseCases(context.read(), context.read())),

lib/data/repository/bookmark/bookmark_repository.dart

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
import 'dart:math';
22

3+
import 'package:readeck_app/data/repository/reading_stats/reading_stats_repository.dart';
34
import 'package:readeck_app/data/service/readeck_api_client.dart';
45
import 'package:readeck_app/domain/models/bookmark/bookmark.dart';
6+
import 'package:readeck_app/domain/models/bookmark_display_model/bookmark_display_model.dart';
57
import 'package:readeck_app/main.dart';
68
import 'package:result_dart/result_dart.dart';
79

810
/// 书签数据变化监听器类型定义
911
typedef BookmarkChangeListener = void Function();
1012

1113
class BookmarkRepository {
12-
BookmarkRepository(this._readeckApiClient);
14+
BookmarkRepository(this._readeckApiClient, this._readingStatsRepository);
1315

1416
final ReadeckApiClient _readeckApiClient;
17+
final ReadingStatsRepository _readingStatsRepository;
1518

1619
// 全局共享数据管理 - 单一数据源
1720
final List<Bookmark> _bookmarks = [];
@@ -92,21 +95,36 @@ class BookmarkRepository {
9295
_listeners.clear();
9396
}
9497

95-
AsyncResult<List<Bookmark>> loadBookmarksByIds(List<String> ids) async {
98+
AsyncResult<List<BookmarkDisplayModel>> _wrapBookmarksWithStats(
99+
List<Bookmark> bookmarks,
100+
) async {
101+
final models = await Future.wait(
102+
bookmarks.map((b) async {
103+
final statsRes = await _readingStatsRepository.getReadingStats(b.id);
104+
return statsRes.isSuccess()
105+
? BookmarkDisplayModel(bookmark: b, stats: statsRes.getOrThrow())
106+
: BookmarkDisplayModel(bookmark: b);
107+
}),
108+
eagerError: false,
109+
);
110+
return Success(models);
111+
}
112+
AsyncResult<List<BookmarkDisplayModel>> loadBookmarksByIds(
113+
List<String> ids) async {
96114
appLogger.i('开始根据ID加载书签,数量: ${ids.length}');
97115
final result = await _readeckApiClient.getBookmarks(ids: ids);
98116
if (result.isSuccess()) {
99117
final bookmarks = result.getOrThrow();
100118
appLogger.i('成功加载书签 ${bookmarks.length} 个');
101119
_insertOrUpdateCachedBookmarks(bookmarks);
102-
return result;
120+
return _wrapBookmarksWithStats(bookmarks);
103121
}
104122

105123
appLogger.e('根据ID加载书签失败', error: result.exceptionOrNull());
106-
return result;
124+
return Failure(result.exceptionOrNull()!);
107125
}
108126

109-
AsyncResult<List<Bookmark>> loadUnarchivedBookmarks({
127+
AsyncResult<List<BookmarkDisplayModel>> loadUnarchivedBookmarks({
110128
int limit = 10,
111129
int page = 1,
112130
}) async {
@@ -120,14 +138,14 @@ class BookmarkRepository {
120138
final bookmarks = result.getOrThrow();
121139
appLogger.i('成功加载未归档书签 ${bookmarks.length} 个');
122140
_insertOrUpdateCachedBookmarks(bookmarks);
123-
return result;
141+
return _wrapBookmarksWithStats(bookmarks);
124142
}
125143

126144
appLogger.e('加载未归档书签失败', error: result.exceptionOrNull());
127-
return result;
145+
return Failure(result.exceptionOrNull()!);
128146
}
129147

130-
AsyncResult<List<Bookmark>> loadArchivedBookmarks({
148+
AsyncResult<List<BookmarkDisplayModel>> loadArchivedBookmarks({
131149
int limit = 10,
132150
int page = 1,
133151
}) async {
@@ -141,14 +159,14 @@ class BookmarkRepository {
141159
final bookmarks = result.getOrThrow();
142160
appLogger.i('成功加载已归档书签 ${bookmarks.length} 个');
143161
_insertOrUpdateCachedBookmarks(bookmarks);
144-
return result;
162+
return _wrapBookmarksWithStats(bookmarks);
145163
}
146164

147165
appLogger.e('加载已归档书签失败', error: result.exceptionOrNull());
148-
return result;
166+
return Failure(result.exceptionOrNull()!);
149167
}
150168

151-
AsyncResult<List<Bookmark>> loadMarkedBookmarks({
169+
AsyncResult<List<BookmarkDisplayModel>> loadMarkedBookmarks({
152170
int limit = 10,
153171
int page = 1,
154172
}) async {
@@ -162,23 +180,25 @@ class BookmarkRepository {
162180
final bookmarks = result.getOrThrow();
163181
appLogger.i('成功加载已标记书签 ${bookmarks.length} 个');
164182
_insertOrUpdateCachedBookmarks(bookmarks);
165-
return result;
183+
return _wrapBookmarksWithStats(bookmarks);
166184
}
167185

168186
appLogger.e('加载已标记书签失败', error: result.exceptionOrNull());
169-
return result;
187+
return Failure(result.exceptionOrNull()!);
170188
}
171189

172-
AsyncResult<List<Bookmark>> loadRandomUnarchivedBookmarks(
190+
AsyncResult<List<BookmarkDisplayModel>> loadRandomUnarchivedBookmarks(
173191
int randomCount) async {
174192
appLogger.i('开始加载随机未归档书签,请求数量: $randomCount');
175193
final allBookmarks = await loadUnarchivedBookmarks(limit: 100);
176194

177195
if (allBookmarks.isSuccess()) {
178-
_insertOrUpdateCachedBookmarks(allBookmarks.getOrThrow());
196+
_insertOrUpdateCachedBookmarks(
197+
allBookmarks.getOrThrow().map((e) => e.bookmark).toList());
179198

180199
// 随机打乱并取前5个
181-
final shuffled = List<Bookmark>.from(allBookmarks.getOrDefault([]));
200+
final shuffled =
201+
List<BookmarkDisplayModel>.from(allBookmarks.getOrDefault([]));
182202
shuffled.shuffle(Random());
183203
final randomBookmarks = shuffled.take(5).toList();
184204

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import 'package:freezed_annotation/freezed_annotation.dart';
2+
import 'package:readeck_app/domain/models/bookmark/bookmark.dart';
3+
import 'package:readeck_app/utils/reading_stats_calculator.dart';
4+
5+
part 'bookmark_display_model.freezed.dart';
6+
7+
@freezed
8+
abstract class BookmarkDisplayModel with _$BookmarkDisplayModel {
9+
factory BookmarkDisplayModel({
10+
required Bookmark bookmark,
11+
ReadingStatsForView? stats,
12+
}) = _BookmarkDisplayModel;
13+
}

0 commit comments

Comments
 (0)