首页调整
This commit is contained in:
parent
114e18698f
commit
2b956eb96c
@ -119,4 +119,7 @@ flutter pub upgrade
|
||||
- **Flutter**: 3.2.5+
|
||||
- **Dart**: 3.2.5+
|
||||
- **目标平台**: iOS、Android(支持 Web)
|
||||
- **构建工具**: 标准 Flutter 工具链
|
||||
- **构建工具**: 标准 Flutter 工具链
|
||||
|
||||
## 特殊说明
|
||||
- 代码中使用中文注释,且简介、有效的说明逻辑,并支持flutter复杂、重点的运用和开发技巧,方便后续复盘和学习
|
||||
109
lib/main.dart
109
lib/main.dart
@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@ -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']}')),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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('年')) {
|
||||
// 格式:2024年5月15日
|
||||
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 {
|
||||
// 格式:5月15日
|
||||
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 {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user