diff --git a/lib/models/activity.dart b/lib/models/activity.dart index f710d87..c3f914d 100644 --- a/lib/models/activity.dart +++ b/lib/models/activity.dart @@ -8,7 +8,7 @@ class Activity { int currentParticipants; String creatorName; String description; - + Activity({ required this.id, required this.title, @@ -20,4 +20,6 @@ class Activity { required this.creatorName, this.description = '', }); + + bool get isFull => currentParticipants >= maxParticipants; } diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index e9474c1..6de6a4f 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -47,6 +47,7 @@ class _HomeScreenState extends State { @override Widget build(BuildContext context) { + final filteredActivities = _filteredActivities; return Scaffold( appBar: AppBar( title: Row( @@ -54,7 +55,10 @@ class _HomeScreenState extends State { children: [ const Icon(Icons.location_on, size: 18, color: Color(0xFF1976D2)), const SizedBox(width: 4), - const Text('成都', style: TextStyle(fontSize: 16, color: Color(0xFF666666))), + const Text( + '成都', + style: TextStyle(fontSize: 16, color: Color(0xFF666666)), + ), const SizedBox(width: 16), Expanded( child: Container( @@ -68,7 +72,10 @@ class _HomeScreenState extends State { SizedBox(width: 12), Icon(Icons.search, size: 20, color: Color(0xFF999999)), SizedBox(width: 8), - Text('搜索活动', style: TextStyle(fontSize: 14, color: Color(0xFF999999))), + Text( + '搜索活动', + style: TextStyle(fontSize: 14, color: Color(0xFF999999)), + ), ], ), ), @@ -82,75 +89,88 @@ class _HomeScreenState extends State { onRefresh: () async => _loadActivities(), child: CustomScrollView( slivers: [ - // Category chips - SliverToBoxAdapter( - child: SizedBox( - height: 48, - child: ListView( - scrollDirection: Axis.horizontal, - padding: const EdgeInsets.symmetric(horizontal: 16), - children: AppConstants.activityCategories.map((cat) { - final selected = _selectedCategory == cat; - return Padding( - padding: const EdgeInsets.only(right: 8), - child: ChoiceChip( - label: Text(cat, style: TextStyle(fontSize: 14, color: selected ? Colors.white : const Color(0xFF666666))), - selected: selected, - selectedColor: const Color(0xFF1976D2), - backgroundColor: const Color(0xFFF5F5F5), - onSelected: (_) => setState(() => _selectedCategory = cat), - ), - ); - }).toList(), + SliverList( + delegate: SliverChildListDelegate([ + SizedBox( + height: 48, + child: ListView( + scrollDirection: Axis.horizontal, + padding: const EdgeInsets.symmetric(horizontal: 16), + children: AppConstants.activityCategories.map((cat) { + final selected = _selectedCategory == cat; + return Padding( + padding: const EdgeInsets.only(right: 8), + child: ChoiceChip( + label: Text( + cat, + style: TextStyle( + fontSize: 14, + color: + selected + ? Colors.white + : const Color(0xFF666666), + ), + ), + selected: selected, + selectedColor: const Color(0xFF1976D2), + backgroundColor: const Color(0xFFF5F5F5), + onSelected: (_) => setState(() => _selectedCategory = cat), + ), + ); + }).toList(), + ), ), - ), - ), - // Time filter - SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: Row( - children: ['全部', '今天', '明天', '本周'].map((t) { - final selected = _timeFilter == t; - return Padding( - padding: const EdgeInsets.only(right: 12), - child: GestureDetector( - onTap: () => setState(() => _timeFilter = t), - child: Text(t, style: TextStyle( - fontSize: 16, - color: selected ? const Color(0xFF1976D2) : const Color(0xFF999999), - fontWeight: selected ? FontWeight.w600 : FontWeight.normal, - )), - ), - ); - }).toList(), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Row( + children: ['全部', '今天', '明天', '本周'].map((t) { + final selected = _timeFilter == t; + return Padding( + padding: const EdgeInsets.only(right: 12), + child: GestureDetector( + onTap: () => setState(() => _timeFilter = t), + child: Text( + t, + style: TextStyle( + fontSize: 16, + color: + selected + ? const Color(0xFF1976D2) + : const Color(0xFF999999), + fontWeight: + selected + ? FontWeight.w600 + : FontWeight.normal, + ), + ), + ), + ); + }).toList(), + ), ), - ), + ]), ), - // Activity list - SliverToBoxAdapter( - child: MasonryGridView.count( + SliverPadding( + padding: const EdgeInsets.all(12), + sliver: SliverMasonryGrid.count( crossAxisCount: 2, mainAxisSpacing: 10, crossAxisSpacing: 10, - padding: const EdgeInsets.all(12), - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: _filteredActivities.length, - itemBuilder: (ctx, index) { - final activity = _filteredActivities[index]; - return _ActivityCard( - key: ValueKey(activity.id), - activity: activity, - imageHeight: index.isOdd ? 160.0 : 210.0, - onTap: () => Navigator.push( - context, - MaterialPageRoute( - builder: (_) => ActivityDetailScreen(activity: activity), + childCount: filteredActivities.length, + itemBuilder: (ctx, i) => _ActivityCard( + key: ValueKey(filteredActivities[i].id), + activity: filteredActivities[i], + onTap: + () => Navigator.push( + context, + MaterialPageRoute( + builder: + (_) => ActivityDetailScreen( + activity: filteredActivities[i], + ), + ), ), - ), - ); - }, + ), ), ), const SliverToBoxAdapter(child: SizedBox(height: 80)), @@ -158,7 +178,11 @@ class _HomeScreenState extends State { ), ), floatingActionButton: FloatingActionButton.extended( - onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const CreateActivityScreen())), + onPressed: + () => Navigator.push( + context, + MaterialPageRoute(builder: (_) => const CreateActivityScreen()), + ), icon: const Icon(Icons.add), label: const Text('发起活动', style: TextStyle(fontSize: 16)), backgroundColor: const Color(0xFF1976D2), @@ -170,12 +194,10 @@ class _HomeScreenState extends State { class _ActivityCard extends StatefulWidget { final Activity activity; - final double imageHeight; final VoidCallback onTap; const _ActivityCard({ super.key, required this.activity, - required this.imageHeight, required this.onTap, }); @@ -184,26 +206,37 @@ class _ActivityCard extends StatefulWidget { } class _ActivityCardState extends State<_ActivityCard> { + bool _isJoined = false; + + void _joinActivity() { + setState(() => _isJoined = true); + ScaffoldMessenger.of(context) + ..hideCurrentSnackBar() + ..showSnackBar(const SnackBar(content: Text("报名成功!"))); + } + @override Widget build(BuildContext context) { final activity = widget.activity; + final canJoin = !activity.isFull && !_isJoined; + final imageHeight = activity.id.hashCode.isOdd ? 160.0 : 210.0; return Material( color: Colors.transparent, - child: InkWell( - onTap: widget.onTap, - borderRadius: BorderRadius.circular(12), - child: Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.06), - blurRadius: 6, - offset: const Offset(0, 2), - ), - ], - ), + child: Ink( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.06), + blurRadius: 6, + offset: const Offset(0, 2), + ), + ], + ), + child: InkWell( + onTap: widget.onTap, + borderRadius: BorderRadius.circular(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -214,11 +247,11 @@ class _ActivityCardState extends State<_ActivityCard> { ), child: Image.network( "https://picsum.photos/seed/${activity.title.hashCode.abs()}/400/300", - height: widget.imageHeight, + height: imageHeight, width: double.infinity, fit: BoxFit.cover, errorBuilder: (_, __, ___) => Container( - height: widget.imageHeight, + height: imageHeight, color: const Color(0xFFE0E0E0), ), ), @@ -265,6 +298,31 @@ class _ActivityCardState extends State<_ActivityCard> { ), ], ), + const SizedBox(height: 8), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: canJoin ? _joinActivity : null, + style: ElevatedButton.styleFrom( + minimumSize: const Size.fromHeight(36), + backgroundColor: const Color(0xFF1976D2), + foregroundColor: Colors.white, + disabledBackgroundColor: const Color(0xFFE0E0E0), + disabledForegroundColor: const Color(0xFF999999), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + textStyle: const TextStyle( + fontSize: 13, + fontWeight: FontWeight.w600, + ), + padding: EdgeInsets.zero, + ), + child: Text( + _isJoined ? "已报名" : activity.isFull ? "已满员" : "报名", + ), + ), + ), ], ), ), diff --git a/pubspec.yaml b/pubspec.yaml index c3f645a..99ab626 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: banxiang_app description: 伴享 -version: 1.1.7+9 +version: 1.1.8+10 environment: sdk: '>=3.0.0 <4.0.0' diff --git a/releases/banxiang-v1.1.8+10.apk b/releases/banxiang-v1.1.8+10.apk new file mode 100644 index 0000000..8d78407 Binary files /dev/null and b/releases/banxiang-v1.1.8+10.apk differ