This commit is contained in:
moon 2026-04-27 10:58:43 +06:30
parent ae210a5990
commit 7da04ab49d
16 changed files with 609 additions and 90 deletions

View File

@ -1,5 +1,33 @@
package com.example.cb_prestige_qr package com.example.cb_prestige_qr
import android.provider.Settings
import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity : FlutterActivity() class MainActivity : FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
"cb_prestige_qr/device"
).setMethodCallHandler { call, result ->
when (call.method) {
"getDeviceId" -> {
val deviceId = Settings.Secure.getString(
contentResolver,
Settings.Secure.ANDROID_ID
)
if (deviceId.isNullOrBlank()) {
result.error("device_id_unavailable", "Unable to retrieve device ID.", null)
} else {
result.success(deviceId)
}
}
else -> result.notImplemented()
}
}
}
}

View File

@ -1,16 +1,42 @@
import Flutter import Flutter
import UIKit import UIKit
@main @main
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { @objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
override func application( private let deviceChannelName = "cb_prestige_qr/device"
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool { ) -> Bool {
return super.application(application, didFinishLaunchingWithOptions: launchOptions) return super.application(application, didFinishLaunchingWithOptions: launchOptions)
} }
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
}
} let channel = FlutterMethodChannel(
name: deviceChannelName,
binaryMessenger: engineBridge.binaryMessenger
)
channel.setMethodCallHandler { call, result in
switch call.method {
case "getDeviceId":
if let identifier = UIDevice.current.identifierForVendor?.uuidString,
!identifier.isEmpty {
result(identifier)
} else {
result(
FlutterError(
code: "device_id_unavailable",
message: "Unable to retrieve device ID.",
details: nil
)
)
}
default:
result(FlutterMethodNotImplemented)
}
}
}
}

5
lib/config.dart Normal file
View File

@ -0,0 +1,5 @@
class AppConfig {
const AppConfig._();
static const apiBaseUrl = 'http://192.168.100.41:3000';
}

View File

@ -0,0 +1,16 @@
import 'package:flutter/services.dart';
class DeviceIdService {
static const _channel = MethodChannel('cb_prestige_qr/device');
Future<String> getDeviceId() async {
// final deviceId = await _channel.invokeMethod<String>('getDeviceId');
// if (deviceId == null || deviceId.trim().isEmpty) {
// throw PlatformException(
// code: 'device_id_unavailable',
// message: 'Unable to retrieve device ID.',
// );
// }
return "demo_device_id";
}
}

View File

@ -1,6 +1,8 @@
import 'package:cb_prestige_qr/features/scan/domain/entities/scan_submit_payload.dart';
abstract interface class ScanRemoteDataSource { abstract interface class ScanRemoteDataSource {
Future<Map<String, dynamic>> submitScan({ Future<Map<String, dynamic>> submitScan({
required String rawValue, required ScanSubmitPayload payload,
}); });
} }

View File

@ -1,12 +1,13 @@
import 'package:cb_prestige_qr/features/scan/data/data_sources/scan_remote_data_source.dart'; import 'package:cb_prestige_qr/features/scan/data/data_sources/scan_remote_data_source.dart';
import 'package:cb_prestige_qr/features/scan/domain/entities/scan_submit_payload.dart';
class FakeScanRemoteDataSource implements ScanRemoteDataSource { class FakeScanRemoteDataSource implements ScanRemoteDataSource {
@override @override
Future<Map<String, dynamic>> submitScan({ Future<Map<String, dynamic>> submitScan({
required String rawValue, required ScanSubmitPayload payload,
}) async { }) async {
return <String, dynamic>{ return <String, dynamic>{
'rawValue': rawValue, 'rawValue': payload.token,
'scannedAt': DateTime.now().toIso8601String(), 'scannedAt': DateTime.now().toIso8601String(),
}; };
} }

View File

@ -0,0 +1,98 @@
import 'dart:convert';
import 'dart:io';
import 'package:cb_prestige_qr/config.dart';
import 'package:cb_prestige_qr/features/scan/data/data_sources/scan_remote_data_source.dart';
import 'package:cb_prestige_qr/features/scan/domain/entities/scan_submit_payload.dart';
class HttpScanRemoteDataSource implements ScanRemoteDataSource {
HttpScanRemoteDataSource({HttpClient? httpClient})
: _httpClient = httpClient ?? HttpClient();
final HttpClient _httpClient;
static const _endpoint = String.fromEnvironment(
'QR_SCAN_PATH',
defaultValue: '/api/qr/scan',
);
@override
Future<Map<String, dynamic>> submitScan({
required ScanSubmitPayload payload,
}) async {
final uri = _resolveUri();
final request = await _httpClient.postUrl(uri);
request.headers.contentType = ContentType.json;
request.write(jsonEncode(payload.toJson()));
final response = await request.close();
final responseBody = await response.transform(utf8.decoder).join();
if (response.statusCode != HttpStatus.ok) {
throw HttpException(
_buildStatusMessage(response.statusCode, responseBody),
uri: uri,
);
}
if (responseBody.trim().isEmpty) {
return <String, dynamic>{
'rawValue': payload.token,
'scannedAt': DateTime.now().toIso8601String(),
};
}
final decoded = jsonDecode(responseBody);
if (decoded is! Map) {
throw const FormatException('QR scan response must be a JSON object.');
}
final data = decoded.map(
(key, value) => MapEntry(key.toString(), value),
);
final isOk = data['OK'] == true || data['ok'] == true;
if (!isOk) {
throw HttpException(
'Submit failed. Response ok flag is false.',
uri: uri,
);
}
return data;
}
Uri _resolveUri() {
final endpointUri = Uri.tryParse(_endpoint);
if (endpointUri != null && endpointUri.hasScheme) {
return endpointUri;
}
if (AppConfig.apiBaseUrl.trim().isEmpty) {
throw const FormatException(
'API_BASE_URL is not configured. Pass --dart-define=API_BASE_URL=https://your-host.',
);
}
return Uri.parse(AppConfig.apiBaseUrl).resolve(_endpoint);
}
String _buildStatusMessage(int statusCode, String responseBody) {
final normalizedBody = responseBody.trim();
final bodySuffix = normalizedBody.isEmpty ? '' : ' Response: $normalizedBody';
return switch (statusCode) {
HttpStatus.badRequest =>
'Submit failed with status 400 (Bad Request).$bodySuffix',
HttpStatus.unauthorized =>
'Submit failed with status 401 (Unauthorized).$bodySuffix',
HttpStatus.forbidden =>
'Submit failed with status 403 (Forbidden).$bodySuffix',
HttpStatus.notFound =>
'Submit failed with status 404 (Not Found).$bodySuffix',
HttpStatus.internalServerError =>
'Submit failed with status 500 (Server Error).$bodySuffix',
_ =>
'Submit failed. Expected status 200 but got $statusCode.$bodySuffix',
};
}
}

View File

@ -1,6 +1,7 @@
import 'package:cb_prestige_qr/features/scan/data/data_sources/scan_remote_data_source.dart'; import 'package:cb_prestige_qr/features/scan/data/data_sources/scan_remote_data_source.dart';
import 'package:cb_prestige_qr/features/scan/data/models/scanned_qr_model.dart'; import 'package:cb_prestige_qr/features/scan/data/models/scanned_qr_model.dart';
import 'package:cb_prestige_qr/features/scan/domain/entities/scanned_qr.dart'; import 'package:cb_prestige_qr/features/scan/domain/entities/scanned_qr.dart';
import 'package:cb_prestige_qr/features/scan/domain/entities/scan_submit_payload.dart';
import 'package:cb_prestige_qr/features/scan/domain/repositories/scan_repository.dart'; import 'package:cb_prestige_qr/features/scan/domain/repositories/scan_repository.dart';
class ScanRepositoryImpl implements ScanRepository { class ScanRepositoryImpl implements ScanRepository {
@ -10,8 +11,17 @@ class ScanRepositoryImpl implements ScanRepository {
@override @override
Future<ScannedQr> processRawValue(String rawValue) async { Future<ScannedQr> processRawValue(String rawValue) async {
final payload = await _remoteDataSource.submitScan(rawValue: rawValue); return ScannedQr(rawValue: rawValue, scannedAt: DateTime.now());
return ScannedQrModel.fromJson(payload).toEntity(); }
@override
Future<ScannedQr> submitScan(String rawValue) async {
final request = ScanSubmitPayload.fromRawValue(rawValue: rawValue);
await _remoteDataSource.submitScan(payload: request);
final normalizedResponse = <String, dynamic>{
'rawValue': request.token,
'scannedAt': DateTime.now().toIso8601String(),
};
return ScannedQrModel.fromJson(normalizedResponse).toEntity();
} }
} }

View File

@ -0,0 +1,53 @@
class ScanSubmitPayload {
const ScanSubmitPayload({
required this.token,
required this.merchantId,
required this.merchantName,
required this.merchantBranchId,
required this.scannedByDeviceId,
});
factory ScanSubmitPayload.fromRawValue({
required String rawValue,
}) {
final payload = ScanSubmitPayload(
token: rawValue.trim(),
merchantId: 'merchant-001',
merchantName: 'cashier_001',
merchantBranchId: 'branch-001',
scannedByDeviceId: 'device-001',
);
payload._validate();
return payload;
}
final String token;
final String merchantId;
final String merchantName;
final String merchantBranchId;
final String scannedByDeviceId;
Map<String, dynamic> toJson() {
return <String, dynamic>{
'token': token,
'merchantId': merchantId,
'merchantName': merchantName,
'merchantBranchId': merchantBranchId,
'scannedByDeviceId': scannedByDeviceId,
};
}
void _validate() {
final missing = <String>[
if (token.trim().isEmpty) 'token',
if (merchantId.trim().isEmpty) 'merchantId',
if (merchantName.trim().isEmpty) 'merchantName',
if (merchantBranchId.trim().isEmpty) 'merchantBranchId',
if (scannedByDeviceId.trim().isEmpty) 'scannedByDeviceId',
];
if (missing.isEmpty) return;
throw FormatException(
'Scanned QR is missing required fields: ${missing.join(', ')}.',
);
}
}

View File

@ -2,5 +2,5 @@ import 'package:cb_prestige_qr/features/scan/domain/entities/scanned_qr.dart';
abstract interface class ScanRepository { abstract interface class ScanRepository {
Future<ScannedQr> processRawValue(String rawValue); Future<ScannedQr> processRawValue(String rawValue);
Future<ScannedQr> submitScan(String rawValue);
} }

View File

@ -7,12 +7,14 @@ class ScanState {
const ScanState({ const ScanState({
this.isScanning = true, this.isScanning = true,
this.isProcessing = false, this.isProcessing = false,
this.isSubmitting = false,
this.scannedValue, this.scannedValue,
this.errorMessage, this.errorMessage,
}); });
final bool isScanning; final bool isScanning;
final bool isProcessing; final bool isProcessing;
final bool isSubmitting;
final String? scannedValue; final String? scannedValue;
final String? errorMessage; final String? errorMessage;
@ -21,16 +23,20 @@ class ScanState {
ScanState copyWith({ ScanState copyWith({
bool? isScanning, bool? isScanning,
bool? isProcessing, bool? isProcessing,
bool? isSubmitting,
Object? scannedValue = _sentinel, Object? scannedValue = _sentinel,
Object? errorMessage = _sentinel, Object? errorMessage = _sentinel,
}) { }) {
return ScanState( return ScanState(
isScanning: isScanning ?? this.isScanning, isScanning: isScanning ?? this.isScanning,
isProcessing: isProcessing ?? this.isProcessing, isProcessing: isProcessing ?? this.isProcessing,
scannedValue: isSubmitting: isSubmitting ?? this.isSubmitting,
identical(scannedValue, _sentinel) ? this.scannedValue : scannedValue as String?, scannedValue: identical(scannedValue, _sentinel)
errorMessage: ? this.scannedValue
identical(errorMessage, _sentinel) ? this.errorMessage : errorMessage as String?, : scannedValue as String?,
errorMessage: identical(errorMessage, _sentinel)
? this.errorMessage
: errorMessage as String?,
); );
} }
} }
@ -64,7 +70,30 @@ class ScanController extends _$ScanController {
} }
} }
Future<void> submitScannedValue(String value) async {
if (state.isSubmitting) return;
state = state.copyWith(isSubmitting: true, errorMessage: null);
try {
await ref.read(scanRepositoryProvider).submitScan(value);
state = state.copyWith(
isSubmitting: false,
scannedValue: null,
errorMessage: null,
);
} catch (e) {
state = state.copyWith(isSubmitting: false, errorMessage: e.toString());
rethrow;
}
}
void resumeScanning() { void resumeScanning() {
state = const ScanState(isScanning: true, isProcessing: false, scannedValue: null, errorMessage: null); state = const ScanState(
isScanning: true,
isProcessing: false,
isSubmitting: false,
scannedValue: null,
errorMessage: null,
);
} }
} }

View File

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:cb_prestige_qr/core/presentation/widgets/start_loading_overlay.dart'; import 'package:cb_prestige_qr/core/presentation/widgets/start_loading_overlay.dart';
import 'package:cb_prestige_qr/features/scan/presentation/manager/scan_controller.dart'; import 'package:cb_prestige_qr/features/scan/presentation/manager/scan_controller.dart';
import 'package:cb_prestige_qr/features/scan/presentation/pages/scan_result_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 'package:mobile_scanner/mobile_scanner.dart'; import 'package:mobile_scanner/mobile_scanner.dart';
@ -15,6 +16,7 @@ class ScanPage extends ConsumerStatefulWidget {
class _ScanPageState extends ConsumerState<ScanPage> { class _ScanPageState extends ConsumerState<ScanPage> {
late final MobileScannerController _scannerController; late final MobileScannerController _scannerController;
String? _openedResultValue;
@override @override
void initState() { void initState() {
@ -47,6 +49,22 @@ class _ScanPageState extends ConsumerState<ScanPage> {
} }
}); });
ref.listen<String?>(scanControllerProvider.select((s) => s.scannedValue), (
previous,
next,
) {
if (next == null || next == _openedResultValue) return;
_openedResultValue = next;
WidgetsBinding.instance.addPostFrameCallback((_) async {
if (!mounted) return;
await Navigator.push(
context,
MaterialPageRoute(builder: (_) => ScanResultPage(rawValue: next)),
);
_openedResultValue = null;
});
});
final scanState = ref.watch(scanControllerProvider); final scanState = ref.watch(scanControllerProvider);
void onDetect(BarcodeCapture capture) { void onDetect(BarcodeCapture capture) {
@ -85,9 +103,7 @@ class _ScanPageState extends ConsumerState<ScanPage> {
child: Center(child: CircularProgressIndicator()), child: Center(child: CircularProgressIndicator()),
), ),
), ),
if (!scanState.isProcessing && if (!scanState.isProcessing && scanState.errorMessage != null)
(scanState.scannedValue != null ||
scanState.errorMessage != null))
Positioned.fill( Positioned.fill(
child: ColoredBox( child: ColoredBox(
color: const Color(0x66000000), color: const Color(0x66000000),
@ -115,11 +131,7 @@ class _ScanPageState extends ConsumerState<ScanPage> {
).textTheme.titleLarge, ).textTheme.titleLarge,
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
SelectableText( SelectableText(scanState.errorMessage ?? ''),
scanState.errorMessage ??
scanState.scannedValue ??
'',
),
const SizedBox(height: 16), const SizedBox(height: 16),
Row( Row(
children: [ children: [

View File

@ -0,0 +1,286 @@
import 'dart:convert';
import 'package:cb_prestige_qr/core/utils/MainShell.dart';
import 'package:cb_prestige_qr/features/scan/presentation/manager/scan_controller.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:jwt_decoder/jwt_decoder.dart';
class ScanResultPage extends ConsumerWidget {
const ScanResultPage({super.key, required this.rawValue});
final String rawValue;
@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
final scanState = ref.watch(scanControllerProvider);
final isSubmitting = scanState.isSubmitting;
final previewValue = _buildPreviewValue(rawValue);
return PopScope<void>(
canPop: !isSubmitting,
onPopInvokedWithResult: (didPop, result) {
if (!didPop) return;
ref.read(scanControllerProvider.notifier).resumeScanning();
},
child: Scaffold(
backgroundColor: theme.scaffoldBackgroundColor,
appBar: AppBar(
title: const Text('QR Result'),
backgroundColor: theme.scaffoldBackgroundColor,
surfaceTintColor: theme.scaffoldBackgroundColor,
elevation: 0,
scrolledUnderElevation: 0,
),
body: SafeArea(
child: ListView(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 24),
children: [
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: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
width: 46,
height: 46,
decoration: BoxDecoration(
color: colorScheme.primaryContainer,
borderRadius: BorderRadius.circular(14),
),
alignment: Alignment.center,
child: Icon(
Icons.qr_code_2_rounded,
color: colorScheme.onPrimaryContainer,
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Scanned QR',
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w800,
),
),
const SizedBox(height: 2),
Text(
'Review before submitting',
style: theme.textTheme.bodySmall?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
],
),
),
],
),
const SizedBox(height: 16),
SelectableText(
previewValue,
style: theme.textTheme.bodyMedium?.copyWith(
height: 1.4,
),
),
],
),
),
),
if (scanState.errorMessage != null) ...[
const SizedBox(height: 12),
Text(
scanState.errorMessage!,
style: theme.textTheme.bodySmall?.copyWith(
color: colorScheme.error,
fontWeight: FontWeight.w600,
),
),
],
const SizedBox(height: 18),
Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: isSubmitting
? null
: () {
ref
.read(scanControllerProvider.notifier)
.resumeScanning();
Navigator.pop(context);
},
child: const Text('Cancel'),
),
),
const SizedBox(width: 12),
Expanded(
child: FilledButton(
onPressed: isSubmitting
? null
: () async {
try {
final navigator = Navigator.of(context);
await ref
.read(scanControllerProvider.notifier)
.submitScannedValue(rawValue);
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('QR submitted successfully.'),
),
);
ref
.read(navIndexNotifierProvider.notifier)
.setIndex(0);
ref
.read(scanControllerProvider.notifier)
.resumeScanning();
navigator.pop();
navigator.pop();
} catch (_) {
if (!context.mounted) return;
final errorMessage =
ref
.read(scanControllerProvider)
.errorMessage ??
'Unable to submit QR.';
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: colorScheme.errorContainer,
content: Text(
errorMessage,
style: TextStyle(
color: colorScheme.onErrorContainer,
),
),
),
);
}
},
child: isSubmitting
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2.4,
),
)
: const Text('Submit'),
),
),
],
),
],
),
),
),
);
}
}
String _buildPreviewValue(String rawValue) {
final decodedJson =
_tryDecodeJsonObject(rawValue) ??
_tryDecodeJwtWithPackage(rawValue) ??
_tryExtractAndDecodeJwt(rawValue) ??
_tryDecodeFromUri(rawValue) ??
_tryDecodeBase64Json(rawValue, urlSafe: true) ??
_tryDecodeBase64Json(rawValue, urlSafe: false);
if (decodedJson != null) {
const encoder = JsonEncoder.withIndent(' ');
return encoder.convert(decodedJson);
}
return _tryDecodeBase64Text(rawValue, urlSafe: true) ??
_tryDecodeBase64Text(rawValue, urlSafe: false) ??
rawValue;
}
Map<String, dynamic>? _tryDecodeJsonObject(String value) {
try {
final decoded = jsonDecode(value);
if (decoded is! Map) return null;
return decoded.map((key, value) => MapEntry(key.toString(), value));
} catch (_) {
return null;
}
}
Map<String, dynamic>? _tryDecodeJwtWithPackage(String value) {
try {
return JwtDecoder.tryDecode(value.trim());
} catch (_) {
return null;
}
}
Map<String, dynamic>? _tryExtractAndDecodeJwt(String value) {
final match = RegExp(
r'([A-Za-z0-9\-_]+=*\.[A-Za-z0-9\-_]+=*\.[A-Za-z0-9\-_]+=*)',
).firstMatch(value);
if (match == null) return null;
return _tryDecodeJwtWithPackage(match.group(1)!);
}
Map<String, dynamic>? _tryDecodeFromUri(String value) {
final uri = Uri.tryParse(value.trim());
if (uri == null) return null;
for (final entry in uri.queryParameters.entries) {
final decoded =
_tryDecodeJwtWithPackage(entry.value) ??
_tryExtractAndDecodeJwt(entry.value) ??
_tryDecodeJsonObject(entry.value) ??
_tryDecodeBase64Json(entry.value, urlSafe: true) ??
_tryDecodeBase64Json(entry.value, urlSafe: false);
if (decoded != null) return decoded;
}
return null;
}
Map<String, dynamic>? _tryDecodeBase64Json(String value, {required bool urlSafe}) {
try {
final normalized = urlSafe ? base64Url.normalize(value) : base64.normalize(value);
final bytes = urlSafe ? base64Url.decode(normalized) : base64.decode(normalized);
final decoded = utf8.decode(bytes);
final payload = jsonDecode(decoded);
if (payload is! Map) return null;
return payload.map((key, value) => MapEntry(key.toString(), value));
} catch (_) {
return null;
}
}
String? _tryDecodeBase64Text(String value, {required bool urlSafe}) {
try {
final normalized = urlSafe ? base64Url.normalize(value) : base64.normalize(value);
final bytes = urlSafe ? base64Url.decode(normalized) : base64.decode(normalized);
final decoded = utf8.decode(bytes).trim();
if (decoded.isEmpty || decoded == value) return null;
return decoded;
} catch (_) {
return null;
}
}

View File

@ -1,15 +1,15 @@
import 'package:cb_prestige_qr/features/scan/data/data_sources/scan_remote_data_source.dart'; import 'package:cb_prestige_qr/features/scan/data/data_sources/scan_remote_data_source.dart';
import 'package:cb_prestige_qr/features/scan/data/data_sources/scan_remote_data_source_fake.dart'; import 'package:cb_prestige_qr/features/scan/data/data_sources/scan_remote_data_source_http.dart';
import 'package:cb_prestige_qr/features/scan/data/repositories/scan_repository_impl.dart'; import 'package:cb_prestige_qr/features/scan/data/repositories/scan_repository_impl.dart';
import 'package:cb_prestige_qr/features/scan/domain/repositories/scan_repository.dart'; import 'package:cb_prestige_qr/features/scan/domain/repositories/scan_repository.dart';
import 'package:cb_prestige_qr/features/scan/domain/use_cases/process_scan_use_case.dart'; import 'package:cb_prestige_qr/features/scan/domain/use_cases/process_scan_use_case.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'scan_providers.g.dart';
part 'scan_providers.g.dart';
@riverpod @riverpod
ScanRemoteDataSource scanRemoteDataSource(Ref ref) { ScanRemoteDataSource scanRemoteDataSource(Ref ref) {
return FakeScanRemoteDataSource(); return HttpScanRemoteDataSource();
} }
@riverpod @riverpod

View File

@ -278,14 +278,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.7" version: "2.4.7"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
sha256: "38d1c268de9097ff59cf0e844ac38759fc78f76836d37edad06fa21e182055a0"
url: "https://pub.dev"
source: hosted
version: "2.0.34"
flutter_riverpod: flutter_riverpod:
dependency: transitive dependency: transitive
description: description:
@ -392,14 +384,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.8.0" version: "4.8.0"
intl:
dependency: transitive
description:
name: intl
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
url: "https://pub.dev"
source: hosted
version: "0.20.2"
io: io:
dependency: transitive dependency: transitive
description: description:
@ -416,6 +400,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.11.0" version: "4.11.0"
jwt_decoder:
dependency: "direct main"
description:
name: jwt_decoder
sha256: "54774aebf83f2923b99e6416b4ea915d47af3bde56884eb622de85feabbc559f"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
leak_tracker: leak_tracker:
dependency: transitive dependency: transitive
description: description:
@ -448,46 +440,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.0" version: "6.1.0"
local_auth:
dependency: "direct main"
description:
name: local_auth
sha256: ae6f382f638108c6becd134318d7c3f0a93875383a54010f61d7c97ac05d5137
url: "https://pub.dev"
source: hosted
version: "3.0.1"
local_auth_android:
dependency: transitive
description:
name: local_auth_android
sha256: b41970749c2d43791790724b76917eeee1e90de76e6b0eec3edca03a329bf44c
url: "https://pub.dev"
source: hosted
version: "2.0.7"
local_auth_darwin:
dependency: transitive
description:
name: local_auth_darwin
sha256: a8c3d4e17454111f7fd31ff72a31222359f6059f7fe956c2dcfe0f88f49826d4
url: "https://pub.dev"
source: hosted
version: "2.0.3"
local_auth_platform_interface:
dependency: transitive
description:
name: local_auth_platform_interface
sha256: f98b8e388588583d3f781f6806e4f4c9f9e189d898d27f0c249b93a1973dd122
url: "https://pub.dev"
source: hosted
version: "1.1.0"
local_auth_windows:
dependency: transitive
description:
name: local_auth_windows
sha256: be12c5b8ba5e64896983123655c5f67d2484ecfcc95e367952ad6e3bff94cb16
url: "https://pub.dev"
source: hosted
version: "2.0.1"
logging: logging:
dependency: transitive dependency: transitive
description: description:
@ -1007,4 +959,4 @@ packages:
version: "3.1.3" version: "3.1.3"
sdks: sdks:
dart: ">=3.11.3 <4.0.0" dart: ">=3.11.3 <4.0.0"
flutter: ">=3.38.0" flutter: ">=3.35.0"

View File

@ -41,6 +41,7 @@ dependencies:
carousel_slider: ^5.1.2 carousel_slider: ^5.1.2
mobile_scanner: ^7.2.0 mobile_scanner: ^7.2.0
shared_preferences: ^2.5.5 shared_preferences: ^2.5.5
jwt_decoder: ^2.0.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: