初始化

This commit is contained in:
谢宇宁
2026-09-15 15:44:13 +07:00
commit a23ba685e9
1065 changed files with 101122 additions and 0 deletions
@@ -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,
),
),
),
],
),
),
);
}
}