Polish navigation, Quran flows, and sharing UX

This commit is contained in:
Dwindi Ramadhana
2026-03-18 00:07:10 +07:00
parent a049129a35
commit 2d09b5b356
59 changed files with 11835 additions and 3184 deletions

View File

@@ -1,16 +1,80 @@
import 'dart:async';
import 'dart:ui' show ViewFocusEvent, ViewFocusState;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../core/providers/theme_provider.dart';
import '../features/dashboard/data/prayer_times_provider.dart';
import 'router.dart';
import 'theme/app_theme.dart';
/// Root MaterialApp.router wired to GoRouter + ThemeMode from Riverpod.
class App extends ConsumerWidget {
class App extends ConsumerStatefulWidget {
const App({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
ConsumerState<App> createState() => _AppState();
}
class _AppState extends ConsumerState<App> with WidgetsBindingObserver {
Timer? _midnightResyncTimer;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
WidgetsBinding.instance.addPostFrameCallback((_) {
HardwareKeyboard.instance.syncKeyboardState();
});
_scheduleMidnightResync();
}
@override
void dispose() {
_midnightResyncTimer?.cancel();
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed ||
state == AppLifecycleState.inactive) {
// Resync stale pressed-key state to avoid repeated KeyDown assertions.
HardwareKeyboard.instance.syncKeyboardState();
}
if (state == AppLifecycleState.resumed) {
ref.invalidate(prayerTimesProvider);
unawaited(ref.read(prayerTimesProvider.future));
_scheduleMidnightResync();
}
}
@override
void didChangeViewFocus(ViewFocusEvent event) {
if (event.state == ViewFocusState.focused) {
HardwareKeyboard.instance.syncKeyboardState();
}
}
void _scheduleMidnightResync() {
_midnightResyncTimer?.cancel();
final now = DateTime.now();
final nextRun = DateTime(now.year, now.month, now.day, 0, 5).isAfter(now)
? DateTime(now.year, now.month, now.day, 0, 5)
: DateTime(now.year, now.month, now.day + 1, 0, 5);
final delay = nextRun.difference(now);
_midnightResyncTimer = Timer(delay, () {
ref.invalidate(prayerTimesProvider);
unawaited(ref.read(prayerTimesProvider.future));
_scheduleMidnightResync();
});
}
@override
Widget build(BuildContext context) {
final themeMode = ref.watch(themeProvider);
return MaterialApp.router(

View File

@@ -0,0 +1,119 @@
import 'package:flutter/material.dart';
import 'package:hugeicons/hugeicons.dart';
@immutable
class AppIconGlyph {
const AppIconGlyph.material(this.material) : huge = null;
const AppIconGlyph.huge(this.huge) : material = null;
final IconData? material;
final List<List<dynamic>>? huge;
}
class AppIcon extends StatelessWidget {
const AppIcon({
super.key,
required this.glyph,
this.color,
this.size,
this.strokeWidth,
this.semanticLabel,
});
final AppIconGlyph glyph;
final Color? color;
final double? size;
final double? strokeWidth;
final String? semanticLabel;
@override
Widget build(BuildContext context) {
final huge = glyph.huge;
if (huge != null) {
return HugeIcon(
icon: huge,
color: color,
size: size,
strokeWidth: strokeWidth,
);
}
return Icon(
glyph.material,
color: color,
size: size,
semanticLabel: semanticLabel,
);
}
}
class AppIcons {
const AppIcons._();
static const AppIconGlyph home =
AppIconGlyph.huge(HugeIcons.strokeRoundedHome01);
static const AppIconGlyph calendar =
AppIconGlyph.huge(HugeIcons.strokeRoundedCalendar01);
static const AppIconGlyph quran =
AppIconGlyph.huge(HugeIcons.strokeRoundedQuran02);
static const AppIconGlyph dzikir =
AppIconGlyph.huge(HugeIcons.strokeRoundedTasbih);
static const AppIconGlyph lainnya =
AppIconGlyph.huge(HugeIcons.strokeRoundedGridView);
static const AppIconGlyph ibadah =
AppIconGlyph.huge(HugeIcons.strokeRoundedCheckList);
static const AppIconGlyph laporan =
AppIconGlyph.huge(HugeIcons.strokeRoundedChart01);
static const AppIconGlyph murattal =
AppIconGlyph.huge(HugeIcons.strokeRoundedHeadset);
static const AppIconGlyph qibla =
AppIconGlyph.huge(HugeIcons.strokeRoundedCompass);
static const AppIconGlyph doa = AppIconGlyph.huge(HugeIcons.strokeRoundedDua);
static const AppIconGlyph hadits =
AppIconGlyph.huge(HugeIcons.strokeRoundedBooks01);
static const AppIconGlyph quranEnrichment =
AppIconGlyph.huge(HugeIcons.strokeRoundedQuran01);
static const AppIconGlyph notification =
AppIconGlyph.huge(HugeIcons.strokeRoundedNotification03);
static const AppIconGlyph settings =
AppIconGlyph.huge(HugeIcons.strokeRoundedSettings02);
static const AppIconGlyph share =
AppIconGlyph.huge(HugeIcons.strokeRoundedShare01);
static const AppIconGlyph themeMoon =
AppIconGlyph.huge(HugeIcons.strokeRoundedMoon02);
static const AppIconGlyph themeSun =
AppIconGlyph.huge(HugeIcons.strokeRoundedSun02);
static const AppIconGlyph checkCircle =
AppIconGlyph.huge(HugeIcons.strokeRoundedCheckmarkCircle02);
static const AppIconGlyph circle =
AppIconGlyph.huge(HugeIcons.strokeRoundedCircle);
static const AppIconGlyph musicNote =
AppIconGlyph.huge(HugeIcons.strokeRoundedMusicNote01);
static const AppIconGlyph shuffle =
AppIconGlyph.huge(HugeIcons.strokeRoundedShuffle);
static const AppIconGlyph previousTrack =
AppIconGlyph.huge(HugeIcons.strokeRoundedPrevious);
static const AppIconGlyph nextTrack =
AppIconGlyph.huge(HugeIcons.strokeRoundedNext);
static const AppIconGlyph play =
AppIconGlyph.huge(HugeIcons.strokeRoundedPlay);
static const AppIconGlyph pause =
AppIconGlyph.huge(HugeIcons.strokeRoundedPause);
static const AppIconGlyph playlist =
AppIconGlyph.huge(HugeIcons.strokeRoundedPlaylist01);
static const AppIconGlyph user =
AppIconGlyph.huge(HugeIcons.strokeRoundedUser03);
static const AppIconGlyph arrowDown =
AppIconGlyph.huge(HugeIcons.strokeRoundedArrowDown01);
static const AppIconGlyph backArrow =
AppIconGlyph.huge(HugeIcons.strokeRoundedArrowLeft01);
static const AppIconGlyph location =
AppIconGlyph.huge(HugeIcons.strokeRoundedMosqueLocation);
static const AppIconGlyph locationActive =
AppIconGlyph.huge(HugeIcons.strokeRoundedLocation01);
static const AppIconGlyph locationOffline =
AppIconGlyph.huge(HugeIcons.strokeRoundedLocationOffline01);
}

View File

@@ -1,4 +1,6 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:go_router/go_router.dart';
import 'package:hive_flutter/hive_flutter.dart';
import '../data/local/hive_boxes.dart';
@@ -19,6 +21,7 @@ import '../features/quran/presentation/quran_reading_screen.dart';
import '../features/quran/presentation/quran_murattal_screen.dart';
import '../features/quran/presentation/quran_bookmarks_screen.dart';
import '../features/quran/presentation/quran_enrichment_screen.dart';
import '../features/notifications/presentation/notification_center_screen.dart';
import '../features/settings/presentation/settings_screen.dart';
/// Navigation key for the shell navigator (bottom-nav screens).
@@ -33,12 +36,16 @@ final GoRouter appRouter = GoRouter(
// ── Shell route (bottom nav persists) ──
ShellRoute(
navigatorKey: _shellNavigatorKey,
builder: (context, state, child) => _ScaffoldWithNav(child: child),
pageBuilder: (context, state, child) => NoTransitionPage(
key: ValueKey<String>('shell-${state.pageKey.value}'),
child: _ScaffoldWithNav(child: child),
),
routes: [
GoRoute(
path: '/',
pageBuilder: (context, state) => const NoTransitionPage(
child: DashboardScreen(),
pageBuilder: (context, state) => NoTransitionPage(
key: state.pageKey,
child: const DashboardScreen(),
),
routes: [
GoRoute(
@@ -50,26 +57,30 @@ final GoRouter appRouter = GoRouter(
),
GoRoute(
path: '/imsakiyah',
pageBuilder: (context, state) => const NoTransitionPage(
child: ImsakiyahScreen(),
pageBuilder: (context, state) => NoTransitionPage(
key: state.pageKey,
child: const ImsakiyahScreen(),
),
),
GoRoute(
path: '/checklist',
pageBuilder: (context, state) => const NoTransitionPage(
child: ChecklistScreen(),
pageBuilder: (context, state) => NoTransitionPage(
key: state.pageKey,
child: const ChecklistScreen(),
),
),
GoRoute(
path: '/laporan',
pageBuilder: (context, state) => const NoTransitionPage(
child: LaporanScreen(),
pageBuilder: (context, state) => NoTransitionPage(
key: state.pageKey,
child: const LaporanScreen(),
),
),
GoRoute(
path: '/tools',
pageBuilder: (context, state) => const NoTransitionPage(
child: ToolsScreen(),
pageBuilder: (context, state) => NoTransitionPage(
key: state.pageKey,
child: const ToolsScreen(),
),
routes: [
GoRoute(
@@ -97,8 +108,10 @@ final GoRouter appRouter = GoRouter(
parentNavigatorKey: _rootNavigatorKey,
builder: (context, state) {
final surahId = state.pathParameters['surahId']!;
final startVerse = int.tryParse(state.uri.queryParameters['startVerse'] ?? '');
return QuranReadingScreen(surahId: surahId, initialVerse: startVerse);
final startVerse = int.tryParse(
state.uri.queryParameters['startVerse'] ?? '');
return QuranReadingScreen(
surahId: surahId, initialVerse: startVerse);
},
routes: [
GoRoute(
@@ -107,9 +120,10 @@ final GoRouter appRouter = GoRouter(
builder: (context, state) {
final surahId = state.pathParameters['surahId']!;
final qariId = state.uri.queryParameters['qariId'];
final autoplay = state.uri.queryParameters['autoplay'] == 'true';
final autoplay =
state.uri.queryParameters['autoplay'] == 'true';
return QuranMurattalScreen(
surahId: surahId,
surahId: surahId,
initialQariId: qariId,
autoPlay: autoplay,
);
@@ -139,7 +153,8 @@ final GoRouter appRouter = GoRouter(
// Simple Mode Tab: Zikir
GoRoute(
path: '/dzikir',
builder: (context, state) => const DzikirScreen(isSimpleModeTab: true),
builder: (context, state) =>
const DzikirScreen(isSimpleModeTab: true),
),
// Simple Mode Tab: Tilawah
GoRoute(
@@ -148,18 +163,24 @@ final GoRouter appRouter = GoRouter(
routes: [
GoRoute(
path: 'enrichment',
builder: (context, state) => const QuranEnrichmentScreen(),
builder: (context, state) =>
const QuranEnrichmentScreen(isSimpleModeTab: true),
),
GoRoute(
path: 'bookmarks',
builder: (context, state) => const QuranBookmarksScreen(),
builder: (context, state) =>
const QuranBookmarksScreen(isSimpleModeTab: true),
),
GoRoute(
path: ':surahId',
builder: (context, state) {
final surahId = state.pathParameters['surahId']!;
final startVerse = int.tryParse(state.uri.queryParameters['startVerse'] ?? '');
return QuranReadingScreen(surahId: surahId, initialVerse: startVerse, isSimpleModeTab: true);
final startVerse =
int.tryParse(state.uri.queryParameters['startVerse'] ?? '');
return QuranReadingScreen(
surahId: surahId,
initialVerse: startVerse,
isSimpleModeTab: true);
},
routes: [
GoRoute(
@@ -168,9 +189,10 @@ final GoRouter appRouter = GoRouter(
builder: (context, state) {
final surahId = state.pathParameters['surahId']!;
final qariId = state.uri.queryParameters['qariId'];
final autoplay = state.uri.queryParameters['autoplay'] == 'true';
final autoplay =
state.uri.queryParameters['autoplay'] == 'true';
return QuranMurattalScreen(
surahId: surahId,
surahId: surahId,
initialQariId: qariId,
autoPlay: autoplay,
isSimpleModeTab: true,
@@ -187,11 +209,17 @@ final GoRouter appRouter = GoRouter(
),
GoRoute(
path: '/hadits',
builder: (context, state) => const HaditsScreen(isSimpleModeTab: true),
builder: (context, state) =>
const HaditsScreen(isSimpleModeTab: true),
),
],
),
// ── Settings (pushed, no bottom nav) ──
GoRoute(
path: '/notifications',
parentNavigatorKey: _rootNavigatorKey,
builder: (context, state) => const NotificationCenterScreen(),
),
GoRoute(
path: '/settings',
parentNavigatorKey: _rootNavigatorKey,
@@ -201,11 +229,31 @@ final GoRouter appRouter = GoRouter(
);
/// Scaffold wrapper that provides the persistent bottom nav bar.
class _ScaffoldWithNav extends StatelessWidget {
class _ScaffoldWithNav extends StatefulWidget {
const _ScaffoldWithNav({required this.child});
final Widget child;
@override
State<_ScaffoldWithNav> createState() => _ScaffoldWithNavState();
}
class _ScaffoldWithNavState extends State<_ScaffoldWithNav> {
DateTime? _lastBackPressedAt;
bool _shouldHideBottomNav({
required bool isSimpleMode,
required String path,
}) {
if (!isSimpleMode) return false;
if (path == '/dzikir') return true;
if (!path.startsWith('/quran/')) return false;
final tail = path.substring('/quran/'.length);
if (tail == 'bookmarks' || tail == 'enrichment') return false;
return !tail.contains('/');
}
/// Maps route locations to bottom nav indices.
int _currentIndex(BuildContext context) {
final location = GoRouterState.of(context).uri.toString();
@@ -214,9 +262,13 @@ class _ScaffoldWithNav extends StatelessWidget {
if (isSimpleMode) {
if (location.startsWith('/imsakiyah')) return 1;
if (location.startsWith('/quran') && !location.contains('/murattal')) return 2;
if (location.contains('/murattal')) return 3;
if (location.startsWith('/dzikir')) return 4;
if (location.startsWith('/quran')) return 2;
if (location.startsWith('/dzikir')) return 3;
if (location.startsWith('/tools') ||
location.startsWith('/doa') ||
location.startsWith('/hadits')) {
return 4;
}
return 0;
} else {
if (location.startsWith('/imsakiyah')) return 1;
@@ -243,10 +295,10 @@ class _ScaffoldWithNav extends StatelessWidget {
context.go('/quran');
break;
case 3:
context.push('/quran/1/murattal');
context.go('/dzikir');
break;
case 4:
context.go('/dzikir');
context.go('/tools');
break;
}
} else {
@@ -270,16 +322,97 @@ class _ScaffoldWithNav extends StatelessWidget {
}
}
bool _isMainShellRoute({
required bool isSimpleMode,
required String path,
}) {
if (isSimpleMode) {
return path == '/' ||
path == '/imsakiyah' ||
path == '/quran' ||
path == '/dzikir' ||
path == '/tools';
}
return path == '/' ||
path == '/imsakiyah' ||
path == '/checklist' ||
path == '/laporan' ||
path == '/tools';
}
Future<void> _handleMainRouteBack(
BuildContext context, {
required String path,
}) async {
if (path != '/') {
context.go('/');
return;
}
final now = DateTime.now();
final pressedRecently = _lastBackPressedAt != null &&
now.difference(_lastBackPressedAt!) <= const Duration(seconds: 2);
if (pressedRecently) {
await SystemNavigator.pop();
return;
}
_lastBackPressedAt = now;
final messenger = ScaffoldMessenger.maybeOf(context);
messenger
?..hideCurrentSnackBar()
..showSnackBar(
const SnackBar(
content: Text('Tekan sekali lagi untuk keluar'),
duration: Duration(seconds: 2),
),
);
}
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<Box<AppSettings>>(
valueListenable: Hive.box<AppSettings>(HiveBoxes.settings).listenable(),
builder: (context, box, _) {
return Scaffold(
body: child,
bottomNavigationBar: AppBottomNavBar(
currentIndex: _currentIndex(context),
onTap: (i) => _onTap(context, i),
final isSimpleMode = box.get('default')?.simpleMode ?? false;
final path = GoRouterState.of(context).uri.path;
final hideBottomNav = _shouldHideBottomNav(
isSimpleMode: isSimpleMode,
path: path,
);
final modeScopedChild = KeyedSubtree(
key: ValueKey(
'shell:$path:${isSimpleMode ? 'simple' : 'full'}',
),
child: widget.child,
);
final pageBody = hideBottomNav
? SafeArea(
top: false,
child: modeScopedChild,
)
: modeScopedChild;
final handleBackInShell =
defaultTargetPlatform == TargetPlatform.android &&
_isMainShellRoute(isSimpleMode: isSimpleMode, path: path);
return PopScope(
canPop: !handleBackInShell,
onPopInvokedWithResult: (didPop, _) async {
if (didPop || !handleBackInShell) return;
await _handleMainRouteBack(context, path: path);
},
child: Scaffold(
body: pageBody,
bottomNavigationBar: hideBottomNav
? null
: AppBottomNavBar(
currentIndex: _currentIndex(context),
onTap: (i) => _onTap(context, i),
),
),
);
},

View File

@@ -4,29 +4,55 @@ import 'package:flutter/material.dart';
class AppColors {
AppColors._();
// ── Primary ──
static const Color primary = Color(0xFF70DF20);
static const Color onPrimary = Color(0xFF0A1A00);
// ── Brand tokens: logo palette (teal + gold) ──
static const Color brandTeal500 = Color(0xFF118A8D);
static const Color brandTeal700 = Color(0xFF0C676A);
static const Color brandTeal900 = Color(0xFF0A4447);
// ── Background ──
static const Color backgroundLight = Color(0xFFF7F8F6);
static const Color backgroundDark = Color(0xFF182111);
static const Color brandGold200 = Color(0xFFF6DE96);
static const Color brandGold300 = Color(0xFFE9C75B);
static const Color brandGold400 = Color(0xFFD6A21D);
static const Color brandGold700 = Color(0xFF8B6415);
// ── Theme base tokens ──
static const Color backgroundLight = Color(0xFFF3F4F6);
static const Color backgroundDark = Color(0xFF0F1217);
// ── Surface ──
static const Color surfaceLight = Color(0xFFFFFFFF);
static const Color surfaceDark = Color(0xFF1E2A14);
static const Color surfaceLightElevated = Color(0xFFF9FAFB);
// ── Sage (secondary text / section labels) ──
static const Color sage = Color(0xFF728764);
static const Color surfaceDark = Color(0xFF171B22);
static const Color surfaceDarkElevated = Color(0xFF1D222B);
// ── Cream (dividers, borders — light mode only) ──
static const Color cream = Color(0xFFF2F4F0);
static const Color textPrimaryLight = Color(0xFF1F2937);
static const Color textPrimaryDark = Color(0xFFE8ECF2);
static const Color textSecondaryLight = Color(0xFF6B7280);
static const Color textSecondaryDark = Color(0xFF9AA4B2);
// ── Text ──
static const Color textPrimaryLight = Color(0xFF1A2A0A);
static const Color textPrimaryDark = Color(0xFFF2F4F0);
static const Color textSecondaryLight = Color(0xFF64748B);
static const Color textSecondaryDark = Color(0xFF94A3B8);
// ── Compatibility aliases (existing UI references) ──
static const Color primary = brandTeal500;
static const Color onPrimary = Color(0xFFFFFFFF);
static const Color sage = brandTeal700;
static const Color cream = Color(0xFFE5E7EB);
// ── Luxury active-state tokens ──
static const Color navActiveGold = brandGold400;
static const Color navActiveGoldBright = brandGold300;
static const Color navActiveGoldPale = brandGold200;
static const Color navActiveGoldDeep = brandGold700;
static const Color navActiveSurfaceDark = surfaceDarkElevated;
static const Color navActiveSurfaceLight = surfaceLight;
static const Color navGlowDark = Color(0x5CD6A21D);
static const Color navGlowLight = Color(0x36D6A21D);
static const Color navShadowLight = Color(0x1F0F172A);
static const Color navStrokeNeutralDark = Color(0x33FFFFFF);
static const Color navStrokeNeutralLight = Color(0x220F172A);
static const Color navEmbossHighlight = Color(0xE6FFFFFF);
static const Color navEmbossShadow = Color(0x2B0F172A);
static const Color navEmbossGoldShadow = Color(0x42B88912);
// ── Semantic ──
static const Color errorLight = Color(0xFFEF4444);
@@ -36,25 +62,29 @@ class AppColors {
// ── Convenience helpers for theme building ──
static ColorScheme get lightColorScheme => ColorScheme.light(
static ColorScheme get lightColorScheme => const ColorScheme.light(
primary: primary,
onPrimary: onPrimary,
primaryContainer: brandTeal700,
onPrimaryContainer: Colors.white,
surface: surfaceLight,
onSurface: textPrimaryLight,
error: errorLight,
onError: Colors.white,
secondary: sage,
onSecondary: Colors.white,
secondary: navActiveGold,
onSecondary: brandGold700,
);
static ColorScheme get darkColorScheme => ColorScheme.dark(
static ColorScheme get darkColorScheme => const ColorScheme.dark(
primary: primary,
onPrimary: onPrimary,
primaryContainer: brandTeal900,
onPrimaryContainer: textPrimaryDark,
surface: surfaceDark,
onSurface: textPrimaryDark,
error: errorDark,
onError: Colors.black,
secondary: sage,
onSecondary: Colors.white,
secondary: navActiveGold,
onSecondary: brandGold200,
);
}

View File

@@ -1,11 +1,22 @@
import 'package:flutter/material.dart';
/// Typography definitions from PRD §3.2.
/// Plus Jakarta Sans (bundled) for UI text, Amiri (bundled) for Arabic content.
/// Plus Jakarta Sans (bundled) for UI text.
/// Scheherazade New (bundled) for Arabic/Quran text, with Uthman/KFGQPC fallback.
class AppTextStyles {
AppTextStyles._();
static const String _fontFamily = 'PlusJakartaSans';
static const String _arabicFontFamily = 'ScheherazadeNew';
static const List<String> _arabicFallbackFamilies = <String>[
'UthmanTahaNaskh',
'KFGQPCUthmanicHafs',
'Amiri',
'Noto Naskh Arabic',
'Noto Sans Arabic',
'Droid Arabic Naskh',
'sans-serif',
];
/// Builds the full TextTheme for the app using bundled Plus Jakarta Sans.
static const TextTheme textTheme = TextTheme(
@@ -52,19 +63,21 @@ class AppTextStyles {
),
);
// ── Arabic text styles (Amiri — bundled font) ──
// ── Arabic text styles (Scheherazade New — bundled font) ──
static const TextStyle arabicBody = TextStyle(
fontFamily: 'Amiri',
fontFamily: _arabicFontFamily,
fontFamilyFallback: _arabicFallbackFamilies,
fontSize: 24,
fontWeight: FontWeight.w400,
height: 2.0,
height: 1.8,
);
static const TextStyle arabicLarge = TextStyle(
fontFamily: 'Amiri',
fontFamily: _arabicFontFamily,
fontFamilyFallback: _arabicFallbackFamilies,
fontSize: 28,
fontWeight: FontWeight.w400,
height: 2.2,
height: 2.0,
);
}

View File

@@ -29,17 +29,17 @@ class AppTheme {
),
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
backgroundColor: AppColors.surfaceLight,
selectedItemColor: AppColors.primary,
selectedItemColor: AppColors.navActiveGoldDeep,
unselectedItemColor: AppColors.textSecondaryLight,
type: BottomNavigationBarType.fixed,
elevation: 0,
),
cardTheme: CardThemeData(
color: AppColors.surfaceLight,
color: AppColors.surfaceLightElevated,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side: BorderSide(
side: const BorderSide(
color: AppColors.cream,
),
),
@@ -70,21 +70,21 @@ class AppTheme {
),
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
backgroundColor: AppColors.surfaceDark,
selectedItemColor: AppColors.primary,
selectedItemColor: AppColors.navActiveGold,
unselectedItemColor: AppColors.textSecondaryDark,
type: BottomNavigationBarType.fixed,
elevation: 0,
),
cardTheme: CardThemeData(
color: AppColors.surfaceDark,
color: AppColors.surfaceDarkElevated,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side: BorderSide(
color: AppColors.primary.withValues(alpha: 0.1),
color: AppColors.brandTeal500.withValues(alpha: 0.22),
),
),
),
dividerColor: AppColors.surfaceDark,
dividerColor: AppColors.surfaceDarkElevated,
);
}