258 lines
7.6 KiB
Dart
Raw Normal View History

2025-10-24 20:06:42 +07:00
import 'package:auto_route/auto_route.dart';
2025-10-24 20:25:30 +07:00
import 'package:flutter/material.dart';
2025-10-24 23:20:41 +07:00
import 'package:flutter_bloc/flutter_bloc.dart';
2025-10-24 20:25:30 +07:00
2025-10-24 23:20:41 +07:00
import '../../../application/sync/sync_bloc.dart';
2025-10-24 20:25:30 +07:00
import '../../../common/extension/extension.dart';
import '../../../common/theme/theme.dart';
2025-10-24 23:20:41 +07:00
import '../../../injection.dart';
import '../../components/button/button.dart';
2025-10-24 20:25:30 +07:00
import '../../components/spaces/space.dart';
2025-10-24 23:20:41 +07:00
import '../../router/app_router.gr.dart';
import 'widgets/sync_completed.dart';
import 'widgets/sync_initial.dart';
import 'widgets/sync_state.dart';
2025-10-24 20:06:42 +07:00
@RoutePage()
2025-10-24 23:20:41 +07:00
class SyncPage extends StatefulWidget implements AutoRouteWrapper {
2025-10-24 20:06:42 +07:00
const SyncPage({super.key});
2025-10-24 23:20:41 +07:00
@override
State<SyncPage> createState() => _SyncPageState();
@override
2025-10-24 23:23:58 +07:00
Widget wrappedRoute(BuildContext context) => BlocProvider(
create: (context) => getIt<SyncBloc>()..add(SyncEvent.startSync()),
child: this,
);
2025-10-24 23:20:41 +07:00
}
class _SyncPageState extends State<SyncPage> with TickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _progressAnimation;
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: Duration(milliseconds: 500),
vsync: this,
);
_progressAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
);
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
2025-10-24 20:06:42 +07:00
@override
Widget build(BuildContext context) {
2025-10-24 20:25:30 +07:00
return Scaffold(
backgroundColor: AppColor.background,
body: SafeArea(
2025-10-24 23:20:41 +07:00
child: BlocConsumer<SyncBloc, SyncState>(
listener: (context, state) {
// Kalau lagi syncing, update progress animasi
if (state.isSyncing) {
_animationController.animateTo(state.progress);
}
// Kalau sudah selesai sukses
else if (state.stats != null && state.errorMessage == null) {
_animationController.animateTo(1.0);
// Tunggu sebentar lalu pindah ke dashboard
2025-10-24 23:23:58 +07:00
Future.delayed(const Duration(seconds: 2), () {
context.router.replace(MainRoute());
});
2025-10-24 23:20:41 +07:00
}
// Kalau error
else if (state.errorMessage != null) {
_animationController.stop();
}
},
builder: (context, state) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 32, vertical: 16),
child: Row(
children: [
Expanded(
flex: 2,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildHeader(),
SizedBox(height: 20),
_buildActions(state),
],
),
),
SpaceWidth(40),
SizedBox(width: 40),
Expanded(
flex: 3,
child: SizedBox(
height: context.deviceHeight * 0.8,
child: Builder(
builder: (context) {
// Kondisi 1: error
if (state.errorMessage != null) {
return _buildErrorState(state.errorMessage!);
}
// Kondisi 2: sudah selesai
if (state.stats != null) {
return SyncCompletedWidget(stats: state.stats!);
}
// Kondisi 3: sedang syncing
if (state.isSyncing) {
return SyncStateWidget(
step: state.currentStep ?? SyncStep.categories,
progress: state.progress,
message: state.errorMessage ?? '',
progressAnimation: _progressAnimation,
);
}
// Kondisi default: initial
return SyncInitialWidget();
},
),
),
),
],
2025-10-24 20:25:30 +07:00
),
2025-10-24 23:20:41 +07:00
);
},
2025-10-24 20:25:30 +07:00
),
),
);
}
2025-10-24 23:20:41 +07:00
Widget _buildErrorState(String message) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error_outline, size: 48, color: Colors.red.shade400),
SizedBox(height: 12),
Text(
'Sinkronisasi Gagal',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.red.shade600,
),
),
SizedBox(height: 8),
Container(
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.red.shade50,
borderRadius: BorderRadius.circular(8),
),
child: Text(
message,
style: TextStyle(fontSize: 12, color: Colors.red.shade700),
textAlign: TextAlign.center,
),
),
SizedBox(height: 12),
Text(
'Periksa koneksi internet dan coba lagi',
style: TextStyle(fontSize: 12, color: Colors.grey.shade600),
textAlign: TextAlign.center,
),
],
);
}
2025-10-24 20:25:30 +07:00
Widget _buildHeader() {
return Column(
children: [
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: AppColor.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(15),
),
child: Icon(Icons.sync, size: 30, color: AppColor.primary),
),
SizedBox(height: 12),
Text(
'Sinkronisasi Data',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.grey.shade800,
),
),
SizedBox(height: 8),
Text(
'Mengunduh kategori dan produk terbaru',
style: TextStyle(fontSize: 16, color: Colors.grey.shade600),
textAlign: TextAlign.center,
),
],
);
2025-10-24 20:06:42 +07:00
}
2025-10-24 23:20:41 +07:00
Widget _buildActions(SyncState state) {
if (state.isSyncing) {
return AppElevatedButton.outlined(
onPressed: () {
context.read<SyncBloc>().add(const SyncEvent.cancelSync());
},
label: 'Batalkan',
);
}
// Completed state
if (state.stats != null && state.errorMessage == null) {
return AppElevatedButton.filled(
onPressed: () {
context.router.replace(MainRoute());
},
label: 'Lanjutkan ke Aplikasi',
);
}
// Error state
if (state.errorMessage != null) {
return Row(
children: [
Expanded(
child: AppElevatedButton.outlined(
onPressed: () {
context.router.replace(MainRoute());
},
label: 'Lewati',
),
),
const SizedBox(width: 16),
Expanded(
child: AppElevatedButton.filled(
onPressed: () {
context.read<SyncBloc>().add(const SyncEvent.startSync());
},
label: 'Coba Lagi',
),
),
],
);
}
// Default (initial)
return AppElevatedButton.filled(
onPressed: () {
context.read<SyncBloc>().add(const SyncEvent.startSync());
},
label: 'Mulai Sinkronisasi',
);
}
2025-10-24 20:06:42 +07:00
}