feat: Implement a new application theme with custom colors and Google Fonts, applying it to the notifications screen and other core UI.
This commit is contained in:
parent
f9478a47c6
commit
0fe07fd555
@ -3,11 +3,11 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:workmanager/workmanager.dart';
|
import 'package:workmanager/workmanager.dart';
|
||||||
import 'screens/login_screen.dart';
|
import 'screens/login_screen.dart';
|
||||||
import 'services/background_service.dart';
|
import 'services/background_service.dart';
|
||||||
|
import 'theme/app_theme.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
// Workmanager background tasks are only supported on physical iOS and Android devices.
|
|
||||||
if (Platform.isAndroid || Platform.isIOS) {
|
if (Platform.isAndroid || Platform.isIOS) {
|
||||||
Workmanager().initialize(
|
Workmanager().initialize(
|
||||||
callbackDispatcher,
|
callbackDispatcher,
|
||||||
@ -30,12 +30,9 @@ class OdooLoyaltyApp extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
title: 'Odoo Loyalty App',
|
title: 'Mie Mapan Loyalty App',
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
theme: ThemeData(
|
theme: AppTheme.lightTheme,
|
||||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
|
||||||
useMaterial3: true,
|
|
||||||
),
|
|
||||||
home: const LoginScreen(),
|
home: const LoginScreen(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import 'package:odoo_rpc/odoo_rpc.dart';
|
|||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import '../services/odoo_service.dart';
|
import '../services/odoo_service.dart';
|
||||||
import 'loyalty_dashboard.dart';
|
import 'loyalty_dashboard.dart';
|
||||||
|
import '../theme/app_theme.dart';
|
||||||
|
|
||||||
class LoginScreen extends StatefulWidget {
|
class LoginScreen extends StatefulWidget {
|
||||||
const LoginScreen({super.key});
|
const LoginScreen({super.key});
|
||||||
@ -49,21 +50,13 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||||||
if (mounted) {
|
if (mounted) {
|
||||||
Navigator.pushReplacement(
|
Navigator.pushReplacement(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(builder: (_) => LoyaltyDashboard(partnerId: session.partnerId)),
|
||||||
builder: (_) => LoyaltyDashboard(
|
|
||||||
partnerId: session.partnerId,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} on OdooException catch (e) {
|
} on OdooException catch (e) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Login failed: ${e.message}')));
|
||||||
SnackBar(content: Text('Login failed: ${e.message}')),
|
|
||||||
);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error: $e')));
|
||||||
SnackBar(content: Text('Error: $e')),
|
|
||||||
);
|
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) setState(() => _isLoading = false);
|
if (mounted) setState(() => _isLoading = false);
|
||||||
}
|
}
|
||||||
@ -72,40 +65,57 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: const Text('Login to Odoo')),
|
body: SafeArea(
|
||||||
body: Padding(
|
child: SingleChildScrollView(
|
||||||
padding: const EdgeInsets.all(24.0),
|
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 48.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
children: [
|
||||||
children: [
|
const SizedBox(height: 64),
|
||||||
TextField(
|
Text(
|
||||||
controller: _usernameController,
|
'Mie Mapan\nMembresia', // Editorial high-end entry
|
||||||
decoration: const InputDecoration(
|
style: Theme.of(context).textTheme.displayMedium?.copyWith(
|
||||||
labelText: 'Email / Username',
|
color: AppTheme.primary,
|
||||||
border: OutlineInputBorder(),
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 12),
|
||||||
const SizedBox(height: 12),
|
Text(
|
||||||
TextField(
|
'Sign in to access your culinary loyalty tier and discover exclusive offers.',
|
||||||
controller: _passwordController,
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: 'Password',
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
),
|
||||||
obscureText: true,
|
const SizedBox(height: 56),
|
||||||
),
|
TextField(
|
||||||
const SizedBox(height: 24),
|
controller: _usernameController,
|
||||||
SizedBox(
|
decoration: const InputDecoration(labelText: 'Email / Username'),
|
||||||
height: 50,
|
),
|
||||||
child: _isLoading
|
const SizedBox(height: 20),
|
||||||
? const Center(child: CircularProgressIndicator())
|
TextField(
|
||||||
: ElevatedButton(
|
controller: _passwordController,
|
||||||
onPressed: _login,
|
decoration: const InputDecoration(labelText: 'Password'),
|
||||||
child: const Text('Login', style: TextStyle(fontSize: 18)),
|
obscureText: true,
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 48),
|
||||||
],
|
SizedBox(
|
||||||
|
height: 56,
|
||||||
|
child: _isLoading
|
||||||
|
? const Center(child: CircularProgressIndicator())
|
||||||
|
: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: AppTheme.primaryGradient,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: _login,
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
shadowColor: Colors.transparent,
|
||||||
|
),
|
||||||
|
child: const Text('Access Membership', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import '../services/odoo_service.dart';
|
import '../services/odoo_service.dart';
|
||||||
|
import '../theme/app_theme.dart';
|
||||||
|
import 'notifications_screen.dart';
|
||||||
|
|
||||||
class LoyaltyDashboard extends StatefulWidget {
|
class LoyaltyDashboard extends StatefulWidget {
|
||||||
final int partnerId;
|
final int partnerId;
|
||||||
|
|
||||||
const LoyaltyDashboard({super.key, required this.partnerId});
|
const LoyaltyDashboard({super.key, required this.partnerId});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -28,10 +29,10 @@ class _LoyaltyDashboardState extends State<LoyaltyDashboard> {
|
|||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setState(() => _isLoading = false);
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
setState(() => _isLoading = false);
|
||||||
SnackBar(content: Text('Error loading loyalty cards: $e')),
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error loading loyalty cards: $e')));
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,72 +40,99 @@ class _LoyaltyDashboardState extends State<LoyaltyDashboard> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('My Loyalty Programs'),
|
title: const Text('My Rewards'),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.refresh),
|
icon: const Icon(Icons.notifications_outlined),
|
||||||
onPressed: () {
|
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const NotificationsScreen())),
|
||||||
setState(() => _isLoading = true);
|
),
|
||||||
_fetchLoyaltyData();
|
const SizedBox(width: 8),
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: _isLoading
|
body: _isLoading
|
||||||
? const Center(child: CircularProgressIndicator())
|
? const Center(child: CircularProgressIndicator())
|
||||||
: _loyaltyCards.isEmpty
|
: RefreshIndicator(
|
||||||
? const Center(
|
onRefresh: _fetchLoyaltyData,
|
||||||
child: Text(
|
child: _loyaltyCards.isEmpty
|
||||||
'No loyalty cards found.',
|
? Center(
|
||||||
style: TextStyle(fontSize: 18),
|
child: Text(
|
||||||
),
|
'No active rewards yet.',
|
||||||
)
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
: ListView.builder(
|
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
itemCount: _loyaltyCards.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final card = _loyaltyCards[index];
|
|
||||||
return Card(
|
|
||||||
elevation: 4,
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
),
|
||||||
margin: const EdgeInsets.symmetric(vertical: 8.0),
|
)
|
||||||
child: Padding(
|
: ListView.builder(
|
||||||
padding: const EdgeInsets.all(12.0),
|
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 32.0),
|
||||||
child: Column(
|
itemCount: _loyaltyCards.length,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
itemBuilder: (context, index) {
|
||||||
children: [
|
final card = _loyaltyCards[index];
|
||||||
Text(
|
return Container(
|
||||||
'${card['program_id']?[1] ?? 'Loyalty Program'}',
|
margin: const EdgeInsets.only(bottom: 32),
|
||||||
style: const TextStyle(
|
padding: const EdgeInsets.all(24),
|
||||||
fontSize: 18,
|
decoration: BoxDecoration(
|
||||||
fontWeight: FontWeight.bold,
|
color: AppTheme.surfaceContainerLow,
|
||||||
),
|
borderRadius: BorderRadius.circular(24),
|
||||||
),
|
boxShadow: [
|
||||||
const SizedBox(height: 8),
|
BoxShadow(
|
||||||
Text('Code: ${card['code'] ?? 'N/A'}'),
|
color: AppTheme.onSurface.withOpacity(0.06),
|
||||||
const SizedBox(height: 8),
|
blurRadius: 24,
|
||||||
Row(
|
offset: const Offset(0, 8),
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
)
|
||||||
children: [
|
]
|
||||||
const Text('Points Available:'),
|
),
|
||||||
Text(
|
child: Column(
|
||||||
'${card['points'] ?? 0} pts',
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
style: const TextStyle(
|
children: [
|
||||||
fontWeight: FontWeight.bold,
|
Row(
|
||||||
fontSize: 20,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
color: Colors.green,
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
'${card['program_id']?[1] ?? 'Loyalty Program'}',
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
softWrap: true,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(width: 16),
|
||||||
],
|
Container(
|
||||||
),
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
],
|
decoration: BoxDecoration(
|
||||||
),
|
color: AppTheme.secondaryContainer,
|
||||||
),
|
borderRadius: BorderRadius.circular(24),
|
||||||
);
|
),
|
||||||
},
|
child: Text(
|
||||||
),
|
'Gold Member',
|
||||||
|
style: Theme.of(context).textTheme.labelLarge?.copyWith(
|
||||||
|
color: AppTheme.onSecondaryContainer,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 32),
|
||||||
|
Text('Membership Code', style: Theme.of(context).textTheme.bodyMedium),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text('${card['code'] ?? 'N/A'}', style: Theme.of(context).textTheme.titleMedium),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Text('Available Points', style: Theme.of(context).textTheme.bodyMedium),
|
||||||
|
Text(
|
||||||
|
'${card['points'] ?? 0}',
|
||||||
|
style: Theme.of(context).textTheme.displayMedium?.copyWith(
|
||||||
|
color: AppTheme.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:odoo_rpc/odoo_rpc.dart';
|
||||||
import '../services/odoo_service.dart';
|
import '../services/odoo_service.dart';
|
||||||
|
import '../theme/app_theme.dart';
|
||||||
|
|
||||||
class NotificationsScreen extends StatefulWidget {
|
class NotificationsScreen extends StatefulWidget {
|
||||||
const NotificationsScreen({super.key});
|
const NotificationsScreen({super.key});
|
||||||
@ -42,9 +44,7 @@ class _NotificationsScreenState extends State<NotificationsScreen> {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() => _isLoading = false);
|
setState(() => _isLoading = false);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error loading notifications: $e')));
|
||||||
SnackBar(content: Text('Error loading notifications: $e')),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -52,23 +52,49 @@ class _NotificationsScreenState extends State<NotificationsScreen> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: const Text('Promo Notifications')),
|
appBar: AppBar(title: const Text('Notifications')),
|
||||||
body: _isLoading
|
body: _isLoading
|
||||||
? const Center(child: CircularProgressIndicator())
|
? const Center(child: CircularProgressIndicator())
|
||||||
: _notifications.isEmpty
|
: _notifications.isEmpty
|
||||||
? const Center(child: Text('No new notifications.', style: TextStyle(fontSize: 16)))
|
? const Center(child: Text('No new promos.', style: TextStyle(fontSize: 16)))
|
||||||
: ListView.builder(
|
: ListView.builder(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
||||||
itemCount: _notifications.length,
|
itemCount: _notifications.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final notif = _notifications[index];
|
final notif = _notifications[index];
|
||||||
return Card(
|
return Container(
|
||||||
|
margin: const EdgeInsets.only(bottom: 16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppTheme.surfaceContainerLow,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: AppTheme.onSurface.withOpacity(0.04),
|
||||||
|
blurRadius: 16,
|
||||||
|
offset: const Offset(0, 4),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
leading: const Icon(Icons.campaign, color: Colors.amber, size: 36),
|
contentPadding: const EdgeInsets.all(16),
|
||||||
title: Text(notif['title'] ?? 'Notice', style: const TextStyle(fontWeight: FontWeight.bold)),
|
leading: Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppTheme.primaryContainer.withOpacity(0.1),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: const Icon(Icons.star, color: AppTheme.primary),
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
notif['title'] ?? 'Notice',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
subtitle: Padding(
|
subtitle: Padding(
|
||||||
padding: const EdgeInsets.only(top: 8.0),
|
padding: const EdgeInsets.only(top: 8.0),
|
||||||
child: Text(notif['body'] ?? ''),
|
child: Text(
|
||||||
|
notif['body'] ?? '',
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
106
lib/theme/app_theme.dart
Normal file
106
lib/theme/app_theme.dart
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
|
||||||
|
class AppTheme {
|
||||||
|
// Mapan Core Tokens
|
||||||
|
static const Color primary = Color(0xFFB20000);
|
||||||
|
static const Color primaryContainer = Color(0xFFE00101);
|
||||||
|
static const Color secondary = Color(0xFF825500);
|
||||||
|
static const Color secondaryContainer = Color(0xFFFEB23D);
|
||||||
|
static const Color onSecondaryContainer = Color(0xFF6E4700);
|
||||||
|
|
||||||
|
// Surface Hierarchy
|
||||||
|
static const Color surface = Color(0xFFFCF9F8);
|
||||||
|
static const Color surfaceContainer = Color(0xFFF0EDED);
|
||||||
|
static const Color surfaceContainerLow = Color(0xFFF6F3F2);
|
||||||
|
static const Color surfaceContainerHighest = Color(0xFFE5E2E1);
|
||||||
|
|
||||||
|
// Text & On-Colors
|
||||||
|
static const Color onSurface = Color(0xFF1C1B1B);
|
||||||
|
static const Color onSurfaceVariant = Color(0xFF5E3F3A);
|
||||||
|
static const Color onPrimary = Colors.white;
|
||||||
|
|
||||||
|
/// The Signature 135-degree CTA Gradient for main buttons.
|
||||||
|
static const LinearGradient primaryGradient = LinearGradient(
|
||||||
|
colors: [primary, primaryContainer],
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight, // Approximation of 135 degrees
|
||||||
|
);
|
||||||
|
|
||||||
|
static ThemeData get lightTheme {
|
||||||
|
final baseTheme = ThemeData.light();
|
||||||
|
|
||||||
|
return ThemeData(
|
||||||
|
useMaterial3: true,
|
||||||
|
scaffoldBackgroundColor: surface,
|
||||||
|
colorScheme: const ColorScheme.light(
|
||||||
|
primary: primary,
|
||||||
|
primaryContainer: primaryContainer,
|
||||||
|
secondary: secondary,
|
||||||
|
secondaryContainer: secondaryContainer,
|
||||||
|
onSecondaryContainer: onSecondaryContainer,
|
||||||
|
surface: surface,
|
||||||
|
onSurface: onSurface,
|
||||||
|
onSurfaceVariant: onSurfaceVariant,
|
||||||
|
onPrimary: onPrimary,
|
||||||
|
error: Color(0xFFBA1A1A),
|
||||||
|
),
|
||||||
|
textTheme: baseTheme.textTheme.copyWith(
|
||||||
|
displayLarge: GoogleFonts.plusJakartaSans(color: onSurface, fontWeight: FontWeight.bold, letterSpacing: -0.5),
|
||||||
|
displayMedium: GoogleFonts.plusJakartaSans(color: onSurface, fontWeight: FontWeight.bold),
|
||||||
|
displaySmall: GoogleFonts.plusJakartaSans(color: onSurface, fontWeight: FontWeight.bold),
|
||||||
|
headlineMedium: GoogleFonts.plusJakartaSans(color: onSurface, fontWeight: FontWeight.bold),
|
||||||
|
titleLarge: GoogleFonts.beVietnamPro(color: onSurface, fontWeight: FontWeight.w600),
|
||||||
|
titleMedium: GoogleFonts.beVietnamPro(color: onSurface, fontWeight: FontWeight.w600),
|
||||||
|
titleSmall: GoogleFonts.beVietnamPro(color: onSurface, fontWeight: FontWeight.w500),
|
||||||
|
bodyLarge: GoogleFonts.beVietnamPro(color: onSurfaceVariant),
|
||||||
|
bodyMedium: GoogleFonts.beVietnamPro(color: onSurfaceVariant),
|
||||||
|
bodySmall: GoogleFonts.beVietnamPro(color: onSurfaceVariant),
|
||||||
|
labelLarge: GoogleFonts.beVietnamPro(color: onSurfaceVariant),
|
||||||
|
),
|
||||||
|
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
|
foregroundColor: onPrimary,
|
||||||
|
backgroundColor: primaryContainer, // Fallback if no gradient is used
|
||||||
|
elevation: 0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
cardTheme: CardThemeData(
|
||||||
|
color: surfaceContainerLow,
|
||||||
|
elevation: 0, // Depth created via tonal shifts
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
|
),
|
||||||
|
inputDecorationTheme: InputDecorationTheme(
|
||||||
|
filled: true,
|
||||||
|
fillColor: surfaceContainerHighest,
|
||||||
|
// Using "Ghost Border" logic at 15% opacity
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
borderSide: const BorderSide(color: Color(0x26946E68)),
|
||||||
|
),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
borderSide: const BorderSide(color: Color(0x26946E68)),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
borderSide: BorderSide(color: primary.withOpacity(0.4), width: 2),
|
||||||
|
),
|
||||||
|
labelStyle: const TextStyle(color: onSurfaceVariant),
|
||||||
|
),
|
||||||
|
appBarTheme: AppBarTheme(
|
||||||
|
backgroundColor: surface,
|
||||||
|
foregroundColor: onSurface,
|
||||||
|
elevation: 0,
|
||||||
|
surfaceTintColor: Colors.transparent,
|
||||||
|
titleTextStyle: GoogleFonts.plusJakartaSans(
|
||||||
|
color: onSurface,
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
98
pubspec.lock
98
pubspec.lock
@ -49,6 +49,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.2"
|
version: "1.1.2"
|
||||||
|
code_assets:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: code_assets
|
||||||
|
sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.0"
|
||||||
collection:
|
collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -160,6 +168,30 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
glob:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: glob
|
||||||
|
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.3"
|
||||||
|
google_fonts:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: google_fonts
|
||||||
|
sha256: db9df7a5898d894eeda4c78143f35c30a243558be439518972366880b80bf88e
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "8.0.2"
|
||||||
|
hooks:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: hooks
|
||||||
|
sha256: e79ed1e8e1929bc6ecb6ec85f0cb519c887aa5b423705ded0d0f2d9226def388
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.2"
|
||||||
http:
|
http:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -208,6 +240,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.0"
|
version: "6.1.0"
|
||||||
|
logging:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: logging
|
||||||
|
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.0"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -232,6 +272,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.17.0"
|
version: "1.17.0"
|
||||||
|
native_toolchain_c:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: native_toolchain_c
|
||||||
|
sha256: "6ba77bb18063eebe9de401f5e6437e95e1438af0a87a3a39084fbd37c90df572"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.17.6"
|
||||||
|
objective_c:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: objective_c
|
||||||
|
sha256: "100a1c87616ab6ed41ec263b083c0ef3261ee6cd1dc3b0f35f8ddfa4f996fe52"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "9.3.0"
|
||||||
odoo_rpc:
|
odoo_rpc:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -248,6 +304,30 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.1"
|
version: "1.9.1"
|
||||||
|
path_provider:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider
|
||||||
|
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.5"
|
||||||
|
path_provider_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_android
|
||||||
|
sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.22"
|
||||||
|
path_provider_foundation:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_foundation
|
||||||
|
sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.6.0"
|
||||||
path_provider_linux:
|
path_provider_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -296,6 +376,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.8"
|
version: "2.1.8"
|
||||||
|
pub_semver:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pub_semver
|
||||||
|
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
shared_preferences:
|
shared_preferences:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -493,6 +581,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.6.1"
|
version: "6.6.1"
|
||||||
|
yaml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: yaml
|
||||||
|
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.11.3 <4.0.0"
|
dart: ">=3.11.3 <4.0.0"
|
||||||
flutter: ">=3.38.1"
|
flutter: ">=3.38.4"
|
||||||
|
|||||||
@ -38,6 +38,7 @@ dependencies:
|
|||||||
workmanager: ^0.9.0+3
|
workmanager: ^0.9.0+3
|
||||||
flutter_local_notifications: ^21.0.0
|
flutter_local_notifications: ^21.0.0
|
||||||
shared_preferences: ^2.5.4
|
shared_preferences: ^2.5.4
|
||||||
|
google_fonts: ^8.0.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user