- Extract business logic from screens into dedicated controllers - Split large screen files into smaller, reusable widget components - Add controllers for AddSubscriptionScreen and DetailScreen - Create modular widgets for subscription and detail features - Improve code organization and maintainability - Remove duplicated code and improve reusability 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
257 lines
8.0 KiB
Dart
257 lines
8.0 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';
|
|
|
|
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: () {},
|
|
);
|
|
|
|
// 화면 목록 초기화
|
|
_screens = [
|
|
HomeContent(
|
|
fadeController: _fadeController,
|
|
rotateController: _rotateController,
|
|
slideController: _slideController,
|
|
pulseController: _pulseController,
|
|
waveController: _waveController,
|
|
scrollController: _scrollController,
|
|
onAddPressed: () => _navigateToAddSubscription(context),
|
|
),
|
|
const AnalysisScreen(),
|
|
Container(), // 추가 버튼은 별도 처리
|
|
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: const Row(
|
|
children: [
|
|
Icon(
|
|
Icons.check_circle,
|
|
color: Colors.white,
|
|
size: 20,
|
|
),
|
|
SizedBox(width: 12),
|
|
Text(
|
|
'구독이 추가되었습니다',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
backgroundColor: const Color(0xFF10B981), // 초록색
|
|
behavior: SnackBarBehavior.floating,
|
|
margin: EdgeInsets.only(
|
|
top: MediaQuery.of(context).padding.top + 16, // 상단 여백
|
|
left: 16,
|
|
right: 16,
|
|
bottom: MediaQuery.of(context).size.height - 120, // 상단에 위치하도록 bottom 마진 설정
|
|
),
|
|
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>();
|
|
final hour = DateTime.now().hour;
|
|
List<Color> backgroundGradient;
|
|
|
|
// 시간대별 배경 그라디언트 설정
|
|
if (hour >= 6 && hour < 10) {
|
|
backgroundGradient = AppColors.morningGradient;
|
|
} else if (hour >= 10 && hour < 17) {
|
|
backgroundGradient = AppColors.dayGradient;
|
|
} else if (hour >= 17 && hour < 20) {
|
|
backgroundGradient = AppColors.eveningGradient;
|
|
} else {
|
|
backgroundGradient = AppColors.nightGradient;
|
|
}
|
|
|
|
// 현재 인덱스가 유효한지 확인
|
|
int currentIndex = navigationProvider.currentIndex;
|
|
if (currentIndex == 2) {
|
|
currentIndex = 0; // 추가 버튼은 홈으로 표시
|
|
}
|
|
|
|
return GlassmorphicScaffold(
|
|
body: IndexedStack(
|
|
index: currentIndex == 3 ? 3 : currentIndex == 4 ? 4 : currentIndex,
|
|
children: _screens,
|
|
),
|
|
backgroundGradient: backgroundGradient,
|
|
useFloatingNavBar: true,
|
|
floatingNavBarIndex: navigationProvider.currentIndex,
|
|
onFloatingNavBarTapped: (index) {
|
|
_handleNavigation(index, context);
|
|
},
|
|
enableParticles: false,
|
|
enableWaveAnimation: false,
|
|
);
|
|
}
|
|
} |