Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions lib/features/legacy_seed_view/domain/get_legacy_seeds_usecase.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import 'package:bb_mobile/core/storage/data/datasources/key_value_storage/keychain_locked_exception.dart';
import 'package:bb_mobile/core/storage/migrations/005_hive_to_sqlite/get_old_seeds_usecase.dart';
import 'package:bb_mobile/core/storage/migrations/005_hive_to_sqlite/old/entities/old_seed.dart';
import 'package:bb_mobile/core/utils/logger.dart';
import 'package:bb_mobile/core/utils/result.dart';
import 'package:bb_mobile/features/legacy_seed_view/domain/legacy_seed_view_failure.dart';
import 'package:meta/meta.dart';

/// Feature boundary: wraps the shared core [GetOldSeedsUsecase] (which still
/// throws) in the first layer this feature owns. Logs sanitized exception
/// metadata and returns a typed [LegacySeedViewFailure] so nothing above ever
/// sees raw exception text.
class GetLegacySeedsUsecase {
final GetOldSeedsUsecase _getOldSeedsUsecase;

GetLegacySeedsUsecase({required this._getOldSeedsUsecase});

@useResult
Future<Result<List<OldSeed>, LegacySeedViewFailure>> execute() async {
try {
final seeds = await _getOldSeedsUsecase.execute();
return Ok(_dedupeByMnemonic(seeds));
} on KeychainLockedException catch (e, st) {
// Exceptions from this secret-bearing flow may contain key material.
// Record the safe type and trace, never the exception payload itself.
log.warning(
'fetch legacy seeds unavailable (${e.runtimeType})',
error: e.runtimeType,
trace: st,
);
return const Err(LegacySeedViewFetchFailure());
} catch (e, st) {
log.severe(
message: 'fetch legacy seeds failed (${e.runtimeType})',
error: e.runtimeType,
trace: st,
);
return const Err(LegacySeedViewUnexpectedFailure());
}
}

/// Collapses seeds that share a mnemonic into one entry, merging their
/// passphrases (deduplicated by passphrase + source fingerprint).
List<OldSeed> _dedupeByMnemonic(List<OldSeed> seeds) {
final mnemonicMap = <String, OldSeed>{};
for (final seed in seeds) {
final existing = mnemonicMap[seed.mnemonic];
if (existing == null) {
mnemonicMap[seed.mnemonic] = seed;
continue;
}
mnemonicMap[seed.mnemonic] = existing.copyWith(
passphrases: [
...existing.passphrases,
...seed.passphrases.where(
(p) => !existing.passphrases.any(
(ep) =>
ep.passphrase == p.passphrase &&
ep.sourceFingerprint == p.sourceFingerprint,
),
),
],
);
}
return mnemonicMap.values.toList();
}
}
13 changes: 13 additions & 0 deletions lib/features/legacy_seed_view/domain/legacy_seed_view_failure.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import 'package:bb_mobile/core/failures/failure.dart';

sealed class LegacySeedViewFailure extends Failure {
const LegacySeedViewFailure([super.logMessage]);
}

final class LegacySeedViewFetchFailure extends LegacySeedViewFailure {
const LegacySeedViewFetchFailure([super.logMessage]);
}

final class LegacySeedViewUnexpectedFailure extends LegacySeedViewFailure {
const LegacySeedViewUnexpectedFailure([super.logMessage]);
}
8 changes: 7 additions & 1 deletion lib/features/legacy_seed_view/legacy_seed_view_locator.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import 'package:bb_mobile/core/storage/migrations/005_hive_to_sqlite/get_old_seeds_usecase.dart';
import 'package:bb_mobile/features/legacy_seed_view/domain/get_legacy_seeds_usecase.dart';
import 'package:bb_mobile/features/legacy_seed_view/presentation/legacy_seed_view_cubit.dart';
import 'package:get_it/get_it.dart';

class LegacySeedViewLocator {
static void setup(GetIt locator) {
locator.registerFactory<GetLegacySeedsUsecase>(
() => GetLegacySeedsUsecase(
getOldSeedsUsecase: locator<GetOldSeedsUsecase>(),
),
);
locator.registerFactory<LegacySeedViewCubit>(
() => LegacySeedViewCubit(
getOldSeedsUsecase: locator<GetOldSeedsUsecase>(),
getLegacySeedsUsecase: locator<GetLegacySeedsUsecase>(),
),
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,50 +1,26 @@
import 'package:bb_mobile/core/storage/migrations/005_hive_to_sqlite/get_old_seeds_usecase.dart';
import 'package:bb_mobile/core/storage/migrations/005_hive_to_sqlite/old/entities/old_seed.dart';
import 'package:bb_mobile/core/utils/result.dart';
import 'package:bb_mobile/features/legacy_seed_view/domain/get_legacy_seeds_usecase.dart';
import 'package:bb_mobile/features/legacy_seed_view/domain/legacy_seed_view_failure.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

part 'legacy_seed_view_cubit.freezed.dart';
part 'legacy_seed_view_state.dart';

class LegacySeedViewCubit extends Cubit<LegacySeedViewState> {
LegacySeedViewCubit({required this._getOldSeedsUsecase})
: super(const LegacySeedViewState());
final GetLegacySeedsUsecase _getLegacySeedsUsecase;

final GetOldSeedsUsecase _getOldSeedsUsecase;
LegacySeedViewCubit({required this._getLegacySeedsUsecase})
: super(const LegacySeedViewState());

Future<void> fetchOldSeeds() async {
emit(state.copyWith(loading: true, error: null));
try {
final seeds = await _getOldSeedsUsecase.execute();
final Map<String, OldSeed> mnemonicMap = {};
for (final seed in seeds) {
if (mnemonicMap.containsKey(seed.mnemonic)) {
final existing = mnemonicMap[seed.mnemonic]!;
mnemonicMap[seed.mnemonic] = existing.copyWith(
passphrases: [
...existing.passphrases,
...seed.passphrases.where(
(p) => !existing.passphrases.any(
(ep) =>
ep.passphrase == p.passphrase &&
ep.sourceFingerprint == p.sourceFingerprint,
),
),
],
);
} else {
mnemonicMap[seed.mnemonic] = seed;
}
}
emit(
state.copyWith(
loading: false,
seeds: mnemonicMap.values.toList(),
error: null,
),
);
} catch (e) {
emit(state.copyWith(loading: false, error: e.toString()));
emit(state.copyWith(loading: true, failure: null));
switch (await _getLegacySeedsUsecase.execute()) {
case Ok(:final value):
emit(state.copyWith(loading: false, seeds: value, failure: null));
case Err(:final failure):
emit(state.copyWith(loading: false, failure: failure));
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import 'package:bb_mobile/core/utils/build_context_x.dart';
import 'package:bb_mobile/features/legacy_seed_view/domain/legacy_seed_view_failure.dart';
import 'package:flutter/widgets.dart';

extension LegacySeedViewFailureL10n on LegacySeedViewFailure {
String toTranslated(BuildContext context) => switch (this) {
LegacySeedViewFetchFailure() => context.loc.legacySeedViewErrorFetch,
LegacySeedViewUnexpectedFailure() => context.loc.oopsSomethingWentWrong,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ abstract class LegacySeedViewState with _$LegacySeedViewState {
const factory LegacySeedViewState({
@Default(<OldSeed>[]) List<OldSeed> seeds,
@Default(false) bool loading,
String? error,
LegacySeedViewFailure? failure,
}) = _LegacySeedViewState;
const LegacySeedViewState._();
}
27 changes: 21 additions & 6 deletions lib/features/legacy_seed_view/ui/legacy_seed_view_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,27 @@ import 'package:bb_mobile/core/themes/app_theme.dart';
import 'package:bb_mobile/core/utils/build_context_x.dart';
import 'package:bb_mobile/core/widgets/text/text.dart';
import 'package:bb_mobile/features/legacy_seed_view/presentation/legacy_seed_view_cubit.dart';
import 'package:bb_mobile/features/legacy_seed_view/presentation/legacy_seed_view_failure_l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class LegacySeedViewScreen extends StatelessWidget {
class LegacySeedViewScreen extends StatefulWidget {
const LegacySeedViewScreen({super.key});

@override
State<LegacySeedViewScreen> createState() => _LegacySeedViewScreenState();
}

class _LegacySeedViewScreenState extends State<LegacySeedViewScreen> {
@override
void initState() {
super.initState();
// Fetch once on mount. Triggering the fetch from BlocBuilder.builder
// instead re-fires on every rebuild when the result is an empty success
// (the user has no legacy seeds), which loops indefinitely.
context.read<LegacySeedViewCubit>().fetchOldSeeds();
}

@override
Widget build(BuildContext context) {
return PopScope(
Expand All @@ -25,15 +40,15 @@ class LegacySeedViewScreen extends StatelessWidget {
),
body: BlocBuilder<LegacySeedViewCubit, LegacySeedViewState>(
builder: (context, state) {
if (!state.loading && state.seeds.isEmpty && state.error == null) {
context.read<LegacySeedViewCubit>().fetchOldSeeds();
}
if (state.loading) {
return const Center(child: CircularProgressIndicator());
}
if (state.error != null) {
if (state.failure case final failure?) {
return Center(
child: BBText(state.error!, style: context.font.bodyLarge),
child: BBText(
failure.toTranslated(context),
style: context.font.bodyLarge,
),
);
}
if (state.seeds.isEmpty) {
Expand Down
3 changes: 2 additions & 1 deletion localization/app_ar.arb
Original file line number Diff line number Diff line change
Expand Up @@ -13281,5 +13281,6 @@
"receiveMessageForSender": "رسالة إلى المرسل",
"sendPayjoinLabel": "Payjoin",
"receivePayjoinAwaitingFunds": "تم تفعيل Payjoin، لكن هذه المحفظة لا تحتوي بعد على رصيد للمساهمة. سيتم تفعيله بمجرد استلام أموال.",
"announcementAutoswapDescription": "يتم تحويل رصيد Liquid الخاص بك تلقائيًا إلى Bitcoin."
"announcementAutoswapDescription": "يتم تحويل رصيد Liquid الخاص بك تلقائيًا إلى Bitcoin.",
"legacySeedViewErrorFetch": "فشل تحميل البذور القديمة. يرجى المحاولة مرة أخرى."
}
3 changes: 2 additions & 1 deletion localization/app_as.arb
Original file line number Diff line number Diff line change
Expand Up @@ -10325,5 +10325,6 @@
"receiveMessageForSender": "প্ৰেৰকৰ বাবে বাৰ্তা",
"sendPayjoinLabel": "Payjoin",
"receivePayjoinAwaitingFunds": "Payjoin সক্ৰিয় কৰা হৈছে, কিন্তু এই ৱালেটত এতিয়াও অৱদান দিবলৈ কোনো বেলেঞ্চ নাই। আপুনি ধন লাভ কৰিলেই ই সক্ৰিয় হ’ব।",
"announcementAutoswapDescription": "আপোনাৰ Liquid স্বয়ংক্ৰিয়ভাৱে Bitcoin-লৈ ৰূপান্তৰ কৰা হয়।"
"announcementAutoswapDescription": "আপোনাৰ Liquid স্বয়ংক্ৰিয়ভাৱে Bitcoin-লৈ ৰূপান্তৰ কৰা হয়।",
"legacySeedViewErrorFetch": "পুৰণা ছীড লোড কৰিব পৰা নগ’ল। অনুগ্ৰহ কৰি পুনৰ চেষ্টা কৰক।"
}
3 changes: 2 additions & 1 deletion localization/app_bg.arb
Original file line number Diff line number Diff line change
Expand Up @@ -10967,5 +10967,6 @@
"receiveMessageForSender": "Съобщение за изпращача",
"sendPayjoinLabel": "Payjoin",
"receivePayjoinAwaitingFunds": "Payjoin е активиран, но този портфейл все още няма наличност за участие. Ще се активира, щом получите средства.",
"announcementAutoswapDescription": "Вашият Liquid се обменя автоматично за Bitcoin."
"announcementAutoswapDescription": "Вашият Liquid се обменя автоматично за Bitcoin.",
"legacySeedViewErrorFetch": "Неуспешно зареждане на наследените семена. Моля, опитайте отново."
}
3 changes: 2 additions & 1 deletion localization/app_bn.arb
Original file line number Diff line number Diff line change
Expand Up @@ -13488,5 +13488,6 @@
"receiveMessageForSender": "প্রেরকের জন্য বার্তা",
"sendPayjoinLabel": "Payjoin",
"receivePayjoinAwaitingFunds": "Payjoin চালু আছে, কিন্তু এই ওয়ালেটে অবদান রাখার মতো কোনো ব্যালেন্স নেই। অর্থ পাওয়ার পর এটি সক্রিয় হবে।",
"announcementAutoswapDescription": "আপনার Liquid স্বয়ংক্রিয়ভাবে Bitcoin-এ রূপান্তরিত হয়।"
"announcementAutoswapDescription": "আপনার Liquid স্বয়ংক্রিয়ভাবে Bitcoin-এ রূপান্তরিত হয়।",
"legacySeedViewErrorFetch": "পুরোনো সিড লোড করা যায়নি। অনুগ্রহ করে আবার চেষ্টা করুন।"
}
3 changes: 2 additions & 1 deletion localization/app_cs.arb
Original file line number Diff line number Diff line change
Expand Up @@ -13278,5 +13278,6 @@
"receiveMessageForSender": "Zpráva pro odesílatele",
"sendPayjoinLabel": "Payjoin",
"receivePayjoinAwaitingFunds": "Payjoin je povolen, ale tato peněženka zatím nemá zůstatek, kterým by mohla přispět. Aktivuje se, jakmile obdržíte prostředky.",
"announcementAutoswapDescription": "Váš Liquid se automaticky směňuje za Bitcoin."
"announcementAutoswapDescription": "Váš Liquid se automaticky směňuje za Bitcoin.",
"legacySeedViewErrorFetch": "Nepodařilo se načíst starší seedy. Zkuste to prosím znovu."
}
3 changes: 2 additions & 1 deletion localization/app_de.arb
Original file line number Diff line number Diff line change
Expand Up @@ -5221,5 +5221,6 @@
"receiveMessageForSender": "Nachricht an den Sender",
"sendPayjoinLabel": "Payjoin",
"receivePayjoinAwaitingFunds": "Payjoin ist aktiviert, aber dieses Wallet hat noch kein Guthaben für einen Beitrag. Es wird aktiv, sobald du Guthaben empfängst.",
"announcementAutoswapDescription": "Dein Liquid wird automatisch in Bitcoin getauscht."
"announcementAutoswapDescription": "Dein Liquid wird automatisch in Bitcoin getauscht.",
"legacySeedViewErrorFetch": "Legacy-Seeds konnten nicht geladen werden. Bitte versuchen Sie es erneut."
}
3 changes: 2 additions & 1 deletion localization/app_el.arb
Original file line number Diff line number Diff line change
Expand Up @@ -13278,5 +13278,6 @@
"receiveMessageForSender": "Μήνυμα για τον αποστολέα",
"sendPayjoinLabel": "Payjoin",
"receivePayjoinAwaitingFunds": "Το Payjoin είναι ενεργοποιημένο, αλλά αυτό το πορτοφόλι δεν έχει ακόμη υπόλοιπο για συνεισφορά. Θα ενεργοποιηθεί μόλις λάβετε χρήματα.",
"announcementAutoswapDescription": "Το Liquid σας ανταλλάσσεται αυτόματα σε Bitcoin."
"announcementAutoswapDescription": "Το Liquid σας ανταλλάσσεται αυτόματα σε Bitcoin.",
"legacySeedViewErrorFetch": "Αποτυχία φόρτωσης των παλαιών seeds. Παρακαλώ δοκιμάστε ξανά."
}
4 changes: 4 additions & 0 deletions localization/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -15024,5 +15024,9 @@
"receivePayjoinBelowMinimumAmount": "Payjoin is off for this amount — it is below your Payjoin minimum. Request a larger amount to enable it.",
"@receivePayjoinBelowMinimumAmount": {
"description": "Hint on the receive screen when payjoin is enabled but the requested amount is below the anti-probing minimum, so the QR does not advertise a payjoin endpoint."
},
"legacySeedViewErrorFetch": "Failed to load legacy seeds. Please try again.",
"@legacySeedViewErrorFetch": {
"description": "Error shown when legacy seeds cannot be loaded"
}
}
3 changes: 2 additions & 1 deletion localization/app_es.arb
Original file line number Diff line number Diff line change
Expand Up @@ -5270,5 +5270,6 @@
"receiveMessageForSender": "Mensaje para el remitente",
"sendPayjoinLabel": "Payjoin",
"receivePayjoinAwaitingFunds": "Payjoin está activado, pero esta cartera todavía no tiene saldo para contribuir. Se activará cuando recibas fondos.",
"announcementAutoswapDescription": "Tu Liquid se convierte automáticamente en Bitcoin."
"announcementAutoswapDescription": "Tu Liquid se convierte automáticamente en Bitcoin.",
"legacySeedViewErrorFetch": "No se pudieron cargar las semillas antiguas. Por favor, inténtalo de nuevo."
}
3 changes: 2 additions & 1 deletion localization/app_fa.arb
Original file line number Diff line number Diff line change
Expand Up @@ -13278,5 +13278,6 @@
"receiveMessageForSender": "پیام برای فرستنده",
"sendPayjoinLabel": "Payjoin",
"receivePayjoinAwaitingFunds": "Payjoin فعال است، اما این کیف پول هنوز موجودی‌ای برای مشارکت ندارد. پس از دریافت وجه فعال خواهد شد.",
"announcementAutoswapDescription": "Liquid شما به‌طور خودکار به Bitcoin تبدیل می‌شود."
"announcementAutoswapDescription": "Liquid شما به‌طور خودکار به Bitcoin تبدیل می‌شود.",
"legacySeedViewErrorFetch": "بارگذاری سیدهای قدیمی ناموفق بود. لطفاً دوباره تلاش کنید."
}
3 changes: 2 additions & 1 deletion localization/app_fi.arb
Original file line number Diff line number Diff line change
Expand Up @@ -5270,5 +5270,6 @@
"receiveMessageForSender": "Viesti lähettäjälle",
"sendPayjoinLabel": "Payjoin",
"receivePayjoinAwaitingFunds": "Payjoin on käytössä, mutta tässä lompakossa ei vielä ole osallistumiseen tarvittavaa saldoa. Se aktivoituu, kun vastaanotat varoja.",
"announcementAutoswapDescription": "Liquid-varasi vaihdetaan automaattisesti Bitcoiniin."
"announcementAutoswapDescription": "Liquid-varasi vaihdetaan automaattisesti Bitcoiniin.",
"legacySeedViewErrorFetch": "Vanhojen siementen lataaminen epäonnistui. Yritä uudelleen."
}
3 changes: 2 additions & 1 deletion localization/app_fr.arb
Original file line number Diff line number Diff line change
Expand Up @@ -5289,5 +5289,6 @@
"receiveAdditionalInformationPlaceholder": "Ajouter un montant ou un message pour l'expéditeur",
"receiveMessageForSender": "Message pour l'expéditeur",
"sendPayjoinLabel": "Payjoin",
"receivePayjoinAwaitingFunds": "Payjoin est activé, mais ce portefeuille n’a pas encore de solde à apporter. Il s’activera dès que vous recevrez des fonds."
"receivePayjoinAwaitingFunds": "Payjoin est activé, mais ce portefeuille n’a pas encore de solde à apporter. Il s’activera dès que vous recevrez des fonds.",
"legacySeedViewErrorFetch": "Échec du chargement des anciens mnémoniques. Veuillez réessayer."
}
3 changes: 2 additions & 1 deletion localization/app_hi.arb
Original file line number Diff line number Diff line change
Expand Up @@ -13468,5 +13468,6 @@
"receiveMessageForSender": "भेजने वाले के लिए संदेश",
"sendPayjoinLabel": "Payjoin",
"receivePayjoinAwaitingFunds": "Payjoin चालू है, लेकिन इस वॉलेट में योगदान देने के लिए अभी कोई बैलेंस नहीं है। धन प्राप्त होते ही यह सक्रिय हो जाएगा।",
"announcementAutoswapDescription": "आपका Liquid स्वचालित रूप से Bitcoin में बदल दिया जाता है।"
"announcementAutoswapDescription": "आपका Liquid स्वचालित रूप से Bitcoin में बदल दिया जाता है।",
"legacySeedViewErrorFetch": "पुराने सीड लोड करने में विफल। कृपया पुनः प्रयास करें।"
}
3 changes: 2 additions & 1 deletion localization/app_hi_Latn.arb
Original file line number Diff line number Diff line change
Expand Up @@ -13673,5 +13673,6 @@
"receiveMessageForSender": "Sender ke liye message",
"sendPayjoinLabel": "Payjoin",
"receivePayjoinAwaitingFunds": "Payjoin chalu hai, lekin is wallet mein yogdan dene ke liye abhi koi balance nahin hai. Funds milte hi yeh active ho jayega.",
"announcementAutoswapDescription": "Aapka Liquid apne aap Bitcoin mein badal diya jata hai."
"announcementAutoswapDescription": "Aapka Liquid apne aap Bitcoin mein badal diya jata hai.",
"legacySeedViewErrorFetch": "Purane seeds load karne mein vifal. Kripya punah prayas karein."
}
3 changes: 2 additions & 1 deletion localization/app_hy.arb
Original file line number Diff line number Diff line change
Expand Up @@ -3748,5 +3748,6 @@
"receiveMessageForSender": "Հաղորդագրություն ուղարկողին",
"sendPayjoinLabel": "Payjoin",
"receivePayjoinAwaitingFunds": "Payjoin-ը միացված է, սակայն այս դրամապանակը դեռ ներդրման համար մնացորդ չունի։ Այն կակտիվանա միջոցներ ստանալուն պես։",
"announcementAutoswapDescription": "Ձեր Liquid-ը ավտոմատ փոխանակվում է Bitcoin-ի։"
"announcementAutoswapDescription": "Ձեր Liquid-ը ավտոմատ փոխանակվում է Bitcoin-ի։",
"legacySeedViewErrorFetch": "Չհաջողվեց բեռնել հին սերմերը։ Խնդրում ենք կրկին փորձել։"
}
Loading
Loading