初始化
This commit is contained in:
@@ -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),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
);
|
||||
})
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user