Files
sweet_chat_app/lib/pages/explore_page.dart
2026-06-17 13:24:37 +08:00

278 lines
8.1 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/material.dart';
import 'package:liquid_glass_widgets/liquid_glass_widgets.dart';
class Comment {
final String nickname;
final String content;
Comment({required this.nickname, required this.content});
}
class Moment {
final String avatar;
final String nickname;
final String content;
final List<String> images;
bool liked;
int likeCount;
List<Comment> comments;
Moment({
required this.avatar,
required this.nickname,
required this.content,
this.images = const [],
this.liked = false,
this.likeCount = 0,
this.comments = const [],
});
}
/// ================= Explore Page =================
class ExplorePage extends StatefulWidget {
const ExplorePage({super.key});
@override
State<ExplorePage> createState() => _ExplorePageState();
}
class _ExplorePageState extends State<ExplorePage> {
final List<Moment> _moments = [
Moment(
avatar: 'https://picsum.photos/100?random=1',
nickname: '张三',
content: '今天天气真不错 ☀️',
images: [
'https://picsum.photos/300?random=1',
'https://picsum.photos/300?random=2',
'https://picsum.photos/300?random=3',
'https://picsum.photos/300?random=4',
'https://picsum.photos/300?random=5',
],
likeCount: 12,
comments: [
Comment(nickname: '李四', content: '确实不错'),
Comment(nickname: '王五', content: '想去旅游'),
],
),
Moment(
avatar: 'https://picsum.photos/100?random=2',
nickname: '李四',
content: 'Flutter 真香 🚀',
images: ['https://picsum.photos/300?random=6'],
likeCount: 8,
comments: [],
),
];
void _handleLike(int index) {
setState(() {
final m = _moments[index];
m.liked ? m.likeCount-- : m.likeCount++;
m.liked = !m.liked;
});
}
void _handleComment(int index, String text) {
setState(() {
_moments[index].comments.add(Comment(nickname: '', content: text));
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.transparent,
appBar: _buildAppBar(),
body: Padding(padding: const EdgeInsets.all(10), child: _buildMoment()),
);
}
PreferredSizeWidget _buildAppBar() {
return AppBar(
automaticallyImplyLeading: false,
backgroundColor: Color(0xFF120D25),
elevation: 0,
centerTitle: true,
title: const Text(
'朋友圈',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
actions: [
IconButton(
onPressed: () {},
icon: const Icon(Icons.add, size: 22, color: Colors.white),
splashRadius: 20,
),
],
);
}
Widget _buildMoment() {
return ListView.separated(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom + 80,
),
separatorBuilder: (BuildContext context, int index) {
return SizedBox(height: 10);
},
itemCount: _moments.length,
itemBuilder: (context, index) {
return MomentItem(
moment: _moments[index],
onLike: () => _handleLike(index),
onComment: (text) => _handleComment(index, text),
);
},
);
}
}
/// ================= Moment Item =================
class MomentItem extends StatefulWidget {
final Moment moment;
final VoidCallback onLike;
final Function(String) onComment;
const MomentItem({
super.key,
required this.moment,
required this.onLike,
required this.onComment,
});
@override
State<MomentItem> createState() => _MomentItemState();
}
class _MomentItemState extends State<MomentItem> {
final TextEditingController _controller = TextEditingController();
void _submit() {
if (_controller.text.trim().isEmpty) return;
widget.onComment(_controller.text.trim());
_controller.clear();
}
@override
Widget build(BuildContext context) {
final m = widget.moment;
return GlassCard(
padding: const EdgeInsets.all(12),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CircleAvatar(backgroundImage: NetworkImage(m.avatar)),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
m.nickname,
style: const TextStyle(
color: Colors.purple,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 6),
Text(m.content, style: const TextStyle(color: Colors.white)),
if (m.images.isNotEmpty)
Padding(
padding: const EdgeInsets.only(top: 8),
child: LayoutBuilder(
builder: (context, constraints) {
final isSingle = m.images.length == 1;
final itemWidth =
isSingle ? 180.0 : (constraints.maxWidth - 8) / 3;
return Wrap(
spacing: 4,
runSpacing: 4,
children:
m.images.map((url) {
return ClipRRect(
borderRadius: BorderRadius.circular(6),
child: Image.network(
url,
width: itemWidth,
height: itemWidth,
fit: BoxFit.cover,
),
);
}).toList(),
);
},
),
),
const SizedBox(height: 4),
Row(
children: [
Badge(
label: Text('${m.likeCount}'),
isLabelVisible: m.likeCount > 0,
offset: const Offset(0, 0),
child: IconButton(
onPressed: widget.onLike,
icon: Icon(
m.liked ? Icons.favorite : Icons.favorite_border,
color: m.liked ? Colors.red : Colors.grey,
),
),
),
Badge(
label: Text('${m.comments.length}'),
isLabelVisible: m.comments.length > 0,
offset: const Offset(0, 0),
child: IconButton(
onPressed: () => {},
icon: Icon(Icons.comment, color: Colors.grey),
),
),
],
),
if (m.comments.isNotEmpty)
Container(
width: double.infinity,
margin: const EdgeInsets.only(top: 6),
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Color(0xFF120D25),
borderRadius: BorderRadius.circular(6),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children:
m.comments
.map(
(c) => Text(
'${c.nickname}${c.content}',
style: const TextStyle(
fontSize: 13,
color: Colors.white,
),
),
)
.toList(),
),
),
],
),
),
],
),
);
}
}