Files
bowlingManager/mobile/lib/main.dart
T
2025-08-09 03:09:17 +09:00

362 lines
12 KiB
Dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'services/auth_service.dart';
import 'services/club_service.dart';
import 'services/member_service.dart';
import 'services/event_service.dart';
import 'services/score_service.dart';
import 'services/notification_service.dart';
import 'screens/auth/login_screen.dart';
import 'screens/auth/profile_screen.dart';
import 'screens/club/dashboard_screen.dart';
import 'screens/club/members_screen.dart';
import 'screens/club/events_screen.dart';
import 'screens/club/club_settings_screen.dart';
import 'screens/score/club_statistics_screen.dart';
// 전역 네비게이터 키 (인증 오류 처리용)
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 알림 서비스 초기화
final notificationService = NotificationService();
await notificationService.initialize();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => AuthService()),
ChangeNotifierProvider(create: (_) => ClubService()),
ChangeNotifierProvider(create: (_) => MemberService()),
ChangeNotifierProvider(create: (_) => EventService()),
ChangeNotifierProvider(create: (_) => ScoreService()),
],
child: Consumer<AuthService>(
builder: (context, authService, _) {
// 앱 시작 시 인증 서비스 초기화
Future.delayed(Duration.zero, () {
if (!authService.isInitialized) {
authService.initialize();
}
});
return MaterialApp(
navigatorKey: navigatorKey, // 인증 오류 처리를 위한 전역 네비게이터 키
title: '볼링 매니저',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
appBarTheme: const AppBarTheme(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
),
),
// 로케일 설정 추가
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [
Locale('ko', 'KR'),
Locale('en', 'US'),
],
locale: const Locale('ko', 'KR'),
home: authService.isAuthenticated ? const HomeScreen() : const LoginScreen(),
routes: {
'/login': (context) => const LoginScreen(),
'/profile': (context) => const ProfileScreen(),
ClubSettingsScreen.routeName: (context) => const ClubSettingsScreen(),
},
);
},
),
);
}
}
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
int _selectedIndex = 0;
bool _isInit = false;
static final List<Widget> _widgetOptions = <Widget>[
const DashboardScreen(),
const MembersScreen(),
const EventsScreen(),
const ClubStatisticsScreen(),
const ProfileScreen(),
];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
// 클럽 선택 다이얼로그 표시
Future<void> _showClubSelectionDialog(BuildContext context) async {
final clubService = Provider.of<ClubService>(context, listen: false);
final authService = Provider.of<AuthService>(context, listen: false);
// 사용자의 모든 클럽 가져오기
if (clubService.clubs.isEmpty) {
try {
await clubService.fetchUserClubs();
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('클럽 목록을 불러오는데 실패했습니다: $e')),
);
return;
}
}
if (clubService.clubs.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('소속된 클럽이 없습니다')),
);
return;
}
if (!context.mounted) return;
// 다이얼로그 표시
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('클럽 선택'),
content: SizedBox(
width: double.maxFinite,
child: ListView.builder(
shrinkWrap: true,
itemCount: clubService.clubs.length,
itemBuilder: (context, index) {
final club = clubService.clubs[index];
final isSelected = clubService.currentClub?.id == club.id;
return ListTile(
title: Text(club.name),
subtitle: Text(club.description ?? ''),
trailing: isSelected ? const Icon(Icons.check, color: Colors.blue) : null,
onTap: () async {
Navigator.of(context).pop();
if (!isSelected) {
try {
// 백엔드 세션에 clubId 저장하고 클럽 정보 가져오기
await clubService.selectClub(club.id);
// 관련 서비스 초기화
final memberService = Provider.of<MemberService>(context, listen: false);
final eventService = Provider.of<EventService>(context, listen: false);
final scoreService = Provider.of<ScoreService>(context, listen: false);
memberService.initialize(authService.token!);
eventService.initialize(authService.token!);
scoreService.initialize(authService.token!);
// 데이터 다시 로드
await Future.wait([
memberService.fetchClubMembers(),
eventService.fetchClubEvents(),
]);
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('${club.name} 클럽으로 변경되었습니다')),
);
}
} catch (e) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('클럽 변경 중 오류가 발생했습니다: $e')),
);
}
}
}
},
);
},
),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('취소'),
),
],
);
},
);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (!_isInit) {
_initializeServices();
_isInit = true;
}
}
Future<void> _initializeServices() async {
try {
final authService = Provider.of<AuthService>(context, listen: false);
final clubService = Provider.of<ClubService>(context, listen: false);
final scoreService = Provider.of<ScoreService>(context, listen: false);
if (authService.isAuthenticated && authService.token != null) {
// 클럽 서비스 초기화
await clubService.initialize(authService.token!);
// 클럽이 선택된 경우 점수 서비스 초기화
if (clubService.currentClub != null) {
scoreService.initialize(authService.token!);
} else {
// 현재 선택된 클럽이 없는 경우
// 사용자의 클럽 목록 가져오기
await clubService.fetchUserClubs();
// 클럽이 하나만 있으면 자동으로 선택
if (clubService.clubs.length == 1) {
try {
await clubService.selectClub(clubService.clubs[0].id);
scoreService.initialize(authService.token!);
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('${clubService.clubs[0].name} 클럽이 자동으로 선택되었습니다')),
);
}
} catch (e) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('클럽 자동 선택 중 오류가 발생했습니다: $e')),
);
}
}
} else if (clubService.clubs.isNotEmpty) {
// 클럽이 여러 개 있으면 선택 다이얼로그 표시
Future.delayed(Duration.zero, () {
if (context.mounted) {
_showClubSelectionDialog(context);
}
});
} else {
// 클럽이 없는 경우 메시지 표시
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('소속된 클럽이 없습니다. 클럽을 생성하거나 초대를 요청하세요.')),
);
}
}
}
}
} catch (e) {
// 오류 처리
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('서비스 초기화 중 오류가 발생했습니다: $e')),
);
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Consumer<ClubService>(
builder: (context, clubService, child) {
return InkWell(
onTap: () => _showClubSelectionDialog(context),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
clubService.currentClub?.name ?? '볼링 매니저',
style: const TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(width: 4),
const Icon(Icons.arrow_drop_down, size: 20),
],
),
);
},
),
actions: [
IconButton(
icon: const Icon(Icons.notifications),
onPressed: () async {
// 알림 권한 요청
final notificationService = NotificationService();
final hasPermission = await notificationService.requestPermission();
if (context.mounted) {
if (hasPermission) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('알림 권한이 허용되었습니다')),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('알림 권한이 거부되었습니다. 설정에서 권한을 허용해주세요')),
);
}
}
},
),
],
),
body: _widgetOptions.elementAt(_selectedIndex),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.dashboard),
label: '대시보드',
),
BottomNavigationBarItem(
icon: Icon(Icons.people),
label: '회원',
),
BottomNavigationBarItem(
icon: Icon(Icons.event),
label: '이벤트',
),
BottomNavigationBarItem(
icon: Icon(Icons.score),
label: '통계',
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
label: '프로필',
),
],
currentIndex: _selectedIndex,
selectedItemColor: Colors.blue,
onTap: _onItemTapped,
),
);
}
}