首页调整

This commit is contained in:
ddshi 2025-08-28 20:04:53 +08:00
parent 114e18698f
commit 2b956eb96c
4 changed files with 369 additions and 107 deletions

View File

@ -119,4 +119,7 @@ flutter pub upgrade
- **Flutter**: 3.2.5+
- **Dart**: 3.2.5+
- **目标平台**: iOS、Android支持 Web
- **构建工具**: 标准 Flutter 工具链
- **构建工具**: 标准 Flutter 工具链
## 特殊说明
- 代码中使用中文注释且简介、有效的说明逻辑并支持flutter复杂、重点的运用和开发技巧方便后续复盘和学习

View File

@ -14,6 +14,7 @@ class SnapWishApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
title: 'SnapWish',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
@ -44,28 +45,92 @@ class _MainPageState extends State<MainPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: _pages[_currentIndex],
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.pushNamed(context, '/add-photo');
},
child: const Icon(Icons.add),
),
bottomNavigationBar: NavigationBar(
selectedIndex: _currentIndex,
onDestinationSelected: (index) {
setState(() {
_currentIndex = index;
});
},
destinations: const [
NavigationDestination(
icon: Icon(Icons.photo_library),
label: '照片',
),
NavigationDestination(
icon: Icon(Icons.folder),
label: '分类',
extendBody: true, //
body: Stack(
children: [
_pages[_currentIndex],
//
Positioned(
left: 20,
right: 20,
bottom: 80,
child: Container(
height: 70,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.95),
borderRadius: BorderRadius.circular(35),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 10,
offset: const Offset(0, 5),
),
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
//
IconButton(
onPressed: () {
setState(() {
_currentIndex = 0;
});
},
icon: Icon(
Icons.photo_library,
color: _currentIndex == 0
? Colors.deepPurple
: Colors.grey,
size: 28,
),
),
//
GestureDetector(
onTap: () {
Navigator.pushNamed(context, '/add-photo');
},
child: Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: Colors.deepPurple,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.deepPurple.withOpacity(0.3),
blurRadius: 8,
offset: const Offset(0, 3),
),
],
),
child: const Icon(
Icons.add,
color: Colors.white,
size: 30,
),
),
),
//
IconButton(
onPressed: () {
setState(() {
_currentIndex = 1;
});
},
icon: Icon(
Icons.folder,
color: _currentIndex == 1
? Colors.deepPurple
: Colors.grey,
size: 28,
),
),
],
),
),
),
],
),

View File

@ -93,50 +93,99 @@ class _CategoriesPageState extends State<CategoriesPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('分类'),
actions: [
IconButton(
icon: const Icon(Icons.create_new_folder),
onPressed: _createCategory,
body: Stack(
children: [
ListView.builder(
padding: const EdgeInsets.only(top: 140, bottom: 16),
itemCount: _categories.length + 1, // +1 for header
itemBuilder: (context, index) {
if (index == 0) {
return Padding(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'分类',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.deepPurple,
),
),
IconButton(
icon: const Icon(Icons.create_new_folder, color: Colors.deepPurple),
onPressed: _createCategory,
),
],
),
);
}
final categoryIndex = index - 1;
final category = _categories[categoryIndex];
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: ListTile(
leading: CircleAvatar(
backgroundColor: category['isDefault']
? Colors.blue
: Colors.deepPurple,
child: const Icon(Icons.folder, color: Colors.white),
),
title: Text(
category['name'],
style: const TextStyle(fontWeight: FontWeight.bold),
),
subtitle: Text('${category['count']} 张照片'),
trailing: category['isDefault']
? null
: IconButton(
icon: const Icon(Icons.delete_outline, color: Colors.red),
onPressed: () => _deleteCategory(categoryIndex),
),
onTap: () {
// TODO:
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('打开 ${category['name']}'))
);
},
),
);
},
),
//
Positioned(
top: 0,
left: 0,
right: 0,
child: Container(
height: 80,
padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.95),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: const Center(
child: Text(
'SnapWish',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.deepPurple,
),
),
),
),
),
],
),
body: ListView.builder(
padding: const EdgeInsets.all(8),
itemCount: _categories.length,
itemBuilder: (context, index) {
final category = _categories[index];
return Card(
margin: const EdgeInsets.symmetric(vertical: 4),
child: ListTile(
leading: CircleAvatar(
backgroundColor: category['isDefault']
? Colors.blue
: Colors.deepPurple,
child: const Icon(Icons.folder, color: Colors.white),
),
title: Text(
category['name'],
style: const TextStyle(fontWeight: FontWeight.bold),
),
subtitle: Text('${category['count']} 张照片'),
trailing: category['isDefault']
? null
: IconButton(
icon: const Icon(Icons.delete_outline, color: Colors.red),
onPressed: () => _deleteCategory(index),
),
onTap: () {
// TODO:
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('打开 ${category['name']}')),
);
},
),
);
},
),
);
}
}

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'dart:math';
class PhotoPage extends StatelessWidget {
const PhotoPage({super.key});
@ -6,68 +7,212 @@ class PhotoPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('照片'),
body: Stack(
children: [
const PhotoTimeline(),
Positioned(
top: 0,
left: 0,
right: 0,
child: Container(
height: 80,
padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.95),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: const Center(
child: Text(
'SnapWish',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.deepPurple,
),
),
),
),
),
],
),
body: const PhotoGrid(),
);
}
}
class PhotoGrid extends StatelessWidget {
const PhotoGrid({super.key});
class PhotoTimeline extends StatelessWidget {
const PhotoTimeline({super.key});
//
Map<String, List<Map<String, dynamic>>> _groupPhotosByDate() {
final random = Random();
final now = DateTime.now();
final photos = <Map<String, dynamic>>[];
// 30
for (int i = 0; i < 30; i++) {
final date = now.subtract(Duration(days: i));
final photoCount = random.nextInt(5) + 1; // 1-5
for (int j = 0; j < photoCount; j++) {
photos.add({
'id': 'photo_${i}_$j',
'title': '照片 ${i * 5 + j + 1}',
'date': date,
});
}
}
//
final grouped = <String, List<Map<String, dynamic>>>{};
for (final photo in photos) {
final date = photo['date'] as DateTime;
final key = _formatDateKey(date);
grouped.putIfAbsent(key, () => []).add(photo);
}
return grouped;
}
String _formatDateKey(DateTime date) {
final now = DateTime.now();
final today = DateTime(now.year, now.month, now.day);
final yesterday = today.subtract(const Duration(days: 1));
final dateDay = DateTime(date.year, date.month, date.day);
if (dateDay == today) {
return '今天';
} else if (dateDay == yesterday) {
return '昨天';
} else if (date.year == now.year) {
return '${date.month}${date.day}';
} else {
return '${date.year}${date.month}${date.day}';
}
}
@override
Widget build(BuildContext context) {
// TODO:
final mockPhotos = List.generate(20, (index) => '照片 ${index + 1}');
final groupedPhotos = _groupPhotosByDate();
final sortedKeys = groupedPhotos.keys.toList()
..sort((a, b) {
//
final dateA = _parseDateKey(a);
final dateB = _parseDateKey(b);
return dateB.compareTo(dateA); //
});
return GridView.builder(
padding: const EdgeInsets.all(8),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 4,
mainAxisSpacing: 4,
childAspectRatio: 1,
),
itemCount: mockPhotos.length,
return ListView.builder(
padding: const EdgeInsets.only(top: 88, bottom: 16),
itemCount: sortedKeys.length,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PhotoDetailPage(
photos: mockPhotos,
initialIndex: index,
final dateKey = sortedKeys[index];
final photos = groupedPhotos[dateKey]!;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
child: Text(
dateKey,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.deepPurple,
),
),
);
},
child: Container(
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(8),
),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.photo, size: 40, color: Colors.grey),
const SizedBox(height: 4),
Text(
mockPhotos[index],
style: const TextStyle(fontSize: 12),
overflow: TextOverflow.ellipsis,
),
],
GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
padding: const EdgeInsets.symmetric(horizontal: 16),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 4,
mainAxisSpacing: 4,
childAspectRatio: 1,
),
itemCount: photos.length,
itemBuilder: (context, photoIndex) {
final photo = photos[photoIndex];
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PhotoDetailPage(
photos: photos.map((p) => p['title'] as String).toList(),
initialIndex: photoIndex,
),
),
);
},
child: Container(
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(8),
),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.photo, size: 40, color: Colors.grey),
const SizedBox(height: 4),
Text(
photo['title'] as String,
style: const TextStyle(fontSize: 12),
overflow: TextOverflow.ellipsis,
),
],
),
),
),
);
},
),
),
],
);
},
);
}
DateTime _parseDateKey(String key) {
final now = DateTime.now();
if (key == '今天') {
return now;
} else if (key == '昨天') {
return now.subtract(const Duration(days: 1));
} else if (key.contains('')) {
// 2024515
final match = RegExp(r'(\d+)年(\d+)月(\d+)日').firstMatch(key);
if (match != null) {
return DateTime(
int.parse(match.group(1)!),
int.parse(match.group(2)!),
int.parse(match.group(3)!),
);
}
} else {
// 515
final match = RegExp(r'(\d+)月(\d+)日').firstMatch(key);
if (match != null) {
return DateTime(
now.year,
int.parse(match.group(1)!),
int.parse(match.group(2)!),
);
}
}
return now;
}
}
class PhotoDetailPage extends StatefulWidget {