POLISHING STUFF and Smarter AI, and alot i mean.. ALOT OF BUG FIX AND OPTIMIZE
This commit is contained in:
+214
-167
@@ -8,6 +8,9 @@ import '../../app/injection_container.dart';
|
||||
import '../../core/errors/friendly_error.dart';
|
||||
import '../../core/network/api_client.dart';
|
||||
import '../../core/theme/app_colors.dart';
|
||||
import '../../core/theme/app_decorations.dart';
|
||||
import '../../core/theme/app_text_styles.dart';
|
||||
import '../../shared/widgets/animations/animations.dart';
|
||||
|
||||
Dio get _api => sl<ApiClient>().dio;
|
||||
|
||||
@@ -103,91 +106,114 @@ class _ActivityLogScreenState extends State<ActivityLogScreen> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Header
|
||||
Row(
|
||||
return DecoratedBox(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [AppColors.softBlueBg, Colors.white],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: FadeSlideWrapper(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Activity Log',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headlineSmall
|
||||
?.copyWith(fontWeight: FontWeight.w800),
|
||||
// Header
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Activity Log',
|
||||
style: AppTextStyles.heading,
|
||||
),
|
||||
Text(
|
||||
'${_items.length} aktivitas tercatat',
|
||||
style: const TextStyle(color: AppColors.muted),
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
'${_items.length} aktivitas tercatat',
|
||||
style: const TextStyle(color: AppColors.muted),
|
||||
),
|
||||
],
|
||||
),
|
||||
IconButton(
|
||||
onPressed: _load,
|
||||
icon: const Icon(Icons.refresh),
|
||||
tooltip: 'Refresh',
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Filter chips
|
||||
SizedBox(
|
||||
height: 36,
|
||||
child: ListView.separated(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: _filters.length,
|
||||
separatorBuilder: (_, __) => const SizedBox(width: 8),
|
||||
itemBuilder: (_, i) {
|
||||
final f = _filters[i];
|
||||
final selected = _selectedFilter == f;
|
||||
return FilterChip(
|
||||
label: Text(f),
|
||||
selected: selected,
|
||||
onSelected: (_) {
|
||||
setState(() => _applyFilter(f));
|
||||
},
|
||||
selectedColor:
|
||||
AppColors.primary.withValues(alpha: 0.15),
|
||||
backgroundColor: AppColors.cardWhite,
|
||||
side: BorderSide(
|
||||
color: selected
|
||||
? AppColors.primary.withValues(alpha: 0.4)
|
||||
: AppColors.border,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
),
|
||||
checkmarkColor: AppColors.primary,
|
||||
labelStyle: TextStyle(
|
||||
color: selected ? AppColors.primary : AppColors.muted,
|
||||
fontWeight:
|
||||
selected ? FontWeight.w700 : FontWeight.normal,
|
||||
fontSize: 12,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: _load,
|
||||
icon: const Icon(Icons.refresh),
|
||||
tooltip: 'Refresh',
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Body
|
||||
Expanded(
|
||||
child: _loading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: _error != null
|
||||
? _ErrorPanel(message: _error!, onRetry: _load)
|
||||
: _filtered.isEmpty
|
||||
? _EmptyPanel(filter: _selectedFilter)
|
||||
: RefreshIndicator(
|
||||
onRefresh: _load,
|
||||
child: ListView(
|
||||
children: [
|
||||
StaggerWrapper(
|
||||
children: [
|
||||
for (final item in _filtered)
|
||||
_LogCard(item: item),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Filter chips
|
||||
SizedBox(
|
||||
height: 36,
|
||||
child: ListView.separated(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: _filters.length,
|
||||
separatorBuilder: (_, __) => const SizedBox(width: 8),
|
||||
itemBuilder: (_, i) {
|
||||
final f = _filters[i];
|
||||
final selected = _selectedFilter == f;
|
||||
return FilterChip(
|
||||
label: Text(f),
|
||||
selected: selected,
|
||||
onSelected: (_) {
|
||||
setState(() => _applyFilter(f));
|
||||
},
|
||||
selectedColor: AppColors.primary.withValues(alpha: 0.15),
|
||||
checkmarkColor: AppColors.primary,
|
||||
labelStyle: TextStyle(
|
||||
color: selected ? AppColors.primary : AppColors.muted,
|
||||
fontWeight:
|
||||
selected ? FontWeight.w700 : FontWeight.normal,
|
||||
fontSize: 12,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Body
|
||||
Expanded(
|
||||
child: _loading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: _error != null
|
||||
? _ErrorPanel(message: _error!, onRetry: _load)
|
||||
: _filtered.isEmpty
|
||||
? _EmptyPanel(filter: _selectedFilter)
|
||||
: RefreshIndicator(
|
||||
onRefresh: _load,
|
||||
child: ListView.builder(
|
||||
itemCount: _filtered.length,
|
||||
itemBuilder: (ctx, i) =>
|
||||
_LogCard(item: _filtered[i]),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -228,71 +254,76 @@ class _LogCard extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final meta = _logMeta(item.logType);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Timeline dot + line
|
||||
Column(
|
||||
children: [
|
||||
Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: meta.color.withValues(alpha: 0.12),
|
||||
shape: BoxShape.circle,
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.cardWhite,
|
||||
borderRadius: AppDecorations.cardRadius,
|
||||
border: Border.all(color: AppColors.border),
|
||||
boxShadow: AppDecorations.cardShadow,
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Timeline dot + line
|
||||
Column(
|
||||
children: [
|
||||
Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: meta.color.withValues(alpha: 0.12),
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
),
|
||||
child: Icon(meta.icon, color: meta.color, size: 18),
|
||||
),
|
||||
child: Icon(meta.icon, color: meta.color, size: 18),
|
||||
),
|
||||
Container(
|
||||
width: 1.5,
|
||||
height: 20,
|
||||
color: const Color(0xFFE2E8F0),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
// Content
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
meta.label,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: meta.color,
|
||||
fontSize: 13,
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
// Content
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
meta.label,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: meta.color,
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
_formatTime(item.createdAt),
|
||||
style: const TextStyle(
|
||||
color: AppColors.muted, fontSize: 11),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (item.description != null && item.description!.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 2),
|
||||
child: Text(
|
||||
item.description!,
|
||||
style: const TextStyle(
|
||||
fontSize: 13, color: AppColors.text),
|
||||
),
|
||||
Text(
|
||||
_formatTime(item.createdAt),
|
||||
style: const TextStyle(
|
||||
color: AppColors.muted, fontSize: 11),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
if (item.description != null &&
|
||||
item.description!.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 2),
|
||||
child: Text(
|
||||
item.description!,
|
||||
style: const TextStyle(
|
||||
fontSize: 13, color: AppColors.text),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -394,21 +425,29 @@ class _ErrorPanel extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.wifi_off, size: 48, color: AppColors.muted),
|
||||
const SizedBox(height: 12),
|
||||
Text(message,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(color: AppColors.muted)),
|
||||
const SizedBox(height: 16),
|
||||
FilledButton.icon(
|
||||
onPressed: onRetry,
|
||||
icon: const Icon(Icons.refresh),
|
||||
label: const Text('Coba lagi'),
|
||||
),
|
||||
],
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(24),
|
||||
decoration: const BoxDecoration(
|
||||
color: AppColors.cardWhite,
|
||||
borderRadius: AppDecorations.cardRadius,
|
||||
boxShadow: AppDecorations.cardShadow,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.wifi_off, size: 48, color: AppColors.muted),
|
||||
const SizedBox(height: 12),
|
||||
Text(message,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(color: AppColors.muted)),
|
||||
const SizedBox(height: 16),
|
||||
FilledButton.icon(
|
||||
onPressed: onRetry,
|
||||
icon: const Icon(Icons.refresh),
|
||||
label: const Text('Coba lagi'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -421,21 +460,29 @@ class _EmptyPanel extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.history, size: 64, color: AppColors.muted),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
filter == 'ALL'
|
||||
? 'Belum ada aktivitas'
|
||||
: 'Tidak ada aktivitas "$filter"',
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.muted),
|
||||
),
|
||||
],
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(24),
|
||||
decoration: const BoxDecoration(
|
||||
color: AppColors.cardWhite,
|
||||
borderRadius: AppDecorations.cardRadius,
|
||||
boxShadow: AppDecorations.cardShadow,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.history, size: 64, color: AppColors.muted),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
filter == 'ALL'
|
||||
? 'Belum ada aktivitas'
|
||||
: 'Tidak ada aktivitas "$filter"',
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.muted),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user