Skip to content

Commit

Permalink
feat(thread): Support filter posts by author uid
Browse files Browse the repository at this point in the history
  • Loading branch information
realth000 committed Feb 2, 2024
1 parent 747a9a2 commit 165ad31
Show file tree
Hide file tree
Showing 12 changed files with 181 additions and 6 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
### Added

- 新增提示帖子内的红包已经领完了。
- 新增支持看帖时只看某个作者的楼层。
* 楼层右下角菜单 -> 只看该作者/看所有作者。
- 缓存首页轮播图。

### Fixed
Expand Down
3 changes: 2 additions & 1 deletion lib/constants/url.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ const modifyUserCredentialUrl =
/// * User email
const checkAuthenticationStateUrl = modifyUserCredentialUrl;

const upgradeGithubRelaseUrl =
/// Url to get the latest app on Github.
const upgradeGithubReleaseUrl =
'https://github.com/realth000/tsdm_client/releases/latest';

/// Target url to post a reply to thread [tid], forum [fid].
Expand Down
16 changes: 16 additions & 0 deletions lib/extensions/build_context.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:tsdm_client/extensions/string.dart';
import 'package:url_launcher/url_launcher.dart';
Expand Down Expand Up @@ -36,3 +37,18 @@ extension DispatchUrl on BuildContext {
await launchUrl(u, mode: LaunchMode.externalApplication);
}
}

/// Extension on [BuildContext] provides methods to access the widget tree.
extension AccessContext on BuildContext {
/// Try to read the bloc type [T] on context.
///
/// * Return [T] if bloc found.
/// * Return null if bloc not found.
T? readOrNull<T>() {
try {
return read<T>();
} catch (e) {
return null;
}
}
}
64 changes: 61 additions & 3 deletions lib/features/thread/bloc/thread_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ class ThreadBloc extends Bloc<ThreadEvent, ThreadState> {
on<ThreadRefreshRequested>(_onThreadRefreshRequested);
on<ThreadJumpPageRequested>(_onThreadJumpPageRequested);
on<ThreadClosedStateUpdated>(_onThreadUpdateClosedState);
on<ThreadOnlyViewAuthorRequested>(_onThreadOnlyViewAuthorRequested);
on<ThreadViewAllAuthorsRequested>(_onThreadViewAllAuthorsRequested);
}

final ThreadRepository _threadRepository;
Expand All @@ -38,6 +40,7 @@ class ThreadBloc extends Bloc<ThreadEvent, ThreadState> {
final document = await _threadRepository.fetchThread(
tid: state.tid,
pageNumber: event.pageNumber,
onlyVisibleUid: state.onlyVisibleUid,
);
emit(await _parseFromDocument(document, event.pageNumber));
} on HttpRequestFailedException catch (e) {
Expand All @@ -52,7 +55,10 @@ class ThreadBloc extends Bloc<ThreadEvent, ThreadState> {
) async {
emit(state.copyWith(status: ThreadStatus.loading, postList: []));
try {
final document = await _threadRepository.fetchThread(tid: state.tid);
final document = await _threadRepository.fetchThread(
tid: state.tid,
onlyVisibleUid: state.onlyVisibleUid,
);
emit(await _parseFromDocument(document, 1));
} on HttpRequestFailedException catch (e) {
debug('failed to load thread page: $e');
Expand All @@ -70,6 +76,7 @@ class ThreadBloc extends Bloc<ThreadEvent, ThreadState> {
final document = await _threadRepository.fetchThread(
tid: state.tid,
pageNumber: event.pageNumber,
onlyVisibleUid: state.onlyVisibleUid,
);
emit(await _parseFromDocument(document, event.pageNumber));
} on HttpRequestFailedException catch (e) {
Expand All @@ -85,10 +92,59 @@ class ThreadBloc extends Bloc<ThreadEvent, ThreadState> {
emit(state.copyWith(threadClosed: event.closed));
}

Future<void> _onThreadOnlyViewAuthorRequested(
ThreadOnlyViewAuthorRequested event,
ThreadEmitter emit,
) async {
emit(state.copyWith(status: ThreadStatus.loading, postList: []));

try {
final document = await _threadRepository.fetchThread(
tid: state.tid,
pageNumber: state.currentPage,
onlyVisibleUid: event.uid,
);
// Use "1" as current page number to prevent page number overflow.
final s = await _parseFromDocument(document, 1);
emit(s.copyWith(onlyVisibleUid: event.uid));
} on HttpRequestFailedException catch (e) {
debug('failed to load thread page: fid=${state.tid}, pageNumber=1 : $e');
emit(state.copyWith(status: ThreadStatus.failed));
}
}

Future<void> _onThreadViewAllAuthorsRequested(
ThreadViewAllAuthorsRequested event,
ThreadEmitter emit,
) async {
emit(state.copyWith(status: ThreadStatus.loading, postList: []));

try {
// Switching from "only view specified author" to "view all authors"
// will have more posts and pages so there is no page number overflow
// risk.
final document = await _threadRepository.fetchThread(
tid: state.tid,
pageNumber: state.currentPage,
);
// Use "1" as current page number to prevent page number overflow.
final s = await _parseFromDocument(
document,
state.currentPage,
clearOnlyVisibleUid: true,
);
emit(s.copyWith(onlyVisibleUid: state.onlyVisibleUid));
} on HttpRequestFailedException catch (e) {
debug('failed to load thread page: fid=${state.tid}, pageNumber=1 : $e');
emit(state.copyWith(status: ThreadStatus.failed));
}
}

Future<ThreadState> _parseFromDocument(
uh.Document document,
int pageNumber,
) async {
int pageNumber, {
bool? clearOnlyVisibleUid,
}) async {
final threadClosed = document.querySelector('form#fastpostform') == null;
final threadDataNode = document.querySelector('div#postlist');
final postList = Post.buildListFromThreadDataNode(threadDataNode);
Expand Down Expand Up @@ -184,6 +240,8 @@ class ThreadBloc extends Bloc<ThreadEvent, ThreadState> {
threadClosed: threadClosed,
postList: [...state.postList, ...postList],
threadType: threadType,
onlyVisibleUid:
(clearOnlyVisibleUid ?? false) ? null : state.onlyVisibleUid,
);
}
}
21 changes: 21 additions & 0 deletions lib/features/thread/bloc/thread_event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,24 @@ final class ThreadClosedStateUpdated extends ThreadEvent {

/// Clear current reply parameters.
final class ThreadClearReplyParameterRequested extends ThreadEvent {}

/// User requested to only view the posts published by user with [uid] in
/// current thread.
///
/// Note that triggering this event **will not change the current page number**.
/// We behave like what it acts in browser.
final class ThreadOnlyViewAuthorRequested extends ThreadEvent {
/// Constructor.
const ThreadOnlyViewAuthorRequested(this.uid);

/// The only author to view in current thread.
final String uid;
}

/// User requested to view posts published by all authors in current thread.
///
/// This is the default behavior when display threads.
///
/// Note that triggering this event **will not change the current page number**.
/// We behave like what it acts in browser.
final class ThreadViewAllAuthorsRequested extends ThreadEvent {}
10 changes: 10 additions & 0 deletions lib/features/thread/bloc/thread_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class ThreadState extends Equatable {
this.postList = const [],
this.replyParameters,
this.threadType,
this.onlyVisibleUid,
});

/// Status.
Expand Down Expand Up @@ -80,6 +81,12 @@ class ThreadState extends Equatable {
/// Thread type.
final String? threadType;

/// Indicating only show posts published by the user who has the given uid
/// in current thread.
///
/// Show all posts if value is null;
final String? onlyVisibleUid;

/// Copy with.
ThreadState copyWith({
ThreadStatus? status,
Expand All @@ -94,6 +101,7 @@ class ThreadState extends Equatable {
bool? threadClosed,
List<Post>? postList,
ReplyParameters? replyParameters,
String? onlyVisibleUid,
}) {
return ThreadState(
status: status ?? this.status,
Expand All @@ -109,6 +117,7 @@ class ThreadState extends Equatable {
threadClosed: threadClosed ?? this.threadClosed,
postList: postList ?? this.postList,
replyParameters: replyParameters ?? this.replyParameters,
onlyVisibleUid: onlyVisibleUid ?? this.onlyVisibleUid,
);
}

Expand All @@ -126,5 +135,6 @@ class ThreadState extends Equatable {
threadClosed,
postList,
replyParameters,
onlyVisibleUid,
];
}
6 changes: 5 additions & 1 deletion lib/features/thread/repository/thread_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,14 @@ class ThreadRepository {
Future<uh.Document> fetchThread({
required String tid,
int pageNumber = 1,
String? onlyVisibleUid,
}) async {
/// Only visible uid.
final visibleUid =
onlyVisibleUid == null ? '' : '&authorid=$onlyVisibleUid';
_pageNumber = pageNumber;
_threadUrl =
'$baseUrl/forum.php?mod=viewthread&tid=$tid&extra=page%3D1&page=$pageNumber';
'$baseUrl/forum.php?mod=viewthread&tid=$tid&extra=page%3D1$visibleUid&page=$pageNumber';

final resp = await getIt.get<NetClientProvider>().get(_threadUrl!);
if (resp.statusCode != HttpStatus.ok) {
Expand Down
2 changes: 1 addition & 1 deletion lib/features/upgrade/view/upgrade_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ class _UpgradePageState extends State<UpgradePage> {
icon: const Icon(Icons.launch_outlined),
onPressed: () async {
await launchUrl(
Uri.parse(upgradeGithubRelaseUrl),
Uri.parse(upgradeGithubReleaseUrl),
mode: LaunchMode.externalApplication,
);
},
Expand Down
2 changes: 2 additions & 0 deletions lib/i18n/strings.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,8 @@
"postCard": {
"reply": "Reply",
"rate": "Rate",
"onlyViewAuthor": "Only view the author",
"viewAllAuthors": "View all authors",
"rateFetchInfoFailed": "Fetch info failed",
"rateFailed": "Rate failed"
},
Expand Down
2 changes: 2 additions & 0 deletions lib/i18n/strings_zh-CN.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,8 @@
"postCard": {
"reply": "回复",
"rate": "评分",
"onlyViewAuthor": "只看该作者",
"viewAllAuthors": "查看所有作者",
"rateFetchInfoFailed": "获取评分信息失败",
"rateFailed": "评分失败"
},
Expand Down
2 changes: 2 additions & 0 deletions lib/i18n/strings_zh-TW.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,8 @@
"postCard": {
"reply": "回覆",
"rate": "評分",
"onlyViewAuthor": "只看該作者",
"viewAllAuthors": "查看所有作者",
"rateFetchInfoFailed": "获取评分信息失败",
"rateFailed": "评分失败"
},
Expand Down
57 changes: 57 additions & 0 deletions lib/widgets/card/post_card.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:tsdm_client/constants/layout.dart';
import 'package:tsdm_client/constants/url.dart';
import 'package:tsdm_client/extensions/build_context.dart';
import 'package:tsdm_client/extensions/date_time.dart';
import 'package:tsdm_client/features/thread/bloc/thread_bloc.dart';
import 'package:tsdm_client/generated/i18n/strings.g.dart';
import 'package:tsdm_client/packages/html_muncher/lib/html_muncher.dart';
import 'package:tsdm_client/routes/screen_paths.dart';
Expand All @@ -17,9 +19,20 @@ import 'package:tsdm_client/widgets/card/packet_card.dart';
import 'package:tsdm_client/widgets/card/rate_card.dart';
import 'package:universal_html/parsing.dart';

/// Actions in post context menu.
///
/// * State of [viewTheAuthor] and [viewAllAuthors] are thread level state so
/// this state is stored by the parent thread. Disable these actions when
/// there is no available thread above current post in the widget tree.
enum _PostCardActions {
reply,
rate,

/// Only view posts published by current author.
viewTheAuthor,

/// View all authors.
viewAllAuthors,
}

/// Card for a [Post] model.
Expand Down Expand Up @@ -61,6 +74,9 @@ class _PostCardState extends State<PostCard>
Widget build(BuildContext context) {
super.build(context);

final threadBloc = context.readOrNull<ThreadBloc>();
final onlyVisibleUid = threadBloc?.state.onlyVisibleUid;

return SingleChildScrollView(
child: Padding(
padding: edgeInsetsL10R10B10,
Expand Down Expand Up @@ -154,6 +170,34 @@ class _PostCardState extends State<PostCard>
],
),
),

/// Viewing all authors, can switch to only view current
/// author mode.
if (threadBloc != null &&
onlyVisibleUid == null &&
widget.post.author.uid != null)
PopupMenuItem(
value: _PostCardActions.viewTheAuthor,
child: Row(
children: [
const Icon(Icons.person_outlined),
Text(context.t.postCard.onlyViewAuthor),
],
),
),

/// Viewing specified author now, can switch to view all
/// authors mode.
if (threadBloc != null && onlyVisibleUid != null)
PopupMenuItem(
value: _PostCardActions.viewAllAuthors,
child: Row(
children: [
const Icon(Icons.group_outlined),
Text(context.t.postCard.viewAllAuthors),
],
),
),
],
onSelected: (value) async {
switch (value) {
Expand All @@ -167,6 +211,19 @@ class _PostCardState extends State<PostCard>
if (widget.post.rateAction != null) {
await _rateCallback.call();
}
case _PostCardActions.viewTheAuthor:
// Here is guaranteed a not-null `ThreadBloc`.
context.read<ThreadBloc>().add(
ThreadOnlyViewAuthorRequested(
widget.post.author.uid!,
),
);
case _PostCardActions.viewAllAuthors:
// Here is guaranteed a not-null `ThreadBloc` and a
// not-null author uid.
context
.read<ThreadBloc>()
.add(ThreadViewAllAuthorsRequested());
}
},
),
Expand Down

0 comments on commit 165ad31

Please sign in to comment.