Files
submanager/lib/main.dart
JiWoong Sul d37f66d526 feat(settings): SMS 읽기 권한 상태/요청 위젯 추가 (Android)
- 설정 화면에 SMS 권한 카드 추가: 상태 표시(허용/미허용/영구 거부), 권한 요청/설정 이동 지원\n- 기존 알림 권한 카드 스타일과 일관성 유지

feat(permissions): 최초 실행 시 SMS 권한 온보딩 화면 추가 및 Splash에서 라우팅 (Android)

- 권한 필요 이유/수집 범위 현지화 문구 추가\n- 거부/영구거부 케이스 처리 및 설정 이동

chore(codex): AGENTS.md/체크 스크립트/CI/프롬프트 템플릿 추가

- AGENTS.md, scripts/check.sh, scripts/fix.sh, .github/workflows/flutter_ci.yml, .claude/agents/codex.md, 문서 템플릿 추가

refactor(logging): 경로별 print 제거 후 경량 로거(Log) 도입

- SMS 스캐너/컨트롤러, URL 매처, 데이터 리포지토리, 내비게이션, 메모리/성능 유틸 등 핵심 경로 치환

feat(exchange): 환율 API URL을 --dart-define로 오버라이드 가능 + 폴백 로깅 강화

test: URL 매처/환율 스모크 테스트 추가

chore(android): RECEIVE_SMS 권한 제거 (READ_SMS만 유지)

fix(lints): dart fix + 수동 정리로 경고 대폭 감소, 비동기 context(mounted) 보강

fix(deprecations):\n- flutter_local_notifications의 androidAllowWhileIdle → androidScheduleMode 전환\n- WillPopScope → PopScope 교체

i18n: SMS 권한 온보딩/설정 문구 현지화 키 추가
2025-09-07 21:32:16 +09:00

170 lines
6.0 KiB
Dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:flutter/foundation.dart' show kIsWeb, kDebugMode;
import 'package:flutter_localizations/flutter_localizations.dart';
import 'models/subscription_model.dart';
import 'models/category_model.dart';
import 'providers/subscription_provider.dart';
import 'providers/app_lock_provider.dart';
import 'providers/notification_provider.dart';
import 'providers/navigation_provider.dart';
import 'services/notification_service.dart';
import 'providers/category_provider.dart';
import 'providers/locale_provider.dart';
import 'providers/theme_provider.dart';
import 'l10n/app_localizations.dart';
import 'theme/adaptive_theme.dart';
import 'routes/app_routes.dart';
import 'navigation/app_navigation_observer.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'dart:io' show Platform;
import 'dart:async' show unawaited;
import 'utils/memory_manager.dart';
import 'utils/logger.dart';
import 'utils/performance_optimizer.dart';
import 'navigator_key.dart';
// AdMob 활성화 플래그 (개발 중 false, 프로덕션 시 true로 변경)
const bool enableAdMob = true;
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
// 구글 모바일 광고 SDK 초기화 (웹이 아니고, Android/iOS에서만)
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS) && enableAdMob) {
unawaited(MobileAds.instance.initialize());
}
// 성능 최적화 설정
MemoryManager.optimizeImageCache();
MemoryManager().startAutoCleanup();
// 앱 시작 시 이미지 캐시 관리
try {
// 메모리 이미지 캐시는 유지하지만 필요한 경우 삭제할 수 있도록 준비
// 오래된 디스크 캐시 파일만 지우기 (새로운 것은 유지)
await DefaultCacheManager().emptyCache();
if (kDebugMode) {
Log.d('이미지 캐시 관리 초기화 완료');
PerformanceOptimizer.checkConstOptimization();
}
} catch (e) {
if (kDebugMode) {
Log.e('캐시 초기화 오류', e);
}
}
// Hive 초기화
await Hive.initFlutter();
Hive.registerAdapter(SubscriptionModelAdapter());
Hive.registerAdapter(CategoryModelAdapter());
await Hive.openBox<SubscriptionModel>('subscriptions');
await Hive.openBox<CategoryModel>('categories');
final appLockBox = await Hive.openBox<bool>('app_lock');
// 알림 서비스를 가장 먼저 초기화
await NotificationService.init();
final subscriptionProvider = SubscriptionProvider();
final categoryProvider = CategoryProvider();
final localeProvider = LocaleProvider();
final notificationProvider = NotificationProvider();
final themeProvider = ThemeProvider();
final navigationProvider = NavigationProvider();
await subscriptionProvider.init();
await categoryProvider.init();
await localeProvider.init();
await notificationProvider.init();
await themeProvider.initialize();
// NotificationProvider에 SubscriptionProvider 연결 (알림 재예약용)
// SRP 원칙에 따라 다른 Provider 객체를 명시적으로 주입
notificationProvider.setSubscriptionProvider(subscriptionProvider);
// 별도의 비동기 함수로 알림 관련 초기화 오류 처리
Future.delayed(Duration.zero, () {
try {
if (notificationProvider.isPaymentEnabled) {
// 백그라운드에서 비동기적으로 알림 설정 업데이트
NotificationService.reschedulAllNotifications(
subscriptionProvider.subscriptions);
}
} catch (e) {
debugPrint('알림 초기 설정 중 오류 발생: $e');
}
});
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => subscriptionProvider),
ChangeNotifierProvider(create: (_) => categoryProvider),
ChangeNotifierProvider(create: (_) => AppLockProvider(appLockBox)),
ChangeNotifierProvider(create: (_) => notificationProvider),
ChangeNotifierProvider(create: (_) => localeProvider),
ChangeNotifierProvider(create: (_) => themeProvider),
ChangeNotifierProvider(create: (_) => navigationProvider),
],
child: const SubManagerApp(),
),
);
}
class SubManagerApp extends StatelessWidget {
const SubManagerApp({super.key});
@override
Widget build(BuildContext context) {
return Consumer2<LocaleProvider, ThemeProvider>(
builder: (context, localeProvider, themeProvider, child) {
// 시스템 UI 오버레이 스타일 적용
AdaptiveTheme.applySystemUIOverlay(context);
return MaterialApp(
key: ValueKey(localeProvider.locale),
title: 'Digital Rent Manager',
debugShowCheckedModeBanner: false,
theme: themeProvider.getTheme(context),
locale: localeProvider.locale,
localizationsDelegates: const [
AppLocalizationsDelegate(),
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [
Locale('en'),
Locale('ko'),
Locale('ja'),
Locale('zh'),
],
navigatorKey: navigatorKey,
navigatorObservers: [AppNavigationObserver()],
initialRoute: AppRoutes.splash,
routes: AppRoutes.getRoutes(),
onGenerateRoute: AppRoutes.generateRoute,
builder: (context, child) {
// 성능 최적화 및 메모리 관리
if (kDebugMode) {
PerformanceOptimizer().startFrameMonitoring();
}
return MediaQuery(
data: MediaQuery.of(context).copyWith(
textScaler:
TextScaler.linear(themeProvider.largeText ? 1.2 : 1.0),
disableAnimations: themeProvider.reduceMotion,
),
child: child!,
);
},
);
},
);
}
}