diff --git a/lib/home_content_page.dart b/lib/home_content_page.dart index a979564..ca8949e 100644 --- a/lib/home_content_page.dart +++ b/lib/home_content_page.dart @@ -1,21 +1,72 @@ +import 'package:communityapp/bill.dart'; +import 'package:communityapp/emergency.dart'; +import 'package:communityapp/package.dart'; +import 'package:communityapp/visitor.dart'; import 'package:flutter/material.dart'; import 'reapair.dart'; +import 'activity.dart'; class HomeContentPage extends StatelessWidget { const HomeContentPage({super.key}); @override Widget build(BuildContext context) { - return SingleChildScrollView( - child: Column( - children: [ - _announcementSection(), - _quickMenuSection(context), - _adCarousel(), - _marqueeNotice(), - _activitySection(), + 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, + ), + ), + ), + ], + ), + ], + ), ], ), + body: SingleChildScrollView( + child: Column( + children: [ + _announcementSection(), + _quickMenuSection(context), + _adCarousel(), + _marqueeNotice(), + _activitySection(), + ], + ), + ), ); } @@ -61,7 +112,7 @@ class HomeContentPage extends StatelessWidget { childAspectRatio: 1, children: [ _buildQuickButton(context, '報修', 'assets/icons/repair.png'), - _buildQuickButton(context, '郵件', 'assets/icons/mail.png'), + _buildQuickButton(context, '包裹', 'assets/icons/mail.png'), _buildQuickButton(context, '訪客', 'assets/icons/visitor.png'), _buildQuickButton(context, '繳費', 'assets/icons/payment.png'), _buildQuickButton(context, '社區互動', 'assets/icons/community.png'), @@ -82,9 +133,54 @@ class HomeContentPage extends StatelessWidget { onTap: () { switch (title) { case "報修": - Navigator.push( + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (_) => const RepairPageWrapper(), + ); + + case "包裹": + /*Navigator.push( context, - MaterialPageRoute(builder: (context) => const RepairPage()), + 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(), ); default: } @@ -212,11 +308,45 @@ class HomeContentPage extends StatelessWidget { style: const TextStyle(fontSize: 12, color: Colors.black54), ), const SizedBox(height: 8), - ElevatedButton( - onPressed: () { - // TODO: 活動詳情 - }, - child: const Text('查看'), + 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), + ), + ), + ], ), ], ), diff --git a/lib/home_page.dart b/lib/home_page.dart index 6a83bc0..9a10b7d 100644 --- a/lib/home_page.dart +++ b/lib/home_page.dart @@ -24,50 +24,6 @@ class _HomePageState extends State { @override Widget build(BuildContext context) { 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, - ), - ), - ), - ], - ), - ], - ), - ], - ), body: _pages[_currentIndex], bottomNavigationBar: BottomNavigationBar( type: BottomNavigationBarType.fixed, diff --git a/lib/message_page.dart b/lib/message_page.dart index 4d9787e..075b1c7 100644 --- a/lib/message_page.dart +++ b/lib/message_page.dart @@ -1,10 +1,174 @@ import 'package:flutter/material.dart'; -class MessagePage extends StatelessWidget { +void main() { + runApp(const MyApp()); +} + +class Message { + final String text; + final bool isUser; + Message(this.text, this.isUser); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + @override + Widget build(BuildContext context) { + return MaterialApp( + title: '訊息中心', + theme: ThemeData(primarySwatch: Colors.green), + home: const MessagePage(), + debugShowCheckedModeBanner: false, + ); + } +} + +class MessagePage extends StatefulWidget { const MessagePage({super.key}); + @override + State createState() => _MessagePageState(); +} + +class _MessagePageState extends State { + final List _messages = [ + Message('您好,請記得繳交這個月的管理費。', false), + Message('好的,謝謝通知!', true), + Message('提醒您 4/18 上午社區將進行水塔清洗,會暫停供水。', false), + Message('了解!', true), + ]; + + final TextEditingController _controller = TextEditingController(); + final ScrollController _scrollController = ScrollController(); + + void _sendMessage() { + final text = _controller.text.trim(); + if (text.isEmpty) return; + setState(() { + _messages.add(Message(text, true)); + }); + _controller.clear(); + + // 自動滾到底部,延遲讓畫面先更新 + Future.delayed(const Duration(milliseconds: 100), () { + if (_scrollController.hasClients) { + _scrollController.animateTo( + _scrollController.position.maxScrollExtent, + duration: const Duration(milliseconds: 300), + curve: Curves.easeOut, + ); + } + }); + } + + int _currentIndex = 2; // 預設訊息頁 @override Widget build(BuildContext context) { - return const Center(child: Text('訊息頁內容')); + return Scaffold( + appBar: AppBar( + title: const Text('訊息中心'), + backgroundColor: const Color(0xFF9eaf9f), + foregroundColor: Colors.white, + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () => Navigator.pop(context), + ), + ), + body: Column( + children: [ + Expanded( + child: ListView.builder( + controller: _scrollController, + padding: const EdgeInsets.all(16), + itemCount: _messages.length, + itemBuilder: (context, index) { + final msg = _messages[index]; + return Align( + alignment: + msg.isUser ? Alignment.centerRight : Alignment.centerLeft, + child: Container( + padding: const EdgeInsets.symmetric( + vertical: 10, + horizontal: 16, + ), + margin: const EdgeInsets.symmetric(vertical: 6), + constraints: const BoxConstraints(maxWidth: 300), + decoration: BoxDecoration( + color: + msg.isUser + ? const Color(0xFFD0E9C6) + : const Color(0xFFE0E0E0), + borderRadius: BorderRadius.circular(16), + ), + child: Text( + msg.text, + style: const TextStyle(fontSize: 14, height: 1.5), + ), + ), + ); + }, + ), + ), + ], + ), + bottomNavigationBar: SafeArea( + top: false, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // 輸入列 + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + color: Colors.white, + child: Row( + children: [ + IconButton( + icon: const Icon(Icons.image_outlined), + onPressed: () { + // 這裡可加選擇圖片功能 + }, + ), + Expanded( + child: TextField( + controller: _controller, + decoration: const InputDecoration( + hintText: '輸入訊息...', + contentPadding: EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(20)), + ), + ), + onSubmitted: (_) => _sendMessage(), + ), + ), + const SizedBox(width: 8), + ElevatedButton( + onPressed: _sendMessage, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF4CAF50), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + ), + child: const Text('發送'), + ), + ], + ), + ), + ], + ), + ), + resizeToAvoidBottomInset: true, // 鍵盤彈出時畫面會推上去 + ); + } + + @override + void dispose() { + _controller.dispose(); + _scrollController.dispose(); + super.dispose(); } } diff --git a/lib/personal_page.dart b/lib/personal_page.dart index de42d2f..135e5bf 100644 --- a/lib/personal_page.dart +++ b/lib/personal_page.dart @@ -3,6 +3,9 @@ import 'login_page.dart'; import 'edit_profile.dart'; import 'feedback.dart'; import 'reapair.dart'; +import 'visitor.dart'; +import 'package.dart'; +import 'bill.dart'; class PersonalPage extends StatelessWidget { const PersonalPage({super.key}); @@ -193,20 +196,36 @@ class PersonalPage extends StatelessWidget { physics: const NeverScrollableScrollPhysics(), children: [ _buildMenuItem('繳費通知', 'receipt.png', () { - // TODO: 繳費通知點擊 + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (_) => BillPageWrapper(), + ); }), _buildMenuItem('報修申請', 'repair.png', () { - // TODO: 報修申請點擊 - Navigator.push( - context, - MaterialPageRoute(builder: (context) => const RepairPage()), + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (_) => const RepairPageWrapper(), ); }), _buildMenuItem('包裹通知', 'package.png', () { - // TODO: 包裹通知點擊 + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (context) => PackagePageWrapper(), + ); }), _buildMenuItem('訪客', 'visitor.png', () { - // TODO: 訪客點擊 + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (context) => const VisitorPageWrapper(), + ); }), ], ), @@ -289,7 +308,10 @@ class PersonalPage extends StatelessWidget { onTap: () {}, child: Image.asset('assets/icons/logout.png', width: 18, height: 18), ),*/ - label: const Text('登出', style: TextStyle(fontSize: 16)), + label: const Text( + '登出', + style: TextStyle(fontSize: 16, color: Colors.white), + ), ), ); } diff --git a/lib/reapair.dart b/lib/reapair.dart index 494d307..3ea9c93 100644 --- a/lib/reapair.dart +++ b/lib/reapair.dart @@ -2,7 +2,9 @@ import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; class RepairPage extends StatefulWidget { - const RepairPage({super.key}); + const RepairPage({super.key, required this.scrollController}); + + final ScrollController scrollController; @override State createState() => _RepairPageState(); @@ -51,7 +53,7 @@ class _RepairPageState extends State { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('報修已提交!'))); - // 可擴充送出資料到伺服器 + // 這裡可以擴充送出資料到伺服器的邏輯 } } @@ -64,120 +66,160 @@ class _RepairPageState extends State { @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('水電網路報修'), - backgroundColor: const Color(0xFF9eaf9f), - foregroundColor: Colors.white, + return Container( + decoration: const BoxDecoration( + color: Color(0xFFF7F8FA), + borderRadius: BorderRadius.vertical(top: Radius.circular(16)), ), - body: ListView( - padding: const EdgeInsets.all(16), + child: Column( children: [ - const Text('🛠️ 填寫報修單', style: TextStyle(fontSize: 16)), - const SizedBox(height: 12), - Form( - key: _formKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + // 標題列 + Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + decoration: const BoxDecoration( + color: Color(0xFF9EAF9F), + borderRadius: BorderRadius.vertical(top: Radius.circular(16)), + ), + child: Row( children: [ + IconButton( + icon: const Icon(Icons.close, color: Colors.white), + onPressed: () => Navigator.pop(context), + ), + const SizedBox(width: 8), const Text( - '報修類別', - style: TextStyle(fontWeight: FontWeight.bold), - ), - DropdownButtonFormField( - value: _repairType, - items: const [ - DropdownMenuItem(value: '電力問題', child: Text('電力問題')), - DropdownMenuItem(value: '水管漏水', child: Text('水管漏水')), - DropdownMenuItem(value: '網路異常', child: Text('網路異常')), - DropdownMenuItem(value: '其他', child: Text('其他')), - ], - hint: const Text('請選擇'), - onChanged: (value) { - setState(() { - _repairType = value; - }); - }, - validator: (value) => value == null ? '請選擇報修類別' : null, - ), - const SizedBox(height: 12), - const Text( - '地點/房號', - style: TextStyle(fontWeight: FontWeight.bold), - ), - TextFormField( - controller: _locationController, - decoration: const InputDecoration(hintText: '如:B棟 3F'), - validator: - (value) => - value == null || value.isEmpty ? '請輸入地點' : null, - ), - const SizedBox(height: 12), - const Text( - '問題描述', - style: TextStyle(fontWeight: FontWeight.bold), - ), - TextFormField( - controller: _descriptionController, - maxLines: 3, - decoration: const InputDecoration( - hintText: '請簡要描述問題...', - border: OutlineInputBorder(), - ), - validator: - (value) => - value == null || value.isEmpty ? '請輸入問題描述' : null, - ), - const SizedBox(height: 12), - const Text( - '照片上傳(選填)', - style: TextStyle(fontWeight: FontWeight.bold), - ), - Row( - children: [ - ElevatedButton( - onPressed: _pickImage, - child: const Text( - '選擇照片', - style: TextStyle(color: Colors.purple), - ), - ), - const SizedBox(width: 8), - if (_photo != null) - Text('已選擇圖片', style: TextStyle(color: Colors.green)), - ], - ), - const SizedBox(height: 16), - ElevatedButton( - onPressed: _submitForm, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.green, - minimumSize: const Size.fromHeight(48), - ), - child: const Text( - '送出報修', - style: TextStyle(color: Colors.white), + '水電網路報修', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20, + color: Colors.white, ), ), ], ), ), - const SizedBox(height: 24), - const Text('📋 已申報紀錄', style: TextStyle(fontSize: 16)), - const SizedBox(height: 12), - ..._repairRecords.map( - (record) => Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '${record["date"]} - ${record["type"]}', - style: const TextStyle(fontWeight: FontWeight.bold), + + Expanded( + child: ListView( + controller: widget.scrollController, + padding: const EdgeInsets.all(16), + children: [ + const Text('🛠️ 填寫報修單', style: TextStyle(fontSize: 16)), + const SizedBox(height: 12), + Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + '報修類別', + style: TextStyle(fontWeight: FontWeight.bold), + ), + DropdownButtonFormField( + value: _repairType, + items: const [ + DropdownMenuItem(value: '電力問題', child: Text('電力問題')), + DropdownMenuItem(value: '水管漏水', child: Text('水管漏水')), + DropdownMenuItem(value: '網路異常', child: Text('網路異常')), + DropdownMenuItem(value: '其他', child: Text('其他')), + ], + hint: const Text('請選擇'), + onChanged: (value) { + setState(() { + _repairType = value; + }); + }, + validator: (value) => value == null ? '請選擇報修類別' : null, + ), + const SizedBox(height: 12), + const Text( + '地點/房號', + style: TextStyle(fontWeight: FontWeight.bold), + ), + TextFormField( + controller: _locationController, + decoration: const InputDecoration(hintText: '如:B棟 3F'), + validator: + (value) => + value == null || value.isEmpty ? '請輸入地點' : null, + ), + const SizedBox(height: 12), + const Text( + '問題描述', + style: TextStyle(fontWeight: FontWeight.bold), + ), + TextFormField( + controller: _descriptionController, + maxLines: 3, + decoration: const InputDecoration( + hintText: '請簡要描述問題...', + border: OutlineInputBorder(), + ), + validator: + (value) => + value == null || value.isEmpty + ? '請輸入問題描述' + : null, + ), + const SizedBox(height: 12), + const Text( + '照片上傳(選填)', + style: TextStyle(fontWeight: FontWeight.bold), + ), + Row( + children: [ + ElevatedButton( + onPressed: _pickImage, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.white, + ), + child: const Text( + '選擇照片', + style: TextStyle(color: Colors.purple), + ), + ), + const SizedBox(width: 8), + if (_photo != null) + const Text( + '已選擇圖片', + style: TextStyle(color: Colors.green), + ), + ], + ), + const SizedBox(height: 16), + ElevatedButton( + onPressed: _submitForm, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.green, + minimumSize: const Size.fromHeight(48), + ), + child: const Text( + '送出報修', + style: TextStyle(color: Colors.white), + ), + ), + ], ), - Text('地點:${record["location"]}|狀態:${record["status"]}'), - ], - ), + ), + const SizedBox(height: 24), + const Text('📋 已申報紀錄', style: TextStyle(fontSize: 16)), + const SizedBox(height: 12), + ..._repairRecords.map( + (record) => Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '${record["date"]} - ${record["type"]}', + style: const TextStyle(fontWeight: FontWeight.bold), + ), + Text('地點:${record["location"]}|狀態:${record["status"]}'), + ], + ), + ), + ), + ], ), ), ], @@ -185,3 +227,24 @@ class _RepairPageState extends State { ); } } + +// 浮層包裝元件,給 showModalBottomSheet 使用 +class RepairPageWrapper extends StatelessWidget { + const RepairPageWrapper({super.key}); + + @override + Widget build(BuildContext context) { + return DraggableScrollableSheet( + initialChildSize: 0.95, + minChildSize: 0.5, + maxChildSize: 1.0, + expand: false, + builder: (_, scrollController) { + return Container( + color: Colors.transparent, + child: RepairPage(scrollController: scrollController), + ); + }, + ); + } +}