发现页面
This commit is contained in:
63
lib/views/moments/index/widgets/future_button.dart
Normal file
63
lib/views/moments/index/widgets/future_button.dart
Normal file
@@ -0,0 +1,63 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
typedef VoidFutureCallBack = Future<void> Function();
|
||||
|
||||
class FutureTextButton extends StatefulWidget {
|
||||
const FutureTextButton({
|
||||
Key? key,
|
||||
this.onLongPress,
|
||||
this.onHover,
|
||||
this.onFocusChange,
|
||||
this.style,
|
||||
this.focusNode,
|
||||
this.autofocus = false,
|
||||
this.clipBehavior = Clip.none,
|
||||
required this.child,
|
||||
required this.onPressed,
|
||||
}) : super(key: key);
|
||||
|
||||
final VoidFutureCallBack? onPressed;
|
||||
final VoidCallback? onLongPress;
|
||||
final ValueChanged<bool>? onHover;
|
||||
final ValueChanged<bool>? onFocusChange;
|
||||
final ButtonStyle? style;
|
||||
final Clip clipBehavior;
|
||||
final FocusNode? focusNode;
|
||||
final bool autofocus;
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
State<FutureTextButton> createState() => _FutureTextButtonState();
|
||||
}
|
||||
|
||||
class _FutureTextButtonState extends State<FutureTextButton> {
|
||||
bool _isBusy = false;
|
||||
|
||||
Future<void> onPressed() async {
|
||||
if (_isBusy) return;
|
||||
setState(() => _isBusy = true);
|
||||
try {
|
||||
await widget.onPressed?.call();
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
} finally {
|
||||
if (mounted) setState(() => _isBusy = false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextButton(
|
||||
key: widget.key,
|
||||
onPressed: _isBusy || widget.onPressed == null ? null : () => onPressed(),
|
||||
onLongPress: widget.onLongPress,
|
||||
onHover: widget.onHover,
|
||||
onFocusChange: widget.onFocusChange,
|
||||
style: widget.style,
|
||||
focusNode: widget.focusNode,
|
||||
autofocus: widget.autofocus,
|
||||
clipBehavior: widget.clipBehavior,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
}
|
||||
143
lib/views/moments/index/widgets/grid_media.dart
Normal file
143
lib/views/moments/index/widgets/grid_media.dart
Normal file
@@ -0,0 +1,143 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:chat/configs/app_colors.dart';
|
||||
import 'package:chat/views/moments/index/widgets/media_preview.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class GridMedia extends StatelessWidget {
|
||||
final List<String>? mediaList;
|
||||
|
||||
const GridMedia(this.mediaList, {Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (mediaList?.length == 1) {
|
||||
return ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: Get.width * 0.5,
|
||||
maxWidth: Get.width * 0.5,
|
||||
minHeight: 50,
|
||||
minWidth: 50,
|
||||
),
|
||||
child: mediaList!.first.split('?').first.isImageFileName
|
||||
? imageWidget(mediaList![0], 0)
|
||||
: videoWidget(mediaList![0], 0),
|
||||
);
|
||||
} else if (mediaList?.length == 2 || mediaList?.length == 4) {
|
||||
return GridView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
mainAxisSpacing: 8,
|
||||
crossAxisSpacing: 8,
|
||||
childAspectRatio: 1,
|
||||
),
|
||||
itemCount: mediaList?.length,
|
||||
itemBuilder: (context, index) {
|
||||
final source = mediaList![index];
|
||||
if (source.split('?').first.isImageFileName) {
|
||||
return imageWidget(source, index);
|
||||
}
|
||||
return videoWidget(source, index);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return GridView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 3,
|
||||
mainAxisSpacing: 8,
|
||||
crossAxisSpacing: 8,
|
||||
childAspectRatio: 1,
|
||||
),
|
||||
itemCount: mediaList?.length ?? 0,
|
||||
itemBuilder: (context, index) {
|
||||
final source = mediaList![index];
|
||||
if (source.split('?').first.isImageFileName) {
|
||||
return imageWidget(source, index);
|
||||
}
|
||||
return videoWidget(source, index);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget videoWidget(String sourse, int index) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
pushToPreview(index);
|
||||
},
|
||||
child: const ColoredBox(
|
||||
color: AppColors.black,
|
||||
child: Center(
|
||||
child: ClipOval(
|
||||
child: ColoredBox(
|
||||
color: AppColors.white,
|
||||
child: SizedBox.square(
|
||||
dimension: 30,
|
||||
child: Icon(Icons.play_arrow),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// ignore: todo
|
||||
// TODO: 缩略图太耗流量
|
||||
// child: FutureBuilder<Uint8List?>(
|
||||
// // future: getVideoThumbnail(sourse),
|
||||
// builder: (context, snapshot) {
|
||||
// if (snapshot.data == null) {
|
||||
// return Container(color: Colors.white);
|
||||
// } else {
|
||||
// return DecoratedBox(
|
||||
// decoration: BoxDecoration(
|
||||
// image: DecorationImage(
|
||||
// image: MemoryImage(snapshot.data!),
|
||||
// fit: BoxFit.cover,
|
||||
// ),
|
||||
// ),
|
||||
// child: const Center(
|
||||
// child: Icon(
|
||||
// Icons.play_arrow_rounded,
|
||||
// color: Colors.white,
|
||||
// size: 40,
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// },
|
||||
// ),
|
||||
);
|
||||
}
|
||||
|
||||
Widget imageWidget(String source, int index) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
pushToPreview(index);
|
||||
},
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: source,
|
||||
alignment: Alignment.center,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void pushToPreview(int index) {
|
||||
Get.dialog(
|
||||
MomentMediaPreview(
|
||||
mediaSourceList: mediaList!,
|
||||
initialPage: index,
|
||||
),
|
||||
useSafeArea: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
107
lib/views/moments/index/widgets/media_preview.dart
Normal file
107
lib/views/moments/index/widgets/media_preview.dart
Normal file
@@ -0,0 +1,107 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:chat/configs/app_colors.dart';
|
||||
import 'package:chewie/chewie.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:photo_view/photo_view.dart';
|
||||
import 'package:photo_view/photo_view_gallery.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
|
||||
class MomentMediaPreview extends StatelessWidget {
|
||||
final List<String> mediaSourceList;
|
||||
final int? initialPage;
|
||||
const MomentMediaPreview({
|
||||
Key? key,
|
||||
required this.mediaSourceList,
|
||||
this.initialPage,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () => Navigator.pop(context),
|
||||
child: Material(
|
||||
color: AppColors.black,
|
||||
child: SafeArea(
|
||||
child: Stack(children: [
|
||||
Positioned.fill(
|
||||
child: PhotoViewGallery.builder(
|
||||
pageController: PageController(initialPage: initialPage ?? 0),
|
||||
itemCount: mediaSourceList.length,
|
||||
builder: (context, index) {
|
||||
final source = mediaSourceList[index];
|
||||
if (source.split('?').first.isImageFileName) {
|
||||
return PhotoViewGalleryPageOptions(
|
||||
imageProvider: CachedNetworkImageProvider(source),
|
||||
minScale: PhotoViewComputedScale.contained,
|
||||
maxScale: PhotoViewComputedScale.covered * 2,
|
||||
);
|
||||
} else if (source.split('?').first.isVideoFileName) {
|
||||
return PhotoViewGalleryPageOptions.customChild(
|
||||
disableGestures: true,
|
||||
child: _VideoPreview(source: source),
|
||||
);
|
||||
} else {
|
||||
return PhotoViewGalleryPageOptions.customChild(
|
||||
child: const Center(
|
||||
child: Text(
|
||||
'格式不支持',
|
||||
style: TextStyle(color: AppColors.white),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
const SafeArea(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(4.0),
|
||||
child: BackButton(color: AppColors.white),
|
||||
),
|
||||
)
|
||||
]),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _VideoPreview extends StatefulWidget {
|
||||
final String source;
|
||||
const _VideoPreview({Key? key, required this.source}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<_VideoPreview> createState() => __VideoPreviewState();
|
||||
}
|
||||
|
||||
class __VideoPreviewState extends State<_VideoPreview> {
|
||||
late final videoCtrl = VideoPlayerController.network(widget.source);
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
videoCtrl.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> initVideo() async {
|
||||
await videoCtrl.initialize();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder(
|
||||
future: initVideo(),
|
||||
builder: (context, snapshot) {
|
||||
return Chewie(
|
||||
controller: ChewieController(
|
||||
showOptions: false,
|
||||
autoPlay: true,
|
||||
aspectRatio: videoCtrl.value.aspectRatio,
|
||||
videoPlayerController: videoCtrl,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
30
lib/views/moments/index/widgets/moment_avatar.dart
Normal file
30
lib/views/moments/index/widgets/moment_avatar.dart
Normal file
@@ -0,0 +1,30 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:chat/configs/app_colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MomentAvatar extends StatelessWidget {
|
||||
final String imageUrl;
|
||||
const MomentAvatar({Key? key, String? imageUrl})
|
||||
: imageUrl = imageUrl ?? '',
|
||||
super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: 40,
|
||||
height: 40,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
||||
color: AppColors.white,
|
||||
image: imageUrl.isNotEmpty
|
||||
? DecorationImage(
|
||||
image: CachedNetworkImageProvider(imageUrl),
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
184
lib/views/moments/index/widgets/moment_header.dart
Normal file
184
lib/views/moments/index/widgets/moment_header.dart
Normal file
@@ -0,0 +1,184 @@
|
||||
import 'package:chat/configs/app_colors.dart';
|
||||
import 'package:chat/routes/moments_routes.dart';
|
||||
import 'package:chat/services/tabbar_service.dart';
|
||||
import 'package:chat/widgets/custom_avatar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_easyrefresh/easy_refresh.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class MomentHeader extends StatelessWidget {
|
||||
final LinkHeaderNotifier linkNotifier;
|
||||
final VoidCallback? onTitleDoubleTap;
|
||||
const MomentHeader({
|
||||
Key? key,
|
||||
required this.linkNotifier,
|
||||
this.onTitleDoubleTap,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SliverAppBar(
|
||||
systemOverlayStyle: SystemUiOverlayStyle.light,
|
||||
pinned: true,
|
||||
expandedHeight: 260,
|
||||
backgroundColor: AppColors.primary,
|
||||
foregroundColor: AppColors.white,
|
||||
titleTextStyle: const TextStyle(color: AppColors.white),
|
||||
leading: CircleHeader(linkNotifier),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Get.toNamed(MomentsRoutes.publish);
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.camera_alt_rounded,
|
||||
),
|
||||
),
|
||||
],
|
||||
flexibleSpace: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final FlexibleSpaceBarSettings settings = context
|
||||
.dependOnInheritedWidgetOfExactType<FlexibleSpaceBarSettings>()!;
|
||||
return FlexibleSpaceBar(
|
||||
collapseMode: CollapseMode.pin,
|
||||
centerTitle: true,
|
||||
title: Visibility(
|
||||
visible: constraints.maxHeight <= settings.minExtent,
|
||||
child: GestureDetector(
|
||||
onDoubleTap: () {
|
||||
onTitleDoubleTap?.call();
|
||||
},
|
||||
child: Text(
|
||||
'发现',
|
||||
style: Theme.of(context)
|
||||
.appBarTheme
|
||||
.titleTextStyle
|
||||
?.copyWith(color: AppColors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
background: const _HeaderBackground(),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _HeaderBackground extends StatelessWidget {
|
||||
const _HeaderBackground({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ColoredBox(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
bottom: 32,
|
||||
child: GestureDetector(
|
||||
child: Image.asset(
|
||||
'assets/backgrounds/moment_3.jpg',
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
right: 16,
|
||||
bottom: 0,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 52,
|
||||
child: Obx(() {
|
||||
return const Text(
|
||||
"UserController.to.userInfo.value?.nickname ?? ''",
|
||||
style: TextStyle(
|
||||
color: AppColors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Obx(() {
|
||||
return GestureDetector(
|
||||
onTap: () => TabbarService.to.index = 4,
|
||||
child: CustomAvatar(
|
||||
'',
|
||||
size: 64,
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 圆形Header
|
||||
class CircleHeader extends StatefulWidget {
|
||||
final LinkHeaderNotifier linkNotifier;
|
||||
|
||||
const CircleHeader(this.linkNotifier, {Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
CircleHeaderState createState() {
|
||||
return CircleHeaderState();
|
||||
}
|
||||
}
|
||||
|
||||
class CircleHeaderState extends State<CircleHeader> {
|
||||
// 指示器值
|
||||
double? _indicatorValue = 0.0;
|
||||
|
||||
RefreshMode get _refreshState => widget.linkNotifier.refreshState;
|
||||
double get _pulledExtent => widget.linkNotifier.pulledExtent;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
widget.linkNotifier.addListener(onLinkNotify);
|
||||
}
|
||||
|
||||
void onLinkNotify() {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
if (_refreshState == RefreshMode.armed ||
|
||||
_refreshState == RefreshMode.refresh) {
|
||||
_indicatorValue = null;
|
||||
} else if (_refreshState == RefreshMode.refreshed ||
|
||||
_refreshState == RefreshMode.done) {
|
||||
_indicatorValue = 1.0;
|
||||
} else {
|
||||
if (_refreshState == RefreshMode.inactive) {
|
||||
_indicatorValue = 0.0;
|
||||
} else {
|
||||
double indicatorValue = _pulledExtent / 70.0 * 0.8;
|
||||
_indicatorValue = indicatorValue < 0.8 ? indicatorValue : 0.8;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: SizedBox.square(
|
||||
dimension: 24.0,
|
||||
child: CircularProgressIndicator(
|
||||
value: _indicatorValue,
|
||||
valueColor: const AlwaysStoppedAnimation(AppColors.white),
|
||||
strokeWidth: 2.4,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
204
lib/views/moments/index/widgets/moment_list_item.dart
Normal file
204
lib/views/moments/index/widgets/moment_list_item.dart
Normal file
@@ -0,0 +1,204 @@
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:chat/configs/app_colors.dart';
|
||||
import 'package:chat/configs/app_size.dart';
|
||||
import 'package:chat/controllers/moment_controller.dart';
|
||||
import 'package:chat/models/moment/moment_model.dart';
|
||||
import 'package:chat/views/moments/index/widgets/future_button.dart';
|
||||
import 'package:chat/views/moments/index/widgets/grid_media.dart';
|
||||
import 'package:chat/views/moments/index/widgets/moment_avatar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class MomentListItem extends StatelessWidget {
|
||||
final MomentItemModel item;
|
||||
MomentListItem({Key? key, required this.item}) : super(key: key);
|
||||
|
||||
final actionStyle = ButtonStyle(
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
padding: MaterialStateProperty.all(
|
||||
const EdgeInsets.symmetric(vertical: 4, horizontal: 8)),
|
||||
minimumSize: MaterialStateProperty.all(Size.zero),
|
||||
foregroundColor: MaterialStateProperty.all(AppColors.deep.withOpacity(0.6)),
|
||||
textStyle: MaterialStateProperty.all(
|
||||
const TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Future<Uint8List?> getVideoThumbnail(String source) async {
|
||||
// return await VideoThumbnail.thumbnailData(
|
||||
// video: source,
|
||||
// imageFormat: ImageFormat.JPEG,
|
||||
// maxWidth: 128,
|
||||
// quality: 25,
|
||||
// );
|
||||
// }
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: AppSize.horizontalLargePadding,
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
MomentAvatar(imageUrl: item.user?.avatar),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
item.user?.nickname ?? '',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: AppSize.fontSize + 1,
|
||||
color: AppColors.darkBlue,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
item.description ?? '',
|
||||
style: const TextStyle(
|
||||
fontSize: AppSize.fontSize,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Visibility(
|
||||
visible: item.pictures?.isNotEmpty ?? false,
|
||||
child: GridMedia(item.pictures),
|
||||
),
|
||||
timeAndAction()
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Widget gridMedia() {
|
||||
// if (item.pictures?.length == 1) {
|
||||
// return ConstrainedBox(
|
||||
// constraints: BoxConstraints(
|
||||
// maxHeight: Get.width * 0.5,
|
||||
// maxWidth: Get.width * 0.5,
|
||||
// minHeight: 50,
|
||||
// minWidth: 50,
|
||||
// ),
|
||||
// child: imageWidget(item.pictures![0], 0),
|
||||
// );
|
||||
// } else {
|
||||
// return GridView.builder(
|
||||
// padding: EdgeInsets.zero,
|
||||
// shrinkWrap: true,
|
||||
// physics: const NeverScrollableScrollPhysics(),
|
||||
// gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
// crossAxisCount: 3,
|
||||
// mainAxisSpacing: 8,
|
||||
// crossAxisSpacing: 8,
|
||||
// childAspectRatio: 1,
|
||||
// ),
|
||||
// itemCount: item.pictures?.length ?? 0,
|
||||
// itemBuilder: (context, index) {
|
||||
// final source = item.pictures![index];
|
||||
// if (source.isImageFileName) {
|
||||
// return imageWidget(source, index);
|
||||
// }
|
||||
// return videoWidget(source, index);
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
Widget timeAndAction() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
item.time ?? '',
|
||||
style: const TextStyle(
|
||||
fontSize: AppSize.fontSize - 2,
|
||||
color: AppColors.unactive,
|
||||
),
|
||||
),
|
||||
item.isMe == true
|
||||
? InkWell(
|
||||
onTap: () async {
|
||||
OkCancelResult result = await showOkCancelAlertDialog(
|
||||
style: AdaptiveStyle.iOS,
|
||||
context: Get.context!,
|
||||
title: '系统提示',
|
||||
message: '删除后无法撤回',
|
||||
okLabel: '确定',
|
||||
cancelLabel: '取消',
|
||||
defaultType: OkCancelAlertDefaultType.cancel,
|
||||
);
|
||||
if (result == OkCancelResult.ok) {
|
||||
MomentController.to.delMoment(item);
|
||||
}
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Text(
|
||||
'删除',
|
||||
style: TextStyle(
|
||||
fontSize: AppSize.fontSize - 2,
|
||||
color: AppColors.darkBlue,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox(),
|
||||
],
|
||||
),
|
||||
),
|
||||
FutureTextButton(
|
||||
style: actionStyle,
|
||||
onPressed: () => MomentController.to.likeMoment(item),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Visibility(
|
||||
visible: item.isLike ?? false,
|
||||
child: const Icon(Icons.favorite, size: 14),
|
||||
replacement: const Icon(Icons.favorite_border, size: 14),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 2.0),
|
||||
child: Text('${item.likerCount ?? ''}'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
TextButton(
|
||||
style: actionStyle,
|
||||
onPressed: () => MomentController.to.showReplyBar(item.dynamicId!),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.message, size: 14),
|
||||
const SizedBox(width: 4),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 2.0),
|
||||
child: Text('${item.comments?.length ?? ''}'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
200
lib/views/moments/index/widgets/moment_list_reply.dart
Normal file
200
lib/views/moments/index/widgets/moment_list_reply.dart
Normal file
@@ -0,0 +1,200 @@
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:chat/configs/app_colors.dart';
|
||||
import 'package:chat/configs/app_size.dart';
|
||||
import 'package:chat/controllers/moment_controller.dart';
|
||||
import 'package:chat/models/moment/moment_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class MomentListItemReplay extends StatelessWidget {
|
||||
final int? maxDisplayCount;
|
||||
final int? maxLine;
|
||||
final int index;
|
||||
final MomentItemModel item;
|
||||
final void Function(Comment value)? reply;
|
||||
const MomentListItemReplay({
|
||||
Key? key,
|
||||
required this.index,
|
||||
this.maxDisplayCount,
|
||||
this.reply,
|
||||
this.maxLine,
|
||||
required this.item,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final listLength = item.comments?.length ?? 0;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: AppSize.horizontalLargePadding),
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.unactive.withOpacity(0.1),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ListView.separated(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: AppSize.horizontalPadding,
|
||||
),
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
itemCount:
|
||||
listLength > 3 ? maxDisplayCount ?? listLength : listLength,
|
||||
itemBuilder: (context, index) {
|
||||
final comment = item.comments![index];
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
if (comment.isMe) return;
|
||||
reply?.call(comment);
|
||||
},
|
||||
onLongPress: () async {
|
||||
if (comment.isMe) {
|
||||
OkCancelResult result = await showOkCancelAlertDialog(
|
||||
style: AdaptiveStyle.iOS,
|
||||
context: Get.context!,
|
||||
title: '系统提示',
|
||||
message: '删除后无法恢复',
|
||||
okLabel: '确定',
|
||||
cancelLabel: '取消',
|
||||
defaultType: OkCancelAlertDefaultType.cancel,
|
||||
);
|
||||
if (result == OkCancelResult.ok) {
|
||||
MomentController.to
|
||||
.delReply(index, item.dynamicId!, comment);
|
||||
}
|
||||
} else {
|
||||
reply?.call(comment);
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: AppSize.verticalPadding,
|
||||
),
|
||||
child: replayItem(comment),
|
||||
),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (context, index) => const Divider(height: 0.4),
|
||||
),
|
||||
if (maxDisplayCount != null && listLength > 3)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(
|
||||
AppSize.verticalPadding,
|
||||
),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
MomentController.to.pushToDetail(index);
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
'查看全部$listLength条回复',
|
||||
style: const TextStyle(
|
||||
color: AppColors.tTextColor999,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 2.0,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.keyboard_arrow_down,
|
||||
color: AppColors.tTextColor999,
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget replayItem(Comment comment) {
|
||||
final name = comment.user?.nickname ?? '';
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// MomentAvatar(imageUrl: comment.user?.avatar),
|
||||
// const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (comment.parent == null)
|
||||
Text.rich(TextSpan(children: [
|
||||
TextSpan(
|
||||
text: '$name:',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 13,
|
||||
color: AppColors.darkBlue,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: comment.content ?? '',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.normal,
|
||||
color: AppColors.tTextColor333,
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
]))
|
||||
else
|
||||
Text.rich(
|
||||
TextSpan(children: [
|
||||
TextSpan(
|
||||
text: name,
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
const TextSpan(
|
||||
text: ' 回复 ',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.normal,
|
||||
color: AppColors.tTextColor333,
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: '${comment.parent?.nickname}: ',
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: comment.content ?? '',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.normal,
|
||||
color: AppColors.tTextColor333,
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
]),
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
// fontSize: AppSize.titleFontSize,
|
||||
color: AppColors.darkBlue,
|
||||
),
|
||||
),
|
||||
// Text(
|
||||
// comment.content ?? '',
|
||||
// maxLines: maxLine ?? 99,
|
||||
// overflow: TextOverflow.ellipsis,
|
||||
// )
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
101
lib/views/moments/index/widgets/quick_reply_bar.dart
Normal file
101
lib/views/moments/index/widgets/quick_reply_bar.dart
Normal file
@@ -0,0 +1,101 @@
|
||||
import 'package:chat/configs/app_colors.dart';
|
||||
import 'package:chat/controllers/moment_controller.dart';
|
||||
import 'package:chat/models/moment/moment_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class QuickReplyBar extends StatefulWidget {
|
||||
final bool? autofocus;
|
||||
final int dynamicId;
|
||||
final Comment? comment;
|
||||
const QuickReplyBar({
|
||||
Key? key,
|
||||
this.autofocus,
|
||||
required this.dynamicId,
|
||||
this.comment,
|
||||
}) : super(key: key);
|
||||
|
||||
static const _border = OutlineInputBorder(
|
||||
borderSide: BorderSide.none,
|
||||
borderRadius: BorderRadius.all(Radius.circular(6)),
|
||||
);
|
||||
|
||||
@override
|
||||
State<QuickReplyBar> createState() => _QuickReplyBarState();
|
||||
}
|
||||
|
||||
class _QuickReplyBarState extends State<QuickReplyBar> {
|
||||
final content = ''.obs;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ctrl = MomentController.to;
|
||||
return ColoredBox(
|
||||
color: AppColors.page,
|
||||
child: SafeArea(
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: SizedBox(
|
||||
height: 36,
|
||||
child: TextField(
|
||||
onChanged: (value) => content.value = value,
|
||||
autofocus: widget.autofocus ?? true,
|
||||
decoration: InputDecoration(
|
||||
hintStyle: const TextStyle(
|
||||
fontSize: 13,
|
||||
),
|
||||
border: QuickReplyBar._border,
|
||||
focusedBorder: QuickReplyBar._border,
|
||||
disabledBorder: QuickReplyBar._border,
|
||||
hintText: widget.comment?.user?.nickname.isNotEmpty ?? false
|
||||
? '回复:${widget.comment?.user?.nickname}'
|
||||
: '评论',
|
||||
filled: true,
|
||||
fillColor: AppColors.white,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
constraints: const BoxConstraints(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
// IconButton(
|
||||
// onPressed: () {
|
||||
// print('emoji picker');
|
||||
// },
|
||||
// icon: const Icon(Icons.emoji_emotions_outlined),
|
||||
// color: Colors.grey,
|
||||
// ),
|
||||
Obx(() {
|
||||
return ElevatedButton(
|
||||
style: ButtonStyle(
|
||||
elevation: MaterialStateProperty.all(0),
|
||||
backgroundColor: MaterialStateProperty.resolveWith(
|
||||
(states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return AppColors.tTextColor999.withAlpha(128);
|
||||
} else {
|
||||
return AppColors.primary;
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
onPressed: content.isEmpty
|
||||
? null
|
||||
: () => ctrl.sendReply(
|
||||
widget.dynamicId,
|
||||
content.value,
|
||||
widget.comment,
|
||||
),
|
||||
child: const Text('发送'),
|
||||
);
|
||||
}),
|
||||
const SizedBox(width: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user