初始化
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgdj/hj_utils/const.dart';
|
||||
import 'package:hgdj/hj_utils/history_util.dart';
|
||||
|
||||
import 'search_result_page/search_result_main_page.dart';
|
||||
|
||||
/// 搜索主页逻辑:只管搜索框回填和搜索历史,热门排行由 RankSubPage 自己取数
|
||||
class SearchMainLogic extends GetxController {
|
||||
SearchMainLogic({this.initialResultType});
|
||||
|
||||
/// 搜索框控制器:由 logic 持有,点热搜词/历史项后能直接回填输入框
|
||||
final searchTextCtr = TextEditingController();
|
||||
|
||||
/// 搜索历史(展示用):去重、排序、上限都由 SearchHistoryStore 负责,这里只缓存结果
|
||||
final histories = <String>[];
|
||||
|
||||
/// 结果页默认 Tab(短剧频道进入时为 [MediaStyle.Drama])
|
||||
final MediaStyle? initialResultType;
|
||||
|
||||
@override
|
||||
void onReady() {
|
||||
super.onReady();
|
||||
loadHistories();
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
searchTextCtr.dispose();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
/// 重读搜索历史
|
||||
Future<void> loadHistories() async {
|
||||
final list = await HistoryUtil.searchHistories();
|
||||
histories
|
||||
..clear()
|
||||
..addAll(list);
|
||||
update(['history']);
|
||||
}
|
||||
|
||||
/// 清空搜索历史
|
||||
Future<void> clearHistories() async {
|
||||
await HistoryUtil.clearSearch();
|
||||
histories.clear();
|
||||
update(['history']);
|
||||
}
|
||||
|
||||
/// 提交搜索:记一条历史 → 跳结果页 → 返回后重读(用户此时才重新看到历史列表)
|
||||
/// 空串由 SearchHistoryStore.save 内部忽略,不入库
|
||||
Future<void> onSubmitted(String text) async {
|
||||
searchTextCtr.text = text;
|
||||
HistoryUtil.addSearch(text); //不 await:落盘不该挡住跳转,返回时早已写完
|
||||
await Get.to(
|
||||
() => SearchResultMainPage(text, initialType: initialResultType));
|
||||
loadHistories();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgdj/hj_utils/const.dart';
|
||||
import 'package:hgdj/tools_base/banner/ads_grid_view_widget.dart';
|
||||
|
||||
import '../../find/rank_module/rank_main_page.dart';
|
||||
import '../../find/rank_module/rank_sub_page.dart';
|
||||
import 'search_main_logic.dart';
|
||||
import 'widget/search_app_bar.dart';
|
||||
import 'widget/search_history_view.dart';
|
||||
import 'widget/search_hot_tag_view.dart';
|
||||
|
||||
/// 搜索主页:头部是广告 / 搜索历史 / 热搜词,body 挂视频周榜,两者联动滚动
|
||||
class SearchMainPage extends StatelessWidget {
|
||||
/// 进入页面时预填的搜索词(可为空)
|
||||
final String? searchText;
|
||||
|
||||
/// 结果页默认 Tab;短剧频道进入时传 [MediaStyle.Drama]
|
||||
final MediaStyle? initialResultType;
|
||||
|
||||
const SearchMainPage({super.key, this.searchText, this.initialResultType});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GetBuilder<SearchMainLogic>(
|
||||
init: SearchMainLogic(initialResultType: initialResultType),
|
||||
builder: (logic) {
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
body: Column(
|
||||
children: [
|
||||
SearchAppBar(
|
||||
isClose: true,
|
||||
searchText: searchText,
|
||||
controller: logic.searchTextCtr, //由 logic 持有,点热搜词/历史项后回填输入框
|
||||
onSubmitted: logic.onSubmitted,
|
||||
),
|
||||
Expanded(
|
||||
child: NestedScrollView(
|
||||
headerSliverBuilder: (_, __) => _headerSlivers(logic),
|
||||
body: const RankSubPage(
|
||||
pageType: MediaStyle.Video, sortType: 2), // 2 = 周榜
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _headerSlivers(SearchMainLogic logic) {
|
||||
return [
|
||||
//顶部广告网格(position=32)
|
||||
SliverToBoxAdapter(
|
||||
child: AdsGridViewWidget(
|
||||
32,
|
||||
accordingAdsType: true,
|
||||
padding: EdgeInsets.symmetric(vertical: 12.h, horizontal: 12.w),
|
||||
),
|
||||
),
|
||||
//搜索历史:随增删局部刷新,不带动整页(id 要和 logic 里 update 的一致)
|
||||
SliverToBoxAdapter(
|
||||
child: GetBuilder<SearchMainLogic>(
|
||||
id: 'history',
|
||||
builder: (_) => SearchHistoryView(
|
||||
histories: logic.histories,
|
||||
onHistoryClick: logic.onSubmitted,
|
||||
onClearAll: logic.clearHistories,
|
||||
),
|
||||
),
|
||||
),
|
||||
//热搜词
|
||||
SliverToBoxAdapter(
|
||||
child: SearchHotTagView(onTagClick: logic.onSubmitted)),
|
||||
//「热门排行」标题行,右侧跳完整排行榜页
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 20, 12, 10),
|
||||
child: Row(
|
||||
children: [
|
||||
const Text(
|
||||
'热门排行',
|
||||
style: TextStyle(
|
||||
color: Color(0xE5FFFFFF),
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600),
|
||||
),
|
||||
const Spacer(),
|
||||
InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: () => Get.to(() => const RankMainPage(),
|
||||
preventDuplicates: false),
|
||||
child: Container(
|
||||
width: 54,
|
||||
height: 18,
|
||||
alignment: Alignment.centerRight,
|
||||
margin: const EdgeInsets.only(left: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Text('更多',
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.55),
|
||||
fontSize: 14)),
|
||||
Icon(Icons.navigate_next,
|
||||
size: 22,
|
||||
color: Colors.white.withValues(alpha: 0.55)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hgdj/hj_utils/api_service/acg_service.dart';
|
||||
import 'package:hgdj/hj_utils/const.dart';
|
||||
import 'package:hgdj/tools_base/debug_log.dart';
|
||||
import 'package:hgdj/tools_base/loading/loading_center_widget.dart';
|
||||
import 'package:pull_to_refresh/pull_to_refresh.dart';
|
||||
|
||||
import '../../../../hj_model/acg/cartoon_more_list.dart';
|
||||
import '../../../../hj_model/cartoon_media_info.dart';
|
||||
import '../../../../tools_base/refresh/pull_refresh.dart';
|
||||
import '../../../cartoon/acg_widget_item.dart';
|
||||
|
||||
class SearchACGResultPage extends StatefulWidget {
|
||||
final String keywords;
|
||||
final MediaStyle type;
|
||||
|
||||
SearchACGResultPage({
|
||||
super.key,
|
||||
required this.type,
|
||||
required this.keywords,
|
||||
});
|
||||
|
||||
@override
|
||||
State<SearchACGResultPage> createState() => _SearchACGResultPageState();
|
||||
}
|
||||
|
||||
class _SearchACGResultPageState extends State<SearchACGResultPage> {
|
||||
RefreshController? refreshController; //由 pullYsRefresh 的 onInit 注入,组件负责释放
|
||||
int pageNumber = 1;
|
||||
List<CartoonMediaInfo>? videoList;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
_loadData();
|
||||
});
|
||||
}
|
||||
|
||||
_loadData({int page = 1, int size = 12}) async {
|
||||
try {
|
||||
MediaSearchListModel? retResp = await ACGService.mediaSearch(
|
||||
keyword: widget.keywords,
|
||||
page: page,
|
||||
size: size,
|
||||
kind: widget.type.searchKind);
|
||||
pageNumber = page;
|
||||
videoList ??= [];
|
||||
if (page == 1) {
|
||||
videoList?.clear();
|
||||
}
|
||||
videoList?.addAll(retResp?.list ?? []);
|
||||
retResp?.hasNext == false
|
||||
? refreshController?.loadNoData()
|
||||
: refreshController?.loadComplete();
|
||||
} catch (e) {
|
||||
refreshController?.loadComplete();
|
||||
debugLog(e);
|
||||
}
|
||||
refreshController?.refreshCompleted();
|
||||
videoList ??= [];
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (videoList == null) {
|
||||
return LoadingCenterWidget();
|
||||
} else if (videoList?.isEmpty == true) {
|
||||
return CErrorWidget(
|
||||
retryOnTap: () {
|
||||
videoList = null;
|
||||
setState(() {});
|
||||
_loadData();
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return pullYsRefresh(
|
||||
onInit: (ctr) => refreshController = ctr,
|
||||
onLoading: (ctr) => _loadData(page: pageNumber + 1),
|
||||
onRefresh: (ctr) => _loadData(),
|
||||
child: GridView.builder(
|
||||
padding: EdgeInsets.fromLTRB(12, 12, 12, 0),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 3,
|
||||
mainAxisSpacing: 12,
|
||||
crossAxisSpacing: 6,
|
||||
childAspectRatio: 111 / 190,
|
||||
),
|
||||
itemCount: videoList?.length ?? 0,
|
||||
itemBuilder: (context, index) {
|
||||
return AcgItemWidget(info: videoList![index]);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hgdj/hj_utils/const.dart';
|
||||
import 'package:hgdj/hj_utils/history_util.dart';
|
||||
import 'package:hgdj/tools_base/widget/keep_alive_widget.dart';
|
||||
|
||||
import '../../../../tools_base/indicator/custom_tab_indicator.dart';
|
||||
import '../widget/search_app_bar.dart';
|
||||
import 'search_acg_result_page.dart';
|
||||
import 'search_video_result_page.dart';
|
||||
|
||||
class SearchResultMainPage extends StatefulWidget {
|
||||
final String keywords;
|
||||
|
||||
/// 从短剧频道进入时默认切到短剧 Tab(对应 kind=4)
|
||||
final MediaStyle? initialType;
|
||||
|
||||
const SearchResultMainPage(this.keywords, {super.key, this.initialType});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return _SearchResultMainPageState();
|
||||
}
|
||||
}
|
||||
|
||||
class _SearchResultMainPageState extends State<SearchResultMainPage>
|
||||
with SingleTickerProviderStateMixin {
|
||||
final typeList = ['影片', '短剧', '抖音', '漫画', '动漫', '帖子', '图集'];
|
||||
|
||||
late final TabController tabCtr;
|
||||
late String keywords = widget.keywords;
|
||||
|
||||
static int _tabIndexOf(MediaStyle? type) => switch (type) {
|
||||
MediaStyle.Drama => 1,
|
||||
MediaStyle.ShortVideo => 2,
|
||||
MediaStyle.Comics => 3,
|
||||
MediaStyle.Cartoon => 4,
|
||||
MediaStyle.Community => 5,
|
||||
MediaStyle.Pic => 6,
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
tabCtr = TabController(
|
||||
length: typeList.length,
|
||||
vsync: this,
|
||||
initialIndex: _tabIndexOf(widget.initialType),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
tabCtr.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Column(
|
||||
children: [
|
||||
SearchAppBar(
|
||||
searchText: keywords,
|
||||
onSubmitted: (value) {
|
||||
HistoryUtil.addSearch(value); //结果页里再次搜索同样记历史
|
||||
keywords = value;
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
Container(
|
||||
height: 32,
|
||||
margin: EdgeInsets.symmetric(horizontal: 12),
|
||||
child: TabBar(
|
||||
tabAlignment: TabAlignment.fill,
|
||||
tabs: typeList.map((e) {
|
||||
return Container(
|
||||
alignment: Alignment.center,
|
||||
child: Text(e),
|
||||
);
|
||||
}).toList(),
|
||||
labelStyle: TextStyle(fontSize: 14),
|
||||
labelColor: Color(0xE5FFFFFF),
|
||||
unselectedLabelStyle: TextStyle(fontSize: 14),
|
||||
unselectedLabelColor: Color(0x73FFFFFF),
|
||||
controller: tabCtr,
|
||||
padding: EdgeInsets.zero,
|
||||
isScrollable: false,
|
||||
labelPadding: EdgeInsets.zero,
|
||||
indicator: const CustomIndicator(
|
||||
height: 4, width: 16, isGradient: true, offsetY: 4),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
controller: tabCtr,
|
||||
children: [
|
||||
SearchVideoResultPage(
|
||||
key: ValueKey("0$keywords"),
|
||||
keywords: keywords,
|
||||
type: MediaStyle.Video)
|
||||
.keepAlive,
|
||||
SearchVideoResultPage(
|
||||
key: ValueKey("1$keywords"),
|
||||
keywords: keywords,
|
||||
type: MediaStyle.Drama)
|
||||
.keepAlive,
|
||||
SearchVideoResultPage(
|
||||
key: ValueKey("2$keywords"),
|
||||
keywords: keywords,
|
||||
type: MediaStyle.ShortVideo)
|
||||
.keepAlive,
|
||||
SearchACGResultPage(
|
||||
key: ValueKey("3$keywords"),
|
||||
keywords: keywords,
|
||||
type: MediaStyle.Comics)
|
||||
.keepAlive,
|
||||
SearchACGResultPage(
|
||||
key: ValueKey("4$keywords"),
|
||||
keywords: keywords,
|
||||
type: MediaStyle.Cartoon)
|
||||
.keepAlive,
|
||||
SearchVideoResultPage(
|
||||
key: ValueKey("5$keywords"),
|
||||
keywords: keywords,
|
||||
type: MediaStyle.Community)
|
||||
.keepAlive,
|
||||
SearchVideoResultPage(
|
||||
key: ValueKey("6$keywords"),
|
||||
keywords: keywords,
|
||||
type: MediaStyle.Pic)
|
||||
.keepAlive,
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,421 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgdj/hj_model/drama_media_info.dart';
|
||||
import 'package:hgdj/hj_model/video_model.dart';
|
||||
import 'package:hgdj/hj_page/drama/drama_detail_page.dart';
|
||||
import 'package:hgdj/hj_page/drama/drama_list_page.dart';
|
||||
import 'package:hgdj/hj_utils/api_service/drama_service.dart';
|
||||
import 'package:hgdj/hj_utils/api_service/search_service.dart';
|
||||
import 'package:hgdj/hj_utils/const.dart';
|
||||
import 'package:hgdj/hj_utils/screen.dart';
|
||||
import 'package:hgdj/tools_base/debug_log.dart';
|
||||
import 'package:hgdj/tools_base/loading/loading_center_widget.dart';
|
||||
import 'package:pull_to_refresh/pull_to_refresh.dart';
|
||||
|
||||
import '../../../../hj_model/home/video_list_model.dart';
|
||||
import '../../../../tools_base/refresh/pull_refresh.dart';
|
||||
import '../../../../tools_base/widget/multitap_recognizer.dart';
|
||||
import '../../../cartoon/photo_gallery_item.dart';
|
||||
import '../../../community/widget/community_post_widget.dart';
|
||||
import '../../home_cell_style/video_simple_cell.dart';
|
||||
import '../../home_cell_style/divider_tab_bar.dart';
|
||||
import '../../tag/video_tag_page.dart';
|
||||
|
||||
//视频搜索结果排序 tab(标题与接口 sort 值绑定)
|
||||
const _sortTabs = [
|
||||
SortTab('最多收藏', 3),
|
||||
SortTab('最新上架', 2),
|
||||
SortTab('最多观看', 1),
|
||||
];
|
||||
|
||||
class SearchVideoResultPage extends StatefulWidget {
|
||||
final String keywords;
|
||||
final MediaStyle type;
|
||||
|
||||
SearchVideoResultPage({
|
||||
super.key,
|
||||
required this.type,
|
||||
required this.keywords,
|
||||
});
|
||||
|
||||
@override
|
||||
State<SearchVideoResultPage> createState() => _SearchVideoResultPageState();
|
||||
}
|
||||
|
||||
class _SearchVideoResultPageState extends State<SearchVideoResultPage> {
|
||||
RefreshController? refreshController; //由 pullYsRefresh 的 onInit 注入,组件负责释放
|
||||
int pageNumber = 1;
|
||||
List<VideoModel>? videoList;
|
||||
List<VideoModel>? tagVidList;
|
||||
String? tagID;
|
||||
int sortIndex = 0;
|
||||
|
||||
bool get _isDrama => widget.type == MediaStyle.Drama;
|
||||
|
||||
int get sortParam => _sortTabs[sortIndex].sort;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
_loadData(sortParam);
|
||||
});
|
||||
}
|
||||
|
||||
_loadData(int sortValue, {int page = 1, int size = 12}) async {
|
||||
try {
|
||||
if (_isDrama) {
|
||||
final retResp = await DramaService.search(
|
||||
keyword: widget.keywords,
|
||||
page: page,
|
||||
size: size,
|
||||
);
|
||||
pageNumber = page;
|
||||
if (page == 1) {
|
||||
videoList = [];
|
||||
tagVidList =
|
||||
retResp?.tagMediaList.map((e) => e.toVideoModel(null)).toList() ??
|
||||
[];
|
||||
tagID = retResp?.tagID;
|
||||
}
|
||||
videoList ??= [];
|
||||
videoList?.addAll(retResp?.list.map((e) => e.toVideoModel(null)) ?? []);
|
||||
retResp?.hasNext != true
|
||||
? refreshController?.loadNoData()
|
||||
: refreshController?.loadComplete();
|
||||
} else {
|
||||
VideoListResp? retResp = await SearchService.searchMedia(
|
||||
widget.keywords,
|
||||
pageNumber: page,
|
||||
pageSize: size,
|
||||
realm: widget.type.searchRealm,
|
||||
sortType: sortValue);
|
||||
pageNumber = page;
|
||||
videoList ??= [];
|
||||
videoList?.addAll(retResp?.videos ?? []);
|
||||
if (page == 1) {
|
||||
tagVidList = retResp?.tagVidList ?? [];
|
||||
tagID = retResp?.tagID;
|
||||
}
|
||||
retResp?.hasNext == false
|
||||
? refreshController?.loadNoData()
|
||||
: refreshController?.loadComplete();
|
||||
}
|
||||
} catch (e) {
|
||||
refreshController?.loadComplete();
|
||||
debugLog(e);
|
||||
}
|
||||
|
||||
refreshController?.refreshCompleted();
|
||||
videoList ??= [];
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (videoList == null && tagVidList == null) {
|
||||
return LoadingCenterWidget();
|
||||
} else if (videoList?.isEmpty == true && tagVidList?.isEmpty == true) {
|
||||
return CErrorWidget(
|
||||
retryOnTap: () {
|
||||
videoList = null;
|
||||
setState(() {});
|
||||
_loadData(sortParam);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return NestedScrollView(
|
||||
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
||||
return [
|
||||
SliverToBoxAdapter(
|
||||
key: ValueKey(tagID),
|
||||
child: _buildTagWidget(),
|
||||
)
|
||||
];
|
||||
},
|
||||
body: Column(
|
||||
children: [
|
||||
if (widget.type == MediaStyle.Video ||
|
||||
widget.type == MediaStyle.ShortVideo)
|
||||
DividerTabBar(
|
||||
_sortTabs.map((e) => e.name).toList(),
|
||||
selectIndex: sortIndex,
|
||||
alignment: Alignment.center,
|
||||
callback: (value) {
|
||||
sortIndex = value;
|
||||
videoList = null;
|
||||
_loadData(sortParam);
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: _buildContent(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildContent() {
|
||||
if (videoList == null) {
|
||||
return LoadingCenterWidget();
|
||||
} else if (videoList?.isEmpty == true) {
|
||||
return CErrorWidget(
|
||||
retryOnTap: () {
|
||||
videoList = null;
|
||||
setState(() {});
|
||||
_loadData(sortParam);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return pullYsRefresh(
|
||||
onInit: (ctr) => refreshController = ctr,
|
||||
onLoading: (ctr) => _loadData(sortParam, page: pageNumber + 1),
|
||||
onRefresh: (ctr) => _loadData(sortParam),
|
||||
child: _buildTypeList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 进短剧二级页;搜索列表不一定带 dramaInfo,最少用 id/标题/封面兜底
|
||||
void _openDrama(VideoModel model) {
|
||||
final drama = model.dramaInfo ??
|
||||
(DramaMediaInfo()
|
||||
..id = model.id
|
||||
..title = model.title
|
||||
..verticalCover = model.cover
|
||||
..horizontalCover = model.cover
|
||||
..totalEpisode = model.totalEpisode);
|
||||
Get.to(() => DramaDetailPage(drama: drama));
|
||||
}
|
||||
|
||||
Widget _buildTypeList() {
|
||||
if (widget.type == MediaStyle.Drama) {
|
||||
return GridView.builder(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
mainAxisSpacing: 12,
|
||||
crossAxisSpacing: 6,
|
||||
childAspectRatio: 168 / 266,
|
||||
),
|
||||
itemCount: videoList?.length ?? 0,
|
||||
itemBuilder: (context, index) {
|
||||
final model = videoList![index];
|
||||
return MultiTap(
|
||||
onTap: () => _openDrama(model),
|
||||
child: VideoSimpleCell(
|
||||
videoModel: model,
|
||||
textLines: 1,
|
||||
coverRightText: model.dramaInfo?.episodeNumberStatus,
|
||||
showLevelIcon: false,
|
||||
isFromSearch: true,
|
||||
onTap: () => _openDrama(model),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
if (widget.type == MediaStyle.Video ||
|
||||
widget.type == MediaStyle.ShortVideo) {
|
||||
bool isShort = widget.type == MediaStyle.ShortVideo;
|
||||
return GridView.builder(
|
||||
padding: EdgeInsets.only(left: 12, right: 12),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: isShort ? 3 : 2,
|
||||
mainAxisSpacing: 12,
|
||||
crossAxisSpacing: 6,
|
||||
childAspectRatio: isShort ? 111 / 190 : 168 / 154,
|
||||
),
|
||||
itemCount: videoList?.length ?? 0,
|
||||
itemBuilder: (context, index) {
|
||||
return VideoSimpleCell(
|
||||
videoModel: videoList![index],
|
||||
textLines: isShort ? 1 : 2,
|
||||
isFromSearch: true,
|
||||
);
|
||||
},
|
||||
);
|
||||
} else if (widget.type == MediaStyle.Pic) {
|
||||
return GridView.builder(
|
||||
padding: EdgeInsets.only(left: 12, right: 12),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 3,
|
||||
mainAxisSpacing: 12,
|
||||
crossAxisSpacing: 6,
|
||||
childAspectRatio: 111 / 190,
|
||||
),
|
||||
itemCount: videoList?.length ?? 0,
|
||||
itemBuilder: (context, index) {
|
||||
return PhotoGalleryItem(videoModel: videoList![index], textline: 1);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
itemCount: videoList?.length ?? 0,
|
||||
itemBuilder: (context, index) {
|
||||
return CommunityPostWidget(
|
||||
videoModel: videoList![index],
|
||||
videoModels: videoList,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildTagWidget() {
|
||||
if (tagVidList?.isNotEmpty != true) return SizedBox();
|
||||
if (_isDrama) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.fromLTRB(12, 12, 12, 0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'${widget.keywords} 标签内容',
|
||||
style: const TextStyle(color: Color(0xE5FFFFFF), fontSize: 14),
|
||||
),
|
||||
12.sizeBoxH,
|
||||
GridView.builder(
|
||||
itemCount: min(4, tagVidList?.length ?? 0),
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
padding: EdgeInsets.zero,
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
mainAxisSpacing: 12,
|
||||
crossAxisSpacing: 6,
|
||||
childAspectRatio: 168 / 266,
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
final videoModel = tagVidList![index];
|
||||
return MultiTap(
|
||||
onTap: () => _openDrama(videoModel),
|
||||
child: VideoSimpleCell(
|
||||
videoModel: videoModel,
|
||||
textLines: 1,
|
||||
coverRightText: videoModel.dramaInfo?.episodeNumberStatus,
|
||||
showLevelIcon: false,
|
||||
isFromSearch: true,
|
||||
onTap: () => _openDrama(videoModel),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (tagID?.isNotEmpty == true) ...[
|
||||
const SizedBox(height: 12),
|
||||
GestureDetector(
|
||||
onTap: () => DramaListPage.toTag(
|
||||
TagsBean(id: tagID, name: widget.keywords)),
|
||||
child: Container(
|
||||
height: 40,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0x1AFFFFFF),
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
),
|
||||
child: const Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text('查看更多',
|
||||
style: TextStyle(
|
||||
color: Color(0xffEFEFEF), fontSize: 14)),
|
||||
SizedBox(width: 6),
|
||||
Icon(Icons.keyboard_arrow_right,
|
||||
color: Color(0xffDCDCDC), size: 18),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 6),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
if (widget.type == MediaStyle.Video ||
|
||||
widget.type == MediaStyle.ShortVideo) {
|
||||
bool isShort = widget.type == MediaStyle.ShortVideo;
|
||||
return Container(
|
||||
padding: EdgeInsets.fromLTRB(12, 12, 12, 0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"${widget.keywords} 标签内容",
|
||||
style: TextStyle(
|
||||
color: Color(0xE5FFFFFF),
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
12.sizeBoxH,
|
||||
GridView.builder(
|
||||
itemCount: min(isShort ? 6 : 4, tagVidList?.length ?? 0),
|
||||
shrinkWrap: true,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
padding: EdgeInsets.zero,
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: isShort ? 3 : 2,
|
||||
mainAxisSpacing: 12,
|
||||
crossAxisSpacing: 6,
|
||||
childAspectRatio: isShort ? 111 / 190 : 168 / 154,
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
VideoModel videoModel = tagVidList![index];
|
||||
return VideoSimpleCell(
|
||||
videoModel: videoModel,
|
||||
textLines: isShort ? 1 : 2,
|
||||
isFromSearch: true,
|
||||
);
|
||||
},
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
Get.to(
|
||||
VideoTagPage(
|
||||
TagsBean(id: tagID, name: widget.keywords),
|
||||
isFromSearch: true,
|
||||
isShortStyle: widget.type == MediaStyle.ShortVideo,
|
||||
),
|
||||
preventDuplicates: false);
|
||||
},
|
||||
child: Container(
|
||||
height: 40,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: Color(0x1AFFFFFF),
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
"查看更多",
|
||||
style: TextStyle(
|
||||
color: Color(0xffEFEFEF),
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 6),
|
||||
Icon(
|
||||
Icons.keyboard_arrow_right,
|
||||
color: Color(0xffDCDCDC),
|
||||
size: 18,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 6),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
return SizedBox();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pull_to_refresh/pull_to_refresh.dart';
|
||||
|
||||
import '../../../hj_model/cartoon_media_info.dart';
|
||||
import '../../../hj_model/home/video_library_model.dart';
|
||||
import '../../../hj_model/home/module_detail_model.dart';
|
||||
import '../../../hj_model/video_model.dart';
|
||||
import '../../../hj_utils/api_service/vid_service.dart';
|
||||
import '../../../tools_base/debug_log.dart';
|
||||
import '../../../tools_base/loading/loading_alert_widget.dart';
|
||||
|
||||
class VideoAllTypeLogic extends GetxController with GetSingleTickerProviderStateMixin {
|
||||
VideoAllTypeLogic();
|
||||
|
||||
HomeVideoLibrary? homeVideoLibrary;
|
||||
TimeType? firstSelectedType;
|
||||
TimeType? secondSelectedType;
|
||||
Tags? thirdSelectedType;
|
||||
TimeType? forthSelectedType;
|
||||
TimeType? fivthSelectedType;
|
||||
RefreshController? refreshController;
|
||||
List<VideoModel>? searchVideoList;
|
||||
List<CartoonMediaInfo>? searchACGList;
|
||||
Keyword? preKeyword;
|
||||
int currentPage = 1;
|
||||
RxBool isInitData = true.obs;
|
||||
RxBool showFloatingTags = false.obs;
|
||||
|
||||
List<Tags>? get tags {
|
||||
if (firstSelectedType?.isACG == true) {
|
||||
return homeVideoLibrary?.acgTags;
|
||||
} else {
|
||||
return homeVideoLibrary?.vidTags;
|
||||
}
|
||||
}
|
||||
|
||||
String get selectedTagsText {
|
||||
// 上滑吸顶摘要:展示分类/排序/标签/时间四个维度的当前选中值;paymentType 无入口不参与。
|
||||
// 「全部」为默认兜底选项,摘要里省略避免多出一段无意义文案。
|
||||
final names = [
|
||||
firstSelectedType?.name,
|
||||
secondSelectedType?.name,
|
||||
thirdSelectedType?.name,
|
||||
fivthSelectedType?.name,
|
||||
];
|
||||
return names.whereType<String>().where((e) => e.isNotEmpty && e != '全部').join('·');
|
||||
}
|
||||
|
||||
Keyword get keywordValue {
|
||||
Keyword keyword = Keyword();
|
||||
keyword.tags = thirdSelectedType;
|
||||
keyword.canvas = firstSelectedType;
|
||||
keyword.orderBy = secondSelectedType;
|
||||
keyword.paymentType = forthSelectedType;
|
||||
keyword.timeType = fivthSelectedType;
|
||||
return keyword;
|
||||
}
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
_loadMenuData();
|
||||
});
|
||||
}
|
||||
|
||||
void reloadMenuData() {
|
||||
isInitData.value = true;
|
||||
_loadMenuData();
|
||||
}
|
||||
|
||||
void _loadMenuData() async {
|
||||
try {
|
||||
homeVideoLibrary = await VidService.fetchLibrary();
|
||||
firstSelectedType = homeVideoLibrary?.canvas?.first;
|
||||
secondSelectedType = homeVideoLibrary?.orderBy?.first;
|
||||
thirdSelectedType = homeVideoLibrary?.vidTags?.first;
|
||||
forthSelectedType = homeVideoLibrary?.paymentType?.first;
|
||||
fivthSelectedType = homeVideoLibrary?.timeType?.first;
|
||||
_loadSearchData();
|
||||
} catch (e) {
|
||||
debugLog(e);
|
||||
}
|
||||
homeVideoLibrary ??= HomeVideoLibrary();
|
||||
isInitData.value = false;
|
||||
update();
|
||||
}
|
||||
|
||||
void reloadData() {
|
||||
searchVideoList = null;
|
||||
update();
|
||||
_loadSearchData();
|
||||
}
|
||||
|
||||
void refreshData() {
|
||||
_loadSearchData();
|
||||
}
|
||||
|
||||
void loadMoreData() {
|
||||
_loadSearchData(page: currentPage + 1);
|
||||
}
|
||||
|
||||
void menuExchangeEvent() {
|
||||
update();
|
||||
_loadSearchData(isSelectMenu: true);
|
||||
}
|
||||
|
||||
void _loadSearchData({int page = 1, int size = 10, bool isSelectMenu = false}) async {
|
||||
try {
|
||||
if (isSelectMenu) {
|
||||
LoadingAlertWidget.show();
|
||||
}
|
||||
Keyword keywordParam = keywordValue;
|
||||
HomeVideoLibraryResult? respResult = await VidService.searchLibrary(
|
||||
page,
|
||||
size,
|
||||
filterMenu: keywordParam,
|
||||
);
|
||||
if (keywordValue.modelKey != keywordParam.modelKey) {
|
||||
return;
|
||||
}
|
||||
if (isSelectMenu) {
|
||||
LoadingAlertWidget.cancel();
|
||||
}
|
||||
searchVideoList ??= [];
|
||||
searchACGList ??= [];
|
||||
currentPage = page;
|
||||
if (page == 1) {
|
||||
searchVideoList?.clear();
|
||||
searchACGList?.clear();
|
||||
}
|
||||
searchVideoList?.addAll(respResult?.list ?? []);
|
||||
searchACGList?.addAll(respResult?.allMediaList ?? []);
|
||||
respResult?.hasNext == true ? refreshController?.loadComplete() : refreshController?.loadNoData();
|
||||
} catch (e) {
|
||||
if (isSelectMenu) {
|
||||
LoadingAlertWidget.cancel();
|
||||
}
|
||||
debugLog(e);
|
||||
refreshController?.loadComplete();
|
||||
}
|
||||
refreshController?.refreshCompleted();
|
||||
searchVideoList ??= [];
|
||||
update();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,387 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgdj/assets_tool/app_colors.dart';
|
||||
import 'package:hgdj/hj_model/cartoon_media_info.dart';
|
||||
import 'package:waterfall_flow/waterfall_flow.dart';
|
||||
|
||||
import '../../../hj_model/home/video_library_model.dart';
|
||||
import '../../../hj_model/home/module_detail_model.dart';
|
||||
import '../../../hj_model/video_model.dart';
|
||||
import '../../../tools_base/loading/loading_center_widget.dart';
|
||||
import '../../../tools_base/refresh/pull_refresh.dart';
|
||||
import '../../cartoon/acg_widget_item.dart';
|
||||
import '../../cartoon/photo_gallery_item.dart';
|
||||
import '../../community/widget/community_post_widget.dart';
|
||||
import '../home_cell_style/video_simple_cell.dart';
|
||||
import 'video_all_type_logic.dart';
|
||||
|
||||
//片库
|
||||
class VideoAllTypePage extends StatefulWidget {
|
||||
const VideoAllTypePage({super.key});
|
||||
|
||||
@override
|
||||
State<VideoAllTypePage> createState() => _VideoAllTypePageState();
|
||||
}
|
||||
|
||||
class _VideoAllTypePageState extends State<VideoAllTypePage> {
|
||||
final ScrollController scrollController = ScrollController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GetBuilder<VideoAllTypeLogic>(
|
||||
init: VideoAllTypeLogic(),
|
||||
builder: (logic) {
|
||||
// 摘要文案随筛选变化,靠 GetBuilder 的 update() 重算;下方 Obx 只管吸顶显隐
|
||||
final tagsText = logic.selectedTagsText;
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('片库')),
|
||||
body: Stack(
|
||||
children: [
|
||||
Obx(
|
||||
() {
|
||||
if (logic.isInitData.value) {
|
||||
return LoadingCenterWidget();
|
||||
} else if (logic.homeVideoLibrary?.isNotEmpty != true) {
|
||||
return CErrorWidget(
|
||||
retryOnTap: () {
|
||||
logic.reloadMenuData();
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return NotificationListener<ScrollNotification>(
|
||||
onNotification: (scrollInfo) {
|
||||
if (scrollInfo.metrics.pixels > 200) {
|
||||
logic.showFloatingTags.value = true;
|
||||
} else {
|
||||
logic.showFloatingTags.value = false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
child: pullYsRefresh(
|
||||
onInit: (ctr) => logic.refreshController = ctr,
|
||||
onLoading: (_) => logic.loadMoreData(),
|
||||
onRefresh: (_) => logic.refreshData(),
|
||||
child: CustomScrollView(
|
||||
controller: scrollController,
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: Column(
|
||||
children: [
|
||||
_buildItemMenuWidget(
|
||||
"分类",
|
||||
logic.homeVideoLibrary?.canvas,
|
||||
logic.firstSelectedType, (data) {
|
||||
if (logic.firstSelectedType?.isACG !=
|
||||
data.isACG) {
|
||||
logic.thirdSelectedType = null;
|
||||
}
|
||||
if (logic.firstSelectedType != data) {
|
||||
logic.firstSelectedType = data;
|
||||
} else {
|
||||
logic.firstSelectedType = null;
|
||||
}
|
||||
logic.searchVideoList?.clear();
|
||||
logic.menuExchangeEvent();
|
||||
}),
|
||||
_buildItemMenuWidget(
|
||||
"排序",
|
||||
logic.homeVideoLibrary?.orderBy,
|
||||
logic.secondSelectedType, (data) {
|
||||
if (logic.secondSelectedType != data) {
|
||||
logic.secondSelectedType = data;
|
||||
} else {
|
||||
logic.secondSelectedType = null;
|
||||
}
|
||||
logic.menuExchangeEvent();
|
||||
}),
|
||||
_buildTagMenuWidget(
|
||||
"标签", logic.tags, logic.thirdSelectedType,
|
||||
(data) {
|
||||
if (logic.thirdSelectedType != data) {
|
||||
logic.thirdSelectedType = data;
|
||||
} else {
|
||||
logic.thirdSelectedType = null;
|
||||
}
|
||||
logic.menuExchangeEvent();
|
||||
}),
|
||||
_buildItemMenuWidget(
|
||||
"时间",
|
||||
logic.homeVideoLibrary?.timeType,
|
||||
logic.fivthSelectedType, (data) {
|
||||
if (logic.fivthSelectedType != data) {
|
||||
logic.fivthSelectedType = data;
|
||||
} else {
|
||||
logic.fivthSelectedType = null;
|
||||
}
|
||||
logic.menuExchangeEvent();
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
_buildTableContent(logic),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
Obx(() {
|
||||
if (!logic.showFloatingTags.value) return const SizedBox();
|
||||
if (tagsText.isEmpty) return const SizedBox();
|
||||
return Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
// 滚动到页面顶部
|
||||
if (scrollController.hasClients) {
|
||||
scrollController.animateTo(
|
||||
0,
|
||||
duration: Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
height: 42,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: 0.8),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
tagsText,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildConditionItem(String? title, bool isSelected) {
|
||||
return Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 6),
|
||||
margin: EdgeInsets.only(right: 5),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
title ?? "",
|
||||
style: TextStyle(
|
||||
color: isSelected ? AppColors.actionRed : Color(0x8CFFFFFF),
|
||||
fontSize: 14,
|
||||
fontWeight: isSelected ? FontWeight.w500 : FontWeight.w400,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTitleItem(String? typeName) {
|
||||
return Text(
|
||||
typeName ?? "",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTableContent(VideoAllTypeLogic logic) {
|
||||
if (logic.searchVideoList == null && logic.searchACGList == null) {
|
||||
return SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: 300,
|
||||
child: LoadingCenterWidget(),
|
||||
),
|
||||
);
|
||||
} else if (logic.searchVideoList?.isNotEmpty != true &&
|
||||
logic.searchACGList?.isNotEmpty != true) {
|
||||
return SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: 300,
|
||||
child: CErrorWidget(
|
||||
retryOnTap: () {
|
||||
logic.reloadData();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
String typeName = logic.firstSelectedType?.name ?? "";
|
||||
if (typeName == "帖子") {
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
return CommunityPostWidget(
|
||||
videoModel: logic.searchVideoList![index],
|
||||
videoModels: logic.searchVideoList,
|
||||
);
|
||||
},
|
||||
childCount: logic.searchVideoList?.length ?? 0,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (typeName == "图集") {
|
||||
return _buildWaterfall(
|
||||
logic, (vm) => PhotoGalleryItem(videoModel: vm, textline: 1));
|
||||
}
|
||||
if (typeName == "抖音" || logic.firstSelectedType?.key == "sp") {
|
||||
return _buildWaterfall(
|
||||
logic, (vm) => VideoSimpleCell(videoModel: vm, textLines: 1));
|
||||
} else if (typeName == "动漫" || typeName == "漫画") {
|
||||
return SliverPadding(
|
||||
padding: EdgeInsets.fromLTRB(12, 12, 12, 0),
|
||||
sliver: SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 3,
|
||||
mainAxisSpacing: 12.0,
|
||||
crossAxisSpacing: 6,
|
||||
childAspectRatio: 111 / 190,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
CartoonMediaInfo videoItem = logic.searchACGList![index];
|
||||
return AcgItemWidget(info: videoItem);
|
||||
},
|
||||
childCount: logic.searchACGList?.length ?? 0,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return SliverPadding(
|
||||
padding: EdgeInsets.fromLTRB(12, 12, 12, 0),
|
||||
sliver: SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
mainAxisSpacing: 12.0,
|
||||
crossAxisSpacing: 6,
|
||||
childAspectRatio: 168 / 154,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
VideoModel videoItem = logic.searchVideoList![index];
|
||||
return VideoSimpleCell(
|
||||
videoModel: videoItem,
|
||||
);
|
||||
},
|
||||
childCount: logic.searchVideoList?.length ?? 0,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//图集/抖音共用的 3 列瀑布流(仅 item 构建不同)
|
||||
Widget _buildWaterfall(
|
||||
VideoAllTypeLogic logic, Widget Function(VideoModel vm) itemBuilder) {
|
||||
return SliverPadding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 12, 12, 0),
|
||||
sliver: SliverWaterfallFlow(
|
||||
gridDelegate: const SliverWaterfallFlowDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 3,
|
||||
crossAxisSpacing: 6,
|
||||
mainAxisSpacing: 12,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(c, index) => AspectRatio(
|
||||
aspectRatio: 111 / 190,
|
||||
child: itemBuilder(logic.searchVideoList![index]),
|
||||
),
|
||||
childCount: logic.searchVideoList?.length ?? 0,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildItemMenuWidget(String typeName, List<TimeType>? listData,
|
||||
TimeType? selectType, Function(TimeType timeType) callBack) {
|
||||
selectType ??= listData?[0];
|
||||
return Container(
|
||||
height: 40,
|
||||
padding: EdgeInsets.only(left: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
_buildTitleItem(typeName),
|
||||
SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: listData?.length ?? 0,
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: EdgeInsets.symmetric(vertical: 8),
|
||||
itemBuilder: (context, index) {
|
||||
TimeType data = listData![index];
|
||||
bool isSelected = selectType?.name == data.name;
|
||||
return InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: () async {
|
||||
callBack.call(data);
|
||||
},
|
||||
child: _buildConditionItem(data.name, isSelected),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTagMenuWidget(String typeName, List<Tags>? listData,
|
||||
Tags? selectType, Function(Tags timeType) callBack) {
|
||||
selectType ??= listData?[0];
|
||||
return Container(
|
||||
height: 40,
|
||||
padding: EdgeInsets.only(left: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
_buildTitleItem(typeName),
|
||||
SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: listData?.length ?? 0,
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: EdgeInsets.symmetric(vertical: 8),
|
||||
itemBuilder: (context, index) {
|
||||
Tags data = listData![index];
|
||||
bool isSelected = selectType?.name == data.name;
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
callBack.call(data);
|
||||
},
|
||||
child: _buildConditionItem(data.name, isSelected),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgdj/assets_tool/images.dart';
|
||||
import 'package:hgdj/config/config.dart';
|
||||
import 'package:hgdj/hj_utils/screen.dart';
|
||||
import 'package:hgdj/tools_base/widget/net_image_widget.dart';
|
||||
|
||||
import '../../../../tools_base/widget/marquee_widget.dart';
|
||||
import '../../../mine/welfare/sign_daily_page.dart';
|
||||
import '../../home_main_logic.dart';
|
||||
import '../search_main_page.dart';
|
||||
|
||||
class CommonSearchBarView extends StatefulWidget {
|
||||
final HomeMainLogic? logic;
|
||||
|
||||
const CommonSearchBarView({super.key, this.logic});
|
||||
|
||||
@override
|
||||
State<CommonSearchBarView> createState() => _CommonSearchBarViewState();
|
||||
}
|
||||
|
||||
class _CommonSearchBarViewState extends State<CommonSearchBarView> {
|
||||
/// 当前轮播展示的搜索热词,点击搜索框时带入搜索页
|
||||
String? _searchText;
|
||||
|
||||
static final _hintStyle =
|
||||
TextStyle(color: Colors.white.withValues(alpha: 0.5), fontSize: 14);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (Config.searchHints.isNotEmpty) _searchText = Config.searchHints.first;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
16.w.sizeBoxW,
|
||||
Image.asset('place_holder_logo.webp'.commonImgPath, height: 28),
|
||||
8.sizeBoxW,
|
||||
Expanded(child: _buildSearchBox()),
|
||||
12.sizeBoxW,
|
||||
if (Config.signIcon != null && Config.signIcon!.isNotEmpty)
|
||||
_buildSignIcon(),
|
||||
12.sizeBoxW,
|
||||
_buildMenuIcon(),
|
||||
16.sizeBoxW,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// 中间搜索框(热词轮播)
|
||||
Widget _buildSearchBox() {
|
||||
return InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: () => Get.to(SearchMainPage(searchText: _searchText)),
|
||||
child: Container(
|
||||
height: 36,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xff1E1C1D),
|
||||
borderRadius: BorderRadius.circular(99),
|
||||
),
|
||||
child: Config.searchHints.isEmpty
|
||||
? Text('请输入关键字', style: _hintStyle)
|
||||
: MarqueeWidget(
|
||||
count: Config.searchHints.length,
|
||||
onIndexChanged: (index) =>
|
||||
_searchText = Config.searchHints[index],
|
||||
itemBuilder: (_, index) => Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(Config.searchHints[index], style: _hintStyle),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 签到入口
|
||||
Widget _buildSignIcon() {
|
||||
return InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: () => Get.to(SignDailyPage()),
|
||||
child: NetworkImageLoader(
|
||||
imageUrl: Config.signIcon,
|
||||
width: 20,
|
||||
borderRadius: 0,
|
||||
placeHolderWidget: const SizedBox(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 右侧菜单入口
|
||||
Widget _buildMenuIcon() {
|
||||
return InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: () => widget.logic?.openEndDrawer(),
|
||||
child: Image.asset('acg_menu.png'.acgImgPath,
|
||||
width: 24, color: const Color(0xff989898)),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgdj/assets_tool/images.dart';
|
||||
import 'package:hgdj/hj_utils/screen.dart';
|
||||
import 'package:hgdj/tools_base/toast.dart';
|
||||
|
||||
import '../video_all_type_page.dart';
|
||||
|
||||
final class SearchAppBar extends StatefulWidget {
|
||||
final Function(String keywork) onSubmitted;
|
||||
final bool? isClose;
|
||||
final String? searchText;
|
||||
|
||||
/// 外部持有的输入控制器:搜索主页由 logic 持有,点热搜词/历史项后好回填。
|
||||
/// 不传则本组件自建自销——每个 SearchAppBar 各用各的,不再共享全局单例
|
||||
final TextEditingController? controller;
|
||||
|
||||
const SearchAppBar({
|
||||
super.key,
|
||||
required this.onSubmitted,
|
||||
this.searchText,
|
||||
this.isClose,
|
||||
this.controller,
|
||||
});
|
||||
|
||||
@override
|
||||
State<SearchAppBar> createState() => _SearchAppBarState();
|
||||
}
|
||||
|
||||
class _SearchAppBarState extends State<SearchAppBar> {
|
||||
late final searchTextCtr = widget.controller ?? TextEditingController();
|
||||
late bool showDeleteIcon;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final initText = widget.searchText ?? "";
|
||||
showDeleteIcon = initText.isNotEmpty;
|
||||
// 控制器不再跨路由共享,此处赋值不会 markNeedsBuild 别的路由,无需延到首帧后
|
||||
searchTextCtr.text = initText;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// 只释放自己 new 的;外部传进来的归调用方管
|
||||
if (widget.controller == null) searchTextCtr.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(top: screen.paddingTop),
|
||||
color: Colors.black,
|
||||
child: Container(
|
||||
height: 56,
|
||||
padding: EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
showDeleteIcon = false;
|
||||
searchTextCtr.text = "";
|
||||
});
|
||||
Get.back();
|
||||
},
|
||||
child: Image.asset(
|
||||
'common_back.png'.commonImgPath,
|
||||
width: 22,
|
||||
),
|
||||
),
|
||||
10.sizeBoxW,
|
||||
Expanded(
|
||||
child: Container(
|
||||
height: 32,
|
||||
padding: EdgeInsets.symmetric(horizontal: 10),
|
||||
alignment: Alignment.centerLeft,
|
||||
decoration: BoxDecoration(
|
||||
color: Color(0x11FFFFFF),
|
||||
borderRadius: BorderRadius.circular(20)),
|
||||
child: Row(
|
||||
children: [
|
||||
InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: () {
|
||||
Get.to(() => VideoAllTypePage(), opaque: false);
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
2.sizeBoxW,
|
||||
Image.asset('libary_search.webp'.homePath, width: 20),
|
||||
6.sizeBoxW,
|
||||
Text(
|
||||
'片库',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFF68804),
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
6.sizeBoxW,
|
||||
Container(
|
||||
height: 12,
|
||||
width: 1,
|
||||
color: Colors.white.withValues(alpha: .05),
|
||||
),
|
||||
6.sizeBoxW,
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: searchTextCtr,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: .8),
|
||||
fontSize: 14,
|
||||
height: 1),
|
||||
onSubmitted: (value) => widget.onSubmitted(value),
|
||||
onChanged: (value) {
|
||||
if (value.isNotEmpty == true) {
|
||||
setState(() {
|
||||
showDeleteIcon = true;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
showDeleteIcon = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
textInputAction: TextInputAction.search,
|
||||
decoration: InputDecoration(
|
||||
hintText: '搜索关键词',
|
||||
hintStyle: TextStyle(
|
||||
color: Colors.white.withValues(alpha: .55),
|
||||
fontSize: 14,
|
||||
height: 1),
|
||||
border: InputBorder.none,
|
||||
isDense: true,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
),
|
||||
),
|
||||
),
|
||||
12.sizeBoxW,
|
||||
Visibility(
|
||||
visible: showDeleteIcon,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
searchTextCtr.text = "";
|
||||
showDeleteIcon = false;
|
||||
});
|
||||
},
|
||||
child: Image.asset('search_close.webp'.homePath,
|
||||
width: 18),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
10.sizeBoxW,
|
||||
InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: () {
|
||||
if (widget.isClose == true) {
|
||||
setState(() {
|
||||
showDeleteIcon = false;
|
||||
});
|
||||
}
|
||||
if (searchTextCtr.text.isEmpty == true) {
|
||||
showToast("请输入关键字");
|
||||
return;
|
||||
}
|
||||
widget.onSubmitted(searchTextCtr.text);
|
||||
},
|
||||
child: Text(
|
||||
"搜索",
|
||||
style: TextStyle(
|
||||
color: Color(0x73FFFFFF),
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hgdj/assets_tool/images.dart';
|
||||
import 'package:hgdj/hj_utils/screen.dart';
|
||||
|
||||
/// 搜索历史区:纯展示,数据与增删由 SearchMainLogic 持有
|
||||
class SearchHistoryView extends StatefulWidget {
|
||||
const SearchHistoryView({
|
||||
super.key,
|
||||
required this.histories,
|
||||
required this.onHistoryClick,
|
||||
required this.onClearAll,
|
||||
});
|
||||
|
||||
final List<String> histories;
|
||||
final Function(String keyword) onHistoryClick;
|
||||
final VoidCallback onClearAll;
|
||||
|
||||
@override
|
||||
State<SearchHistoryView> createState() => _SearchHistoryViewState();
|
||||
}
|
||||
|
||||
class _SearchHistoryViewState extends State<SearchHistoryView> {
|
||||
/// 折叠状态最多展示几条
|
||||
static const _collapsedMax = 8;
|
||||
|
||||
bool isExpandData = false;
|
||||
|
||||
int get itemCount {
|
||||
final total = widget.histories.length;
|
||||
return isExpandData ? total : min(_collapsedMax, total);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.histories.isEmpty) return const SizedBox.shrink();
|
||||
return Container(
|
||||
padding: const EdgeInsets.only(left: 12, right: 12, top: 12),
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text(
|
||||
'历史记录',
|
||||
style: TextStyle(
|
||||
color: Color(0xE5FFFFFF),
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: widget.onClearAll,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(1),
|
||||
child: Image.asset("history_delete.webp".homePath,
|
||||
width: 18, height: 18),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
12.sizeBoxH,
|
||||
GridView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
itemCount: itemCount,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 4,
|
||||
childAspectRatio: 79 / 34,
|
||||
mainAxisSpacing: 12,
|
||||
crossAxisSpacing: 12,
|
||||
),
|
||||
itemBuilder: (_, index) =>
|
||||
_buildHistoryItem(widget.histories[index]),
|
||||
),
|
||||
_buildMoreRecord(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHistoryItem(String tag) {
|
||||
return GestureDetector(
|
||||
onTap: () => widget.onHistoryClick(tag),
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0x0DFFFFFF),
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
),
|
||||
child: Text(
|
||||
tag,
|
||||
maxLines: 1,
|
||||
style: const TextStyle(color: Color(0xE5FFFFFF), fontSize: 12),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 「查看 / 收起完整记录」按钮:少于 [_collapsedMax] 条时不显示
|
||||
Widget _buildMoreRecord() {
|
||||
if (widget.histories.length < _collapsedMax) {
|
||||
return const SizedBox();
|
||||
}
|
||||
return Container(
|
||||
padding: const EdgeInsets.only(top: 12),
|
||||
alignment: Alignment.topCenter,
|
||||
child: InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: () => setState(() => isExpandData = !isExpandData),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
isExpandData ? "收起完整记录" : "查看完整记录",
|
||||
style: const TextStyle(color: Color(0xff989898), fontSize: 12),
|
||||
),
|
||||
4.sizeBoxW,
|
||||
Transform.rotate(
|
||||
angle: isExpandData ? pi : 0,
|
||||
child: Image.asset(
|
||||
"arrow_down.png".commonImgPath,
|
||||
width: 12,
|
||||
color: const Color(0xffDCDCDC),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:hgdj/config/config.dart';
|
||||
import 'package:hgdj/hj_utils/screen.dart';
|
||||
|
||||
class SearchHotTagView extends StatelessWidget {
|
||||
final Function(String keyword) onTagClick;
|
||||
|
||||
const SearchHotTagView({super.key, required this.onTagClick});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final tags = Config.hotWords;
|
||||
if (tags.isEmpty) return const SizedBox.shrink();
|
||||
return Container(
|
||||
padding: const EdgeInsets.only(left: 12, right: 12, top: 18),
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'大家都在搜',
|
||||
style: TextStyle(
|
||||
color: Color(0xE5FFFFFF),
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
12.h.sizeBoxH,
|
||||
GridView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
itemCount: tags.length,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 4,
|
||||
childAspectRatio: 79 / 34,
|
||||
mainAxisSpacing: 12,
|
||||
crossAxisSpacing: 12,
|
||||
),
|
||||
itemBuilder: (_, index) => _buildTagItem(tags[index]),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTagItem(String tag) {
|
||||
return GestureDetector(
|
||||
onTap: () => onTagClick(tag),
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0x0DFFFFFF),
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
),
|
||||
child: Text(
|
||||
tag,
|
||||
maxLines: 1,
|
||||
style: const TextStyle(
|
||||
color: Color(0xE5FFFFFF),
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgdj/assets_tool/app_colors.dart';
|
||||
import 'package:hgdj/extension/extensions.dart';
|
||||
import 'package:hgdj/hj_model/video_model.dart';
|
||||
import 'package:hgdj/hj_utils/screen.dart';
|
||||
import 'package:hgdj/tools_base/widget/net_image_widget.dart';
|
||||
|
||||
import '../../../community/community_tag_page/community_tag_page.dart';
|
||||
|
||||
class TopicItem extends StatelessWidget {
|
||||
final TagsBean model;
|
||||
final bool isSelect;
|
||||
final Function()? onTap;
|
||||
|
||||
const TopicItem(
|
||||
{super.key, required this.model, this.onTap, this.isSelect = false});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () {
|
||||
if (onTap != null) {
|
||||
onTap!();
|
||||
} else {
|
||||
Get.to(() => CommunityTagDetailPage(model: model));
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
height: 66,
|
||||
padding: EdgeInsets.only(left: 12.w, right: 12.w),
|
||||
decoration: BoxDecoration(
|
||||
color: Color(0x0DFFFFFF),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
NetworkImageLoader(
|
||||
imageUrl: model.coverImg ?? '',
|
||||
width: 38,
|
||||
height: 38,
|
||||
borderRadius: 3,
|
||||
),
|
||||
10.sizeBoxW,
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'#${model.name}',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
// 4.sizeBoxH,
|
||||
Text(
|
||||
'${model.vidCount?.countStr}个帖子 ${model.playCount.countStr}浏览 ${model.followCount.countStr}关注',
|
||||
style: TextStyle(color: Color(0x73FFFFFF), fontSize: 12),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
12.sizeBoxW,
|
||||
Container(
|
||||
width: 52,
|
||||
height: 26,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: isSelect ? AppColors.actionRed : Color(0x33FFFFFF),
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
child: Text(
|
||||
isSelect ? '已选' : "选择",
|
||||
style: TextStyle(
|
||||
color: isSelect ? Color(0xffffffff) : Color(0xffDCDCDC),
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user