初始化

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,101 @@
import 'dart:async';
import 'dart:math';
import 'package:flutter/cupertino.dart';
import 'package:hgdj/hj_model/video_model.dart';
import 'package:hgdj/tools_base/event_bus/event_bus_util.dart';
import 'package:hgdj/tools_base/event_bus/events.dart';
import '../../../hj_model/acg/cartoon_more_list.dart';
import '../../../hj_model/cartoon_media_info.dart';
typedef WidgetBuilder = Widget Function();
/// 将点赞/收藏/关注事件同步到 [VideoModel](列表与详情可共用同一引用)
void applyVideoModelCollectStatus(VideoModel? model, CollectStatusModel event) {
if (model == null || event.id?.isNotEmpty != true || model.id != event.id) {
return;
}
if (event.isCollected != null) {
model.vidStatus?.hasCollected = event.isCollected;
}
if (event.isLiked != null) {
model.vidStatus?.hasLiked = event.isLiked;
if (event.likeCountDelta != null) {
model.likeCount = max(0, (model.likeCount ?? 0) + event.likeCountDelta!);
}
}
if (event.isFollowed != null && event.uid == model.publisher?.uid) {
model.publisher?.hasFollowed = event.isFollowed;
}
}
class CollectStatusWrapper extends StatefulWidget {
final WidgetBuilder builder;
final AllMediaInfo? allMediaInfo;
final CartoonMediaInfo? mediaInfo;
final VideoModel? videoModel;
final TagsBean? tagModel;
const CollectStatusWrapper({
required this.builder,
super.key,
this.mediaInfo,
this.allMediaInfo,
this.videoModel,
this.tagModel,
});
@override
State<StatefulWidget> createState() {
return _CollectStatusWrapperState();
}
}
class _CollectStatusWrapperState extends State<CollectStatusWrapper> {
late StreamSubscription subscription;
@override
void initState() {
super.initState();
subscription = eventBus.on<CollectStatusModel>(_listenCallback);
}
void _listenCallback(CollectStatusModel model) {
String? obId = widget.allMediaInfo?.id ??
widget.mediaInfo?.id ??
widget.videoModel?.id ??
widget.tagModel?.id;
//收藏和点赞逻辑
if (obId == model.id && obId?.isNotEmpty == true) {
if (model.isCollected != null) {
widget.allMediaInfo?.mediaStatus?.hasCollected = model.isCollected;
widget.mediaInfo?.mediaStatus?.hasCollected = model.isCollected;
widget.videoModel?.vidStatus?.hasCollected = model.isCollected;
widget.tagModel?.hasCollected = model.isCollected;
setState(() {});
} else if (model.isLiked != null) {
widget.allMediaInfo?.mediaStatus?.hasLiked = model.isLiked;
widget.mediaInfo?.mediaStatus?.hasLiked = model.isLiked;
widget.videoModel?.vidStatus?.hasLiked = model.isLiked;
setState(() {});
} else {}
}
// 用户关注逻辑
if (model.isFollowed != null &&
model.uid == widget.videoModel?.publisher?.uid) {
widget.videoModel?.publisher?.hasFollowed = model.isFollowed;
setState(() {});
}
}
@override
Widget build(BuildContext context) {
return widget.builder();
}
@override
void dispose() {
subscription.cancel();
eventBus.off(subscription);
super.dispose();
}
}
@@ -0,0 +1,85 @@
import 'package:flutter/material.dart';
class HomeSortMenuView extends StatefulWidget {
final List<String> titleArr;
final int selectIndex;
final Function(int)? callback;
final String gapChar;
const HomeSortMenuView(this.titleArr, {super.key, this.selectIndex = 0, this.callback, this.gapChar = '/'});
@override
State<StatefulWidget> createState() {
return _HomeSortMenuViewState();
}
}
class _HomeSortMenuViewState extends State<HomeSortMenuView> {
int selectIndex = 0;
@override
void initState() {
selectIndex = widget.selectIndex;
super.initState();
}
@override
void didUpdateWidget(covariant HomeSortMenuView oldWidget) {
super.didUpdateWidget(oldWidget);
}
@override
Widget build(BuildContext context) {
List<Widget> widgetArr = [];
for (int i = 0; i < widget.titleArr.length; i++) {
widgetArr.add(_buildMenuButton(widget.titleArr[i], i == selectIndex, i));
}
return Row(
mainAxisSize: MainAxisSize.min,
children: widgetArr,
);
}
Widget _buildMenuButton(String text, bool isSelected, int index) {
bool isLast = index == (widget.titleArr.length - 1);
return InkWell(enableFeedback: false,
onTap: () {
selectIndex = index;
setState(() {});
widget.callback?.call(index);
},
child: Container(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 0),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
text,
style: TextStyle(
fontSize: 14,
color: isSelected ? Color(0xE5FFFFFF) : const Color(0x73FFFFFF),
),
),
const SizedBox(width: 12),
if (isLast)
const SizedBox(width: 12)
else
Text(
widget.gapChar,
style: TextStyle(
fontSize: 14,
color: Color(0x73FFFFFF),
),
),
const SizedBox(width: 12),
],
),
),
);
}
@override
void dispose() {
super.dispose();
}
}
@@ -0,0 +1,564 @@
import 'dart:async';
import 'dart:math';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:get/get.dart';
import 'package:hgdj/assets_tool/images.dart';
import 'package:hgdj/config/config.dart';
import 'package:hgdj/hj_model/splash/domain_source_model.dart';
import 'package:hgdj/hj_utils/screen.dart';
import '../../../hj_utils/api_service/vid_service.dart';
import '../../../routers/jump_router.dart';
class HomeTxtMarQuee extends StatefulWidget {
final double stepOffset;
//首页读取type=1,暗网读取type=2,发现读取type=4,社区读取type=3,我的界面读取type=0
final int typeValue;
final EdgeInsetsGeometry? margin;
final bool showClose;
final double? fontSize;
final double? iconSize;
final double? height;
final EdgeInsets? padding;
final double borderRadius;
HomeTxtMarQuee({
this.stepOffset = 1,
this.typeValue = 0,
this.margin,
this.showClose = true,
this.fontSize,
this.iconSize,
this.height,
this.padding,
this.borderRadius = 0,
super.key,
});
@override
State<StatefulWidget> createState() {
return HomeTxtMarQueeState();
}
}
class HomeTxtMarQueeState extends State<HomeTxtMarQuee>
with SingleTickerProviderStateMixin {
List<MarqueeModel>? marquees;
bool isShow = true;
// 关闭收起动画(value 1→0:高度收起 + 淡出)
late final AnimationController closeAniCtr = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 300),
value: 1,
);
@override
void initState() {
super.initState();
marquees = Config.marquees;
if (marquees == null) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_loadData();
});
}
}
_loadData() async {
await VidService.fetchAnnounce(widget.typeValue);
marquees = Config.marquees;
if (mounted) setState(() {});
}
// 点关闭先播收起动画,结束后再移除
void _close() {
closeAniCtr.reverse().then((_) {
if (mounted) setState(() => isShow = false);
});
}
@override
void dispose() {
closeAniCtr.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (marquees == null || marquees!.isEmpty || !isShow) return SizedBox();
return FadeTransition(
opacity: closeAniCtr,
child: _marqueeBar(),
);
}
Widget _marqueeBar() {
return Container(
height: widget.height ?? 36,
alignment: Alignment.centerLeft,
margin: widget.margin,
padding: widget.padding ??
EdgeInsets.only(left: 14, right: 14, top: 8, bottom: 8),
decoration: BoxDecoration(
color: Color(0xCC000000),
borderRadius: BorderRadius.circular(widget.borderRadius),
),
child: Row(
children: [
Image.asset(
"icon_broadcast.png".commonImgPath,
width: widget.iconSize ?? 20,
),
6.sizeBoxW,
Expanded(
child: InfiniteMarquee(
stepOffset: widget.stepOffset,
itemBuilder: (BuildContext context, int index) {
final idx = index % marquees!.length;
final model = marquees![idx];
return InkWell(
enableFeedback: false,
onTap: () => pushToPageByLink(model.url),
child: Text(
model.content ?? '',
style: TextStyle(
color: Color(0xff989898),
fontSize: widget.fontSize ?? 12,
height: 1.4),
),
);
},
),
),
if (widget.showClose) ...[
6.sizeBoxW,
GestureDetector(
onTap: _close,
child: Icon(Icons.close, size: 18, color: Color(0xffDCDCDC)),
)
],
],
),
);
}
}
// 无限滚动list
class InfiniteMarquee extends StatefulWidget {
/// 每次移动步长,默认0.5,越大越快
final double stepOffset;
/// 自定义内容
final IndexedWidgetBuilder itemBuilder;
InfiniteMarquee({
super.key,
this.stepOffset = 0.5,
required this.itemBuilder,
});
@override
State<InfiniteMarquee> createState() => _InfiniteMarqueeState();
}
class _InfiniteMarqueeState extends State<InfiniteMarquee> {
// 执行动画的controller
InfiniteScrollController? _controller;
// 定时器timer
Timer? _timer;
// 定时器时间
Duration duration = Duration(milliseconds: 30);
// 手势打断定时器
bool timerStop = false;
// 执行位移开始的偏移量
double _offset = 0.0;
@override
void initState() {
super.initState();
_controller = InfiniteScrollController(initialScrollOffset: _offset);
_startScrollTimer();
}
/// 开启定时器
_startScrollTimer() {
_timer = Timer.periodic(duration, (timer) {
_autoScroll();
});
}
/// 自动滚动
_autoScroll() {
double newOffset = (_controller?.offset ?? 0) + widget.stepOffset;
if (timerStop == false) {
_offset = newOffset;
_controller?.jumpTo(_offset);
}
}
@override
void dispose() {
_timer?.cancel();
_controller?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
/// 监听滚动
return InfiniteListView.separated(
scrollDirection: Axis.horizontal,
controller: _controller,
itemBuilder: widget.itemBuilder,
physics: NeverScrollableScrollPhysics(),
separatorBuilder: (BuildContext context, int index) =>
SizedBox(width: Get.width - 100),
anchor: 0,
);
}
}
class InfiniteListView extends StatefulWidget {
/// See [ListView.builder]
const InfiniteListView.builder({
super.key,
this.scrollDirection = Axis.vertical,
this.reverse = false,
this.controller,
this.physics,
this.padding,
this.itemExtent,
required this.itemBuilder,
this.itemCount,
this.addAutomaticKeepAlives = true,
this.addRepaintBoundaries = true,
this.addSemanticIndexes = true,
this.cacheExtent,
this.anchor = 0.0,
this.dragStartBehavior = DragStartBehavior.start,
this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
this.restorationId,
this.clipBehavior = Clip.hardEdge,
}) : separatorBuilder = null;
/// See [ListView.separated]
const InfiniteListView.separated({
super.key,
this.scrollDirection = Axis.vertical,
this.reverse = false,
this.controller,
this.physics,
this.padding,
required this.itemBuilder,
required this.separatorBuilder,
this.itemCount,
this.addAutomaticKeepAlives = true,
this.addRepaintBoundaries = true,
this.addSemanticIndexes = true,
this.cacheExtent,
this.anchor = 0.0,
this.dragStartBehavior = DragStartBehavior.start,
this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
this.restorationId,
this.clipBehavior = Clip.hardEdge,
}) : itemExtent = null;
/// See: [ScrollView.scrollDirection]
final Axis scrollDirection;
/// See: [ScrollView.reverse]
final bool reverse;
/// See: [ScrollView.controller]
final InfiniteScrollController? controller;
/// See: [ScrollView.physics]
final ScrollPhysics? physics;
/// See: [BoxScrollView.padding]
final EdgeInsets? padding;
/// See: [ListView.builder]
final IndexedWidgetBuilder itemBuilder;
/// See: [ListView.separated]
final IndexedWidgetBuilder? separatorBuilder;
/// See: [SliverChildBuilderDelegate.childCount]
final int? itemCount;
/// See: [ListView.itemExtent]
final double? itemExtent;
/// See: [ScrollView.cacheExtent]
final double? cacheExtent;
/// See: [ScrollView.anchor]
final double anchor;
/// See: [SliverChildBuilderDelegate.addAutomaticKeepAlives]
final bool addAutomaticKeepAlives;
/// See: [SliverChildBuilderDelegate.addRepaintBoundaries]
final bool addRepaintBoundaries;
/// See: [SliverChildBuilderDelegate.addSemanticIndexes]
final bool addSemanticIndexes;
/// See: [ScrollView.dragStartBehavior]
final DragStartBehavior dragStartBehavior;
/// See: [ScrollView.keyboardDismissBehavior]
final ScrollViewKeyboardDismissBehavior keyboardDismissBehavior;
/// See: [ScrollView.restorationId]
final String? restorationId;
/// See: [ScrollView.clipBehavior]
final Clip clipBehavior;
@override
_InfiniteListViewState createState() => _InfiniteListViewState();
}
class _InfiniteListViewState extends State<InfiniteListView> {
InfiniteScrollController? _controller;
InfiniteScrollController get _effectiveController =>
widget.controller ?? _controller!;
@override
void initState() {
super.initState();
if (widget.controller == null) {
_controller = InfiniteScrollController();
}
}
@override
void didUpdateWidget(InfiniteListView oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.controller == null && oldWidget.controller != null) {
_controller = InfiniteScrollController();
} else if (widget.controller != null && oldWidget.controller == null) {
_controller!.dispose();
_controller = null;
}
}
@override
void dispose() {
_controller?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final List<Widget> slivers = _buildSlivers(context, negative: false);
final List<Widget> negativeSlivers = _buildSlivers(context, negative: true);
final AxisDirection axisDirection = _getDirection(context);
final scrollPhysics =
widget.physics ?? const AlwaysScrollableScrollPhysics();
return Scrollable(
axisDirection: axisDirection,
controller: _effectiveController,
physics: scrollPhysics,
viewportBuilder: (BuildContext context, ViewportOffset offset) {
return Builder(builder: (BuildContext context) {
/// Build negative [ScrollPosition] for the negative scrolling [Viewport].
final state = Scrollable.of(context);
final negativeOffset = _InfiniteScrollPosition(
physics: scrollPhysics,
context: state,
initialPixels: -offset.pixels,
keepScrollOffset: _effectiveController.keepScrollOffset,
negativeScroll: true,
);
/// Keep the negative scrolling [Viewport] positioned to the [ScrollPosition].
offset.addListener(() {
negativeOffset._forceNegativePixels(offset.pixels);
});
/// Stack the two [Viewport]s on top of each other so they move in sync.
return Stack(
children: <Widget>[
Viewport(
axisDirection: flipAxisDirection(axisDirection),
anchor: 1.0 - widget.anchor,
offset: negativeOffset,
slivers: negativeSlivers,
cacheExtent: widget.cacheExtent,
),
Viewport(
axisDirection: axisDirection,
anchor: widget.anchor,
offset: offset,
slivers: slivers,
cacheExtent: widget.cacheExtent,
),
],
);
});
},
);
}
AxisDirection _getDirection(BuildContext context) {
return getAxisDirectionFromAxisReverseAndDirectionality(
context, widget.scrollDirection, widget.reverse);
}
List<Widget> _buildSlivers(BuildContext context, {bool negative = false}) {
final itemExtent = widget.itemExtent;
final padding = widget.padding ?? EdgeInsets.zero;
return <Widget>[
SliverPadding(
padding: negative
? padding - EdgeInsets.only(bottom: padding.bottom)
: padding - EdgeInsets.only(top: padding.top),
sliver: (itemExtent != null)
? SliverFixedExtentList(
delegate: negative
? negativeChildrenDelegate
: positiveChildrenDelegate,
itemExtent: itemExtent,
)
: SliverList(
delegate: negative
? negativeChildrenDelegate
: positiveChildrenDelegate,
),
)
];
}
SliverChildDelegate get negativeChildrenDelegate {
return SliverChildBuilderDelegate(
(BuildContext context, int index) {
final separatorBuilder = widget.separatorBuilder;
if (separatorBuilder != null) {
final itemIndex = (-1 - index) ~/ 2;
return index.isOdd
? widget.itemBuilder(context, itemIndex)
: separatorBuilder(context, itemIndex);
} else {
return widget.itemBuilder(context, -1 - index);
}
},
childCount: widget.itemCount,
addAutomaticKeepAlives: widget.addAutomaticKeepAlives,
addRepaintBoundaries: widget.addRepaintBoundaries,
);
}
SliverChildDelegate get positiveChildrenDelegate {
final separatorBuilder = widget.separatorBuilder;
final itemCount = widget.itemCount;
return SliverChildBuilderDelegate(
(separatorBuilder != null)
? (BuildContext context, int index) {
final itemIndex = index ~/ 2;
return index.isEven
? widget.itemBuilder(context, itemIndex)
: separatorBuilder(context, itemIndex);
}
: widget.itemBuilder,
childCount: separatorBuilder == null
? itemCount
: (itemCount != null ? max(0, itemCount * 2 - 1) : null),
addAutomaticKeepAlives: widget.addAutomaticKeepAlives,
addRepaintBoundaries: widget.addRepaintBoundaries,
);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
.add(EnumProperty<Axis>('scrollDirection', widget.scrollDirection));
properties.add(FlagProperty('reverse',
value: widget.reverse, ifTrue: 'reversed', showName: true));
properties.add(DiagnosticsProperty<ScrollController>(
'controller', widget.controller,
showName: false, defaultValue: null));
properties.add(DiagnosticsProperty<ScrollPhysics>('physics', widget.physics,
showName: false, defaultValue: null));
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>(
'padding', widget.padding,
defaultValue: null));
properties.add(
DoubleProperty('itemExtent', widget.itemExtent, defaultValue: null));
properties.add(
DoubleProperty('cacheExtent', widget.cacheExtent, defaultValue: null));
}
}
/// Same as a [ScrollController] except it provides [ScrollPosition] objects with infinite bounds.
class InfiniteScrollController extends ScrollController {
/// Creates a new [InfiniteScrollController]
InfiniteScrollController({
super.initialScrollOffset,
super.keepScrollOffset,
super.debugLabel,
});
@override
ScrollPosition createScrollPosition(ScrollPhysics physics,
ScrollContext context, ScrollPosition? oldPosition) {
return _InfiniteScrollPosition(
physics: physics,
context: context,
initialPixels: initialScrollOffset,
keepScrollOffset: keepScrollOffset,
oldPosition: oldPosition,
debugLabel: debugLabel,
);
}
}
class _InfiniteScrollPosition extends ScrollPositionWithSingleContext {
_InfiniteScrollPosition({
required super.physics,
required super.context,
super.initialPixels,
super.keepScrollOffset,
super.oldPosition,
super.debugLabel,
this.negativeScroll = false,
});
final bool negativeScroll;
void _forceNegativePixels(double value) {
super.forcePixels(-value);
}
@override
void saveScrollOffset() {
if (!negativeScroll) {
super.saveScrollOffset();
}
}
@override
void restoreScrollOffset() {
if (!negativeScroll) {
super.restoreScrollOffset();
}
}
@override
double get minScrollExtent => double.negativeInfinity;
@override
double get maxScrollExtent => double.infinity;
}
@@ -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/app_colors.dart';
import 'package:hgdj/assets_tool/images.dart';
import 'package:hgdj/hj_utils/screen.dart'; // sizeBoxH/sizeBoxW 扩展
import 'package:hgdj/tools_base/widget/sheet_handle_bar.dart';
import '../../community/publish_page/publish_page.dart';
enum PublishEntry {
community(40, 40);
final double width;
final double height;
const PublishEntry(this.width, this.height);
String realPath() => 'publish.png'.homePath;
}
class PublishButton extends StatelessWidget {
final PublishEntry entry;
const PublishButton({super.key, this.entry = PublishEntry.community});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => Get.bottomSheet(CommunityPublishBottomSheet(),
isScrollControlled: true, isDismissible: true),
child: Image.asset(
entry.realPath(),
width: entry.width.w,
height: entry.height.h,
),
);
}
}
class CommunityPublishBottomSheet extends StatelessWidget {
const CommunityPublishBottomSheet({super.key});
@override
Widget build(BuildContext context) {
return Container(
// 底部加系统安全区,避免手势导航条遮住按钮(如 vivo iQOO 13)
padding: EdgeInsets.only(
left: 18.w,
right: 18.w,
top: 18,
bottom: 18 + Get.mediaQuery.padding.bottom),
decoration: BoxDecoration(
color: AppColors.primaryColor,
borderRadius: BorderRadius.vertical(top: Radius.circular(13))),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const SheetHandleBar(),
18.sizeBoxH,
Text(
'选择发布类型',
style: TextStyle(
color: Colors.white,
fontSize: 20.sp,
fontWeight: FontWeight.w900),
),
40.h.sizeBoxH,
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_publishItem('publish_img.webp', '图片', PublishType.homeImg),
40.sizeBoxW,
_publishItem('publish_video.webp', '视频', PublishType.homeVideo),
40.sizeBoxW,
_publishItem(
'publish_img_text.webp', '图文', PublishType.homeImgText),
],
),
],
),
);
}
Widget _publishItem(String img, String label, PublishType type) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
Get.back();
Get.to(() => PublishPage(type: type));
},
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Image.asset(img.communityPath, width: 45.w, height: 45.w),
4.sizeBoxH,
Text(label,
style:
TextStyle(color: const Color(0xffa3a2a2), fontSize: 18.sp)),
],
),
);
}
}
@@ -0,0 +1,79 @@
import 'dart:math';
import 'package:easy_rich_text/easy_rich_text.dart';
import 'package:flutter/material.dart';
import 'package:hgdj/assets_tool/images.dart';
import 'package:hgdj/hj_model/comment/comment_list_res.dart';
import 'package:hgdj/routers/jump_router.dart';
class QuickSearchView extends StatefulWidget {
final List<CommentLink> dataSource;
const QuickSearchView(this.dataSource, {super.key});
@override
State<StatefulWidget> createState() {
return _QuickSearchViewState();
}
}
class _QuickSearchViewState extends State<QuickSearchView> {
CommentLink? model;
final realQuickSearchs = <CommentLink>[];
@override
void initState() {
super.initState();
realQuickSearchs.addAll(widget.dataSource.where((e) => e.type == 2));
loadData();
}
void loadData() {
if (model == null && realQuickSearchs.isNotEmpty) {
if (realQuickSearchs.length > 1) {
final index = Random().nextInt(realQuickSearchs.length);
model = realQuickSearchs[index];
} else {
model = realQuickSearchs.first;
}
}
}
@override
void didUpdateWidget(covariant QuickSearchView oldWidget) {
loadData();
super.didUpdateWidget(oldWidget);
}
@override
Widget build(BuildContext context) {
if (model == null) return SizedBox();
return GestureDetector(
onTap: () {
pushToPageByLink(model?.link ?? '', arguments: {'id': model?.id});
},
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
child: EasyRichText(
'${model?.title ?? ''}',
defaultStyle: TextStyle(color: Colors.white, fontSize: 14),
patternList: [
EasyRichTextPattern(
targetString: model?.searchKeyword ?? '',
style: TextStyle(
color: Color(0xffDB361F), fontSize: 14, height: 1))
],
),
),
Image.asset(
'hot_search.png'.communityPath,
width: 9,
height: 9,
),
],
),
);
}
}
@@ -0,0 +1,285 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:hgdj/assets_tool/app_colors.dart';
import 'package:hgdj/assets_tool/images.dart';
import 'package:hgdj/hj_utils/screen.dart';
import 'package:hgdj/tools_base/global_store/store.dart';
import 'package:provider/provider.dart';
import '../../mine/mine_vip/mine_charge_coin_page.dart';
import '../../mine/widgets/gradient_text.dart';
class SupportUPAlert extends StatefulWidget {
final bool isPublish;
final int? selectCoin;
const SupportUPAlert({super.key, this.isPublish = false, this.selectCoin});
@override
State<SupportUPAlert> createState() => _SupportUPAlertState();
}
class _SupportUPAlertState extends State<SupportUPAlert> {
Rx<int?> selectValue = Rx<int?>(0);
final textEidtCtr = TextEditingController();
final focusN = FocusNode();
final coins = ['10', '20', '30', '40', '50'];
var enable = false.obs;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
globalStore.refreshWallet().then((value) => checkEnable());
});
selectValue.value = widget.selectCoin;
final contain = coins.contains(widget.selectCoin.toString());
if (!contain)
textEidtCtr.text =
widget.selectCoin == null ? '' : widget.selectCoin.toString();
checkEnable();
}
checkEnable() {
if (widget.isPublish) {
enable.value = true;
} else {
final gold = globalStore.wallet?.amount ?? 0;
if (gold == 0)
enable.value = false;
else {
final selectCoin = selectValue.value ?? 0;
if (selectCoin == 0)
enable.value = true;
else {
enable.value = gold >= selectCoin;
}
}
}
}
@override
Widget build(BuildContext context) {
return Container(
decoration: const BoxDecoration(
color: Color(0xff0F0F0F),
borderRadius: BorderRadius.all(Radius.circular(12)),
),
child: _buildContent(),
);
}
_buildContent() {
return Container(
height: 420,
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
Container(
width: 60,
margin: EdgeInsets.symmetric(vertical: 18),
height: 6,
decoration: BoxDecoration(
color: Color(0x33FFFFFF),
borderRadius: BorderRadius.circular(4),
),
),
Text(
widget.isPublish ? '设置价格' : '为喜欢的UP主加油',
style: TextStyle(
color: Colors.white.withValues(alpha: .9),
fontSize: 20,
fontWeight: FontWeight.w500),
),
18.sizeBoxH,
Container(
padding: EdgeInsets.symmetric(horizontal: 18),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LayoutBuilder(builder: (_, cons) {
final width_ = (cons.maxWidth - 48) / 4;
return Wrap(
spacing: 12,
runSpacing: 12,
children:
['10', '20', '30', '40', '50', '60', '70', '80'].map(
(e) {
final gold = int.tryParse(e);
return Obx(() {
final select = gold == (selectValue.value ?? 0);
return GestureDetector(
onTap: gold == null
? null
: () {
selectValue.value = gold;
focusN.unfocus();
textEidtCtr.clear();
checkEnable();
},
child: Container(
alignment: Alignment.center,
decoration: BoxDecoration(
color: (gold != null && select)
? AppColors.actionRed
: Color(0x0DFFFFFF),
borderRadius: BorderRadius.circular(10),
border: (gold != null && select)
? Border.all(color: AppColors.actionRed)
: Border.all(color: Color(0x1AFFFFFF))),
width: width_,
height: width_ * 1.1,
child: () {
if (gold != null)
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
'community_coin.webp'.communityPath,
width: 20,
height: 20,
),
12.sizeBoxH,
GradientText(
'$gold金币',
style: TextStyle(
fontSize: 12,
),
gradient: LinearGradient(
colors: select
? [
Color(0xffFFE8BE),
Color(0xffE6B764)
]
: [
Colors.white
.withValues(alpha: .55),
Colors.white
.withValues(alpha: .55),
],
),
)
],
);
}(),
),
);
});
},
).toList(),
);
}),
20.sizeBoxH,
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("自定义", style: TextStyle(color: Color(0x8CFFFFFF))),
Container(
margin: EdgeInsets.symmetric(horizontal: 5, vertical: 0),
width: 60,
decoration: BoxDecoration(
color: Color(0x33FFFFFF),
borderRadius: BorderRadius.circular(6)),
child: TextField(
controller: textEidtCtr,
focusNode: focusN,
textAlign: TextAlign.center,
onChanged: (value) {
selectValue.value = int.tryParse(value);
checkEnable();
},
keyboardType: TextInputType.number,
maxLength: 4,
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp('[0-9]'))
],
style: TextStyle(
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.w500),
decoration: InputDecoration(
border: InputBorder.none,
counterText: '',
hintText: '100',
isDense: true,
contentPadding: EdgeInsets.zero,
hintStyle: TextStyle(
color: Colors.white.withValues(alpha: .6),
fontSize: 12),
),
),
),
Text("金币", style: TextStyle(color: Color(0x8CFFFFFF))),
],
),
20.sizeBoxH,
Consumer<GlobalStore>(builder: (_, store, __) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (!widget.isPublish) ...[
Text(
'钱包余额:${store.wallet?.amount ?? 0}',
style: TextStyle(
color: Colors.white.withValues(alpha: .6),
fontSize: 12,
height: 14 / 12),
),
16.sizeBoxH,
],
Obx(() {
if (enable.value)
return Row(
children: [
Expanded(
child: GestureDetector(
onTap: () =>
Get.back(result: selectValue.value),
child: Container(
height: 54,
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(3),
color: AppColors.actionRed,
),
child: Text(
widget.isPublish ? '确定设置' : '立即打赏',
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.w500),
),
),
),
),
],
);
return GestureDetector(
onTap: () => Get.to(MineChargeCoinPage()),
child: Container(
height: 38,
margin: EdgeInsets.symmetric(horizontal: 20),
alignment: Alignment.center,
decoration: BoxDecoration(
color: AppColors.actionRed,
borderRadius: BorderRadius.circular(3)),
child: Text(
'余额不足 前往充值',
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w500),
),
),
);
}),
],
);
})
],
),
)
],
),
);
}
}
+56
View File
@@ -0,0 +1,56 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:hgdj/hj_model/video_model.dart';
import 'package:hgdj/hj_page/user_center_page/user_center_page.dart';
import 'package:hgdj/tools_base/widget/net_image_widget.dart';
class UserAvatar extends StatelessWidget {
final double size;
final bool showVip;
final Publisher? model;
final bool isCircle;
final double? bigVsize;
final bool showBorder;
final GestureTapCallback? onTap;
const UserAvatar({
super.key,
this.size = 52,
this.showVip = false,
this.model,
this.isCircle = true,
this.bigVsize,
this.showBorder = false,
this.onTap,
});
@override
Widget build(BuildContext context) {
return Container(
width: size,
height: size,
decoration: BoxDecoration(
border: showBorder
? Border.all(color: Color(0xffDB361F), width: 2)
: null,
borderRadius: BorderRadius.circular(100)),
child: Stack(
alignment: Alignment.center,
children: [
GestureDetector(
onTap: onTap ??
() {
Get.to(() => UserCenterPage(uid: model?.uid ?? 0),
preventDuplicates: false);
},
child: NetworkImageLoader(
imageUrl: model?.portrait ?? '',
width: size - (showBorder ? 4 : 0),
height: size - (showBorder ? 4 : 0),
borderRadius: isCircle ? size / 2 : 0,
),
),
],
),
);
}
}
@@ -0,0 +1,36 @@
import 'package:flutter/material.dart';
class UserNameView extends StatelessWidget {
final String? name;
final bool? isVip;
final bool? isOfficial;
final double fontSize;
final FontWeight fontWeight;
final Color? nameColor;
const UserNameView({
super.key,
this.name,
this.isVip,
this.isOfficial,
this.fontSize = 14,
this.fontWeight = FontWeight.w500,
this.nameColor,
});
@override
Widget build(BuildContext context) {
if (name?.isEmpty ?? true) return SizedBox.shrink();
return Text(
name ?? '',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: nameColor ?? Color(0xff9A9A9A),
fontSize: fontSize,
fontWeight: fontWeight,
),
);
}
}