Skip to content

Commit d8adc44

Browse files
authored
Merge pull request #56 from shadowfish07/issue/31
Enhances settings and data management
2 parents 363e1cb + d2c940c commit d8adc44

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+5154
-1187
lines changed

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ jobs:
105105
id: semantic_release_dry
106106
run: |
107107
# Run semantic-release in dry-run mode to get the next version
108-
NEXT_VERSION=$(npx semantic-release --dry-run --no-ci | grep -oP 'The next release version is \K[0-9]+\.[0-9]+\.[0-9]+' || echo "")
108+
NEXT_VERSION=$(npx semantic-release --dry-run --no-ci | grep -oP 'The next release version is \K[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+\.[0-9]+)?' || echo "")
109109
if [ -n "$NEXT_VERSION" ]; then
110110
echo "next_version=$NEXT_VERSION" >> $GITHUB_OUTPUT
111111
echo "has_release=true" >> $GITHUB_OUTPUT

.trae/rules/project_rules.md

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,68 @@ ViewModel A → Local State (❌ 数据重复)
125125
ViewModel B → Local State (❌ 数据重复)
126126
```
127127

128+
### 7. ViewModel 解耦原则(ViewModel Decoupling Principle)
129+
130+
ReadeckApp 中的 ViewModel 必须严格遵循解耦原则:
131+
132+
- **ViewModel 之间不得直接相互引用**
133+
- **ViewModel 不能持有其他 ViewModel 的实例**
134+
- **ViewModel 之间的通信必须通过 Repository 层进行**
135+
- **避免 ViewModel 之间的紧耦合关系**
136+
- **确保每个 ViewModel 的独立性和可测试性**
137+
138+
**正确的 ViewModel 通信方式:**
139+
140+
```
141+
ViewModel A → Repository → StreamController → ViewModel B
142+
```
143+
144+
**错误的 ViewModel 通信方式:**
145+
146+
```
147+
ViewModel A → ViewModel B (❌ 直接引用)
148+
```
149+
150+
### 8. Repository 通知机制原则(Repository Notification Principle)
151+
152+
ReadeckApp 中的 Repository 必须使用正确的通知机制:
153+
154+
- **Repository 不得继承 ChangeNotifier**
155+
- **Repository 必须使用 StreamController 对外暴露数据变更**
156+
- **Repository 通过 Stream 通知数据变化,而非 ChangeNotifier**
157+
- **确保 Repository 层的职责单一性**
158+
- **避免 Repository 与 UI 层的直接耦合**
159+
160+
**正确的 Repository 通知方式:**
161+
162+
```dart
163+
class ExampleRepository {
164+
final StreamController<void> _dataChangedController = StreamController<void>.broadcast();
165+
166+
Stream<void> get dataChanged => _dataChangedController.stream;
167+
168+
Future<void> saveData() async {
169+
// 保存数据逻辑
170+
_dataChangedController.add(null); // 通知数据变更
171+
}
172+
173+
void dispose() {
174+
_dataChangedController.close();
175+
}
176+
}
177+
```
178+
179+
**错误的 Repository 通知方式:**
180+
181+
```dart
182+
class ExampleRepository extends ChangeNotifier { // ❌ 禁止继承 ChangeNotifier
183+
Future<void> saveData() async {
184+
// 保存数据逻辑
185+
notifyListeners(); // ❌ 禁止使用 notifyListeners
186+
}
187+
}
188+
```
189+
128190
---
129191

130192
## 分层架构设计
@@ -461,6 +523,40 @@ test_resources/ # 测试资源
461523

462524
---
463525

526+
## 错误处理和错误页面规范
527+
528+
### 统一错误页面组件
529+
530+
ReadeckApp 项目必须使用统一的错误页面组件来处理各种错误状态,确保用户体验的一致性。
531+
532+
**错误页面组件位置:**
533+
534+
```
535+
lib/ui/core/ui/error_page.dart
536+
```
537+
538+
### ErrorPage 组件使用规范
539+
540+
**ReadeckApp ErrorPage 规范要点:**
541+
542+
1. **统一错误处理入口**:所有错误状态都应使用 `ErrorPage` 组件
543+
2. **工厂方法优先**:优先使用预定义的工厂方法创建错误页面
544+
3. **异常类型映射**:使用 `ErrorPage.fromException()` 自动映射异常类型
545+
4. **主题一致性**:所有错误页面样式遵循应用主题
546+
5. **用户友好**:提供清晰的错误信息和操作指引
547+
548+
### 使用示例
549+
550+
```dart
551+
// Repository 层抛出特定异常
552+
if (response.statusCode == 404) {
553+
throw ResourceNotFoundException('书签不存在');
554+
}
555+
556+
// View 层自动处理
557+
final errorPage = ErrorPage.fromException(exception);
558+
```
559+
464560
## 测试策略
465561

466562
### ReadeckApp 测试规范

lib/config/dependencies.dart

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import 'package:provider/provider.dart';
22
import 'package:provider/single_child_widget.dart';
3+
import 'package:readeck_app/data/repository/article/article_repository.dart';
34
import 'package:readeck_app/data/repository/bookmark/bookmark_repository.dart';
45
import 'package:readeck_app/data/repository/daily_read_history/daily_read_history_repository.dart';
6+
import 'package:readeck_app/data/repository/openrouter/openrouter_repository.dart';
7+
import 'package:readeck_app/data/repository/reading_stats/reading_stats_repository.dart';
58
import 'package:readeck_app/data/repository/settings/settings_repository.dart';
69
import 'package:readeck_app/data/service/database_service.dart';
710
import 'package:readeck_app/data/service/readeck_api_client.dart';
@@ -11,35 +14,38 @@ import 'package:readeck_app/domain/use_cases/bookmark_operation_use_cases.dart';
1114
import 'package:readeck_app/data/repository/label/label_repository.dart';
1215

1316
import '../data/service/shared_preference_service.dart';
14-
import '../data/repository/theme/theme_repository.dart';
17+
1518
import '../main_viewmodel.dart';
1619

1720
List<SingleChildWidget> providers(String host, String token) {
1821
return [
1922
Provider(create: (context) => SharedPreferencesService()),
23+
Provider(create: (context) => ReadeckApiClient(host, token)),
24+
Provider(create: (context) => DatabaseService()),
25+
Provider(create: (context) {
26+
final prefsService = context.read<SharedPreferencesService>();
27+
final apiClient = context.read<ReadeckApiClient>();
28+
return SettingsRepository(prefsService, apiClient);
29+
}),
2030
Provider(
21-
create: (context) => ThemeRepository(
22-
context.read<SharedPreferencesService>(),
23-
),
24-
),
31+
create: (context) =>
32+
OpenRouterApiClient(context.read<SettingsRepository>())),
33+
Provider(create: (context) {
34+
final openRouterClient = context.read<OpenRouterApiClient>();
35+
return OpenRouterRepository(openRouterClient);
36+
}),
2537
ChangeNotifierProvider(
2638
create: (context) => MainAppViewModel(
27-
context.read<ThemeRepository>(),
39+
context.read<SettingsRepository>(),
2840
),
2941
),
30-
Provider(create: (context) => ReadeckApiClient(host, token)),
31-
Provider(create: (context) => DatabaseService()),
32-
Provider(
33-
create: (context) =>
34-
SettingsRepository(context.read(), context.read(), context.read())),
35-
Provider(
36-
create: (context) =>
37-
OpenRouterApiClient(context.read<SharedPreferencesService>())),
3842
Provider(
39-
create: (context) => BookmarkRepository(
43+
create: (context) => ArticleRepository(
4044
context.read(), context.read(), context.read(), context.read())),
45+
Provider(create: (context) => BookmarkRepository(context.read())),
4146
Provider(create: (context) => DailyReadHistoryRepository(context.read())),
4247
Provider(create: (context) => LabelRepository(context.read())),
48+
Provider(create: (context) => ReadingStatsRepository(context.read())),
4349
Provider(
4450
create: (context) =>
4551
BookmarkOperationUseCases(context.read(), context.read())),

0 commit comments

Comments
 (0)