chnages has been made
This commit is contained in:
@@ -19,10 +19,14 @@ class ApiClient {
|
||||
dio.interceptors.add(
|
||||
InterceptorsWrapper(
|
||||
onRequest: (options, handler) async {
|
||||
// Get token from SharedPreferences
|
||||
final token = sharedPreferences?.getString(_tokenKey);
|
||||
if (token != null && token.isNotEmpty) {
|
||||
options.headers['Authorization'] = 'Bearer $token';
|
||||
// Skip auth if the request explicitly opts out
|
||||
final skipAuth = options.extra['skipAuth'] == true;
|
||||
if (!skipAuth) {
|
||||
// Get token from SharedPreferences
|
||||
final token = sharedPreferences?.getString(_tokenKey);
|
||||
if (token != null && token.isNotEmpty) {
|
||||
options.headers['Authorization'] = 'Bearer $token';
|
||||
}
|
||||
}
|
||||
return handler.next(options);
|
||||
},
|
||||
|
||||
@@ -13,11 +13,13 @@ abstract class AttendanceRemoteDataSource {
|
||||
Future<AttendanceResponseDto> login({
|
||||
required String employeeId,
|
||||
required File faceImage,
|
||||
bool localAuth = false,
|
||||
});
|
||||
|
||||
Future<AttendanceResponseDto> logout({
|
||||
required String employeeId,
|
||||
required File faceImage,
|
||||
bool localAuth = false,
|
||||
});
|
||||
|
||||
Future<List<AttendanceRecordDto>> getAttendanceRecords({
|
||||
@@ -43,11 +45,13 @@ class AttendanceRemoteDataSourceImpl implements AttendanceRemoteDataSource {
|
||||
Future<AttendanceResponseDto> login({
|
||||
required String employeeId,
|
||||
required File faceImage,
|
||||
bool localAuth = false,
|
||||
}) async {
|
||||
try {
|
||||
final formData = FormData.fromMap({
|
||||
'EmployeeId': employeeId,
|
||||
'FaceImage': await MultipartFile.fromFile(faceImage.path),
|
||||
'IsAuth': localAuth.toString(),
|
||||
});
|
||||
|
||||
final response = await apiClient.post(
|
||||
@@ -107,11 +111,13 @@ class AttendanceRemoteDataSourceImpl implements AttendanceRemoteDataSource {
|
||||
Future<AttendanceResponseDto> logout({
|
||||
required String employeeId,
|
||||
required File faceImage,
|
||||
bool localAuth = false,
|
||||
}) async {
|
||||
try {
|
||||
final formData = FormData.fromMap({
|
||||
'EmployeeId': employeeId,
|
||||
'FaceImage': await MultipartFile.fromFile(faceImage.path),
|
||||
'IsAuth': localAuth.toString(),
|
||||
});
|
||||
|
||||
final response = await apiClient.post(
|
||||
|
||||
49
lib/data/datasources/theme_remote_data_source.dart
Normal file
49
lib/data/datasources/theme_remote_data_source.dart
Normal file
@@ -0,0 +1,49 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import '../../core/error/exceptions.dart';
|
||||
import '../../core/network/api_client.dart';
|
||||
import '../dto/theme_response_dto.dart';
|
||||
|
||||
abstract class ThemeRemoteDataSource {
|
||||
Future<ThemeDataDto> getTheme();
|
||||
}
|
||||
|
||||
class ThemeRemoteDataSourceImpl implements ThemeRemoteDataSource {
|
||||
final ApiClient apiClient;
|
||||
|
||||
ThemeRemoteDataSourceImpl({required this.apiClient});
|
||||
|
||||
@override
|
||||
Future<ThemeDataDto> getTheme() async {
|
||||
try {
|
||||
debugPrint('[ThemeDataSource] Calling GET /Theme (with auth)...');
|
||||
|
||||
final res = await apiClient.get('/Theme'); // ✅ no custom headers
|
||||
|
||||
debugPrint('[ThemeDataSource] Status: ${res.statusCode}');
|
||||
debugPrint('[ThemeDataSource] Data: ${res.data}');
|
||||
|
||||
if (res.statusCode == 200) {
|
||||
final dto = ThemeResponseDto.fromJson(
|
||||
Map<String, dynamic>.from(res.data),
|
||||
);
|
||||
|
||||
if (dto.isSuccess && dto.data != null) {
|
||||
debugPrint('[ThemeDataSource] ✅ logo = ${dto.data!.logo}');
|
||||
return dto.data!;
|
||||
}
|
||||
|
||||
throw ServerException(message: dto.message ?? 'Theme request failed');
|
||||
}
|
||||
|
||||
throw ServerException(
|
||||
message: 'Theme request failed (code ${res.statusCode})',
|
||||
);
|
||||
} on ServerException {
|
||||
rethrow;
|
||||
} catch (e, stack) {
|
||||
debugPrint('[ThemeDataSource] ❌ Exception: $e');
|
||||
debugPrint('[ThemeDataSource] ❌ Stack: $stack');
|
||||
throw ServerException(message: e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
36
lib/data/dto/theme_response_dto.dart
Normal file
36
lib/data/dto/theme_response_dto.dart
Normal file
@@ -0,0 +1,36 @@
|
||||
class ThemeResponseDto {
|
||||
final int statusCode;
|
||||
final bool isSuccess;
|
||||
final String? message;
|
||||
final ThemeDataDto? data;
|
||||
|
||||
ThemeResponseDto({
|
||||
required this.statusCode,
|
||||
required this.isSuccess,
|
||||
required this.message,
|
||||
required this.data,
|
||||
});
|
||||
|
||||
factory ThemeResponseDto.fromJson(Map<String, dynamic> json) {
|
||||
return ThemeResponseDto(
|
||||
statusCode: json['statusCode'] ?? 0,
|
||||
isSuccess: json['isSuccess'] ?? false,
|
||||
message: json['message']?.toString(),
|
||||
data: json['data'] == null ? null : ThemeDataDto.fromJson(json['data']),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ThemeDataDto {
|
||||
final String name;
|
||||
final String logo;
|
||||
|
||||
ThemeDataDto({required this.name, required this.logo});
|
||||
|
||||
factory ThemeDataDto.fromJson(Map<String, dynamic> json) {
|
||||
return ThemeDataDto(
|
||||
name: (json['name'] ?? '').toString(),
|
||||
logo: (json['logo'] ?? '').toString(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ class AttendanceRepositoryImpl implements AttendanceRepository {
|
||||
final dto = await remoteDataSource.login(
|
||||
employeeId: request.employeeId,
|
||||
faceImage: request.faceImage,
|
||||
localAuth: request.localAuth,
|
||||
);
|
||||
|
||||
return AttendanceResponseModel(
|
||||
@@ -34,6 +35,7 @@ class AttendanceRepositoryImpl implements AttendanceRepository {
|
||||
final dto = await remoteDataSource.logout(
|
||||
employeeId: request.employeeId,
|
||||
faceImage: request.faceImage,
|
||||
localAuth: request.localAuth,
|
||||
);
|
||||
|
||||
return AttendanceResponseModel(
|
||||
|
||||
24
lib/data/repositories/theme_repository_impl.dart
Normal file
24
lib/data/repositories/theme_repository_impl.dart
Normal file
@@ -0,0 +1,24 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import '../../core/error/failures.dart';
|
||||
import '../../core/error/exceptions.dart';
|
||||
import '../../domain/models/theme_model.dart';
|
||||
import '../../domain/repositories/theme_repository.dart';
|
||||
import '../datasources/theme_remote_data_source.dart';
|
||||
|
||||
class ThemeRepositoryImpl implements ThemeRepository {
|
||||
final ThemeRemoteDataSource remote;
|
||||
|
||||
ThemeRepositoryImpl({required this.remote});
|
||||
|
||||
@override
|
||||
Future<Either<Failure, ThemeModel>> getTheme() async {
|
||||
try {
|
||||
final dto = await remote.getTheme();
|
||||
return Right(ThemeModel(name: dto.name, logo: dto.logo));
|
||||
} on ServerException catch (e) {
|
||||
return Left(ServerFailure(e.message));
|
||||
} catch (e) {
|
||||
return Left(ServerFailure(e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,11 @@ import 'dart:io';
|
||||
class AttendanceLoginRequest {
|
||||
final String employeeId;
|
||||
final File faceImage;
|
||||
final bool localAuth;
|
||||
|
||||
AttendanceLoginRequest({required this.employeeId, required this.faceImage});
|
||||
AttendanceLoginRequest({
|
||||
required this.employeeId,
|
||||
required this.faceImage,
|
||||
this.localAuth = false,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,6 +3,11 @@ import 'dart:io';
|
||||
class AttendanceLogoutRequest {
|
||||
final String employeeId;
|
||||
final File faceImage;
|
||||
final bool localAuth;
|
||||
|
||||
AttendanceLogoutRequest({required this.employeeId, required this.faceImage});
|
||||
AttendanceLogoutRequest({
|
||||
required this.employeeId,
|
||||
required this.faceImage,
|
||||
this.localAuth = false,
|
||||
});
|
||||
}
|
||||
|
||||
6
lib/domain/models/theme_model.dart
Normal file
6
lib/domain/models/theme_model.dart
Normal file
@@ -0,0 +1,6 @@
|
||||
class ThemeModel {
|
||||
final String name;
|
||||
final String logo; // filename or url
|
||||
|
||||
const ThemeModel({required this.name, required this.logo});
|
||||
}
|
||||
7
lib/domain/repositories/theme_repository.dart
Normal file
7
lib/domain/repositories/theme_repository.dart
Normal file
@@ -0,0 +1,7 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import '../../core/error/failures.dart';
|
||||
import '../models/theme_model.dart';
|
||||
|
||||
abstract class ThemeRepository {
|
||||
Future<Either<Failure, ThemeModel>> getTheme();
|
||||
}
|
||||
11
lib/domain/usecases/get_theme_usecase.dart
Normal file
11
lib/domain/usecases/get_theme_usecase.dart
Normal file
@@ -0,0 +1,11 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import '../../core/error/failures.dart';
|
||||
import '../models/theme_model.dart';
|
||||
import '../repositories/theme_repository.dart';
|
||||
|
||||
class GetThemeUseCase {
|
||||
final ThemeRepository repo;
|
||||
GetThemeUseCase(this.repo);
|
||||
|
||||
Future<Either<Failure, ThemeModel>> call() => repo.getTheme();
|
||||
}
|
||||
56
lib/presentation/blocs/theme/theme_cubit.dart
Normal file
56
lib/presentation/blocs/theme/theme_cubit.dart
Normal file
@@ -0,0 +1,56 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../../domain/usecases/get_theme_usecase.dart';
|
||||
import 'theme_state.dart';
|
||||
|
||||
class ThemeCubit extends Cubit<ThemeState> {
|
||||
final GetThemeUseCase getThemeUseCase;
|
||||
|
||||
/// Base URL for loading theme images (logo, etc.)
|
||||
static const String _imageBaseUrl = 'https://hrm.go.iq/Images/';
|
||||
|
||||
/// Guard against re-entrant / duplicate calls
|
||||
bool _isLoading = false;
|
||||
|
||||
ThemeCubit({required this.getThemeUseCase}) : super(const ThemeInitial());
|
||||
|
||||
/// Load theme. Set [forceReload] to true after login to refresh.
|
||||
Future<void> loadTheme({bool forceReload = false}) async {
|
||||
// Prevent duplicate concurrent calls
|
||||
if (_isLoading) {
|
||||
debugPrint('[ThemeCubit] loadTheme() skipped — already loading');
|
||||
return;
|
||||
}
|
||||
|
||||
// If already loaded and not forced, skip
|
||||
if (!forceReload && state is ThemeLoaded) {
|
||||
debugPrint('[ThemeCubit] loadTheme() skipped — already loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
_isLoading = true;
|
||||
debugPrint('[ThemeCubit] loadTheme() called (forceReload=$forceReload)');
|
||||
emit(const ThemeLoading());
|
||||
|
||||
final result = await getThemeUseCase();
|
||||
|
||||
_isLoading = false;
|
||||
|
||||
result.fold(
|
||||
(failure) {
|
||||
debugPrint('[ThemeCubit] ❌ FAILED: ${failure.message}');
|
||||
emit(ThemeError(failure.message));
|
||||
},
|
||||
(theme) {
|
||||
debugPrint(
|
||||
'[ThemeCubit] ✅ Got theme — name: ${theme.name}, logo: ${theme.logo}',
|
||||
);
|
||||
// Build the full logo URL: https://hrm.go.iq/Images/{filename}
|
||||
final encodedLogo = Uri.encodeFull(theme.logo);
|
||||
final logoUrl = '$_imageBaseUrl$encodedLogo';
|
||||
debugPrint('[ThemeCubit] 🖼️ Logo URL: $logoUrl');
|
||||
emit(ThemeLoaded(theme: theme, logoUrl: logoUrl));
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
36
lib/presentation/blocs/theme/theme_state.dart
Normal file
36
lib/presentation/blocs/theme/theme_state.dart
Normal file
@@ -0,0 +1,36 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import '../../../domain/models/theme_model.dart';
|
||||
|
||||
abstract class ThemeState extends Equatable {
|
||||
const ThemeState();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class ThemeInitial extends ThemeState {
|
||||
const ThemeInitial();
|
||||
}
|
||||
|
||||
class ThemeLoading extends ThemeState {
|
||||
const ThemeLoading();
|
||||
}
|
||||
|
||||
class ThemeLoaded extends ThemeState {
|
||||
final ThemeModel theme;
|
||||
final String logoUrl;
|
||||
|
||||
const ThemeLoaded({required this.theme, required this.logoUrl});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [theme, logoUrl];
|
||||
}
|
||||
|
||||
class ThemeError extends ThemeState {
|
||||
final String message;
|
||||
|
||||
const ThemeError(this.message);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [message];
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,7 +20,18 @@ class AuthScreen extends StatelessWidget {
|
||||
children: [
|
||||
const SizedBox(height: 60),
|
||||
// Logo
|
||||
Center(child: Image.asset("assets/images/logo2.png", width: 200)),
|
||||
// Center(child: Image.asset("assets/images/logo2.png", width: 200)),
|
||||
const Center(
|
||||
child: Text(
|
||||
'LOGO',
|
||||
style: TextStyle(
|
||||
fontSize: 36,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
letterSpacing: 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
// const SizedBox(height: 15),
|
||||
// Form - taking remaining space and centered
|
||||
Expanded(child: Center(child: const AuthForm())),
|
||||
|
||||
@@ -12,10 +12,11 @@ import 'package:google_mlkit_face_detection/google_mlkit_face_detection.dart';
|
||||
|
||||
import '../../core/error/exceptions.dart';
|
||||
import '../face/face_feedback.dart';
|
||||
import 'package:local_auth/local_auth.dart';
|
||||
|
||||
class OvalCameraCapturePage extends StatefulWidget {
|
||||
final bool isLogin;
|
||||
final Future<void> Function(File image) onCapture;
|
||||
final Future<void> Function(File image, {required bool localAuth}) onCapture;
|
||||
|
||||
const OvalCameraCapturePage({
|
||||
super.key,
|
||||
@@ -37,6 +38,13 @@ class _OvalCameraCapturePageState extends State<OvalCameraCapturePage> {
|
||||
bool _isSubmitting = false;
|
||||
bool _isStreaming = false;
|
||||
|
||||
//to handel the state of auth when it gives 422 status code
|
||||
|
||||
final LocalAuthentication _localAuth = LocalAuthentication();
|
||||
|
||||
bool _handlingAuth422 = false; // prevents multiple dialogs/auth prompts
|
||||
File? _lastCapturedFile; // keep the same image for retry
|
||||
|
||||
// Smart feedback
|
||||
FaceFeedback _feedback = FaceFeedback(
|
||||
type: FaceHintType.noFace,
|
||||
@@ -252,6 +260,49 @@ class _OvalCameraCapturePageState extends State<OvalCameraCapturePage> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool?> _showLocalAuthDialog() {
|
||||
return showDialog<bool>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder:
|
||||
(_) => AlertDialog(
|
||||
title: const Text('فشل التحقق بالوجه', textAlign: TextAlign.center),
|
||||
content: const Text(
|
||||
'لم يتم التعرف على الوجه.\n\nيرجى استخدام بصمة الإصبع أو رمز القفل (PIN/النمط) للمتابعة.',
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
child: const Text('إلغاء'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: const Text('استخدام البصمة / رمز القفل'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<bool> _authenticateLocally() async {
|
||||
try {
|
||||
final isSupported = await _localAuth.isDeviceSupported();
|
||||
if (!isSupported) return false;
|
||||
|
||||
return await _localAuth.authenticate(
|
||||
localizedReason: 'تأكيد هويتك لإكمال تسجيل الحضور.',
|
||||
options: const AuthenticationOptions(
|
||||
biometricOnly: false, // ✅ allows PIN/pattern fallback
|
||||
stickyAuth: true,
|
||||
useErrorDialogs: true,
|
||||
),
|
||||
);
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _stopImageStream() async {
|
||||
if (!_isStreaming || _cameraController == null) return;
|
||||
try {
|
||||
@@ -408,7 +459,9 @@ class _OvalCameraCapturePageState extends State<OvalCameraCapturePage> {
|
||||
final xFile = await _cameraController!.takePicture();
|
||||
final file = File(xFile.path);
|
||||
|
||||
await widget.onCapture(file);
|
||||
_lastCapturedFile = file;
|
||||
|
||||
await widget.onCapture(file, localAuth: false);
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
@@ -423,8 +476,13 @@ class _OvalCameraCapturePageState extends State<OvalCameraCapturePage> {
|
||||
});
|
||||
}
|
||||
} on ServerException catch (e) {
|
||||
// Check if this is an "already logged in" error from the API
|
||||
final msg = e.message.toLowerCase();
|
||||
// If your ServerException has statusCode, prefer that:
|
||||
if (e.statusCode == 422 || msg.contains('face verification failed')) {
|
||||
await _handleFaceVerificationFailed422(e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.contains('already logged in') ||
|
||||
msg.contains('مسجل دخول بالفعل')) {
|
||||
// Stop camera and go back with a dialog
|
||||
@@ -545,6 +603,86 @@ class _OvalCameraCapturePageState extends State<OvalCameraCapturePage> {
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<void> _handleFaceVerificationFailed422(ServerException e) async {
|
||||
if (!mounted) return;
|
||||
if (_handlingAuth422) return;
|
||||
|
||||
_handlingAuth422 = true;
|
||||
|
||||
// stop everything so camera doesn’t keep scanning
|
||||
await _stopImageStream();
|
||||
|
||||
setState(() {
|
||||
_isSubmitting = false;
|
||||
_errorMessage = null;
|
||||
_debugInfo = "Face verification failed (422) → Local Auth...";
|
||||
});
|
||||
|
||||
final proceed = await _showLocalAuthDialog();
|
||||
if (proceed != true) {
|
||||
_handlingAuth422 = false;
|
||||
_stopCameraCompletely();
|
||||
if (!mounted) return;
|
||||
|
||||
// Go back to attendance + show message there
|
||||
Navigator.of(context).pop(false);
|
||||
// If you prefer to show inside this screen before pop:
|
||||
// ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('تم الإلغاء.')));
|
||||
return;
|
||||
}
|
||||
|
||||
final ok = await _authenticateLocally();
|
||||
if (!ok) {
|
||||
_handlingAuth422 = false;
|
||||
_stopCameraCompletely();
|
||||
if (!mounted) return;
|
||||
|
||||
// Return to attendance; attendance screen should show snack "failed fingerprint/pattern"
|
||||
Navigator.of(context).pop("local_auth_failed");
|
||||
return;
|
||||
}
|
||||
|
||||
// Local auth success → retry SAME image with localAuth=true
|
||||
final file = _lastCapturedFile;
|
||||
if (file == null) {
|
||||
_handlingAuth422 = false;
|
||||
_stopCameraCompletely();
|
||||
if (!mounted) return;
|
||||
Navigator.of(context).pop("retry_missing_file");
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_isSubmitting = true;
|
||||
_debugInfo = "Local auth success → retrying with localAuth=true...";
|
||||
});
|
||||
|
||||
try {
|
||||
await widget.onCapture(file, localAuth: true);
|
||||
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_isSuccess = true;
|
||||
_isSubmitting = false;
|
||||
});
|
||||
|
||||
Future.delayed(const Duration(seconds: 1), () {
|
||||
if (mounted) Navigator.of(context).pop(true);
|
||||
});
|
||||
} on ServerException catch (e2) {
|
||||
_handlingAuth422 = false;
|
||||
_stopCameraCompletely();
|
||||
if (!mounted) return;
|
||||
// Retry failed → go back to attendance
|
||||
Navigator.of(context).pop("retry_failed");
|
||||
} catch (_) {
|
||||
_handlingAuth422 = false;
|
||||
_stopCameraCompletely();
|
||||
if (!mounted) return;
|
||||
Navigator.of(context).pop("retry_failed");
|
||||
}
|
||||
}
|
||||
|
||||
Uint8List _convertYUV420ToNV21(CameraImage image) {
|
||||
final int width = image.width;
|
||||
final int height = image.height;
|
||||
|
||||
@@ -88,7 +88,16 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
|
||||
Column(
|
||||
children: [
|
||||
const SizedBox(height: 70),
|
||||
Image.asset("assets/images/logo2.png", width: 200),
|
||||
// Image.asset("assets/images/logo2.png", width: 200),
|
||||
const Text(
|
||||
'LOGO',
|
||||
style: TextStyle(
|
||||
fontSize: 36,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
letterSpacing: 2,
|
||||
),
|
||||
),
|
||||
|
||||
/// PAGEVIEW (SVG + TEXT ONLY)
|
||||
Expanded(
|
||||
|
||||
@@ -31,7 +31,7 @@ class _SplashScreenState extends State<SplashScreen> {
|
||||
final token = await sl<UserLocalDataSource>().getCachedUserToken();
|
||||
|
||||
if (token != null && token.isNotEmpty) {
|
||||
// Token exists, navigate directly to MainPage
|
||||
// Token exists — go to MainPage (theme already loaded in main.dart)
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const MainPage()),
|
||||
@@ -65,7 +65,18 @@ class _SplashScreenState extends State<SplashScreen> {
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
child: Center(child: Image.asset("assets/images/logo.png", width: 200)),
|
||||
// child: Center(child: Image.asset("assets/images/logo.png", width: 200)),
|
||||
child: const Center(
|
||||
child: Text(
|
||||
'LOGO',
|
||||
style: TextStyle(
|
||||
fontSize: 36,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
letterSpacing: 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ class SettingsBar extends StatelessWidget {
|
||||
super.key,
|
||||
required this.selectedIndex,
|
||||
required this.onTap,
|
||||
this.showBackButton = false, // to switch between back button and settings icons
|
||||
this.showBackButton = false,
|
||||
this.onBackTap,
|
||||
required this.iconPaths,
|
||||
});
|
||||
@@ -25,10 +25,15 @@ class SettingsBar extends StatelessWidget {
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Image.asset('assets/images/logo2.png', width: 150, height: 40),
|
||||
],
|
||||
// Text Logo
|
||||
const Text(
|
||||
'LOGO',
|
||||
style: TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
letterSpacing: 2,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
@@ -50,11 +55,10 @@ class SettingsBar extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
child: Center(
|
||||
// Always use Flutter's built-in back icon pointing to the right
|
||||
child: const Icon(
|
||||
Icons.arrow_forward, // Changed to arrow_forward for RTL
|
||||
Icons.arrow_forward,
|
||||
size: 26,
|
||||
color: Colors.black, // Adjust color as needed
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -65,7 +69,6 @@ class SettingsBar extends StatelessWidget {
|
||||
...iconPaths.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final iconPath = entry.value;
|
||||
// final isSelected = selectedIndex == index;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 10),
|
||||
@@ -102,4 +105,4 @@ class SettingsBar extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user