profile added
This commit is contained in:
parent
4e8e6af86e
commit
ae210a5990
@ -1,6 +1,6 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<application
|
<application
|
||||||
android:label="CB Prestige QR"
|
android:label="CB Prestige QR"
|
||||||
android:icon="@mipmap/ic_launcher">
|
android:icon="@mipmap/ic_launcher">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
package com.example.cb_prestige_qr
|
package com.example.cb_prestige_qr
|
||||||
|
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
|
||||||
class MainActivity : FlutterActivity()
|
class MainActivity : FlutterActivity()
|
||||||
|
|||||||
@ -14,10 +14,10 @@
|
|||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
<string>6.0</string>
|
<string>6.0</string>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>cb_prestige_qr</string>
|
<string>cb_prestige_qr</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
|
|||||||
@ -1,13 +1,23 @@
|
|||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
class LocalStorageService {
|
class LocalStorageService {
|
||||||
Future<void> setBool(String key, bool value) async{
|
Future<void> setBool(String key, bool value) async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
await prefs.setBool(key, value);
|
await prefs.setBool(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool?> getBool(String key) async {
|
Future<bool?> getBool(String key) async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
return prefs.getBool(key);
|
return prefs.getBool(key);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
Future<void> setString(String key, String value) async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.setString(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> getString(String key) async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
return prefs.getString(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -3,6 +3,6 @@ enum AnalysisChartMetric { scans, pointsUsed }
|
|||||||
extension AnalysisChartMetricX on AnalysisChartMetric {
|
extension AnalysisChartMetricX on AnalysisChartMetric {
|
||||||
String get label => switch (this) {
|
String get label => switch (this) {
|
||||||
AnalysisChartMetric.scans => 'Member scans',
|
AnalysisChartMetric.scans => 'Member scans',
|
||||||
AnalysisChartMetric.pointsUsed => 'Coins redeemed',
|
AnalysisChartMetric.pointsUsed => 'Points redeemed',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,16 +0,0 @@
|
|||||||
import 'package:cb_prestige_qr/core/storage/local_storage_service.dart';
|
|
||||||
|
|
||||||
class FingerprintRepository {
|
|
||||||
final LocalStorageService storage;
|
|
||||||
FingerprintRepository(this.storage);
|
|
||||||
|
|
||||||
static const key = "fingerprint_enabled";
|
|
||||||
|
|
||||||
Future<bool> load() async {
|
|
||||||
return await storage.getBool(key) ?? false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> save(bool value) async {
|
|
||||||
await storage.setBool(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
63
lib/features/auth/data/user_profile_repository.dart
Normal file
63
lib/features/auth/data/user_profile_repository.dart
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import 'package:cb_prestige_qr/core/storage/local_storage_service.dart';
|
||||||
|
import 'package:cb_prestige_qr/features/auth/domain/user_profile.dart';
|
||||||
|
|
||||||
|
class UserProfileRepository {
|
||||||
|
UserProfileRepository(this._storage);
|
||||||
|
|
||||||
|
final LocalStorageService _storage;
|
||||||
|
|
||||||
|
static const _usernameKey = 'logged_in_username';
|
||||||
|
static const _displayNameKey = 'logged_in_display_name';
|
||||||
|
static const _roleKey = 'logged_in_role';
|
||||||
|
static const _branchKey = 'logged_in_branch';
|
||||||
|
|
||||||
|
Future<UserProfile> load() async {
|
||||||
|
final username = await _storage.getString(_usernameKey) ?? 'cashier';
|
||||||
|
final displayName =
|
||||||
|
await _storage.getString(_displayNameKey) ??
|
||||||
|
_displayNameFromUsername(username);
|
||||||
|
final roleLabel = await _storage.getString(_roleKey) ?? 'Cashier';
|
||||||
|
final branchLabel =
|
||||||
|
await _storage.getString(_branchKey) ?? 'Prestige Counter';
|
||||||
|
|
||||||
|
return UserProfile(
|
||||||
|
username: username,
|
||||||
|
displayName: displayName,
|
||||||
|
roleLabel: roleLabel,
|
||||||
|
branchLabel: branchLabel,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> saveFromUsername(String username) async {
|
||||||
|
final normalizedUsername = username.trim();
|
||||||
|
final safeUsername = normalizedUsername.isEmpty
|
||||||
|
? 'cashier'
|
||||||
|
: normalizedUsername;
|
||||||
|
|
||||||
|
await _storage.setString(_usernameKey, safeUsername);
|
||||||
|
await _storage.setString(
|
||||||
|
_displayNameKey,
|
||||||
|
_displayNameFromUsername(safeUsername),
|
||||||
|
);
|
||||||
|
await _storage.setString(_roleKey, 'Cashier');
|
||||||
|
await _storage.setString(_branchKey, 'Prestige Counter');
|
||||||
|
}
|
||||||
|
|
||||||
|
static String _displayNameFromUsername(String username) {
|
||||||
|
final words = username
|
||||||
|
.replaceAll(RegExp(r'[._-]+'), ' ')
|
||||||
|
.trim()
|
||||||
|
.split(RegExp(r'\s+'))
|
||||||
|
.where((word) => word.isNotEmpty)
|
||||||
|
.toList(growable: false);
|
||||||
|
if (words.isEmpty) return 'Cashier';
|
||||||
|
|
||||||
|
return words.map(_capitalize).join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
static String _capitalize(String value) {
|
||||||
|
if (value.isEmpty) return value;
|
||||||
|
if (value.length == 1) return value.toUpperCase();
|
||||||
|
return '${value.substring(0, 1).toUpperCase()}${value.substring(1)}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,9 +0,0 @@
|
|||||||
class FingerprintState {
|
|
||||||
final bool enabled;
|
|
||||||
|
|
||||||
FingerprintState(this.enabled);
|
|
||||||
|
|
||||||
FingerprintState copyWith({bool? enabled}){
|
|
||||||
return FingerprintState(enabled ?? this.enabled);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
25
lib/features/auth/domain/user_profile.dart
Normal file
25
lib/features/auth/domain/user_profile.dart
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
class UserProfile {
|
||||||
|
const UserProfile({
|
||||||
|
required this.username,
|
||||||
|
required this.displayName,
|
||||||
|
required this.roleLabel,
|
||||||
|
required this.branchLabel,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String username;
|
||||||
|
final String displayName;
|
||||||
|
final String roleLabel;
|
||||||
|
final String branchLabel;
|
||||||
|
|
||||||
|
String get initials {
|
||||||
|
final parts = displayName
|
||||||
|
.trim()
|
||||||
|
.split(RegExp(r'\s+'))
|
||||||
|
.where((part) => part.isNotEmpty)
|
||||||
|
.toList(growable: false);
|
||||||
|
if (parts.isEmpty) return 'U';
|
||||||
|
if (parts.length == 1) return parts.first.substring(0, 1).toUpperCase();
|
||||||
|
return '${parts.first.substring(0, 1)}${parts.last.substring(0, 1)}'
|
||||||
|
.toUpperCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
|
import 'package:cb_prestige_qr/core/storage/local_storage_service.dart';
|
||||||
import 'package:cb_prestige_qr/core/utils/MainShell.dart';
|
import 'package:cb_prestige_qr/core/utils/MainShell.dart';
|
||||||
|
import 'package:cb_prestige_qr/features/auth/data/user_profile_repository.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:local_auth/local_auth.dart';
|
|
||||||
|
|
||||||
class LoginPage extends StatefulWidget {
|
class LoginPage extends StatefulWidget {
|
||||||
const LoginPage({super.key});
|
const LoginPage({super.key});
|
||||||
@ -9,33 +10,16 @@ class LoginPage extends StatefulWidget {
|
|||||||
State<LoginPage> createState() => _LoginPageState();
|
State<LoginPage> createState() => _LoginPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
enum _SupportState { unknown, supported, unsupported }
|
|
||||||
|
|
||||||
class _LoginPageState extends State<LoginPage> {
|
class _LoginPageState extends State<LoginPage> {
|
||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
final _usernameController = TextEditingController();
|
final _usernameController = TextEditingController();
|
||||||
final _passwordController = TextEditingController();
|
final _passwordController = TextEditingController();
|
||||||
final LocalAuthentication auth = LocalAuthentication();
|
final UserProfileRepository _userProfileRepository = UserProfileRepository(
|
||||||
bool? _canCheckBiometric;
|
LocalStorageService(),
|
||||||
List<BiometricType>? _availableBiometrics;
|
);
|
||||||
String _authorized = "Not Authorized";
|
|
||||||
bool _isAuthenticating = false;
|
|
||||||
bool _isSigningIn = false;
|
bool _isSigningIn = false;
|
||||||
_SupportState _supportState = _SupportState.unknown;
|
|
||||||
|
|
||||||
var _obscurePassword = true;
|
var _obscurePassword = true;
|
||||||
|
|
||||||
@override
|
|
||||||
void initState(){
|
|
||||||
super.initState();
|
|
||||||
auth.isDeviceSupported().then(
|
|
||||||
(bool isSupported) => setState(
|
|
||||||
() => _supportState = isSupported
|
|
||||||
? _SupportState.supported
|
|
||||||
: _SupportState.unsupported,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
@ -56,9 +40,15 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
Navigator.of(context).pushReplacement(
|
await _userProfileRepository.saveFromUsername(
|
||||||
MaterialPageRoute(builder: (_) => const MainShell()),
|
_usernameController.text.trim(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
|
Navigator.of(
|
||||||
|
context,
|
||||||
|
).pushReplacement(MaterialPageRoute(builder: (_) => const MainShell()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -72,11 +62,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
begin: Alignment.topCenter,
|
begin: Alignment.topCenter,
|
||||||
end: Alignment.bottomCenter,
|
end: Alignment.bottomCenter,
|
||||||
colors: [
|
colors: [Color(0xff17181c), Color(0xff25262b), Color(0xff2c2d33)],
|
||||||
Color(0xff17181c),
|
|
||||||
Color(0xff25262b),
|
|
||||||
Color(0xff2c2d33),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
@ -88,12 +74,14 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(24),
|
padding: const EdgeInsets.all(24),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(0xff1d1e23).withOpacity(0.92),
|
color: const Color(0xff1d1e23).withValues(alpha: 0.92),
|
||||||
borderRadius: BorderRadius.circular(28),
|
borderRadius: BorderRadius.circular(28),
|
||||||
border: Border.all(color: Colors.white.withOpacity(0.08)),
|
border: Border.all(
|
||||||
|
color: Colors.white.withValues(alpha: 0.08),
|
||||||
|
),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black.withOpacity(0.28),
|
color: Colors.black.withValues(alpha: 0.28),
|
||||||
blurRadius: 28,
|
blurRadius: 28,
|
||||||
offset: const Offset(0, 18),
|
offset: const Offset(0, 18),
|
||||||
),
|
),
|
||||||
@ -111,7 +99,9 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
height: 56,
|
height: 56,
|
||||||
width: 56,
|
width: 56,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: colorScheme.primary.withOpacity(0.16),
|
color: colorScheme.primary.withValues(
|
||||||
|
alpha: 0.16,
|
||||||
|
),
|
||||||
borderRadius: BorderRadius.circular(18),
|
borderRadius: BorderRadius.circular(18),
|
||||||
),
|
),
|
||||||
child: const Icon(
|
child: const Icon(
|
||||||
@ -151,10 +141,10 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.all(18),
|
padding: const EdgeInsets.all(18),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white.withOpacity(0.04),
|
color: Colors.white.withValues(alpha: 0.04),
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: Colors.white.withOpacity(0.06),
|
color: Colors.white.withValues(alpha: 0.06),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
|||||||
@ -1,33 +0,0 @@
|
|||||||
import 'package:cb_prestige_qr/core/storage/local_storage_service.dart';
|
|
||||||
import 'package:cb_prestige_qr/features/auth/data/fingerprint_repository.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:hooks_riverpod/legacy.dart';
|
|
||||||
|
|
||||||
|
|
||||||
final storageProvider = Provider((ref) => LocalStorageService());
|
|
||||||
|
|
||||||
final fingerprintRepoProvider = Provider((ref) {
|
|
||||||
return FingerprintRepository(ref.read(storageProvider));
|
|
||||||
});
|
|
||||||
|
|
||||||
final fingerprintProvider =
|
|
||||||
StateNotifierProvider<FingerprintNotifier, bool>((ref) {
|
|
||||||
return FingerprintNotifier(ref.read(fingerprintRepoProvider));
|
|
||||||
});
|
|
||||||
|
|
||||||
class FingerprintNotifier extends StateNotifier<bool> {
|
|
||||||
final FingerprintRepository repo;
|
|
||||||
|
|
||||||
FingerprintNotifier(this.repo) : super(false) {
|
|
||||||
_init();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _init() async {
|
|
||||||
state = await repo.load();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> toggle(bool value) async {
|
|
||||||
state = value;
|
|
||||||
await repo.save(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,41 +1,41 @@
|
|||||||
|
import 'package:cb_prestige_qr/core/storage/local_storage_service.dart';
|
||||||
|
import 'package:cb_prestige_qr/features/auth/data/user_profile_repository.dart';
|
||||||
|
|
||||||
import '../../domain/entities/settings_content.dart';
|
import '../../domain/entities/settings_content.dart';
|
||||||
|
|
||||||
abstract class SettingsLocalDataSource {
|
abstract class SettingsLocalDataSource {
|
||||||
SettingsContent getSettings();
|
Future<SettingsContent> getSettings();
|
||||||
void setNotificationsEnabled(bool value);
|
Future<void> setNotificationsEnabled(bool value);
|
||||||
void setHapticsEnabled(bool value);
|
Future<void> setHapticsEnabled(bool value);
|
||||||
void setFingerEnabled(bool value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class SettingsLocalDataSourceImpl implements SettingsLocalDataSource {
|
class SettingsLocalDataSourceImpl implements SettingsLocalDataSource {
|
||||||
SettingsLocalDataSourceImpl();
|
SettingsLocalDataSourceImpl(this._storage);
|
||||||
|
|
||||||
|
final LocalStorageService _storage;
|
||||||
|
|
||||||
bool _notificationsEnabled = true;
|
bool _notificationsEnabled = true;
|
||||||
bool _hapticsEnabled = true;
|
bool _hapticsEnabled = true;
|
||||||
bool _fingerEnabled = true;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
SettingsContent getSettings() {
|
Future<SettingsContent> getSettings() async {
|
||||||
|
final userProfile = await UserProfileRepository(_storage).load();
|
||||||
|
|
||||||
return SettingsContent(
|
return SettingsContent(
|
||||||
notificationsEnabled: _notificationsEnabled,
|
notificationsEnabled: _notificationsEnabled,
|
||||||
hapticsEnabled: _hapticsEnabled,
|
hapticsEnabled: _hapticsEnabled,
|
||||||
fingerEnabled: _fingerEnabled,
|
|
||||||
appVersionLabel: 'v1.0.0',
|
appVersionLabel: 'v1.0.0',
|
||||||
|
userProfile: userProfile,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void setNotificationsEnabled(bool value) {
|
Future<void> setNotificationsEnabled(bool value) async {
|
||||||
_notificationsEnabled = value;
|
_notificationsEnabled = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void setHapticsEnabled(bool value) {
|
Future<void> setHapticsEnabled(bool value) async {
|
||||||
_hapticsEnabled = value;
|
_hapticsEnabled = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void setFingerEnabled(bool value) {
|
|
||||||
_fingerEnabled = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,20 +8,15 @@ class SettingsRepositoryImpl implements SettingsRepository {
|
|||||||
final SettingsLocalDataSource _localDataSource;
|
final SettingsLocalDataSource _localDataSource;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<SettingsContent> getSettings() async => _localDataSource.getSettings();
|
Future<SettingsContent> getSettings() => _localDataSource.getSettings();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> setNotificationsEnabled(bool value) async {
|
Future<void> setNotificationsEnabled(bool value) async {
|
||||||
_localDataSource.setNotificationsEnabled(value);
|
await _localDataSource.setNotificationsEnabled(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> setHapticsEnabled(bool value) async {
|
Future<void> setHapticsEnabled(bool value) async {
|
||||||
_localDataSource.setHapticsEnabled(value);
|
await _localDataSource.setHapticsEnabled(value);
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> setFingerEnabled(bool value) async {
|
|
||||||
_localDataSource.setFingerEnabled(value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,15 @@
|
|||||||
|
import 'package:cb_prestige_qr/features/auth/domain/user_profile.dart';
|
||||||
|
|
||||||
class SettingsContent {
|
class SettingsContent {
|
||||||
const SettingsContent({
|
const SettingsContent({
|
||||||
required this.notificationsEnabled,
|
required this.notificationsEnabled,
|
||||||
required this.hapticsEnabled,
|
required this.hapticsEnabled,
|
||||||
required this.fingerEnabled,
|
|
||||||
required this.appVersionLabel,
|
required this.appVersionLabel,
|
||||||
|
required this.userProfile,
|
||||||
});
|
});
|
||||||
|
|
||||||
final bool notificationsEnabled;
|
final bool notificationsEnabled;
|
||||||
final bool hapticsEnabled;
|
final bool hapticsEnabled;
|
||||||
final bool fingerEnabled;
|
|
||||||
final String appVersionLabel;
|
final String appVersionLabel;
|
||||||
|
final UserProfile userProfile;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,5 +4,4 @@ abstract class SettingsRepository {
|
|||||||
Future<SettingsContent> getSettings();
|
Future<SettingsContent> getSettings();
|
||||||
Future<void> setNotificationsEnabled(bool value);
|
Future<void> setNotificationsEnabled(bool value);
|
||||||
Future<void> setHapticsEnabled(bool value);
|
Future<void> setHapticsEnabled(bool value);
|
||||||
Future<void> setFingerEnabled(bool value);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +0,0 @@
|
|||||||
import '../repositories/settings_repository.dart';
|
|
||||||
|
|
||||||
class SetFingerEnabled {
|
|
||||||
const SetFingerEnabled(this._repository);
|
|
||||||
|
|
||||||
final SettingsRepository _repository;
|
|
||||||
|
|
||||||
Future<void> call(bool value) => _repository.setFingerEnabled(value);
|
|
||||||
}
|
|
||||||
@ -1,30 +1,32 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
import '../../../auth/domain/user_profile.dart';
|
||||||
|
|
||||||
@immutable
|
@immutable
|
||||||
class SettingsUiState {
|
class SettingsUiState {
|
||||||
const SettingsUiState({
|
const SettingsUiState({
|
||||||
required this.notificationsEnabled,
|
required this.notificationsEnabled,
|
||||||
required this.hapticsEnabled,
|
required this.hapticsEnabled,
|
||||||
required this.fingerEnabled,
|
|
||||||
required this.appVersionLabel,
|
required this.appVersionLabel,
|
||||||
|
required this.userProfile,
|
||||||
});
|
});
|
||||||
|
|
||||||
final bool notificationsEnabled;
|
final bool notificationsEnabled;
|
||||||
final bool hapticsEnabled;
|
final bool hapticsEnabled;
|
||||||
final bool fingerEnabled;
|
|
||||||
final String appVersionLabel;
|
final String appVersionLabel;
|
||||||
|
final UserProfile userProfile;
|
||||||
|
|
||||||
SettingsUiState copyWith({
|
SettingsUiState copyWith({
|
||||||
bool? notificationsEnabled,
|
bool? notificationsEnabled,
|
||||||
bool? hapticsEnabled,
|
bool? hapticsEnabled,
|
||||||
bool? fingerEnabled,
|
|
||||||
String? appVersionLabel,
|
String? appVersionLabel,
|
||||||
|
UserProfile? userProfile,
|
||||||
}) {
|
}) {
|
||||||
return SettingsUiState(
|
return SettingsUiState(
|
||||||
notificationsEnabled: notificationsEnabled ?? this.notificationsEnabled,
|
notificationsEnabled: notificationsEnabled ?? this.notificationsEnabled,
|
||||||
hapticsEnabled: hapticsEnabled ?? this.hapticsEnabled,
|
hapticsEnabled: hapticsEnabled ?? this.hapticsEnabled,
|
||||||
fingerEnabled: fingerEnabled ?? this.fingerEnabled,
|
|
||||||
appVersionLabel: appVersionLabel ?? this.appVersionLabel,
|
appVersionLabel: appVersionLabel ?? this.appVersionLabel,
|
||||||
|
userProfile: userProfile ?? this.userProfile,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +1,21 @@
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
|
||||||
|
import '../../../../core/storage/local_storage_service.dart';
|
||||||
import '../../data/data_sources/settings_local_data_source.dart';
|
import '../../data/data_sources/settings_local_data_source.dart';
|
||||||
import '../../data/repositories/settings_repository_impl.dart';
|
import '../../data/repositories/settings_repository_impl.dart';
|
||||||
import '../../domain/entities/settings_content.dart';
|
import '../../domain/entities/settings_content.dart';
|
||||||
import '../../domain/repositories/settings_repository.dart';
|
import '../../domain/repositories/settings_repository.dart';
|
||||||
import '../../domain/use_cases/get_settings.dart';
|
import '../../domain/use_cases/get_settings.dart';
|
||||||
import '../../domain/use_cases/set_finger_enabled.dart';
|
|
||||||
import '../../domain/use_cases/set_haptics_enabled.dart';
|
import '../../domain/use_cases/set_haptics_enabled.dart';
|
||||||
import '../../domain/use_cases/set_notifications_enabled.dart';
|
import '../../domain/use_cases/set_notifications_enabled.dart';
|
||||||
import 'settings_ui_state.dart';
|
import 'settings_ui_state.dart';
|
||||||
|
|
||||||
|
final _localStorageServiceProvider = Provider<LocalStorageService>(
|
||||||
|
(ref) => LocalStorageService(),
|
||||||
|
);
|
||||||
|
|
||||||
final _settingsLocalDataSourceProvider = Provider<SettingsLocalDataSource>(
|
final _settingsLocalDataSourceProvider = Provider<SettingsLocalDataSource>(
|
||||||
(ref) => SettingsLocalDataSourceImpl(),
|
(ref) => SettingsLocalDataSourceImpl(ref.watch(_localStorageServiceProvider)),
|
||||||
);
|
);
|
||||||
|
|
||||||
final _settingsRepositoryProvider = Provider<SettingsRepository>(
|
final _settingsRepositoryProvider = Provider<SettingsRepository>(
|
||||||
@ -30,10 +34,6 @@ final _setHapticsEnabledProvider = Provider<SetHapticsEnabled>(
|
|||||||
(ref) => SetHapticsEnabled(ref.watch(_settingsRepositoryProvider)),
|
(ref) => SetHapticsEnabled(ref.watch(_settingsRepositoryProvider)),
|
||||||
);
|
);
|
||||||
|
|
||||||
final _setFingerEnabledProvider = Provider<SetFingerEnabled>(
|
|
||||||
(ref) => SetFingerEnabled(ref.watch(_settingsRepositoryProvider)),
|
|
||||||
);
|
|
||||||
|
|
||||||
final settingsViewModelProvider =
|
final settingsViewModelProvider =
|
||||||
AsyncNotifierProvider<SettingsViewModel, SettingsUiState>(
|
AsyncNotifierProvider<SettingsViewModel, SettingsUiState>(
|
||||||
SettingsViewModel.new,
|
SettingsViewModel.new,
|
||||||
@ -50,8 +50,8 @@ class SettingsViewModel extends AsyncNotifier<SettingsUiState> {
|
|||||||
return SettingsUiState(
|
return SettingsUiState(
|
||||||
notificationsEnabled: content.notificationsEnabled,
|
notificationsEnabled: content.notificationsEnabled,
|
||||||
hapticsEnabled: content.hapticsEnabled,
|
hapticsEnabled: content.hapticsEnabled,
|
||||||
fingerEnabled: content.fingerEnabled,
|
|
||||||
appVersionLabel: content.appVersionLabel,
|
appVersionLabel: content.appVersionLabel,
|
||||||
|
userProfile: content.userProfile,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,9 +64,4 @@ class SettingsViewModel extends AsyncNotifier<SettingsUiState> {
|
|||||||
await ref.watch(_setHapticsEnabledProvider)(value);
|
await ref.watch(_setHapticsEnabledProvider)(value);
|
||||||
state = state.whenData((s) => s.copyWith(hapticsEnabled: value));
|
state = state.whenData((s) => s.copyWith(hapticsEnabled: value));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> toggleFinger(bool value) async {
|
|
||||||
await ref.watch(_setFingerEnabledProvider)(value);
|
|
||||||
state = state.whenData((s) => s.copyWith(fingerEnabled: value));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import 'package:cb_prestige_qr/features/auth/presentation/pages/login_page.dart'
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
|
||||||
|
import '../../../auth/domain/user_profile.dart';
|
||||||
import '../manager/settings_ui_state.dart';
|
import '../manager/settings_ui_state.dart';
|
||||||
import '../manager/settings_view_model.dart';
|
import '../manager/settings_view_model.dart';
|
||||||
|
|
||||||
@ -29,7 +30,6 @@ class SettingsPage extends ConsumerWidget {
|
|||||||
state: state,
|
state: state,
|
||||||
onNotificationsChanged: viewModel.toggleNotifications,
|
onNotificationsChanged: viewModel.toggleNotifications,
|
||||||
onHapticsChanged: viewModel.toggleHaptics,
|
onHapticsChanged: viewModel.toggleHaptics,
|
||||||
onFingerChanged: viewModel.toggleFinger,
|
|
||||||
onLogout: () {
|
onLogout: () {
|
||||||
ref.read(navIndexNotifierProvider.notifier).setIndex(0);
|
ref.read(navIndexNotifierProvider.notifier).setIndex(0);
|
||||||
Navigator.of(context).pushAndRemoveUntil(
|
Navigator.of(context).pushAndRemoveUntil(
|
||||||
@ -55,14 +55,12 @@ class _SettingsBody extends StatelessWidget {
|
|||||||
required this.state,
|
required this.state,
|
||||||
required this.onNotificationsChanged,
|
required this.onNotificationsChanged,
|
||||||
required this.onHapticsChanged,
|
required this.onHapticsChanged,
|
||||||
required this.onFingerChanged,
|
|
||||||
required this.onLogout,
|
required this.onLogout,
|
||||||
});
|
});
|
||||||
|
|
||||||
final SettingsUiState state;
|
final SettingsUiState state;
|
||||||
final ValueChanged<bool> onNotificationsChanged;
|
final ValueChanged<bool> onNotificationsChanged;
|
||||||
final ValueChanged<bool> onHapticsChanged;
|
final ValueChanged<bool> onHapticsChanged;
|
||||||
final ValueChanged<bool> onFingerChanged;
|
|
||||||
final VoidCallback onLogout;
|
final VoidCallback onLogout;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -70,6 +68,8 @@ class _SettingsBody extends StatelessWidget {
|
|||||||
return ListView(
|
return ListView(
|
||||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 24),
|
padding: const EdgeInsets.fromLTRB(16, 12, 16, 24),
|
||||||
children: [
|
children: [
|
||||||
|
_ProfileCard(profile: state.userProfile),
|
||||||
|
const SizedBox(height: 12),
|
||||||
_SectionCard(
|
_SectionCard(
|
||||||
title: 'Preferences',
|
title: 'Preferences',
|
||||||
children: [
|
children: [
|
||||||
@ -86,13 +86,6 @@ class _SettingsBody extends StatelessWidget {
|
|||||||
title: const Text('Haptics'),
|
title: const Text('Haptics'),
|
||||||
subtitle: const Text('Vibration feedback'),
|
subtitle: const Text('Vibration feedback'),
|
||||||
),
|
),
|
||||||
const Divider(height: 1),
|
|
||||||
SwitchListTile.adaptive(
|
|
||||||
value: state.fingerEnabled,
|
|
||||||
onChanged: onFingerChanged,
|
|
||||||
title: const Text('Fingerprint'),
|
|
||||||
subtitle: const Text('Enable fingerprint authentication'),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
@ -112,6 +105,134 @@ class _SettingsBody extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _ProfileCard extends StatelessWidget {
|
||||||
|
const _ProfileCard({required this.profile});
|
||||||
|
|
||||||
|
final UserProfile profile;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
|
final textTheme = Theme.of(context).textTheme;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: colorScheme.surface,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
border: Border.all(
|
||||||
|
color: colorScheme.outlineVariant.withValues(alpha: 0.5),
|
||||||
|
),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withValues(alpha: 0.06),
|
||||||
|
blurRadius: 14,
|
||||||
|
offset: const Offset(0, 6),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 54,
|
||||||
|
height: 54,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: colorScheme.primaryContainer,
|
||||||
|
borderRadius: BorderRadius.circular(18),
|
||||||
|
),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Text(
|
||||||
|
profile.initials,
|
||||||
|
style: textTheme.titleLarge?.copyWith(
|
||||||
|
color: colorScheme.onPrimaryContainer,
|
||||||
|
fontWeight: FontWeight.w900,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 14),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
profile.displayName,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w800,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 3),
|
||||||
|
Text(
|
||||||
|
'@${profile.username}',
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: textTheme.bodySmall?.copyWith(
|
||||||
|
color: colorScheme.onSurfaceVariant,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Wrap(
|
||||||
|
spacing: 8,
|
||||||
|
runSpacing: 8,
|
||||||
|
children: [
|
||||||
|
_ProfileChip(
|
||||||
|
icon: Icons.badge_rounded,
|
||||||
|
label: profile.roleLabel,
|
||||||
|
),
|
||||||
|
_ProfileChip(
|
||||||
|
icon: Icons.store_rounded,
|
||||||
|
label: profile.branchLabel,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ProfileChip extends StatelessWidget {
|
||||||
|
const _ProfileChip({required this.icon, required this.label});
|
||||||
|
|
||||||
|
final IconData icon;
|
||||||
|
final String label;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
|
final textTheme = Theme.of(context).textTheme;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 9, vertical: 6),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: colorScheme.surfaceContainerHighest.withValues(alpha: 0.55),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(icon, size: 15, color: colorScheme.onSurfaceVariant),
|
||||||
|
const SizedBox(width: 5),
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: textTheme.labelMedium?.copyWith(
|
||||||
|
color: colorScheme.onSurfaceVariant,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _LogoutButton extends StatelessWidget {
|
class _LogoutButton extends StatelessWidget {
|
||||||
const _LogoutButton({required this.onPressed});
|
const _LogoutButton({required this.onPressed});
|
||||||
|
|
||||||
@ -157,10 +278,12 @@ class _SectionCard extends StatelessWidget {
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: colorScheme.surface,
|
color: colorScheme.surface,
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
border: Border.all(color: colorScheme.outlineVariant.withOpacity(0.5)),
|
border: Border.all(
|
||||||
|
color: colorScheme.outlineVariant.withValues(alpha: 0.5),
|
||||||
|
),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black.withOpacity(0.06),
|
color: Colors.black.withValues(alpha: 0.06),
|
||||||
blurRadius: 14,
|
blurRadius: 14,
|
||||||
offset: const Offset(0, 6),
|
offset: const Offset(0, 6),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -40,8 +40,7 @@ dependencies:
|
|||||||
riverpod_annotation: ^4.0.2
|
riverpod_annotation: ^4.0.2
|
||||||
carousel_slider: ^5.1.2
|
carousel_slider: ^5.1.2
|
||||||
mobile_scanner: ^7.2.0
|
mobile_scanner: ^7.2.0
|
||||||
local_auth: ^3.0.1
|
shared_preferences: ^2.5.5
|
||||||
shared_preferences: ^2.5.5
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user