Merge branch 'design'
This commit is contained in:
@ -183,8 +183,10 @@ class _BangumiPageState extends State<BangumiPage>
|
||||
return HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () {
|
||||
_futureBuilderFuture =
|
||||
_bangumidController.queryBangumiListFeed();
|
||||
setState(() {
|
||||
_futureBuilderFuture =
|
||||
_bangumidController.queryBangumiListFeed();
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@ -77,10 +77,10 @@ class _BlackListPageState extends State<BlackListPage> {
|
||||
List<BlackListItem> list = _blackListController.blackList;
|
||||
return Obx(
|
||||
() => list.isEmpty
|
||||
? CustomScrollView(
|
||||
slivers: [
|
||||
HttpError(errMsg: '你没有拉黑任何人哦~_~', fn: () => {})
|
||||
],
|
||||
? HttpError(
|
||||
errMsg: '你没有拉黑任何人哦~_~',
|
||||
fn: () => {},
|
||||
isInSliver: false,
|
||||
)
|
||||
: ListView.builder(
|
||||
controller: scrollController,
|
||||
@ -119,13 +119,10 @@ class _BlackListPageState extends State<BlackListPage> {
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () => _blackListController.queryBlacklist(),
|
||||
)
|
||||
],
|
||||
return HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () => _blackListController.queryBlacklist(),
|
||||
isInSliver: false,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -14,7 +14,7 @@ class DynamicDetailController extends GetxController {
|
||||
int? type;
|
||||
dynamic item;
|
||||
int? floor;
|
||||
int currentPage = 0;
|
||||
String nextOffset = "";
|
||||
bool isLoadingMore = false;
|
||||
RxString noMore = ''.obs;
|
||||
RxList<ReplyItemModel> replyList = <ReplyItemModel>[].obs;
|
||||
@ -49,25 +49,25 @@ class DynamicDetailController extends GetxController {
|
||||
|
||||
Future queryReplyList({reqType = 'init'}) async {
|
||||
if (reqType == 'init') {
|
||||
currentPage = 0;
|
||||
nextOffset = "";
|
||||
}
|
||||
var res = await ReplyHttp.replyList(
|
||||
oid: oid!,
|
||||
pageNum: currentPage + 1,
|
||||
nextOffset: nextOffset,
|
||||
type: type!,
|
||||
sort: _sortType.index,
|
||||
);
|
||||
if (res['status']) {
|
||||
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) {
|
||||
currentPage++;
|
||||
noMore.value = '加载中...';
|
||||
if (replies.length < 20) {
|
||||
if (res['data'].cursor.isEnd == true) {
|
||||
noMore.value = '没有更多了';
|
||||
}
|
||||
} else {
|
||||
noMore.value = currentPage == 0 ? '还没有评论' : '没有更多了';
|
||||
noMore.value = nextOffset == "" ? '还没有评论' : '没有更多了';
|
||||
}
|
||||
if (reqType == 'init') {
|
||||
// 添加置顶回复
|
||||
|
||||
@ -103,17 +103,14 @@ class _FansPageState extends State<FansPage> {
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return CustomScrollView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () {
|
||||
_futureBuilderFuture =
|
||||
_fansController.queryFans('init');
|
||||
},
|
||||
)
|
||||
],
|
||||
return HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () {
|
||||
setState(() {
|
||||
_futureBuilderFuture = _fansController.queryFans('init');
|
||||
});
|
||||
},
|
||||
isInSliver: false,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -112,23 +112,19 @@ class _FavPageState extends State<FavPage> {
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return CustomScrollView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
HttpError(
|
||||
errMsg: data?['msg'] ?? '请求异常',
|
||||
btnText: data?['code'] == -101 ? '去登录' : null,
|
||||
fn: () {
|
||||
if (data?['code'] == -101) {
|
||||
RoutePush.loginRedirectPush();
|
||||
} else {
|
||||
setState(() {
|
||||
_futureBuilderFuture = _favController.queryFavFolder();
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
return HttpError(
|
||||
errMsg: data?['msg'] ?? '请求异常',
|
||||
btnText: data?['code'] == -101 ? '去登录' : null,
|
||||
fn: () {
|
||||
if (data?['code'] == -101) {
|
||||
RoutePush.loginRedirectPush();
|
||||
} else {
|
||||
setState(() {
|
||||
_futureBuilderFuture = _favController.queryFavFolder();
|
||||
});
|
||||
}
|
||||
},
|
||||
isInSliver: false,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -94,13 +94,14 @@ class _FollowListState extends State<FollowList> {
|
||||
: const CustomScrollView(slivers: [NoData()]),
|
||||
);
|
||||
} else {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () => widget.ctr.queryFollowings('init'),
|
||||
)
|
||||
],
|
||||
return HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () {
|
||||
setState(() {
|
||||
_futureBuilderFuture = widget.ctr.queryFollowings('init');
|
||||
});
|
||||
},
|
||||
isInSliver: false,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -112,13 +112,10 @@ class _OwnerFollowListState extends State<OwnerFollowList>
|
||||
: const CustomScrollView(slivers: [NoData()]),
|
||||
);
|
||||
} else {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () => widget.ctr.queryFollowings('init'),
|
||||
)
|
||||
],
|
||||
return HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () => widget.ctr.queryFollowings('init'),
|
||||
isInSliver: false,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -82,10 +82,10 @@ class _FollowSearchPageState extends State<FollowSearchPage> {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
var data = snapshot.data;
|
||||
if (data == null) {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
HttpError(errMsg: snapshot.data['msg'], fn: reRequest)
|
||||
],
|
||||
return HttpError(
|
||||
errMsg: snapshot.data['msg'],
|
||||
fn: reRequest,
|
||||
isInSliver: false,
|
||||
);
|
||||
}
|
||||
if (data['status']) {
|
||||
@ -101,15 +101,17 @@ class _FollowSearchPageState extends State<FollowSearchPage> {
|
||||
);
|
||||
}),
|
||||
)
|
||||
: CustomScrollView(
|
||||
slivers: [HttpError(errMsg: '未搜索到结果', fn: reRequest)],
|
||||
: HttpError(
|
||||
errMsg: '未搜索到结果',
|
||||
fn: reRequest,
|
||||
isInSliver: false,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
HttpError(errMsg: snapshot.data['msg'], fn: reRequest)
|
||||
],
|
||||
return HttpError(
|
||||
errMsg: snapshot.data['msg'],
|
||||
fn: reRequest,
|
||||
isInSliver: false,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -15,7 +15,7 @@ class HtmlRenderController extends GetxController {
|
||||
RxInt oid = (-1).obs;
|
||||
late Map response;
|
||||
int? floor;
|
||||
int currentPage = 0;
|
||||
String nextOffset = "";
|
||||
bool isLoadingMore = false;
|
||||
RxString noMore = ''.obs;
|
||||
RxList<ReplyItemModel> replyList = <ReplyItemModel>[].obs;
|
||||
@ -52,21 +52,21 @@ class HtmlRenderController extends GetxController {
|
||||
Future queryReplyList({reqType = 'init'}) async {
|
||||
var res = await ReplyHttp.replyList(
|
||||
oid: oid.value,
|
||||
pageNum: currentPage + 1,
|
||||
nextOffset: nextOffset,
|
||||
type: type,
|
||||
sort: _sortType.index,
|
||||
);
|
||||
if (res['status']) {
|
||||
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) {
|
||||
currentPage++;
|
||||
noMore.value = '加载中...';
|
||||
if (replies.length < 20) {
|
||||
if (res['data'].cursor.isEnd == true) {
|
||||
noMore.value = '没有更多了';
|
||||
}
|
||||
} else {
|
||||
noMore.value = currentPage == 0 ? '还没有评论' : '没有更多了';
|
||||
noMore.value = nextOffset == "" ? '还没有评论' : '没有更多了';
|
||||
}
|
||||
if (reqType == 'init') {
|
||||
// 添加置顶回复
|
||||
@ -102,7 +102,7 @@ class HtmlRenderController extends GetxController {
|
||||
}
|
||||
sortTypeTitle.value = _sortType.titles;
|
||||
sortTypeLabel.value = _sortType.labels;
|
||||
currentPage = 0;
|
||||
nextOffset = "";
|
||||
replyList.clear();
|
||||
queryReplyList(reqType: 'init');
|
||||
}
|
||||
|
||||
@ -380,13 +380,10 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
|
||||
);
|
||||
} else {
|
||||
// 请求错误
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () => setState(() {}),
|
||||
)
|
||||
],
|
||||
return HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () => setState(() {}),
|
||||
isInSliver: false,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -22,11 +22,11 @@ class LaterController extends GetxController {
|
||||
userInfo = userInfoCache.get('userInfoCache');
|
||||
}
|
||||
|
||||
Future queryLaterList() async {
|
||||
Future queryLaterList({type = 'init'}) async {
|
||||
if (userInfo == null) {
|
||||
return {'status': false, 'msg': '账号未登录', 'code': -101};
|
||||
}
|
||||
isLoading.value = true;
|
||||
isLoading.value = type == 'init';
|
||||
var res = await UserHttp.seeYouLater();
|
||||
if (res['status']) {
|
||||
count = res['data']['count'];
|
||||
|
||||
@ -66,67 +66,74 @@ class _LaterPageState extends State<LaterPage> {
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
),
|
||||
body: CustomScrollView(
|
||||
controller: _laterController.scrollController,
|
||||
slivers: [
|
||||
FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
Map? data = snapshot.data;
|
||||
if (data != null && data['status']) {
|
||||
return Obx(
|
||||
() => _laterController.laterList.isNotEmpty &&
|
||||
!_laterController.isLoading.value
|
||||
? SliverList(
|
||||
delegate:
|
||||
SliverChildBuilderDelegate((context, index) {
|
||||
var videoItem = _laterController.laterList[index];
|
||||
return VideoCardH(
|
||||
videoItem: videoItem,
|
||||
source: 'later',
|
||||
onPressedFn: () => _laterController.toViewDel(
|
||||
aid: videoItem.aid));
|
||||
}, childCount: _laterController.laterList.length),
|
||||
)
|
||||
: _laterController.isLoading.value
|
||||
? const SliverToBoxAdapter(
|
||||
child: Center(child: Text('加载中')),
|
||||
)
|
||||
: const NoData(),
|
||||
);
|
||||
body: RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
await _laterController.queryLaterList(type: 'onRefresh');
|
||||
},
|
||||
child: CustomScrollView(
|
||||
controller: _laterController.scrollController,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
Map? data = snapshot.data;
|
||||
if (data != null && data['status']) {
|
||||
return Obx(
|
||||
() => _laterController.laterList.isNotEmpty &&
|
||||
!_laterController.isLoading.value
|
||||
? SliverList(
|
||||
delegate:
|
||||
SliverChildBuilderDelegate((context, index) {
|
||||
var videoItem =
|
||||
_laterController.laterList[index];
|
||||
return VideoCardH(
|
||||
videoItem: videoItem,
|
||||
source: 'later',
|
||||
onPressedFn: () => _laterController
|
||||
.toViewDel(aid: videoItem.aid));
|
||||
}, childCount: _laterController.laterList.length),
|
||||
)
|
||||
: _laterController.isLoading.value
|
||||
? const SliverToBoxAdapter(
|
||||
child: Center(child: Text('加载中')),
|
||||
)
|
||||
: const NoData(),
|
||||
);
|
||||
} else {
|
||||
return HttpError(
|
||||
errMsg: data?['msg'] ?? '请求异常',
|
||||
btnText: data?['code'] == -101 ? '去登录' : null,
|
||||
fn: () {
|
||||
if (data?['code'] == -101) {
|
||||
RoutePush.loginRedirectPush();
|
||||
} else {
|
||||
setState(() {
|
||||
_futureBuilderFuture =
|
||||
_laterController.queryLaterList();
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return HttpError(
|
||||
errMsg: data?['msg'] ?? '请求异常',
|
||||
btnText: data?['code'] == -101 ? '去登录' : null,
|
||||
fn: () {
|
||||
if (data?['code'] == -101) {
|
||||
RoutePush.loginRedirectPush();
|
||||
} else {
|
||||
setState(() {
|
||||
_futureBuilderFuture =
|
||||
_laterController.queryLaterList();
|
||||
});
|
||||
}
|
||||
},
|
||||
// 骨架屏
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
return const VideoCardHSkeleton();
|
||||
}, childCount: 10),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// 骨架屏
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
return const VideoCardHSkeleton();
|
||||
}, childCount: 10),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: MediaQuery.of(context).padding.bottom + 10,
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: MediaQuery.of(context).padding.bottom + 10,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
floatingActionButton: Obx(
|
||||
() => _laterController.laterList.isNotEmpty
|
||||
|
||||
@ -11,6 +11,7 @@ import 'package:pilipala/pages/media/index.dart';
|
||||
import 'package:pilipala/pages/rank/index.dart';
|
||||
import 'package:pilipala/utils/event_bus.dart';
|
||||
import 'package:pilipala/utils/feed_back.dart';
|
||||
import 'package:pilipala/utils/global_data_cache.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
import './controller.dart';
|
||||
|
||||
@ -126,6 +127,7 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
||||
double sheetHeight = MediaQuery.sizeOf(context).height -
|
||||
MediaQuery.of(context).padding.top -
|
||||
MediaQuery.sizeOf(context).width * 9 / 16;
|
||||
GlobalDataCache().sheetHeight = sheetHeight;
|
||||
localCache.put('sheetHeight', sheetHeight);
|
||||
localCache.put('statusBarHeight', statusBarHeight);
|
||||
|
||||
|
||||
@ -138,16 +138,10 @@ class _MemberArticlePageState extends State<MemberArticlePage> {
|
||||
}
|
||||
|
||||
Widget _buildError(String errMsg) {
|
||||
return CustomScrollView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: HttpError(
|
||||
errMsg: errMsg,
|
||||
fn: () {},
|
||||
),
|
||||
),
|
||||
],
|
||||
return HttpError(
|
||||
errMsg: errMsg,
|
||||
fn: () {},
|
||||
isInSliver: false,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -164,13 +164,10 @@ class _MemberSearchPageState extends State<MemberSearchPage>
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return CustomScrollView(
|
||||
slivers: <Widget>[
|
||||
HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () => setState(() {}),
|
||||
)
|
||||
],
|
||||
return HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () => setState(() {}),
|
||||
isInSliver: false,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -85,18 +85,14 @@ class _MessageLikePageState extends State<MessageLikePage> {
|
||||
);
|
||||
} else {
|
||||
// 请求错误
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
HttpError(
|
||||
errMsg: snapshot.data['msg'],
|
||||
fn: () {
|
||||
setState(() {
|
||||
_futureBuilderFuture =
|
||||
_messageLikeCtr.queryMessageLike();
|
||||
});
|
||||
},
|
||||
)
|
||||
],
|
||||
return HttpError(
|
||||
errMsg: snapshot.data['msg'],
|
||||
fn: () {
|
||||
setState(() {
|
||||
_futureBuilderFuture = _messageLikeCtr.queryMessageLike();
|
||||
});
|
||||
},
|
||||
isInSliver: false,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -82,18 +82,15 @@ class _MessageReplyPageState extends State<MessageReplyPage> {
|
||||
);
|
||||
} else {
|
||||
// 请求错误
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
HttpError(
|
||||
errMsg: snapshot.data['msg'],
|
||||
fn: () {
|
||||
setState(() {
|
||||
_futureBuilderFuture =
|
||||
_messageReplyCtr.queryMessageReply();
|
||||
});
|
||||
},
|
||||
)
|
||||
],
|
||||
return HttpError(
|
||||
errMsg: snapshot.data['msg'],
|
||||
fn: () {
|
||||
setState(() {
|
||||
_futureBuilderFuture =
|
||||
_messageReplyCtr.queryMessageReply();
|
||||
});
|
||||
},
|
||||
isInSliver: false,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -63,18 +63,15 @@ class _MessageSystemPageState extends State<MessageSystemPage> {
|
||||
);
|
||||
} else {
|
||||
// 请求错误
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
HttpError(
|
||||
errMsg: snapshot.data['msg'],
|
||||
fn: () {
|
||||
setState(() {
|
||||
_futureBuilderFuture =
|
||||
_messageSystemCtr.queryMessageSystem();
|
||||
});
|
||||
},
|
||||
)
|
||||
],
|
||||
return HttpError(
|
||||
errMsg: snapshot.data['msg'],
|
||||
fn: () {
|
||||
setState(() {
|
||||
_futureBuilderFuture =
|
||||
_messageSystemCtr.queryMessageSystem();
|
||||
});
|
||||
},
|
||||
isInSliver: false,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -1,10 +1,13 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:get/get_rx/src/rx_workers/utils/debouncer.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/http/search.dart';
|
||||
import 'package:pilipala/models/search/hot.dart';
|
||||
import 'package:pilipala/models/search/suggest.dart';
|
||||
import 'package:pilipala/utils/global_data_cache.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
|
||||
class SSearchController extends GetxController {
|
||||
@ -12,7 +15,7 @@ class SSearchController extends GetxController {
|
||||
RxString searchKeyWord = ''.obs;
|
||||
Rx<TextEditingController> controller = TextEditingController().obs;
|
||||
RxList<HotSearchItem> hotSearchList = <HotSearchItem>[].obs;
|
||||
Box histiryWord = GStrorage.historyword;
|
||||
Box localCache = GStrorage.localCache;
|
||||
List historyCacheList = [];
|
||||
RxList historyList = [].obs;
|
||||
RxList<SearchSuggestItem> searchSuggestList = <SearchSuggestItem>[].obs;
|
||||
@ -22,48 +25,55 @@ class SSearchController extends GetxController {
|
||||
RxString defaultSearch = ''.obs;
|
||||
Box setting = GStrorage.setting;
|
||||
bool enableHotKey = true;
|
||||
bool enableSearchSuggest = true;
|
||||
late StreamController<bool> clearStream = StreamController<bool>.broadcast();
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
// 其他页面跳转过来
|
||||
if (Get.parameters.keys.isNotEmpty) {
|
||||
if (Get.parameters['keyword'] != null) {
|
||||
onClickKeyword(Get.parameters['keyword']!);
|
||||
final parameters = Get.parameters;
|
||||
if (parameters.keys.isNotEmpty) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
historyCacheList = histiryWord.get('cacheList') ?? [];
|
||||
historyCacheList = GlobalDataCache().historyCacheList;
|
||||
historyList.value = historyCacheList;
|
||||
enableHotKey = setting.get(SettingBoxKey.enableHotKey, defaultValue: true);
|
||||
enableSearchSuggest = GlobalDataCache().enableSearchSuggest;
|
||||
}
|
||||
|
||||
void onChange(value) {
|
||||
searchKeyWord.value = value;
|
||||
if (value == '') {
|
||||
searchSuggestList.value = [];
|
||||
clearStream.add(false);
|
||||
return;
|
||||
}
|
||||
_debouncer.call(() => querySearchSuggest(value));
|
||||
clearStream.add(true);
|
||||
if (enableSearchSuggest) {
|
||||
_debouncer.call(() => querySearchSuggest(value));
|
||||
}
|
||||
}
|
||||
|
||||
void onClear() {
|
||||
if (searchKeyWord.value.isNotEmpty && controller.value.text != '') {
|
||||
controller.value.clear();
|
||||
searchKeyWord.value = '';
|
||||
searchSuggestList.value = [];
|
||||
} else {
|
||||
Get.back();
|
||||
}
|
||||
controller.value.clear();
|
||||
searchKeyWord.value = '';
|
||||
searchSuggestList.value = [];
|
||||
clearStream.add(false);
|
||||
}
|
||||
|
||||
// 搜索
|
||||
void submit() {
|
||||
// ignore: unrelated_type_equality_checks
|
||||
if (searchKeyWord == '') {
|
||||
if (searchKeyWord.value == '') {
|
||||
return;
|
||||
}
|
||||
List arr = historyCacheList.where((e) => e != searchKeyWord.value).toList();
|
||||
@ -73,7 +83,7 @@ class SSearchController extends GetxController {
|
||||
historyList.value = historyCacheList;
|
||||
// 手动刷新
|
||||
historyList.refresh();
|
||||
histiryWord.put('cacheList', historyCacheList);
|
||||
localCache.put('cacheList', historyCacheList);
|
||||
searchFocusNode.unfocus();
|
||||
Get.toNamed('/searchResult', parameters: {'keyword': searchKeyWord.value});
|
||||
}
|
||||
@ -117,13 +127,14 @@ class SSearchController extends GetxController {
|
||||
int index = historyList.indexOf(word);
|
||||
historyList.removeAt(index);
|
||||
historyList.refresh();
|
||||
histiryWord.put('cacheList', historyList);
|
||||
localCache.put('cacheList', historyList);
|
||||
}
|
||||
|
||||
onClearHis() {
|
||||
historyList.value = [];
|
||||
historyCacheList = [];
|
||||
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:get/get.dart';
|
||||
import 'package:pilipala/common/widgets/http_error.dart';
|
||||
@ -54,7 +53,7 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () => _searchController.submit(),
|
||||
icon: const Icon(CupertinoIcons.search, size: 22),
|
||||
icon: const Icon(Icons.search),
|
||||
),
|
||||
const SizedBox(width: 10)
|
||||
],
|
||||
@ -68,13 +67,19 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
|
||||
decoration: InputDecoration(
|
||||
hintText: _searchController.hintText,
|
||||
border: InputBorder.none,
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
Icons.clear,
|
||||
size: 22,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
onPressed: () => _searchController.onClear(),
|
||||
suffixIcon: StreamBuilder(
|
||||
initialData: false,
|
||||
stream: _searchController.clearStream.stream,
|
||||
builder: (_, snapshot) {
|
||||
if (snapshot.data == true) {
|
||||
return IconButton(
|
||||
icon: const Icon(Icons.clear, size: 22),
|
||||
onPressed: () => _searchController.onClear(),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
onSubmitted: (String value) => _searchController.submit(),
|
||||
@ -84,7 +89,7 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 12),
|
||||
const SizedBox(height: 6),
|
||||
// 搜索建议
|
||||
_searchSuggest(),
|
||||
// 热搜
|
||||
@ -135,7 +140,7 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(6, 0, 6, 6),
|
||||
padding: const EdgeInsets.fromLTRB(6, 0, 6, 4),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
@ -153,7 +158,7 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
|
||||
padding: MaterialStateProperty.all(const EdgeInsets.only(
|
||||
left: 10, top: 6, bottom: 6, right: 10)),
|
||||
),
|
||||
onPressed: () => ctr.queryHotSearchList(),
|
||||
onPressed: ctr.queryHotSearchList,
|
||||
icon: const Icon(Icons.refresh_outlined, size: 18),
|
||||
label: const Text('刷新'),
|
||||
),
|
||||
@ -187,13 +192,10 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () => setState(() {}),
|
||||
)
|
||||
],
|
||||
return HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () => setState(() {}),
|
||||
isInSliver: false,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
@ -202,6 +204,7 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
|
||||
return HotKeyword(
|
||||
width: width,
|
||||
hotSearchList: _searchController.hotSearchList,
|
||||
onClick: () {},
|
||||
);
|
||||
} else {
|
||||
return const SizedBox();
|
||||
@ -220,13 +223,13 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
|
||||
return Obx(
|
||||
() => Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.fromLTRB(10, 25, 6, 0),
|
||||
padding: const EdgeInsets.fromLTRB(10, 20, 4, 0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (_searchController.historyList.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(6, 0, 0, 2),
|
||||
padding: const EdgeInsets.fromLTRB(6, 0, 6, 8),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
@ -237,10 +240,19 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
|
||||
.titleMedium!
|
||||
.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => _searchController.onClearHis(),
|
||||
child: const Text('清空'),
|
||||
)
|
||||
SizedBox(
|
||||
height: 34,
|
||||
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:flutter/material.dart';
|
||||
|
||||
class HotKeyword extends StatelessWidget {
|
||||
final double? width;
|
||||
final List? hotSearchList;
|
||||
final Function? onClick;
|
||||
final double width;
|
||||
final List hotSearchList;
|
||||
final Function onClick;
|
||||
|
||||
const HotKeyword({
|
||||
this.width,
|
||||
this.hotSearchList,
|
||||
this.onClick,
|
||||
required this.width,
|
||||
required this.hotSearchList,
|
||||
required this.onClick,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@ -18,45 +18,67 @@ class HotKeyword extends StatelessWidget {
|
||||
return Wrap(
|
||||
runSpacing: 0.4,
|
||||
spacing: 5.0,
|
||||
children: [
|
||||
for (var i in hotSearchList!)
|
||||
SizedBox(
|
||||
width: width! / 2 - 4,
|
||||
child: Material(
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: InkWell(
|
||||
onTap: () => onClick!(i.keyword),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: 2,
|
||||
right: hotSearchList!.indexOf(i) % 2 == 1 ? 10 : 0),
|
||||
child: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(6, 5, 4, 5),
|
||||
child: Text(
|
||||
i.keyword!,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (i.icon != null && i.icon != '')
|
||||
SizedBox(
|
||||
height: 15,
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: i.icon!, height: 15.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
children: hotSearchList.map((item) {
|
||||
return HotKeywordItem(
|
||||
width: width,
|
||||
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(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: InkWell(
|
||||
onTap: () => onClick.call(item.keyword),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: 2, right: isRightPadding ? 10 : 0),
|
||||
child: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(6, 5, 4, 5),
|
||||
child: Text(
|
||||
item.keyword,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (item.icon != null && item.icon != '')
|
||||
SizedBox(
|
||||
height: 15,
|
||||
child:
|
||||
CachedNetworkImage(imageUrl: item.icon!, height: 15.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pilipala/utils/feed_back.dart';
|
||||
|
||||
class SearchText extends StatelessWidget {
|
||||
final String? searchText;
|
||||
@ -17,30 +18,31 @@ class SearchText extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ColorScheme colorScheme = Theme.of(context).colorScheme;
|
||||
return Material(
|
||||
color: isSelect
|
||||
? Theme.of(context).colorScheme.primaryContainer
|
||||
: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.5),
|
||||
? colorScheme.primaryContainer
|
||||
: colorScheme.surfaceVariant.withOpacity(0.5),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.zero,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
onSelect!(searchText);
|
||||
onSelect?.call(searchText);
|
||||
},
|
||||
onLongPress: () {
|
||||
onLongSelect!(searchText);
|
||||
feedBack();
|
||||
onLongSelect?.call(searchText);
|
||||
},
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(top: 5, bottom: 5, left: 11, right: 11),
|
||||
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 11),
|
||||
child: Text(
|
||||
searchText!,
|
||||
style: TextStyle(
|
||||
color: isSelect
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
? colorScheme.primary
|
||||
: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@ -109,33 +109,25 @@ class _SearchPanelState extends State<SearchPanel>
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return CustomScrollView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () {
|
||||
setState(() {
|
||||
_searchPanelController.onSearch();
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
return HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () {
|
||||
setState(() {
|
||||
_searchPanelController.onSearch();
|
||||
});
|
||||
},
|
||||
isInSliver: false,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return CustomScrollView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
HttpError(
|
||||
errMsg: '没有相关数据',
|
||||
fn: () {
|
||||
setState(() {
|
||||
_searchPanelController.onSearch();
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
return HttpError(
|
||||
errMsg: '没有相关数据',
|
||||
fn: () {
|
||||
setState(() {
|
||||
_searchPanelController.onSearch();
|
||||
});
|
||||
},
|
||||
isInSliver: false,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -174,14 +174,11 @@ Widget searchArticlePanel(BuildContext context, ctr, list) {
|
||||
);
|
||||
},
|
||||
)
|
||||
: CustomScrollView(
|
||||
slivers: [
|
||||
HttpError(
|
||||
errMsg: '没有数据',
|
||||
isShowBtn: false,
|
||||
fn: () => {},
|
||||
)
|
||||
],
|
||||
: HttpError(
|
||||
errMsg: '没有数据',
|
||||
isShowBtn: false,
|
||||
fn: () => {},
|
||||
isInSliver: false,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -46,14 +46,11 @@ class SearchVideoPanel extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
)
|
||||
: CustomScrollView(
|
||||
slivers: [
|
||||
HttpError(
|
||||
errMsg: '没有数据',
|
||||
isShowBtn: false,
|
||||
fn: () => {},
|
||||
)
|
||||
],
|
||||
: HttpError(
|
||||
errMsg: '没有数据',
|
||||
isShowBtn: false,
|
||||
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/reply_sort_type.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 '../home/index.dart';
|
||||
@ -146,6 +147,15 @@ class _ExtraSettingState extends State<ExtraSetting> {
|
||||
setKey: SettingBoxKey.enableHotKey,
|
||||
defaultVal: true,
|
||||
),
|
||||
SetSwitchItem(
|
||||
title: '展示搜索建议',
|
||||
subTitle: '输入搜索内容时展示建议词',
|
||||
setKey: SettingBoxKey.enableSearchSuggest,
|
||||
defaultVal: true,
|
||||
callFn: (val) {
|
||||
GlobalDataCache().enableSearchSuggest = val;
|
||||
},
|
||||
),
|
||||
SetSwitchItem(
|
||||
title: '搜索默认词',
|
||||
subTitle: '是否展示搜索框默认词',
|
||||
|
||||
@ -68,30 +68,27 @@ class _SubPageState extends State<SubPage> {
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const CustomScrollView(
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
slivers: [HttpError(errMsg: '', btnText: '没有数据', fn: null)],
|
||||
return const HttpError(
|
||||
errMsg: '',
|
||||
btnText: '没有数据',
|
||||
fn: null,
|
||||
isInSliver: false,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return CustomScrollView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
HttpError(
|
||||
errMsg: data?['msg'] ?? '请求异常',
|
||||
btnText: data?['code'] == -101 ? '去登录' : null,
|
||||
fn: () {
|
||||
if (data?['code'] == -101) {
|
||||
RoutePush.loginRedirectPush();
|
||||
} else {
|
||||
setState(() {
|
||||
_futureBuilderFuture =
|
||||
_subController.querySubFolder();
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
return HttpError(
|
||||
errMsg: data?['msg'] ?? '请求异常',
|
||||
btnText: data?['code'] == -101 ? '去登录' : null,
|
||||
fn: () {
|
||||
if (data?['code'] == -101) {
|
||||
RoutePush.loginRedirectPush();
|
||||
} else {
|
||||
setState(() {
|
||||
_futureBuilderFuture = _subController.querySubFolder();
|
||||
});
|
||||
}
|
||||
},
|
||||
isInSliver: false,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -242,6 +242,12 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
showBottomSheet(
|
||||
context: context,
|
||||
enableDrag: true,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(25),
|
||||
topRight: Radius.circular(25),
|
||||
),
|
||||
),
|
||||
builder: (BuildContext context) {
|
||||
return AiDetail(modelResult: videoIntroController.modelResult);
|
||||
},
|
||||
|
||||
@ -21,11 +21,9 @@ class VideoReplyController extends GetxController {
|
||||
// rpid 请求楼中楼回复
|
||||
String? rpid;
|
||||
RxList<ReplyItemModel> replyList = <ReplyItemModel>[].obs;
|
||||
// 当前页
|
||||
int currentPage = 0;
|
||||
String nextOffset = "";
|
||||
bool isLoadingMore = false;
|
||||
RxString noMore = ''.obs;
|
||||
int ps = 20;
|
||||
RxInt count = 0.obs;
|
||||
// 当前回复的回复
|
||||
ReplyItemModel? currentReplyItem;
|
||||
@ -57,7 +55,7 @@ class VideoReplyController extends GetxController {
|
||||
}
|
||||
isLoadingMore = true;
|
||||
if (type == 'init') {
|
||||
currentPage = 0;
|
||||
nextOffset = '';
|
||||
noMore.value = '';
|
||||
}
|
||||
if (noMore.value == '没有更多了') {
|
||||
@ -66,28 +64,20 @@ class VideoReplyController extends GetxController {
|
||||
}
|
||||
final res = await ReplyHttp.replyList(
|
||||
oid: aid!,
|
||||
pageNum: currentPage + 1,
|
||||
ps: ps,
|
||||
nextOffset: nextOffset,
|
||||
type: ReplyType.video.index,
|
||||
sort: _sortType.index,
|
||||
);
|
||||
if (res['status']) {
|
||||
final List<ReplyItemModel> replies = res['data'].replies;
|
||||
nextOffset = res['data'].cursor.paginationReply.nextOffset ?? "";
|
||||
if (replies.isNotEmpty) {
|
||||
noMore.value = '加载中...';
|
||||
|
||||
/// 第一页回复数小于20
|
||||
if (currentPage == 0 && replies.length < 18) {
|
||||
noMore.value = '没有更多了';
|
||||
}
|
||||
currentPage++;
|
||||
|
||||
if (replyList.length == res['data'].page.acount) {
|
||||
if (res['data'].cursor.isEnd == true) {
|
||||
noMore.value = '没有更多了';
|
||||
}
|
||||
} else {
|
||||
// 未登录状态replies可能返回null
|
||||
noMore.value = currentPage == 0 ? '还没有评论' : '没有更多了';
|
||||
noMore.value = nextOffset == "" ? '还没有评论' : '没有更多了';
|
||||
}
|
||||
if (type == 'init') {
|
||||
// 添加置顶回复
|
||||
@ -99,7 +89,7 @@ class VideoReplyController extends GetxController {
|
||||
}
|
||||
}
|
||||
replies.insertAll(0, res['data'].topReplies);
|
||||
count.value = res['data'].page.count;
|
||||
count.value = res['data'].cursor.allCount;
|
||||
replyList.value = replies;
|
||||
} else {
|
||||
replyList.addAll(replies);
|
||||
@ -130,7 +120,7 @@ class VideoReplyController extends GetxController {
|
||||
}
|
||||
sortTypeTitle.value = _sortType.titles;
|
||||
sortTypeLabel.value = _sortType.labels;
|
||||
currentPage = 0;
|
||||
nextOffset = "";
|
||||
noMore.value = '';
|
||||
replyList.clear();
|
||||
queryReplyList(type: 'init');
|
||||
|
||||
@ -238,28 +238,53 @@ class ReplyItem extends StatelessWidget {
|
||||
// title
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 10, left: 45, right: 6, bottom: 4),
|
||||
child: Text.rich(
|
||||
style: const TextStyle(height: 1.75),
|
||||
maxLines:
|
||||
replyItem!.content!.isText! && replyLevel == '1' ? 3 : 999,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
TextSpan(
|
||||
children: [
|
||||
if (replyItem!.isTop!)
|
||||
const WidgetSpan(
|
||||
alignment: PlaceholderAlignment.top,
|
||||
child: PBadge(
|
||||
text: 'TOP',
|
||||
size: 'small',
|
||||
stack: 'normal',
|
||||
type: 'line',
|
||||
fs: 9,
|
||||
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),
|
||||
TextSpan(
|
||||
children: [
|
||||
if (replyItem!.isTop!)
|
||||
const WidgetSpan(
|
||||
alignment: PlaceholderAlignment.top,
|
||||
child: PBadge(
|
||||
text: 'TOP',
|
||||
size: 'small',
|
||||
stack: 'normal',
|
||||
type: 'line',
|
||||
fs: 9,
|
||||
),
|
||||
),
|
||||
buildContent(
|
||||
context,
|
||||
replyItem!,
|
||||
replyReply,
|
||||
null,
|
||||
didExceedMaxLines,
|
||||
textPainter,
|
||||
),
|
||||
buildContent(context, replyItem!, replyReply, null),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
// 操作区域
|
||||
bottonAction(context, replyItem!.replyControl, replySave),
|
||||
@ -465,8 +490,8 @@ class ReplyItemRow extends StatelessWidget {
|
||||
fs: 9,
|
||||
),
|
||||
),
|
||||
buildContent(
|
||||
context, replies![i], replyReply, replyItem),
|
||||
buildContent(context, replies![i], replyReply,
|
||||
replyItem, false, null),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -508,7 +533,13 @@ class ReplyItemRow extends StatelessWidget {
|
||||
}
|
||||
|
||||
InlineSpan buildContent(
|
||||
BuildContext context, replyItem, replyReply, fReplyItem) {
|
||||
BuildContext context,
|
||||
replyItem,
|
||||
replyReply,
|
||||
fReplyItem,
|
||||
bool didExceedMaxLines,
|
||||
TextPainter? textPainter,
|
||||
) {
|
||||
final String routePath = Get.currentRoute;
|
||||
bool isVideoPage = routePath.startsWith('/video');
|
||||
ColorScheme colorScheme = Theme.of(context).colorScheme;
|
||||
@ -519,6 +550,25 @@ InlineSpan buildContent(
|
||||
final content = replyItem.content;
|
||||
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) {
|
||||
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('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll(''', "'")
|
||||
.replaceAll(' ', ' ');
|
||||
// 构建正则表达式
|
||||
final List<String> specialTokens = [
|
||||
...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) {
|
||||
final List<String> picList = <String>[];
|
||||
|
||||
@ -1,16 +1,11 @@
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/models/video/ai.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';
|
||||
|
||||
Box localCache = GStrorage.localCache;
|
||||
late double sheetHeight;
|
||||
|
||||
class AiDetail extends StatelessWidget {
|
||||
final ModelResult? modelResult;
|
||||
|
||||
@ -21,124 +16,21 @@ class AiDetail extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
sheetHeight = localCache.get('sheetHeight');
|
||||
return Container(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
padding: const EdgeInsets.only(left: 14, right: 14),
|
||||
height: sheetHeight,
|
||||
padding: const EdgeInsets.only(left: 16, right: 16),
|
||||
height: GlobalDataCache().sheetHeight,
|
||||
child: Column(
|
||||
children: [
|
||||
InkWell(
|
||||
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)),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
_buildHeader(context),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
if (modelResult!.resultType != 0 &&
|
||||
modelResult!.summary != '') ...[
|
||||
SelectableText(
|
||||
modelResult!.summary!,
|
||||
style: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
if (modelResult!.summary != '') ...[
|
||||
_buildSummaryText(modelResult!.summary!),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: modelResult!.outline!.length,
|
||||
itemBuilder: (context, index) {
|
||||
final outline = modelResult!.outline![index];
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SelectableText(
|
||||
outline.title!,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: outline.partOutline!.length,
|
||||
itemBuilder: (context, i) {
|
||||
final part = outline.partOutline![i];
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
try {
|
||||
final controller =
|
||||
Get.find<VideoDetailController>(
|
||||
tag: Get.arguments['heroTag'],
|
||||
);
|
||||
controller.plPlayerController.seekTo(
|
||||
Duration(
|
||||
seconds: Utils.duration(
|
||||
Utils.tampToSeektime(
|
||||
part.timestamp!),
|
||||
).toInt(),
|
||||
),
|
||||
);
|
||||
} catch (_) {}
|
||||
},
|
||||
child: SelectableText.rich(
|
||||
TextSpan(
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface,
|
||||
height: 1.5,
|
||||
),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: Utils.tampToSeektime(
|
||||
part.timestamp!),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.primary,
|
||||
),
|
||||
),
|
||||
const TextSpan(text: ' '),
|
||||
TextSpan(text: part.content!),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
)
|
||||
_buildOutlineList(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -148,77 +40,113 @@ class AiDetail extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
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),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
Widget _buildSummaryText(String summary) {
|
||||
return SelectableText(
|
||||
summary,
|
||||
textAlign: TextAlign.justify,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1.6,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (previousEndIndex < currentDesc.rawText.length) {
|
||||
spanChildren.add(TextSpan(
|
||||
text: currentDesc.rawText.substring(previousEndIndex)));
|
||||
}
|
||||
Widget _buildOutlineList(BuildContext context) {
|
||||
return ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: modelResult!.outline!.length,
|
||||
itemBuilder: (context, index) {
|
||||
final outline = modelResult!.outline![index];
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildOutlineTitle(outline.title!),
|
||||
const SizedBox(height: 20),
|
||||
_buildPartOutlineList(context, outline.partOutline!),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
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),
|
||||
Widget _buildOutlineTitle(String title) {
|
||||
return SelectableText(
|
||||
title,
|
||||
textAlign: TextAlign.justify,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1.5,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPartOutlineList(
|
||||
BuildContext context, List<PartOutline> partOutline) {
|
||||
return ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: partOutline.length,
|
||||
itemBuilder: (context, i) {
|
||||
final part = partOutline[i];
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildPartText(context, part),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _onPartTap(BuildContext context, int timestamp) {
|
||||
try {
|
||||
final controller = Get.find<VideoDetailController>(
|
||||
tag: Get.arguments['heroTag'],
|
||||
);
|
||||
controller.plPlayerController.seekTo(
|
||||
Duration(seconds: timestamp),
|
||||
);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
Widget _buildPartText(BuildContext context, PartOutline part) {
|
||||
return SelectableText.rich(
|
||||
TextSpan(
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: Utils.tampToSeektime(part.timestamp!),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
Get.toNamed(
|
||||
'/member?mid=${currentDesc.bizId}',
|
||||
arguments: {'face': '', 'heroTag': heroTag},
|
||||
);
|
||||
},
|
||||
);
|
||||
default:
|
||||
return const TextSpan();
|
||||
}
|
||||
});
|
||||
return TextSpan(children: spanChilds);
|
||||
..onTap = () => _onPartTap(context, part.timestamp!),
|
||||
),
|
||||
const TextSpan(text: ' '),
|
||||
TextSpan(text: part.content!),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user