Merge branch 'design'
This commit is contained in:
@ -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';
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
@ -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';
|
||||||
|
|||||||
@ -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+');
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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']};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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'],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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('>', '>')
|
.replaceAll('>', '>')
|
||||||
.replaceAll('"', '"')
|
.replaceAll('"', '"')
|
||||||
.replaceAll(''', "'");
|
.replaceAll(''', "'")
|
||||||
|
.replaceAll('&', '&')
|
||||||
|
.replaceAll('<', '<')
|
||||||
|
.replaceAll('>', '>')
|
||||||
|
.replaceAll('"', '"')
|
||||||
|
.replaceAll(''', "'")
|
||||||
|
.replaceAll(' ', ' ');
|
||||||
|
|
||||||
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'] ?? {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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') {
|
||||||
// 添加置顶回复
|
// 添加置顶回复
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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'];
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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);
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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('搜索历史已清空');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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('清空'),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -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),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -174,14 +174,11 @@ Widget searchArticlePanel(BuildContext context, ctr, list) {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
: CustomScrollView(
|
: HttpError(
|
||||||
slivers: [
|
|
||||||
HttpError(
|
|
||||||
errMsg: '没有数据',
|
errMsg: '没有数据',
|
||||||
isShowBtn: false,
|
isShowBtn: false,
|
||||||
fn: () => {},
|
fn: () => {},
|
||||||
)
|
isInSliver: false,
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -46,14 +46,11 @@ class SearchVideoPanel extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
: CustomScrollView(
|
: HttpError(
|
||||||
slivers: [
|
|
||||||
HttpError(
|
|
||||||
errMsg: '没有数据',
|
errMsg: '没有数据',
|
||||||
isShowBtn: false,
|
isShowBtn: false,
|
||||||
fn: () => {},
|
fn: () => {},
|
||||||
)
|
isInSliver: false,
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// 分类筛选
|
// 分类筛选
|
||||||
|
|||||||
@ -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: '是否展示搜索框默认词',
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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');
|
||||||
|
|||||||
@ -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('&', '&')
|
|
||||||
.replaceAll('<', '<')
|
|
||||||
.replaceAll('>', '>')
|
|
||||||
.replaceAll('"', '"')
|
|
||||||
.replaceAll(''', "'")
|
|
||||||
.replaceAll(' ', ' ');
|
|
||||||
// 构建正则表达式
|
// 构建正则表达式
|
||||||
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>[];
|
||||||
|
|||||||
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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',
|
||||||
|
|||||||
@ -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;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user