首页调整
This commit is contained in:
parent
114e18698f
commit
2b956eb96c
@ -120,3 +120,6 @@ flutter pub upgrade
|
|||||||
- **Dart**: 3.2.5+
|
- **Dart**: 3.2.5+
|
||||||
- **目标平台**: iOS、Android(支持 Web)
|
- **目标平台**: iOS、Android(支持 Web)
|
||||||
- **构建工具**: 标准 Flutter 工具链
|
- **构建工具**: 标准 Flutter 工具链
|
||||||
|
|
||||||
|
## 特殊说明
|
||||||
|
- 代码中使用中文注释,且简介、有效的说明逻辑,并支持flutter复杂、重点的运用和开发技巧,方便后续复盘和学习
|
||||||
@ -14,6 +14,7 @@ class SnapWishApp extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
title: 'SnapWish',
|
title: 'SnapWish',
|
||||||
|
debugShowCheckedModeBanner: false,
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
@ -44,28 +45,92 @@ class _MainPageState extends State<MainPage> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: _pages[_currentIndex],
|
extendBody: true, // 允许内容延伸到导航栏区域
|
||||||
floatingActionButton: FloatingActionButton(
|
body: Stack(
|
||||||
onPressed: () {
|
children: [
|
||||||
Navigator.pushNamed(context, '/add-photo');
|
_pages[_currentIndex],
|
||||||
},
|
// 悬浮底部导航组件
|
||||||
child: const Icon(Icons.add),
|
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),
|
||||||
),
|
),
|
||||||
bottomNavigationBar: NavigationBar(
|
],
|
||||||
selectedIndex: _currentIndex,
|
),
|
||||||
onDestinationSelected: (index) {
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: [
|
||||||
|
// 照片标签按钮
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
_currentIndex = index;
|
_currentIndex = 0;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
destinations: const [
|
icon: Icon(
|
||||||
NavigationDestination(
|
Icons.photo_library,
|
||||||
icon: Icon(Icons.photo_library),
|
color: _currentIndex == 0
|
||||||
label: '照片',
|
? 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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
NavigationDestination(
|
|
||||||
icon: Icon(Icons.folder),
|
|
||||||
label: '分类',
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@ -93,22 +93,39 @@ class _CategoriesPageState extends State<CategoriesPage> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
body: Stack(
|
||||||
title: const Text('分类'),
|
children: [
|
||||||
actions: [
|
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(
|
IconButton(
|
||||||
icon: const Icon(Icons.create_new_folder),
|
icon: const Icon(Icons.create_new_folder, color: Colors.deepPurple),
|
||||||
onPressed: _createCategory,
|
onPressed: _createCategory,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: ListView.builder(
|
);
|
||||||
padding: const EdgeInsets.all(8),
|
}
|
||||||
itemCount: _categories.length,
|
|
||||||
itemBuilder: (context, index) {
|
final categoryIndex = index - 1;
|
||||||
final category = _categories[index];
|
final category = _categories[categoryIndex];
|
||||||
return Card(
|
return Card(
|
||||||
margin: const EdgeInsets.symmetric(vertical: 4),
|
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
leading: CircleAvatar(
|
leading: CircleAvatar(
|
||||||
backgroundColor: category['isDefault']
|
backgroundColor: category['isDefault']
|
||||||
@ -125,18 +142,50 @@ class _CategoriesPageState extends State<CategoriesPage> {
|
|||||||
? null
|
? null
|
||||||
: IconButton(
|
: IconButton(
|
||||||
icon: const Icon(Icons.delete_outline, color: Colors.red),
|
icon: const Icon(Icons.delete_outline, color: Colors.red),
|
||||||
onPressed: () => _deleteCategory(index),
|
onPressed: () => _deleteCategory(categoryIndex),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// TODO: 打开该分类的照片列表
|
// TODO: 打开该分类的照片列表
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text('打开 ${category['name']}')),
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
class PhotoPage extends StatelessWidget {
|
class PhotoPage extends StatelessWidget {
|
||||||
const PhotoPage({super.key});
|
const PhotoPage({super.key});
|
||||||
@ -6,40 +7,148 @@ class PhotoPage extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
body: Stack(
|
||||||
title: const Text('照片'),
|
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 {
|
class PhotoTimeline extends StatelessWidget {
|
||||||
const PhotoGrid({super.key});
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// TODO: 替换为真实照片数据
|
final groupedPhotos = _groupPhotosByDate();
|
||||||
final mockPhotos = List.generate(20, (index) => '照片 ${index + 1}');
|
final sortedKeys = groupedPhotos.keys.toList()
|
||||||
|
..sort((a, b) {
|
||||||
|
// 将中文日期转换为可排序的格式
|
||||||
|
final dateA = _parseDateKey(a);
|
||||||
|
final dateB = _parseDateKey(b);
|
||||||
|
return dateB.compareTo(dateA); // 降序排列
|
||||||
|
});
|
||||||
|
|
||||||
return GridView.builder(
|
return ListView.builder(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.only(top: 88, bottom: 16),
|
||||||
|
itemCount: sortedKeys.length,
|
||||||
|
itemBuilder: (context, 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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GridView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
crossAxisCount: 3,
|
crossAxisCount: 3,
|
||||||
crossAxisSpacing: 4,
|
crossAxisSpacing: 4,
|
||||||
mainAxisSpacing: 4,
|
mainAxisSpacing: 4,
|
||||||
childAspectRatio: 1,
|
childAspectRatio: 1,
|
||||||
),
|
),
|
||||||
itemCount: mockPhotos.length,
|
itemCount: photos.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, photoIndex) {
|
||||||
|
final photo = photos[photoIndex];
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => PhotoDetailPage(
|
builder: (context) => PhotoDetailPage(
|
||||||
photos: mockPhotos,
|
photos: photos.map((p) => p['title'] as String).toList(),
|
||||||
initialIndex: index,
|
initialIndex: photoIndex,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -56,7 +165,7 @@ class PhotoGrid extends StatelessWidget {
|
|||||||
const Icon(Icons.photo, size: 40, color: Colors.grey),
|
const Icon(Icons.photo, size: 40, color: Colors.grey),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
mockPhotos[index],
|
photo['title'] as String,
|
||||||
style: const TextStyle(fontSize: 12),
|
style: const TextStyle(fontSize: 12),
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
@ -66,7 +175,43 @@ class PhotoGrid extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user