Files
submanager/lib/screens/main_screen.dart
JiWoong Sul a9a715d67c feat: SMS 스캔 패키지를 flutter_sms_inbox로 변경 및 플랫폼별 최적화
- telephony 패키지를 flutter_sms_inbox로 교체
- 플랫폼별 SMS 스캔 로직 구현:
  * Web: mock data 사용
  * Android: flutter_sms_inbox로 실제 SMS 스캔
  * iOS: SMS 기능 비활성화
- iOS에서 SMS 스캔 버튼 숨김 처리
- PlatformHelper 유틸리티 추가로 웹 환경 오류 해결
- Android 네이티브 MethodChannel 코드 제거
2025-07-17 18:30:21 +09:00

252 lines
7.9 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import '../providers/app_lock_provider.dart';
import '../providers/navigation_provider.dart';
import '../theme/app_colors.dart';
import '../routes/app_routes.dart';
import 'analysis_screen.dart';
import 'app_lock_screen.dart';
import 'settings_screen.dart';
import 'sms_scan_screen.dart';
import '../utils/animation_controller_helper.dart';
import '../widgets/floating_navigation_bar.dart';
import '../widgets/glassmorphic_scaffold.dart';
import '../widgets/home_content.dart';
import '../l10n/app_localizations.dart';
import '../utils/platform_helper.dart';
class MainScreen extends StatefulWidget {
const MainScreen({super.key});
@override
State<MainScreen> createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen>
with WidgetsBindingObserver, TickerProviderStateMixin {
late AnimationController _fadeController;
late AnimationController _scaleController;
late AnimationController _rotateController;
late AnimationController _slideController;
late AnimationController _pulseController;
late AnimationController _waveController;
late ScrollController _scrollController;
late FloatingNavBarScrollController _navBarScrollController;
// 화면 목록
late final List<Widget> _screens;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
_checkAppLock();
// 애니메이션 컨트롤러 초기화
_fadeController = AnimationController(vsync: this);
_scaleController = AnimationController(vsync: this);
_rotateController = AnimationController(vsync: this);
_slideController = AnimationController(vsync: this);
_pulseController = AnimationController(vsync: this);
_waveController = AnimationController(vsync: this);
// 헬퍼 클래스를 사용해 애니메이션 컨트롤러 초기화
AnimationControllerHelper.initControllers(
vsync: this,
fadeController: _fadeController,
scaleController: _scaleController,
rotateController: _rotateController,
slideController: _slideController,
pulseController: _pulseController,
waveController: _waveController,
);
_scrollController = ScrollController();
_navBarScrollController = FloatingNavBarScrollController(
scrollController: _scrollController,
onHide: () {},
onShow: () {},
);
// 화면 목록 초기화 (iOS에서는 SMS 스캔 제외)
_screens = [
HomeContent(
fadeController: _fadeController,
rotateController: _rotateController,
slideController: _slideController,
pulseController: _pulseController,
waveController: _waveController,
scrollController: _scrollController,
onAddPressed: () => _navigateToAddSubscription(context),
),
const AnalysisScreen(),
Container(), // 추가 버튼은 별도 처리
if (!PlatformHelper.isIOS) const SmsScanScreen(),
const SettingsScreen(),
];
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
// 헬퍼 클래스를 사용해 애니메이션 컨트롤러 해제
AnimationControllerHelper.disposeControllers(
fadeController: _fadeController,
scaleController: _scaleController,
rotateController: _rotateController,
slideController: _slideController,
pulseController: _pulseController,
waveController: _waveController,
);
_scrollController.dispose();
_navBarScrollController.dispose();
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.paused) {
// 앱이 백그라운드로 갈 때
final appLockProvider = context.read<AppLockProvider>();
if (appLockProvider.isBiometricEnabled) {
appLockProvider.lock();
}
} else if (state == AppLifecycleState.resumed) {
// 앱이 포그라운드로 돌아올 때
_checkAppLock();
_resetAnimations();
}
}
void _resetAnimations() {
AnimationControllerHelper.resetAnimations(
fadeController: _fadeController,
scaleController: _scaleController,
slideController: _slideController,
pulseController: _pulseController,
waveController: _waveController,
);
}
Future<void> _checkAppLock() async {
final appLockProvider = context.read<AppLockProvider>();
if (appLockProvider.isLocked) {
await Navigator.of(context).push(
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) =>
const AppLockScreen(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: animation,
child: child,
);
},
),
);
}
}
void _navigateToAddSubscription(BuildContext context) {
HapticFeedback.mediumImpact();
Navigator.pushNamed(
context,
AppRoutes.addSubscription,
).then((result) {
_resetAnimations();
// 구독이 성공적으로 추가된 경우
if (result == true) {
// 상단에 스낵바 표시
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Row(
children: [
const Icon(
Icons.check_circle,
color: AppColors.pureWhite,
size: 20,
),
const SizedBox(width: 12),
Text(
AppLocalizations.of(context).subscriptionAdded,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: AppColors.pureWhite,
),
),
],
),
backgroundColor: AppColors.successColor,
behavior: SnackBarBehavior.floating,
margin: EdgeInsets.only(
top: MediaQuery.of(context).padding.top + 8, // 더 상단으로
left: 16,
right: 16,
bottom: MediaQuery.of(context).size.height - 100, // 더 상단으로
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
duration: const Duration(seconds: 3),
dismissDirection: DismissDirection.horizontal,
),
);
}
});
}
void _handleNavigation(int index, BuildContext context) {
final navigationProvider = context.read<NavigationProvider>();
// 이미 같은 인덱스면 무시
if (navigationProvider.currentIndex == index) {
return;
}
// 추가 버튼은 별도 처리
if (index == 2) {
_navigateToAddSubscription(context);
return;
}
// 인덱스 업데이트
navigationProvider.updateCurrentIndex(index);
}
@override
Widget build(BuildContext context) {
final navigationProvider = context.watch<NavigationProvider>();
// 메인 그라데이션 사용
List<Color> backgroundGradient = AppColors.mainGradient;
// 현재 인덱스가 유효한지 확인
int currentIndex = navigationProvider.currentIndex;
if (currentIndex == 2) {
currentIndex = 0; // 추가 버튼은 홈으로 표시
}
return GlassmorphicScaffold(
body: IndexedStack(
index: PlatformHelper.isIOS
? (currentIndex == 3 ? 3 : currentIndex) // iOS: 설정화면은 인덱스 3
: (currentIndex == 3 ? 3 : currentIndex == 4 ? 4 : currentIndex), // Android: 기존 로직
children: _screens,
),
backgroundGradient: backgroundGradient,
useFloatingNavBar: true,
floatingNavBarIndex: navigationProvider.currentIndex,
onFloatingNavBarTapped: (index) {
_handleNavigation(index, context);
},
enableParticles: false,
enableWaveAnimation: false,
);
}
}