15 changed files with 1144 additions and 20 deletions
Unified View
Diff Options
-
BINassets/images/comment_icon.png
-
BINassets/images/img.png
-
BINassets/images/like_icon.png
-
BINassets/images/publish.png
-
21lib/controller/home/home_controller.dart
-
107lib/controller/home/send_timeline_controller.dart
-
7lib/generated/assets.dart
-
156lib/pages/home/all_timeline.dart
-
16lib/pages/home/home_page.dart
-
92lib/pages/home/recommend_window.dart
-
263lib/pages/home/send_timeline.dart
-
212lib/pages/home/timeline_info.dart
-
119lib/pages/home/timeline_item.dart
-
111lib/pages/home/timeline_window.dart
-
60lib/pages/main/main_page.dart
@ -0,0 +1,107 @@ |
|||||
|
import 'package:dating_touchme_app/config/emoji_config.dart'; |
||||
|
import 'package:flutter/material.dart'; |
||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart'; |
||||
|
import 'package:get/get.dart'; |
||||
|
|
||||
|
class SendTimelineController extends GetxController { |
||||
|
final title = "".obs; |
||||
|
final message = ''.obs; |
||||
|
final TextEditingController messageController = TextEditingController(); |
||||
|
|
||||
|
final focusNode = FocusNode().obs; |
||||
|
|
||||
|
final isEmojiVisible = false.obs; |
||||
|
|
||||
|
@override |
||||
|
void onInit() { |
||||
|
super.onInit(); |
||||
|
focusNode.value.addListener(() { |
||||
|
if (focusNode.value.hasFocus) { |
||||
|
// 输入框获得焦点(键盘弹起),关闭所有控制面板 |
||||
|
isEmojiVisible.value = false; |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
void onClose() { |
||||
|
super.onClose(); |
||||
|
focusNode.value.dispose(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
void toggleEmojiPanel() { |
||||
|
isEmojiVisible.value = !isEmojiVisible.value; |
||||
|
FocusManager.instance.primaryFocus?.unfocus(); |
||||
|
} |
||||
|
|
||||
|
void handleEmojiSelected(EmojiItem emoji) { |
||||
|
// 将表情添加到输入框 |
||||
|
final currentText = messageController.text; |
||||
|
final emojiText = '[emoji:${emoji.id}]'; |
||||
|
messageController.text = currentText + emojiText; |
||||
|
// 将光标移到末尾 |
||||
|
messageController.selection = TextSelection.fromPosition( |
||||
|
TextPosition(offset: messageController.text.length), |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/// 构建输入框内容(文本+表情) |
||||
|
List<Widget> buildInputContentWidgets() { |
||||
|
final List<Widget> widgets = []; |
||||
|
final text = messageController.value.text; |
||||
|
final RegExp emojiRegex = RegExp(r'\[emoji:(\d+)\]'); |
||||
|
|
||||
|
int lastMatchEnd = 0; |
||||
|
final matches = emojiRegex.allMatches(text); |
||||
|
|
||||
|
for (final match in matches) { |
||||
|
// 添加表情之前的文本 |
||||
|
if (match.start > lastMatchEnd) { |
||||
|
final textPart = text.substring(lastMatchEnd, match.start); |
||||
|
widgets.add( |
||||
|
Text( |
||||
|
textPart, |
||||
|
style: TextStyle(fontSize: 14.sp, color: Colors.black), |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
// 添加表情图片 |
||||
|
final emojiId = match.group(1); |
||||
|
if (emojiId != null) { |
||||
|
final emoji = EmojiConfig.getEmojiById(emojiId); |
||||
|
if (emoji != null) { |
||||
|
widgets.add( |
||||
|
Padding( |
||||
|
padding: EdgeInsets.symmetric(horizontal: 2.w), |
||||
|
child: Image.asset( |
||||
|
emoji.path, |
||||
|
width: 24.w, |
||||
|
height: 24.w, |
||||
|
fit: BoxFit.contain, |
||||
|
), |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
lastMatchEnd = match.end; |
||||
|
} |
||||
|
|
||||
|
// 添加剩余的文本 |
||||
|
if (lastMatchEnd < text.length) { |
||||
|
final textPart = text.substring(lastMatchEnd); |
||||
|
widgets.add( |
||||
|
Text( |
||||
|
textPart, |
||||
|
style: TextStyle(fontSize: 14.sp, color: Colors.black), |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
return widgets; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,156 @@ |
|||||
|
import 'package:dating_touchme_app/pages/home/timeline_item.dart'; |
||||
|
import 'package:easy_refresh/easy_refresh.dart'; |
||||
|
import 'package:flutter/material.dart'; |
||||
|
import 'package:get/get.dart'; |
||||
|
import 'package:dating_touchme_app/controller/home/home_controller.dart'; |
||||
|
import 'package:dating_touchme_app/pages/home/content_card.dart'; |
||||
|
|
||||
|
/// 推荐列表 Tab |
||||
|
class AllTimeline extends StatefulWidget { |
||||
|
const AllTimeline({super.key}); |
||||
|
|
||||
|
@override |
||||
|
State<AllTimeline> createState() => _AllTimelineState(); |
||||
|
} |
||||
|
|
||||
|
class _AllTimelineState extends State<AllTimeline> |
||||
|
with AutomaticKeepAliveClientMixin { |
||||
|
final HomeController controller = Get.find<HomeController>(); |
||||
|
late final EasyRefreshController _refreshController; |
||||
|
|
||||
|
@override |
||||
|
void initState() { |
||||
|
super.initState(); |
||||
|
_refreshController = EasyRefreshController(controlFinishRefresh: true, controlFinishLoad: true); |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
void dispose() { |
||||
|
_refreshController.dispose(); |
||||
|
super.dispose(); |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
Widget build(BuildContext context) { |
||||
|
super.build(context); |
||||
|
// 获取底部安全区域高度和 tabbar 高度(约64) |
||||
|
final bottomPadding = MediaQuery.of(context).padding.bottom; |
||||
|
final tabBarHeight = 64.0; |
||||
|
final totalBottomPadding = bottomPadding + tabBarHeight; |
||||
|
return Obx(() { |
||||
|
if (controller.recommendIsLoading.value && controller.recommendFeed.isEmpty) { |
||||
|
return Center( |
||||
|
child: Column( |
||||
|
mainAxisAlignment: MainAxisAlignment.center, |
||||
|
children: [ |
||||
|
CircularProgressIndicator(), |
||||
|
SizedBox(height: 16), |
||||
|
Text('加载数据中...'), |
||||
|
], |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
return EasyRefresh( |
||||
|
controller: _refreshController, |
||||
|
header: const ClassicHeader( |
||||
|
dragText: '下拉刷新', |
||||
|
armedText: '释放刷新', |
||||
|
readyText: '刷新中...', |
||||
|
processingText: '刷新中...', |
||||
|
processedText: '刷新完成', |
||||
|
failedText: '刷新失败', |
||||
|
noMoreText: '没有更多数据', |
||||
|
showMessage: false |
||||
|
), |
||||
|
footer: ClassicFooter( |
||||
|
dragText: '上拉加载', |
||||
|
armedText: '释放加载', |
||||
|
readyText: '加载中...', |
||||
|
processingText: '加载中...', |
||||
|
processedText: '加载完成', |
||||
|
failedText: '加载失败', |
||||
|
noMoreText: '没有更多数据', |
||||
|
showMessage: false |
||||
|
), |
||||
|
// 下拉刷新 |
||||
|
onRefresh: () async { |
||||
|
print('推荐列表下拉刷新被触发'); |
||||
|
try { |
||||
|
await controller.refreshRecommendData(); |
||||
|
print( '推荐列表刷新完成, hasMore: $controller.recommendHasMore.value'); |
||||
|
_refreshController.finishRefresh(); |
||||
|
_refreshController.resetFooter(); |
||||
|
} catch (e) { |
||||
|
print('推荐列表刷新失败: $e'); |
||||
|
_refreshController.finishRefresh(IndicatorResult.fail); |
||||
|
} |
||||
|
}, |
||||
|
// 上拉加载更多 |
||||
|
onLoad: () async { |
||||
|
print('推荐列表上拉加载被触发, hasMore: $controller.recommendHasMore.value'); |
||||
|
try { |
||||
|
await controller.loadRecommendMoreData(); |
||||
|
// 完成加载,根据是否有更多数据决定 |
||||
|
if (controller.recommendHasMore.value) { |
||||
|
_refreshController.finishLoad(IndicatorResult.success); |
||||
|
print('推荐列表加载更多成功'); |
||||
|
} else { |
||||
|
_refreshController.finishLoad(IndicatorResult.noMore); |
||||
|
print('推荐列表没有更多数据了'); |
||||
|
} |
||||
|
} catch (e) { |
||||
|
print('推荐列表加载更多失败: $e'); |
||||
|
_refreshController.finishLoad(IndicatorResult.fail); |
||||
|
} |
||||
|
}, |
||||
|
child: ListView.separated( |
||||
|
// 关键:始终允许滚动,即使内容不足 |
||||
|
// 移除顶部 padding,让刷新指示器可以正确显示在 AppBar 下方 |
||||
|
padding: EdgeInsets.only(left: 12, right: 12), |
||||
|
itemBuilder: (context, index) { |
||||
|
// 空数据状态 |
||||
|
if (controller.recommendFeed.isEmpty && index == 0) { |
||||
|
// 使用足够的高度确保可以滚动 |
||||
|
if (controller.recommendIsLoading.value) { |
||||
|
return Center( |
||||
|
child: Column( |
||||
|
mainAxisAlignment: MainAxisAlignment.center, |
||||
|
children: [ |
||||
|
CircularProgressIndicator(), |
||||
|
SizedBox(height: 16), |
||||
|
Text('加载数据中...'), |
||||
|
], |
||||
|
), |
||||
|
); |
||||
|
} else { |
||||
|
return Center( |
||||
|
child: Column( |
||||
|
mainAxisAlignment: MainAxisAlignment.center, |
||||
|
children: [ |
||||
|
Text('暂无数据'), |
||||
|
], |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
// 数据项 |
||||
|
// final item = controller.recommendFeed[index]; |
||||
|
return TimelineItem(); |
||||
|
}, |
||||
|
separatorBuilder: (context, index) { |
||||
|
// 空状态或加载状态时不显示分隔符 |
||||
|
if (controller.recommendFeed.isEmpty) { |
||||
|
return const SizedBox.shrink(); |
||||
|
} |
||||
|
return const SizedBox(height: 12); |
||||
|
}, |
||||
|
// 至少显示一个 item(用于显示加载或空状态) |
||||
|
itemCount: 10, |
||||
|
) |
||||
|
); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
bool get wantKeepAlive => true; |
||||
|
} |
||||
@ -0,0 +1,92 @@ |
|||||
|
import 'package:dating_touchme_app/controller/home/home_controller.dart'; |
||||
|
import 'package:dating_touchme_app/pages/home/nearby_tab.dart'; |
||||
|
import 'package:dating_touchme_app/pages/home/recommend_tab.dart'; |
||||
|
import 'package:flutter/material.dart'; |
||||
|
import 'package:get/get.dart'; |
||||
|
import 'package:tdesign_flutter/tdesign_flutter.dart'; |
||||
|
|
||||
|
class RecommendWindow extends StatefulWidget { |
||||
|
const RecommendWindow({super.key}); |
||||
|
|
||||
|
@override |
||||
|
State<RecommendWindow> createState() => _RecommendWindowState(); |
||||
|
} |
||||
|
|
||||
|
class _RecommendWindowState extends State<RecommendWindow> with AutomaticKeepAliveClientMixin, SingleTickerProviderStateMixin { |
||||
|
|
||||
|
|
||||
|
late TabController tabController; |
||||
|
final HomeController controller = Get.find<HomeController>(); |
||||
|
|
||||
|
@override |
||||
|
void initState() { |
||||
|
super.initState(); |
||||
|
|
||||
|
tabController = TabController(length: 2, vsync: this); |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
Widget build(BuildContext context) { |
||||
|
super.build(context); |
||||
|
return Column( |
||||
|
children: [ |
||||
|
TDTabBar( |
||||
|
tabs: [ |
||||
|
TDTab( |
||||
|
child: Padding( |
||||
|
padding: EdgeInsets.only(right: 16, left: 16), |
||||
|
child: Text('推荐'), |
||||
|
), |
||||
|
), |
||||
|
TDTab( |
||||
|
child: Padding( |
||||
|
padding: EdgeInsets.only(right: 16, left: 16), |
||||
|
child: Text('同城'), |
||||
|
), |
||||
|
), |
||||
|
], |
||||
|
backgroundColor: Colors.transparent, |
||||
|
labelPadding: const EdgeInsets.only(right: 4, top: 10, bottom: 10, left: 4), |
||||
|
selectedBgColor: const Color.fromRGBO(108, 105, 244, 1), |
||||
|
unSelectedBgColor: Colors.transparent, |
||||
|
labelColor: Colors.white, |
||||
|
dividerHeight: 0, |
||||
|
tabAlignment: TabAlignment.start, |
||||
|
outlineType: TDTabBarOutlineType.capsule, |
||||
|
controller: tabController, |
||||
|
showIndicator: false, |
||||
|
isScrollable: true, |
||||
|
onTap: (index) async { |
||||
|
print('相亲页面 Tab: $index'); |
||||
|
if (controller.selectedTabIndex.value != index) { |
||||
|
controller.setSelectedTabIndex(index); |
||||
|
// 确保状态更新后刷新UI |
||||
|
controller.update(); |
||||
|
} |
||||
|
}, |
||||
|
), |
||||
|
|
||||
|
Expanded( |
||||
|
child: Obx(() { |
||||
|
// 使用 IndexedStack 保持两个列表的状态,根据当前选中的标签显示对应的列表 |
||||
|
return IndexedStack( |
||||
|
index: controller.selectedTabIndex.value, |
||||
|
children: const [ |
||||
|
// 推荐列表 |
||||
|
RecommendTab(), |
||||
|
// 同城列表 |
||||
|
NearbyTab(), |
||||
|
], |
||||
|
); |
||||
|
}), |
||||
|
), |
||||
|
], |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
@override |
||||
|
bool get wantKeepAlive => true; |
||||
|
} |
||||
|
|
||||
|
|
||||
@ -0,0 +1,263 @@ |
|||||
|
import 'package:dating_touchme_app/components/page_appbar.dart'; |
||||
|
import 'package:dating_touchme_app/config/emoji_config.dart'; |
||||
|
import 'package:dating_touchme_app/controller/home/send_timeline_controller.dart'; |
||||
|
import 'package:dating_touchme_app/extension/ex_widget.dart'; |
||||
|
import 'package:dating_touchme_app/generated/assets.dart'; |
||||
|
import 'package:dating_touchme_app/widget/emoji_panel.dart'; |
||||
|
import 'package:flutter/material.dart'; |
||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart'; |
||||
|
import 'package:get/get.dart'; |
||||
|
|
||||
|
class SendTimeline extends StatefulWidget { |
||||
|
const SendTimeline({super.key}); |
||||
|
|
||||
|
@override |
||||
|
State<SendTimeline> createState() => _SendTimelineState(); |
||||
|
} |
||||
|
|
||||
|
class _SendTimelineState extends State<SendTimeline> { |
||||
|
|
||||
|
String title = ""; |
||||
|
String message = ''; |
||||
|
final TextEditingController messageController = TextEditingController(); |
||||
|
|
||||
|
final FocusNode focusNode = FocusNode(); |
||||
|
|
||||
|
bool isEmojiVisible = false; |
||||
|
|
||||
|
@override |
||||
|
void initState() { |
||||
|
super.initState(); |
||||
|
focusNode.addListener(() { |
||||
|
if (focusNode.hasFocus) { |
||||
|
// 输入框获得焦点(键盘弹起),关闭所有控制面板 |
||||
|
isEmojiVisible = false; |
||||
|
setState(() { |
||||
|
|
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
void dispose() { |
||||
|
super.dispose(); |
||||
|
focusNode.dispose(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
void toggleEmojiPanel() { |
||||
|
isEmojiVisible = !isEmojiVisible; |
||||
|
FocusManager.instance.primaryFocus?.unfocus(); |
||||
|
setState(() { |
||||
|
|
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
void handleEmojiSelected(EmojiItem emoji) { |
||||
|
// 将表情添加到输入框 |
||||
|
final currentText = messageController.text; |
||||
|
final emojiText = '[emoji:${emoji.id}]'; |
||||
|
messageController.text = currentText + emojiText; |
||||
|
// 将光标移到末尾 |
||||
|
messageController.selection = TextSelection.fromPosition( |
||||
|
TextPosition(offset: messageController.text.length), |
||||
|
); |
||||
|
setState(() {}); // 刷新显示 |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/// 构建输入框内容(文本+表情) |
||||
|
List<Widget> buildInputContentWidgets() { |
||||
|
final List<Widget> widgets = []; |
||||
|
final text = messageController.value.text; |
||||
|
final RegExp emojiRegex = RegExp(r'\[emoji:(\d+)\]'); |
||||
|
|
||||
|
int lastMatchEnd = 0; |
||||
|
final matches = emojiRegex.allMatches(text); |
||||
|
|
||||
|
for (final match in matches) { |
||||
|
// 添加表情之前的文本 |
||||
|
if (match.start > lastMatchEnd) { |
||||
|
final textPart = text.substring(lastMatchEnd, match.start); |
||||
|
widgets.add( |
||||
|
Text( |
||||
|
textPart, |
||||
|
style: TextStyle(fontSize: 14.sp, color: Colors.black), |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
// 添加表情图片 |
||||
|
final emojiId = match.group(1); |
||||
|
if (emojiId != null) { |
||||
|
final emoji = EmojiConfig.getEmojiById(emojiId); |
||||
|
if (emoji != null) { |
||||
|
widgets.add( |
||||
|
Padding( |
||||
|
padding: EdgeInsets.symmetric(horizontal: 2.w), |
||||
|
child: Image.asset( |
||||
|
emoji.path, |
||||
|
width: 24.w, |
||||
|
height: 24.w, |
||||
|
fit: BoxFit.contain, |
||||
|
), |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
lastMatchEnd = match.end; |
||||
|
} |
||||
|
|
||||
|
// 添加剩余的文本 |
||||
|
if (lastMatchEnd < text.length) { |
||||
|
final textPart = text.substring(lastMatchEnd); |
||||
|
widgets.add( |
||||
|
Text( |
||||
|
textPart, |
||||
|
style: TextStyle(fontSize: 14.sp, color: Colors.black), |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
return widgets; |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
Widget build(BuildContext context) { |
||||
|
return Scaffold( |
||||
|
appBar: PageAppbar(title: "", right: Container( |
||||
|
width: 53.w, |
||||
|
height: 26.w, |
||||
|
margin: EdgeInsets.only(right: 17.w), |
||||
|
decoration: BoxDecoration( |
||||
|
borderRadius: BorderRadius.all(Radius.circular(8.w)), |
||||
|
color: const Color.fromRGBO(117, 98, 249, 1) |
||||
|
), |
||||
|
child: Center( |
||||
|
child: Text( |
||||
|
"发送", |
||||
|
style: TextStyle( |
||||
|
fontSize: 13.w, |
||||
|
color: Colors.white |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
),), |
||||
|
body: Container( |
||||
|
padding: EdgeInsets.symmetric(horizontal: 17.w, vertical: 10.w), |
||||
|
child: Column( |
||||
|
crossAxisAlignment: CrossAxisAlignment.start, |
||||
|
children: [ |
||||
|
Expanded( |
||||
|
child: Stack( |
||||
|
children: [ |
||||
|
TextField( |
||||
|
controller: messageController, |
||||
|
|
||||
|
focusNode: focusNode, |
||||
|
minLines: 1, |
||||
|
maxLines: null, // 关键 |
||||
|
style: TextStyle( |
||||
|
fontSize: 14.sp, |
||||
|
color: messageController.text.contains('[emoji:') |
||||
|
? Colors.transparent |
||||
|
: Colors.black, |
||||
|
), |
||||
|
decoration: InputDecoration( |
||||
|
contentPadding: EdgeInsets.symmetric( |
||||
|
vertical: 0, |
||||
|
horizontal: 0 |
||||
|
), |
||||
|
hintText: "分享你的日常,让缘分更早来临~", |
||||
|
hintStyle: TextStyle( |
||||
|
fontSize: 14.sp, |
||||
|
color: Colors.grey, |
||||
|
), |
||||
|
border: const OutlineInputBorder( |
||||
|
borderSide: BorderSide.none, // 这将移除边框 // 可选:设置圆角 |
||||
|
), |
||||
|
// 如果你希望聚焦时和未聚焦时都没有边框,也可以设置 focusedBorder 和 enabledBorder |
||||
|
focusedBorder: const OutlineInputBorder( |
||||
|
borderSide: BorderSide.none, |
||||
|
borderRadius: BorderRadius.all(Radius.circular(8.0)), |
||||
|
), |
||||
|
enabledBorder: const OutlineInputBorder( |
||||
|
borderSide: BorderSide.none, |
||||
|
borderRadius: BorderRadius.all(Radius.circular(8.0)), |
||||
|
), |
||||
|
), |
||||
|
onChanged: (value){ |
||||
|
setState(() { |
||||
|
|
||||
|
}); |
||||
|
}, |
||||
|
), |
||||
|
if (messageController.text.contains('[emoji:')) |
||||
|
Positioned.fill( |
||||
|
child: IgnorePointer( |
||||
|
child: SingleChildScrollView( |
||||
|
child: Wrap( |
||||
|
children: buildInputContentWidgets(), |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
), |
||||
|
Container( |
||||
|
width: 98.w, |
||||
|
height: 26.w, |
||||
|
margin: EdgeInsets.symmetric(vertical: 16.w), |
||||
|
decoration: BoxDecoration( |
||||
|
borderRadius: BorderRadius.all(Radius.circular(26.w)), |
||||
|
color: const Color.fromRGBO(245, 245, 245, 1) |
||||
|
), |
||||
|
child: Row( |
||||
|
mainAxisAlignment: MainAxisAlignment.center, |
||||
|
children: [ |
||||
|
Icon( |
||||
|
Icons.location_on, |
||||
|
size: 16.w, |
||||
|
color: const Color.fromRGBO(51, 51, 51, 1), |
||||
|
), |
||||
|
SizedBox(width: 5.w,), |
||||
|
Text( |
||||
|
"开启定位", |
||||
|
style: TextStyle( |
||||
|
fontSize: 12.w, |
||||
|
fontWeight: FontWeight.w500 |
||||
|
), |
||||
|
) |
||||
|
], |
||||
|
), |
||||
|
), |
||||
|
Row( |
||||
|
children: [ |
||||
|
Image.asset( |
||||
|
Assets.imagesImg, |
||||
|
width: 20.w, |
||||
|
), |
||||
|
SizedBox(width: 25.w,), |
||||
|
Image.asset( |
||||
|
Assets.imagesEmoji, |
||||
|
width: 18.w, |
||||
|
).onTap(toggleEmojiPanel) |
||||
|
], |
||||
|
), |
||||
|
|
||||
|
// 表情面板 |
||||
|
EmojiPanel( |
||||
|
isVisible: isEmojiVisible, |
||||
|
onEmojiSelected: handleEmojiSelected, |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,212 @@ |
|||||
|
import 'package:dating_touchme_app/components/page_appbar.dart'; |
||||
|
import 'package:dating_touchme_app/generated/assets.dart'; |
||||
|
import 'package:flutter/material.dart'; |
||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart'; |
||||
|
|
||||
|
class TimelineInfo extends StatefulWidget { |
||||
|
const TimelineInfo({super.key}); |
||||
|
|
||||
|
@override |
||||
|
State<TimelineInfo> createState() => _TimelineInfoState(); |
||||
|
} |
||||
|
|
||||
|
class _TimelineInfoState extends State<TimelineInfo> { |
||||
|
@override |
||||
|
Widget build(BuildContext context) { |
||||
|
return Scaffold( |
||||
|
appBar: PageAppbar(title: "详情"), |
||||
|
body: SingleChildScrollView( |
||||
|
child: Container( |
||||
|
padding: EdgeInsets.symmetric( |
||||
|
horizontal: 16.w, |
||||
|
vertical: 10.w |
||||
|
), |
||||
|
child: Column( |
||||
|
crossAxisAlignment: CrossAxisAlignment.start, |
||||
|
children: [ |
||||
|
Row( |
||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
||||
|
children: [ |
||||
|
Row( |
||||
|
children: [ |
||||
|
Image.asset( |
||||
|
Assets.imagesUserAvatar, |
||||
|
width: 40.w, |
||||
|
height: 40.w, |
||||
|
), |
||||
|
SizedBox(width: 8.w,), |
||||
|
Column( |
||||
|
crossAxisAlignment: CrossAxisAlignment.start, |
||||
|
children: [ |
||||
|
Text( |
||||
|
"刘美玲", |
||||
|
style: TextStyle( |
||||
|
fontSize: 13.w, |
||||
|
fontWeight: FontWeight.w500 |
||||
|
), |
||||
|
), |
||||
|
Text( |
||||
|
"15:16", |
||||
|
style: TextStyle( |
||||
|
fontSize: 11.w, |
||||
|
color: const Color.fromRGBO(51, 51, 51, .6), |
||||
|
fontWeight: FontWeight.w500 |
||||
|
), |
||||
|
) |
||||
|
], |
||||
|
) |
||||
|
], |
||||
|
), |
||||
|
Icon( |
||||
|
Icons.keyboard_control, |
||||
|
size: 15.w, |
||||
|
color: const Color.fromRGBO(51, 51, 51, 1), |
||||
|
) |
||||
|
], |
||||
|
), |
||||
|
Container( |
||||
|
margin: EdgeInsets.symmetric(vertical: 11.w), |
||||
|
child: Text( |
||||
|
"你总顾及别人,那谁来顾及你。你总顾及别人,那谁来顾及你。你总顾及别人,那谁来顾及你。你总顾及别人,那谁来顾及你。" |
||||
|
), |
||||
|
), |
||||
|
Image.asset( |
||||
|
Assets.imagesRoseBanner, |
||||
|
width: 343.w, |
||||
|
), |
||||
|
SizedBox(height: 15.w,), |
||||
|
Row( |
||||
|
mainAxisAlignment: MainAxisAlignment.end, |
||||
|
children: [ |
||||
|
Row( |
||||
|
children: [ |
||||
|
Image.asset( |
||||
|
Assets.imagesLikeIcon, |
||||
|
width: 14.w, |
||||
|
height: 12.w, |
||||
|
), |
||||
|
SizedBox(width: 6.w,), |
||||
|
Text( |
||||
|
"47", |
||||
|
style: TextStyle( |
||||
|
fontSize: 11.w, |
||||
|
color: const Color.fromRGBO(144, 144, 144, .6) |
||||
|
), |
||||
|
) |
||||
|
], |
||||
|
), |
||||
|
SizedBox(width: 33.w,), |
||||
|
Row( |
||||
|
children: [ |
||||
|
Image.asset( |
||||
|
Assets.imagesCommentIcon, |
||||
|
width: 15.w, |
||||
|
height: 15.w, |
||||
|
), |
||||
|
SizedBox(width: 6.w,), |
||||
|
Text( |
||||
|
"23", |
||||
|
style: TextStyle( |
||||
|
fontSize: 11.w, |
||||
|
color: const Color.fromRGBO(144, 144, 144, .6) |
||||
|
), |
||||
|
) |
||||
|
], |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
SizedBox(height: 18.w,), |
||||
|
Text( |
||||
|
"全部评论(23)", |
||||
|
style: TextStyle( |
||||
|
fontSize: 13.w, |
||||
|
color: const Color.fromRGBO(144, 144, 144, 1), |
||||
|
fontWeight: FontWeight.w500 |
||||
|
), |
||||
|
), |
||||
|
SizedBox(height: 20.w,), |
||||
|
CommentItem(), |
||||
|
CommentItem(), |
||||
|
CommentItem(), |
||||
|
CommentItem(), |
||||
|
CommentItem(), |
||||
|
CommentItem(), |
||||
|
CommentItem(), |
||||
|
], |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
class CommentItem extends StatefulWidget { |
||||
|
const CommentItem({super.key}); |
||||
|
|
||||
|
@override |
||||
|
State<CommentItem> createState() => _CommentItemState(); |
||||
|
} |
||||
|
|
||||
|
class _CommentItemState extends State<CommentItem> { |
||||
|
@override |
||||
|
Widget build(BuildContext context) { |
||||
|
return Container( |
||||
|
margin: EdgeInsets.only(bottom: 20.w), |
||||
|
child: Row( |
||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
||||
|
children: [ |
||||
|
Expanded( |
||||
|
child: Row( |
||||
|
crossAxisAlignment: CrossAxisAlignment.start, |
||||
|
children: [ |
||||
|
Image.asset( |
||||
|
Assets.imagesUserAvatar, |
||||
|
width: 40.w, |
||||
|
height: 40.w, |
||||
|
), |
||||
|
SizedBox(width: 8.w,), |
||||
|
Expanded( |
||||
|
child: Column( |
||||
|
crossAxisAlignment: CrossAxisAlignment.start, |
||||
|
children: [ |
||||
|
Text( |
||||
|
"刘美玲", |
||||
|
style: TextStyle( |
||||
|
fontSize: 13.w, |
||||
|
color: const Color.fromRGBO(144, 144, 144, 1), |
||||
|
), |
||||
|
), |
||||
|
SizedBox(height: 5.w,), |
||||
|
SizedBox( |
||||
|
child: Text( |
||||
|
"看起来还不错!看起来还不错!看起来还不错!看起来还不错!看起来还不错!看起来还不错!看起来还不错!看起来还不错!", |
||||
|
style: TextStyle( |
||||
|
fontSize: 13.w, |
||||
|
fontWeight: FontWeight.w500 |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
SizedBox(height: 5.w,), |
||||
|
Text( |
||||
|
"15:16·回复", |
||||
|
style: TextStyle( |
||||
|
fontSize: 11.w, |
||||
|
color: const Color.fromRGBO(51, 51, 51, .6), |
||||
|
), |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
) |
||||
|
], |
||||
|
), |
||||
|
), |
||||
|
Image.asset( |
||||
|
Assets.imagesLikeIcon, |
||||
|
width: 14.w, |
||||
|
) |
||||
|
], |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,119 @@ |
|||||
|
import 'package:dating_touchme_app/extension/ex_widget.dart'; |
||||
|
import 'package:dating_touchme_app/generated/assets.dart'; |
||||
|
import 'package:dating_touchme_app/pages/home/timeline_info.dart'; |
||||
|
import 'package:flutter/material.dart'; |
||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart'; |
||||
|
import 'package:get/get.dart'; |
||||
|
|
||||
|
class TimelineItem extends StatefulWidget { |
||||
|
const TimelineItem({super.key}); |
||||
|
|
||||
|
@override |
||||
|
State<TimelineItem> createState() => _TimelineItemState(); |
||||
|
} |
||||
|
|
||||
|
class _TimelineItemState extends State<TimelineItem> { |
||||
|
@override |
||||
|
Widget build(BuildContext context) { |
||||
|
return Container( |
||||
|
margin: EdgeInsets.only(bottom: 15.w), |
||||
|
child: Column( |
||||
|
children: [ |
||||
|
Row( |
||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
||||
|
children: [ |
||||
|
Row( |
||||
|
children: [ |
||||
|
Image.asset( |
||||
|
Assets.imagesUserAvatar, |
||||
|
width: 40.w, |
||||
|
height: 40.w, |
||||
|
), |
||||
|
SizedBox(width: 8.w,), |
||||
|
Column( |
||||
|
crossAxisAlignment: CrossAxisAlignment.start, |
||||
|
children: [ |
||||
|
Text( |
||||
|
"刘美玲", |
||||
|
style: TextStyle( |
||||
|
fontSize: 13.w, |
||||
|
fontWeight: FontWeight.w500 |
||||
|
), |
||||
|
), |
||||
|
Text( |
||||
|
"15:16", |
||||
|
style: TextStyle( |
||||
|
fontSize: 11.w, |
||||
|
color: const Color.fromRGBO(51, 51, 51, .6), |
||||
|
fontWeight: FontWeight.w500 |
||||
|
), |
||||
|
) |
||||
|
], |
||||
|
) |
||||
|
], |
||||
|
), |
||||
|
Icon( |
||||
|
Icons.keyboard_control, |
||||
|
size: 15.w, |
||||
|
color: const Color.fromRGBO(51, 51, 51, 1), |
||||
|
) |
||||
|
], |
||||
|
), |
||||
|
Container( |
||||
|
margin: EdgeInsets.symmetric(vertical: 11.w), |
||||
|
child: Text( |
||||
|
"你总顾及别人,那谁来顾及你。你总顾及别人,那谁来顾及你。你总顾及别人,那谁来顾及你。你总顾及别人,那谁来顾及你。" |
||||
|
), |
||||
|
), |
||||
|
Image.asset( |
||||
|
Assets.imagesRoseBanner, |
||||
|
width: 375.w - 24, |
||||
|
), |
||||
|
SizedBox(height: 14.w,), |
||||
|
Row( |
||||
|
mainAxisAlignment: MainAxisAlignment.end, |
||||
|
children: [ |
||||
|
Row( |
||||
|
children: [ |
||||
|
Image.asset( |
||||
|
Assets.imagesLikeIcon, |
||||
|
width: 14.w, |
||||
|
height: 12.w, |
||||
|
), |
||||
|
SizedBox(width: 6.w,), |
||||
|
Text( |
||||
|
"47", |
||||
|
style: TextStyle( |
||||
|
fontSize: 11.w, |
||||
|
color: const Color.fromRGBO(144, 144, 144, .6) |
||||
|
), |
||||
|
) |
||||
|
], |
||||
|
), |
||||
|
SizedBox(width: 33.w,), |
||||
|
Row( |
||||
|
children: [ |
||||
|
Image.asset( |
||||
|
Assets.imagesCommentIcon, |
||||
|
width: 15.w, |
||||
|
height: 15.w, |
||||
|
), |
||||
|
SizedBox(width: 6.w,), |
||||
|
Text( |
||||
|
"23", |
||||
|
style: TextStyle( |
||||
|
fontSize: 11.w, |
||||
|
color: const Color.fromRGBO(144, 144, 144, .6) |
||||
|
), |
||||
|
) |
||||
|
], |
||||
|
), |
||||
|
], |
||||
|
) |
||||
|
], |
||||
|
), |
||||
|
).onTap((){ |
||||
|
Get.to(() => TimelineInfo()); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,111 @@ |
|||||
|
import 'package:dating_touchme_app/controller/home/home_controller.dart'; |
||||
|
import 'package:dating_touchme_app/pages/home/all_timeline.dart'; |
||||
|
import 'package:flutter/material.dart'; |
||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart'; |
||||
|
import 'package:get/get.dart'; |
||||
|
import 'package:tdesign_flutter/tdesign_flutter.dart'; |
||||
|
|
||||
|
class TimelineWindow extends StatefulWidget { |
||||
|
const TimelineWindow({super.key}); |
||||
|
|
||||
|
@override |
||||
|
State<TimelineWindow> createState() => _TimelineWindowState(); |
||||
|
} |
||||
|
|
||||
|
class _TimelineWindowState extends State<TimelineWindow> with AutomaticKeepAliveClientMixin, SingleTickerProviderStateMixin { |
||||
|
|
||||
|
|
||||
|
late TabController tabController; |
||||
|
final HomeController controller = Get.find<HomeController>(); |
||||
|
|
||||
|
@override |
||||
|
void initState() { |
||||
|
super.initState(); |
||||
|
|
||||
|
tabController = TabController(length: 2, vsync: this); |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
Widget build(BuildContext context) { |
||||
|
super.build(context); |
||||
|
return Column( |
||||
|
children: [ |
||||
|
Container( |
||||
|
height: 25.w, |
||||
|
color: const Color.fromRGBO(128, 91, 253, 1), |
||||
|
child: Row( |
||||
|
mainAxisAlignment: MainAxisAlignment.center, |
||||
|
children: [ |
||||
|
Icon( |
||||
|
Icons.location_on, |
||||
|
size: 14.w, |
||||
|
color: Colors.white, |
||||
|
), |
||||
|
SizedBox(width: 7.w,), |
||||
|
Text( |
||||
|
"点击开启定位,即可查看附近的人", |
||||
|
style: TextStyle( |
||||
|
fontSize: 12.w, |
||||
|
color: Colors.white, |
||||
|
fontWeight: FontWeight.w500 |
||||
|
), |
||||
|
) |
||||
|
], |
||||
|
), |
||||
|
), |
||||
|
TDTabBar( |
||||
|
tabs: [ |
||||
|
TDTab( |
||||
|
child: Padding( |
||||
|
padding: EdgeInsets.only(right: 16, left: 16), |
||||
|
child: Text('全部'), |
||||
|
), |
||||
|
), |
||||
|
TDTab( |
||||
|
child: Padding( |
||||
|
padding: EdgeInsets.only(right: 16, left: 16), |
||||
|
child: Text('好友动态'), |
||||
|
), |
||||
|
), |
||||
|
], |
||||
|
backgroundColor: Colors.transparent, |
||||
|
labelPadding: const EdgeInsets.only(right: 4, top: 10, bottom: 10, left: 4), |
||||
|
selectedBgColor: const Color.fromRGBO(108, 105, 244, 1), |
||||
|
unSelectedBgColor: Colors.transparent, |
||||
|
labelColor: Colors.white, |
||||
|
dividerHeight: 0, |
||||
|
tabAlignment: TabAlignment.start, |
||||
|
outlineType: TDTabBarOutlineType.capsule, |
||||
|
controller: tabController, |
||||
|
showIndicator: false, |
||||
|
isScrollable: true, |
||||
|
onTap: (index) async { |
||||
|
print('相亲页面 Tab: $index'); |
||||
|
if (controller.timelineTab.value != index) { |
||||
|
controller.setTimelineTab(index); |
||||
|
// 确保状态更新后刷新UI |
||||
|
controller.update(); |
||||
|
} |
||||
|
}, |
||||
|
), |
||||
|
Expanded( |
||||
|
child: Obx(() { |
||||
|
// 使用 IndexedStack 保持两个列表的状态,根据当前选中的标签显示对应的列表 |
||||
|
return IndexedStack( |
||||
|
index: controller.timelineTab.value, |
||||
|
children: const [ |
||||
|
// 推荐列表 |
||||
|
AllTimeline(), |
||||
|
// 同城列表 |
||||
|
], |
||||
|
); |
||||
|
}), |
||||
|
), |
||||
|
], |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
@override |
||||
|
bool get wantKeepAlive => true; |
||||
|
} |
||||
Write
Preview
Loading…
Cancel
Save