Skip to content
Merged
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
48 changes: 24 additions & 24 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -260,45 +260,45 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
AppAuth: d4f13a8fe0baf391b2108511793e4b479691fb73
AppCheckCore: cc8fd0a3a230ddd401f326489c99990b013f0c4f
ark_wallet: cb7a2af0c3a711d488709b7b91b6e639cfd441b1
bdk_flutter: cef9180019b4c6b67a3e3dfb74611683fe140107
ark_wallet: f985745c06c7cf93f368c61218c51ac56b46c71e
bdk_flutter: 86c9ba59ee282dee08c3a29599abe867d10a8b10
boltz: 6388ec2412f3753b63a9e65c97f87ea26f43bddc
camera_avfoundation: 5675ca25298b6f81fa0a325188e7df62cc217741
dart_bbqr: bfd89cc8a74538d94ef6d87d11e4a2ad55578e7d
camera_avfoundation: 281867ff09f1da66f031a184ecfbc6f2e625c9f5
dart_bbqr: 10143a83ef3919f9ac06974e3bbe23cac4abc39b
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
flutter_nfc_kit: e1b71583eafd2c9650bc86844a7f2d185fb414f6
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
flutter_zxing: e8bcc43bd3056c70c271b732ed94e7a16fd62f93
google_sign_in_ios: b48bb9af78576358a168361173155596c845f0b9
flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29
flutter_nfc_kit: 3985c93f749b9cb4747479205c2f10bd2f877a11
flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12
flutter_zxing: d527c3ff9c7f3606dd29a7ec8c4055f7daa088c5
google_sign_in_ios: 7411fab6948df90490dc4620ecbcabdc3ca04017
GoogleSignIn: ce8c89bb9b37fb624b92e7514cc67335d1e277e4
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
GTMAppAuth: f69bd07d68cd3b766125f7e072c45d7340dea0de
GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
image_picker_ios: 4f2f91b01abdb52842a8e277617df877e40f905b
integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573
lwk: 22e06bc5664247d6b2dac91cfe209b63b70dd580
no_screenshot: 6d183496405a3ab709a67a54e5cd0f639e94729e
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
no_screenshot: 67d110f12466f4913b488803d4e498d03ef2889e
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
path_provider_foundation: 0b743cbb62d8e47eab856f09262bb8c1ddcfe6ba
payjoin_flutter: 6397d7b698cdad6453be4949ab6aca1863f6c5e5
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
ScreenProtectorKit: 83a6281b02c7a5902ee6eac4f5045f674e902ae4
SDWebImage: e9c98383c7572d713c1a0d7dd2783b10599b9838
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
sqlite3: 8d708bc63e9f4ce48f0ad9d6269e478c5ced1d9b
sqlite3_flutter_libs: d13b8b3003f18f596e542bcb9482d105577eff41
sqlite3_flutter_libs: c7b32a4d17d2cbad19895873ccc815f276b1f125
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
tor: 767208930250ef7be241963b75568c55c0a81890
universal_ble: ff19787898040d721109c6324472e5dd4bc86adc
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
webview_cookie_manager: d63a76cabdf42a7ea3d92768ac67d4853a1367f8
webview_flutter_wkwebview: 8ebf4fded22593026f7dbff1fbff31ea98573c8d
workmanager_apple: 904529ae31e97fc5be632cf628507652294a0778
tor: 662a9f5b980b5c86decb8ba611de9bcd4c8286eb
universal_ble: cf52a7b3fd2e7c14d6d7262e9fdadb72ab6b88a6
url_launcher_ios: bb13df5870e8c4234ca12609d04010a21be43dfa
webview_cookie_manager: eaf920722b493bd0f7611b5484771ca53fed03f7
webview_flutter_wkwebview: 29eb20d43355b48fe7d07113835b9128f84e3af4
workmanager_apple: 7bac258335c310689a641e2d66e88d4845d372e9

PODFILE CHECKSUM: cc3fb9f3ad75d6f5db35855d6f129b8b2fb2d9f5

Expand Down
39 changes: 39 additions & 0 deletions lib/core/exchange/data/datasources/bullbitcoin_api_datasource.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class BullbitcoinApiDatasource implements BitcoinPriceDatasource {
final _ordersPath = '/ak/api-orders';
final _orderTriggerPath = '/ak/api-ordertrigger';
final _recipientsPath = '/ak/api-recipients';
final _messagesPath = '/ak/api-commcenter';

BullbitcoinApiDatasource({required Dio bullbitcoinApiHttpClient})
: _http = bullbitcoinApiHttpClient;
Expand Down Expand Up @@ -583,6 +584,44 @@ class BullbitcoinApiDatasource implements BitcoinPriceDatasource {

return resp.data['result'] as Map<String, dynamic>;
}

Future<List<Map<String, dynamic>>> listAnnouncements({
required String apiKey,
}) async {
try {
final resp = await _http.post(
_messagesPath,
data: {
'jsonrpc': '2.0',
'id': '1',
'method': 'listAnnouncements',
'params': {
'paginator': {'pageSize': 5, 'page': 1},
'sortBy': {'id': 'updatedAt', 'sort': 'desc'},
},
},
options: Options(headers: {'X-API-Key': apiKey}),
);

if (resp.statusCode == null || resp.statusCode != 200) {
throw Exception('Failed to list announcements');
}

final error = resp.data['error'];
if (error != null) {
throw Exception('Failed to list announcements: $error');
}

final result = resp.data['result'] as Map<String, dynamic>?;
if (result == null) {
return [];
}
final items = result['elements'] as List<dynamic>? ?? [];
return items.cast<Map<String, dynamic>>();
} catch (e) {
rethrow;
}
}
}

class BullBitcoinApiMinAmountException extends BullException {
Expand Down
28 changes: 28 additions & 0 deletions lib/core/exchange/data/models/announcement_model.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import 'package:bb_mobile/core/exchange/domain/entity/announcement.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

part 'announcement_model.freezed.dart';
part 'announcement_model.g.dart';

@freezed
sealed class AnnouncementModel with _$AnnouncementModel {
const factory AnnouncementModel({
required String title,
required String description,
required String updatedAt,
}) = _AnnouncementModel;

factory AnnouncementModel.fromJson(Map<String, dynamic> json) =>
_$AnnouncementModelFromJson(json);

const AnnouncementModel._();

Announcement toEntity() {
return Announcement(
title: title,
description: description,
updatedAt: DateTime.parse(updatedAt),
);
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import 'package:bb_mobile/core/errors/exchange_errors.dart';
import 'package:bb_mobile/core/exchange/data/datasources/bullbitcoin_api_datasource.dart';
import 'package:bb_mobile/core/exchange/data/datasources/bullbitcoin_api_key_datasource.dart';
import 'package:bb_mobile/core/exchange/data/mappers/user_summary_mapper.dart';
import 'package:bb_mobile/core/exchange/data/models/announcement_model.dart';
import 'package:bb_mobile/core/exchange/data/models/user_preference_payload_model.dart';
import 'package:bb_mobile/core/exchange/domain/entity/announcement.dart';
import 'package:bb_mobile/core/exchange/domain/entity/user_summary.dart';
import 'package:bb_mobile/core/exchange/domain/repositories/exchange_user_repository.dart';

Expand Down Expand Up @@ -90,4 +92,36 @@ class ExchangeUserRepositoryImpl implements ExchangeUserRepository {
}
}
}

@override
Future<List<Announcement>> listAnnouncements() async {
try {
final apiKey = await _bullbitcoinApiKeyDatasource.get(
isTestnet: _isTestnet,
);
if (apiKey == null) {
throw ApiKeyException(
'API key not found. Please login to your Bull Bitcoin account.',

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Text should go in the localizations files

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure but I think this is an error that doesn't reach the user level, so only in logs

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

none of the APIKeyExceptions are localised (35 occurrences) .
@i5hi pls confirm what to do here

);
}
try {
final announcementDataList =
await _bullbitcoinApiDatasource.listAnnouncements(
apiKey: apiKey.key,
);
final announcements = announcementDataList
.map((json) => AnnouncementModel.fromJson(json).toEntity())
.toList();
return announcements;
} catch (e) {
throw Exception('Failed to fetch announcements: $e');
}
} catch (e) {
if (e is ApiKeyException) {
rethrow;
} else {
throw Exception('Failed to fetch announcements: $e');
}
}
}
}
15 changes: 15 additions & 0 deletions lib/core/exchange/domain/entity/announcement.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import 'package:freezed_annotation/freezed_annotation.dart';

part 'announcement.freezed.dart';

@freezed
sealed class Announcement with _$Announcement {
const factory Announcement({
required String title,
required String description,
required DateTime updatedAt,
}) = _Announcement;

const Announcement._();
}

Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:bb_mobile/core/exchange/domain/entity/announcement.dart';
import 'package:bb_mobile/core/exchange/domain/entity/user_summary.dart';

abstract class ExchangeUserRepository {
Expand All @@ -8,4 +9,5 @@ abstract class ExchangeUserRepository {
bool? dcaEnabled,
String? autoBuyEnabled,
});
Future<List<Announcement>> listAnnouncements();
}
42 changes: 42 additions & 0 deletions lib/core/exchange/domain/usecases/get_announcements_usecase.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import 'package:bb_mobile/core/errors/bull_exception.dart';
import 'package:bb_mobile/core/errors/exchange_errors.dart';
import 'package:bb_mobile/core/exchange/domain/entity/announcement.dart';
import 'package:bb_mobile/core/exchange/domain/repositories/exchange_user_repository.dart';
import 'package:bb_mobile/core/settings/data/settings_repository.dart';

class GetAnnouncementsUsecase {
final ExchangeUserRepository _mainnetExchangeUserRepository;
final ExchangeUserRepository _testnetExchangeUserRepository;
final SettingsRepository _settingsRepository;

GetAnnouncementsUsecase({
required ExchangeUserRepository mainnetExchangeUserRepository,
required ExchangeUserRepository testnetExchangeUserRepository,
required SettingsRepository settingsRepository,
}) : _mainnetExchangeUserRepository = mainnetExchangeUserRepository,
_testnetExchangeUserRepository = testnetExchangeUserRepository,
_settingsRepository = settingsRepository;

Future<List<Announcement>> execute() async {
try {
final settings = await _settingsRepository.fetch();
final isTestnet = settings.environment.isTestnet;
final repo =
isTestnet
? _testnetExchangeUserRepository
: _mainnetExchangeUserRepository;
final announcements = await repo.listAnnouncements();
return announcements;
} catch (e) {
if (e is ApiKeyException) {
rethrow;
}
throw GetAnnouncementsException('$e');
}
}
}

class GetAnnouncementsException extends BullException {
GetAnnouncementsException(super.message);
}

13 changes: 13 additions & 0 deletions lib/core/exchange/exchange_locator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import 'package:bb_mobile/core/exchange/domain/usecases/create_log_attachment_us
import 'package:bb_mobile/core/exchange/domain/usecases/delete_exchange_api_key_usecase.dart';
import 'package:bb_mobile/core/exchange/domain/usecases/get_available_currencies_usecase.dart';
import 'package:bb_mobile/core/exchange/domain/usecases/get_exchange_funding_details_usecase.dart';
import 'package:bb_mobile/core/exchange/domain/usecases/get_announcements_usecase.dart';
import 'package:bb_mobile/core/exchange/domain/usecases/get_exchange_user_summary_usecase.dart';
import 'package:bb_mobile/core/exchange/domain/usecases/get_order_usercase.dart';
import 'package:bb_mobile/core/exchange/domain/usecases/get_price_history_usecase.dart';
Expand Down Expand Up @@ -306,6 +307,18 @@ class ExchangeLocator {
),
);

locator.registerFactory<GetAnnouncementsUsecase>(
() => GetAnnouncementsUsecase(
mainnetExchangeUserRepository: locator<ExchangeUserRepository>(
instanceName: 'mainnetExchangeUserRepository',
),
testnetExchangeUserRepository: locator<ExchangeUserRepository>(
instanceName: 'testnetExchangeUserRepository',
),
settingsRepository: locator<SettingsRepository>(),
),
);

locator.registerFactory<CreateBuyOrderUsecase>(
() => CreateBuyOrderUsecase(
mainnetExchangeOrderRepository: locator<ExchangeOrderRepository>(
Expand Down
2 changes: 2 additions & 0 deletions lib/features/exchange/exchange_locator.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:bb_mobile/core/exchange/domain/usecases/delete_exchange_api_key_usecase.dart';
import 'package:bb_mobile/core/exchange/domain/usecases/get_announcements_usecase.dart';
import 'package:bb_mobile/core/exchange/domain/usecases/get_exchange_user_summary_usecase.dart';
import 'package:bb_mobile/core/exchange/domain/usecases/save_exchange_api_key_usecase.dart';
import 'package:bb_mobile/core/exchange/domain/usecases/save_user_preferences_usecase.dart';
Expand All @@ -18,6 +19,7 @@ class ExchangeLocator {
locator.get<GetExchangeUserSummaryUsecase>(),
saveUserPreferencesUsecase: locator.get<SaveUserPreferencesUsecase>(),
deleteExchangeApiKeyUsecase: locator.get<DeleteExchangeApiKeyUsecase>(),
getAnnouncementsUsecase: locator.get<GetAnnouncementsUsecase>(),
),
);
}
Expand Down
28 changes: 28 additions & 0 deletions lib/features/exchange/presentation/exchange_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:async';

import 'package:bb_mobile/core/errors/exchange_errors.dart';
import 'package:bb_mobile/core/exchange/domain/usecases/delete_exchange_api_key_usecase.dart';
import 'package:bb_mobile/core/exchange/domain/usecases/get_announcements_usecase.dart';
import 'package:bb_mobile/core/exchange/domain/usecases/get_exchange_user_summary_usecase.dart';
import 'package:bb_mobile/core/exchange/domain/usecases/save_exchange_api_key_usecase.dart';
import 'package:bb_mobile/core/exchange/domain/usecases/save_user_preferences_usecase.dart';
Expand All @@ -16,10 +17,12 @@ class ExchangeCubit extends Cubit<ExchangeState> {
required SaveExchangeApiKeyUsecase saveExchangeApiKeyUsecase,
required SaveUserPreferencesUsecase saveUserPreferencesUsecase,
required DeleteExchangeApiKeyUsecase deleteExchangeApiKeyUsecase,
required GetAnnouncementsUsecase getAnnouncementsUsecase,
}) : _getExchangeUserSummaryUsecase = getExchangeUserSummaryUsecase,
_saveExchangeApiKeyUsecase = saveExchangeApiKeyUsecase,
_saveUserPreferencesUsecase = saveUserPreferencesUsecase,
_deleteExchangeApiKeyUsecase = deleteExchangeApiKeyUsecase,
_getAnnouncementsUsecase = getAnnouncementsUsecase,
super(const ExchangeState());

Timer? _pollingTimer;
Expand All @@ -28,6 +31,7 @@ class ExchangeCubit extends Cubit<ExchangeState> {
final SaveExchangeApiKeyUsecase _saveExchangeApiKeyUsecase;
final SaveUserPreferencesUsecase _saveUserPreferencesUsecase;
final DeleteExchangeApiKeyUsecase _deleteExchangeApiKeyUsecase;
final GetAnnouncementsUsecase _getAnnouncementsUsecase;

Future<void> fetchUserSummary() async {
try {
Expand All @@ -41,6 +45,7 @@ class ExchangeCubit extends Cubit<ExchangeState> {
emit(state.copyWith(selectedLanguage: userSummary.language));
emit(state.copyWith(selectedCurrency: userSummary.currency));

loadAnnouncements();
startPolling();
} catch (e) {
log.severe('$ExchangeCubit init: $e');
Expand Down Expand Up @@ -151,6 +156,29 @@ class ExchangeCubit extends Cubit<ExchangeState> {
}
}

void loadAnnouncements() async {
emit(
state.copyWith(loadingAnnouncements: true, errLoadingAnnouncements: null),
);
try {
final announcements = await _getAnnouncementsUsecase.execute();
emit(
state.copyWith(
announcements: announcements,
loadingAnnouncements: false,
),
);
} catch (e) {
log.warning('Failed to load announcements: $e');
emit(
state.copyWith(
errLoadingAnnouncements: e.toString(),
loadingAnnouncements: false,
),
);
}
}

@override
Future<void> close() {
stopPolling();
Expand Down
4 changes: 4 additions & 0 deletions lib/features/exchange/presentation/exchange_state.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:bb_mobile/core/errors/exchange_errors.dart';
import 'package:bb_mobile/core/exchange/domain/entity/announcement.dart';
import 'package:bb_mobile/core/exchange/domain/entity/user_summary.dart';
import 'package:bb_mobile/core/exchange/domain/usecases/delete_exchange_api_key_usecase.dart';
import 'package:bb_mobile/core/exchange/domain/usecases/get_exchange_user_summary_usecase.dart';
Expand All @@ -18,6 +19,9 @@ abstract class ExchangeState with _$ExchangeState {
String? selectedLanguage,
String? selectedCurrency,
@Default(false) bool isSaving,
@Default([]) List<Announcement> announcements,
@Default(false) bool loadingAnnouncements,
String? errLoadingAnnouncements,
}) = _ExchangeState;

const ExchangeState._();
Expand Down
Loading