Skip to content

Commit 48d9f7e

Browse files
committed
work on #10 設定動態路由
1 parent 8ef4919 commit 48d9f7e

File tree

11 files changed

+730
-46
lines changed

11 files changed

+730
-46
lines changed

GO_ROUTER_FIX_SUMMARY.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Go Router 返回按鈕修復總結
2+
3+
## 問題描述
4+
5+
用戶報告左上角的"回上一頁"按鈕失效,出現錯誤:
6+
```
7+
GoError: There is nothing to pop
8+
```
9+
10+
## 問題原因
11+
12+
1. **導航方式錯誤**:使用 `context.go()` 會替換當前路由棧,而不是推入新路由,導致沒有歷史記錄可以返回
13+
2. **返回邏輯不安全**:直接使用 `context.pop()` 在沒有歷史記錄時會報錯
14+
15+
## 解決方案
16+
17+
### 1. 修改導航方式 (context.go → context.push)
18+
19+
**修改的文件:**
20+
- `lib/features/home/presentation/screens/home_screen.dart`
21+
- `lib/features/map/map_screen.dart`
22+
- `lib/features/profile/user_detail_screen.dart`
23+
24+
**修改內容:**
25+
```dart
26+
// 舊版本
27+
context.go('/map');
28+
context.go('/user/${user.id}');
29+
30+
// 新版本
31+
context.push('/map');
32+
context.push('/user/${user.id}');
33+
```
34+
35+
### 2. 安全的返回按鈕邏輯
36+
37+
**修改的文件:**
38+
- `lib/features/match/match_screen.dart`
39+
- `lib/features/map/map_screen.dart`
40+
- `lib/features/profile/user_detail_screen.dart`
41+
- `lib/features/qr/presentation/screens/my_qr_screen.dart`
42+
- `lib/features/learning_center/presentation/screens/learning_center_screen.dart`
43+
- `lib/features/quick_practice/presentation/screens/quick_practice_screen.dart`
44+
45+
**新的返回邏輯:**
46+
```dart
47+
IconButton(
48+
icon: const Icon(Icons.arrow_back),
49+
onPressed: () {
50+
if (context.canPop()) {
51+
context.pop();
52+
} else {
53+
context.go('/');
54+
}
55+
},
56+
),
57+
```
58+
59+
### 3. 導入 go_router
60+
61+
為所有修改的頁面添加了 go_router 導入:
62+
```dart
63+
import 'package:go_router/go_router.dart';
64+
```
65+
66+
## 修復效果
67+
68+
**路由歷史正確維護**:使用 `context.push()` 保持導航歷史
69+
**安全的返回邏輯**:檢查是否可以返回,不能則導向首頁
70+
**動態路由正常**`/user/:uid``/map_detail/:latlng` 正常工作
71+
**瀏覽器支援**:支援瀏覽器前進/後退按鈕
72+
**深度連結**:直接訪問 URL 也能正常處理返回
73+
74+
## 測試建議
75+
76+
1. **基本導航**:從首頁點擊各功能按鈕,測試返回
77+
2. **深度連結**:直接在瀏覽器輸入 `/user/123`,測試返回按鈕
78+
3. **瀏覽器按鈕**:測試瀏覽器的前進/後退按鈕
79+
4. **動態路由**:測試地圖詳細頁面和用戶詳細頁面的返回
80+
81+
## 注意事項
82+
83+
- 對話框和底部彈出窗中的 `Navigator.pop()` 保持不變(這些是正確的)
84+
- 只修改了主要頁面的 AppBar 返回按鈕
85+
- 首頁不需要返回按鈕,所以沒有修改

GO_ROUTER_IMPLEMENTATION.md

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# Go Router 動態路由實現總結
2+
3+
## 已實現的功能
4+
5+
### 1. 路由配置 (`lib/core/config/app_router.dart`)
6+
- ✅ 設置 go_router 作為主要路由系統
7+
- ✅ 整合 AuthProvider 進行身份驗證導向
8+
- ✅ 自動重導向邏輯(未登入→登入頁面,已登入→首頁)
9+
10+
### 2. 主要頁面路由
11+
-`/` - 首頁 (HomeScreen)
12+
-`/login` - 登入頁面
13+
-`/map` - 地圖頁面
14+
-`/match` - 配對頁面
15+
-`/social` - 社群頁面
16+
-`/qr` - QR碼頁面
17+
-`/learning` - 學習中心頁面
18+
-`/profile` - 個人資料頁面
19+
20+
### 3. 動態路由
21+
-`/user/:uid` - 用戶詳細頁面
22+
-`/flag/:uid` - 互助旗頁面(用戶詳細頁面的變體)
23+
24+
### 4. 用戶詳細頁面 (`lib/features/profile/user_detail_screen.dart`)
25+
- ✅ 處理 Firebase 資料載入狀態
26+
- ✅ 顯示載入中提示
27+
- ✅ 錯誤處理和重試功能
28+
- ✅ 完整的用戶資訊顯示
29+
- ✅ 支援互助旗模式
30+
- ✅ 動作按鈕(傳送訊息、在地圖上查看)
31+
32+
### 5. 現有頁面的路由更新
33+
- ✅ HomeScreen - 所有導航按鈕改用 `context.go()`
34+
- ✅ MapScreen - 「查看完整資料」改用動態路由 `/user/:uid`
35+
- ✅ MatchScreen - 返回按鈕改用 `context.pop()`
36+
37+
### 6. 主應用程式更新 (`lib/main.dart`)
38+
- ✅ 替換 MaterialApp 為 MaterialApp.router
39+
- ✅ 整合 go_router 配置
40+
41+
## 路由使用方式
42+
43+
### 基本導航
44+
```dart
45+
// 導航到特定頁面
46+
context.go('/map');
47+
context.go('/match');
48+
49+
// 返回上一頁
50+
context.pop();
51+
52+
// 導航到用戶詳細頁面
53+
context.go('/user/userId123');
54+
55+
// 導航到互助旗頁面
56+
context.go('/flag/userId123');
57+
```
58+
59+
### 動態路由參數獲取
60+
```dart
61+
// 在路由定義中
62+
GoRoute(
63+
path: '/user/:uid',
64+
builder: (context, state) {
65+
final uid = state.pathParameters['uid']!;
66+
return UserDetailScreen(uid: uid);
67+
},
68+
)
69+
```
70+
71+
## Firebase 資料載入處理
72+
73+
UserDetailScreen 具備完整的載入狀態管理:
74+
75+
1. **載入中狀態** - 顯示 CircularProgressIndicator
76+
2. **錯誤狀態** - 顯示錯誤訊息和重試按鈕
77+
3. **空資料狀態** - 顯示「找不到用戶資料」
78+
4. **成功狀態** - 顯示完整用戶資訊
79+
80+
## 網址結構
81+
82+
- `https://yourapp.com/` - 首頁
83+
- `https://yourapp.com/map` - 地圖
84+
- `https://yourapp.com/user/ABC123` - 用戶 ABC123 的詳細頁面
85+
- `https://yourapp.com/flag/ABC123` - 用戶 ABC123 的互助旗頁面
86+
87+
## 測試方式
88+
89+
1. 啟動應用程式:`fvm flutter run -d chrome --web-port=8000`
90+
2. 在瀏覽器中訪問不同路由
91+
3. 測試地圖頁面的「查看完整資料」按鈕
92+
4. 驗證動態路由和載入狀態
93+
94+
## 注意事項
95+
96+
- Firebase 資料在載入期間會顯示適當的載入狀態
97+
- 錯誤處理包含重試機制
98+
- 所有導航都使用 go_router 的 API
99+
- 路由配置集中管理,便於維護

lib/core/config/app_router.dart

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:go_router/go_router.dart';
3+
import 'package:provider/provider.dart';
4+
import 'package:auto30_next/features/auth/presentation/providers/auth_provider.dart';
5+
import 'package:auto30_next/features/auth/presentation/screens/login_screen.dart';
6+
import 'package:auto30_next/features/home/presentation/screens/home_screen.dart';
7+
import 'package:auto30_next/features/map/map_screen.dart';
8+
import 'package:auto30_next/features/match/match_screen.dart';
9+
import 'package:auto30_next/features/profile/profile_screen.dart';
10+
import 'package:auto30_next/features/qr/presentation/screens/my_qr_screen.dart';
11+
import 'package:auto30_next/features/social/presentation/screens/social_main_screen.dart';
12+
import 'package:auto30_next/features/learning_center/presentation/screens/learning_center_screen.dart';
13+
import 'package:auto30_next/features/profile/user_detail_screen.dart';
14+
15+
class AppRouter {
16+
static GoRouter createRouter(AuthProvider authProvider) {
17+
return GoRouter(
18+
initialLocation: '/',
19+
refreshListenable: authProvider,
20+
redirect: (context, state) {
21+
final isAuthenticated = authProvider.isAuthenticated;
22+
final isLoggingIn = state.matchedLocation == '/login';
23+
24+
// 如果未登入且不在登入頁面,導向登入頁面
25+
if (!isAuthenticated && !isLoggingIn) {
26+
return '/login';
27+
}
28+
29+
// 如果已登入且在登入頁面,導向首頁
30+
if (isAuthenticated && isLoggingIn) {
31+
return '/';
32+
}
33+
34+
return null; // 不需要重導向
35+
},
36+
routes: [
37+
// 登入頁面
38+
GoRoute(
39+
path: '/login',
40+
name: 'login',
41+
builder: (context, state) => const LoginScreen(),
42+
),
43+
44+
// 首頁
45+
GoRoute(
46+
path: '/',
47+
name: 'home',
48+
builder: (context, state) => const HomeScreen(),
49+
),
50+
51+
// 地圖頁面
52+
GoRoute(
53+
path: '/map',
54+
name: 'map',
55+
builder: (context, state) => const MapScreen(),
56+
),
57+
58+
// 配對頁面
59+
GoRoute(
60+
path: '/match',
61+
name: 'match',
62+
builder: (context, state) => const MatchScreen(),
63+
),
64+
65+
// 社群頁面
66+
GoRoute(
67+
path: '/social',
68+
name: 'social',
69+
builder: (context, state) => const SocialMainScreen(),
70+
),
71+
72+
// QR碼頁面
73+
GoRoute(
74+
path: '/qr',
75+
name: 'qr',
76+
builder: (context, state) => const MyQrScreen(),
77+
),
78+
79+
// 學習中心頁面
80+
GoRoute(
81+
path: '/learning',
82+
name: 'learning',
83+
builder: (context, state) => const LearningCenterScreen(),
84+
),
85+
86+
// 個人資料頁面
87+
GoRoute(
88+
path: '/profile',
89+
name: 'profile',
90+
builder: (context, state) => const ProfileScreen(),
91+
),
92+
93+
// 地圖頁面 - 動態路由
94+
GoRoute(
95+
path: '/map_detail/:latlng',
96+
name: 'map_detail',
97+
builder: (context, state) {
98+
final latlng = state.pathParameters['latlng']!;
99+
return MapScreen(latlng: latlng);
100+
},
101+
),
102+
103+
// 用戶詳細頁面 - 動態路由
104+
GoRoute(
105+
path: '/user/:uid',
106+
name: 'userDetail',
107+
builder: (context, state) {
108+
final uid = state.pathParameters['uid']!;
109+
return UserDetailScreen(uid: uid);
110+
},
111+
),
112+
113+
// 互助旗頁面 - 動態路由
114+
GoRoute(
115+
path: '/flag/:uid',
116+
name: 'flag',
117+
builder: (context, state) {
118+
final uid = state.pathParameters['uid']!;
119+
return UserDetailScreen(uid: uid, showAsFlag: true);
120+
},
121+
),
122+
],
123+
124+
// 錯誤頁面
125+
errorBuilder: (context, state) => Scaffold(
126+
appBar: AppBar(title: const Text('頁面不存在')),
127+
body: Center(
128+
child: Column(
129+
mainAxisAlignment: MainAxisAlignment.center,
130+
children: [
131+
const Icon(Icons.error_outline, size: 64, color: Colors.red),
132+
const SizedBox(height: 16),
133+
Text('找不到頁面: ${state.matchedLocation}'),
134+
const SizedBox(height: 16),
135+
ElevatedButton(
136+
onPressed: () => context.go('/'),
137+
child: const Text('回到首頁'),
138+
),
139+
],
140+
),
141+
),
142+
),
143+
);
144+
}
145+
}

lib/features/home/presentation/screens/home_screen.dart

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'package:flutter/material.dart';
22
import 'package:provider/provider.dart';
3+
import 'package:go_router/go_router.dart';
34
import 'package:auto30_next/features/auth/presentation/providers/auth_provider.dart';
45
import 'package:auto30_next/features/learning_center/presentation/screens/learning_center_screen.dart';
56
import 'package:auto30_next/features/quick_practice/presentation/screens/quick_practice_screen.dart';
@@ -43,9 +44,7 @@ class _HomeAppBar extends StatelessWidget {
4344
IconButton(
4445
icon: const Icon(Icons.qr_code),
4546
onPressed: () {
46-
Navigator.of(context).push(
47-
MaterialPageRoute(builder: (_) => const MyQrScreen()),
48-
);
47+
context.push('/qr');
4948
},
5049
),
5150
_UserMenuButton(),
@@ -197,24 +196,16 @@ class _QuickFeatureCard extends StatelessWidget {
197196
void _onTap() {
198197
switch (title) {
199198
case '附近的人':
200-
Navigator.of(context).push(
201-
MaterialPageRoute(builder: (_) => const MapScreen()),
202-
);
199+
context.push('/map');
203200
break;
204201
case '隨機配對':
205-
Navigator.of(context).push(
206-
MaterialPageRoute(builder: (_) => const MatchScreen()),
207-
);
202+
context.push('/match');
208203
break;
209204
case '我的互助旗':
210-
Navigator.of(context).push(
211-
MaterialPageRoute(builder: (_) => const ProfileScreen()),
212-
);
205+
context.push('/profile');
213206
break;
214207
case '我的QR碼':
215-
Navigator.of(context).push(
216-
MaterialPageRoute(builder: (_) => const MyQrScreen()),
217-
);
208+
context.push('/qr');
218209
break;
219210
}
220211
}

0 commit comments

Comments
 (0)