345 lines
10 KiB
Dart
345 lines
10 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'dart:math';
|
||
|
||
class PhotoPage extends StatelessWidget {
|
||
const PhotoPage({super.key});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Scaffold(
|
||
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,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
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) {
|
||
final groupedPhotos = _groupPhotosByDate();
|
||
final sortedKeys = groupedPhotos.keys.toList()
|
||
..sort((a, b) {
|
||
// 将中文日期转换为可排序的格式
|
||
final dateA = _parseDateKey(a);
|
||
final dateB = _parseDateKey(b);
|
||
return dateB.compareTo(dateA); // 降序排列
|
||
});
|
||
|
||
return ListView.builder(
|
||
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(
|
||
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 {
|
||
final List<String> photos;
|
||
final int initialIndex;
|
||
|
||
const PhotoDetailPage({
|
||
super.key,
|
||
required this.photos,
|
||
required this.initialIndex,
|
||
});
|
||
|
||
@override
|
||
State<PhotoDetailPage> createState() => _PhotoDetailPageState();
|
||
}
|
||
|
||
class _PhotoDetailPageState extends State<PhotoDetailPage> {
|
||
late int _currentIndex;
|
||
late PageController _pageController;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
_currentIndex = widget.initialIndex;
|
||
_pageController = PageController(initialPage: _currentIndex);
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_pageController.dispose();
|
||
super.dispose();
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Scaffold(
|
||
backgroundColor: Colors.black,
|
||
appBar: AppBar(
|
||
backgroundColor: Colors.transparent,
|
||
elevation: 0,
|
||
actions: [
|
||
IconButton(
|
||
icon: const Icon(Icons.favorite_border),
|
||
onPressed: () {
|
||
// TODO: 实现收藏功能
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
const SnackBar(content: Text('收藏功能待实现')),
|
||
);
|
||
},
|
||
),
|
||
IconButton(
|
||
icon: const Icon(Icons.share),
|
||
onPressed: () {
|
||
// TODO: 实现分享功能
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
const SnackBar(content: Text('分享功能待实现')),
|
||
);
|
||
},
|
||
),
|
||
IconButton(
|
||
icon: const Icon(Icons.delete),
|
||
onPressed: () {
|
||
// TODO: 实现删除功能
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
const SnackBar(content: Text('删除功能待实现')),
|
||
);
|
||
},
|
||
),
|
||
],
|
||
),
|
||
body: Stack(
|
||
children: [
|
||
PageView.builder(
|
||
controller: _pageController,
|
||
itemCount: widget.photos.length,
|
||
onPageChanged: (index) {
|
||
setState(() {
|
||
_currentIndex = index;
|
||
});
|
||
},
|
||
itemBuilder: (context, index) {
|
||
return Center(
|
||
child: Column(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
Container(
|
||
width: MediaQuery.of(context).size.width,
|
||
height: MediaQuery.of(context).size.height * 0.7,
|
||
color: Colors.grey[800],
|
||
child: Center(
|
||
child: Column(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
const Icon(Icons.photo, size: 100, color: Colors.white),
|
||
const SizedBox(height: 20),
|
||
Text(
|
||
widget.photos[index],
|
||
style: const TextStyle(
|
||
color: Colors.white,
|
||
fontSize: 24,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
},
|
||
),
|
||
Positioned(
|
||
bottom: 20,
|
||
left: 0,
|
||
right: 0,
|
||
child: Center(
|
||
child: Text(
|
||
'${_currentIndex + 1} / ${widget.photos.length}',
|
||
style: const TextStyle(
|
||
color: Colors.white,
|
||
fontSize: 16,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
} |