fix: v1.1.6+8 - time filter / signup button / friends sync
This commit is contained in:
parent
df047ad64a
commit
5849b5f1f6
@ -13,6 +13,7 @@ class ActivityDetailScreen extends StatefulWidget {
|
|||||||
class _ActivityDetailScreenState extends State<ActivityDetailScreen> {
|
class _ActivityDetailScreenState extends State<ActivityDetailScreen> {
|
||||||
final TextEditingController _commentController = TextEditingController();
|
final TextEditingController _commentController = TextEditingController();
|
||||||
bool _isLiked = false;
|
bool _isLiked = false;
|
||||||
|
bool _isJoined = false;
|
||||||
int _likeCount = 0;
|
int _likeCount = 0;
|
||||||
int _currentImageIndex = 0;
|
int _currentImageIndex = 0;
|
||||||
|
|
||||||
@ -454,7 +455,12 @@ class _ActivityDetailScreenState extends State<ActivityDetailScreen> {
|
|||||||
|
|
||||||
// 报名按钮
|
// 报名按钮
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: _isJoined
|
||||||
|
? null
|
||||||
|
: () {
|
||||||
|
setState(() {
|
||||||
|
_isJoined = true;
|
||||||
|
});
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text('报名成功!'),
|
content: Text('报名成功!'),
|
||||||
@ -470,7 +476,7 @@ class _ActivityDetailScreenState extends State<ActivityDetailScreen> {
|
|||||||
),
|
),
|
||||||
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 10),
|
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 10),
|
||||||
),
|
),
|
||||||
child: Text('报名'),
|
child: Text(_isJoined ? '已报名' : '报名'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@ -14,11 +14,13 @@ class ActivityListScreen extends StatefulWidget {
|
|||||||
class _ActivityListScreenState extends State<ActivityListScreen> {
|
class _ActivityListScreenState extends State<ActivityListScreen> {
|
||||||
int _selectedIndex = 0;
|
int _selectedIndex = 0;
|
||||||
final _feedKey = GlobalKey<RefreshIndicatorState>();
|
final _feedKey = GlobalKey<RefreshIndicatorState>();
|
||||||
|
String? _selectedTime;
|
||||||
|
|
||||||
// 模拟活动数据
|
// 模拟活动数据
|
||||||
final List<Map<String, dynamic>> activities = [
|
final List<Map<String, dynamic>> activities = [
|
||||||
{
|
{
|
||||||
'title': '周末公园太极拳',
|
'title': '周末公园太极拳',
|
||||||
|
'date': '今天 07:30',
|
||||||
'location': '人民公园',
|
'location': '人民公园',
|
||||||
'participants': 12,
|
'participants': 12,
|
||||||
'image': 'https://picsum.photos/seed/1/400/300',
|
'image': 'https://picsum.photos/seed/1/400/300',
|
||||||
@ -27,6 +29,7 @@ class _ActivityListScreenState extends State<ActivityListScreen> {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
'title': '社区书法班开课啦',
|
'title': '社区书法班开课啦',
|
||||||
|
'date': '明天 09:00',
|
||||||
'location': '社区活动中心',
|
'location': '社区活动中心',
|
||||||
'participants': 8,
|
'participants': 8,
|
||||||
'image': 'https://picsum.photos/seed/2/400/500',
|
'image': 'https://picsum.photos/seed/2/400/500',
|
||||||
@ -35,6 +38,7 @@ class _ActivityListScreenState extends State<ActivityListScreen> {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
'title': '广场舞队招新',
|
'title': '广场舞队招新',
|
||||||
|
'date': '本周三 19:00',
|
||||||
'location': '中央广场',
|
'location': '中央广场',
|
||||||
'participants': 25,
|
'participants': 25,
|
||||||
'image': 'https://picsum.photos/seed/3/400/400',
|
'image': 'https://picsum.photos/seed/3/400/400',
|
||||||
@ -43,6 +47,7 @@ class _ActivityListScreenState extends State<ActivityListScreen> {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
'title': '老年摄影兴趣小组',
|
'title': '老年摄影兴趣小组',
|
||||||
|
'date': '本周四 14:00',
|
||||||
'location': '文化馆',
|
'location': '文化馆',
|
||||||
'participants': 15,
|
'participants': 15,
|
||||||
'image': 'https://picsum.photos/seed/4/400/350',
|
'image': 'https://picsum.photos/seed/4/400/350',
|
||||||
@ -51,6 +56,7 @@ class _ActivityListScreenState extends State<ActivityListScreen> {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
'title': '周三棋牌活动',
|
'title': '周三棋牌活动',
|
||||||
|
'date': '本周五 16:00',
|
||||||
'location': '社区会所',
|
'location': '社区会所',
|
||||||
'participants': 20,
|
'participants': 20,
|
||||||
'image': 'https://picsum.photos/seed/5/400/450',
|
'image': 'https://picsum.photos/seed/5/400/450',
|
||||||
@ -59,6 +65,7 @@ class _ActivityListScreenState extends State<ActivityListScreen> {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
'title': '健康养生讲座',
|
'title': '健康养生讲座',
|
||||||
|
'date': '${DateTime.now().month}月${DateTime.now().day}日 10:30',
|
||||||
'location': '图书馆',
|
'location': '图书馆',
|
||||||
'participants': 30,
|
'participants': 30,
|
||||||
'image': 'https://picsum.photos/seed/6/400/380',
|
'image': 'https://picsum.photos/seed/6/400/380',
|
||||||
@ -147,24 +154,80 @@ class _ActivityListScreenState extends State<ActivityListScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildFeed() {
|
Widget _buildFeed() {
|
||||||
|
final filteredActivities = _getFilteredActivities();
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
key: _feedKey,
|
key: _feedKey,
|
||||||
onRefresh: _refreshActivities,
|
onRefresh: _refreshActivities,
|
||||||
child: Padding(
|
child: ListView(
|
||||||
padding: EdgeInsets.all(12),
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
child: MasonryGridView.count(
|
padding: const EdgeInsets.all(12),
|
||||||
|
children: [
|
||||||
|
_buildTimeFilterChips(),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
MasonryGridView.count(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
crossAxisCount: 2,
|
crossAxisCount: 2,
|
||||||
mainAxisSpacing: 10,
|
mainAxisSpacing: 10,
|
||||||
crossAxisSpacing: 10,
|
crossAxisSpacing: 10,
|
||||||
itemCount: activities.length,
|
itemCount: filteredActivities.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return _buildActivityCard(activities[index], index);
|
return _buildActivityCard(filteredActivities[index], index);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildTimeFilterChips() {
|
||||||
|
const timeOptions = ['今天', '明天', '本周'];
|
||||||
|
return SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: Row(
|
||||||
|
children: timeOptions.map((time) {
|
||||||
|
final isSelected = _selectedTime == time;
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 8),
|
||||||
|
child: ChoiceChip(
|
||||||
|
label: Text(time),
|
||||||
|
selected: isSelected,
|
||||||
|
onSelected: (_) {
|
||||||
|
setState(() {
|
||||||
|
_selectedTime = isSelected ? null : time;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
selectedColor: const Color(0xFFFF6B35),
|
||||||
|
labelStyle: TextStyle(
|
||||||
|
color: isSelected ? Colors.white : const Color(0xFF666666),
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
backgroundColor: const Color(0xFFFFEFE3),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Map<String, dynamic>> _getFilteredActivities() {
|
||||||
|
if (_selectedTime == null || _selectedTime == '本周') {
|
||||||
|
return activities;
|
||||||
|
}
|
||||||
|
|
||||||
|
final todayLabel = '${DateTime.now().month}月${DateTime.now().day}日';
|
||||||
|
return activities.where((activity) {
|
||||||
|
final dateText = (activity['date'] ?? '').toString();
|
||||||
|
if (_selectedTime == '今天') {
|
||||||
|
return dateText.contains('今天') || dateText.contains(todayLabel);
|
||||||
|
}
|
||||||
|
if (_selectedTime == '明天') {
|
||||||
|
return dateText.contains('明天');
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildActivityCard(Map<String, dynamic> activity, int index) {
|
Widget _buildActivityCard(Map<String, dynamic> activity, int index) {
|
||||||
final imageHeight = index.isOdd ? 160.0 : 210.0;
|
final imageHeight = index.isOdd ? 160.0 : 210.0;
|
||||||
final coverUrl =
|
final coverUrl =
|
||||||
@ -281,6 +344,28 @@ class _ActivityListScreenState extends State<ActivityListScreen> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
SizedBox(height: 6),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.access_time,
|
||||||
|
size: 13,
|
||||||
|
color: Color(0xFF999999),
|
||||||
|
),
|
||||||
|
SizedBox(width: 4),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
activity['date'] ?? '',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Color(0xFF999999),
|
||||||
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -9,26 +9,41 @@ class FriendsScreen extends StatefulWidget {
|
|||||||
|
|
||||||
class _FriendsScreenState extends State<FriendsScreen> {
|
class _FriendsScreenState extends State<FriendsScreen> {
|
||||||
final List<Map<String, dynamic>> _followingUsers = [
|
final List<Map<String, dynamic>> _followingUsers = [
|
||||||
{'name': '林阿姨', 'avatar': 3, 'isFollowing': true},
|
{'id': 'linayi', 'name': '林阿姨', 'avatar': 3, 'isFollowing': true},
|
||||||
{'name': '黄叔叔', 'avatar': 7, 'isFollowing': true},
|
{'id': 'huangshu', 'name': '黄叔叔', 'avatar': 7, 'isFollowing': true},
|
||||||
{'name': '周老师', 'avatar': 12, 'isFollowing': true},
|
{'id': 'zhoulaoshi', 'name': '周老师', 'avatar': 12, 'isFollowing': true},
|
||||||
{'name': '何姐', 'avatar': 18, 'isFollowing': true},
|
{'id': 'hejie', 'name': '何姐', 'avatar': 18, 'isFollowing': true},
|
||||||
{'name': '苏大哥', 'avatar': 24, 'isFollowing': false},
|
{'id': 'sudage', 'name': '苏大哥', 'avatar': 24, 'isFollowing': false},
|
||||||
{'name': '陶阿姨', 'avatar': 31, 'isFollowing': true},
|
{'id': 'taoayi', 'name': '陶阿姨', 'avatar': 31, 'isFollowing': true},
|
||||||
];
|
];
|
||||||
|
|
||||||
final List<Map<String, dynamic>> _fansUsers = [
|
final List<Map<String, dynamic>> _fansUsers = [
|
||||||
{'name': '白阿姨', 'avatar': 5, 'isFollowing': false},
|
{'id': 'baiayi', 'name': '白阿姨', 'avatar': 5, 'isFollowing': false},
|
||||||
{'name': '邓叔', 'avatar': 14, 'isFollowing': true},
|
{'id': 'dengshu', 'name': '邓叔', 'avatar': 14, 'isFollowing': true},
|
||||||
{'name': '贺老师', 'avatar': 19, 'isFollowing': false},
|
{'id': 'helaoshi', 'name': '贺老师', 'avatar': 19, 'isFollowing': false},
|
||||||
{'name': '任姐', 'avatar': 26, 'isFollowing': true},
|
{'id': 'renjie', 'name': '任姐', 'avatar': 26, 'isFollowing': true},
|
||||||
{'name': '罗阿姨', 'avatar': 38, 'isFollowing': false},
|
{'id': 'luoayi', 'name': '罗阿姨', 'avatar': 38, 'isFollowing': false},
|
||||||
{'name': '章叔叔', 'avatar': 46, 'isFollowing': true},
|
{'id': 'zhangshu', 'name': '章叔叔', 'avatar': 46, 'isFollowing': true},
|
||||||
];
|
];
|
||||||
|
|
||||||
void _toggleFollow(List<Map<String, dynamic>> users, int index) {
|
late final Set<String> _following;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_following = {
|
||||||
|
..._followingUsers.where((u) => u['isFollowing'] as bool).map((u) => u['id'] as String),
|
||||||
|
..._fansUsers.where((u) => u['isFollowing'] as bool).map((u) => u['id'] as String),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void _toggleFollow(String userId) {
|
||||||
setState(() {
|
setState(() {
|
||||||
users[index]['isFollowing'] = !(users[index]['isFollowing'] as bool);
|
if (_following.contains(userId)) {
|
||||||
|
_following.remove(userId);
|
||||||
|
} else {
|
||||||
|
_following.add(userId);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,7 +82,8 @@ class _FriendsScreenState extends State<FriendsScreen> {
|
|||||||
separatorBuilder: (_, __) => const SizedBox(height: 10),
|
separatorBuilder: (_, __) => const SizedBox(height: 10),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final user = users[index];
|
final user = users[index];
|
||||||
final isFollowing = user['isFollowing'] as bool;
|
final userId = user['id'] as String;
|
||||||
|
final isFollowing = _following.contains(userId);
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@ -95,7 +111,7 @@ class _FriendsScreenState extends State<FriendsScreen> {
|
|||||||
SizedBox(
|
SizedBox(
|
||||||
height: 34,
|
height: 34,
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
onPressed: () => _toggleFollow(users, index),
|
onPressed: () => _toggleFollow(userId),
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
backgroundColor: isFollowing ? Colors.white : const Color(0xFFFF8A3D),
|
backgroundColor: isFollowing ? Colors.white : const Color(0xFFFF8A3D),
|
||||||
foregroundColor: isFollowing ? const Color(0xFFFF8A3D) : Colors.white,
|
foregroundColor: isFollowing ? const Color(0xFFFF8A3D) : Colors.white,
|
||||||
|
|||||||
@ -33,6 +33,13 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
List<Activity> get _filteredActivities {
|
List<Activity> get _filteredActivities {
|
||||||
return _activities.where((a) {
|
return _activities.where((a) {
|
||||||
if (_selectedCategory != '全部' && a.category != _selectedCategory) return false;
|
if (_selectedCategory != '全部' && a.category != _selectedCategory) return false;
|
||||||
|
if (_timeFilter == '今天') {
|
||||||
|
return DateUtils.isSameDay(a.time, DateTime.now());
|
||||||
|
}
|
||||||
|
if (_timeFilter == '明天') {
|
||||||
|
final tomorrow = DateTime.now().add(const Duration(days: 1));
|
||||||
|
return DateUtils.isSameDay(a.time, tomorrow);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}).toList();
|
}).toList();
|
||||||
}
|
}
|
||||||
@ -123,6 +130,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
SliverList(
|
SliverList(
|
||||||
delegate: SliverChildBuilderDelegate(
|
delegate: SliverChildBuilderDelegate(
|
||||||
(ctx, i) => _ActivityCard(
|
(ctx, i) => _ActivityCard(
|
||||||
|
key: ValueKey(_filteredActivities[i].id),
|
||||||
activity: _filteredActivities[i],
|
activity: _filteredActivities[i],
|
||||||
onTap: () => Navigator.push(context, MaterialPageRoute(
|
onTap: () => Navigator.push(context, MaterialPageRoute(
|
||||||
builder: (_) => ActivityDetailScreen(activity: _filteredActivities[i]),
|
builder: (_) => ActivityDetailScreen(activity: _filteredActivities[i]),
|
||||||
@ -146,22 +154,44 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ActivityCard extends StatelessWidget {
|
class _ActivityCard extends StatefulWidget {
|
||||||
final Activity activity;
|
final Activity activity;
|
||||||
final VoidCallback onTap;
|
final VoidCallback onTap;
|
||||||
const _ActivityCard({required this.activity, required this.onTap});
|
const _ActivityCard({super.key, required this.activity, required this.onTap});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_ActivityCard> createState() => _ActivityCardState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ActivityCardState extends State<_ActivityCard> {
|
||||||
|
bool _isJoined = false;
|
||||||
|
|
||||||
String _categoryEmoji(String cat) {
|
String _categoryEmoji(String cat) {
|
||||||
const map = {'太极':'🧘','茶话会':'🍵','书法':'✍️','摄影':'📷','舞蹈':'💃','户外徒步':'🥾','手工':'🎨','唱歌':'🎵','棋牌':'♟️','读书':'📚','晨练':'🏃'};
|
const map = {'太极':'🧘','茶话会':'🍵','书法':'✍️','摄影':'📷','舞蹈':'💃','户外徒步':'🥾','手工':'🎨','唱歌':'🎵','棋牌':'♟️','读书':'📚','晨练':'🏃'};
|
||||||
return map[cat] ?? '🎯';
|
return map[cat] ?? '🎯';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _joinActivity() {
|
||||||
|
setState(() {
|
||||||
|
_isJoined = true;
|
||||||
|
});
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(content: Text('报名成功!')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final activity = widget.activity;
|
||||||
|
final canJoin = !activity.isFull && !_isJoined;
|
||||||
|
final buttonText = _isJoined
|
||||||
|
? '已报名'
|
||||||
|
: (activity.isFull ? '已满员' : '报名');
|
||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
|
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: onTap,
|
onTap: widget.onTap,
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
@ -203,12 +233,12 @@ class _ActivityCard extends StatelessWidget {
|
|||||||
SizedBox(
|
SizedBox(
|
||||||
height: 40,
|
height: 40,
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: activity.isFull ? null : () {},
|
onPressed: canJoin ? _joinActivity : null,
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
minimumSize: const Size(80, 40),
|
minimumSize: const Size(80, 40),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
),
|
),
|
||||||
child: Text(activity.isFull ? '已满员' : '报名', style: const TextStyle(fontSize: 16)),
|
child: Text(buttonText, style: const TextStyle(fontSize: 16)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
name: banxiang_app
|
name: banxiang_app
|
||||||
description: 伴享
|
description: 伴享
|
||||||
version: 1.1.5+7
|
version: 1.1.6+8
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.0.0 <4.0.0'
|
sdk: '>=3.0.0 <4.0.0'
|
||||||
|
|||||||
BIN
releases/banxiang-v1.1.6+8.apk
Normal file
BIN
releases/banxiang-v1.1.6+8.apk
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user