6177214e-ce7c-49e3-99de-ff9721b26f63 — Commit 7ebfaf67

AuthorEdmir Suljic<esu@dwarf.dk>
Date2025-10-22 10:40:29 +0200
Changed loading strategy on booking details and up sales

Changed files

.../lib/.generated/database/comwell_db.g.dart      | 318 ++++++++++++++++++++-
 .../.generated/database/daos/upsales_dao.g.dart    |   8 +
 .../overview/models/payment_details.g.dart         |   1 +
 .../services/adyen/payment_method.g.dart           |  24 +-
 .../authentication/authentication_repository.dart  |  12 +-
 .../booking_details/bloc/booking_details_bloc.dart |  57 +++-
 .../bloc/booking_details_event.dart                |   3 +
 .../lib/booking_details/booking_details_page.dart  |   2 -
 .../components/booking_details_bottom_sheet.dart   |   2 +-
 .../components/preregister_button.dart             |   2 +-
 .../placeholders/catalog_item_placeholder.dart     |  83 ++++++
 .../up_sales_catalog_shimmer_loader.dart           |  69 +++++
 comwell_key_app/lib/database/comwell_db.dart       |  52 +++-
 .../lib/database/daos/bookings_dao.dart            |  16 +-
 comwell_key_app/lib/database/daos/upsales_dao.dart |  79 +++++
 .../lib/database/mappers/upsale_mapper.dart        |  34 +++
 comwell_key_app/lib/database/models/upsale_db.dart |  49 ++++
 .../lib/database/tables/upsale_table.dart          |   8 +
 .../components/current_bookings_tab_view.dart      |   2 +
 .../lib/overview/cubit/overview_cubit.dart         |   1 -
 comwell_key_app/lib/overview/overview_page.dart    |   1 +
 .../overview/repository/overview_repository.dart   |   7 +-
 .../lib/profile/profile_repository.dart            |  40 ++-
 comwell_key_app/lib/routing/app_router.dart        |  32 +--
 comwell_key_app/lib/services/api.dart              |   2 +
 .../interceptors/response_handle_interceptor.dart  |  55 ++--
 .../lib/up_sales/cubit/up_sales_cubit.dart         |  29 +-
 .../lib/up_sales/cubit/up_sales_state.dart         |   2 +
 comwell_key_app/lib/up_sales/up_sales_catalog.dart |  10 +
 .../lib/up_sales/up_sales_repository.dart          |  26 +-
 comwell_key_app/lib/utils/database_recovery.dart   | 108 +++++++
 comwell_key_app/lib/utils/locator.dart             |  24 +-
 32 files changed, 1022 insertions(+), 136 deletions(-)

Diff

diff --git a/comwell_key_app/lib/.generated/database/comwell_db.g.dart b/comwell_key_app/lib/.generated/database/comwell_db.g.dart
index 256d56c9..0879f5b0 100644
--- a/comwell_key_app/lib/.generated/database/comwell_db.g.dart
+++ b/comwell_key_app/lib/.generated/database/comwell_db.g.dart
@@ -618,6 +618,195 @@ class NotificationPermissionEntityCompanion
}
}
+class $UpsaleEntityTable extends UpsaleEntity
+ with TableInfo<$UpsaleEntityTable, UpsaleDb> {
+ @override
+ final GeneratedDatabase attachedDatabase;
+ final String? _alias;
+ $UpsaleEntityTable(this.attachedDatabase, [this._alias]);
+ static const VerificationMeta _idMeta = const VerificationMeta('id');
+ @override
+ late final GeneratedColumn<String> id = GeneratedColumn<String>(
+ 'id', aliasedName, false,
+ type: DriftSqlType.string,
+ requiredDuringInsert: true,
+ defaultConstraints: GeneratedColumn.constraintIsAlways('UNIQUE'));
+ static const VerificationMeta _jsonMeta = const VerificationMeta('json');
+ @override
+ late final GeneratedColumn<String> json = GeneratedColumn<String>(
+ 'json', aliasedName, false,
+ type: DriftSqlType.string, requiredDuringInsert: true);
+ @override
+ List<GeneratedColumn> get $columns => [id, json];
+ @override
+ String get aliasedName => _alias ?? actualTableName;
+ @override
+ String get actualTableName => $name;
+ static const String $name = 'upsale_entity';
+ @override
+ VerificationContext validateIntegrity(Insertable<UpsaleDb> instance,
+ {bool isInserting = false}) {
+ final context = VerificationContext();
+ final data = instance.toColumns(true);
+ if (data.containsKey('id')) {
+ context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
+ } else if (isInserting) {
+ context.missing(_idMeta);
+ }
+ if (data.containsKey('json')) {
+ context.handle(
+ _jsonMeta, json.isAcceptableOrUnknown(data['json']!, _jsonMeta));
+ } else if (isInserting) {
+ context.missing(_jsonMeta);
+ }
+ return context;
+ }
+
+ @override
+ Set<GeneratedColumn> get $primaryKey => const {};
+ @override
+ UpsaleDb map(Map<String, dynamic> data, {String? tablePrefix}) {
+ final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
+ return UpsaleDb(
+ id: attachedDatabase.typeMapping
+ .read(DriftSqlType.string, data['${effectivePrefix}id'])!,
+ json: attachedDatabase.typeMapping
+ .read(DriftSqlType.string, data['${effectivePrefix}json'])!,
+ );
+ }
+
+ @override
+ $UpsaleEntityTable createAlias(String alias) {
+ return $UpsaleEntityTable(attachedDatabase, alias);
+ }
+}
+
+class UpsaleDb extends DataClass implements Insertable<UpsaleDb> {
+ final String id;
+ final String json;
+ const UpsaleDb({required this.id, required this.json});
+ @override
+ Map<String, Expression> toColumns(bool nullToAbsent) {
+ final map = <String, Expression>{};
+ map['id'] = Variable<String>(id);
+ map['json'] = Variable<String>(json);
+ return map;
+ }
+
+ UpsaleEntityCompanion toCompanion(bool nullToAbsent) {
+ return UpsaleEntityCompanion(
+ id: Value(id),
+ json: Value(json),
+ );
+ }
+
+ factory UpsaleDb.fromJson(Map<String, dynamic> json,
+ {ValueSerializer? serializer}) {
+ serializer ??= driftRuntimeOptions.defaultSerializer;
+ return UpsaleDb(
+ id: serializer.fromJson<String>(json['id']),
+ json: serializer.fromJson<String>(json['json']),
+ );
+ }
+ @override
+ Map<String, dynamic> toJson({ValueSerializer? serializer}) {
+ serializer ??= driftRuntimeOptions.defaultSerializer;
+ return <String, dynamic>{
+ 'id': serializer.toJson<String>(id),
+ 'json': serializer.toJson<String>(json),
+ };
+ }
+
+ UpsaleDb copyWith({String? id, String? json}) => UpsaleDb(
+ id: id ?? this.id,
+ json: json ?? this.json,
+ );
+ UpsaleDb copyWithCompanion(UpsaleEntityCompanion data) {
+ return UpsaleDb(
+ id: data.id.present ? data.id.value : this.id,
+ json: data.json.present ? data.json.value : this.json,
+ );
+ }
+
+ @override
+ String toString() {
+ return (StringBuffer('UpsaleDb(')
+ ..write('id: $id, ')
+ ..write('json: $json')
+ ..write(')'))
+ .toString();
+ }
+
+ @override
+ int get hashCode => Object.hash(id, json);
+ @override
+ bool operator ==(Object other) =>
+ identical(this, other) ||
+ (other is UpsaleDb && other.id == this.id && other.json == this.json);
+}
+
+class UpsaleEntityCompanion extends UpdateCompanion<UpsaleDb> {
+ final Value<String> id;
+ final Value<String> json;
+ final Value<int> rowid;
+ const UpsaleEntityCompanion({
+ this.id = const Value.absent(),
+ this.json = const Value.absent(),
+ this.rowid = const Value.absent(),
+ });
+ UpsaleEntityCompanion.insert({
+ required String id,
+ required String json,
+ this.rowid = const Value.absent(),
+ }) : id = Value(id),
+ json = Value(json);
+ static Insertable<UpsaleDb> custom({
+ Expression<String>? id,
+ Expression<String>? json,
+ Expression<int>? rowid,
+ }) {
+ return RawValuesInsertable({
+ if (id != null) 'id': id,
+ if (json != null) 'json': json,
+ if (rowid != null) 'rowid': rowid,
+ });
+ }
+
+ UpsaleEntityCompanion copyWith(
+ {Value<String>? id, Value<String>? json, Value<int>? rowid}) {
+ return UpsaleEntityCompanion(
+ id: id ?? this.id,
+ json: json ?? this.json,
+ rowid: rowid ?? this.rowid,
+ );
+ }
+
+ @override
+ Map<String, Expression> toColumns(bool nullToAbsent) {
+ final map = <String, Expression>{};
+ if (id.present) {
+ map['id'] = Variable<String>(id.value);
+ }
+ if (json.present) {
+ map['json'] = Variable<String>(json.value);
+ }
+ if (rowid.present) {
+ map['rowid'] = Variable<int>(rowid.value);
+ }
+ return map;
+ }
+
+ @override
+ String toString() {
+ return (StringBuffer('UpsaleEntityCompanion(')
+ ..write('id: $id, ')
+ ..write('json: $json, ')
+ ..write('rowid: $rowid')
+ ..write(')'))
+ .toString();
+ }
+}
+
abstract class _$ComwellDatabase extends GeneratedDatabase {
_$ComwellDatabase(QueryExecutor e) : super(e);
$ComwellDatabaseManager get managers => $ComwellDatabaseManager(this);
@@ -625,16 +814,18 @@ abstract class _$ComwellDatabase extends GeneratedDatabase {
late final $UserEntityTable userEntity = $UserEntityTable(this);
late final $NotificationPermissionEntityTable notificationPermissionEntity =
$NotificationPermissionEntityTable(this);
+ late final $UpsaleEntityTable upsaleEntity = $UpsaleEntityTable(this);
late final BookingsDao bookingsDao = BookingsDao(this as ComwellDatabase);
late final UserDAO userDAO = UserDAO(this as ComwellDatabase);
late final NotificationPermissionDAO notificationPermissionDAO =
NotificationPermissionDAO(this as ComwellDatabase);
+ late final UpsalesDAO upsalesDAO = UpsalesDAO(this as ComwellDatabase);
@override
Iterable<TableInfo<Table, Object?>> get allTables =>
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
@override
List<DatabaseSchemaEntity> get allSchemaEntities =>
- [bookingEntity, userEntity, notificationPermissionEntity];
+ [bookingEntity, userEntity, notificationPermissionEntity, upsaleEntity];
}
typedef $$BookingEntityTableCreateCompanionBuilder = BookingEntityCompanion
@@ -1036,6 +1227,129 @@ typedef $$NotificationPermissionEntityTableProcessedTableManager
),
NotificationPermissionDb,
PrefetchHooks Function()>;
+typedef $$UpsaleEntityTableCreateCompanionBuilder = UpsaleEntityCompanion
+ Function({
+ required String id,
+ required String json,
+ Value<int> rowid,
+});
+typedef $$UpsaleEntityTableUpdateCompanionBuilder = UpsaleEntityCompanion
+ Function({
+ Value<String> id,
+ Value<String> json,
+ Value<int> rowid,
+});
+
+class $$UpsaleEntityTableFilterComposer
+ extends Composer<_$ComwellDatabase, $UpsaleEntityTable> {
+ $$UpsaleEntityTableFilterComposer({
+ required super.$db,
+ required super.$table,
+ super.joinBuilder,
+ super.$addJoinBuilderToRootComposer,
+ super.$removeJoinBuilderFromRootComposer,
+ });
+ ColumnFilters<String> get id => $composableBuilder(
+ column: $table.id, builder: (column) => ColumnFilters(column));
+
+ ColumnFilters<String> get json => $composableBuilder(
+ column: $table.json, builder: (column) => ColumnFilters(column));
+}
+
+class $$UpsaleEntityTableOrderingComposer
+ extends Composer<_$ComwellDatabase, $UpsaleEntityTable> {
+ $$UpsaleEntityTableOrderingComposer({
+ required super.$db,
+ required super.$table,
+ super.joinBuilder,
+ super.$addJoinBuilderToRootComposer,
+ super.$removeJoinBuilderFromRootComposer,
+ });
+ ColumnOrderings<String> get id => $composableBuilder(
+ column: $table.id, builder: (column) => ColumnOrderings(column));
+
+ ColumnOrderings<String> get json => $composableBuilder(
+ column: $table.json, builder: (column) => ColumnOrderings(column));
+}
+
+class $$UpsaleEntityTableAnnotationComposer
+ extends Composer<_$ComwellDatabase, $UpsaleEntityTable> {
+ $$UpsaleEntityTableAnnotationComposer({
+ required super.$db,
+ required super.$table,
+ super.joinBuilder,
+ super.$addJoinBuilderToRootComposer,
+ super.$removeJoinBuilderFromRootComposer,
+ });
+ GeneratedColumn<String> get id =>
+ $composableBuilder(column: $table.id, builder: (column) => column);
+
+ GeneratedColumn<String> get json =>
+ $composableBuilder(column: $table.json, builder: (column) => column);
+}
+
+class $$UpsaleEntityTableTableManager extends RootTableManager<
+ _$ComwellDatabase,
+ $UpsaleEntityTable,
+ UpsaleDb,
+ $$UpsaleEntityTableFilterComposer,
+ $$UpsaleEntityTableOrderingComposer,
+ $$UpsaleEntityTableAnnotationComposer,
+ $$UpsaleEntityTableCreateCompanionBuilder,
+ $$UpsaleEntityTableUpdateCompanionBuilder,
+ (UpsaleDb, BaseReferences<_$ComwellDatabase, $UpsaleEntityTable, UpsaleDb>),
+ UpsaleDb,
+ PrefetchHooks Function()> {
+ $$UpsaleEntityTableTableManager(
+ _$ComwellDatabase db, $UpsaleEntityTable table)
+ : super(TableManagerState(
+ db: db,
+ table: table,
+ createFilteringComposer: () =>
+ $$UpsaleEntityTableFilterComposer($db: db, $table: table),
+ createOrderingComposer: () =>
+ $$UpsaleEntityTableOrderingComposer($db: db, $table: table),
+ createComputedFieldComposer: () =>
+ $$UpsaleEntityTableAnnotationComposer($db: db, $table: table),
+ updateCompanionCallback: ({
+ Value<String> id = const Value.absent(),
+ Value<String> json = const Value.absent(),
+ Value<int> rowid = const Value.absent(),
+ }) =>
+ UpsaleEntityCompanion(
+ id: id,
+ json: json,
+ rowid: rowid,
+ ),
+ createCompanionCallback: ({
+ required String id,
+ required String json,
+ Value<int> rowid = const Value.absent(),
+ }) =>
+ UpsaleEntityCompanion.insert(
+ id: id,
+ json: json,
+ rowid: rowid,
+ ),
+ withReferenceMapper: (p0) => p0
+ .map((e) => (e.readTable(table), BaseReferences(db, table, e)))
+ .toList(),
+ prefetchHooksCallback: null,
+ ));
+}
+
+typedef $$UpsaleEntityTableProcessedTableManager = ProcessedTableManager<
+ _$ComwellDatabase,
+ $UpsaleEntityTable,
+ UpsaleDb,
+ $$UpsaleEntityTableFilterComposer,
+ $$UpsaleEntityTableOrderingComposer,
+ $$UpsaleEntityTableAnnotationComposer,
+ $$UpsaleEntityTableCreateCompanionBuilder,
+ $$UpsaleEntityTableUpdateCompanionBuilder,
+ (UpsaleDb, BaseReferences<_$ComwellDatabase, $UpsaleEntityTable, UpsaleDb>),
+ UpsaleDb,
+ PrefetchHooks Function()>;
class $ComwellDatabaseManager {
final _$ComwellDatabase _db;
@@ -1048,4 +1362,6 @@ class $ComwellDatabaseManager {
get notificationPermissionEntity =>
$$NotificationPermissionEntityTableTableManager(
_db, _db.notificationPermissionEntity);
+ $$UpsaleEntityTableTableManager get upsaleEntity =>
+ $$UpsaleEntityTableTableManager(_db, _db.upsaleEntity);
}
diff --git a/comwell_key_app/lib/.generated/database/daos/upsales_dao.g.dart b/comwell_key_app/lib/.generated/database/daos/upsales_dao.g.dart
new file mode 100644
index 00000000..9d866ed0
--- /dev/null
+++ b/comwell_key_app/lib/.generated/database/daos/upsales_dao.g.dart
@@ -0,0 +1,8 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of '../../../database/daos/upsales_dao.dart';
+
+// ignore_for_file: type=lint
+mixin _$UpsalesDAOMixin on DatabaseAccessor<ComwellDatabase> {
+ $UpsaleEntityTable get upsaleEntity => attachedDatabase.upsaleEntity;
+}
diff --git a/comwell_key_app/lib/.generated/overview/models/payment_details.g.dart b/comwell_key_app/lib/.generated/overview/models/payment_details.g.dart
index aa6f2456..de5fed15 100644
--- a/comwell_key_app/lib/.generated/overview/models/payment_details.g.dart
+++ b/comwell_key_app/lib/.generated/overview/models/payment_details.g.dart
@@ -28,5 +28,6 @@ Map<String, dynamic> _$PaymentDetailsToJson(PaymentDetails instance) =>
const _$CardTypeEnumMap = {
CardType.visa: 'visa',
CardType.mastercard: 'mastercard',
+ CardType.amexgooglepay: 'amexgooglepay',
CardType.maestro: 'maestro',
};
diff --git a/comwell_key_app/lib/.generated/services/adyen/payment_method.g.dart b/comwell_key_app/lib/.generated/services/adyen/payment_method.g.dart
index a15cb52b..552af401 100644
--- a/comwell_key_app/lib/.generated/services/adyen/payment_method.g.dart
+++ b/comwell_key_app/lib/.generated/services/adyen/payment_method.g.dart
@@ -7,18 +7,18 @@ part of '../../../services/adyen/payment_method.dart';
// **************************************************************************
PaymentMethod _$PaymentMethodFromJson(Map json) => PaymentMethod(
- brand: json['brand'] as String,
- cvc: json['cvc'] as String,
- encryptedCard: json['encryptedCard'] as String,
- encryptedCardNumber: json['encryptedCardNumber'] as String,
- encryptedExpiryMonth: json['encryptedExpiryMonth'] as String,
- encryptedExpiryYear: json['encryptedExpiryYear'] as String,
- encryptedSecurityCode: json['encryptedSecurityCode'] as String,
- expiryMonth: json['expiryMonth'] as String,
- expiryYear: json['expiryYear'] as String,
- holderName: json['holderName'] as String,
- number: json['number'] as String,
- type: json['type'] as String,
+ brand: json['brand'] as String?,
+ cvc: json['cvc'] as String?,
+ encryptedCard: json['encryptedCard'] as String?,
+ encryptedCardNumber: json['encryptedCardNumber'] as String?,
+ encryptedExpiryMonth: json['encryptedExpiryMonth'] as String?,
+ encryptedExpiryYear: json['encryptedExpiryYear'] as String?,
+ encryptedSecurityCode: json['encryptedSecurityCode'] as String?,
+ expiryMonth: json['expiryMonth'] as String?,
+ expiryYear: json['expiryYear'] as String?,
+ holderName: json['holderName'] as String?,
+ number: json['number'] as String?,
+ type: json['type'] as String?,
);
Map<String, dynamic> _$PaymentMethodToJson(PaymentMethod instance) =>
diff --git a/comwell_key_app/lib/authentication/authentication_repository.dart b/comwell_key_app/lib/authentication/authentication_repository.dart
index 0742d2e8..63c58507 100644
--- a/comwell_key_app/lib/authentication/authentication_repository.dart
+++ b/comwell_key_app/lib/authentication/authentication_repository.dart
@@ -1,5 +1,4 @@
import 'dart:async';
-import 'dart:convert';
import 'package:comwell_key_app/authentication/enum/authentication_status.dart';
import 'package:comwell_key_app/database/comwell_db.dart';
@@ -33,7 +32,7 @@ class AuthenticationRepository {
Future<void> init() async {
final clientId = dotenv.env["ENTRA_ID_CLIENT_ID"]!;
final redirect = dotenv
- .env["ENTRA_ID_REDIRECT_URL"]!; // should probably be an env variable
+ .env["ENTRA_ID_REDIRECT_URL"]!;
switch (appFlavor?.toLowerCase()) {
@@ -114,6 +113,9 @@ class AuthenticationRepository {
// Clear local storage first
await secureStorage.deleteAll();
await database.deleteDatabase();
+
+ // Reset the database singleton to create a fresh instance
+ resetDatabase();
// await msAuth.removeAccount();
@@ -135,6 +137,10 @@ class AuthenticationRepository {
// Even if logout fails, still clear local data and update status
await secureStorage.deleteAll();
await database.deleteDatabase();
+
+ // Reset the database singleton to create a fresh instance
+ resetDatabase();
+
if (forced) {
_controller.sink.add(AuthenticationStatus.forcedUnauthenticated);
} else {
@@ -199,7 +205,7 @@ class AuthenticationRepository {
return await msAuth.acquireTokenSilent(
scopes: scopes,
- //authority: authorityUrl,
+ authority: authorityUrl,
identifier: identifier,
);
diff --git a/comwell_key_app/lib/booking_details/bloc/booking_details_bloc.dart b/comwell_key_app/lib/booking_details/bloc/booking_details_bloc.dart
index dd85b997..aee1be46 100644
--- a/comwell_key_app/lib/booking_details/bloc/booking_details_bloc.dart
+++ b/comwell_key_app/lib/booking_details/bloc/booking_details_bloc.dart
@@ -58,6 +58,13 @@ class BookingDetailsBloc
}
});
+ on<PreregisterEvent>((event, emit) async {
+ emit(state.loading());
+ await getBookingDetails(emit, booking.confirmationId, preregister: true);
+ await getUpSales(emit, preregister: true);
+ emit(state.main());
+ });
+
on<StartTimerEvent>((event, emit) async {
_startTimer();
});
@@ -101,22 +108,41 @@ class BookingDetailsBloc
});
}
- Future<void> getUpSales(Emitter<BookingDetailsState> emit) async {
- final response = await upSaleRepository.getUpSales(
- booking.confirmationId, booking.hotelCode);
-
- emit(state.getUpSales(response));
+ Future<void> getUpSales(Emitter<BookingDetailsState> emit,
+ {bool preregister = false}) async {
+ try {
+ if (preregister) {
+ final response = await upSaleRepository.getRemoteUpSales(
+ booking.confirmationId, booking.hotelCode);
+ emit(state.getUpSales(response));
+ } else {
+ final response = await upSaleRepository.getUpSales(
+ booking.confirmationId, booking.hotelCode);
+ emit(state.getUpSales(response));
+ }
+ } catch (e) {
+ if (kDebugMode) print("err=$e");
+ emit(state.setupError());
+ }
}
Future<Booking> getBookingDetails(
- Emitter<BookingDetailsState> emit, String bookingId) async {
+ Emitter<BookingDetailsState> emit, String bookingId,
+ {bool preregister = false}) async {
emit(state.loading());
try {
- final bookingDetails =
- await profileRepository.getBookingDetails(bookingId);
- booking = bookingDetails;
-
- return booking;
+ if (preregister) {
+ final bookingDetails =
+ await profileRepository.getRemoteBookingDetails(bookingId);
+ booking = bookingDetails;
+ return booking;
+ } else {
+ final bookingDetails =
+ await profileRepository.getBookingDetails(bookingId);
+ booking = bookingDetails;
+
+ return booking;
+ }
} catch (e) {
if (kDebugMode) print("err=$e");
emit(state.setupError());
@@ -129,7 +155,7 @@ class BookingDetailsBloc
final isEndPointSetup = await seosRepository.isEndpointSetup();
if (isEndPointSetup) {
final keys = await seosRepository.refreshKeys();
-
+
emit(state.updateKeys(keys));
}
} catch (e) {
@@ -139,15 +165,16 @@ class BookingDetailsBloc
Future<void> checkIfHouseKeepingOrdered(
Emitter<BookingDetailsState> emit) async {
- final isHouseKeepingOrdered = await houseKeepingRepository
- .isHousesKeepingOrdered(booking.roomNumber);
+ final isHouseKeepingOrdered =
+ await houseKeepingRepository.isHousesKeepingOrdered(booking.roomNumber);
if (isHouseKeepingOrdered) {
emit(state.houseKeepingOrdered());
}
}
- Future<void> orderHouseKeeping(Emitter<BookingDetailsState> emit, List<String> selectedServices) async {
+ Future<void> orderHouseKeeping(
+ Emitter<BookingDetailsState> emit, List<String> selectedServices) async {
final housekeeping = Housekeeping.toJson(
booking.hotelCode,
booking.roomNumber,
diff --git a/comwell_key_app/lib/booking_details/bloc/booking_details_event.dart b/comwell_key_app/lib/booking_details/bloc/booking_details_event.dart
index 684a7b73..d103390e 100644
--- a/comwell_key_app/lib/booking_details/bloc/booking_details_event.dart
+++ b/comwell_key_app/lib/booking_details/bloc/booking_details_event.dart
@@ -12,6 +12,9 @@ final class InitialEvent extends BookingDetailsEvent {}
final class GetUpSalesEvent extends BookingDetailsEvent {
}
+final class PreregisterEvent extends BookingDetailsEvent {
+}
+
final class CheckIfHouseKeepingOrdered extends BookingDetailsEvent {}
final class GetBookingDetailsEvent extends BookingDetailsEvent {
diff --git a/comwell_key_app/lib/booking_details/booking_details_page.dart b/comwell_key_app/lib/booking_details/booking_details_page.dart
index d201b7ff..b01d0445 100644
--- a/comwell_key_app/lib/booking_details/booking_details_page.dart
+++ b/comwell_key_app/lib/booking_details/booking_details_page.dart
@@ -31,8 +31,6 @@ class BookingDetailsPage extends StatelessWidget {
if (state.status == BookingDetailsStatus.initial) {
cubit.add(InitialEvent());
}
-
- print(cubit.booking);
return Scaffold(
extendBodyBehindAppBar: true,
diff --git a/comwell_key_app/lib/booking_details/components/booking_details_bottom_sheet.dart b/comwell_key_app/lib/booking_details/components/booking_details_bottom_sheet.dart
index a22f940c..5cd6f1ae 100644
--- a/comwell_key_app/lib/booking_details/components/booking_details_bottom_sheet.dart
+++ b/comwell_key_app/lib/booking_details/components/booking_details_bottom_sheet.dart
@@ -166,7 +166,7 @@ class BookingDetailsBottomSheet extends StatelessWidget {
text: 'up_sales_see_all'.tr(),
onTap: () async {
await context.pushNamed(AppRoutes.upSalesCatalog.name,
- extra: [cubit.state.upSales, cubit.booking]);
+ extra: [cubit.booking]);
cubit.add(InitialEvent());
},
),
diff --git a/comwell_key_app/lib/booking_details/components/preregister_button.dart b/comwell_key_app/lib/booking_details/components/preregister_button.dart
index b02f37fb..9165318d 100644
--- a/comwell_key_app/lib/booking_details/components/preregister_button.dart
+++ b/comwell_key_app/lib/booking_details/components/preregister_button.dart
@@ -23,7 +23,7 @@ class PreregisterButton extends StatelessWidget {
AppRoutes.preregistration.name,
extra: [bloc.booking, bloc.state.upSales]);
if (result != null) {
- bloc.add(InitialEvent());
+ bloc.add(PreregisterEvent());
} else {
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
diff --git a/comwell_key_app/lib/common/components/shimmer_loader/placeholders/catalog_item_placeholder.dart b/comwell_key_app/lib/common/components/shimmer_loader/placeholders/catalog_item_placeholder.dart
new file mode 100644
index 00000000..2523be38
--- /dev/null
+++ b/comwell_key_app/lib/common/components/shimmer_loader/placeholders/catalog_item_placeholder.dart
@@ -0,0 +1,83 @@
+import 'package:flutter/material.dart';
+
+class CatalogItemPlaceholder extends StatelessWidget {
+ const CatalogItemPlaceholder({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16.0),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ // Image placeholder
+ Container(
+ width: 80.0,
+ height: 80.0,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(12.0),
+ color: Colors.white,
+ ),
+ ),
+ const SizedBox(width: 16.0),
+ // Text content
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Container(
+ width: double.infinity,
+ height: 16.0,
+ color: Colors.white,
+ margin: const EdgeInsets.only(bottom: 8.0),
+ ),
+ Container(
+ width: 200.0,
+ height: 14.0,
+ color: Colors.white,
+ margin: const EdgeInsets.only(bottom: 8.0),
+ ),
+ Container(
+ width: 120.0,
+ height: 14.0,
+ color: Colors.white,
+ margin: const EdgeInsets.only(bottom: 8.0),
+ ),
+ Container(
+ width: 80.0,
+ height: 12.0,
+ color: Colors.white,
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(width: 16.0),
+ // Price placeholder
+ Container(
+ width: 60.0,
+ height: 20.0,
+ decoration: BoxDecoration(
+ color: Colors.white,
+ borderRadius: BorderRadius.circular(4.0),
+ ),
+ ),
+ ],
+ ),
+ const SizedBox(height: 16.0),
+ // Button placeholder
+ Container(
+ width: double.infinity,
+ height: 44.0,
+ decoration: BoxDecoration(
+ color: Colors.white,
+ borderRadius: BorderRadius.circular(24.0),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/comwell_key_app/lib/common/components/shimmer_loader/up_sales_catalog_shimmer_loader.dart b/comwell_key_app/lib/common/components/shimmer_loader/up_sales_catalog_shimmer_loader.dart
new file mode 100644
index 00000000..7700f025
--- /dev/null
+++ b/comwell_key_app/lib/common/components/shimmer_loader/up_sales_catalog_shimmer_loader.dart
@@ -0,0 +1,69 @@
+import 'package:comwell_key_app/common/components/shimmer_loader/placeholders/catalog_item_placeholder.dart';
+import 'package:comwell_key_app/common/components/shimmer_loader/placeholders/title_placeholder.dart';
+import 'package:comwell_key_app/themes/light_theme.dart';
+import 'package:flutter/material.dart';
+import 'package:shimmer/shimmer.dart';
+
+class UpSalesCatalogShimmerLoader extends StatelessWidget {
+ const UpSalesCatalogShimmerLoader({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Shimmer.fromColors(
+ baseColor: sandColor[40]!,
+ highlightColor: sandColor[10]!,
+ enabled: true,
+ child: SingleChildScrollView(
+ child: Padding(
+ padding: const EdgeInsets.only(
+ top: 24,
+ bottom: 100,
+ left: 0,
+ right: 0,
+ ),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ // Main title placeholder
+ const TitlePlaceholder(width: 200),
+ const SizedBox(height: 40),
+
+ // Services section placeholder
+ const Padding(
+ padding: EdgeInsets.symmetric(horizontal: 16),
+ child: TitlePlaceholder(width: 100),
+ ),
+ const SizedBox(height: 8),
+ const CatalogItemPlaceholder(),
+ const SizedBox(height: 16),
+ const CatalogItemPlaceholder(),
+ const SizedBox(height: 24),
+
+ // Room upgrades section placeholder
+ const Padding(
+ padding: EdgeInsets.symmetric(horizontal: 16),
+ child: TitlePlaceholder(width: 120),
+ ),
+ const SizedBox(height: 8),
+ const CatalogItemPlaceholder(),
+ const SizedBox(height: 16),
+ const CatalogItemPlaceholder(),
+ const SizedBox(height: 24),
+
+ // Other up sales section placeholder
+ const Padding(
+ padding: EdgeInsets.symmetric(horizontal: 16),
+ child: TitlePlaceholder(width: 140),
+ ),
+ const SizedBox(height: 8),
+ const CatalogItemPlaceholder(),
+ const SizedBox(height: 16),
+ const CatalogItemPlaceholder(),
+ const SizedBox(height: 24),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/comwell_key_app/lib/database/comwell_db.dart b/comwell_key_app/lib/database/comwell_db.dart
index 069efada..d7c2d620 100644
--- a/comwell_key_app/lib/database/comwell_db.dart
+++ b/comwell_key_app/lib/database/comwell_db.dart
@@ -4,26 +4,26 @@ import 'package:comwell_key_app/database/daos/notifications_dao.dart';
import 'package:comwell_key_app/database/tables/booking_table.dart';
import 'package:comwell_key_app/database/tables/notification_table.dart';
import 'package:comwell_key_app/database/tables/user_table.dart';
+import 'package:comwell_key_app/database/tables/upsale_table.dart';
import 'package:comwell_key_app/utils/secure_storage.dart';
import 'package:drift/drift.dart';
import 'package:drift/native.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
-import 'package:sqlcipher_flutter_libs/sqlcipher_flutter_libs.dart';
-import 'package:sqlite3/open.dart';
import 'package:sqlite3/sqlite3.dart';
import 'package:uuid/uuid.dart' as uuid;
import 'daos/bookings_dao.dart';
import 'daos/user_dao.dart';
+import 'daos/upsales_dao.dart';
part '../.generated/database/comwell_db.g.dart';
final secureStorage = SecureStorage();
@DriftDatabase(
- tables: [BookingEntity, UserEntity, NotificationPermissionEntity],
- daos: [BookingsDao, UserDAO, NotificationPermissionDAO])
+ tables: [BookingEntity, UserEntity, NotificationPermissionEntity, UpsaleEntity],
+ daos: [BookingsDao, UserDAO, NotificationPermissionDAO, UpsalesDAO])
class ComwellDatabase extends _$ComwellDatabase {
static const String _cipherKey = "sql_cipher";
static const String _dbFileName = "comwell.db.enc";
@@ -31,7 +31,7 @@ class ComwellDatabase extends _$ComwellDatabase {
ComwellDatabase() : super(_openDatabase());
@override
- int get schemaVersion => 1;
+ int get schemaVersion => 2;
@override
MigrationStrategy get migration => destructiveFallback;
@@ -46,6 +46,34 @@ class ComwellDatabase extends _$ComwellDatabase {
}
}
+ /// Reset the database singleton in the locator
+ static void resetSingleton() {
+ // This will be called from locator to reset the singleton
+ }
+
+ /// Force database recreation by deleting and recreating the database file
+ static Future<void> forceRecreate() async {
+ final supportDir = await getApplicationSupportDirectory();
+ final databaseDir = File(p.join(supportDir.path, _dbFileName));
+ final exists = await databaseDir.exists();
+ if (exists) {
+ await databaseDir.delete();
+ }
+ }
+
+ /// Complete database reset including cipher key for corrupted databases
+ static Future<void> forceRecreateWithCipherReset() async {
+ final supportDir = await getApplicationSupportDirectory();
+ final databaseDir = File(p.join(supportDir.path, _dbFileName));
+ final exists = await databaseDir.exists();
+ if (exists) {
+ await databaseDir.delete();
+ }
+
+ // Clear the corrupted cipher key to force generation of a new one
+ await secureStorage.delete(_cipherKey);
+ }
+
static Future<String> getCipher() async {
final cipher = await secureStorage.read(_cipherKey);
if (cipher == null || cipher.isEmpty) {
@@ -62,10 +90,6 @@ class ComwellDatabase extends _$ComwellDatabase {
final path = await getApplicationSupportDirectory();
final file = File(p.join(path.path, _dbFileName));
final cipher = await getCipher();
- Future<void> isolateSetup() async {
- await applyWorkaroundToOpenSqlCipherOnOldAndroidVersions();
- open.overrideFor(OperatingSystem.android, openCipherOnAndroid);
- }
void setup(Database db) {
final result = db.select("pragma cipher_version");
@@ -75,10 +99,16 @@ class ComwellDatabase extends _$ComwellDatabase {
}
db.execute("pragma key = \"$cipher\"");
db.execute("select count(*) from sqlite_master");
+ // Add pragmas for better stability
+ db.execute("pragma journal_mode = WAL");
+ db.execute("pragma synchronous = NORMAL");
+ db.execute("pragma cache_size = 1000");
+ db.execute("pragma temp_store = MEMORY");
}
- return NativeDatabase.createInBackground(file,
- isolateSetup: isolateSetup, setup: setup);
+ // Use synchronous database creation instead of createInBackground
+ // to avoid isolate channel issues during concurrent access
+ return NativeDatabase(file, setup: setup);
});
}
}
diff --git a/comwell_key_app/lib/database/daos/bookings_dao.dart b/comwell_key_app/lib/database/daos/bookings_dao.dart
index 2ac3e16e..01deb89b 100644
--- a/comwell_key_app/lib/database/daos/bookings_dao.dart
+++ b/comwell_key_app/lib/database/daos/bookings_dao.dart
@@ -1,5 +1,7 @@
import 'dart:convert';
import 'package:comwell_key_app/database/comwell_db.dart';
+import 'package:comwell_key_app/overview/models/room.dart';
+import 'package:comwell_key_app/services/mappers/booking_mapper.dart';
import 'package:comwell_key_app/services/models/booking_dto.dart';
import 'package:comwell_key_app/services/models/bookings_dto.dart';
import 'package:comwell_key_app/utils/json.dart';
@@ -19,17 +21,23 @@ class BookingsDao extends DatabaseAccessor<ComwellDatabase>
return bookingEntity.all().get();
}
- Future<BookingDb> getBookingDetails(String bookingId) {
- return (select(bookingEntity)
+ Future<Booking> getBookingDetails(
+ String bookingId, int userId, List<Room> rooms) async {
+ final booking = await (select(bookingEntity)
..where((entity) => entity.id.equals(bookingId)))
.getSingle();
+ final json = jsonDecode(booking.json) as Json;
+ return BookingDTO.fromJson(json).toBooking(userId, BookingStatus.current, rooms);
}
- Future<void> insert(Iterable<BookingDTO> bookings, BookingStatus status) async {
+ Future<void> insert(
+ Iterable<BookingDTO> bookings, BookingStatus status) async {
final entities = bookings.map((booking) {
final json = jsonEncode(booking.toJson());
return BookingEntityCompanion.insert(
- id: booking.confirmationNumber, json: json, status: status.toString());
+ id: booking.confirmationNumber,
+ json: json,
+ status: status.toString());
});
await batch((batch) => batch.insertAll(bookingEntity, entities,
mode: InsertMode.insertOrReplace));
diff --git a/comwell_key_app/lib/database/daos/upsales_dao.dart b/comwell_key_app/lib/database/daos/upsales_dao.dart
new file mode 100644
index 00000000..8b268728
--- /dev/null
+++ b/comwell_key_app/lib/database/daos/upsales_dao.dart
@@ -0,0 +1,79 @@
+import 'dart:convert';
+
+import 'package:comwell_key_app/database/comwell_db.dart';
+import 'package:comwell_key_app/database/models/upsale_db.dart';
+import 'package:comwell_key_app/database/tables/upsale_table.dart';
+import 'package:comwell_key_app/up_sales/mappers/up_sales_mapper.dart';
+import 'package:comwell_key_app/up_sales/models/dto/up_sales_dto.dart';
+import 'package:comwell_key_app/up_sales/models/up_sales.dart';
+import 'package:comwell_key_app/utils/json.dart';
+import 'package:drift/drift.dart';
+
+part '../../.generated/database/daos/upsales_dao.g.dart';
+
+@DriftAccessor(tables: [UpsaleEntity])
+class UpsalesDAO extends DatabaseAccessor<ComwellDatabase>
+ with _$UpsalesDAOMixin {
+ UpsalesDAO(super.attachedDatabase);
+
+ /// Get all upsales from the database
+ Future<List<UpsaleModel>> getAllUpsales() async {
+ final results = await select(upsaleEntity).get();
+ return results.map((row) => UpsaleModel.fromJson(row.toJson())).toList();
+ }
+
+ /// Get upsales by confirmation number
+ Future<UpSales> getUpsaleByConfirmationNumber(
+ String confirmationNumber) async {
+ final query = select(upsaleEntity)
+ ..where((tbl) => tbl.id.equals(confirmationNumber));
+ final result = await query.getSingleOrNull();
+ if (result == null) {
+ throw Exception("Up sales not found in database");
+ }
+ final json = jsonDecode(result.json) as Json;
+ return UpSalesDTO.fromJson(json).toUpSales();
+ }
+
+ /// Insert a single upsale
+ Future<void> insertUpsale(UpSalesDTO upsale) async {
+ final upsaleModel = UpsaleModel.fromUpSalesDTO(upsale);
+ final entity = UpsaleEntityCompanion.insert(
+ id: upsaleModel.id,
+ json: upsaleModel.json,
+ );
+ await batch((batch) =>
+ batch.insert(upsaleEntity, entity, mode: InsertMode.insertOrReplace));
+ }
+
+ /// Update an existing upsale
+ Future<void> updateUpsale(UpSalesDTO upsale) async {
+ final upsaleModel = UpsaleModel.fromUpSalesDTO(upsale);
+ final entity = UpsaleEntityCompanion(
+ id: Value(upsaleModel.id),
+ json: Value(upsaleModel.json),
+ );
+ await update(upsaleEntity).replace(entity);
+ }
+
+ /// Delete upsale by confirmation number
+ Future<void> deleteUpsaleByConfirmationNumber(
+ String confirmationNumber) async {
+ await (delete(upsaleEntity)
+ ..where((tbl) => tbl.id.equals(confirmationNumber)))
+ .go();
+ }
+
+ /// Delete all upsales
+ Future<void> deleteAllUpsales() async {
+ await delete(upsaleEntity).go();
+ }
+
+ /// Check if upsale exists by confirmation number
+ Future<bool> upsaleExists(String confirmationNumber) async {
+ final query = select(upsaleEntity)
+ ..where((tbl) => tbl.id.equals(confirmationNumber));
+ final result = await query.getSingleOrNull();
+ return result != null;
+ }
+}
diff --git a/comwell_key_app/lib/database/mappers/upsale_mapper.dart b/comwell_key_app/lib/database/mappers/upsale_mapper.dart
new file mode 100644
index 00000000..df7db81e
--- /dev/null
+++ b/comwell_key_app/lib/database/mappers/upsale_mapper.dart
@@ -0,0 +1,34 @@
+import 'package:comwell_key_app/database/models/upsale_db.dart';
+import 'package:comwell_key_app/up_sales/models/dto/up_sales_dto.dart';
+
+/// Extension to provide mapping functionality between UpsaleModel and UpSalesDTO
+extension UpsaleModelMapper on UpsaleModel {
+ /// Converts UpsaleModel to UpSalesDTO
+ UpSalesDTO toUpSalesDTO() {
+ return toUpSalesDTO();
+ }
+}
+
+/// Extension to provide mapping functionality for lists of UpsaleModel
+extension ListUpsaleModelMapper on List<UpsaleModel> {
+ /// Converts list of UpsaleModel to list of UpSalesDTO
+ List<UpSalesDTO> toUpSalesDTOs() {
+ return map((upsaleModel) => upsaleModel.toUpSalesDTO()).toList();
+ }
+}
+
+/// Extension to provide mapping functionality between UpSalesDTO and UpsaleModel
+extension UpSalesDTOMapper on UpSalesDTO {
+ /// Converts UpSalesDTO to UpsaleModel
+ UpsaleModel toUpsaleModel() {
+ return UpsaleModel.fromUpSalesDTO(this);
+ }
+}
+
+/// Extension to provide mapping functionality for lists of UpSalesDTO
+extension ListUpSalesDTOMapper on List<UpSalesDTO> {
+ /// Converts list of UpSalesDTO to list of UpsaleModel
+ List<UpsaleModel> toUpsaleModels() {
+ return map((upSalesDTO) => upSalesDTO.toUpsaleModel()).toList();
+ }
+}
diff --git a/comwell_key_app/lib/database/models/upsale_db.dart b/comwell_key_app/lib/database/models/upsale_db.dart
new file mode 100644
index 00000000..9fabbc32
--- /dev/null
+++ b/comwell_key_app/lib/database/models/upsale_db.dart
@@ -0,0 +1,49 @@
+import 'dart:convert';
+import 'package:comwell_key_app/up_sales/models/dto/up_sales_dto.dart';
+import 'package:equatable/equatable.dart';
+
+class UpsaleModel extends Equatable {
+ final String id;
+ final String json;
+
+ const UpsaleModel({
+ required this.id,
+ required this.json,
+ });
+
+ factory UpsaleModel.fromJson(Map<String, dynamic> json) {
+ return UpsaleModel(
+ id: json['id'] as String,
+ json: json['json'] as String,
+ );
+ }
+
+ Map<String, dynamic> toJson() {
+ return {
+ 'id': id,
+ 'json': json,
+ };
+ }
+
+ /// Converts the stored JSON string back to UpSalesDTO
+ UpSalesDTO toUpSalesDTO() {
+ final Map<String, dynamic> jsonMap = jsonDecode(json) as Map<String, dynamic>;
+ return UpSalesDTO.fromJson(jsonMap);
+ }
+
+ /// Creates UpsaleModel from UpSalesDTO
+ factory UpsaleModel.fromUpSalesDTO(UpSalesDTO upSalesDTO) {
+ return UpsaleModel(
+ id: upSalesDTO.confirmationNumber,
+ json: jsonEncode(upSalesDTO.toJson()),
+ );
+ }
+
+ @override
+ List<Object?> get props => [id, json];
+
+ @override
+ String toString() {
+ return 'UpsaleModel(id: $id, json: $json)';
+ }
+}
diff --git a/comwell_key_app/lib/database/tables/upsale_table.dart b/comwell_key_app/lib/database/tables/upsale_table.dart
new file mode 100644
index 00000000..720f0f48
--- /dev/null
+++ b/comwell_key_app/lib/database/tables/upsale_table.dart
@@ -0,0 +1,8 @@
+import 'package:drift/drift.dart';
+
+@DataClassName('UpsaleDb')
+class UpsaleEntity extends Table {
+ TextColumn get id => text().unique()();
+
+ TextColumn get json => text()();
+}
\ No newline at end of file
diff --git a/comwell_key_app/lib/overview/components/current_bookings_tab_view.dart b/comwell_key_app/lib/overview/components/current_bookings_tab_view.dart
index 47bf2927..fe1addd4 100644
--- a/comwell_key_app/lib/overview/components/current_bookings_tab_view.dart
+++ b/comwell_key_app/lib/overview/components/current_bookings_tab_view.dart
@@ -26,6 +26,7 @@ class CurrentBookingsTabView extends StatelessWidget {
Widget build(BuildContext context) {
final theme = Theme.of(context);
final cubit = context.read<OverviewCubit>();
+
return BlocBuilder<OverviewCubit, OverviewState>(
builder: (context, state) {
if (state is OverviewLoading) {
@@ -49,6 +50,7 @@ class CurrentBookingsTabView extends StatelessWidget {
child: Builder(
builder: (context) {
if (bookings.isEmpty) {
+
return ListView(
children: [
const SizedBox(
diff --git a/comwell_key_app/lib/overview/cubit/overview_cubit.dart b/comwell_key_app/lib/overview/cubit/overview_cubit.dart
index 13bfebe1..01b7c4a3 100644
--- a/comwell_key_app/lib/overview/cubit/overview_cubit.dart
+++ b/comwell_key_app/lib/overview/cubit/overview_cubit.dart
@@ -15,7 +15,6 @@ class OverviewCubit extends Cubit<OverviewState> {
Future<void> fetchBookings() async {
emit(OverviewLoading());
try {
-
final bookings = await overviewRepository.fetchAllBookingsForUser();
emit(OverviewLoaded(bookings: bookings));
diff --git a/comwell_key_app/lib/overview/overview_page.dart b/comwell_key_app/lib/overview/overview_page.dart
index 22281e54..ea416bf0 100644
--- a/comwell_key_app/lib/overview/overview_page.dart
+++ b/comwell_key_app/lib/overview/overview_page.dart
@@ -211,6 +211,7 @@ class OverviewTabView extends StatelessWidget {
final TabController _tabController;
final Bookings bookings;
+
@override
Widget build(BuildContext context) {
return Expanded(
diff --git a/comwell_key_app/lib/overview/repository/overview_repository.dart b/comwell_key_app/lib/overview/repository/overview_repository.dart
index a6820a9e..f485dddb 100644
--- a/comwell_key_app/lib/overview/repository/overview_repository.dart
+++ b/comwell_key_app/lib/overview/repository/overview_repository.dart
@@ -10,6 +10,7 @@ import 'package:comwell_key_app/services/mappers/booking_mapper.dart';
import 'package:comwell_key_app/services/mappers/bookings_mapper.dart';
import 'package:comwell_key_app/services/models/booking_dto.dart';
import 'package:comwell_key_app/utils/locator.dart';
+import 'package:comwell_key_app/utils/database_recovery.dart';
class OverviewRepository {
final api = Api();
@@ -24,7 +25,11 @@ class OverviewRepository {
final user = await profileRepository.fetchProfileSettings();
final rooms = await chooseShareRoomRepository.getMockRooms();
- await locator<ComwellDatabase>().bookingsDao.insertBookings(response);
+
+ await DatabaseRecovery.executeWithRecovery(
+ () => locator<ComwellDatabase>().bookingsDao.insertBookings(response),
+ 'save bookings to database',
+ );
return response.toBookings(user.id, rooms);
} catch (e) {
diff --git a/comwell_key_app/lib/profile/profile_repository.dart b/comwell_key_app/lib/profile/profile_repository.dart
index f1e42197..79232ee7 100644
--- a/comwell_key_app/lib/profile/profile_repository.dart
+++ b/comwell_key_app/lib/profile/profile_repository.dart
@@ -10,6 +10,7 @@ import 'package:comwell_key_app/services/models/bookings_dto.dart';
import 'package:comwell_key_app/services/models/user_dto.dart';
import 'package:comwell_key_app/utils/json.dart';
import 'package:comwell_key_app/utils/locator.dart';
+import 'package:comwell_key_app/utils/database_recovery.dart';
import 'package:comwell_key_app/utils/secure_storage.dart';
import 'package:comwell_key_app/utils/seos_repository.dart';
import 'package:flutter/material.dart';
@@ -47,14 +48,31 @@ class ProfileRepository {
return user;
}
- Future<Booking> getBookingDetails(String bookingId) async {
- user = await fetchProfileSettings();
+ Future<Booking> getRemoteBookingDetails(String bookingId) async {
final response = await api.getBookingDetails(bookingId);
- await locator<ComwellDatabase>().bookingsDao.insertBookings(
- BookingsDTO(current: [response!], past: [], cancelled: []));
- final booking = response.toBooking(user.id, BookingStatus.current, []);
+ return response!.toBooking(user.id, BookingStatus.current, []);
+ }
- return booking;
+ Future<Booking> getBookingDetails(String bookingId) async {
+ user = await fetchProfileSettings();
+ try {
+ final booking = await locator<ComwellDatabase>()
+ .bookingsDao
+ .getBookingDetails(bookingId, user.id, []);
+ return booking;
+ } catch (e) {
+ try {
+ final response = await api.getBookingDetails(bookingId);
+ await locator<ComwellDatabase>().bookingsDao.insertBookings(
+ BookingsDTO(current: [response!], past: [], cancelled: []));
+ final booking = response.toBooking(user.id, BookingStatus.current, []);
+ debugPrint("Booking saved to database: ${booking.confirmationId}");
+ return booking;
+ } catch (dbError) {
+ debugPrint("Database error while saving booking: $dbError");
+ rethrow;
+ }
+ }
}
Future<User> fetchProfileSettings() async {
@@ -68,12 +86,10 @@ class ProfileRepository {
final userDto = UserDto.fromJson(data);
final user = userDto.toUser();
- try {
- await locator<ComwellDatabase>().userDAO.saveUser(userDto);
- } catch (dbError) {
- debugPrint("Database error while saving user: $dbError");
- // Continue execution even if database save fails
- }
+ await DatabaseRecovery.executeWithRecoverySafe(
+ () => locator<ComwellDatabase>().userDAO.saveUser(userDto),
+ 'save user to database',
+ );
return user;
} catch (e) {
diff --git a/comwell_key_app/lib/routing/app_router.dart b/comwell_key_app/lib/routing/app_router.dart
index 2033cbf0..c2eb355d 100644
--- a/comwell_key_app/lib/routing/app_router.dart
+++ b/comwell_key_app/lib/routing/app_router.dart
@@ -109,7 +109,6 @@ GoRouter goRouter() {
if (status == AuthenticationStatus.unknown) {
bool doesTokenExist = await authRepo.doesTokenExist();
if (doesTokenExist) {
-
authRepo.logIn();
}
}
@@ -438,31 +437,12 @@ GoRouter goRouter() {
key: state.pageKey,
child: BlocProvider(
create: (_) {
- final extras = state.extra;
- if (extras is List<dynamic> && extras.length == 2) {
- return UpSalesCubit(
- upSaleRepository: locator<UpSalesRepository>(),
- upSales: extras[0] as UpSales,
- booking: extras[1] as Booking,
- )..init();
- } else if (extras is RoomUpgradeList) {
- final upSales = UpSales(
- property: extras.booking.hotelName,
- confirmationNumber: extras.booking.confirmationId,
- roomUpgrades: extras.roomUpgrade != null
- ? [extras.roomUpgrade!]
- : [],
- addOnUpgrades: extras.addOnUpgrade != null
- ? [extras.addOnUpgrade!]
- : []);
- return UpSalesCubit(
- upSaleRepository: locator<UpSalesRepository>(),
- upSales: upSales,
- booking: (extras.booking),
- )..init();
- } else {
- throw Exception('Invalid extra type');
- }
+ final extras = state.extra as List<dynamic>;
+
+ return UpSalesCubit(
+ upSaleRepository: locator<UpSalesRepository>(),
+ booking: extras[0] as Booking,
+ )..init();
},
child: child,
),
diff --git a/comwell_key_app/lib/services/api.dart b/comwell_key_app/lib/services/api.dart
index 3a44c63b..a358e68f 100644
--- a/comwell_key_app/lib/services/api.dart
+++ b/comwell_key_app/lib/services/api.dart
@@ -124,6 +124,8 @@ class Api {
Future<Response<dynamic>> fetchProfileSettings() async {
final response = await dio.get<dynamic>(ApiEndpoints.getGuestProfile);
+
+
return response;
}
diff --git a/comwell_key_app/lib/services/interceptors/response_handle_interceptor.dart b/comwell_key_app/lib/services/interceptors/response_handle_interceptor.dart
index dce897af..a2daca0b 100644
--- a/comwell_key_app/lib/services/interceptors/response_handle_interceptor.dart
+++ b/comwell_key_app/lib/services/interceptors/response_handle_interceptor.dart
@@ -36,9 +36,9 @@ class ResponseHandleInterceptor extends Interceptor {
error: 'Token not found');
return handler.reject(error);
}
-
+
options.headers.addAll({
- 'Authorization':accessToken,
+ 'Authorization': accessToken,
'Ocp-Apim-Subscription-Key': dotenv.env['OCP_APIM_SUBSCRIPTION_KEY']!,
});
return handler.next(options);
@@ -70,34 +70,33 @@ class ResponseHandleInterceptor extends Interceptor {
case 401:
retryCount++;
try {
- final identifier = await _secureStorageService.read(key: constants.identifier);
- final authResult = await _authenticationRepository.acquireTokenSilent(identifier ?? '');
-
- if (retryCount < 3) {
-
- if (authResult.accessToken.isNotEmpty) {
- // Retry the original request with the new token
- final options = response.requestOptions;
- options.headers['Authorization'] = authResult.accessToken;
- final opts = Options(
- method: response.requestOptions.method,
- headers: response.requestOptions.headers);
- final retryRequest = await _dio.request<dynamic>(
- response.requestOptions.path,
- options: opts,
- data: response.requestOptions.data,
- queryParameters: response.requestOptions.queryParameters);
+ final identifier =
+ await _secureStorageService.read(key: constants.identifier);
+ final authResult = await _authenticationRepository
+ .acquireTokenSilent(identifier ?? '');
+
+ if (retryCount < 3) {
+ if (authResult.accessToken.isNotEmpty) {
+ // Retry the original request with the new token
+ final options = response.requestOptions;
+ options.headers['Authorization'] = authResult.accessToken;
+ final opts = Options(
+ method: response.requestOptions.method,
+ headers: response.requestOptions.headers);
+ final retryRequest = await _dio.request<dynamic>(
+ response.requestOptions.path,
+ options: opts,
+ data: response.requestOptions.data,
+ queryParameters: response.requestOptions.queryParameters);
- return handler.resolve(retryRequest);
-
+ return handler.resolve(retryRequest);
+ }
+ } else {
+ retryCount = 0;
+ _authenticationRepository.logOut(forced: true);
+ return handler.reject(err);
}
-
- } else {
- retryCount = 0;
- _authenticationRepository.logOut(forced: true);
- return handler.reject(err);
- }
- } catch (e){
+ } catch (e) {
debugPrint('Error acquiring token silently: $e');
}
break;
diff --git a/comwell_key_app/lib/up_sales/cubit/up_sales_cubit.dart b/comwell_key_app/lib/up_sales/cubit/up_sales_cubit.dart
index b9991c60..827b80a3 100644
--- a/comwell_key_app/lib/up_sales/cubit/up_sales_cubit.dart
+++ b/comwell_key_app/lib/up_sales/cubit/up_sales_cubit.dart
@@ -11,23 +11,23 @@ import 'package:comwell_key_app/up_sales/up_sales_repository.dart';
class UpSalesCubit extends Cubit<UpSalesState> {
final UpSalesRepository upSaleRepository;
- final UpSales upSales;
+ late UpSales upSales;
final Booking booking;
- UpSalesCubit(
- {required this.upSaleRepository,
- required this.upSales,
- required this.booking})
+ UpSalesCubit({required this.upSaleRepository, required this.booking})
: super(UpSalesState.initial());
-
void init() async {
+ emit(state.loading());
+ upSales = await upSaleRepository.getRemoteUpSales(
+ booking.confirmationId, booking.hotelCode);
+
emit(UpSalesState(
selected: false,
addOnUpgrades: upSales.addOnUpgrades,
availableRoomUpgrades: upSales.roomUpgrades,
selectedRoomUpgrade: '',
- isLoading: true,
+ isLoading: false,
processingState: UpSalesProcessingStateNotStarted(),
error: null));
try {
@@ -56,7 +56,7 @@ class UpSalesCubit extends Cubit<UpSalesState> {
int get extrasTotalPrice {
//This is the total price of the selected up sales and the price of the selected
//room upgrade if it is selected
-
+
return selectedAddOnUpgrades.fold(
0, (sum, upgrade) => sum + upgrade.price * upgrade.quantity) +
(state.selectedRoomUpgrade.isNotEmpty
@@ -67,16 +67,14 @@ class UpSalesCubit extends Cubit<UpSalesState> {
}
List<AddOnUpgrade> get selectedAddOnUpgrades {
- final selectedUpgrades = state.addOnUpgrades
- .where((upgrade) => upgrade.isAddedToCart)
- .toList();
+ final selectedUpgrades =
+ state.addOnUpgrades.where((upgrade) => upgrade.isAddedToCart).toList();
return selectedUpgrades;
}
List<AddOnUpgrade> get otherUpgrades {
- final selectedUpgrades = state.addOnUpgrades
- .where((upgrade) => !upgrade.isService)
- .toList();
+ final selectedUpgrades =
+ state.addOnUpgrades.where((upgrade) => !upgrade.isService).toList();
return selectedUpgrades;
}
@@ -84,8 +82,7 @@ class UpSalesCubit extends Cubit<UpSalesState> {
emit(state.copyWith(termsAccepted: !state.termsAccepted));
}
- Future<void> addUpSalesToBooking(
- ) async {
+ Future<void> addUpSalesToBooking() async {
emit(state.processingStateUpdated(UpSalesProcessingStateProcessing()));
try {
// Extract RoomUpgrade from selectedUpSales to get the roomType
diff --git a/comwell_key_app/lib/up_sales/cubit/up_sales_state.dart b/comwell_key_app/lib/up_sales/cubit/up_sales_state.dart
index 88aff282..1c13e81e 100644
--- a/comwell_key_app/lib/up_sales/cubit/up_sales_state.dart
+++ b/comwell_key_app/lib/up_sales/cubit/up_sales_state.dart
@@ -49,6 +49,8 @@ class UpSalesState extends Equatable {
UpSalesState setupError({required Exception error}) =>
copyWith(isLoading: false, error: error);
+ UpSalesState loading() => copyWith(isLoading: true);
+
UpSalesState preRegLoading() => copyWith(isLoading: true);
UpSalesState loaded({required UpSales upSales}) =>
diff --git a/comwell_key_app/lib/up_sales/up_sales_catalog.dart b/comwell_key_app/lib/up_sales/up_sales_catalog.dart
index e4257a78..e8fc19b5 100644
--- a/comwell_key_app/lib/up_sales/up_sales_catalog.dart
+++ b/comwell_key_app/lib/up_sales/up_sales_catalog.dart
@@ -1,4 +1,5 @@
import 'package:comwell_key_app/common/components/comwell_app_bar.dart';
+import 'package:comwell_key_app/common/components/shimmer_loader/up_sales_catalog_shimmer_loader.dart';
import 'package:comwell_key_app/up_sales/components/catalog/addon_upgrade_catalog.dart';
import 'package:comwell_key_app/up_sales/components/catalog/room_upgrade_catalog.dart';
import 'package:comwell_key_app/up_sales/components/catalog/service_catalog.dart';
@@ -17,6 +18,15 @@ class UpSalesCatalog extends StatelessWidget {
Widget build(BuildContext context) {
final theme = Theme.of(context);
return BlocBuilder<UpSalesCubit, UpSalesState>(builder: (context, state) {
+ // Show shimmer loader when loading
+ if (state.isLoading) {
+ return Scaffold(
+ appBar: const ComwellAppBar(),
+ backgroundColor: Theme.of(context).colorScheme.surface,
+ body: const UpSalesCatalogShimmerLoader(),
+ );
+ }
+
final cubit = context.read<UpSalesCubit>();
final serviceUpgrades = cubit.state.addOnUpgrades
.where((upgrade) => upgrade.isService)
diff --git a/comwell_key_app/lib/up_sales/up_sales_repository.dart b/comwell_key_app/lib/up_sales/up_sales_repository.dart
index 91b49f90..a2157f56 100644
--- a/comwell_key_app/lib/up_sales/up_sales_repository.dart
+++ b/comwell_key_app/lib/up_sales/up_sales_repository.dart
@@ -1,18 +1,42 @@
+import 'package:comwell_key_app/database/comwell_db.dart';
import 'package:comwell_key_app/services/api.dart';
import 'package:comwell_key_app/up_sales/mappers/up_sales_mapper.dart';
import 'package:comwell_key_app/up_sales/models/addon_list.dart';
import 'package:comwell_key_app/up_sales/models/up_sales.dart';
+import 'package:comwell_key_app/utils/locator.dart';
+import 'package:flutter/foundation.dart';
class UpSalesRepository {
final Api api = Api();
UpSalesRepository();
- Future<UpSales> getUpSales(String confirmationId, String hotelCode) async {
+ Future<UpSales> getRemoteUpSales(
+ String confirmationId, String hotelCode) async {
final response = await api.fetchUpSales(confirmationId, hotelCode);
return response.toUpSales();
}
+ Future<UpSales> getUpSales(String confirmationId, String hotelCode) async {
+ try {
+ final upsales = await locator<ComwellDatabase>()
+ .upsalesDAO
+ .getUpsaleByConfirmationNumber(confirmationId);
+ return upsales;
+ } catch (e) {
+ try {
+ final response = await api.fetchUpSales(confirmationId, hotelCode);
+ await locator<ComwellDatabase>().upsalesDAO.insertUpsale(response);
+ final upsales = response.toUpSales();
+ debugPrint("Up sales saved to database: $upsales");
+ return upsales;
+ } catch (dbError) {
+ debugPrint("Error saving up sales to database: $dbError");
+ rethrow;
+ }
+ }
+ }
+
Future<void> addUpSalesToBooking(String confirmationId, String hotelCode,
String roomType, List<AddOnList> selectedUpSales) async {
await api.addUpSalesToBooking(
diff --git a/comwell_key_app/lib/utils/database_recovery.dart b/comwell_key_app/lib/utils/database_recovery.dart
new file mode 100644
index 00000000..53f1be40
--- /dev/null
+++ b/comwell_key_app/lib/utils/database_recovery.dart
@@ -0,0 +1,108 @@
+import 'package:comwell_key_app/utils/locator.dart';
+
+/// Utility class for handling database corruption recovery
+class DatabaseRecovery {
+ /// Executes a database operation with automatic corruption recovery
+ ///
+ /// This function will automatically detect database corruption errors and
+ /// attempt recovery by resetting the database and cipher key if needed.
+ ///
+ /// [operation] - The database operation to execute
+ /// [operationName] - Human-readable name for logging purposes
+ ///
+ /// Returns the result of the operation or throws an exception if recovery fails
+ static Future<T> executeWithRecovery<T>(
+ Future<T> Function() operation,
+ String operationName,
+ ) async {
+ try {
+ return await operation();
+ } catch (dbError) {
+ final errorString = dbError.toString();
+
+ if (errorString.contains("Can't re-open a database after closing")) {
+ // Database connection issue - try regular reset
+ try {
+ resetDatabase();
+ await Future<void>.delayed(const Duration(milliseconds: 100));
+ return await operation();
+ } catch (retryError) {
+ throw Exception('Failed to execute $operationName after database reset: $retryError');
+ }
+ } else if (errorString.contains("file is not a database") ||
+ errorString.contains("SqliteException(26)") ||
+ errorString.contains("database disk image is malformed")) {
+ // Database corruption - reset with cipher key reset
+ try {
+ resetDatabaseWithCipherReset();
+ await Future<void>.delayed(const Duration(milliseconds: 200));
+ return await operation();
+ } catch (retryError) {
+ throw Exception('Failed to execute $operationName after cipher reset: $retryError');
+ }
+ } else {
+ // Other database errors - retry once with delay
+ try {
+ await Future<void>.delayed(const Duration(milliseconds: 100));
+ return await operation();
+ } catch (retryError) {
+ throw Exception('Failed to execute $operationName: $retryError');
+ }
+ }
+ }
+ }
+
+ /// Executes a database operation with automatic corruption recovery (non-throwing version)
+ ///
+ /// This function will automatically detect database corruption errors and
+ /// attempt recovery by resetting the database and cipher key if needed.
+ /// If recovery fails, it will log the error and return null instead of throwing.
+ ///
+ /// [operation] - The database operation to execute
+ /// [operationName] - Human-readable name for logging purposes
+ ///
+ /// Returns the result of the operation or null if recovery fails
+ static Future<T?> executeWithRecoverySafe<T>(
+ Future<T> Function() operation,
+ String operationName,
+ ) async {
+ try {
+ return await operation();
+ } catch (dbError) {
+ final errorString = dbError.toString();
+
+ if (errorString.contains("Can't re-open a database after closing")) {
+ // Database connection issue - try regular reset
+ try {
+ resetDatabase();
+ await Future<void>.delayed(const Duration(milliseconds: 100));
+ return await operation();
+ } catch (retryError) {
+ print("Database reset and retry failed for $operationName: $retryError");
+ return null;
+ }
+ } else if (errorString.contains("file is not a database") ||
+ errorString.contains("SqliteException(26)") ||
+ errorString.contains("database disk image is malformed")) {
+ // Database corruption - reset with cipher key reset
+ try {
+ resetDatabaseWithCipherReset();
+ await Future<void>.delayed(const Duration(milliseconds: 200));
+ return await operation();
+ } catch (retryError) {
+ print("Database cipher reset and retry failed for $operationName: $retryError");
+ return null;
+ }
+ } else {
+ // Other database errors - retry once with delay
+ try {
+ await Future<void>.delayed(const Duration(milliseconds: 100));
+ return await operation();
+ } catch (retryError) {
+ print("Database retry failed for $operationName: $retryError");
+ return null;
+ }
+ }
+ }
+ }
+}
diff --git a/comwell_key_app/lib/utils/locator.dart b/comwell_key_app/lib/utils/locator.dart
index a04e915d..8c8545c6 100644
--- a/comwell_key_app/lib/utils/locator.dart
+++ b/comwell_key_app/lib/utils/locator.dart
@@ -29,10 +29,32 @@ final locator = GetIt.I;
void registerDatabase() {
if (!locator.isRegistered<ComwellDatabase>()) {
- locator.registerLazySingleton<ComwellDatabase>(() => ComwellDatabase());
+ locator.registerLazySingleton<ComwellDatabase>(() {
+ final db = ComwellDatabase();
+ // Ensure database is properly initialized
+ return db;
+ });
}
}
+void resetDatabase() async {
+ if (locator.isRegistered<ComwellDatabase>()) {
+ locator.unregister<ComwellDatabase>();
+ }
+ // Force database recreation to ensure new schema is applied
+ await ComwellDatabase.forceRecreate();
+ registerDatabase();
+}
+
+void resetDatabaseWithCipherReset() async {
+ if (locator.isRegistered<ComwellDatabase>()) {
+ locator.unregister<ComwellDatabase>();
+ }
+ // Force database recreation with cipher key reset for corrupted databases
+ await ComwellDatabase.forceRecreateWithCipherReset();
+ registerDatabase();
+}
+
void setupLocator() {
locator.registerFactory<DeviceInfoPlugin>(() => DeviceInfoPlugin());
if (!kIsWeb) {