Merge branch 'design'

This commit is contained in:
guozhigq
2024-10-19 15:46:22 +08:00
46 changed files with 782 additions and 783 deletions

View File

@ -15,6 +15,4 @@ class Constants {
// 59b43e04ad6965f34319062b478f83dd TV端 // 59b43e04ad6965f34319062b478f83dd TV端
static const String appSec = '59b43e04ad6965f34319062b478f83dd'; static const String appSec = '59b43e04ad6965f34319062b478f83dd';
static const String thirdSign = '04224646d1fea004e79606d3b038c84a'; static const String thirdSign = '04224646d1fea004e79606d3b038c84a';
static const String thirdApi =
'https://www.mcbbs.net/template/mcbbs/image/special_photo_bg.png';
} }

View File

@ -7,6 +7,7 @@ class HttpError extends StatelessWidget {
required this.fn, required this.fn,
this.btnText, this.btnText,
this.isShowBtn = true, this.isShowBtn = true,
this.isInSliver = true,
super.key, super.key,
}); });
@ -14,20 +15,18 @@ class HttpError extends StatelessWidget {
final Function()? fn; final Function()? fn;
final String? btnText; final String? btnText;
final bool isShowBtn; final bool isShowBtn;
final bool isInSliver;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SliverToBoxAdapter( Color primary = Theme.of(context).colorScheme.primary;
child: SizedBox( final errorContent = SizedBox(
height: 400, height: 400,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
SvgPicture.asset( SvgPicture.asset("assets/images/error.svg", height: 200),
"assets/images/error.svg",
height: 200,
),
const SizedBox(height: 30), const SizedBox(height: 30),
Text( Text(
errMsg ?? '请求异常', errMsg ?? '请求异常',
@ -37,23 +36,21 @@ class HttpError extends StatelessWidget {
const SizedBox(height: 20), const SizedBox(height: 20),
if (isShowBtn) if (isShowBtn)
FilledButton.tonal( FilledButton.tonal(
onPressed: () { onPressed: () => fn?.call(),
fn!();
},
style: ButtonStyle( style: ButtonStyle(
backgroundColor: MaterialStateProperty.resolveWith((states) { backgroundColor: MaterialStateProperty.resolveWith((states) {
return Theme.of(context).colorScheme.primary.withAlpha(20); return primary.withAlpha(20);
}), }),
), ),
child: Text( child: Text(btnText ?? '点击重试', style: TextStyle(color: primary)),
btnText ?? '点击重试',
style:
TextStyle(color: Theme.of(context).colorScheme.primary),
),
), ),
], ],
), ),
),
); );
if (isInSliver) {
return SliverToBoxAdapter(child: errorContent);
} else {
return Center(child: errorContent);
}
} }
} }

View File

@ -20,6 +20,7 @@ class NetworkImgLayer extends StatelessWidget {
// 图片质量 默认1% // 图片质量 默认1%
this.quality, this.quality,
this.origAspectRatio, this.origAspectRatio,
this.radius,
}); });
final String? src; final String? src;
@ -30,6 +31,18 @@ class NetworkImgLayer extends StatelessWidget {
final Duration? fadeInDuration; final Duration? fadeInDuration;
final int? quality; final int? quality;
final double? origAspectRatio; final double? origAspectRatio;
final double? radius;
BorderRadius getBorderRadius(String? type, double? radius) {
return BorderRadius.circular(
radius ??
(type == 'avatar'
? 50
: type == 'emote'
? 0
: StyleString.imgRadius.x),
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -72,13 +85,7 @@ class NetworkImgLayer extends StatelessWidget {
return src != '' && src != null return src != '' && src != null
? ClipRRect( ? ClipRRect(
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
borderRadius: BorderRadius.circular( borderRadius: getBorderRadius(type, radius),
type == 'avatar'
? 50
: type == 'emote'
? 0
: StyleString.imgRadius.x,
),
child: CachedNetworkImage( child: CachedNetworkImage(
imageUrl: imageUrl, imageUrl: imageUrl,
width: width, width: width,
@ -107,11 +114,7 @@ class NetworkImgLayer extends StatelessWidget {
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).colorScheme.onInverseSurface.withOpacity(0.4), color: Theme.of(context).colorScheme.onInverseSurface.withOpacity(0.4),
borderRadius: BorderRadius.circular(type == 'avatar' borderRadius: getBorderRadius(type, radius),
? 50
: type == 'emote'
? 0
: StyleString.imgRadius.x),
), ),
child: type == 'bg' child: type == 'bg'
? const SizedBox() ? const SizedBox()

View File

@ -104,7 +104,7 @@ class Api {
// 评论列表 // 评论列表
// https://api.bilibili.com/x/v2/reply/main?csrf=6e22efc1a47225ea25f901f922b5cfdd&mode=3&oid=254175381&pagination_str=%7B%22offset%22:%22%22%7D&plat=1&seek_rpid=0&type=11 // https://api.bilibili.com/x/v2/reply/main?csrf=6e22efc1a47225ea25f901f922b5cfdd&mode=3&oid=254175381&pagination_str=%7B%22offset%22:%22%22%7D&plat=1&seek_rpid=0&type=11
static const String replyList = '/x/v2/reply'; static const String replyList = '/x/v2/reply/main';
// 楼中楼 // 楼中楼
static const String replyReplyList = '/x/v2/reply/reply'; static const String replyReplyList = '/x/v2/reply/reply';

View File

@ -21,7 +21,6 @@ class HtmlHttp {
} }
try { try {
Document rootTree = parse(response.data); Document rootTree = parse(response.data);
// log(response.data.body.toString());
Element body = rootTree.body!; Element body = rootTree.body!;
Element appDom = body.querySelector('#app')!; Element appDom = body.querySelector('#app')!;
Element authorHeader = appDom.querySelector('.fixed-author-header')!; Element authorHeader = appDom.querySelector('.fixed-author-header')!;
@ -52,7 +51,6 @@ class HtmlHttp {
.className .className
.split(' ')[1] .split(' ')[1]
.split('-')[2]; .split('-')[2];
// List imgList = opusDetail.querySelectorAll('bili-album__preview__picture__img');
return { return {
'status': true, 'status': true,
'avatar': avatar, 'avatar': avatar,
@ -76,20 +74,10 @@ class HtmlHttp {
Element body = rootTree.body!; Element body = rootTree.body!;
Element appDom = body.querySelector('#app')!; Element appDom = body.querySelector('#app')!;
Element authorHeader = appDom.querySelector('.up-left')!; Element authorHeader = appDom.querySelector('.up-left')!;
// 头像
// String avatar =
// authorHeader.querySelector('.bili-avatar-img')!.attributes['data-src']!;
// print(avatar);
// avatar = 'https:${avatar.split('@')[0]}';
String uname = authorHeader.querySelector('.up-name')!.text.trim(); String uname = authorHeader.querySelector('.up-name')!.text.trim();
// 动态详情 // 动态详情
Element opusDetail = appDom.querySelector('.article-content')!; Element opusDetail = appDom.querySelector('.article-content')!;
// 发布时间 // 发布时间
// String updateTime =
// opusDetail.querySelector('.opus-module-author__pub__text')!.text;
// print(updateTime);
//
String opusContent = String opusContent =
opusDetail.querySelector('#read-article-holder')!.innerHtml; opusDetail.querySelector('#read-article-holder')!.innerHtml;
RegExp digitRegExp = RegExp(r'\d+'); RegExp digitRegExp = RegExp(r'\d+');

View File

@ -8,7 +8,6 @@ import 'package:cookie_jar/cookie_jar.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:dio/io.dart'; import 'package:dio/io.dart';
import 'package:dio_cookie_manager/dio_cookie_manager.dart'; import 'package:dio_cookie_manager/dio_cookie_manager.dart';
// import 'package:dio_http2_adapter/dio_http2_adapter.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:pilipala/utils/id_utils.dart'; import 'package:pilipala/utils/id_utils.dart';
import '../utils/storage.dart'; import '../utils/storage.dart';
@ -171,15 +170,6 @@ class Request {
dio = Dio(options); dio = Dio(options);
/// fix 第三方登录 302重定向 跟iOS代理问题冲突
// ..httpClientAdapter = Http2Adapter(
// ConnectionManager(
// idleTimeout: const Duration(milliseconds: 10000),
// onClientCreate: (_, ClientSetting config) =>
// config.onBadCertificate = (_) => true,
// ),
// );
/// 设置代理 /// 设置代理
if (enableSystemProxy) { if (enableSystemProxy) {
dio.httpClientAdapter = IOHttpClientAdapter( dio.httpClientAdapter = IOHttpClientAdapter(
@ -247,11 +237,26 @@ class Request {
} }
} }
/*
* get请求
*/
getWithoutCookie(url, {data}) {
return get(
url,
data: data,
options: Options(
headers: {
'cookie': 'buvid3= ; b_nut= ; sid= ',
'user-agent': headerUa(type: 'pc'),
},
),
);
}
/* /*
* post请求 * post请求
*/ */
post(url, {data, queryParameters, options, cancelToken, extra}) async { post(url, {data, queryParameters, options, cancelToken, extra}) async {
// print('post-data: $data');
Response response; Response response;
try { try {
response = await dio.post( response = await dio.post(
@ -262,7 +267,6 @@ class Request {
options ?? Options(contentType: Headers.formUrlEncodedContentType), options ?? Options(contentType: Headers.formUrlEncodedContentType),
cancelToken: cancelToken, cancelToken: cancelToken,
); );
// print('post success: ${response.data}');
return response; return response;
} on DioException catch (e) { } on DioException catch (e) {
Response errResponse = Response( Response errResponse = Response(
@ -318,7 +322,7 @@ class Request {
} }
} else { } else {
headerUa = headerUa =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.2 Safari/605.1.15'; 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36';
} }
return headerUa; return headerUa;
} }

View File

@ -3,8 +3,6 @@
import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:hive/hive.dart';
import '../utils/storage.dart';
class ApiInterceptor extends Interceptor { class ApiInterceptor extends Interceptor {
@override @override
@ -19,21 +17,7 @@ class ApiInterceptor extends Interceptor {
@override @override
void onResponse(Response response, ResponseInterceptorHandler handler) { void onResponse(Response response, ResponseInterceptorHandler handler) {
try { try {
if (response.statusCode == 302) { // 在响应之后处理数据
final List<String> locations = response.headers['location']!;
if (locations.isNotEmpty) {
if (locations.first.startsWith('https://www.mcbbs.net')) {
final Uri uri = Uri.parse(locations.first);
final String? accessKey = uri.queryParameters['access_key'];
final String? mid = uri.queryParameters['mid'];
try {
Box localCache = GStrorage.localCache;
localCache.put(LocalCacheKey.accessKey,
<String, String?>{'mid': mid, 'value': accessKey});
} catch (_) {}
}
}
}
} catch (err) { } catch (err) {
print('ApiInterceptor: $err'); print('ApiInterceptor: $err');
} }

View File

@ -278,10 +278,10 @@ class MsgHttp {
'data': MessageLikeModel.fromJson(res.data['data']), 'data': MessageLikeModel.fromJson(res.data['data']),
}; };
} catch (err) { } catch (err) {
return {'status': false, 'date': [], 'msg': err.toString()}; return {'status': false, 'data': [], 'msg': err.toString()};
} }
} else { } else {
return {'status': false, 'date': [], 'msg': res.data['message']}; return {'status': false, 'data': [], 'msg': res.data['message']};
} }
} }

View File

@ -1,3 +1,5 @@
import 'dart:convert';
import '../models/video/reply/data.dart'; import '../models/video/reply/data.dart';
import '../models/video/reply/emote.dart'; import '../models/video/reply/emote.dart';
import 'api.dart'; import 'api.dart';
@ -6,17 +8,16 @@ import 'init.dart';
class ReplyHttp { class ReplyHttp {
static Future replyList({ static Future replyList({
required int oid, required int oid,
required int pageNum, required String nextOffset,
required int type, required int type,
int? ps, int? ps,
int sort = 1, int sort = 1,
}) async { }) async {
var res = await Request().get(Api.replyList, data: { var res = await Request().get(Api.replyList, data: {
'oid': oid, 'oid': oid,
'pn': pageNum,
'type': type, 'type': type,
'sort': sort, 'pagination_str': jsonEncode({'offset': nextOffset}),
'ps': ps ?? 20 'mode': sort + 2,
}); });
if (res.data['code'] == 0) { if (res.data['code'] == 0) {
return { return {
@ -52,19 +53,13 @@ class ReplyHttp {
if (res.data['code'] == 0) { if (res.data['code'] == 0) {
return { return {
'status': true, 'status': true,
'data': ReplyData.fromJson(res.data['data']), 'data': ReplyReplyData.fromJson(res.data['data']),
}; };
} else { } else {
Map errMap = {
-400: '请求错误',
-404: '无此项',
12002: '评论区已关闭',
12009: '评论主体的type不合法',
};
return { return {
'status': false, 'status': false,
'date': [], 'date': [],
'msg': errMap[res.data['code']] ?? '请求异常', 'msg': res.data['message'],
}; };
} }
} }

View File

@ -1,10 +1,6 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:developer';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:html/parser.dart'; import 'package:html/parser.dart';
import 'package:pilipala/models/video/later.dart'; import 'package:pilipala/models/video/later.dart';
import '../common/constants.dart';
import '../models/model_hot_video_item.dart'; import '../models/model_hot_video_item.dart';
import '../models/user/fav_detail.dart'; import '../models/user/fav_detail.dart';
import '../models/user/fav_folder.dart'; import '../models/user/fav_folder.dart';
@ -218,25 +214,6 @@ class UserHttp {
} }
} }
// 获取用户凭证 失效
static Future thirdLogin() async {
var res = await Request().get(
'https://passport.bilibili.com/login/app/third',
data: {
'appkey': Constants.appKey,
'api': Constants.thirdApi,
'sign': Constants.thirdSign,
},
);
try {
if (res.data['code'] == 0 && res.data['data']['has_login'] == 1) {
Request().get(res.data['data']['confirm_uri']);
}
} catch (err) {
SmartDialog.showNotify(msg: '获取用户凭证: $err', notifyType: NotifyType.error);
}
}
// 清空稍后再看 // 清空稍后再看
static Future toViewClear() async { static Future toViewClear() async {
var res = await Request().post( var res = await Request().post(
@ -283,30 +260,6 @@ class UserHttp {
return {'status': false, 'msg': res.data['message']}; return {'status': false, 'msg': res.data['message']};
} }
} }
// // 相互关系查询
// static Future relationSearch(int mid) async {
// Map params = await WbiSign().makSign({
// 'mid': mid,
// 'token': '',
// 'platform': 'web',
// 'web_location': 1550101,
// });
// var res = await Request().get(
// Api.relationSearch,
// data: {
// 'mid': mid,
// 'w_rid': params['w_rid'],
// 'wts': params['wts'],
// },
// );
// if (res.data['code'] == 0) {
// // relation 主动状态
// // 被动状态
// return {'status': true, 'data': res.data['data']};
// } else {
// return {'status': false, 'msg': res.data['message']};
// }
// }
// 搜索历史记录 // 搜索历史记录
static Future searchHistory( static Future searchHistory(
@ -436,31 +389,6 @@ class UserHttp {
} }
} }
// 稍后再看播放全部
// static Future toViewPlayAll({required int oid, required String bvid}) async {
// var res = await Request().get(
// Api.watchLaterHtml,
// data: {
// 'oid': oid,
// 'bvid': bvid,
// },
// );
// String scriptContent =
// extractScriptContents(parse(res.data).body!.outerHtml)[0];
// int startIndex = scriptContent.indexOf('{');
// int endIndex = scriptContent.lastIndexOf('};');
// String jsonContent = scriptContent.substring(startIndex, endIndex + 1);
// // 解析JSON字符串为Map
// Map<String, dynamic> jsonData = json.decode(jsonContent);
// // 输出解析后的数据
// return {
// 'status': true,
// 'data': jsonData['resourceList']
// .map((e) => MediaVideoItemModel.fromJson(e))
// .toList()
// };
// }
static List<String> extractScriptContents(String htmlContent) { static List<String> extractScriptContents(String htmlContent) {
RegExp scriptRegExp = RegExp(r'<script>([\s\S]*?)<\/script>'); RegExp scriptRegExp = RegExp(r'<script>([\s\S]*?)<\/script>');
Iterable<Match> matches = scriptRegExp.allMatches(htmlContent); Iterable<Match> matches = scriptRegExp.allMatches(htmlContent);

View File

@ -1,6 +1,7 @@
class ReplyContent { class ReplyContent {
ReplyContent({ ReplyContent({
this.message, this.message,
this.message2,
this.atNameToMid, // @的用户的mid null this.atNameToMid, // @的用户的mid null
this.members, // 被@的用户List 如果有的话 [] this.members, // 被@的用户List 如果有的话 []
this.emote, // 表情包 如果有的话 null this.emote, // 表情包 如果有的话 null
@ -13,6 +14,7 @@ class ReplyContent {
}); });
String? message; String? message;
String? message2;
Map? atNameToMid; Map? atNameToMid;
List<MemberItemModel>? members; List<MemberItemModel>? members;
Map? emote; Map? emote;
@ -24,10 +26,17 @@ class ReplyContent {
Map? topicsMeta; Map? topicsMeta;
ReplyContent.fromJson(Map<String, dynamic> json) { ReplyContent.fromJson(Map<String, dynamic> json) {
message = json['message'] message = message2 = json['message']
.replaceAll('&gt;', '>') .replaceAll('&gt;', '>')
.replaceAll('&#34;', '"') .replaceAll('&#34;', '"')
.replaceAll('&#39;', "'"); .replaceAll('&#39;', "'")
.replaceAll('&amp;', '&')
.replaceAll('&lt;', '<')
.replaceAll('&gt;', '>')
.replaceAll('&quot;', '"')
.replaceAll('&apos;', "'")
.replaceAll('&nbsp;', ' ');
atNameToMid = json['at_name_to_mid'] ?? {}; atNameToMid = json['at_name_to_mid'] ?? {};
members = json['members'] != null members = json['members'] != null
? json['members'] ? json['members']
@ -39,8 +48,8 @@ class ReplyContent {
pictures = json['pictures'] ?? []; pictures = json['pictures'] ?? [];
vote = json['vote'] ?? {}; vote = json['vote'] ?? {};
richText = json['rich_text'] ?? {}; richText = json['rich_text'] ?? {};
// 不包含@ 笔记 图片的时候,文字可折叠 // 不包含@ 笔记的时候,文字可折叠
isText = atNameToMid!.isEmpty && vote!.isEmpty && pictures!.isEmpty; isText = atNameToMid!.isEmpty && vote!.isEmpty;
topicsMeta = json['topics_meta'] ?? {}; topicsMeta = json['topics_meta'] ?? {};
} }
} }

View File

@ -6,6 +6,98 @@ import 'upper.dart';
class ReplyData { class ReplyData {
ReplyData({ ReplyData({
this.cursor,
this.config,
this.replies,
this.topReplies,
this.upper,
});
ReplyCursor? cursor;
ReplyConfig? config;
late List<ReplyItemModel>? replies;
late List<ReplyItemModel>? topReplies;
ReplyUpper? upper;
ReplyData.fromJson(Map<String, dynamic> json) {
cursor = ReplyCursor.fromJson(json['cursor']);
config = ReplyConfig.fromJson(json['config']);
replies = json['replies'] != null
? json['replies']
.map<ReplyItemModel>(
(item) => ReplyItemModel.fromJson(item, json['upper']['mid']))
.toList()
: [];
topReplies = json['top_replies'] != null
? json['top_replies']
.map<ReplyItemModel>((item) => ReplyItemModel.fromJson(
item, json['upper']['mid'],
isTopStatus: true))
.toList()
: [];
upper = ReplyUpper.fromJson(json['upper']);
}
}
class ReplyCursor {
ReplyCursor({
this.isBegin,
this.prev,
this.next,
this.isEnd,
this.mode,
this.modeText,
this.allCount,
this.supportMode,
this.name,
this.paginationReply,
this.sessionId,
});
bool? isBegin;
int? prev;
int? next;
bool? isEnd;
int? mode;
String? modeText;
int? allCount;
List<int>? supportMode;
String? name;
PaginationReply? paginationReply;
String? sessionId;
ReplyCursor.fromJson(Map<String, dynamic> json) {
isBegin = json['is_begin'];
prev = json['prev'];
next = json['next'];
isEnd = json['is_end'];
mode = json['mode'];
modeText = json['mode_text'];
allCount = json['all_count'];
supportMode = json['support_mode'].cast<int>();
name = json['name'];
paginationReply = json['pagination_reply'] != null
? PaginationReply.fromJson(json['pagination_reply'])
: null;
sessionId = json['session_id'];
}
}
class PaginationReply {
PaginationReply({
this.nextOffset,
this.prevOffset,
});
String? nextOffset;
String? prevOffset;
PaginationReply.fromJson(Map<String, dynamic> json) {
nextOffset = json['next_offset'];
prevOffset = json['prev_offset'];
}
}
class ReplyReplyData {
ReplyReplyData({
this.page, this.page,
this.config, this.config,
this.replies, this.replies,
@ -19,7 +111,7 @@ class ReplyData {
late List<ReplyItemModel>? topReplies; late List<ReplyItemModel>? topReplies;
ReplyUpper? upper; ReplyUpper? upper;
ReplyData.fromJson(Map<String, dynamic> json) { ReplyReplyData.fromJson(Map<String, dynamic> json) {
page = ReplyPage.fromJson(json['page']); page = ReplyPage.fromJson(json['page']);
config = ReplyConfig.fromJson(json['config']); config = ReplyConfig.fromJson(json['config']);
replies = json['replies'] != null replies = json['replies'] != null

View File

@ -183,8 +183,10 @@ class _BangumiPageState extends State<BangumiPage>
return HttpError( return HttpError(
errMsg: data['msg'], errMsg: data['msg'],
fn: () { fn: () {
setState(() {
_futureBuilderFuture = _futureBuilderFuture =
_bangumidController.queryBangumiListFeed(); _bangumidController.queryBangumiListFeed();
});
}, },
); );
} }

View File

@ -77,10 +77,10 @@ class _BlackListPageState extends State<BlackListPage> {
List<BlackListItem> list = _blackListController.blackList; List<BlackListItem> list = _blackListController.blackList;
return Obx( return Obx(
() => list.isEmpty () => list.isEmpty
? CustomScrollView( ? HttpError(
slivers: [ errMsg: '你没有拉黑任何人哦_',
HttpError(errMsg: '你没有拉黑任何人哦_', fn: () => {}) fn: () => {},
], isInSliver: false,
) )
: ListView.builder( : ListView.builder(
controller: scrollController, controller: scrollController,
@ -119,13 +119,10 @@ class _BlackListPageState extends State<BlackListPage> {
), ),
); );
} else { } else {
return CustomScrollView( return HttpError(
slivers: [
HttpError(
errMsg: data['msg'], errMsg: data['msg'],
fn: () => _blackListController.queryBlacklist(), fn: () => _blackListController.queryBlacklist(),
) isInSliver: false,
],
); );
} }
} else { } else {

View File

@ -14,7 +14,7 @@ class DynamicDetailController extends GetxController {
int? type; int? type;
dynamic item; dynamic item;
int? floor; int? floor;
int currentPage = 0; String nextOffset = "";
bool isLoadingMore = false; bool isLoadingMore = false;
RxString noMore = ''.obs; RxString noMore = ''.obs;
RxList<ReplyItemModel> replyList = <ReplyItemModel>[].obs; RxList<ReplyItemModel> replyList = <ReplyItemModel>[].obs;
@ -49,25 +49,25 @@ class DynamicDetailController extends GetxController {
Future queryReplyList({reqType = 'init'}) async { Future queryReplyList({reqType = 'init'}) async {
if (reqType == 'init') { if (reqType == 'init') {
currentPage = 0; nextOffset = "";
} }
var res = await ReplyHttp.replyList( var res = await ReplyHttp.replyList(
oid: oid!, oid: oid!,
pageNum: currentPage + 1, nextOffset: nextOffset,
type: type!, type: type!,
sort: _sortType.index, sort: _sortType.index,
); );
if (res['status']) { if (res['status']) {
List<ReplyItemModel> replies = res['data'].replies; List<ReplyItemModel> replies = res['data'].replies;
acount.value = res['data'].page.acount; acount.value = res['data'].cursor.allCount;
nextOffset = res['data'].cursor.paginationReply.nextOffset ?? "";
if (replies.isNotEmpty) { if (replies.isNotEmpty) {
currentPage++;
noMore.value = '加载中...'; noMore.value = '加载中...';
if (replies.length < 20) { if (res['data'].cursor.isEnd == true) {
noMore.value = '没有更多了'; noMore.value = '没有更多了';
} }
} else { } else {
noMore.value = currentPage == 0 ? '还没有评论' : '没有更多了'; noMore.value = nextOffset == "" ? '还没有评论' : '没有更多了';
} }
if (reqType == 'init') { if (reqType == 'init') {
// 添加置顶回复 // 添加置顶回复

View File

@ -103,17 +103,14 @@ class _FansPageState extends State<FansPage> {
), ),
); );
} else { } else {
return CustomScrollView( return HttpError(
physics: const NeverScrollableScrollPhysics(),
slivers: [
HttpError(
errMsg: data['msg'], errMsg: data['msg'],
fn: () { fn: () {
_futureBuilderFuture = setState(() {
_fansController.queryFans('init'); _futureBuilderFuture = _fansController.queryFans('init');
});
}, },
) isInSliver: false,
],
); );
} }
} else { } else {

View File

@ -112,10 +112,7 @@ class _FavPageState extends State<FavPage> {
), ),
); );
} else { } else {
return CustomScrollView( return HttpError(
physics: const NeverScrollableScrollPhysics(),
slivers: [
HttpError(
errMsg: data?['msg'] ?? '请求异常', errMsg: data?['msg'] ?? '请求异常',
btnText: data?['code'] == -101 ? '去登录' : null, btnText: data?['code'] == -101 ? '去登录' : null,
fn: () { fn: () {
@ -127,8 +124,7 @@ class _FavPageState extends State<FavPage> {
}); });
} }
}, },
), isInSliver: false,
],
); );
} }
} else { } else {

View File

@ -94,13 +94,14 @@ class _FollowListState extends State<FollowList> {
: const CustomScrollView(slivers: [NoData()]), : const CustomScrollView(slivers: [NoData()]),
); );
} else { } else {
return CustomScrollView( return HttpError(
slivers: [
HttpError(
errMsg: data['msg'], errMsg: data['msg'],
fn: () => widget.ctr.queryFollowings('init'), fn: () {
) setState(() {
], _futureBuilderFuture = widget.ctr.queryFollowings('init');
});
},
isInSliver: false,
); );
} }
} else { } else {

View File

@ -112,13 +112,10 @@ class _OwnerFollowListState extends State<OwnerFollowList>
: const CustomScrollView(slivers: [NoData()]), : const CustomScrollView(slivers: [NoData()]),
); );
} else { } else {
return CustomScrollView( return HttpError(
slivers: [
HttpError(
errMsg: data['msg'], errMsg: data['msg'],
fn: () => widget.ctr.queryFollowings('init'), fn: () => widget.ctr.queryFollowings('init'),
) isInSliver: false,
],
); );
} }
} else { } else {

View File

@ -82,10 +82,10 @@ class _FollowSearchPageState extends State<FollowSearchPage> {
if (snapshot.connectionState == ConnectionState.done) { if (snapshot.connectionState == ConnectionState.done) {
var data = snapshot.data; var data = snapshot.data;
if (data == null) { if (data == null) {
return CustomScrollView( return HttpError(
slivers: [ errMsg: snapshot.data['msg'],
HttpError(errMsg: snapshot.data['msg'], fn: reRequest) fn: reRequest,
], isInSliver: false,
); );
} }
if (data['status']) { if (data['status']) {
@ -101,15 +101,17 @@ class _FollowSearchPageState extends State<FollowSearchPage> {
); );
}), }),
) )
: CustomScrollView( : HttpError(
slivers: [HttpError(errMsg: '未搜索到结果', fn: reRequest)], errMsg: '未搜索到结果',
fn: reRequest,
isInSliver: false,
), ),
); );
} else { } else {
return CustomScrollView( return HttpError(
slivers: [ errMsg: snapshot.data['msg'],
HttpError(errMsg: snapshot.data['msg'], fn: reRequest) fn: reRequest,
], isInSliver: false,
); );
} }
} else { } else {

View File

@ -15,7 +15,7 @@ class HtmlRenderController extends GetxController {
RxInt oid = (-1).obs; RxInt oid = (-1).obs;
late Map response; late Map response;
int? floor; int? floor;
int currentPage = 0; String nextOffset = "";
bool isLoadingMore = false; bool isLoadingMore = false;
RxString noMore = ''.obs; RxString noMore = ''.obs;
RxList<ReplyItemModel> replyList = <ReplyItemModel>[].obs; RxList<ReplyItemModel> replyList = <ReplyItemModel>[].obs;
@ -52,21 +52,21 @@ class HtmlRenderController extends GetxController {
Future queryReplyList({reqType = 'init'}) async { Future queryReplyList({reqType = 'init'}) async {
var res = await ReplyHttp.replyList( var res = await ReplyHttp.replyList(
oid: oid.value, oid: oid.value,
pageNum: currentPage + 1, nextOffset: nextOffset,
type: type, type: type,
sort: _sortType.index, sort: _sortType.index,
); );
if (res['status']) { if (res['status']) {
List<ReplyItemModel> replies = res['data'].replies; List<ReplyItemModel> replies = res['data'].replies;
acount.value = res['data'].page.acount; acount.value = res['data'].cursor.allCount;
nextOffset = res['data'].cursor.paginationReply.nextOffset ?? "";
if (replies.isNotEmpty) { if (replies.isNotEmpty) {
currentPage++;
noMore.value = '加载中...'; noMore.value = '加载中...';
if (replies.length < 20) { if (res['data'].cursor.isEnd == true) {
noMore.value = '没有更多了'; noMore.value = '没有更多了';
} }
} else { } else {
noMore.value = currentPage == 0 ? '还没有评论' : '没有更多了'; noMore.value = nextOffset == "" ? '还没有评论' : '没有更多了';
} }
if (reqType == 'init') { if (reqType == 'init') {
// 添加置顶回复 // 添加置顶回复
@ -102,7 +102,7 @@ class HtmlRenderController extends GetxController {
} }
sortTypeTitle.value = _sortType.titles; sortTypeTitle.value = _sortType.titles;
sortTypeLabel.value = _sortType.labels; sortTypeLabel.value = _sortType.labels;
currentPage = 0; nextOffset = "";
replyList.clear(); replyList.clear();
queryReplyList(reqType: 'init'); queryReplyList(reqType: 'init');
} }

View File

@ -380,13 +380,10 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
); );
} else { } else {
// 请求错误 // 请求错误
return CustomScrollView( return HttpError(
slivers: [
HttpError(
errMsg: data['msg'], errMsg: data['msg'],
fn: () => setState(() {}), fn: () => setState(() {}),
) isInSliver: false,
],
); );
} }
} else { } else {

View File

@ -22,11 +22,11 @@ class LaterController extends GetxController {
userInfo = userInfoCache.get('userInfoCache'); userInfo = userInfoCache.get('userInfoCache');
} }
Future queryLaterList() async { Future queryLaterList({type = 'init'}) async {
if (userInfo == null) { if (userInfo == null) {
return {'status': false, 'msg': '账号未登录', 'code': -101}; return {'status': false, 'msg': '账号未登录', 'code': -101};
} }
isLoading.value = true; isLoading.value = type == 'init';
var res = await UserHttp.seeYouLater(); var res = await UserHttp.seeYouLater();
if (res['status']) { if (res['status']) {
count = res['data']['count']; count = res['data']['count'];

View File

@ -66,8 +66,13 @@ class _LaterPageState extends State<LaterPage> {
const SizedBox(width: 8), const SizedBox(width: 8),
], ],
), ),
body: CustomScrollView( body: RefreshIndicator(
onRefresh: () async {
await _laterController.queryLaterList(type: 'onRefresh');
},
child: CustomScrollView(
controller: _laterController.scrollController, controller: _laterController.scrollController,
physics: const AlwaysScrollableScrollPhysics(),
slivers: [ slivers: [
FutureBuilder( FutureBuilder(
future: _futureBuilderFuture, future: _futureBuilderFuture,
@ -81,12 +86,13 @@ class _LaterPageState extends State<LaterPage> {
? SliverList( ? SliverList(
delegate: delegate:
SliverChildBuilderDelegate((context, index) { SliverChildBuilderDelegate((context, index) {
var videoItem = _laterController.laterList[index]; var videoItem =
_laterController.laterList[index];
return VideoCardH( return VideoCardH(
videoItem: videoItem, videoItem: videoItem,
source: 'later', source: 'later',
onPressedFn: () => _laterController.toViewDel( onPressedFn: () => _laterController
aid: videoItem.aid)); .toViewDel(aid: videoItem.aid));
}, childCount: _laterController.laterList.length), }, childCount: _laterController.laterList.length),
) )
: _laterController.isLoading.value : _laterController.isLoading.value
@ -128,6 +134,7 @@ class _LaterPageState extends State<LaterPage> {
) )
], ],
), ),
),
floatingActionButton: Obx( floatingActionButton: Obx(
() => _laterController.laterList.isNotEmpty () => _laterController.laterList.isNotEmpty
? FloatingActionButton.extended( ? FloatingActionButton.extended(

View File

@ -11,6 +11,7 @@ import 'package:pilipala/pages/media/index.dart';
import 'package:pilipala/pages/rank/index.dart'; import 'package:pilipala/pages/rank/index.dart';
import 'package:pilipala/utils/event_bus.dart'; import 'package:pilipala/utils/event_bus.dart';
import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/global_data_cache.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
import './controller.dart'; import './controller.dart';
@ -126,6 +127,7 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
double sheetHeight = MediaQuery.sizeOf(context).height - double sheetHeight = MediaQuery.sizeOf(context).height -
MediaQuery.of(context).padding.top - MediaQuery.of(context).padding.top -
MediaQuery.sizeOf(context).width * 9 / 16; MediaQuery.sizeOf(context).width * 9 / 16;
GlobalDataCache().sheetHeight = sheetHeight;
localCache.put('sheetHeight', sheetHeight); localCache.put('sheetHeight', sheetHeight);
localCache.put('statusBarHeight', statusBarHeight); localCache.put('statusBarHeight', statusBarHeight);

View File

@ -138,16 +138,10 @@ class _MemberArticlePageState extends State<MemberArticlePage> {
} }
Widget _buildError(String errMsg) { Widget _buildError(String errMsg) {
return CustomScrollView( return HttpError(
physics: const NeverScrollableScrollPhysics(),
slivers: [
SliverToBoxAdapter(
child: HttpError(
errMsg: errMsg, errMsg: errMsg,
fn: () {}, fn: () {},
), isInSliver: false,
),
],
); );
} }

View File

@ -164,13 +164,10 @@ class _MemberSearchPageState extends State<MemberSearchPage>
), ),
); );
} else { } else {
return CustomScrollView( return HttpError(
slivers: <Widget>[
HttpError(
errMsg: data['msg'], errMsg: data['msg'],
fn: () => setState(() {}), fn: () => setState(() {}),
) isInSliver: false,
],
); );
} }
} else { } else {

View File

@ -85,18 +85,14 @@ class _MessageLikePageState extends State<MessageLikePage> {
); );
} else { } else {
// 请求错误 // 请求错误
return CustomScrollView( return HttpError(
slivers: [
HttpError(
errMsg: snapshot.data['msg'], errMsg: snapshot.data['msg'],
fn: () { fn: () {
setState(() { setState(() {
_futureBuilderFuture = _futureBuilderFuture = _messageLikeCtr.queryMessageLike();
_messageLikeCtr.queryMessageLike();
}); });
}, },
) isInSliver: false,
],
); );
} }
} else { } else {

View File

@ -82,9 +82,7 @@ class _MessageReplyPageState extends State<MessageReplyPage> {
); );
} else { } else {
// 请求错误 // 请求错误
return CustomScrollView( return HttpError(
slivers: [
HttpError(
errMsg: snapshot.data['msg'], errMsg: snapshot.data['msg'],
fn: () { fn: () {
setState(() { setState(() {
@ -92,8 +90,7 @@ class _MessageReplyPageState extends State<MessageReplyPage> {
_messageReplyCtr.queryMessageReply(); _messageReplyCtr.queryMessageReply();
}); });
}, },
) isInSliver: false,
],
); );
} }
} else { } else {

View File

@ -63,9 +63,7 @@ class _MessageSystemPageState extends State<MessageSystemPage> {
); );
} else { } else {
// 请求错误 // 请求错误
return CustomScrollView( return HttpError(
slivers: [
HttpError(
errMsg: snapshot.data['msg'], errMsg: snapshot.data['msg'],
fn: () { fn: () {
setState(() { setState(() {
@ -73,8 +71,7 @@ class _MessageSystemPageState extends State<MessageSystemPage> {
_messageSystemCtr.queryMessageSystem(); _messageSystemCtr.queryMessageSystem();
}); });
}, },
) isInSliver: false,
],
); );
} }
} else { } else {

View File

@ -1,10 +1,13 @@
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:get/get_rx/src/rx_workers/utils/debouncer.dart'; import 'package:get/get_rx/src/rx_workers/utils/debouncer.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:pilipala/http/search.dart'; import 'package:pilipala/http/search.dart';
import 'package:pilipala/models/search/hot.dart'; import 'package:pilipala/models/search/hot.dart';
import 'package:pilipala/models/search/suggest.dart'; import 'package:pilipala/models/search/suggest.dart';
import 'package:pilipala/utils/global_data_cache.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
class SSearchController extends GetxController { class SSearchController extends GetxController {
@ -12,7 +15,7 @@ class SSearchController extends GetxController {
RxString searchKeyWord = ''.obs; RxString searchKeyWord = ''.obs;
Rx<TextEditingController> controller = TextEditingController().obs; Rx<TextEditingController> controller = TextEditingController().obs;
RxList<HotSearchItem> hotSearchList = <HotSearchItem>[].obs; RxList<HotSearchItem> hotSearchList = <HotSearchItem>[].obs;
Box histiryWord = GStrorage.historyword; Box localCache = GStrorage.localCache;
List historyCacheList = []; List historyCacheList = [];
RxList historyList = [].obs; RxList historyList = [].obs;
RxList<SearchSuggestItem> searchSuggestList = <SearchSuggestItem>[].obs; RxList<SearchSuggestItem> searchSuggestList = <SearchSuggestItem>[].obs;
@ -22,48 +25,55 @@ class SSearchController extends GetxController {
RxString defaultSearch = ''.obs; RxString defaultSearch = ''.obs;
Box setting = GStrorage.setting; Box setting = GStrorage.setting;
bool enableHotKey = true; bool enableHotKey = true;
bool enableSearchSuggest = true;
late StreamController<bool> clearStream = StreamController<bool>.broadcast();
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
// 其他页面跳转过来 // 其他页面跳转过来
if (Get.parameters.keys.isNotEmpty) { final parameters = Get.parameters;
if (Get.parameters['keyword'] != null) { if (parameters.keys.isNotEmpty) {
onClickKeyword(Get.parameters['keyword']!); final keyword = parameters['keyword'];
if (keyword != null) {
onClickKeyword(keyword);
} }
if (Get.parameters['hintText'] != null) {
hintText = Get.parameters['hintText']!; final hint = parameters['hintText'];
if (hint != null) {
hintText = hint;
searchKeyWord.value = hintText; searchKeyWord.value = hintText;
} }
} }
historyCacheList = histiryWord.get('cacheList') ?? []; historyCacheList = GlobalDataCache().historyCacheList;
historyList.value = historyCacheList; historyList.value = historyCacheList;
enableHotKey = setting.get(SettingBoxKey.enableHotKey, defaultValue: true); enableHotKey = setting.get(SettingBoxKey.enableHotKey, defaultValue: true);
enableSearchSuggest = GlobalDataCache().enableSearchSuggest;
} }
void onChange(value) { void onChange(value) {
searchKeyWord.value = value; searchKeyWord.value = value;
if (value == '') { if (value == '') {
searchSuggestList.value = []; searchSuggestList.value = [];
clearStream.add(false);
return; return;
} }
clearStream.add(true);
if (enableSearchSuggest) {
_debouncer.call(() => querySearchSuggest(value)); _debouncer.call(() => querySearchSuggest(value));
} }
}
void onClear() { void onClear() {
if (searchKeyWord.value.isNotEmpty && controller.value.text != '') {
controller.value.clear(); controller.value.clear();
searchKeyWord.value = ''; searchKeyWord.value = '';
searchSuggestList.value = []; searchSuggestList.value = [];
} else { clearStream.add(false);
Get.back();
}
} }
// 搜索 // 搜索
void submit() { void submit() {
// ignore: unrelated_type_equality_checks if (searchKeyWord.value == '') {
if (searchKeyWord == '') {
return; return;
} }
List arr = historyCacheList.where((e) => e != searchKeyWord.value).toList(); List arr = historyCacheList.where((e) => e != searchKeyWord.value).toList();
@ -73,7 +83,7 @@ class SSearchController extends GetxController {
historyList.value = historyCacheList; historyList.value = historyCacheList;
// 手动刷新 // 手动刷新
historyList.refresh(); historyList.refresh();
histiryWord.put('cacheList', historyCacheList); localCache.put('cacheList', historyCacheList);
searchFocusNode.unfocus(); searchFocusNode.unfocus();
Get.toNamed('/searchResult', parameters: {'keyword': searchKeyWord.value}); Get.toNamed('/searchResult', parameters: {'keyword': searchKeyWord.value});
} }
@ -117,13 +127,14 @@ class SSearchController extends GetxController {
int index = historyList.indexOf(word); int index = historyList.indexOf(word);
historyList.removeAt(index); historyList.removeAt(index);
historyList.refresh(); historyList.refresh();
histiryWord.put('cacheList', historyList); localCache.put('cacheList', historyList);
} }
onClearHis() { onClearHis() {
historyList.value = []; historyList.value = [];
historyCacheList = []; historyCacheList = [];
historyList.refresh(); historyList.refresh();
histiryWord.put('cacheList', []); localCache.put('cacheList', []);
SmartDialog.showToast('搜索历史已清空');
} }
} }

View File

@ -1,4 +1,3 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/common/widgets/http_error.dart';
@ -54,7 +53,7 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
actions: [ actions: [
IconButton( IconButton(
onPressed: () => _searchController.submit(), onPressed: () => _searchController.submit(),
icon: const Icon(CupertinoIcons.search, size: 22), icon: const Icon(Icons.search),
), ),
const SizedBox(width: 10) const SizedBox(width: 10)
], ],
@ -68,13 +67,19 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
decoration: InputDecoration( decoration: InputDecoration(
hintText: _searchController.hintText, hintText: _searchController.hintText,
border: InputBorder.none, border: InputBorder.none,
suffixIcon: IconButton( suffixIcon: StreamBuilder(
icon: Icon( initialData: false,
Icons.clear, stream: _searchController.clearStream.stream,
size: 22, builder: (_, snapshot) {
color: Theme.of(context).colorScheme.outline, if (snapshot.data == true) {
), return IconButton(
icon: const Icon(Icons.clear, size: 22),
onPressed: () => _searchController.onClear(), onPressed: () => _searchController.onClear(),
);
} else {
return const SizedBox();
}
},
), ),
), ),
onSubmitted: (String value) => _searchController.submit(), onSubmitted: (String value) => _searchController.submit(),
@ -84,7 +89,7 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
body: SingleChildScrollView( body: SingleChildScrollView(
child: Column( child: Column(
children: [ children: [
const SizedBox(height: 12), const SizedBox(height: 6),
// 搜索建议 // 搜索建议
_searchSuggest(), _searchSuggest(),
// 热搜 // 热搜
@ -135,7 +140,7 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.fromLTRB(6, 0, 6, 6), padding: const EdgeInsets.fromLTRB(6, 0, 6, 4),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
@ -153,7 +158,7 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
padding: MaterialStateProperty.all(const EdgeInsets.only( padding: MaterialStateProperty.all(const EdgeInsets.only(
left: 10, top: 6, bottom: 6, right: 10)), left: 10, top: 6, bottom: 6, right: 10)),
), ),
onPressed: () => ctr.queryHotSearchList(), onPressed: ctr.queryHotSearchList,
icon: const Icon(Icons.refresh_outlined, size: 18), icon: const Icon(Icons.refresh_outlined, size: 18),
label: const Text('刷新'), label: const Text('刷新'),
), ),
@ -187,13 +192,10 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
), ),
); );
} else { } else {
return CustomScrollView( return HttpError(
slivers: [
HttpError(
errMsg: data['msg'], errMsg: data['msg'],
fn: () => setState(() {}), fn: () => setState(() {}),
) isInSliver: false,
],
); );
} }
} else { } else {
@ -202,6 +204,7 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
return HotKeyword( return HotKeyword(
width: width, width: width,
hotSearchList: _searchController.hotSearchList, hotSearchList: _searchController.hotSearchList,
onClick: () {},
); );
} else { } else {
return const SizedBox(); return const SizedBox();
@ -220,13 +223,13 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
return Obx( return Obx(
() => Container( () => Container(
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.fromLTRB(10, 25, 6, 0), padding: const EdgeInsets.fromLTRB(10, 20, 4, 0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (_searchController.historyList.isNotEmpty) if (_searchController.historyList.isNotEmpty)
Padding( Padding(
padding: const EdgeInsets.fromLTRB(6, 0, 0, 2), padding: const EdgeInsets.fromLTRB(6, 0, 6, 8),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
@ -237,10 +240,19 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
.titleMedium! .titleMedium!
.copyWith(fontWeight: FontWeight.bold), .copyWith(fontWeight: FontWeight.bold),
), ),
TextButton( SizedBox(
onPressed: () => _searchController.onClearHis(), height: 34,
child: const Text('清空'), child: TextButton.icon(
) style: ButtonStyle(
padding: MaterialStateProperty.all(
const EdgeInsets.only(
left: 10, top: 6, bottom: 6, right: 10)),
),
onPressed: _searchController.onClearHis,
icon: const Icon(Icons.clear_all_outlined, size: 18),
label: const Text('清空'),
),
),
], ],
), ),
), ),

View File

@ -1,15 +1,15 @@
// ignore: file_names
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class HotKeyword extends StatelessWidget { class HotKeyword extends StatelessWidget {
final double? width; final double width;
final List? hotSearchList; final List hotSearchList;
final Function? onClick; final Function onClick;
const HotKeyword({ const HotKeyword({
this.width, required this.width,
this.hotSearchList, required this.hotSearchList,
this.onClick, required this.onClick,
super.key, super.key,
}); });
@ -18,45 +18,67 @@ class HotKeyword extends StatelessWidget {
return Wrap( return Wrap(
runSpacing: 0.4, runSpacing: 0.4,
spacing: 5.0, spacing: 5.0,
children: [ children: hotSearchList.map((item) {
for (var i in hotSearchList!) return HotKeywordItem(
SizedBox( width: width,
width: width! / 2 - 4, item: item,
onClick: onClick,
isRightPadding: hotSearchList.indexOf(item) % 2 == 1,
);
}).toList(),
);
}
}
class HotKeywordItem extends StatelessWidget {
final double width;
final dynamic item;
final Function onClick;
final bool isRightPadding;
const HotKeywordItem({
required this.width,
required this.item,
required this.onClick,
required this.isRightPadding,
super.key,
});
@override
Widget build(BuildContext context) {
return SizedBox(
width: width / 2 - 4,
child: Material( child: Material(
borderRadius: BorderRadius.circular(3), borderRadius: BorderRadius.circular(4),
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
child: InkWell( child: InkWell(
onTap: () => onClick!(i.keyword), onTap: () => onClick.call(item.keyword),
child: Padding( child: Padding(
padding: EdgeInsets.only( padding: EdgeInsets.only(left: 2, right: isRightPadding ? 10 : 0),
left: 2,
right: hotSearchList!.indexOf(i) % 2 == 1 ? 10 : 0),
child: Row( child: Row(
children: [ children: [
Flexible( Flexible(
child: Padding( child: Padding(
padding: const EdgeInsets.fromLTRB(6, 5, 4, 5), padding: const EdgeInsets.fromLTRB(6, 5, 4, 5),
child: Text( child: Text(
i.keyword!, item.keyword,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
maxLines: 1, maxLines: 1,
style: const TextStyle(fontSize: 14), style: const TextStyle(fontSize: 14),
), ),
), ),
), ),
if (i.icon != null && i.icon != '') if (item.icon != null && item.icon != '')
SizedBox( SizedBox(
height: 15, height: 15,
child: CachedNetworkImage( child:
imageUrl: i.icon!, height: 15.0), CachedNetworkImage(imageUrl: item.icon!, height: 15.0),
), ),
], ],
), ),
), ),
), ),
), ),
),
],
); );
} }
} }

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pilipala/utils/feed_back.dart';
class SearchText extends StatelessWidget { class SearchText extends StatelessWidget {
final String? searchText; final String? searchText;
@ -17,30 +18,31 @@ class SearchText extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ColorScheme colorScheme = Theme.of(context).colorScheme;
return Material( return Material(
color: isSelect color: isSelect
? Theme.of(context).colorScheme.primaryContainer ? colorScheme.primaryContainer
: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.5), : colorScheme.surfaceVariant.withOpacity(0.5),
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
child: Padding( child: Padding(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
child: InkWell( child: InkWell(
onTap: () { onTap: () {
onSelect!(searchText); onSelect?.call(searchText);
}, },
onLongPress: () { onLongPress: () {
onLongSelect!(searchText); feedBack();
onLongSelect?.call(searchText);
}, },
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
child: Padding( child: Padding(
padding: padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 11),
const EdgeInsets.only(top: 5, bottom: 5, left: 11, right: 11),
child: Text( child: Text(
searchText!, searchText!,
style: TextStyle( style: TextStyle(
color: isSelect color: isSelect
? Theme.of(context).colorScheme.primary ? colorScheme.primary
: Theme.of(context).colorScheme.onSurfaceVariant, : colorScheme.onSurfaceVariant,
), ),
), ),
), ),

View File

@ -109,33 +109,25 @@ class _SearchPanelState extends State<SearchPanel>
} }
}); });
} else { } else {
return CustomScrollView( return HttpError(
physics: const NeverScrollableScrollPhysics(),
slivers: [
HttpError(
errMsg: data['msg'], errMsg: data['msg'],
fn: () { fn: () {
setState(() { setState(() {
_searchPanelController.onSearch(); _searchPanelController.onSearch();
}); });
}, },
), isInSliver: false,
],
); );
} }
} else { } else {
return CustomScrollView( return HttpError(
physics: const NeverScrollableScrollPhysics(),
slivers: [
HttpError(
errMsg: '没有相关数据', errMsg: '没有相关数据',
fn: () { fn: () {
setState(() { setState(() {
_searchPanelController.onSearch(); _searchPanelController.onSearch();
}); });
}, },
), isInSliver: false,
],
); );
} }
} else { } else {

View File

@ -174,14 +174,11 @@ Widget searchArticlePanel(BuildContext context, ctr, list) {
); );
}, },
) )
: CustomScrollView( : HttpError(
slivers: [
HttpError(
errMsg: '没有数据', errMsg: '没有数据',
isShowBtn: false, isShowBtn: false,
fn: () => {}, fn: () => {},
) isInSliver: false,
],
), ),
); );
} }

View File

@ -46,14 +46,11 @@ class SearchVideoPanel extends StatelessWidget {
); );
}, },
) )
: CustomScrollView( : HttpError(
slivers: [
HttpError(
errMsg: '没有数据', errMsg: '没有数据',
isShowBtn: false, isShowBtn: false,
fn: () => {}, fn: () => {},
) isInSliver: false,
],
), ),
), ),
// 分类筛选 // 分类筛选

View File

@ -5,6 +5,7 @@ import 'package:hive/hive.dart';
import 'package:pilipala/models/common/dynamics_type.dart'; import 'package:pilipala/models/common/dynamics_type.dart';
import 'package:pilipala/models/common/reply_sort_type.dart'; import 'package:pilipala/models/common/reply_sort_type.dart';
import 'package:pilipala/pages/setting/widgets/select_dialog.dart'; import 'package:pilipala/pages/setting/widgets/select_dialog.dart';
import 'package:pilipala/utils/global_data_cache.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
import '../home/index.dart'; import '../home/index.dart';
@ -146,6 +147,15 @@ class _ExtraSettingState extends State<ExtraSetting> {
setKey: SettingBoxKey.enableHotKey, setKey: SettingBoxKey.enableHotKey,
defaultVal: true, defaultVal: true,
), ),
SetSwitchItem(
title: '展示搜索建议',
subTitle: '输入搜索内容时展示建议词',
setKey: SettingBoxKey.enableSearchSuggest,
defaultVal: true,
callFn: (val) {
GlobalDataCache().enableSearchSuggest = val;
},
),
SetSwitchItem( SetSwitchItem(
title: '搜索默认词', title: '搜索默认词',
subTitle: '是否展示搜索框默认词', subTitle: '是否展示搜索框默认词',

View File

@ -68,16 +68,15 @@ class _SubPageState extends State<SubPage> {
), ),
); );
} else { } else {
return const CustomScrollView( return const HttpError(
physics: NeverScrollableScrollPhysics(), errMsg: '',
slivers: [HttpError(errMsg: '', btnText: '没有数据', fn: null)], btnText: '没有数据',
fn: null,
isInSliver: false,
); );
} }
} else { } else {
return CustomScrollView( return HttpError(
physics: const NeverScrollableScrollPhysics(),
slivers: [
HttpError(
errMsg: data?['msg'] ?? '请求异常', errMsg: data?['msg'] ?? '请求异常',
btnText: data?['code'] == -101 ? '去登录' : null, btnText: data?['code'] == -101 ? '去登录' : null,
fn: () { fn: () {
@ -85,13 +84,11 @@ class _SubPageState extends State<SubPage> {
RoutePush.loginRedirectPush(); RoutePush.loginRedirectPush();
} else { } else {
setState(() { setState(() {
_futureBuilderFuture = _futureBuilderFuture = _subController.querySubFolder();
_subController.querySubFolder();
}); });
} }
}, },
), isInSliver: false,
],
); );
} }
} else { } else {

View File

@ -242,6 +242,12 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
showBottomSheet( showBottomSheet(
context: context, context: context,
enableDrag: true, enableDrag: true,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(25),
topRight: Radius.circular(25),
),
),
builder: (BuildContext context) { builder: (BuildContext context) {
return AiDetail(modelResult: videoIntroController.modelResult); return AiDetail(modelResult: videoIntroController.modelResult);
}, },

View File

@ -21,11 +21,9 @@ class VideoReplyController extends GetxController {
// rpid 请求楼中楼回复 // rpid 请求楼中楼回复
String? rpid; String? rpid;
RxList<ReplyItemModel> replyList = <ReplyItemModel>[].obs; RxList<ReplyItemModel> replyList = <ReplyItemModel>[].obs;
// 当前页 String nextOffset = "";
int currentPage = 0;
bool isLoadingMore = false; bool isLoadingMore = false;
RxString noMore = ''.obs; RxString noMore = ''.obs;
int ps = 20;
RxInt count = 0.obs; RxInt count = 0.obs;
// 当前回复的回复 // 当前回复的回复
ReplyItemModel? currentReplyItem; ReplyItemModel? currentReplyItem;
@ -57,7 +55,7 @@ class VideoReplyController extends GetxController {
} }
isLoadingMore = true; isLoadingMore = true;
if (type == 'init') { if (type == 'init') {
currentPage = 0; nextOffset = '';
noMore.value = ''; noMore.value = '';
} }
if (noMore.value == '没有更多了') { if (noMore.value == '没有更多了') {
@ -66,28 +64,20 @@ class VideoReplyController extends GetxController {
} }
final res = await ReplyHttp.replyList( final res = await ReplyHttp.replyList(
oid: aid!, oid: aid!,
pageNum: currentPage + 1, nextOffset: nextOffset,
ps: ps,
type: ReplyType.video.index, type: ReplyType.video.index,
sort: _sortType.index, sort: _sortType.index,
); );
if (res['status']) { if (res['status']) {
final List<ReplyItemModel> replies = res['data'].replies; final List<ReplyItemModel> replies = res['data'].replies;
nextOffset = res['data'].cursor.paginationReply.nextOffset ?? "";
if (replies.isNotEmpty) { if (replies.isNotEmpty) {
noMore.value = '加载中...'; noMore.value = '加载中...';
if (res['data'].cursor.isEnd == true) {
/// 第一页回复数小于20
if (currentPage == 0 && replies.length < 18) {
noMore.value = '没有更多了';
}
currentPage++;
if (replyList.length == res['data'].page.acount) {
noMore.value = '没有更多了'; noMore.value = '没有更多了';
} }
} else { } else {
// 未登录状态replies可能返回null noMore.value = nextOffset == "" ? '还没有评论' : '没有更多了';
noMore.value = currentPage == 0 ? '还没有评论' : '没有更多了';
} }
if (type == 'init') { if (type == 'init') {
// 添加置顶回复 // 添加置顶回复
@ -99,7 +89,7 @@ class VideoReplyController extends GetxController {
} }
} }
replies.insertAll(0, res['data'].topReplies); replies.insertAll(0, res['data'].topReplies);
count.value = res['data'].page.count; count.value = res['data'].cursor.allCount;
replyList.value = replies; replyList.value = replies;
} else { } else {
replyList.addAll(replies); replyList.addAll(replies);
@ -130,7 +120,7 @@ class VideoReplyController extends GetxController {
} }
sortTypeTitle.value = _sortType.titles; sortTypeTitle.value = _sortType.titles;
sortTypeLabel.value = _sortType.labels; sortTypeLabel.value = _sortType.labels;
currentPage = 0; nextOffset = "";
noMore.value = ''; noMore.value = '';
replyList.clear(); replyList.clear();
queryReplyList(type: 'init'); queryReplyList(type: 'init');

View File

@ -238,11 +238,28 @@ class ReplyItem extends StatelessWidget {
// title // title
Container( Container(
margin: const EdgeInsets.only(top: 10, left: 45, right: 6, bottom: 4), margin: const EdgeInsets.only(top: 10, left: 45, right: 6, bottom: 4),
child: Text.rich( child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints boxConstraints) {
String text = replyItem?.content?.message ?? '';
bool didExceedMaxLines = false;
final double maxWidth = boxConstraints.maxWidth;
TextPainter? textPainter;
final int maxLines =
replyItem!.content!.isText! && replyLevel == '1' ? 6 : 999;
try {
textPainter = TextPainter(
text: TextSpan(text: text),
maxLines: maxLines,
textDirection: Directionality.of(context),
);
textPainter.layout(maxWidth: maxWidth);
didExceedMaxLines = textPainter.didExceedMaxLines;
} catch (e) {
debugPrint('Error while measuring text: $e');
didExceedMaxLines = false;
}
return Text.rich(
style: const TextStyle(height: 1.75), style: const TextStyle(height: 1.75),
maxLines:
replyItem!.content!.isText! && replyLevel == '1' ? 3 : 999,
overflow: TextOverflow.ellipsis,
TextSpan( TextSpan(
children: [ children: [
if (replyItem!.isTop!) if (replyItem!.isTop!)
@ -256,10 +273,18 @@ class ReplyItem extends StatelessWidget {
fs: 9, fs: 9,
), ),
), ),
buildContent(context, replyItem!, replyReply, null), buildContent(
context,
replyItem!,
replyReply,
null,
didExceedMaxLines,
textPainter,
),
], ],
), ),
), );
}),
), ),
// 操作区域 // 操作区域
bottonAction(context, replyItem!.replyControl, replySave), bottonAction(context, replyItem!.replyControl, replySave),
@ -465,8 +490,8 @@ class ReplyItemRow extends StatelessWidget {
fs: 9, fs: 9,
), ),
), ),
buildContent( buildContent(context, replies![i], replyReply,
context, replies![i], replyReply, replyItem), replyItem, false, null),
], ],
), ),
), ),
@ -508,7 +533,13 @@ class ReplyItemRow extends StatelessWidget {
} }
InlineSpan buildContent( InlineSpan buildContent(
BuildContext context, replyItem, replyReply, fReplyItem) { BuildContext context,
replyItem,
replyReply,
fReplyItem,
bool didExceedMaxLines,
TextPainter? textPainter,
) {
final String routePath = Get.currentRoute; final String routePath = Get.currentRoute;
bool isVideoPage = routePath.startsWith('/video'); bool isVideoPage = routePath.startsWith('/video');
ColorScheme colorScheme = Theme.of(context).colorScheme; ColorScheme colorScheme = Theme.of(context).colorScheme;
@ -519,6 +550,25 @@ InlineSpan buildContent(
final content = replyItem.content; final content = replyItem.content;
final List<InlineSpan> spanChilds = <InlineSpan>[]; final List<InlineSpan> spanChilds = <InlineSpan>[];
if (didExceedMaxLines && content.message != '') {
final textSize = textPainter!.size;
var position = textPainter.getPositionForOffset(
Offset(
textSize.width,
textSize.height,
),
);
final endOffset = textPainter.getOffsetBefore(position.offset);
if (endOffset != null && endOffset > 0) {
content.message = content.message.substring(0, endOffset);
} else {
content.message = content.message.substring(0, position.offset);
}
} else {
content.message = content.message2;
}
// 投票 // 投票
if (content.vote.isNotEmpty) { if (content.vote.isNotEmpty) {
content.message.splitMapJoin(RegExp(r"\{vote:.*?\}"), content.message.splitMapJoin(RegExp(r"\{vote:.*?\}"),
@ -547,13 +597,6 @@ InlineSpan buildContent(
}); });
} }
content.message = content.message.replaceAll(RegExp(r"\{vote:.*?\}"), ' '); content.message = content.message.replaceAll(RegExp(r"\{vote:.*?\}"), ' ');
content.message = content.message
.replaceAll('&amp;', '&')
.replaceAll('&lt;', '<')
.replaceAll('&gt;', '>')
.replaceAll('&quot;', '"')
.replaceAll('&apos;', "'")
.replaceAll('&nbsp;', ' ');
// 构建正则表达式 // 构建正则表达式
final List<String> specialTokens = [ final List<String> specialTokens = [
...content.emote.keys, ...content.emote.keys,
@ -874,6 +917,18 @@ InlineSpan buildContent(
} }
} }
} }
if (didExceedMaxLines) {
spanChilds.add(
TextSpan(
text: '\n查看更多',
style: TextStyle(
color: colorScheme.primary,
),
),
);
}
// 图片渲染 // 图片渲染
if (content.pictures.isNotEmpty) { if (content.pictures.isNotEmpty) {
final List<String> picList = <String>[]; final List<String> picList = <String>[];

View File

@ -1,16 +1,11 @@
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/models/video/ai.dart'; import 'package:pilipala/models/video/ai.dart';
import 'package:pilipala/pages/video/detail/index.dart'; import 'package:pilipala/pages/video/detail/index.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/global_data_cache.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';
Box localCache = GStrorage.localCache;
late double sheetHeight;
class AiDetail extends StatelessWidget { class AiDetail extends StatelessWidget {
final ModelResult? modelResult; final ModelResult? modelResult;
@ -21,47 +16,58 @@ class AiDetail extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
sheetHeight = localCache.get('sheetHeight');
return Container( return Container(
color: Theme.of(context).colorScheme.surface, padding: const EdgeInsets.only(left: 16, right: 16),
padding: const EdgeInsets.only(left: 14, right: 14), height: GlobalDataCache().sheetHeight,
height: sheetHeight,
child: Column( child: Column(
children: [ children: [
InkWell( _buildHeader(context),
onTap: () => Get.back(),
child: Container(
height: 35,
padding: const EdgeInsets.only(bottom: 2),
child: Center(
child: Container(
width: 32,
height: 3,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary,
borderRadius: const BorderRadius.all(Radius.circular(3)),
),
),
),
),
),
Expanded( Expanded(
child: SingleChildScrollView( child: SingleChildScrollView(
child: Column( child: Column(
children: [ children: [
if (modelResult!.resultType != 0 && if (modelResult!.summary != '') ...[
modelResult!.summary != '') ...[ _buildSummaryText(modelResult!.summary!),
SelectableText(
modelResult!.summary!,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
height: 1.5,
),
),
const SizedBox(height: 20), const SizedBox(height: 20),
], ],
ListView.builder( _buildOutlineList(context),
],
),
),
),
],
),
);
}
Widget _buildHeader(BuildContext context) {
return Center(
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).hintColor,
borderRadius: const BorderRadius.all(Radius.circular(10)),
),
height: 4,
width: 40,
margin: const EdgeInsets.symmetric(vertical: 16),
),
);
}
Widget _buildSummaryText(String summary) {
return SelectableText(
summary,
textAlign: TextAlign.justify,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
height: 1.6,
),
);
}
Widget _buildOutlineList(BuildContext context) {
return ListView.builder(
shrinkWrap: true, shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemCount: modelResult!.outline!.length, itemCount: modelResult!.outline!.length,
@ -70,155 +76,77 @@ class AiDetail extends StatelessWidget {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SelectableText( _buildOutlineTitle(outline.title!),
outline.title!, const SizedBox(height: 20),
_buildPartOutlineList(context, outline.partOutline!),
],
);
},
);
}
Widget _buildOutlineTitle(String title) {
return SelectableText(
title,
textAlign: TextAlign.justify,
style: const TextStyle( style: const TextStyle(
fontSize: 14, fontSize: 16,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
height: 1.5, height: 1.5,
), ),
), );
const SizedBox(height: 6), }
ListView.builder(
Widget _buildPartOutlineList(
BuildContext context, List<PartOutline> partOutline) {
return ListView.builder(
shrinkWrap: true, shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemCount: outline.partOutline!.length, itemCount: partOutline.length,
itemBuilder: (context, i) { itemBuilder: (context, i) {
final part = outline.partOutline![i]; final part = partOutline[i];
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
GestureDetector( _buildPartText(context, part),
onTap: () { const SizedBox(height: 20),
],
);
},
);
}
void _onPartTap(BuildContext context, int timestamp) {
try { try {
final controller = final controller = Get.find<VideoDetailController>(
Get.find<VideoDetailController>(
tag: Get.arguments['heroTag'], tag: Get.arguments['heroTag'],
); );
controller.plPlayerController.seekTo( controller.plPlayerController.seekTo(
Duration( Duration(seconds: timestamp),
seconds: Utils.duration(
Utils.tampToSeektime(
part.timestamp!),
).toInt(),
),
); );
} catch (_) {} } catch (_) {}
}, }
child: SelectableText.rich(
Widget _buildPartText(BuildContext context, PartOutline part) {
return SelectableText.rich(
TextSpan( TextSpan(
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 15,
color: Theme.of(context) color: Theme.of(context).colorScheme.onSurface,
.colorScheme
.onSurface,
height: 1.5,
), ),
children: [ children: [
TextSpan( TextSpan(
text: Utils.tampToSeektime( text: Utils.tampToSeektime(part.timestamp!),
part.timestamp!),
style: TextStyle( style: TextStyle(
color: Theme.of(context) color: Theme.of(context).colorScheme.primary,
.colorScheme
.primary,
), ),
recognizer: TapGestureRecognizer()
..onTap = () => _onPartTap(context, part.timestamp!),
), ),
const TextSpan(text: ' '), const TextSpan(text: ' '),
TextSpan(text: part.content!), TextSpan(text: part.content!),
], ],
), ),
),
),
const SizedBox(height: 20),
],
);
},
),
],
);
},
)
],
),
),
),
],
),
); );
} }
InlineSpan buildContent(BuildContext context, content) {
List descV2 = content.descV2;
// type
// 1 普通文本
// 2 @用户
List<TextSpan> spanChilds = List.generate(descV2.length, (index) {
final currentDesc = descV2[index];
switch (currentDesc.type) {
case 1:
List<InlineSpan> spanChildren = [];
RegExp urlRegExp = RegExp(r'https?://\S+\b');
Iterable<Match> matches = urlRegExp.allMatches(currentDesc.rawText);
int previousEndIndex = 0;
for (Match match in matches) {
if (match.start > previousEndIndex) {
spanChildren.add(TextSpan(
text: currentDesc.rawText
.substring(previousEndIndex, match.start)));
}
spanChildren.add(
TextSpan(
text: match.group(0),
style: TextStyle(
color: Theme.of(context).colorScheme.primary), // 设置颜色为蓝色
recognizer: TapGestureRecognizer()
..onTap = () {
// 处理点击事件
try {
Get.toNamed(
'/webview',
parameters: {
'url': match.group(0)!,
'type': 'url',
'pageTitle': match.group(0)!,
},
);
} catch (err) {
SmartDialog.showToast(err.toString());
}
},
),
);
previousEndIndex = match.end;
}
if (previousEndIndex < currentDesc.rawText.length) {
spanChildren.add(TextSpan(
text: currentDesc.rawText.substring(previousEndIndex)));
}
TextSpan result = TextSpan(children: spanChildren);
return result;
case 2:
final colorSchemePrimary = Theme.of(context).colorScheme.primary;
final heroTag = Utils.makeHeroTag(currentDesc.bizId);
return TextSpan(
text: '@${currentDesc.rawText}',
style: TextStyle(color: colorSchemePrimary),
recognizer: TapGestureRecognizer()
..onTap = () {
Get.toNamed(
'/member?mid=${currentDesc.bizId}',
arguments: {'face': '', 'heroTag': heroTag},
);
},
);
default:
return const TextSpan();
}
});
return TextSpan(children: spanChilds);
}
} }

View File

@ -15,6 +15,7 @@ class GlobalDataCache {
late FullScreenGestureMode fullScreenGestureMode; late FullScreenGestureMode fullScreenGestureMode;
late bool enablePlayerControlAnimation; late bool enablePlayerControlAnimation;
late List<String> actionTypeSort; late List<String> actionTypeSort;
late double sheetHeight;
String? wWebid; String? wWebid;
/// 播放器相关 /// 播放器相关
@ -44,6 +45,10 @@ class GlobalDataCache {
late List<double> speedsList; late List<double> speedsList;
// 用户信息 // 用户信息
UserInfoData? userInfo; UserInfoData? userInfo;
// 搜索历史
late List historyCacheList;
//
late bool enableSearchSuggest = true;
// 私有构造函数 // 私有构造函数
GlobalDataCache._(); GlobalDataCache._();
@ -103,5 +108,9 @@ class GlobalDataCache {
speedsList.addAll(playSpeedSystem); speedsList.addAll(playSpeedSystem);
userInfo = userInfoCache.get('userInfoCache'); userInfo = userInfoCache.get('userInfoCache');
sheetHeight = localCache.get('sheetHeight', defaultValue: 0.0);
historyCacheList = localCache.get('cacheList', defaultValue: []);
enableSearchSuggest =
setting.get(SettingBoxKey.enableSearchSuggest, defaultValue: true);
} }
} }

View File

@ -5,7 +5,6 @@ import 'package:pilipala/models/user/info.dart';
class GStrorage { class GStrorage {
static late final Box<dynamic> userInfo; static late final Box<dynamic> userInfo;
static late final Box<dynamic> historyword;
static late final Box<dynamic> localCache; static late final Box<dynamic> localCache;
static late final Box<dynamic> setting; static late final Box<dynamic> setting;
static late final Box<dynamic> video; static late final Box<dynamic> video;
@ -26,18 +25,11 @@ class GStrorage {
localCache = await Hive.openBox( localCache = await Hive.openBox(
'localCache', 'localCache',
compactionStrategy: (int entries, int deletedEntries) { compactionStrategy: (int entries, int deletedEntries) {
return deletedEntries > 4; return deletedEntries > 10;
}, },
); );
// 设置 // 设置
setting = await Hive.openBox('setting'); setting = await Hive.openBox('setting');
// 搜索历史
historyword = await Hive.openBox(
'historyWord',
compactionStrategy: (int entries, int deletedEntries) {
return deletedEntries > 10;
},
);
// 视频设置 // 视频设置
video = await Hive.openBox('video'); video = await Hive.openBox('video');
} }
@ -52,8 +44,6 @@ class GStrorage {
// user.close(); // user.close();
userInfo.compact(); userInfo.compact();
userInfo.close(); userInfo.close();
historyword.compact();
historyword.close();
localCache.compact(); localCache.compact();
localCache.close(); localCache.close();
setting.compact(); setting.compact();
@ -117,6 +107,7 @@ class SettingBoxKey {
replySortType = 'replySortType', replySortType = 'replySortType',
defaultDynamicType = 'defaultDynamicType', defaultDynamicType = 'defaultDynamicType',
enableHotKey = 'enableHotKey', enableHotKey = 'enableHotKey',
enableSearchSuggest = 'enableSearchSuggest',
enableQuickFav = 'enableQuickFav', enableQuickFav = 'enableQuickFav',
enableWordRe = 'enableWordRe', enableWordRe = 'enableWordRe',
enableSearchWord = 'enableSearchWord', enableSearchWord = 'enableSearchWord',

View File

@ -351,6 +351,9 @@ class Utils {
// 时间戳转时间 // 时间戳转时间
static tampToSeektime(number) { static tampToSeektime(number) {
if (number is String && int.tryParse(number) == null) {
return number;
}
int hours = number ~/ 60; int hours = number ~/ 60;
int minutes = number % 60; int minutes = number % 60;