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