Skip to content

Commit

Permalink
feat(*): Support page jumping in forum page and thread page
Browse files Browse the repository at this point in the history
Support page jumping in forum page and thread page:
* This commit also fixes junk in JumpPageDialog with large mount of choices.
* Now default selected choice in JumpPageDialog is current page number.
  • Loading branch information
realth000 committed Dec 19, 2023
1 parent 73092bc commit 90cfa34
Show file tree
Hide file tree
Showing 10 changed files with 319 additions and 146 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- 新增解析帖子中回复后才可见的部分。
- 新增解析对帖子回复的点评。
- 现在打开帖子中的链接时对支持的链接优先以页面的形式在应用内打开。
- 现在论坛页面和帖子页面可以快速跳页。

### Fixed

- 修复无法在具有多个隐藏部分的帖子中购买的问题。
- 修复在github构建流水线中发布release时描述信息被空信息覆盖的问题。
- 修复当帖子的回复评分为扣分时显示总评分为空的问题。
- 修复安卓上帖子标题下方空白和其他页面不一致的问题。
- 修复搜索页面中跳页的对话框默认选中的页不是当前页的问题。

### Changed

Expand Down
52 changes: 52 additions & 0 deletions lib/extensions/universal_html.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,60 @@
import 'dart:math';

import 'package:collection/collection.dart';
import 'package:tsdm_client/constants/url.dart';
import 'package:tsdm_client/extensions/string.dart';
import 'package:universal_html/html.dart';

extension GrepDocumentExtension on Document {
/// Parse the current page number of current document.
int? currentPage() {
// Should call on "this" implicitly.
return this
.querySelector('div.pg > strong')
?.firstEndDeepText()
?.parseToInt();
}

/// Parse the total pages count in current document.
int? totalPages() {
// Should call on "this" implicitly.
final paginateNode = this.querySelector('div.pg');
var currentPage = 1;
var ret = 1;
if (paginateNode == null) {
return ret;
}
currentPage = paginateNode
.querySelector('strong')
?.firstEndDeepText()
?.parseToInt() ??
1;

final lastNode = paginateNode.children.lastOrNull;
final skippedLastNode = paginateNode.querySelector('a.last');
if (lastNode != null &&
lastNode.nodeType == Node.ELEMENT_NODE &&
lastNode.localName == 'strong') {
// Already in the last page.
ret = currentPage;
} else if (skippedLastNode != null) {
// 1, 2, .. 100
// |---- Skipped to the last page
// Fall back to 1 if parse int failed.
ret = skippedLastNode.firstEndDeepText()?.substring(4).parseToInt() ?? 1;
} else {
ret = paginateNode
.querySelectorAll('a')
.where((e) => e.classes.isEmpty)
.map((e) => (e.firstEndDeepText() ?? '0').parseToInt())
.whereType<int>()
.toList()
.reduce(max<int>);
}
return ret;
}
}

/// Extension for [Element] type to access children.
extension AccessExtension on Element {
/// Get the child at [index] in [children], return null if not exist.
Expand Down
70 changes: 70 additions & 0 deletions lib/providers/jump_page_provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import 'package:flutter/foundation.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part '../generated/providers/jump_page_provider.g.dart';

/// State of jump page ability of current page.
@immutable
class JumpPageState {
const JumpPageState({
required this.currentPage,
required this.totalPages,
required this.canJumpPage,
});

final int currentPage;
final int totalPages;
final bool canJumpPage;
}

/// Manage ths state of jump page ability of current page.
///
/// In some pages there's a long paginated list, use this provider to save and update current page's info
/// and total pages info.
@Riverpod()
class JumpPage extends _$JumpPage {
@override
JumpPageState build(int key) {
_key = key;
return JumpPageState(
currentPage: _currentPage,
totalPages: _totalPages,
canJumpPage: _canJumpPage,
);
}

/// Update pagination info of current page:
/// * Current page has is in page number [currentPage].
/// * Current page totally has [totalPages] pages.
void setPageState({required int currentPage, required int totalPages}) {
_currentPage = currentPage;
_totalPages = totalPages;
if (_currentPage > 0 && _totalPages > 0 && _currentPage <= _totalPages) {
_setCanJumpPage(canJumpPage: true);
} else {
_setCanJumpPage(canJumpPage: false);
}
ref.invalidateSelf();
}

/// Indicate whether current page is able to jump to another page.
///
/// Because in some situations (e.g. page still loading) current page is temporarily unable to jump to another page
/// or the current page info and total page info isn't ready/outdated.
void setCanJumpPage({required bool canJumpPage}) {
_setCanJumpPage(canJumpPage: canJumpPage);
ref.invalidateSelf();
}

// ignore: use_setters_to_change_properties
void _setCanJumpPage({required bool canJumpPage}) {
_canJumpPage = canJumpPage;
}

/// Key to specify ListAppBar should associate with which widget.
/// Because there may be multiple pages having their own [jumpPageProvider].
int _key = 0;
var _currentPage = 0;
var _totalPages = 0;
var _canJumpPage = false;
}
37 changes: 2 additions & 35 deletions lib/providers/search_provider.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import 'dart:io';
import 'dart:math';

import 'package:flutter/cupertino.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
Expand All @@ -8,7 +7,6 @@ import 'package:tsdm_client/extensions/string.dart';
import 'package:tsdm_client/extensions/universal_html.dart';
import 'package:tsdm_client/models/searched_thread.dart';
import 'package:tsdm_client/providers/net_client_provider.dart';
import 'package:universal_html/html.dart' as uh;
import 'package:universal_html/parsing.dart';

part '../generated/providers/search_provider.g.dart';
Expand Down Expand Up @@ -133,39 +131,8 @@ class Search extends _$Search {
.firstOrNull
?.parseToInt();

var currentPage = 1;
var totalPages = 1;
final paginateNode = document.querySelector('div.pg');
if (paginateNode != null) {
currentPage = paginateNode
.querySelector('strong')
?.firstEndDeepText()
?.parseToInt() ??
1;

final lastNode = paginateNode.children.lastOrNull;
final skippedLastNode = paginateNode.querySelector('a.last');
if (lastNode != null &&
lastNode.nodeType == uh.Node.ELEMENT_NODE &&
lastNode.localName == 'strong') {
// Already in the last page.
totalPages = currentPage;
} else if (skippedLastNode != null) {
// 1, 2, .. 100
// |---- Skipped to the last page
// Fall back to 1 if parse int failed.
totalPages =
skippedLastNode.firstEndDeepText()?.substring(4).parseToInt() ?? 1;
} else {
totalPages = paginateNode
.querySelectorAll('a')
.where((e) => e.classes.isEmpty)
.map((e) => (e.firstEndDeepText() ?? '0').parseToInt())
.whereType<int>()
.toList()
.reduce(max<int>);
}
}
final currentPage = document.currentPage() ?? 1;
final totalPages = document.totalPages() ?? currentPage;

return SearchResult(
currentPage: currentPage,
Expand Down
40 changes: 37 additions & 3 deletions lib/screens/forum/forum_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ import 'package:hooks_riverpod/hooks_riverpod.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/universal_html.dart';
import 'package:tsdm_client/generated/i18n/strings.g.dart';
import 'package:tsdm_client/models/forum.dart';
import 'package:tsdm_client/models/normal_thread.dart';
import 'package:tsdm_client/packages/html_muncher/lib/src/html_muncher.dart';
import 'package:tsdm_client/providers/jump_page_provider.dart';
import 'package:tsdm_client/providers/net_client_provider.dart';
import 'package:tsdm_client/routes/screen_paths.dart';
import 'package:tsdm_client/utils/debug.dart';
Expand Down Expand Up @@ -59,7 +61,10 @@ class _ForumPageState extends ConsumerState<ForumPage>
);

/// Current page number.
int _pageNumber = 1;
int _pageNumber = 0;

/// Total pages number.
int _totalPages = 0;

/// Whether we are in the last page.
bool _inLastPage = false;
Expand Down Expand Up @@ -110,7 +115,6 @@ class _ForumPageState extends ConsumerState<ForumPage>
}

void _clearData() {
_pageNumber = 1;
_allThreadData.clear();
_allSubredditData.clear();
_inLastPage = false;
Expand Down Expand Up @@ -254,7 +258,12 @@ class _ForumPageState extends ConsumerState<ForumPage>
});
}

_pageNumber++;
_pageNumber = document.currentPage() ?? 1;
_totalPages = document.totalPages() ?? _pageNumber;
ref.read(jumpPageProvider(hashCode).notifier).setPageState(
currentPage: _pageNumber,
totalPages: _totalPages,
);

// Update whether we are in the last page.
_inLastPage = !canLoadMore(document);
Expand Down Expand Up @@ -300,6 +309,7 @@ class _ForumPageState extends ConsumerState<ForumPage>
return;
}

_pageNumber++;
await _loadData();
_refreshController.finishLoad();
},
Expand Down Expand Up @@ -335,6 +345,14 @@ class _ForumPageState extends ConsumerState<ForumPage>
);
}

@override
void activate() {
super.activate();
ref
.read(jumpPageProvider(hashCode).notifier)
.setPageState(currentPage: _pageNumber, totalPages: _totalPages);
}

@override
void initState() {
super.initState();
Expand All @@ -348,6 +366,10 @@ class _ForumPageState extends ConsumerState<ForumPage>
await _refreshController.callRefresh();
});
}

ref
.read(jumpPageProvider(hashCode).notifier)
.setCanJumpPage(canJumpPage: false);
}

@override
Expand Down Expand Up @@ -380,6 +402,18 @@ class _ForumPageState extends ConsumerState<ForumPage>
await context.pushNamed(ScreenPaths.search,
queryParameters: {'fid': widget.fid});
},
jumpPageKey: hashCode,
onJumpPage: (pageNumber) async {
if (!mounted) {
return;
}
setState(() {
_pageNumber = pageNumber;
ref.read(jumpPageProvider(hashCode).notifier).setPageState(
currentPage: _pageNumber, totalPages: _totalPages);
});
await _refreshController.callRefresh();
},
onSelected: (value) async {
switch (value) {
case MenuActions.refresh:
Expand Down
53 changes: 0 additions & 53 deletions lib/screens/search/jump_page_dialog.dart

This file was deleted.

2 changes: 1 addition & 1 deletion lib/screens/search/search_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import 'package:tsdm_client/constants/layout.dart';
import 'package:tsdm_client/extensions/list.dart';
import 'package:tsdm_client/generated/i18n/strings.g.dart';
import 'package:tsdm_client/providers/search_provider.dart';
import 'package:tsdm_client/screens/search/jump_page_dialog.dart';
import 'package:tsdm_client/utils/debug.dart';
import 'package:tsdm_client/widgets/debounce_buttons.dart';
import 'package:tsdm_client/widgets/jump_page_dialog.dart';
import 'package:tsdm_client/widgets/thread_card.dart';

/// Page of search, including a form to fill search parameters and search results.
Expand Down
Loading

0 comments on commit 90cfa34

Please sign in to comment.