2025-06-02 14:37:05 +08:00
|
|
|
|
import 'package:communityapp/bill.dart';
|
|
|
|
|
import 'package:communityapp/emergency.dart';
|
|
|
|
|
import 'package:communityapp/package.dart';
|
|
|
|
|
import 'package:communityapp/visitor.dart';
|
2025-04-29 16:13:50 +08:00
|
|
|
|
import 'package:flutter/material.dart';
|
2025-05-09 16:49:13 +08:00
|
|
|
|
import 'reapair.dart';
|
2025-06-02 14:37:05 +08:00
|
|
|
|
import 'activity.dart';
|
2025-06-03 13:01:33 +08:00
|
|
|
|
import 'announcement.dart';
|
2025-04-29 16:13:50 +08:00
|
|
|
|
|
|
|
|
|
class HomeContentPage extends StatelessWidget {
|
|
|
|
|
const HomeContentPage({super.key});
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context) {
|
2025-06-02 14:37:05 +08:00
|
|
|
|
return Scaffold(
|
|
|
|
|
appBar: AppBar(
|
|
|
|
|
title: const Text('社區通'),
|
|
|
|
|
actions: [
|
|
|
|
|
Row(
|
|
|
|
|
children: [
|
|
|
|
|
const Text(
|
|
|
|
|
'林小安 您好',
|
|
|
|
|
style: TextStyle(fontWeight: FontWeight.bold),
|
|
|
|
|
),
|
|
|
|
|
const SizedBox(width: 8),
|
|
|
|
|
Stack(
|
|
|
|
|
children: [
|
|
|
|
|
IconButton(
|
|
|
|
|
icon: const Icon(Icons.notifications),
|
|
|
|
|
onPressed: () {
|
|
|
|
|
// TODO: 跳轉到通知頁
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
Positioned(
|
|
|
|
|
right: 8,
|
|
|
|
|
top: 8,
|
|
|
|
|
child: Container(
|
|
|
|
|
padding: const EdgeInsets.all(2),
|
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
color: Colors.red,
|
|
|
|
|
borderRadius: BorderRadius.circular(10),
|
|
|
|
|
),
|
|
|
|
|
constraints: const BoxConstraints(
|
|
|
|
|
minWidth: 16,
|
|
|
|
|
minHeight: 16,
|
|
|
|
|
),
|
|
|
|
|
child: const Text(
|
|
|
|
|
'3',
|
|
|
|
|
style: TextStyle(color: Colors.white, fontSize: 10),
|
|
|
|
|
textAlign: TextAlign.center,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
2025-04-29 16:13:50 +08:00
|
|
|
|
],
|
|
|
|
|
),
|
2025-06-02 14:37:05 +08:00
|
|
|
|
body: SingleChildScrollView(
|
|
|
|
|
child: Column(
|
|
|
|
|
children: [
|
2025-06-03 13:01:33 +08:00
|
|
|
|
_announcementSection(context),
|
2025-06-02 14:37:05 +08:00
|
|
|
|
_quickMenuSection(context),
|
|
|
|
|
_adCarousel(),
|
|
|
|
|
_marqueeNotice(),
|
|
|
|
|
_activitySection(),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
2025-04-29 16:13:50 +08:00
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 📢 重要公告區塊
|
2025-06-03 13:01:33 +08:00
|
|
|
|
static Widget _announcementSection(BuildContext context) {
|
2025-04-29 16:13:50 +08:00
|
|
|
|
return Padding(
|
|
|
|
|
padding: const EdgeInsets.all(16),
|
|
|
|
|
child: Column(
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
children: [
|
2025-06-03 13:01:33 +08:00
|
|
|
|
Row(
|
|
|
|
|
children: [
|
|
|
|
|
const Text(
|
|
|
|
|
'📢 重要公告',
|
|
|
|
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
|
|
|
|
),
|
|
|
|
|
const Spacer(),
|
|
|
|
|
TextButton(
|
|
|
|
|
onPressed: () {
|
|
|
|
|
showModalBottomSheet(
|
|
|
|
|
context: context,
|
|
|
|
|
isScrollControlled: true,
|
|
|
|
|
backgroundColor: Colors.transparent,
|
|
|
|
|
builder: (_) => const AnnouncementWrapper(),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
style: OutlinedButton.styleFrom(
|
|
|
|
|
foregroundColor: Colors.blue,
|
|
|
|
|
side: const BorderSide(color: Colors.blue), // 藍色外框
|
|
|
|
|
shape: RoundedRectangleBorder(
|
|
|
|
|
borderRadius: BorderRadius.circular(20), // 更小的圓角
|
|
|
|
|
),
|
|
|
|
|
padding: const EdgeInsets.symmetric(
|
|
|
|
|
horizontal: 12,
|
|
|
|
|
vertical: 6,
|
|
|
|
|
), // 更小的內距
|
|
|
|
|
minimumSize: const Size(0, 0), // 取消預設最小尺寸限制
|
|
|
|
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap, // 點擊範圍不外擴
|
|
|
|
|
),
|
|
|
|
|
child: const Text(
|
|
|
|
|
'更多',
|
|
|
|
|
style: TextStyle(fontSize: 14), // 更小字體
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
2025-04-29 16:13:50 +08:00
|
|
|
|
),
|
|
|
|
|
const SizedBox(height: 8),
|
2025-06-03 13:01:33 +08:00
|
|
|
|
Padding(
|
|
|
|
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
|
|
|
child: Card(
|
|
|
|
|
child: ListTile(
|
|
|
|
|
title: const Text('4/20 水塔清洗通知'),
|
|
|
|
|
subtitle: const Text('本週六早上9:00至下午3:00進行清洗,請提前儲水。'),
|
|
|
|
|
),
|
2025-04-29 16:13:50 +08:00
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 🔧 功能選單區塊
|
|
|
|
|
static Widget _quickMenuSection(BuildContext context) {
|
|
|
|
|
return Padding(
|
|
|
|
|
padding: const EdgeInsets.all(16),
|
|
|
|
|
child: Column(
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
children: [
|
|
|
|
|
const Text(
|
|
|
|
|
'🔧 功能選單',
|
|
|
|
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
|
|
|
|
),
|
|
|
|
|
const SizedBox(height: 8),
|
|
|
|
|
GridView.count(
|
|
|
|
|
crossAxisCount: 3,
|
|
|
|
|
shrinkWrap: true,
|
|
|
|
|
physics: const NeverScrollableScrollPhysics(),
|
|
|
|
|
childAspectRatio: 1,
|
|
|
|
|
children: [
|
|
|
|
|
_buildQuickButton(context, '報修', 'assets/icons/repair.png'),
|
2025-06-02 14:37:05 +08:00
|
|
|
|
_buildQuickButton(context, '包裹', 'assets/icons/mail.png'),
|
2025-04-29 16:13:50 +08:00
|
|
|
|
_buildQuickButton(context, '訪客', 'assets/icons/visitor.png'),
|
|
|
|
|
_buildQuickButton(context, '繳費', 'assets/icons/payment.png'),
|
|
|
|
|
_buildQuickButton(context, '社區互動', 'assets/icons/community.png'),
|
|
|
|
|
_buildQuickButton(context, '緊急通報', 'assets/icons/emergency.png'),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static Widget _buildQuickButton(
|
|
|
|
|
BuildContext context,
|
|
|
|
|
String title,
|
|
|
|
|
String imgAssetPath,
|
|
|
|
|
) {
|
|
|
|
|
return GestureDetector(
|
|
|
|
|
onTap: () {
|
2025-05-09 16:49:13 +08:00
|
|
|
|
switch (title) {
|
|
|
|
|
case "報修":
|
2025-06-02 14:37:05 +08:00
|
|
|
|
showModalBottomSheet(
|
|
|
|
|
context: context,
|
|
|
|
|
isScrollControlled: true,
|
|
|
|
|
backgroundColor: Colors.transparent,
|
|
|
|
|
builder: (_) => const RepairPageWrapper(),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
case "包裹":
|
|
|
|
|
/*Navigator.push(
|
2025-05-09 16:49:13 +08:00
|
|
|
|
context,
|
2025-06-02 14:37:05 +08:00
|
|
|
|
MaterialPageRoute(builder: (context) => PackagePage()),
|
|
|
|
|
);*/
|
|
|
|
|
showModalBottomSheet(
|
|
|
|
|
context: context,
|
|
|
|
|
isScrollControlled: true,
|
|
|
|
|
backgroundColor: Colors.transparent,
|
|
|
|
|
builder: (context) => PackagePageWrapper(),
|
|
|
|
|
);
|
|
|
|
|
case "訪客":
|
|
|
|
|
showModalBottomSheet(
|
|
|
|
|
context: context,
|
|
|
|
|
isScrollControlled: true,
|
|
|
|
|
backgroundColor: Colors.transparent,
|
|
|
|
|
builder: (context) => VisitorPageWrapper(),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
case "繳費":
|
|
|
|
|
showModalBottomSheet(
|
|
|
|
|
context: context,
|
|
|
|
|
isScrollControlled: true,
|
|
|
|
|
backgroundColor: Colors.transparent,
|
|
|
|
|
builder: (_) => BillPageWrapper(),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
case "社區互動":
|
|
|
|
|
showModalBottomSheet(
|
|
|
|
|
context: context,
|
|
|
|
|
isScrollControlled: true,
|
|
|
|
|
backgroundColor: Colors.transparent,
|
|
|
|
|
builder: (_) => ActivityListPage(),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
case "緊急通報":
|
|
|
|
|
showModalBottomSheet(
|
|
|
|
|
context: context,
|
|
|
|
|
isScrollControlled: true,
|
|
|
|
|
backgroundColor: Colors.transparent,
|
|
|
|
|
builder: (_) => EmergencyPage(),
|
2025-05-09 16:49:13 +08:00
|
|
|
|
);
|
|
|
|
|
default:
|
|
|
|
|
}
|
2025-04-29 16:13:50 +08:00
|
|
|
|
},
|
|
|
|
|
child: Column(
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
|
children: [
|
|
|
|
|
Image.asset(imgAssetPath, width: 40, height: 40, color: Colors.grey),
|
|
|
|
|
const SizedBox(height: 8),
|
|
|
|
|
Text(title, style: const TextStyle(fontSize: 14)),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 🖼️ 廣告輪播區
|
|
|
|
|
static Widget _adCarousel() {
|
|
|
|
|
return Padding(
|
|
|
|
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
|
|
|
child: SizedBox(
|
|
|
|
|
height: 180,
|
|
|
|
|
child: PageView(
|
|
|
|
|
children: [
|
|
|
|
|
Image.asset('assets/banners/banner1.png', fit: BoxFit.cover),
|
|
|
|
|
Image.asset('assets/banners/banner2.png', fit: BoxFit.cover),
|
|
|
|
|
Image.asset('assets/banners/banner3.png', fit: BoxFit.cover),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 🏃♂️ 跑馬燈公告
|
|
|
|
|
static Widget _marqueeNotice() {
|
|
|
|
|
return const MarqueeNotice();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 🎉 活動卡片列表
|
|
|
|
|
static Widget _activitySection() {
|
|
|
|
|
return Padding(
|
|
|
|
|
padding: const EdgeInsets.all(16),
|
|
|
|
|
child: Column(
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
children: [
|
|
|
|
|
const Text(
|
|
|
|
|
'🎉 最新活動',
|
|
|
|
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
|
|
|
|
),
|
|
|
|
|
const SizedBox(height: 8),
|
|
|
|
|
SizedBox(
|
|
|
|
|
height: 240,
|
|
|
|
|
child: ListView(
|
|
|
|
|
scrollDirection: Axis.horizontal,
|
|
|
|
|
children: [
|
|
|
|
|
_buildActivityCard(
|
|
|
|
|
'中秋烤肉趴',
|
|
|
|
|
'9/9 晚上6點開烤',
|
|
|
|
|
'assets/activities/bbq.png',
|
|
|
|
|
),
|
|
|
|
|
_buildActivityCard(
|
|
|
|
|
'親子日遊園',
|
|
|
|
|
'玩具福袋等你拿!',
|
|
|
|
|
'assets/activities/family_day.png',
|
|
|
|
|
),
|
|
|
|
|
_buildActivityCard(
|
|
|
|
|
'健康講堂',
|
|
|
|
|
'醫師到場解說',
|
|
|
|
|
'assets/activities/health.png',
|
|
|
|
|
),
|
|
|
|
|
_buildActivityCard(
|
|
|
|
|
'防災演習',
|
|
|
|
|
'模擬火災逃生',
|
|
|
|
|
'assets/activities/fire_drill.png',
|
|
|
|
|
),
|
|
|
|
|
_buildActivityCard(
|
|
|
|
|
'社區園遊會',
|
|
|
|
|
'免費攤位吃到飽',
|
|
|
|
|
'assets/activities/festival.png',
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static Widget _buildActivityCard(
|
|
|
|
|
String title,
|
|
|
|
|
String subtitle,
|
|
|
|
|
String imgAssetPath,
|
|
|
|
|
) {
|
|
|
|
|
return Container(
|
|
|
|
|
width: 180,
|
|
|
|
|
margin: const EdgeInsets.only(right: 12),
|
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
color: Colors.white,
|
|
|
|
|
borderRadius: BorderRadius.circular(10),
|
|
|
|
|
boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 5)],
|
|
|
|
|
),
|
|
|
|
|
child: Column(
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
children: [
|
|
|
|
|
ClipRRect(
|
|
|
|
|
borderRadius: const BorderRadius.vertical(top: Radius.circular(10)),
|
|
|
|
|
child: Image.asset(
|
|
|
|
|
imgAssetPath,
|
|
|
|
|
height: 120,
|
|
|
|
|
width: double.infinity,
|
|
|
|
|
fit: BoxFit.cover,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
Padding(
|
|
|
|
|
padding: const EdgeInsets.all(8),
|
|
|
|
|
child: Column(
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
children: [
|
|
|
|
|
Text(
|
|
|
|
|
title,
|
|
|
|
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
|
|
|
|
),
|
|
|
|
|
const SizedBox(height: 4),
|
|
|
|
|
Text(
|
|
|
|
|
subtitle,
|
|
|
|
|
style: const TextStyle(fontSize: 12, color: Colors.black54),
|
|
|
|
|
),
|
|
|
|
|
const SizedBox(height: 8),
|
2025-06-02 14:37:05 +08:00
|
|
|
|
Row(
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.start, // 左對齊或你要調整的位置
|
|
|
|
|
children: [
|
|
|
|
|
ElevatedButton(
|
|
|
|
|
onPressed: () {
|
|
|
|
|
// TODO: 報名事件
|
|
|
|
|
},
|
|
|
|
|
style: ElevatedButton.styleFrom(
|
|
|
|
|
backgroundColor: Colors.green, // 綠色
|
|
|
|
|
fixedSize: const Size(75, 30), // 寬高都 60,正方形
|
|
|
|
|
shape: RoundedRectangleBorder(
|
|
|
|
|
borderRadius: BorderRadius.circular(8), // 0 圓角
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
child: const Text(
|
|
|
|
|
'報名',
|
|
|
|
|
softWrap: false,
|
|
|
|
|
style: TextStyle(color: Colors.white, fontSize: 12),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
const SizedBox(width: 10), // 按鈕間隔
|
|
|
|
|
ElevatedButton(
|
|
|
|
|
onPressed: () {
|
|
|
|
|
// TODO: 查看活動
|
|
|
|
|
},
|
|
|
|
|
style: ElevatedButton.styleFrom(
|
|
|
|
|
backgroundColor: Colors.blue, // 藍色
|
|
|
|
|
fixedSize: const Size(75, 30), // 寬高都 60,正方形
|
|
|
|
|
shape: RoundedRectangleBorder(
|
|
|
|
|
borderRadius: BorderRadius.circular(8), // 0 圓角
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
child: const Text(
|
|
|
|
|
'查看',
|
|
|
|
|
softWrap: false,
|
|
|
|
|
style: TextStyle(color: Colors.white, fontSize: 12),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
2025-04-29 16:13:50 +08:00
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class MarqueeNotice extends StatefulWidget {
|
|
|
|
|
const MarqueeNotice({super.key});
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
State<MarqueeNotice> createState() => _MarqueeNoticeState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class _MarqueeNoticeState extends State<MarqueeNotice>
|
|
|
|
|
with SingleTickerProviderStateMixin {
|
|
|
|
|
late final AnimationController _controller;
|
|
|
|
|
late final Animation<double> _animation;
|
|
|
|
|
|
|
|
|
|
final String _marqueeText = '公告: 歡迎使用我們的社區通,請多加利用。謝謝您~ 如有問題可洽談管理室';
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void initState() {
|
|
|
|
|
super.initState();
|
|
|
|
|
_controller = AnimationController(
|
|
|
|
|
duration: const Duration(seconds: 10),
|
|
|
|
|
vsync: this,
|
|
|
|
|
)..repeat();
|
|
|
|
|
|
|
|
|
|
_animation = Tween<double>(begin: 1.0, end: -1.0).animate(_controller);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void dispose() {
|
|
|
|
|
_controller.dispose();
|
|
|
|
|
super.dispose();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
return Container(
|
|
|
|
|
color: Colors.grey.shade300,
|
|
|
|
|
height: 40,
|
|
|
|
|
alignment: Alignment.centerLeft,
|
|
|
|
|
child: ClipRect(
|
|
|
|
|
child: AnimatedBuilder(
|
|
|
|
|
animation: _animation,
|
|
|
|
|
builder: (context, child) {
|
|
|
|
|
return FractionalTranslation(
|
|
|
|
|
translation: Offset(_animation.value, 0),
|
|
|
|
|
child: child,
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
child: Text(
|
|
|
|
|
_marqueeText,
|
|
|
|
|
style: const TextStyle(fontSize: 16),
|
|
|
|
|
overflow: TextOverflow.visible,
|
|
|
|
|
softWrap: false,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|