diff --git a/.gitignore b/.gitignore index 0bcd9b458..e7b88ff08 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,9 @@ .history .svn/ + +# Fastlane + # IntelliJ related *.iml *.ipr @@ -137,16 +140,6 @@ macos/archethic_wallet.app.dSYM.zip macos/archethic_wallet.pkg .firebase/hosting.YnVpbGQvd2Vi.cache node_modules/ -fastlane/metadata/android/Appfile -fastlane/metadata/ios/Appfile -fastlane/metadata/ios/Preview.html -fastlane/metadata/ios/report.xml -fastlane/metadata/macos/Appfile -fastlane/metadata/macos/Preview.html -fastlane/metadata/macos/report.xml -fastlane/metadata/android/report.xml -android/fastlane/Appfile -ios/fastlane/Appfile -macos/fastlane/Appfile -ios/fastlane/Preview.html -macos/fastlane/Preview.html +Appfile +fastlane/metadata/**/Preview.html +fastlane/metadata/**/report.xml diff --git a/Gemfile b/Gemfile index 7a118b49b..cdd3a6b34 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,6 @@ source "https://rubygems.org" gem "fastlane" + +plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') +eval_gemfile(plugins_path) if File.exist?(plugins_path) diff --git a/android/fastlane/Fastfile b/android/fastlane/Fastfile deleted file mode 100755 index aa2336036..000000000 --- a/android/fastlane/Fastfile +++ /dev/null @@ -1,98 +0,0 @@ -# This file contains the fastlane.tools configuration -# You can find the documentation at https://docs.fastlane.tools -# -# For a list of all available actions, check out -# -# https://docs.fastlane.tools/actions -# -# For a list of all available plugins, check out -# -# https://docs.fastlane.tools/plugins/available-plugins -# - -# Uncomment the line if you want fastlane to automatically update itself -# update_fastlane - -default_platform(:android) - -platform :android do - desc "Deploy to internal test application" - - lane :internal do - version_code = getVersionCode(getVersionInfo()) - version_name = getVersionName(getVersionInfo()) - flutter_build(version_name,version_code) - - upload_to_play_store( - track: 'internal', - version_code: version_code, - aab: '../build/app/outputs/bundle/release/app-release.aab', - ) - end - - lane :alpha do - version_code = getVersionCode(getVersionInfo()) - version_name = getVersionName(getVersionInfo()) - flutter_build(version_name,version_code) - - upload_to_play_store( - track: 'alpha', - version_code: version_code, - aab: '../build/app/outputs/bundle/release/app-release.aab', - ) - end - - - lane :beta do - version_code = getVersionCode(getVersionInfo()) - version_name = getVersionName(getVersionInfo()) - flutter_build(version_name,version_code) - - upload_to_play_store( - track: 'beta', - version_code: version_code, - aab: '../build/app/outputs/bundle/release/app-release.aab', - ) - end - - lane :production do - version_code = getVersionCode(getVersionInfo()) - version_name = getVersionName(getVersionInfo()) - flutter_build(version_name,version_code) - - upload_to_play_store( - track: 'production', - version_code: version_code, - aab: '../build/app/outputs/bundle/release/app-release.aab', - ) - end - - - def getVersionInfo() - version_info = flutter_version( - pubspec_location: '../pubspec.yaml' - ) - end - - def getVersionCode(version_info) - return version_info['version_code'] - end - - def getVersionName(version_info) - return version_info['version_name'] - end - - def flutter_build(versionName,number) - - Dir.chdir '../../' do - sh('flutter', 'packages', 'get') - sh('flutter', 'clean') - sh( - "flutter build appbundle --build-name=#{ - versionName - } --build-number=#{number.to_s}" - ) - end - end - -end \ No newline at end of file diff --git a/android/fastlane/README.md b/android/fastlane/README.md deleted file mode 100644 index 07c150f47..000000000 --- a/android/fastlane/README.md +++ /dev/null @@ -1,56 +0,0 @@ -fastlane documentation ----- - -# Installation - -Make sure you have the latest version of the Xcode command line tools installed: - -```sh -xcode-select --install -``` - -For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane) - -# Available Actions - -## Android - -### android internal - -```sh -[bundle exec] fastlane android internal -``` - -Deploy to internal test application - -### android alpha - -```sh -[bundle exec] fastlane android alpha -``` - - - -### android beta - -```sh -[bundle exec] fastlane android beta -``` - - - -### android production - -```sh -[bundle exec] fastlane android production -``` - - - ----- - -This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run. - -More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools). - -The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools). diff --git a/android/fastlane/en-US/changelogs/264.txt b/android/fastlane/en-US/changelogs/264.txt deleted file mode 100644 index c47b76215..000000000 --- a/android/fastlane/en-US/changelogs/264.txt +++ /dev/null @@ -1,40 +0,0 @@ -Archethic Wallet - Version Bug Bounty 1.0.4 Build 264 - -🚀 Features -- Fungible Token’s management -- Add max number of confirmations -- Password - add random password generation -- Import wallet - use autocompletion -- Password - add status bars showing whether password is strong, medium, easy -- Add flat theme -- Price Chart - Add 1h + all chart  -- Add a possibility to copy balances - -đŸ‘·â€â™‚ïž Enhancements -- UI Updates -- Move message when users have not UCO enough -- Don’t display the @ before the contact’s name in the tx list -- Don't start transaction inputs scan with desktop platforms -- Very small amount of trx not displayed correctly -- upgrade Android version SDK 33 -- Removal of the package-based architecture -- Flat theme - improve UI -- Manage token symbol in receive notification -- Show/Hide amounts - improve option -- When users click on an account in account list screen, go back to the main screen -- Token creation screen : Inform the user of the maximum length of a symbol -- Remove parenthesis in balance widgets for the main information - -🐛 Bugfixes -- After import keychain with many accounts, if user would to change account in accountList screen, an error appears -- Token creation doesn't work with service name contained special characters -- Send screens : Symbol currency is not correct -- Chart - wrong chart appears on main screen -- Send a token to yourself. Error message display "UCO" instead of symbol -- Lock Screen - do not reset the number of attempts if users back -- Sometimes OTP with yubikey doesn't work -- Manage balanceFee API errors -- Price chart - Fix the UI issue with the low value of the ordinate -- Block the wallet creation process when keychain is not create in the blockchain -- Samsung Smart Suggestion crash when entering text to send UCOs -- Wallet not responding when sending UCO to address starting with [1-9] \ No newline at end of file diff --git a/android/fastlane/en-US/changelogs/282.txt b/android/fastlane/en-US/changelogs/282.txt deleted file mode 100644 index ee9b71f05..000000000 --- a/android/fastlane/en-US/changelogs/282.txt +++ /dev/null @@ -1,5 +0,0 @@ -Archethic Wallet - Version Bug Bounty 1.0.7 Build 282 - -Enhancements -- Improve messages to inform users when a transaction is sent -- Technical migration (Flutter 3.3) \ No newline at end of file diff --git a/android/fastlane/en-US/changelogs/287.txt b/android/fastlane/en-US/changelogs/287.txt deleted file mode 100644 index 766f49766..000000000 --- a/android/fastlane/en-US/changelogs/287.txt +++ /dev/null @@ -1,5 +0,0 @@ -Archethic Wallet - Version Bug Bounty 1.0.8 Build 287 - -Enhancements -- Prevent memory leaks -- Add new flat themes \ No newline at end of file diff --git a/android/fastlane/en-US/changelogs/311.txt b/android/fastlane/en-US/changelogs/311.txt deleted file mode 100644 index f23dab4f8..000000000 --- a/android/fastlane/en-US/changelogs/311.txt +++ /dev/null @@ -1,7 +0,0 @@ -Archethic Wallet - Version 1.1.2 Build 311 - -New Feature -- NFT Creation and transfer - -Enhancements -- Improve performance diff --git a/android/fastlane/en-US/full_description.txt b/android/fastlane/en-US/full_description.txt deleted file mode 100644 index fa35d3205..000000000 --- a/android/fastlane/en-US/full_description.txt +++ /dev/null @@ -1,3 +0,0 @@ -The app is mainly a non-custodial cryptocurrency hot wallet that enables you to safely manage assets on Layer 1 Archethic blockchain. -This wallet includes the features of send and receive UCO token, fungible tokens and NFTs instantly to and from anyone. -No signup or KYC needed, users just control their services and access keychain, protected by different secure access methods like PIN Code, Password, YubiKey like devices and Biometrics. \ No newline at end of file diff --git a/android/fastlane/en-US/images/icon.png b/android/fastlane/en-US/images/icon.png deleted file mode 100644 index 3240f55ee..000000000 Binary files a/android/fastlane/en-US/images/icon.png and /dev/null differ diff --git a/android/fastlane/en-US/images/phoneScreenshots/1.png b/android/fastlane/en-US/images/phoneScreenshots/1.png deleted file mode 100644 index 42b3b8bff..000000000 Binary files a/android/fastlane/en-US/images/phoneScreenshots/1.png and /dev/null differ diff --git a/android/fastlane/en-US/images/phoneScreenshots/2.png b/android/fastlane/en-US/images/phoneScreenshots/2.png deleted file mode 100644 index 8062d3e54..000000000 Binary files a/android/fastlane/en-US/images/phoneScreenshots/2.png and /dev/null differ diff --git a/android/fastlane/en-US/images/phoneScreenshots/3.png b/android/fastlane/en-US/images/phoneScreenshots/3.png deleted file mode 100644 index b6b4fd852..000000000 Binary files a/android/fastlane/en-US/images/phoneScreenshots/3.png and /dev/null differ diff --git a/android/fastlane/en-US/short_description.txt b/android/fastlane/en-US/short_description.txt deleted file mode 100644 index 8db5456bd..000000000 --- a/android/fastlane/en-US/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Send, Receive and Exchange UCO, Tokens and NFT \ No newline at end of file diff --git a/android/fastlane/en-US/title.txt b/android/fastlane/en-US/title.txt deleted file mode 100644 index 9c39d759b..000000000 --- a/android/fastlane/en-US/title.txt +++ /dev/null @@ -1 +0,0 @@ -Archethic Wallet \ No newline at end of file diff --git a/android/fastlane/report.xml b/android/fastlane/report.xml deleted file mode 100644 index 14fc94bee..000000000 --- a/android/fastlane/report.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/assets/images/airdrop.png b/assets/images/airdrop.png index 1abfc33ed..7178e0847 100644 Binary files a/assets/images/airdrop.png and b/assets/images/airdrop.png differ diff --git a/fastlane/Fastfile b/fastlane/Fastfile new file mode 100755 index 000000000..a56720fad --- /dev/null +++ b/fastlane/Fastfile @@ -0,0 +1,122 @@ +# This file contains the fastlane.tools configuration +# You can find the documentation at https://docs.fastlane.tools +# +# For a list of all available actions, check out +# +# https://docs.fastlane.tools/actions +# +# For a list of all available plugins, check out +# +# https://docs.fastlane.tools/plugins/available-plugins +# + +# Uncomment the line if you want fastlane to automatically update itself +# update_fastlane + +platform :ios do + desc "Publish to iOS App Store" + lane :release do + version_code = getVersionCode(getVersionInfo()) + version_name = getVersionName(getVersionInfo()) + flutter_build(version_name,version_code, "ios") + build_app(workspace: "ios/Runner.xcworkspace", scheme: "Runner") + upload_to_app_store + end + + desc "Publish to iOS TestFlight" + lane :beta do + version_code = getVersionCode(getVersionInfo()) + version_name = getVersionName(getVersionInfo()) + flutter_build(version_name,version_code, "ios") + build_app(workspace: "ios/Runner.xcworkspace", scheme: "Runner") + upload_to_testflight + end +end + +platform :mac do + desc "Publish to MacOS App Store" + lane :release do + version_code = getVersionCode(getVersionInfo()) + version_name = getVersionName(getVersionInfo()) + flutter_build(version_name,version_code, "macos") + build_app(workspace: "macos/Runner.xcworkspace", scheme: "Runner", + export_options: { + provisioningProfiles: { + "tech.archethic.wallet" => "Archethic Wallet Mac", + } + }, + installer_cert_name: "3rd Party Mac Developer Installer: Archethic Technologies SARL (2QGGN9QQKR)") + upload_to_app_store + end + + desc "Publish to MacOS TestFlight" + lane :beta do + version_code = getVersionCode(getVersionInfo()) + version_name = getVersionName(getVersionInfo()) + flutter_build(version_name,version_code, "macos") + build_app(workspace: "macos/Runner.xcworkspace", scheme: "Runner", + export_options: { + provisioningProfiles: { + "tech.archethic.wallet" => "Archethic Wallet Mac", + } + }, + installer_cert_name: "3rd Party Mac Developer Installer: Archethic Technologies SARL (2QGGN9QQKR)") + upload_to_testflight + end +end + +platform :android do + desc "Publish to GooglePlay" + lane :release do + version_code = getVersionCode(getVersionInfo()) + version_name = getVersionName(getVersionInfo()) + flutter_build(version_name,version_code, "appbundle") + + upload_to_play_store( + track: 'production', + version_code: version_code, + aab: 'build/app/outputs/bundle/release/app-release.aab', + ) + end + + desc "Publish to GooglePlay Beta" + lane :beta do + desc "Deploy to beta canal" + version_code = getVersionCode(getVersionInfo()) + version_name = getVersionName(getVersionInfo()) + flutter_build(version_name,version_code, "appbundle") + + upload_to_play_store( + track: 'beta', + version_code: version_code, + aab: 'build/app/outputs/bundle/release/app-release.aab', + ) + end +end + +def getVersionInfo() + version_info = flutter_version( + pubspec_location: 'pubspec.yaml' + ) +end + +def getVersionCode(version_info) + return version_info['version_code'] +end + +def getVersionName(version_info) + return version_info['version_name'] +end + + +def flutter_build(versionName,number,format) + Dir.chdir '.' do + sh('flutter', 'packages', 'get') + sh('flutter', 'clean') + sh( + "flutter build #{format} --obfuscate --split-debug-info=build/sym/#{format} --build-name=#{ + versionName + } --build-number=#{number.to_s} --dart-define=AIRDROP_SECRET=\"#{ENV['airdropSecret']}\"" + ) + end +end diff --git a/android/fastlane/Gemfile b/fastlane/Gemfile similarity index 100% rename from android/fastlane/Gemfile rename to fastlane/Gemfile diff --git a/android/fastlane/Pluginfile b/fastlane/Pluginfile similarity index 100% rename from android/fastlane/Pluginfile rename to fastlane/Pluginfile diff --git a/macos/fastlane/README.md b/fastlane/README.md similarity index 62% rename from macos/fastlane/README.md rename to fastlane/README.md index c993d7773..5f73e4925 100644 --- a/macos/fastlane/README.md +++ b/fastlane/README.md @@ -13,6 +13,27 @@ For _fastlane_ installation instructions, see [Installing _fastlane_](https://do # Available Actions +## iOS + +### ios release + +```sh +[bundle exec] fastlane ios release +``` + +Publish to iOS App Store + +### ios beta + +```sh +[bundle exec] fastlane ios beta +``` + +Publish to iOS TestFlight + +---- + + ## Mac ### mac release @@ -21,7 +42,7 @@ For _fastlane_ installation instructions, see [Installing _fastlane_](https://do [bundle exec] fastlane mac release ``` -Push a new release build to the App Store +Publish to MacOS App Store ### mac beta @@ -29,7 +50,28 @@ Push a new release build to the App Store [bundle exec] fastlane mac beta ``` -Push a new release build to TestFlight +Publish to MacOS TestFlight + +---- + + +## Android + +### android release + +```sh +[bundle exec] fastlane android release +``` + +Publish to GooglePlay + +### android beta + +```sh +[bundle exec] fastlane android beta +``` + +Publish to GooglePlay Beta ---- diff --git a/fastlane/metadata/android/Fastfile b/fastlane/metadata/android/Fastfile deleted file mode 100755 index aa2336036..000000000 --- a/fastlane/metadata/android/Fastfile +++ /dev/null @@ -1,98 +0,0 @@ -# This file contains the fastlane.tools configuration -# You can find the documentation at https://docs.fastlane.tools -# -# For a list of all available actions, check out -# -# https://docs.fastlane.tools/actions -# -# For a list of all available plugins, check out -# -# https://docs.fastlane.tools/plugins/available-plugins -# - -# Uncomment the line if you want fastlane to automatically update itself -# update_fastlane - -default_platform(:android) - -platform :android do - desc "Deploy to internal test application" - - lane :internal do - version_code = getVersionCode(getVersionInfo()) - version_name = getVersionName(getVersionInfo()) - flutter_build(version_name,version_code) - - upload_to_play_store( - track: 'internal', - version_code: version_code, - aab: '../build/app/outputs/bundle/release/app-release.aab', - ) - end - - lane :alpha do - version_code = getVersionCode(getVersionInfo()) - version_name = getVersionName(getVersionInfo()) - flutter_build(version_name,version_code) - - upload_to_play_store( - track: 'alpha', - version_code: version_code, - aab: '../build/app/outputs/bundle/release/app-release.aab', - ) - end - - - lane :beta do - version_code = getVersionCode(getVersionInfo()) - version_name = getVersionName(getVersionInfo()) - flutter_build(version_name,version_code) - - upload_to_play_store( - track: 'beta', - version_code: version_code, - aab: '../build/app/outputs/bundle/release/app-release.aab', - ) - end - - lane :production do - version_code = getVersionCode(getVersionInfo()) - version_name = getVersionName(getVersionInfo()) - flutter_build(version_name,version_code) - - upload_to_play_store( - track: 'production', - version_code: version_code, - aab: '../build/app/outputs/bundle/release/app-release.aab', - ) - end - - - def getVersionInfo() - version_info = flutter_version( - pubspec_location: '../pubspec.yaml' - ) - end - - def getVersionCode(version_info) - return version_info['version_code'] - end - - def getVersionName(version_info) - return version_info['version_name'] - end - - def flutter_build(versionName,number) - - Dir.chdir '../../' do - sh('flutter', 'packages', 'get') - sh('flutter', 'clean') - sh( - "flutter build appbundle --build-name=#{ - versionName - } --build-number=#{number.to_s}" - ) - end - end - -end \ No newline at end of file diff --git a/fastlane/report.xml b/fastlane/report.xml new file mode 100644 index 000000000..666f70af1 --- /dev/null +++ b/fastlane/report.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/fastlane/Deliverfile b/ios/fastlane/Deliverfile deleted file mode 100644 index 74739f740..000000000 --- a/ios/fastlane/Deliverfile +++ /dev/null @@ -1,3 +0,0 @@ -# The Deliverfile allows you to store various App Store Connect metadata -# For more information, check out the docs -# https://docs.fastlane.tools/actions/deliver/ diff --git a/ios/fastlane/Fastfile b/ios/fastlane/Fastfile deleted file mode 100644 index 285869132..000000000 --- a/ios/fastlane/Fastfile +++ /dev/null @@ -1,30 +0,0 @@ -# This file contains the fastlane.tools configuration -# You can find the documentation at https://docs.fastlane.tools -# -# For a list of all available actions, check out -# -# https://docs.fastlane.tools/actions -# -# For a list of all available plugins, check out -# -# https://docs.fastlane.tools/plugins/available-plugins -# - -# Uncomment the line if you want fastlane to automatically update itself -# update_fastlane - -default_platform(:ios) - -platform :ios do - desc "Push a new release build to the App Store" - lane :release do - build_app(workspace: "Runner.xcworkspace", scheme: "Runner") - upload_to_app_store - end - - desc "Push a new release build to TestFlight" - lane :beta do - build_app(workspace: "Runner.xcworkspace", scheme: "Runner") - upload_to_testflight - end -end diff --git a/ios/fastlane/Gemfile b/ios/fastlane/Gemfile deleted file mode 100644 index 7a118b49b..000000000 --- a/ios/fastlane/Gemfile +++ /dev/null @@ -1,3 +0,0 @@ -source "https://rubygems.org" - -gem "fastlane" diff --git a/ios/fastlane/README.md b/ios/fastlane/README.md deleted file mode 100644 index 7edaa5e2e..000000000 --- a/ios/fastlane/README.md +++ /dev/null @@ -1,40 +0,0 @@ -fastlane documentation ----- - -# Installation - -Make sure you have the latest version of the Xcode command line tools installed: - -```sh -xcode-select --install -``` - -For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane) - -# Available Actions - -## iOS - -### ios release - -```sh -[bundle exec] fastlane ios release -``` - -Push a new release build to the App Store - -### ios beta - -```sh -[bundle exec] fastlane ios beta -``` - -Push a new release build to TestFlight - ----- - -This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run. - -More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools). - -The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools). diff --git a/ios/fastlane/report.xml b/ios/fastlane/report.xml deleted file mode 100755 index 0b55093b6..000000000 --- a/ios/fastlane/report.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ios/fastlane/screenshots/README.txt b/ios/fastlane/screenshots/README.txt deleted file mode 100644 index 8b015ec56..000000000 --- a/ios/fastlane/screenshots/README.txt +++ /dev/null @@ -1,7 +0,0 @@ -Put all screenshots you want to use inside the folder of its language (e.g. en-US). -The device type will automatically be recognized using the image resolution. Apple TV screenshots -should be stored in a subdirectory named appleTV with language folders inside of it. iMessage -screenshots, like Apple TV screenshots, should also be stored in a subdirectory named iMessage -with language folders inside of it. - -The screenshots can be named whatever you want, but keep in mind they are sorted alphabetically. diff --git a/lib/application/airdrop/provider.dart b/lib/application/airdrop/provider.dart new file mode 100644 index 000000000..9a4ef2bf1 --- /dev/null +++ b/lib/application/airdrop/provider.dart @@ -0,0 +1,184 @@ +import 'package:aewallet/application/account/providers.dart'; +import 'package:aewallet/application/device_info.dart'; +import 'package:aewallet/application/settings/settings.dart'; +import 'package:aewallet/domain/models/core/failures.dart'; +import 'package:aewallet/domain/repositories/airdrop.dart'; +import 'package:aewallet/infrastructure/repositories/airdrop.dart'; +import 'package:aewallet/model/available_networks.dart'; +import 'package:aewallet/util/date_util.dart'; +import 'package:aewallet/util/functional_utils.dart'; +import 'package:aewallet/util/screen_util.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'provider.g.dart'; + +@Riverpod(keepAlive: true) +AirDropRepositoryInterface _airDropRepository(Ref ref) { + return AirDropRepository(); +} + +@Riverpod(keepAlive: true) +Future _isDeviceCompatible(Ref ref) async { + if (ScreenUtil.isDesktopMode()) return false; + if (ref.read(SettingsProviders.settings).network.network != + AvailableNetworks.archethicMainNet) return false; + + return true; +} + +@Riverpod(keepAlive: true) +Future _isAirdropEnabled(Ref ref) async { + /// If the airdrop API replyes as expected, it means airdrop is enabled. + final repository = ref.watch(_airDropRepositoryProvider); + + final installationId = await ref.watch( + DeviceInfoProviders.installationId.future, + ); + + final airDropChallenge = await repository.requestChallenge( + deviceId: installationId, + ); + + return airDropChallenge.map( + success: (_) => true, + failure: (failure) => failure.maybeMap( + quotaExceeded: (_) async { + ref.read(AirDropProviders.airdropCooldown.notifier).startCooldown(); + return true; + }, + orElse: () { + // retry request later + Future.delayed( + const Duration(minutes: 1), + () => ref.invalidate(_isAirdropEnabledProvider), + ); + + return false; + }, + ), + ); +} + +class _AirDropCooldownNotifier extends AsyncNotifier { + @override + FutureOr build() async { + final repository = ref.watch(_airDropRepositoryProvider); + final lastAirDropDate = await repository.getLastAirdropDate(); + + if (lastAirDropDate == null) return Duration.zero; + + final cooldownEndDate = + lastAirDropDate.toUtc().add(const Duration(hours: 24)).startOfDay; + final cooldownRemainingTime = + cooldownEndDate.difference(DateTime.now().toUtc()).max(Duration.zero); + + Future.delayed( + const Duration(minutes: 1), + () { + ref.invalidate(AirDropProviders.airdropCooldown); + }, + ); + return cooldownRemainingTime; + } + + Future startCooldown() async { + if (state.isLoading || state.valueOrNull != Duration.zero) return; + + final repository = ref.watch(_airDropRepositoryProvider); + await repository.setLastAirdropDate(); + + ref.invalidate(AirDropProviders.airdropCooldown); + } +} + +class _AirDropRequestNotifier extends AsyncNotifier { + @override + FutureOr build() => const AsyncValue.data(null); + + Future requestAirDrop() async { + state = const AsyncValue.loading(); + state = await AsyncValue.guard(() async { + final repository = ref.read(_airDropRepositoryProvider); + + final installationId = await ref.read( + DeviceInfoProviders.installationId.future, + ); + + final selectedAccount = await ref.read( + AccountProviders.selectedAccount.future, + ); + + final accountLastAddress = selectedAccount?.lastAddress; + + if (accountLastAddress == null) { + throw const Failure.invalidValue(); + } + + final airDropChallenge = (await repository.requestChallenge( + deviceId: installationId, + )) + .map( + success: id, + failure: (failure) => failure.maybeMap( + quotaExceeded: (quotaExceeded) { + ref.read(AirDropProviders.airdropCooldown.notifier).startCooldown(); + throw failure; + }, + orElse: () { + throw failure; + }, + ), + ); + + final result = (await repository.requestAirDrop( + challenge: airDropChallenge, + deviceId: installationId, + keychainAddress: accountLastAddress, + )) + .map( + success: id, + failure: (failure) => failure.maybeMap( + insufficientFunds: (insufficientFunds) { + throw failure; + }, + quotaExceeded: (quotaExceeded) { + ref.read(AirDropProviders.airdropCooldown.notifier).startCooldown(); + throw failure; + }, + orElse: () { + throw failure; + }, + ), + ); + + ref + ..read(AirDropProviders.airdropCooldown.notifier).startCooldown() + ..invalidate(_isAirdropEnabledProvider) + ..read(AccountProviders.selectedAccount.notifier) + .refreshRecentTransactions(); + + return result; + }); + } +} + +abstract class AirDropProviders { + /// Is the device compatible with Faucet ? + static final isDeviceCompatible = _isDeviceCompatibleProvider; + + /// Is the Faucet active ? + static final isFaucetEnabled = _isAirdropEnabledProvider; + + /// Notifier that requests Faucet claim + static final airDropRequest = + AsyncNotifierProvider<_AirDropRequestNotifier, void>( + _AirDropRequestNotifier.new, + ); + + /// Faucet claim cooldown counter + static final airdropCooldown = + AsyncNotifierProvider<_AirDropCooldownNotifier, Duration>( + _AirDropCooldownNotifier.new, + ); +} diff --git a/lib/application/airdrop/provider.g.dart b/lib/application/airdrop/provider.g.dart new file mode 100644 index 000000000..5e0083848 --- /dev/null +++ b/lib/application/airdrop/provider.g.dart @@ -0,0 +1,65 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// ignore_for_file: avoid_private_typedef_functions, non_constant_identifier_names, subtype_of_sealed_class, invalid_use_of_internal_member, unused_element, constant_identifier_names, unnecessary_raw_strings, library_private_types_in_public_api + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +String $_airDropRepositoryHash() => r'f7c97ae3c8b1870ea062645b2d38e9ea3e643d3d'; + +/// See also [_airDropRepository]. +final _airDropRepositoryProvider = Provider( + _airDropRepository, + name: r'_airDropRepositoryProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : $_airDropRepositoryHash, +); +typedef _AirDropRepositoryRef = ProviderRef; +String $_isDeviceCompatibleHash() => + r'c3082af5c03ede8006c9dd49c498337f7d1697ef'; + +/// See also [_isDeviceCompatible]. +final _isDeviceCompatibleProvider = FutureProvider( + _isDeviceCompatible, + name: r'_isDeviceCompatibleProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : $_isDeviceCompatibleHash, +); +typedef _IsDeviceCompatibleRef = FutureProviderRef; +String $_isAirdropEnabledHash() => r'59eebd0a852ab5e5ba6bbd441692ffd0659c0bd9'; + +/// See also [_isAirdropEnabled]. +final _isAirdropEnabledProvider = FutureProvider( + _isAirdropEnabled, + name: r'_isAirdropEnabledProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : $_isAirdropEnabledHash, +); +typedef _IsAirdropEnabledRef = FutureProviderRef; diff --git a/lib/application/device_info.dart b/lib/application/device_info.dart new file mode 100644 index 000000000..928bd536e --- /dev/null +++ b/lib/application/device_info.dart @@ -0,0 +1,22 @@ +import 'package:aewallet/domain/repositories/device_info.dart'; +import 'package:aewallet/infrastructure/repositories/device_info.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'device_info.g.dart'; + +@Riverpod(keepAlive: true) +DeviceInfoRepositoryInterface _deviceInfoRepository(Ref ref) { + return DeviceInfoRepository(); +} + +@Riverpod(keepAlive: true) +Future _installationId(Ref ref) async { + final repository = ref.watch(_deviceInfoRepositoryProvider); + + return repository.getInstallationId(); +} + +abstract class DeviceInfoProviders { + static final installationId = _installationIdProvider; +} diff --git a/lib/application/device_info.g.dart b/lib/application/device_info.g.dart new file mode 100644 index 000000000..e22807e64 --- /dev/null +++ b/lib/application/device_info.g.dart @@ -0,0 +1,54 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'device_info.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// ignore_for_file: avoid_private_typedef_functions, non_constant_identifier_names, subtype_of_sealed_class, invalid_use_of_internal_member, unused_element, constant_identifier_names, unnecessary_raw_strings, library_private_types_in_public_api + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +String $_deviceInfoRepositoryHash() => + r'c3fd974e71cc6de779146c10d9813d82c620e85d'; + +/// See also [_deviceInfoRepository]. +final _deviceInfoRepositoryProvider = Provider( + _deviceInfoRepository, + name: r'_deviceInfoRepositoryProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : $_deviceInfoRepositoryHash, +); +typedef _DeviceInfoRepositoryRef = ProviderRef; +String $_installationIdHash() => r'3731950a3232f2db42099228140ebc5fa99b22a0'; + +/// See also [_installationId]. +final _installationIdProvider = FutureProvider( + _installationId, + name: r'_installationIdProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : $_installationIdHash, +); +typedef _InstallationIdRef = FutureProviderRef; diff --git a/lib/application/nft_category.g.dart b/lib/application/nft_category.g.dart deleted file mode 100644 index f2ec54830..000000000 --- a/lib/application/nft_category.g.dart +++ /dev/null @@ -1,509 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'nft_category.dart'; - -// ************************************************************************** -// RiverpodGenerator -// ************************************************************************** - -// ignore_for_file: avoid_private_typedef_functions, non_constant_identifier_names, subtype_of_sealed_class, invalid_use_of_internal_member, unused_element, constant_identifier_names, unnecessary_raw_strings, library_private_types_in_public_api - -/// Copied from Dart SDK -class _SystemHash { - _SystemHash._(); - - static int combine(int hash, int value) { - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + value); - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); - return hash ^ (hash >> 6); - } - - static int finish(int hash) { - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); - // ignore: parameter_assignments - hash = hash ^ (hash >> 11); - return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); - } -} - -String $_nftCategoryRepositoryHash() => - r'a38f8405031299f092dbef55597261545db4103c'; - -/// See also [_nftCategoryRepository]. -final _nftCategoryRepositoryProvider = - AutoDisposeProvider( - _nftCategoryRepository, - name: r'_nftCategoryRepositoryProvider', - debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') - ? null - : $_nftCategoryRepositoryHash, -); -typedef _NftCategoryRepositoryRef - = AutoDisposeProviderRef; -String $_selectedAccountNftCategoriesHash() => - r'14c939211bff7bc3c39aedd5aa134a7cac928c4f'; - -/// See also [_selectedAccountNftCategories]. -class _SelectedAccountNftCategoriesProvider - extends AutoDisposeFutureProvider> { - _SelectedAccountNftCategoriesProvider({ - required this.context, - }) : super( - (ref) => _selectedAccountNftCategories( - ref, - context: context, - ), - from: _selectedAccountNftCategoriesProvider, - name: r'_selectedAccountNftCategoriesProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : $_selectedAccountNftCategoriesHash, - ); - - final BuildContext context; - - @override - bool operator ==(Object other) { - return other is _SelectedAccountNftCategoriesProvider && - other.context == context; - } - - @override - int get hashCode { - var hash = _SystemHash.combine(0, runtimeType.hashCode); - hash = _SystemHash.combine(hash, context.hashCode); - - return _SystemHash.finish(hash); - } -} - -typedef _SelectedAccountNftCategoriesRef - = AutoDisposeFutureProviderRef>; - -/// See also [_selectedAccountNftCategories]. -final _selectedAccountNftCategoriesProvider = - _SelectedAccountNftCategoriesFamily(); - -class _SelectedAccountNftCategoriesFamily - extends Family>> { - _SelectedAccountNftCategoriesFamily(); - - _SelectedAccountNftCategoriesProvider call({ - required BuildContext context, - }) { - return _SelectedAccountNftCategoriesProvider( - context: context, - ); - } - - @override - AutoDisposeFutureProvider> getProviderOverride( - covariant _SelectedAccountNftCategoriesProvider provider, - ) { - return call( - context: provider.context, - ); - } - - @override - List? get allTransitiveDependencies => null; - - @override - List? get dependencies => null; - - @override - String? get name => r'_selectedAccountNftCategoriesProvider'; -} - -String $_fetchNftCategoryHash() => r'cd908e74d75cc876b704d525b019fcee83b43504'; - -/// See also [_fetchNftCategory]. -class _FetchNftCategoryProvider extends AutoDisposeProvider> { - _FetchNftCategoryProvider({ - required this.context, - required this.account, - }) : super( - (ref) => _fetchNftCategory( - ref, - context: context, - account: account, - ), - from: _fetchNftCategoryProvider, - name: r'_fetchNftCategoryProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : $_fetchNftCategoryHash, - ); - - final BuildContext context; - final Account account; - - @override - bool operator ==(Object other) { - return other is _FetchNftCategoryProvider && - other.context == context && - other.account == account; - } - - @override - int get hashCode { - var hash = _SystemHash.combine(0, runtimeType.hashCode); - hash = _SystemHash.combine(hash, context.hashCode); - hash = _SystemHash.combine(hash, account.hashCode); - - return _SystemHash.finish(hash); - } -} - -typedef _FetchNftCategoryRef = AutoDisposeProviderRef>; - -/// See also [_fetchNftCategory]. -final _fetchNftCategoryProvider = _FetchNftCategoryFamily(); - -class _FetchNftCategoryFamily extends Family> { - _FetchNftCategoryFamily(); - - _FetchNftCategoryProvider call({ - required BuildContext context, - required Account account, - }) { - return _FetchNftCategoryProvider( - context: context, - account: account, - ); - } - - @override - AutoDisposeProvider> getProviderOverride( - covariant _FetchNftCategoryProvider provider, - ) { - return call( - context: provider.context, - account: provider.account, - ); - } - - @override - List? get allTransitiveDependencies => null; - - @override - List? get dependencies => null; - - @override - String? get name => r'_fetchNftCategoryProvider'; -} - -String $_getNbNFTInCategoryHash() => - r'6015ccc4a96e15a7d434528af26f7111386d7d15'; - -/// See also [_getNbNFTInCategory]. -class _GetNbNFTInCategoryProvider extends AutoDisposeProvider { - _GetNbNFTInCategoryProvider({ - required this.account, - required this.categoryNftIndex, - }) : super( - (ref) => _getNbNFTInCategory( - ref, - account: account, - categoryNftIndex: categoryNftIndex, - ), - from: _getNbNFTInCategoryProvider, - name: r'_getNbNFTInCategoryProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : $_getNbNFTInCategoryHash, - ); - - final Account account; - final int categoryNftIndex; - - @override - bool operator ==(Object other) { - return other is _GetNbNFTInCategoryProvider && - other.account == account && - other.categoryNftIndex == categoryNftIndex; - } - - @override - int get hashCode { - var hash = _SystemHash.combine(0, runtimeType.hashCode); - hash = _SystemHash.combine(hash, account.hashCode); - hash = _SystemHash.combine(hash, categoryNftIndex.hashCode); - - return _SystemHash.finish(hash); - } -} - -typedef _GetNbNFTInCategoryRef = AutoDisposeProviderRef; - -/// See also [_getNbNFTInCategory]. -final _getNbNFTInCategoryProvider = _GetNbNFTInCategoryFamily(); - -class _GetNbNFTInCategoryFamily extends Family { - _GetNbNFTInCategoryFamily(); - - _GetNbNFTInCategoryProvider call({ - required Account account, - required int categoryNftIndex, - }) { - return _GetNbNFTInCategoryProvider( - account: account, - categoryNftIndex: categoryNftIndex, - ); - } - - @override - AutoDisposeProvider getProviderOverride( - covariant _GetNbNFTInCategoryProvider provider, - ) { - return call( - account: provider.account, - categoryNftIndex: provider.categoryNftIndex, - ); - } - - @override - List? get allTransitiveDependencies => null; - - @override - List? get dependencies => null; - - @override - String? get name => r'_getNbNFTInCategoryProvider'; -} - -String $_getListByDefaultHash() => r'8538d843366645b4a7c60a792cc8bf4f5af89e64'; - -/// See also [_getListByDefault]. -class _GetListByDefaultProvider extends AutoDisposeProvider> { - _GetListByDefaultProvider({ - required this.context, - }) : super( - (ref) => _getListByDefault( - ref, - context: context, - ), - from: _getListByDefaultProvider, - name: r'_getListByDefaultProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : $_getListByDefaultHash, - ); - - final BuildContext context; - - @override - bool operator ==(Object other) { - return other is _GetListByDefaultProvider && other.context == context; - } - - @override - int get hashCode { - var hash = _SystemHash.combine(0, runtimeType.hashCode); - hash = _SystemHash.combine(hash, context.hashCode); - - return _SystemHash.finish(hash); - } -} - -typedef _GetListByDefaultRef = AutoDisposeProviderRef>; - -/// See also [_getListByDefault]. -final _getListByDefaultProvider = _GetListByDefaultFamily(); - -class _GetListByDefaultFamily extends Family> { - _GetListByDefaultFamily(); - - _GetListByDefaultProvider call({ - required BuildContext context, - }) { - return _GetListByDefaultProvider( - context: context, - ); - } - - @override - AutoDisposeProvider> getProviderOverride( - covariant _GetListByDefaultProvider provider, - ) { - return call( - context: provider.context, - ); - } - - @override - List? get allTransitiveDependencies => null; - - @override - List? get dependencies => null; - - @override - String? get name => r'_getListByDefaultProvider'; -} - -String $_updateNftCategoryListHash() => - r'c3d71c277aee70da60524f024c6de9d48a60288c'; - -/// See also [_updateNftCategoryList]. -class _UpdateNftCategoryListProvider extends AutoDisposeFutureProvider { - _UpdateNftCategoryListProvider({ - required this.nftCategoryListCustomized, - required this.account, - }) : super( - (ref) => _updateNftCategoryList( - ref, - nftCategoryListCustomized: nftCategoryListCustomized, - account: account, - ), - from: _updateNftCategoryListProvider, - name: r'_updateNftCategoryListProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : $_updateNftCategoryListHash, - ); - - final List nftCategoryListCustomized; - final Account account; - - @override - bool operator ==(Object other) { - return other is _UpdateNftCategoryListProvider && - other.nftCategoryListCustomized == nftCategoryListCustomized && - other.account == account; - } - - @override - int get hashCode { - var hash = _SystemHash.combine(0, runtimeType.hashCode); - hash = _SystemHash.combine(hash, nftCategoryListCustomized.hashCode); - hash = _SystemHash.combine(hash, account.hashCode); - - return _SystemHash.finish(hash); - } -} - -typedef _UpdateNftCategoryListRef = AutoDisposeFutureProviderRef; - -/// See also [_updateNftCategoryList]. -final _updateNftCategoryListProvider = _UpdateNftCategoryListFamily(); - -class _UpdateNftCategoryListFamily extends Family> { - _UpdateNftCategoryListFamily(); - - _UpdateNftCategoryListProvider call({ - required List nftCategoryListCustomized, - required Account account, - }) { - return _UpdateNftCategoryListProvider( - nftCategoryListCustomized: nftCategoryListCustomized, - account: account, - ); - } - - @override - AutoDisposeFutureProvider getProviderOverride( - covariant _UpdateNftCategoryListProvider provider, - ) { - return call( - nftCategoryListCustomized: provider.nftCategoryListCustomized, - account: provider.account, - ); - } - - @override - List? get allTransitiveDependencies => null; - - @override - List? get dependencies => null; - - @override - String? get name => r'_updateNftCategoryListProvider'; -} - -String $_getDescriptionHeaderHash() => - r'185dffe43711a9f602f89b67628173a12657f14a'; - -/// See also [_getDescriptionHeader]. -class _GetDescriptionHeaderProvider extends AutoDisposeProvider { - _GetDescriptionHeaderProvider({ - required this.context, - required this.id, - }) : super( - (ref) => _getDescriptionHeader( - ref, - context: context, - id: id, - ), - from: _getDescriptionHeaderProvider, - name: r'_getDescriptionHeaderProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : $_getDescriptionHeaderHash, - ); - - final BuildContext context; - final int id; - - @override - bool operator ==(Object other) { - return other is _GetDescriptionHeaderProvider && - other.context == context && - other.id == id; - } - - @override - int get hashCode { - var hash = _SystemHash.combine(0, runtimeType.hashCode); - hash = _SystemHash.combine(hash, context.hashCode); - hash = _SystemHash.combine(hash, id.hashCode); - - return _SystemHash.finish(hash); - } -} - -typedef _GetDescriptionHeaderRef = AutoDisposeProviderRef; - -/// See also [_getDescriptionHeader]. -final _getDescriptionHeaderProvider = _GetDescriptionHeaderFamily(); - -class _GetDescriptionHeaderFamily extends Family { - _GetDescriptionHeaderFamily(); - - _GetDescriptionHeaderProvider call({ - required BuildContext context, - required int id, - }) { - return _GetDescriptionHeaderProvider( - context: context, - id: id, - ); - } - - @override - AutoDisposeProvider getProviderOverride( - covariant _GetDescriptionHeaderProvider provider, - ) { - return call( - context: provider.context, - id: provider.id, - ); - } - - @override - List? get allTransitiveDependencies => null; - - @override - List? get dependencies => null; - - @override - String? get name => r'_getDescriptionHeaderProvider'; -} diff --git a/lib/application/wallet/wallet.dart b/lib/application/wallet/wallet.dart index 565d0158c..f86da7902 100644 --- a/lib/application/wallet/wallet.dart +++ b/lib/application/wallet/wallet.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:aewallet/application/airdrop/provider.dart'; import 'package:aewallet/application/authentication/authentication.dart'; import 'package:aewallet/application/contact.dart'; import 'package:aewallet/application/settings/settings.dart'; diff --git a/lib/application/wallet/wallet.g.dart b/lib/application/wallet/wallet.g.dart index 886d81545..e4bad0f79 100644 --- a/lib/application/wallet/wallet.g.dart +++ b/lib/application/wallet/wallet.g.dart @@ -29,7 +29,7 @@ class _SystemHash { } } -String $_SessionNotifierHash() => r'd96da25297378cb10323c884ced5437d0add64e3'; +String $_SessionNotifierHash() => r'bf30aade70d0ab61a35dd5a72be3f9c1e3110e6c'; /// See also [_SessionNotifier]. final _sessionNotifierProvider = NotifierProvider<_SessionNotifier, Session>( diff --git a/lib/domain/models/core/failures.dart b/lib/domain/models/core/failures.dart index 1a4709806..675fba5cc 100644 --- a/lib/domain/models/core/failures.dart +++ b/lib/domain/models/core/failures.dart @@ -8,6 +8,7 @@ class Failure with _$Failure implements Exception { const factory Failure.loggedOut() = _LoggedOut; const factory Failure.network() = _NetworkFailure; const factory Failure.quotaExceeded() = _QuotaExceededFailure; + const factory Failure.insufficientFunds() = _InsuffientFunds; const factory Failure.invalidValue() = _InvalidValue; const factory Failure.other({ Object? cause, diff --git a/lib/domain/models/core/failures.freezed.dart b/lib/domain/models/core/failures.freezed.dart index 4e42accdd..c40dfffbf 100644 --- a/lib/domain/models/core/failures.freezed.dart +++ b/lib/domain/models/core/failures.freezed.dart @@ -21,6 +21,7 @@ mixin _$Failure { required TResult Function() loggedOut, required TResult Function() network, required TResult Function() quotaExceeded, + required TResult Function() insufficientFunds, required TResult Function() invalidValue, required TResult Function(Object? cause, StackTrace? stack) other, }) => @@ -30,6 +31,7 @@ mixin _$Failure { TResult? Function()? loggedOut, TResult? Function()? network, TResult? Function()? quotaExceeded, + TResult? Function()? insufficientFunds, TResult? Function()? invalidValue, TResult? Function(Object? cause, StackTrace? stack)? other, }) => @@ -39,6 +41,7 @@ mixin _$Failure { TResult Function()? loggedOut, TResult Function()? network, TResult Function()? quotaExceeded, + TResult Function()? insufficientFunds, TResult Function()? invalidValue, TResult Function(Object? cause, StackTrace? stack)? other, required TResult orElse(), @@ -49,6 +52,7 @@ mixin _$Failure { required TResult Function(_LoggedOut value) loggedOut, required TResult Function(_NetworkFailure value) network, required TResult Function(_QuotaExceededFailure value) quotaExceeded, + required TResult Function(_InsuffientFunds value) insufficientFunds, required TResult Function(_InvalidValue value) invalidValue, required TResult Function(_OtherFailure value) other, }) => @@ -58,6 +62,7 @@ mixin _$Failure { TResult? Function(_LoggedOut value)? loggedOut, TResult? Function(_NetworkFailure value)? network, TResult? Function(_QuotaExceededFailure value)? quotaExceeded, + TResult? Function(_InsuffientFunds value)? insufficientFunds, TResult? Function(_InvalidValue value)? invalidValue, TResult? Function(_OtherFailure value)? other, }) => @@ -67,6 +72,7 @@ mixin _$Failure { TResult Function(_LoggedOut value)? loggedOut, TResult Function(_NetworkFailure value)? network, TResult Function(_QuotaExceededFailure value)? quotaExceeded, + TResult Function(_InsuffientFunds value)? insufficientFunds, TResult Function(_InvalidValue value)? invalidValue, TResult Function(_OtherFailure value)? other, required TResult orElse(), @@ -132,6 +138,7 @@ class _$_LoggedOut extends _LoggedOut { required TResult Function() loggedOut, required TResult Function() network, required TResult Function() quotaExceeded, + required TResult Function() insufficientFunds, required TResult Function() invalidValue, required TResult Function(Object? cause, StackTrace? stack) other, }) { @@ -144,6 +151,7 @@ class _$_LoggedOut extends _LoggedOut { TResult? Function()? loggedOut, TResult? Function()? network, TResult? Function()? quotaExceeded, + TResult? Function()? insufficientFunds, TResult? Function()? invalidValue, TResult? Function(Object? cause, StackTrace? stack)? other, }) { @@ -156,6 +164,7 @@ class _$_LoggedOut extends _LoggedOut { TResult Function()? loggedOut, TResult Function()? network, TResult Function()? quotaExceeded, + TResult Function()? insufficientFunds, TResult Function()? invalidValue, TResult Function(Object? cause, StackTrace? stack)? other, required TResult orElse(), @@ -172,6 +181,7 @@ class _$_LoggedOut extends _LoggedOut { required TResult Function(_LoggedOut value) loggedOut, required TResult Function(_NetworkFailure value) network, required TResult Function(_QuotaExceededFailure value) quotaExceeded, + required TResult Function(_InsuffientFunds value) insufficientFunds, required TResult Function(_InvalidValue value) invalidValue, required TResult Function(_OtherFailure value) other, }) { @@ -184,6 +194,7 @@ class _$_LoggedOut extends _LoggedOut { TResult? Function(_LoggedOut value)? loggedOut, TResult? Function(_NetworkFailure value)? network, TResult? Function(_QuotaExceededFailure value)? quotaExceeded, + TResult? Function(_InsuffientFunds value)? insufficientFunds, TResult? Function(_InvalidValue value)? invalidValue, TResult? Function(_OtherFailure value)? other, }) { @@ -196,6 +207,7 @@ class _$_LoggedOut extends _LoggedOut { TResult Function(_LoggedOut value)? loggedOut, TResult Function(_NetworkFailure value)? network, TResult Function(_QuotaExceededFailure value)? quotaExceeded, + TResult Function(_InsuffientFunds value)? insufficientFunds, TResult Function(_InvalidValue value)? invalidValue, TResult Function(_OtherFailure value)? other, required TResult orElse(), @@ -253,6 +265,7 @@ class _$_NetworkFailure extends _NetworkFailure { required TResult Function() loggedOut, required TResult Function() network, required TResult Function() quotaExceeded, + required TResult Function() insufficientFunds, required TResult Function() invalidValue, required TResult Function(Object? cause, StackTrace? stack) other, }) { @@ -265,6 +278,7 @@ class _$_NetworkFailure extends _NetworkFailure { TResult? Function()? loggedOut, TResult? Function()? network, TResult? Function()? quotaExceeded, + TResult? Function()? insufficientFunds, TResult? Function()? invalidValue, TResult? Function(Object? cause, StackTrace? stack)? other, }) { @@ -277,6 +291,7 @@ class _$_NetworkFailure extends _NetworkFailure { TResult Function()? loggedOut, TResult Function()? network, TResult Function()? quotaExceeded, + TResult Function()? insufficientFunds, TResult Function()? invalidValue, TResult Function(Object? cause, StackTrace? stack)? other, required TResult orElse(), @@ -293,6 +308,7 @@ class _$_NetworkFailure extends _NetworkFailure { required TResult Function(_LoggedOut value) loggedOut, required TResult Function(_NetworkFailure value) network, required TResult Function(_QuotaExceededFailure value) quotaExceeded, + required TResult Function(_InsuffientFunds value) insufficientFunds, required TResult Function(_InvalidValue value) invalidValue, required TResult Function(_OtherFailure value) other, }) { @@ -305,6 +321,7 @@ class _$_NetworkFailure extends _NetworkFailure { TResult? Function(_LoggedOut value)? loggedOut, TResult? Function(_NetworkFailure value)? network, TResult? Function(_QuotaExceededFailure value)? quotaExceeded, + TResult? Function(_InsuffientFunds value)? insufficientFunds, TResult? Function(_InvalidValue value)? invalidValue, TResult? Function(_OtherFailure value)? other, }) { @@ -317,6 +334,7 @@ class _$_NetworkFailure extends _NetworkFailure { TResult Function(_LoggedOut value)? loggedOut, TResult Function(_NetworkFailure value)? network, TResult Function(_QuotaExceededFailure value)? quotaExceeded, + TResult Function(_InsuffientFunds value)? insufficientFunds, TResult Function(_InvalidValue value)? invalidValue, TResult Function(_OtherFailure value)? other, required TResult orElse(), @@ -374,6 +392,7 @@ class _$_QuotaExceededFailure extends _QuotaExceededFailure { required TResult Function() loggedOut, required TResult Function() network, required TResult Function() quotaExceeded, + required TResult Function() insufficientFunds, required TResult Function() invalidValue, required TResult Function(Object? cause, StackTrace? stack) other, }) { @@ -386,6 +405,7 @@ class _$_QuotaExceededFailure extends _QuotaExceededFailure { TResult? Function()? loggedOut, TResult? Function()? network, TResult? Function()? quotaExceeded, + TResult? Function()? insufficientFunds, TResult? Function()? invalidValue, TResult? Function(Object? cause, StackTrace? stack)? other, }) { @@ -398,6 +418,7 @@ class _$_QuotaExceededFailure extends _QuotaExceededFailure { TResult Function()? loggedOut, TResult Function()? network, TResult Function()? quotaExceeded, + TResult Function()? insufficientFunds, TResult Function()? invalidValue, TResult Function(Object? cause, StackTrace? stack)? other, required TResult orElse(), @@ -414,6 +435,7 @@ class _$_QuotaExceededFailure extends _QuotaExceededFailure { required TResult Function(_LoggedOut value) loggedOut, required TResult Function(_NetworkFailure value) network, required TResult Function(_QuotaExceededFailure value) quotaExceeded, + required TResult Function(_InsuffientFunds value) insufficientFunds, required TResult Function(_InvalidValue value) invalidValue, required TResult Function(_OtherFailure value) other, }) { @@ -426,6 +448,7 @@ class _$_QuotaExceededFailure extends _QuotaExceededFailure { TResult? Function(_LoggedOut value)? loggedOut, TResult? Function(_NetworkFailure value)? network, TResult? Function(_QuotaExceededFailure value)? quotaExceeded, + TResult? Function(_InsuffientFunds value)? insufficientFunds, TResult? Function(_InvalidValue value)? invalidValue, TResult? Function(_OtherFailure value)? other, }) { @@ -438,6 +461,7 @@ class _$_QuotaExceededFailure extends _QuotaExceededFailure { TResult Function(_LoggedOut value)? loggedOut, TResult Function(_NetworkFailure value)? network, TResult Function(_QuotaExceededFailure value)? quotaExceeded, + TResult Function(_InsuffientFunds value)? insufficientFunds, TResult Function(_InvalidValue value)? invalidValue, TResult Function(_OtherFailure value)? other, required TResult orElse(), @@ -454,6 +478,133 @@ abstract class _QuotaExceededFailure extends Failure { const _QuotaExceededFailure._() : super._(); } +/// @nodoc +abstract class _$$_InsuffientFundsCopyWith<$Res> { + factory _$$_InsuffientFundsCopyWith( + _$_InsuffientFunds value, $Res Function(_$_InsuffientFunds) then) = + __$$_InsuffientFundsCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$_InsuffientFundsCopyWithImpl<$Res> + extends _$FailureCopyWithImpl<$Res, _$_InsuffientFunds> + implements _$$_InsuffientFundsCopyWith<$Res> { + __$$_InsuffientFundsCopyWithImpl( + _$_InsuffientFunds _value, $Res Function(_$_InsuffientFunds) _then) + : super(_value, _then); +} + +/// @nodoc + +class _$_InsuffientFunds extends _InsuffientFunds { + const _$_InsuffientFunds() : super._(); + + @override + String toString() { + return 'Failure.insufficientFunds()'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$_InsuffientFunds); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() loggedOut, + required TResult Function() network, + required TResult Function() quotaExceeded, + required TResult Function() insufficientFunds, + required TResult Function() invalidValue, + required TResult Function(Object? cause, StackTrace? stack) other, + }) { + return insufficientFunds(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? loggedOut, + TResult? Function()? network, + TResult? Function()? quotaExceeded, + TResult? Function()? insufficientFunds, + TResult? Function()? invalidValue, + TResult? Function(Object? cause, StackTrace? stack)? other, + }) { + return insufficientFunds?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? loggedOut, + TResult Function()? network, + TResult Function()? quotaExceeded, + TResult Function()? insufficientFunds, + TResult Function()? invalidValue, + TResult Function(Object? cause, StackTrace? stack)? other, + required TResult orElse(), + }) { + if (insufficientFunds != null) { + return insufficientFunds(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_LoggedOut value) loggedOut, + required TResult Function(_NetworkFailure value) network, + required TResult Function(_QuotaExceededFailure value) quotaExceeded, + required TResult Function(_InsuffientFunds value) insufficientFunds, + required TResult Function(_InvalidValue value) invalidValue, + required TResult Function(_OtherFailure value) other, + }) { + return insufficientFunds(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_LoggedOut value)? loggedOut, + TResult? Function(_NetworkFailure value)? network, + TResult? Function(_QuotaExceededFailure value)? quotaExceeded, + TResult? Function(_InsuffientFunds value)? insufficientFunds, + TResult? Function(_InvalidValue value)? invalidValue, + TResult? Function(_OtherFailure value)? other, + }) { + return insufficientFunds?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_LoggedOut value)? loggedOut, + TResult Function(_NetworkFailure value)? network, + TResult Function(_QuotaExceededFailure value)? quotaExceeded, + TResult Function(_InsuffientFunds value)? insufficientFunds, + TResult Function(_InvalidValue value)? invalidValue, + TResult Function(_OtherFailure value)? other, + required TResult orElse(), + }) { + if (insufficientFunds != null) { + return insufficientFunds(this); + } + return orElse(); + } +} + +abstract class _InsuffientFunds extends Failure { + const factory _InsuffientFunds() = _$_InsuffientFunds; + const _InsuffientFunds._() : super._(); +} + /// @nodoc abstract class _$$_InvalidValueCopyWith<$Res> { factory _$$_InvalidValueCopyWith( @@ -495,6 +646,7 @@ class _$_InvalidValue extends _InvalidValue { required TResult Function() loggedOut, required TResult Function() network, required TResult Function() quotaExceeded, + required TResult Function() insufficientFunds, required TResult Function() invalidValue, required TResult Function(Object? cause, StackTrace? stack) other, }) { @@ -507,6 +659,7 @@ class _$_InvalidValue extends _InvalidValue { TResult? Function()? loggedOut, TResult? Function()? network, TResult? Function()? quotaExceeded, + TResult? Function()? insufficientFunds, TResult? Function()? invalidValue, TResult? Function(Object? cause, StackTrace? stack)? other, }) { @@ -519,6 +672,7 @@ class _$_InvalidValue extends _InvalidValue { TResult Function()? loggedOut, TResult Function()? network, TResult Function()? quotaExceeded, + TResult Function()? insufficientFunds, TResult Function()? invalidValue, TResult Function(Object? cause, StackTrace? stack)? other, required TResult orElse(), @@ -535,6 +689,7 @@ class _$_InvalidValue extends _InvalidValue { required TResult Function(_LoggedOut value) loggedOut, required TResult Function(_NetworkFailure value) network, required TResult Function(_QuotaExceededFailure value) quotaExceeded, + required TResult Function(_InsuffientFunds value) insufficientFunds, required TResult Function(_InvalidValue value) invalidValue, required TResult Function(_OtherFailure value) other, }) { @@ -547,6 +702,7 @@ class _$_InvalidValue extends _InvalidValue { TResult? Function(_LoggedOut value)? loggedOut, TResult? Function(_NetworkFailure value)? network, TResult? Function(_QuotaExceededFailure value)? quotaExceeded, + TResult? Function(_InsuffientFunds value)? insufficientFunds, TResult? Function(_InvalidValue value)? invalidValue, TResult? Function(_OtherFailure value)? other, }) { @@ -559,6 +715,7 @@ class _$_InvalidValue extends _InvalidValue { TResult Function(_LoggedOut value)? loggedOut, TResult Function(_NetworkFailure value)? network, TResult Function(_QuotaExceededFailure value)? quotaExceeded, + TResult Function(_InsuffientFunds value)? insufficientFunds, TResult Function(_InvalidValue value)? invalidValue, TResult Function(_OtherFailure value)? other, required TResult orElse(), @@ -648,6 +805,7 @@ class _$_OtherFailure extends _OtherFailure { required TResult Function() loggedOut, required TResult Function() network, required TResult Function() quotaExceeded, + required TResult Function() insufficientFunds, required TResult Function() invalidValue, required TResult Function(Object? cause, StackTrace? stack) other, }) { @@ -660,6 +818,7 @@ class _$_OtherFailure extends _OtherFailure { TResult? Function()? loggedOut, TResult? Function()? network, TResult? Function()? quotaExceeded, + TResult? Function()? insufficientFunds, TResult? Function()? invalidValue, TResult? Function(Object? cause, StackTrace? stack)? other, }) { @@ -672,6 +831,7 @@ class _$_OtherFailure extends _OtherFailure { TResult Function()? loggedOut, TResult Function()? network, TResult Function()? quotaExceeded, + TResult Function()? insufficientFunds, TResult Function()? invalidValue, TResult Function(Object? cause, StackTrace? stack)? other, required TResult orElse(), @@ -688,6 +848,7 @@ class _$_OtherFailure extends _OtherFailure { required TResult Function(_LoggedOut value) loggedOut, required TResult Function(_NetworkFailure value) network, required TResult Function(_QuotaExceededFailure value) quotaExceeded, + required TResult Function(_InsuffientFunds value) insufficientFunds, required TResult Function(_InvalidValue value) invalidValue, required TResult Function(_OtherFailure value) other, }) { @@ -700,6 +861,7 @@ class _$_OtherFailure extends _OtherFailure { TResult? Function(_LoggedOut value)? loggedOut, TResult? Function(_NetworkFailure value)? network, TResult? Function(_QuotaExceededFailure value)? quotaExceeded, + TResult? Function(_InsuffientFunds value)? insufficientFunds, TResult? Function(_InvalidValue value)? invalidValue, TResult? Function(_OtherFailure value)? other, }) { @@ -712,6 +874,7 @@ class _$_OtherFailure extends _OtherFailure { TResult Function(_LoggedOut value)? loggedOut, TResult Function(_NetworkFailure value)? network, TResult Function(_QuotaExceededFailure value)? quotaExceeded, + TResult Function(_InsuffientFunds value)? insufficientFunds, TResult Function(_InvalidValue value)? invalidValue, TResult Function(_OtherFailure value)? other, required TResult orElse(), diff --git a/lib/domain/repositories/airdrop.dart b/lib/domain/repositories/airdrop.dart new file mode 100644 index 000000000..e307b1a43 --- /dev/null +++ b/lib/domain/repositories/airdrop.dart @@ -0,0 +1,24 @@ +import 'package:aewallet/domain/models/core/failures.dart'; +import 'package:aewallet/domain/models/core/result.dart'; + +abstract class AirDropRepositoryInterface { + /// Return [null] if device is not authorized to request AirDrop + /// Else, return the challenge. + Future> requestChallenge({ + required String deviceId, + }); + + /// Request AirDrop + Future> requestAirDrop({ + required String challenge, + required String deviceId, + required String keychainAddress, + }); + + Future getLastAirdropDate(); + + Future setLastAirdropDate(); + + /// Clears all stored data + Future clear(); +} diff --git a/lib/domain/repositories/device_info.dart b/lib/domain/repositories/device_info.dart new file mode 100644 index 000000000..a1ef7ec1f --- /dev/null +++ b/lib/domain/repositories/device_info.dart @@ -0,0 +1,3 @@ +abstract class DeviceInfoRepositoryInterface { + Future getInstallationId(); +} diff --git a/lib/infrastructure/datasources/hive_preferences.dart b/lib/infrastructure/datasources/hive_preferences.dart index 0055bb1fc..197598e5f 100644 --- a/lib/infrastructure/datasources/hive_preferences.dart +++ b/lib/infrastructure/datasources/hive_preferences.dart @@ -30,6 +30,7 @@ class HivePreferencesDatasource { static const String lock = 'archethic_wallet_lock'; static const String lockTimeout = 'archethic_wallet_lock_timeout'; static const String autoLockDate = 'archethic_wallet_autolock_date'; + static const String airdropDate = 'archethic_wallet_airdrop_date'; static const String hasShownRootWarning = 'archethic_wallet_has_shown_root_warning'; static const String pinAttempts = 'archethic_wallet_pin_attempts'; @@ -252,4 +253,16 @@ class HivePreferencesDatasource { Future clearAutoLockTriggerDate() async { await _removeValue(autoLockDate); } + + DateTime? getLastAirdropDate() { + return _getValue(airdropDate, defaultValue: null); + } + + Future setLastAirdropDate(DateTime date) async { + return _setValue(airdropDate, date); + } + + Future clearLastAirdropDate() async { + return _removeValue(airdropDate); + } } diff --git a/lib/infrastructure/repositories/airdrop.dart b/lib/infrastructure/repositories/airdrop.dart new file mode 100644 index 000000000..ab08e9c37 --- /dev/null +++ b/lib/infrastructure/repositories/airdrop.dart @@ -0,0 +1,108 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:aewallet/domain/models/core/failures.dart'; +import 'package:aewallet/domain/models/core/result.dart'; +import 'package:aewallet/domain/repositories/airdrop.dart'; +import 'package:aewallet/infrastructure/datasources/hive_preferences.dart'; +import 'package:crypto/crypto.dart'; +import 'package:http/http.dart' as http; + +class _AirDropRoutes { + String get uriRoot => 'https://airdrop.archethic.net'; + String get challenge => '$uriRoot/challenge'; + String get claim => '$uriRoot/claim'; +} + +class AirDropRepository implements AirDropRepositoryInterface { + HivePreferencesDatasource? _preferences; + Future get preferences async => + _preferences ??= await HivePreferencesDatasource.getInstance(); + + @override + Future> requestChallenge({ + required String deviceId, + }) async => + Result.guard(() async { + final response = await http.post( + Uri.parse( + _AirDropRoutes().challenge, + ), + headers: { + HttpHeaders.contentTypeHeader: 'application/json', + }, + body: json.encode({ + 'deviceId': deviceId, + }), + ); + + if (response.statusCode == 429) { + throw const Failure.quotaExceeded(); + } + + if (response.statusCode != 200) { + throw const Failure.other(); + } + + final body = json.decode(response.body); + + return body['challenge'].toString(); + }); + + @override + Future> requestAirDrop({ + required String challenge, + required String deviceId, + required String keychainAddress, + }) async => + Result.guard(() async { + const airDropSecret = String.fromEnvironment('AIRDROP_SECRET'); + final key = utf8.encode(airDropSecret); + final bytes = utf8.encode(challenge); + final challengeHmac = Hmac( + sha256, + key, + ).convert(bytes).toString(); + + final response = await http.post( + Uri.parse( + _AirDropRoutes().claim, + ), + headers: { + HttpHeaders.contentTypeHeader: 'application/json', + }, + body: json.encode({ + 'deviceId': deviceId, + 'challenge': challenge, + 'auth': challengeHmac, + 'keychainAddress': keychainAddress, + }), + ); + + if (response.statusCode == 429) { + throw const Failure.quotaExceeded(); + } + + if (response.statusCode != 200) { + if (response.body.contains('Insufficient funds')) { + throw const Failure.insufficientFunds(); + } + throw const Failure.other(); + } + }); + + @override + Future getLastAirdropDate() async { + return (await preferences).getLastAirdropDate(); + } + + @override + Future setLastAirdropDate() async { + (await preferences).setLastAirdropDate(DateTime.now()); + } + + @override + Future clear() async { + (await preferences).clearLastAirdropDate(); + } +} diff --git a/lib/infrastructure/repositories/device_info.dart b/lib/infrastructure/repositories/device_info.dart new file mode 100644 index 000000000..9363fc338 --- /dev/null +++ b/lib/infrastructure/repositories/device_info.dart @@ -0,0 +1,21 @@ +import 'package:aewallet/domain/repositories/device_info.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:uuid/uuid.dart'; + +class DeviceInfoRepository implements DeviceInfoRepositoryInterface { + @override + Future getInstallationId() async { + const installationIdKey = 'INSTALLATION_ID'; + const storage = FlutterSecureStorage(); + + final installationId = await storage.read(key: installationIdKey); + if (installationId != null) return installationId; + + final newInstallationId = const Uuid().v4(); + await storage.write( + key: installationIdKey, + value: newInstallationId, + ); + return newInstallationId; + } +} diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 7836556e5..2de25960a 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -72,6 +72,8 @@ "placeholders_order": [], "placeholders": {} }, + "airdropError_QUOTA_EXCEEDED_ERROR": "Reward already claimed.", + "airdropError_BACKEND_ERROR": "An error occurred. Try again later.", "noToken": "No token", "@noToken": { "type": "text", @@ -2657,5 +2659,45 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "getUCOButton": "Get UCOs", + "@getUCOButton": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "getUCODescription1": "Bring ARCHETHIC to life with your first ", + "@getUCODescription1": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "getUCOInformation": "In a few minutes you will receive your UCOs which will allow you to use the Archethic network and the features of your wallet.", + "@getUCOInformation": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "getUCOInformationAlreadyReceived": "You have already received your UCOs.", + "@getUCOInformationAlreadyReceived": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "getUCOInformationBackendError": "An error occurred. Try again later.", + "@getUCOInformationBackendError": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "getUCOInformationInsufficientBalance": "The faucet is currently empty. Please reiterate your request tomorrow.", + "@getUCOInformationInsufficientBalance": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "getUCOCount": "See you in %1h%2 to get new UCOs...", + "@getUCOCount": { + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index f98389a22..76ef98a9a 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -12,6 +12,8 @@ "add": "Ajouter", "update": "Mettre Ă  jour", "previewNotAvailable": "Aperçu non disponible", + "airdropError_QUOTA_EXCEEDED_ERROR": "Vous avez dĂ©jĂ  reçu la rĂ©compense.", + "airdropError_BACKEND_ERROR": "Une erreur s'est produite. Essayer Ă  nouveau plus tard.", "noToken": "Aucun jeton", "noNFT": "Aucun NFT", "transferTokens": "Envoyer des %1", @@ -443,5 +445,15 @@ "nftAddImportUrl": "Url", "nftAddPreview": "Votre NFT (Preview)", "nftAddFileSize": "Taille: ", - "createNFTConfirmation": "Etes-vous sĂ»r(e) de vouloir crĂ©er ce NFT ?" + "createNFTConfirmation": "Etes-vous sĂ»r(e) de vouloir crĂ©er ce NFT ?", + "getUCOButton": "Obtenir des UCOs", + "getUCODescription1": "Donnez vie Ă  ARCHETHIC avec vos 1 ers ", + "getUCOInformation": "Vous allez recevoir dans quelques minutes vos UCOs qui vous permettront d'utiliser le rĂ©seau Archethic et les fonctionnalitĂ©s de votre wallet.", + "getUCOInformationAlreadyReceived": "Vous avez dĂ©jĂ  reçu vos UCOs.", + "getUCOInformationBackendError": "Une erreur s'est produite. Essayer Ă  nouveau plus tard.", + "getUCOInformationInsufficientBalance": "The faucet is currently empty. Please reiterate your request tomorrow.", + "getUCOCount": "Rdv dans %1h%2 pour obtenir de nouveaux UCOs...", + "@getUCOCount": { + "placeholders": {} + } } \ No newline at end of file diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index 2d0ffcc06..94a4ee0e7 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2022-11-30T22:42:26.376004", + "@@last_modified": "2022-12-08T08:34:52.518194", "welcomeText": "Welcome to Internet of Trust.\n\nArchethic gives back to humanity control over technology, and to each individual, control over their identity.", "@welcomeText": { "type": "text", @@ -2663,5 +2663,47 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "getUCOButton": "Get UCOs", + "@getUCOButton": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "getUCODescription1": "Bring ARCHETHIC to life with your first ", + "@getUCODescription1": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "getUCOInformation": "In a few minutes you will receive your UCOs which will allow you to use the Archethic network and the features of your wallet.", + "@getUCOInformation": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "getUCOInformationAlreadyReceived": "You have already received your UCOs.", + "@getUCOInformationAlreadyReceived": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "getUCOInformationInsufficientBalance": "The faucet is currently empty. Please reiterate your request tomorrow.", + "@getUCOInformationInsufficientBalance": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "getUCOInformationBackendError": "An error occurred. Try again later.", + "@getUCOInformationBackendError": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "getUCOCount": "See you in HHhMM to get new UCOs...", + "@getUCOCount": { + "type": "text", + "placeholders_order": [], + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/messages_en.dart b/lib/l10n/messages_en.dart index bb92440da..8e6cf60f8 100644 --- a/lib/l10n/messages_en.dart +++ b/lib/l10n/messages_en.dart @@ -166,6 +166,13 @@ class MessageLookup extends MessageLookupByLibrary { "fungiblesTokensListNoTokenYet" : MessageLookupByLibrary.simpleMessage("No token yet"), "getOption" : MessageLookupByLibrary.simpleMessage("Get"), "getPublicKeyHeader" : MessageLookupByLibrary.simpleMessage("Access"), + "getUCOButton" : MessageLookupByLibrary.simpleMessage("Get UCOs"), + "getUCOCount" : MessageLookupByLibrary.simpleMessage("See you in %1h%2 to get new UCOs..."), + "getUCODescription1" : MessageLookupByLibrary.simpleMessage("Bring ARCHETHIC to life with your first "), + "getUCOInformation" : MessageLookupByLibrary.simpleMessage("In a few minutes you will receive your UCOs which will allow you to use the Archethic network and the features of your wallet."), + "getUCOInformationAlreadyReceived" : MessageLookupByLibrary.simpleMessage("You have already received your UCOs."), + "getUCOInformationBackendError" : MessageLookupByLibrary.simpleMessage("An error occurred. Try again later."), + "getUCOInformationInsufficientBalance" : MessageLookupByLibrary.simpleMessage("The faucet is currently empty. Please reiterate your request tomorrow."), "go" : MessageLookupByLibrary.simpleMessage("Go!"), "hiddenCategories" : MessageLookupByLibrary.simpleMessage("Hidden categories"), "iUnderstandTheRisks" : MessageLookupByLibrary.simpleMessage("I Understand the Risks"), diff --git a/lib/l10n/messages_fr.dart b/lib/l10n/messages_fr.dart index eab426e6b..cf00b670b 100644 --- a/lib/l10n/messages_fr.dart +++ b/lib/l10n/messages_fr.dart @@ -166,6 +166,13 @@ class MessageLookup extends MessageLookupByLibrary { "fungiblesTokensListNoTokenYet" : MessageLookupByLibrary.simpleMessage("Aucun jeton pour le moment"), "getOption" : MessageLookupByLibrary.simpleMessage("Get"), "getPublicKeyHeader" : MessageLookupByLibrary.simpleMessage("AccĂšs"), + "getUCOButton" : MessageLookupByLibrary.simpleMessage("Obtenir des UCOs"), + "getUCOCount" : MessageLookupByLibrary.simpleMessage("Rdv dans %1h%2 pour obtenir de nouveaux UCOs..."), + "getUCODescription1" : MessageLookupByLibrary.simpleMessage("Donnez vie Ă  ARCHETHIC avec vos 1 ers "), + "getUCOInformation" : MessageLookupByLibrary.simpleMessage("Vous allez recevoir dans quelques minutes vos UCOs qui vous permettront d\'utiliser le rĂ©seau Archethic et les fonctionnalitĂ©s de votre wallet."), + "getUCOInformationAlreadyReceived" : MessageLookupByLibrary.simpleMessage("Vous avez dĂ©jĂ  reçu vos UCOs."), + "getUCOInformationBackendError" : MessageLookupByLibrary.simpleMessage("Une erreur s\'est produite. Essayer Ă  nouveau plus tard."), + "getUCOInformationInsufficientBalance" : MessageLookupByLibrary.simpleMessage("The faucet is currently empty. Please reiterate your request tomorrow."), "go" : MessageLookupByLibrary.simpleMessage("Go !"), "hiddenCategories" : MessageLookupByLibrary.simpleMessage("CatĂ©gories masquĂ©es"), "iUnderstandTheRisks" : MessageLookupByLibrary.simpleMessage("Je comprends les risques"), diff --git a/lib/l10n/messages_messages.dart b/lib/l10n/messages_messages.dart index 8befcce8c..e77f35b0b 100644 --- a/lib/l10n/messages_messages.dart +++ b/lib/l10n/messages_messages.dart @@ -167,6 +167,13 @@ class MessageLookup extends MessageLookupByLibrary { "fungiblesTokensListNoTokenYet" : MessageLookupByLibrary.simpleMessage("No token yet"), "getOption" : MessageLookupByLibrary.simpleMessage("Get"), "getPublicKeyHeader" : MessageLookupByLibrary.simpleMessage("Access"), + "getUCOButton" : MessageLookupByLibrary.simpleMessage("Get UCOs"), + "getUCOCount" : MessageLookupByLibrary.simpleMessage("See you in HHhMM to get new UCOs..."), + "getUCODescription1" : MessageLookupByLibrary.simpleMessage("Bring ARCHETHIC to life with your first "), + "getUCOInformation" : MessageLookupByLibrary.simpleMessage("In a few minutes you will receive your UCOs which will allow you to use the Archethic network and the features of your wallet."), + "getUCOInformationAlreadyReceived" : MessageLookupByLibrary.simpleMessage("You have already received your UCOs."), + "getUCOInformationBackendError" : MessageLookupByLibrary.simpleMessage("An error occurred. Try again later."), + "getUCOInformationInsufficientBalance" : MessageLookupByLibrary.simpleMessage("The faucet is currently empty. Please reiterate your request tomorrow."), "go" : MessageLookupByLibrary.simpleMessage("Go!"), "hiddenCategories" : MessageLookupByLibrary.simpleMessage("Hidden categories"), "iUnderstandTheRisks" : MessageLookupByLibrary.simpleMessage("I Understand the Risks"), diff --git a/lib/localization.dart b/lib/localization.dart index aed10058d..1a4c814ae 100644 --- a/lib/localization.dart +++ b/lib/localization.dart @@ -2076,6 +2076,42 @@ class AppLocalization { 'The connection to the network could not be completed. Please check your network settings.', name: 'noConnection'); } + + String get getUCOButton { + return Intl.message('Get UCOs', name: 'getUCOButton'); + } + + String get getUCODescription1 { + return Intl.message('Bring ARCHETHIC to life with your first ', + name: 'getUCODescription1'); + } + + String get getUCOInformation { + return Intl.message( + 'In a few minutes you will receive your UCOs which will allow you to use the Archethic network and the features of your wallet.', + name: 'getUCOInformation'); + } + + String get getUCOInformationAlreadyReceived { + return Intl.message('You have already received your UCOs.', + name: 'getUCOInformationAlreadyReceived'); + } + + String get getUCOInformationInsufficientBalance { + return Intl.message( + 'The faucet is currently empty. Please reiterate your request tomorrow.', + name: 'getUCOInformationInsufficientBalance'); + } + + String get getUCOInformationBackendError { + return Intl.message('An error occurred. Try again later.', + name: 'getUCOInformationBackendError'); + } + + String get getUCOCount { + return Intl.message('See you in HHhMM to get new UCOs...', + name: 'getUCOCount'); + } } class AppLocalizationsDelegate extends LocalizationsDelegate { diff --git a/lib/ui/views/intro/intro_backup_confirm.dart b/lib/ui/views/intro/intro_backup_confirm.dart index 4e4603113..2a04a4b47 100755 --- a/lib/ui/views/intro/intro_backup_confirm.dart +++ b/lib/ui/views/intro/intro_backup_confirm.dart @@ -28,6 +28,7 @@ import 'package:aewallet/util/mnemonics.dart'; import 'package:archethic_lib_dart/archethic_lib_dart.dart'; import 'package:auto_size_text/auto_size_text.dart'; import 'package:event_taxi/event_taxi.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -368,7 +369,8 @@ class _IntroBackupConfirmState extends ConsumerState { ), ), if (settings.network.network == - AvailableNetworks.archethicTestNet) + AvailableNetworks.archethicTestNet || + kDebugMode) Column( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ diff --git a/lib/ui/views/main/account_tab.dart b/lib/ui/views/main/account_tab.dart index 5febeb3aa..6237ea94b 100644 --- a/lib/ui/views/main/account_tab.dart +++ b/lib/ui/views/main/account_tab.dart @@ -1,4 +1,5 @@ import 'package:aewallet/application/account/providers.dart'; +import 'package:aewallet/application/airdrop/provider.dart'; import 'package:aewallet/application/blog.dart'; import 'package:aewallet/application/contact.dart'; import 'package:aewallet/application/market_price.dart'; @@ -27,6 +28,9 @@ class AccountTab extends ConsumerWidget { final theme = ref.watch(ThemeProviders.selectedTheme); final preferences = ref.watch(SettingsProviders.settings); + final isDeviceFaucetCompatible = + ref.watch(AirDropProviders.isDeviceCompatible).valueOrNull ?? false; + return Column( children: [ Expanded( @@ -102,13 +106,14 @@ class AccountTab extends ConsumerWidget { color: theme.backgroundDarkest!.withOpacity(0.1), ), - /// ICONS - //const AirDrop(), + if (isDeviceFaucetCompatible) const AirDrop(), + if (isDeviceFaucetCompatible) + Divider( + height: 1, + color: + theme.backgroundDarkest!.withOpacity(0.1), + ), - Divider( - height: 1, - color: theme.backgroundDarkest!.withOpacity(0.1), - ), const SizedBox( height: 15, ), diff --git a/lib/ui/views/main/components/airdrop.dart b/lib/ui/views/main/components/airdrop.dart index 516ba3694..73f35e69d 100644 --- a/lib/ui/views/main/components/airdrop.dart +++ b/lib/ui/views/main/components/airdrop.dart @@ -1,7 +1,39 @@ /// SPDX-License-Identifier: AGPL-3.0-or-later import 'package:aewallet/application/account/providers.dart'; +import 'package:aewallet/application/airdrop/provider.dart'; +import 'package:aewallet/application/settings/theme.dart'; +import 'package:aewallet/domain/models/core/failures.dart'; +import 'package:aewallet/localization.dart'; +import 'package:aewallet/ui/util/styles.dart'; +import 'package:aewallet/ui/util/ui_util.dart'; +import 'package:aewallet/ui/widgets/components/app_button_tiny.dart'; +import 'package:aewallet/ui/widgets/components/dialog.dart'; +import 'package:aewallet/util/functional_utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'airdrop.g.dart'; + +/// True if the AirDrop request button should be active +@Riverpod(keepAlive: true) +bool _isAirDropRequestButtonActive(Ref ref) { + final isCooldownActive = + ref.watch(AirDropProviders.airdropCooldown).maybeWhen( + data: (data) => data > Duration.zero, + orElse: () => true, + ); + final isAirdropEnabled = + ref.watch(AirDropProviders.isFaucetEnabled).maybeWhen( + data: id, + orElse: () => false, + ); + + final isAirDropRequestRunning = + ref.watch(AirDropProviders.airDropRequest).isLoading; + + return !isCooldownActive && isAirdropEnabled && !isAirDropRequestRunning; +} class AirDrop extends ConsumerWidget { const AirDrop({super.key}); @@ -16,13 +48,154 @@ class AirDrop extends ConsumerWidget { if (accountSelected == null) return const SizedBox(); + final theme = ref.watch(ThemeProviders.selectedTheme); + + final isAirDropRequestButtonActive = ref.watch( + _isAirDropRequestButtonActiveProvider, + ); + + final isAirdropRequestRunning = + ref.watch(AirDropProviders.airDropRequest).isLoading; + + final localizations = AppLocalization.of(context)!; + ref.listen( + AirDropProviders.airDropRequest, + (previous, next) { + if (previous == next) return; + + final error = next.error as Failure?; + if (error == null) { + AppDialogs.showInfoDialog( + context, + ref, + localizations.informations, + localizations.getUCOInformation, + ); + return; + } + UIUtil.showSnackbar( + error.maybeMap( + quotaExceeded: (value) => + localizations.getUCOInformationAlreadyReceived, + insufficientFunds: (value) => + localizations.getUCOInformationInsufficientBalance, + orElse: () => localizations.getUCOInformationBackendError, + ), + context, + ref, + theme.text!, + theme.snackBarShadow!, + ); + }, + ); + return Container( alignment: Alignment.centerLeft, width: MediaQuery.of(context).size.width, - child: Image.asset( - 'assets/images/airdrop.png', - fit: BoxFit.fill, + child: Stack( + children: [ + Column( + children: [ + Image.asset( + 'assets/images/airdrop.png', + fit: BoxFit.fill, + ), + Container( + height: 30, + color: Colors.black.withOpacity(0.3), + ) + ], + ), + const Positioned( + right: 5, + child: Align( + alignment: Alignment.topRight, + child: _CooldownCounter(), + ), + ), + Positioned( + top: 40, + right: 0, + child: Padding( + padding: const EdgeInsets.only(bottom: 15), + child: AppButtonTinyWithoutExpanded( + isAirDropRequestButtonActive + ? AppButtonTinyType.primary + : AppButtonTinyType.primaryOutline, + localizations.getUCOButton, + const [14, 8, 14, 0], + disabled: !isAirDropRequestButtonActive, + showProgressIndicator: isAirdropRequestRunning, + key: const Key('getUCO'), + width: 170, + onPressed: isAirDropRequestButtonActive + ? () { + ref + .read(AirDropProviders.airDropRequest.notifier) + .requestAirDrop(); + } + : () {}, + ), + ), + ), + Positioned.fill( + top: 115, + child: Padding( + padding: const EdgeInsets.only(bottom: 15), + child: RichText( + textAlign: TextAlign.center, + text: TextSpan( + text: '', + children: [ + TextSpan( + text: localizations.getUCODescription1, + style: theme.textStyleSize14W600EquinoxPrimary, + ), + TextSpan( + text: 'UCO', + style: theme.textStyleSize24W700EquinoxPrimary, + ), + TextSpan( + text: 's', + style: theme.textStyleSize14W600EquinoxPrimary, + ), + ], + ), + ), + ), + ), + ], ), ); } } + +class _CooldownCounter extends ConsumerWidget { + const _CooldownCounter(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final localizations = AppLocalization.of(context)!; + final cooldownRemainingTime = + ref.watch(AirDropProviders.airdropCooldown).valueOrNull; + + if (cooldownRemainingTime == null || + cooldownRemainingTime == Duration.zero) { + return const SizedBox(); + } + final theme = ref.watch(ThemeProviders.selectedTheme); + + return Text( + localizations.getUCOCount + .replaceAll( + '%1', + cooldownRemainingTime.inHours.toString().padLeft(2, '0'), + ) + .replaceAll( + '%2', + (cooldownRemainingTime.inMinutes % 60).toString().padLeft(2, '0'), + ), + style: theme.textStyleSize12W100Primary, + ); + } +} diff --git a/lib/ui/views/main/components/airdrop.g.dart b/lib/ui/views/main/components/airdrop.g.dart new file mode 100644 index 000000000..6532430de --- /dev/null +++ b/lib/ui/views/main/components/airdrop.g.dart @@ -0,0 +1,45 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'airdrop.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// ignore_for_file: avoid_private_typedef_functions, non_constant_identifier_names, subtype_of_sealed_class, invalid_use_of_internal_member, unused_element, constant_identifier_names, unnecessary_raw_strings, library_private_types_in_public_api + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +String $_isAirDropRequestButtonActiveHash() => + r'd754709efac4eb96bfcc546aad89eafa55d78370'; + +/// True if the AirDrop request button should be active +/// +/// Copied from [_isAirDropRequestButtonActive]. +final _isAirDropRequestButtonActiveProvider = Provider( + _isAirDropRequestButtonActive, + name: r'_isAirDropRequestButtonActiveProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : $_isAirDropRequestButtonActiveHash, +); +typedef _IsAirDropRequestButtonActiveRef = ProviderRef; diff --git a/lib/ui/widgets/components/app_button_tiny.dart b/lib/ui/widgets/components/app_button_tiny.dart index 47ab1224e..05b158d25 100644 --- a/lib/ui/widgets/components/app_button_tiny.dart +++ b/lib/ui/widgets/components/app_button_tiny.dart @@ -18,17 +18,21 @@ class AppButtonTiny extends ConsumerWidget { this.buttonText, this.dimens, { required this.onPressed, + this.showProgressIndicator = false, this.disabled = false, this.icon, + this.width = 400, super.key, }); + final bool showProgressIndicator; final AppButtonTinyType type; final String buttonText; final List dimens; final Function onPressed; final bool disabled; final Icon? icon; + final double? width; @override Widget build( @@ -41,7 +45,7 @@ class AppButtonTiny extends ConsumerWidget { case AppButtonTinyType.primary: return Expanded( child: Container( - width: 400, + width: width, decoration: ShapeDecoration( gradient: theme.gradientMainButton, shape: const StadiumBorder(), @@ -69,12 +73,31 @@ class AppButtonTiny extends ConsumerWidget { borderRadius: BorderRadius.circular(10), ), ), - child: AutoSizeText( - buttonText, - textAlign: TextAlign.center, - style: theme.textStyleSize12W400EquinoxMainButtonLabel, - maxLines: 1, - stepGranularity: 0.5, + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (showProgressIndicator) const Spacer(), + AutoSizeText( + buttonText, + textAlign: TextAlign.center, + style: + theme.textStyleSize12W400EquinoxMainButtonLabel, + maxLines: 1, + stepGranularity: 0.5, + ), + if (showProgressIndicator) const Spacer(), + if (showProgressIndicator) + SizedBox.square( + dimension: 10, + child: CircularProgressIndicator( + color: theme + .textStyleSize12W400EquinoxMainButtonLabel + .color, + strokeWidth: 2, + ), + ), + ], ), onPressed: () { if (!disabled) { @@ -119,7 +142,7 @@ class AppButtonTiny extends ConsumerWidget { case AppButtonTinyType.primaryOutline: return Expanded( child: Container( - width: 400, + width: width, decoration: ShapeDecoration( gradient: theme.gradientMainButton, shape: const StadiumBorder(), @@ -139,13 +162,31 @@ class AppButtonTiny extends ConsumerWidget { borderRadius: BorderRadius.circular(10), ), ), - child: AutoSizeText( - buttonText, - textAlign: TextAlign.center, - style: theme - .textStyleSize12W400EquinoxMainButtonLabelDisabled, - maxLines: 1, - stepGranularity: 0.5, + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (showProgressIndicator) const Spacer(), + AutoSizeText( + buttonText, + textAlign: TextAlign.center, + style: theme + .textStyleSize12W400EquinoxMainButtonLabelDisabled, + maxLines: 1, + stepGranularity: 0.5, + ), + if (showProgressIndicator) const Spacer(), + if (showProgressIndicator) + SizedBox.square( + dimension: 10, + child: CircularProgressIndicator( + color: theme + .textStyleSize12W400EquinoxMainButtonLabel + .color, + strokeWidth: 2, + ), + ), + ], ), onPressed: () { if (!disabled) { @@ -190,3 +231,218 @@ class AppButtonTiny extends ConsumerWidget { } } // } + +class AppButtonTinyWithoutExpanded extends ConsumerWidget { + const AppButtonTinyWithoutExpanded( + this.type, + this.buttonText, + this.dimens, { + required this.onPressed, + this.showProgressIndicator = false, + this.disabled = false, + this.icon, + this.width = 400, + super.key, + }); + + final bool showProgressIndicator; + final AppButtonTinyType type; + final String buttonText; + final List dimens; + final Function onPressed; + final bool disabled; + final Icon? icon; + final double? width; + + @override + Widget build( + BuildContext context, + WidgetRef ref, + ) { + final theme = ref.watch(ThemeProviders.selectedTheme); + final preferences = ref.watch(SettingsProviders.settings); + switch (type) { + case AppButtonTinyType.primary: + return Container( + width: width, + decoration: ShapeDecoration( + gradient: theme.gradientMainButton, + shape: const StadiumBorder(), + shadows: [ + BoxShadow( + color: Colors.black.withOpacity(0.5), + blurRadius: 7, + spreadRadius: 1, + offset: const Offset(0, 5), + ), + ], + ), + height: 35, + margin: EdgeInsetsDirectional.fromSTEB( + dimens[0], + dimens[1], + dimens[2], + dimens[3], + ), + child: icon == null + ? TextButton( + key: key, + style: TextButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (showProgressIndicator) const Spacer(), + AutoSizeText( + buttonText, + textAlign: TextAlign.center, + style: theme.textStyleSize12W400EquinoxMainButtonLabel, + maxLines: 1, + stepGranularity: 0.5, + ), + if (showProgressIndicator) const Spacer(), + if (showProgressIndicator) + SizedBox.square( + dimension: 10, + child: CircularProgressIndicator( + color: theme + .textStyleSize12W400EquinoxMainButtonLabel + .color, + strokeWidth: 2, + ), + ), + ], + ), + onPressed: () { + if (!disabled) { + sl.get().feedback( + FeedbackType.light, + preferences.activeVibrations, + ); + onPressed(); + } + return; + }, + ) + : TextButton.icon( + key: key, + style: TextButton.styleFrom( + foregroundColor: theme.text, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + icon: icon!, + label: AutoSizeText( + buttonText, + textAlign: TextAlign.center, + style: theme.textStyleSize12W400EquinoxMainButtonLabel, + maxLines: 1, + stepGranularity: 0.5, + ), + onPressed: () { + if (!disabled) { + sl.get().feedback( + FeedbackType.light, + preferences.activeVibrations, + ); + onPressed(); + } + return; + }, + ), + ); + case AppButtonTinyType.primaryOutline: + return Container( + width: width, + decoration: ShapeDecoration( + gradient: theme.gradientMainButton, + shape: const StadiumBorder(), + ), + height: 35, + margin: EdgeInsetsDirectional.fromSTEB( + dimens[0], + dimens[1], + dimens[2], + dimens[3], + ), + child: icon == null + ? TextButton( + key: key, + style: TextButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (showProgressIndicator) const Spacer(), + AutoSizeText( + buttonText, + textAlign: TextAlign.center, + style: theme + .textStyleSize12W400EquinoxMainButtonLabelDisabled, + maxLines: 1, + stepGranularity: 0.5, + ), + if (showProgressIndicator) const Spacer(), + if (showProgressIndicator) + SizedBox.square( + dimension: 10, + child: CircularProgressIndicator( + color: theme + .textStyleSize12W400EquinoxMainButtonLabel + .color, + strokeWidth: 2, + ), + ), + ], + ), + onPressed: () { + if (!disabled) { + sl.get().feedback( + FeedbackType.light, + preferences.activeVibrations, + ); + onPressed(); + } + return; + }, + ) + : TextButton.icon( + key: key, + style: TextButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + icon: icon!, + label: AutoSizeText( + buttonText, + textAlign: TextAlign.center, + style: + theme.textStyleSize12W400EquinoxMainButtonLabelDisabled, + maxLines: 1, + stepGranularity: 0.5, + ), + onPressed: () { + if (!disabled) { + sl.get().feedback( + FeedbackType.light, + preferences.activeVibrations, + ); + onPressed(); + } + return; + }, + ), + ); + } + } // +} diff --git a/lib/util/date_util.dart b/lib/util/date_util.dart new file mode 100644 index 000000000..fb66b150c --- /dev/null +++ b/lib/util/date_util.dart @@ -0,0 +1,15 @@ +extension DateTimeUtil on DateTime { + DateTime get startOfDay => DateTime( + year, + month, + day, + 0, + 0, + 0, + ); +} + +extension DurationUtil on Duration { + Duration min(Duration other) => other < this ? other : this; + Duration max(Duration other) => other > this ? other : this; +} diff --git a/macos/fastlane/Deliverfile b/macos/fastlane/Deliverfile deleted file mode 100644 index 74739f740..000000000 --- a/macos/fastlane/Deliverfile +++ /dev/null @@ -1,3 +0,0 @@ -# The Deliverfile allows you to store various App Store Connect metadata -# For more information, check out the docs -# https://docs.fastlane.tools/actions/deliver/ diff --git a/macos/fastlane/Fastfile b/macos/fastlane/Fastfile deleted file mode 100644 index 664d76a4c..000000000 --- a/macos/fastlane/Fastfile +++ /dev/null @@ -1,42 +0,0 @@ -# This file contains the fastlane.tools configuration -# You can find the documentation at https://docs.fastlane.tools -# -# For a list of all available actions, check out -# -# https://docs.fastlane.tools/actions -# -# For a list of all available plugins, check out -# -# https://docs.fastlane.tools/plugins/available-plugins -# - -# Uncomment the line if you want fastlane to automatically update itself -# update_fastlane - -default_platform(:mac) - -platform :mac do - desc "Push a new release build to the App Store" - lane :release do - build_app(workspace: "Runner.xcworkspace", scheme: "Runner", - export_options: { - provisioningProfiles: { - "tech.archethic.wallet" => "Archethic Wallet Mac", - } - }, - installer_cert_name: "3rd Party Mac Developer Installer: Archethic Technologies SARL (2QGGN9QQKR)") - upload_to_app_store - end - - desc "Push a new release build to TestFlight" - lane :beta do - build_app(workspace: "Runner.xcworkspace", scheme: "Runner", - export_options: { - provisioningProfiles: { - "tech.archethic.wallet" => "Archethic Wallet Mac", - } - }, - installer_cert_name: "3rd Party Mac Developer Installer: Archethic Technologies SARL (2QGGN9QQKR)") - upload_to_testflight - end -end diff --git a/macos/fastlane/report.xml b/macos/fastlane/report.xml deleted file mode 100755 index ef3c8aa3a..000000000 --- a/macos/fastlane/report.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/macos/fastlane/screenshots/README.txt b/macos/fastlane/screenshots/README.txt deleted file mode 100644 index 8b015ec56..000000000 --- a/macos/fastlane/screenshots/README.txt +++ /dev/null @@ -1,7 +0,0 @@ -Put all screenshots you want to use inside the folder of its language (e.g. en-US). -The device type will automatically be recognized using the image resolution. Apple TV screenshots -should be stored in a subdirectory named appleTV with language folders inside of it. iMessage -screenshots, like Apple TV screenshots, should also be stored in a subdirectory named iMessage -with language folders inside of it. - -The screenshots can be named whatever you want, but keep in mind they are sorted alphabetically. diff --git a/macos/fastlane/screenshots/mac screenshot - 1.png b/macos/fastlane/screenshots/mac screenshot - 1.png deleted file mode 100644 index 3e429d13a..000000000 Binary files a/macos/fastlane/screenshots/mac screenshot - 1.png and /dev/null differ diff --git a/macos/fastlane/screenshots/mac screenshot - 2.png b/macos/fastlane/screenshots/mac screenshot - 2.png deleted file mode 100644 index d1ec00d06..000000000 Binary files a/macos/fastlane/screenshots/mac screenshot - 2.png and /dev/null differ diff --git a/macos/fastlane/screenshots/mac screenshot - 3.png b/macos/fastlane/screenshots/mac screenshot - 3.png deleted file mode 100644 index 16c940937..000000000 Binary files a/macos/fastlane/screenshots/mac screenshot - 3.png and /dev/null differ diff --git a/pubspec.lock b/pubspec.lock index 1905eb7c9..455150d3e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -100,7 +100,7 @@ packages: name: bottom_bar url: "https://pub.dartlang.org" source: hosted - version: "2.0.3" + version: "2.0.2" build: dependency: transitive description: @@ -429,7 +429,7 @@ packages: name: file_picker url: "https://pub.dartlang.org" source: hosted - version: "5.2.3" + version: "5.2.2" filesize: dependency: "direct main" description: @@ -516,7 +516,7 @@ packages: name: flutter_native_splash url: "https://pub.dartlang.org" source: hosted - version: "2.2.16" + version: "2.2.15" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -971,7 +971,7 @@ packages: name: mime url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "1.0.2" mime_dart: dependency: "direct main" description: @@ -1524,7 +1524,7 @@ packages: source: hosted version: "3.0.1" uuid: - dependency: transitive + dependency: "direct main" description: name: uuid url: "https://pub.dartlang.org" @@ -1571,7 +1571,7 @@ packages: name: win32 url: "https://pub.dartlang.org" source: hosted - version: "3.1.2" + version: "3.1.1" window_manager: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index f45f98bef..1440939cf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -212,6 +212,9 @@ dependencies: # Flutter plugin for launching a URL. Supports web, phone, SMS, and email schemes url_launcher: ^6.1.6 + # Generate UUIDs + uuid: ^3.0.7 + # String validation and sanitization for Dart validators: ^3.0.0