Files
submanager/lib/screens/main_screen.dart
JiWoong Sul 4731288622 Major UI/UX and architecture improvements
- Implemented new navigation system with NavigationProvider and route management
- Added adaptive theme system with ThemeProvider for better theme handling
- Introduced glassmorphism design elements (app bars, scaffolds, cards)
- Added advanced animations (spring animations, page transitions, staggered lists)
- Implemented performance optimizations (memory manager, lazy loading)
- Refactored Analysis screen into modular components
- Added floating navigation bar with haptic feedback
- Improved subscription cards with swipe actions
- Enhanced skeleton loading with better animations
- Added cached network image support
- Improved overall app architecture and code organization

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-10 18:36:57 +09:00

259 lines
8.2 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import '../providers/subscription_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/glassmorphic_app_bar.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;
bool _isNavBarVisible = true;
// 화면 목록
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: () => setState(() => _isNavBarVisible = false),
onShow: () => setState(() => _isNavBarVisible = true),
);
// 화면 목록 초기화
_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) {
// 상단에 스낵바 표시
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Row(
children: [
const Icon(
Icons.check_circle,
color: Colors.white,
size: 20,
),
const SizedBox(width: 12),
const 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,
);
}
}