Skip to content

Commit 68072d4

Browse files
shadowfish07claude
andauthored
feat(bookmark): 空内容的书签点击后会直接浏览器打开 (#70)
* feat(bookmark): implement smart navigation for bookmark taps This commit introduces a smarter navigation logic when a user taps on a bookmark card. The behavior is now determined by the availability of reading statistics, which serves as a proxy for whether the bookmark has content parsed and ready for reading within the app. - If a bookmark has reading stats, tapping it will navigate to the in-app detail page. - If a bookmark lacks reading stats (implying it might have empty content or hasn't been processed), it will open the URL directly in an external browser. This change enhances the user experience by avoiding blank detail pages and providing immediate access to the content in the most appropriate way. Comprehensive unit tests have been added for `BookmarksViewModel` and `DailyReadViewModel` to ensure the new decision logic and the navigation callback mechanism are working correctly. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * refactor: 移除未使用的导航异常类并简化代码 移除不再使用的 NavigationNeededException 类 简化 SnackBarHelper 的样式配置 将 getOrNull 替换为 getOrThrow 以提高代码健壮性 * refactor(bookmark_card): 使用 BookmarkDisplayModel 替换直接传递 Bookmark 对象 重构书签卡片组件,将直接传递的 Bookmark 对象改为使用 BookmarkDisplayModel 封装,统一数据传递方式 移除不再需要的 readingStats 参数,相关数据已包含在 BookmarkDisplayModel 中 更新相关测试用例以适应新的数据结构 * refactor(bookmark): 统一使用BookmarkDisplayModel替换Bookmark参数 修改所有相关组件、视图模型和测试,将Bookmark类型参数替换为BookmarkDisplayModel 更新相关命令和回调函数以保持一致 * refactor(书签操作): 将书签点击处理逻辑提取到用例中 将重复的书签点击处理逻辑从视图模型提取到 BookmarkOperationUseCases 中 统一处理书签点击后的导航和浏览器打开逻辑 * test(view_model): 完善DailyReadViewModel的测试覆盖 * test(bookmarks): 重构书签点击处理测试以使用新的用例方法 --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 8664175 commit 68072d4

File tree

15 files changed

+1577
-206
lines changed

15 files changed

+1577
-206
lines changed

lib/data/repository/bookmark/bookmark_repository.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ class BookmarkRepository {
117117
final articleResult =
118118
await _articleRepository.getBookmarkArticle(b.id);
119119
if (articleResult.isSuccess()) {
120-
final htmlContent = articleResult.getOrNull()!;
120+
final htmlContent = articleResult.getOrThrow();
121121
final calculateResult = await _readingStatsRepository
122122
.calculateAndSaveReadingStats(b.id, htmlContent);
123123

lib/domain/use_cases/bookmark_operation_use_cases.dart

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'package:readeck_app/data/repository/article/article_repository.dart';
22
import 'package:readeck_app/data/repository/reading_stats/reading_stats_repository.dart';
33
import 'package:readeck_app/domain/models/bookmark/bookmark.dart';
4+
import 'package:readeck_app/domain/models/bookmark_display_model/bookmark_display_model.dart';
45

56
import 'package:readeck_app/main.dart';
67
import 'package:readeck_app/utils/reading_stats_calculator.dart';
@@ -114,4 +115,19 @@ class BookmarkOperationUseCases {
114115
}
115116
return null;
116117
}
118+
119+
void handleBookmarkTap({
120+
required BookmarkDisplayModel bookmark,
121+
required void Function(Bookmark) onNavigateToDetail,
122+
}) {
123+
appLogger.i('处理书签点击: ${bookmark.bookmark.title}');
124+
125+
if (bookmark.stats == null) {
126+
appLogger.i('书签没有阅读统计数据,可能文章内容为空,使用浏览器打开: ${bookmark.bookmark.url}');
127+
openUrl(bookmark.bookmark.url);
128+
} else {
129+
appLogger.i('书签有阅读统计数据,触发详情页导航');
130+
onNavigateToDetail(bookmark.bookmark);
131+
}
132+
}
117133
}

lib/ui/bookmarks/view_models/bookmarks_viewmodel.dart

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,10 @@ abstract class BaseBookmarksViewmodel extends ChangeNotifier {
4545
loadMore = Command.createAsync<int, List<BookmarkDisplayModel>>(_loadMore,
4646
initialValue: [], includeLastResultInCommandResults: true);
4747
openUrl = Command.createAsyncNoResult(_openUrl);
48-
toggleBookmarkMarked =
49-
Command.createAsyncNoResult<Bookmark>(_toggleBookmarkMarked);
50-
toggleBookmarkArchived =
51-
Command.createAsyncNoResult<Bookmark>(_toggleBookmarkArchived);
48+
toggleBookmarkMarked = Command.createAsyncNoResult<BookmarkDisplayModel>(
49+
_toggleBookmarkMarked);
50+
toggleBookmarkArchived = Command.createAsyncNoResult<BookmarkDisplayModel>(
51+
_toggleBookmarkArchived);
5252
loadLabels = Command.createAsyncNoParam(_loadLabels, initialValue: []);
5353

5454
// 注册书签数据变化监听器
@@ -67,8 +67,8 @@ abstract class BaseBookmarksViewmodel extends ChangeNotifier {
6767
late Command<int, List<BookmarkDisplayModel>> load;
6868
late Command<int, List<BookmarkDisplayModel>> loadMore;
6969
late Command<String, void> openUrl;
70-
late Command<Bookmark, void> toggleBookmarkMarked;
71-
late Command<Bookmark, void> toggleBookmarkArchived;
70+
late Command<BookmarkDisplayModel, void> toggleBookmarkMarked;
71+
late Command<BookmarkDisplayModel, void> toggleBookmarkArchived;
7272
late Command<void, List<String>> loadLabels;
7373

7474
List<BookmarkDisplayModel> get bookmarks => _bookmarks;
@@ -132,8 +132,29 @@ abstract class BaseBookmarksViewmodel extends ChangeNotifier {
132132
}
133133
}
134134

135-
Future<void> _toggleBookmarkMarked(Bookmark bookmark) async {
136-
final result = await _bookmarkRepository.toggleMarked(bookmark);
135+
/// 处理书签点击,根据文章内容状态决定打开方式
136+
void handleBookmarkTap(BookmarkDisplayModel bookmark) {
137+
_bookmarkOperationUseCases.handleBookmarkTap(
138+
bookmark: bookmark,
139+
onNavigateToDetail: _navigateToDetail,
140+
);
141+
}
142+
143+
/// 触发详情页导航的回调
144+
void Function(Bookmark)? _onNavigateToDetail;
145+
146+
/// 设置详情页导航回调
147+
void setNavigateToDetailCallback(void Function(Bookmark) callback) {
148+
_onNavigateToDetail = callback;
149+
}
150+
151+
/// 导航到详情页
152+
void _navigateToDetail(Bookmark bookmark) {
153+
_onNavigateToDetail?.call(bookmark);
154+
}
155+
156+
Future<void> _toggleBookmarkMarked(BookmarkDisplayModel bookmark) async {
157+
final result = await _bookmarkRepository.toggleMarked(bookmark.bookmark);
137158

138159
if (result.isError()) {
139160
appLogger.e("Failed to toggle bookmark marked",
@@ -142,8 +163,8 @@ abstract class BaseBookmarksViewmodel extends ChangeNotifier {
142163
}
143164
}
144165

145-
Future<void> _toggleBookmarkArchived(Bookmark bookmark) async {
146-
final result = await _bookmarkRepository.toggleArchived(bookmark);
166+
Future<void> _toggleBookmarkArchived(BookmarkDisplayModel bookmark) async {
167+
final result = await _bookmarkRepository.toggleArchived(bookmark.bookmark);
147168

148169
if (result.isError()) {
149170
appLogger.e("Failed to toggle bookmark archived",
@@ -163,8 +184,9 @@ abstract class BaseBookmarksViewmodel extends ChangeNotifier {
163184
}
164185

165186
Future<void> updateBookmarkLabels(
166-
Bookmark bookmark, List<String> labels) async {
167-
final result = await _bookmarkRepository.updateLabels(bookmark, labels);
187+
BookmarkDisplayModel bookmark, List<String> labels) async {
188+
final result =
189+
await _bookmarkRepository.updateLabels(bookmark.bookmark, labels);
168190

169191
if (result.isError()) {
170192
appLogger.e("Failed to update bookmark labels",

lib/ui/bookmarks/widget/bookmark_list_screen.dart

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,18 @@ class _BookmarkListScreenState<T extends BaseBookmarksViewmodel>
6262
super.initState();
6363
_scrollController = ScrollController();
6464
_scrollController.addListener(_onScroll);
65+
66+
// 设置详情页导航回调
67+
widget.viewModel.setNavigateToDetailCallback((bookmark) {
68+
if (mounted) {
69+
context.push(
70+
Routes.bookmarkDetailWithId(bookmark.id),
71+
extra: {
72+
'bookmark': bookmark,
73+
},
74+
);
75+
}
76+
});
6577
}
6678

6779
@override
@@ -217,13 +229,14 @@ class _BookmarkListScreenState<T extends BaseBookmarksViewmodel>
217229

218230
final bookmarkModel = bookmarks[index];
219231
return BookmarkCard(
220-
bookmark: bookmarkModel.bookmark,
232+
bookmarkDisplayModel: bookmarkModel,
221233
onOpenUrl: widget.viewModel.openUrl,
234+
onCardTap: widget.viewModel.handleBookmarkTap,
222235
onToggleMark: (bookmark) =>
223-
widget.viewModel.toggleBookmarkMarked(bookmark),
236+
widget.viewModel.toggleBookmarkMarked(bookmarkModel),
224237
onUpdateLabels: (bookmark, labels) {
225238
widget.viewModel
226-
.updateBookmarkLabels(bookmark, labels)
239+
.updateBookmarkLabels(bookmarkModel, labels)
227240
.catchError((error) {
228241
if (context.mounted) {
229242
SnackBarHelper.showError(
@@ -234,19 +247,10 @@ class _BookmarkListScreenState<T extends BaseBookmarksViewmodel>
234247
}
235248
});
236249
},
237-
readingStats: bookmarkModel.stats,
238-
onCardTap: (bookmark) {
239-
context.push(
240-
Routes.bookmarkDetailWithId(bookmark.id),
241-
extra: {
242-
'bookmark': bookmark,
243-
},
244-
);
245-
},
246250
availableLabels: widget.viewModel.availableLabels,
247251
onLoadLabels: () => widget.viewModel.loadLabels.executeWithFuture(),
248252
onToggleArchive: (bookmark) {
249-
widget.viewModel.toggleBookmarkArchived(bookmark);
253+
widget.viewModel.toggleBookmarkArchived(bookmarkModel);
250254
},
251255
);
252256
},

0 commit comments

Comments
 (0)