POLISHING STUFF and Smarter AI, and alot i mean.. ALOT OF BUG FIX AND OPTIMIZE
This commit is contained in:
+564
-542
File diff suppressed because it is too large
Load Diff
+617
-595
File diff suppressed because it is too large
Load Diff
+26
-13
@@ -8,6 +8,9 @@ import 'package:latlong2/latlong.dart';
|
||||
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 '../../shared/widgets/animations/animations.dart';
|
||||
import '../../shared/widgets/feature_page.dart';
|
||||
|
||||
class GuardianMapScreen extends StatefulWidget {
|
||||
@@ -106,8 +109,9 @@ class _GuardianMapCard extends StatelessWidget {
|
||||
final center = _pointFrom(location) ??
|
||||
(points.isNotEmpty ? points.first : null) ??
|
||||
const LatLng(-7.2575, 112.7521);
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
return Container(
|
||||
decoration: AppDecorations.card,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: FlutterMap(
|
||||
options: MapOptions(initialCenter: center, initialZoom: 16),
|
||||
children: [
|
||||
@@ -121,7 +125,7 @@ class _GuardianMapCard extends StatelessWidget {
|
||||
Polyline(
|
||||
points: points,
|
||||
strokeWidth: 4,
|
||||
color: const Color(0xFF2563EB),
|
||||
color: AppColors.primaryBlue,
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -171,10 +175,18 @@ class _TimelineList extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
return ListView.separated(
|
||||
itemCount: segments.length,
|
||||
separatorBuilder: (_, __) => const SizedBox(height: 10),
|
||||
itemBuilder: (_, index) => _TimelineCard(segment: segments[index]),
|
||||
return ListView(
|
||||
children: [
|
||||
StaggerWrapper(
|
||||
children: [
|
||||
for (final segment in segments)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
child: _TimelineCard(segment: segment),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -189,9 +201,10 @@ class _TimelineCard extends StatelessWidget {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: const Color(0xFFE2E8F0)),
|
||||
color: AppColors.cardWhite,
|
||||
borderRadius: AppDecorations.cardRadius,
|
||||
border: Border.all(color: AppColors.border),
|
||||
boxShadow: AppDecorations.cardShadow,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
@@ -199,10 +212,10 @@ class _TimelineCard extends StatelessWidget {
|
||||
width: 42,
|
||||
height: 42,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFEFF6FF),
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
color: AppColors.softBlueBg,
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
),
|
||||
child: Icon(segment.icon, color: const Color(0xFF1D4ED8)),
|
||||
child: Icon(segment.icon, color: AppColors.primaryBlue),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
|
||||
+121
-120
@@ -8,6 +8,9 @@ import 'package:record/record.dart';
|
||||
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 '../../shared/widgets/animations/animations.dart';
|
||||
import '../../shared/widgets/feature_page.dart';
|
||||
|
||||
class GuardianSendNotifScreen extends StatefulWidget {
|
||||
@@ -132,132 +135,130 @@ class _GuardianSendNotifScreenState extends State<GuardianSendNotifScreen> {
|
||||
subtitle: 'Kirim pesan singkat ke User yang sudah pairing',
|
||||
child: ListView(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(18),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: const Color(0xFFE2E8F0)),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: const Color(0xFF1E293B).withValues(alpha: 0.06),
|
||||
blurRadius: 22,
|
||||
offset: const Offset(0, 12),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
SegmentedButton<bool>(
|
||||
segments: const [
|
||||
ButtonSegment(
|
||||
value: false,
|
||||
icon: Icon(Icons.message_outlined),
|
||||
label: Text('Text'),
|
||||
FadeSlideWrapper(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(18),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.cardWhite,
|
||||
borderRadius: AppDecorations.cardRadius,
|
||||
border: Border.all(color: AppColors.border),
|
||||
boxShadow: AppDecorations.cardShadow,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
SegmentedButton<bool>(
|
||||
segments: const [
|
||||
ButtonSegment(
|
||||
value: false,
|
||||
icon: Icon(Icons.message_outlined),
|
||||
label: Text('Text'),
|
||||
),
|
||||
ButtonSegment(
|
||||
value: true,
|
||||
icon: Icon(Icons.mic_none_outlined),
|
||||
label: Text('Voice'),
|
||||
),
|
||||
],
|
||||
selected: {_voiceMode},
|
||||
onSelectionChanged: _loading || _recording
|
||||
? null
|
||||
: (value) => setState(() => _voiceMode = value.first),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
TextField(
|
||||
controller: _message,
|
||||
minLines: _voiceMode ? 2 : 5,
|
||||
maxLines: _voiceMode ? 3 : 8,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Message',
|
||||
hintText: 'Contoh: Hati-hati, belok kanan setelah pintu.',
|
||||
prefixIcon: Icon(Icons.message_outlined),
|
||||
alignLabelWithHint: true,
|
||||
),
|
||||
ButtonSegment(
|
||||
value: true,
|
||||
icon: Icon(Icons.mic_none_outlined),
|
||||
label: Text('Voice'),
|
||||
),
|
||||
if (_voiceMode) ...[
|
||||
const SizedBox(height: 14),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF8FAFC),
|
||||
borderRadius: AppDecorations.cardRadius,
|
||||
border: Border.all(color: AppColors.border),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
backgroundColor: _recording
|
||||
? const Color(0xFFFEE2E2)
|
||||
: const Color(0xFFEFF6FF),
|
||||
child: Icon(
|
||||
_recording ? Icons.graphic_eq : Icons.mic,
|
||||
color: _recording
|
||||
? const Color(0xFFDC2626)
|
||||
: const Color(0xFF2563EB),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
_recording
|
||||
? 'Recording... tap stop when done'
|
||||
: _voicePath == null
|
||||
? 'No voice note recorded'
|
||||
: 'Voice note ready',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w800),
|
||||
),
|
||||
Text(
|
||||
_recording
|
||||
? 'Speak clearly near the microphone'
|
||||
: _voicePath == null
|
||||
? 'Record a short message for User'
|
||||
: '${_voiceDuration}s audio attached',
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF64748B), fontSize: 12),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
FilledButton.icon(
|
||||
onPressed: _loading ? null : _toggleRecording,
|
||||
icon: Icon(_recording
|
||||
? Icons.stop
|
||||
: Icons.fiber_manual_record),
|
||||
label: Text(_recording ? 'Stop' : 'Record'),
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: _recording
|
||||
? const Color(0xFFDC2626)
|
||||
: const Color(0xFF2563EB),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
selected: {_voiceMode},
|
||||
onSelectionChanged: _loading || _recording
|
||||
? null
|
||||
: (value) => setState(() => _voiceMode = value.first),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
TextField(
|
||||
controller: _message,
|
||||
minLines: _voiceMode ? 2 : 5,
|
||||
maxLines: _voiceMode ? 3 : 8,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Message',
|
||||
hintText: 'Contoh: Hati-hati, belok kanan setelah pintu.',
|
||||
prefixIcon: Icon(Icons.message_outlined),
|
||||
alignLabelWithHint: true,
|
||||
),
|
||||
),
|
||||
if (_voiceMode) ...[
|
||||
const SizedBox(height: 14),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF8FAFC),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: const Color(0xFFE2E8F0)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
backgroundColor: _recording
|
||||
? const Color(0xFFFEE2E2)
|
||||
: const Color(0xFFEFF6FF),
|
||||
child: Icon(
|
||||
_recording ? Icons.graphic_eq : Icons.mic,
|
||||
color: _recording
|
||||
? const Color(0xFFDC2626)
|
||||
: const Color(0xFF2563EB),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
_recording
|
||||
? 'Recording... tap stop when done'
|
||||
: _voicePath == null
|
||||
? 'No voice note recorded'
|
||||
: 'Voice note ready',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w800),
|
||||
),
|
||||
Text(
|
||||
_recording
|
||||
? 'Speak clearly near the microphone'
|
||||
: _voicePath == null
|
||||
? 'Record a short message for User'
|
||||
: '${_voiceDuration}s audio attached',
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF64748B), fontSize: 12),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
FilledButton.icon(
|
||||
onPressed: _loading ? null : _toggleRecording,
|
||||
icon: Icon(_recording ? Icons.stop : Icons.fiber_manual_record),
|
||||
label: Text(_recording ? 'Stop' : 'Record'),
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: _recording
|
||||
? const Color(0xFFDC2626)
|
||||
: const Color(0xFF2563EB),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
FilledButton.icon(
|
||||
onPressed: _loading ? null : _send,
|
||||
icon: _loading
|
||||
? const SizedBox(
|
||||
width: 18,
|
||||
height: 18,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Icon(Icons.send),
|
||||
label: Text(_loading
|
||||
? 'Sending...'
|
||||
: _voiceMode
|
||||
? 'Send Voice Message'
|
||||
: 'Send Message'),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 14),
|
||||
FilledButton.icon(
|
||||
onPressed: _loading ? null : _send,
|
||||
icon: _loading
|
||||
? const SizedBox(
|
||||
width: 18,
|
||||
height: 18,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Icon(Icons.send),
|
||||
label: Text(_loading
|
||||
? 'Sending...'
|
||||
: _voiceMode
|
||||
? 'Send Voice Message'
|
||||
: 'Send Message'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
+46
-30
@@ -9,6 +9,9 @@ import '../../app/injection_container.dart';
|
||||
import '../../core/constants/app_constants.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 '../../shared/widgets/animations/animations.dart';
|
||||
import '../../core/storage/secure_storage.dart';
|
||||
import '../../shared/widgets/feature_page.dart';
|
||||
|
||||
@@ -49,23 +52,27 @@ class GuardianSettingsScreen extends StatelessWidget {
|
||||
subtitle: 'Account, pairing, AI tools, and server',
|
||||
child: ListView(
|
||||
children: [
|
||||
_SettingsTile(
|
||||
icon: Icons.link,
|
||||
title: 'Pair User',
|
||||
subtitle: 'Masukkan Pairing Code User atau cek status pairing.',
|
||||
onTap: () => context.go('/guardian/pairing'),
|
||||
),
|
||||
_SettingsTile(
|
||||
icon: Icons.speed,
|
||||
title: 'AI Benchmark',
|
||||
subtitle: 'Catat capture, inference, notification, dan TTS.',
|
||||
onTap: () => context.go('/guardian/benchmark'),
|
||||
),
|
||||
_SettingsTile(
|
||||
icon: Icons.tune,
|
||||
title: 'AI Config',
|
||||
subtitle: 'Atur threshold deteksi dan label yang aktif.',
|
||||
onTap: () => context.go('/guardian/ai-config'),
|
||||
StaggerWrapper(
|
||||
children: [
|
||||
_SettingsTile(
|
||||
icon: Icons.link,
|
||||
title: 'Pair User',
|
||||
subtitle: 'Masukkan Pairing Code User atau cek status pairing.',
|
||||
onTap: () => context.go('/guardian/pairing'),
|
||||
),
|
||||
_SettingsTile(
|
||||
icon: Icons.speed,
|
||||
title: 'AI Benchmark',
|
||||
subtitle: 'Catat capture, inference, notification, dan TTS.',
|
||||
onTap: () => context.go('/guardian/benchmark'),
|
||||
),
|
||||
_SettingsTile(
|
||||
icon: Icons.tune,
|
||||
title: 'AI Config',
|
||||
subtitle: 'Atur threshold deteksi dan label yang aktif.',
|
||||
onTap: () => context.go('/guardian/ai-config'),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
OutlinedButton.icon(
|
||||
@@ -103,19 +110,28 @@ class _SettingsTile extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: const Color(0xFFE2E8F0)),
|
||||
),
|
||||
child: ListTile(
|
||||
leading: Icon(icon, color: const Color(0xFF1D4ED8)),
|
||||
title: Text(title, style: const TextStyle(fontWeight: FontWeight.w800)),
|
||||
subtitle: Text(subtitle),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: onTap,
|
||||
return BounceTap(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(bottom: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.cardWhite,
|
||||
borderRadius: AppDecorations.cardRadius,
|
||||
border: Border.all(color: AppColors.border),
|
||||
boxShadow: AppDecorations.cardShadow,
|
||||
),
|
||||
child: ListTile(
|
||||
leading: Container(
|
||||
width: 44,
|
||||
height: 44,
|
||||
decoration: AppDecorations.iconCircle(),
|
||||
child: Icon(icon, color: AppColors.primaryBlue),
|
||||
),
|
||||
title:
|
||||
Text(title, style: const TextStyle(fontWeight: FontWeight.w800)),
|
||||
subtitle: Text(subtitle),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user