363 lines
12 KiB
Dart
363 lines
12 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'dart:async';
|
|
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, _) {
|
|
// 앱 시작 시 인증 서비스 초기화 (타이머 생성 회피: microtask 사용)
|
|
Future.microtask(() {
|
|
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) {
|
|
// 클럽이 여러 개 있으면 선택 다이얼로그 표시 (타이머 생성 회피: microtask 사용)
|
|
Future.microtask(() {
|
|
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,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
|