From 7ad5f5239dfed9ecbc5c21b13f19a6cdb7c0967e Mon Sep 17 00:00:00 2001 From: kev-techi Date: Thu, 6 Nov 2025 12:14:26 +0100 Subject: [PATCH 01/11] cursor rules --- .cursorrules | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 .cursorrules diff --git a/.cursorrules b/.cursorrules new file mode 100644 index 00000000..cd19ddfc --- /dev/null +++ b/.cursorrules @@ -0,0 +1,57 @@ +flutter +- cupertino widgets, no material + +documentation +- docs/ directory +- includes documentation on how the app is structured + +colors +- lib/theme +- defined as const that can be globally imported + +routing +- gorouter +- lib/routes/router +- this is where we provide state management + +modals +- lib/widgets/modals/dismissible_modal_popup.dart +- showCupertinoDialog or showCupertinoModalPopup +- state is provided around the modal directly +- kept next to relevant screen as 'name_modal.dart' + +services +- lib/services +- any package or external service that needs to be called should be defined as a service + +state +- docs/state.md +- provider +- lib/state +- favor provider over local state management +- this is where we call services, we never call services from widgets directly +- state is defined under routes or modals and therefore typically scoped +- use local state management only for self-contained widgets (animations, hiding widgets, etc...) + +screens +- lib/screens +- folder structure follows routing structure +- modals live next to their respective screen, move modals up if they are shared + +widgets +- lib/widgets +- anything that doesn't use provider + +models +- lib/models +- models related specifically to services should live with the service +- models intended for the app, should be kept in the models folder +- docs/models.md - comprehensive documentation of all app models + +db +- docs/db.md +- a service lib/services/db +- abstract class for db lib/services/db/db.dart +- abstract class for a db table lib/services/db/db.dart +- we instantiate the db and include the tables inside lib/services/db/app/db.dart +- we import and use table individually From 197781d867bdf4fe8de723e0046da44cac9b8493 Mon Sep 17 00:00:00 2001 From: kev-techi Date: Thu, 6 Nov 2025 12:14:37 +0100 Subject: [PATCH 02/11] get cursor to understand existing flows --- docs/send_flow.md | 228 +++++++++++++++++++++++++++++++++++++++ docs/tip_flow.md | 267 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 495 insertions(+) create mode 100644 docs/send_flow.md create mode 100644 docs/tip_flow.md diff --git a/docs/send_flow.md b/docs/send_flow.md new file mode 100644 index 00000000..4b3f9409 --- /dev/null +++ b/docs/send_flow.md @@ -0,0 +1,228 @@ +# Token Sending Flow + +This document describes the three-step token sending flow in the CitizenWallet app. + +## Overview + +The token sending flow consists of three main steps: +1. **Who** - Select recipient (address/profile) +2. **Amount + Description** - Enter amount and optional message +3. **Progress / Success** - Transaction processing and confirmation + +## Step 1: Who (`send_to.dart`) + +**Route:** `/wallet/:account/send` + +**Purpose:** Select the recipient for the token transfer. + +### Features +- **Search by username/address**: Text input field with real-time profile search +- **QR Code scanning**: Scan QR codes to capture recipient address +- **NFC reading**: Read NFC tags (if available and enabled) +- **Profile suggestions**: Shows matching profiles as user types +- **Address validation**: Validates Ethereum addresses and profile accounts + +### User Flow +1. User enters username, address, or scans QR/NFC +2. System searches profiles and validates address +3. User selects from suggestions or confirms entered address +4. Once valid recipient is selected, navigates to Step 2 + +### Key Components +- `SendToScreen` - Main screen widget +- `WalletLogic.updateAddress()` - Validates and updates address +- `ProfilesLogic.searchProfile()` - Searches for matching profiles +- `ScanLogic` - Handles QR code and NFC scanning + +### Navigation +```dart +navigator.push('/wallet/${walletLogic.account}/send/$toAccount') +``` + +## Step 2: Amount + Description (`send_details.dart` / `tip_details.dart`) + +**Routes:** +- `/wallet/:account/send/:to` - Standard send +- `/wallet/:account/send/:to/tip` - Tip flow (uses `tip_details.dart`) + +**Purpose:** Enter the amount to send and optional description/message. + +### Features +- **Recipient display**: Shows selected profile/address at top +- **Amount input**: Large, prominent amount field with currency symbol +- **Balance display**: Shows current balance with "MAX" button +- **Description field**: Optional multi-line text input for message +- **Validation**: Real-time validation of amount (checks balance, format) +- **Top-up option**: Shows top-up button if balance is insufficient +- **Slide to complete**: Swipe gesture to confirm and send + +### User Flow +1. Displays selected recipient (profile picture, name, address) +2. User enters amount (auto-focuses on amount field) +3. System validates amount against balance in real-time +4. User optionally enters description/message +5. User swipes "Slide to Complete" button +6. System validates all fields +7. Creates `SendTransaction` object +8. Calls `walletLogic.sendTransaction()` +9. Navigates to Step 3 + +### Key Components +- `SendDetailsScreen` / `TipDetailsScreen` - Main screen widgets +- `WalletLogic.sendTransaction()` - Initiates transaction +- `SendTransaction` model - Contains amount, to, description +- `SlideToComplete` widget - Swipe-to-confirm interaction + +### Transaction Creation +```dart +final sendTransaction = SendTransaction( + amount: walletLogic.amountController.value.text, + to: toAccount, + description: walletLogic.messageController.value.text.trim(), +); + +walletLogic.sendTransaction( + sendTransaction.amount!, + sendTransaction.to!, + message: sendTransaction.description!, +); +``` + +### Navigation +```dart +navigator.push('/wallet/${walletLogic.account}/send/$toAccount/progress') +``` + +## Step 3: Progress / Success (`send_progress.dart`) + +**Route:** `/wallet/:account/send/:to/progress` + +**Purpose:** Display transaction progress and final confirmation. + +### Features +- **Progress circle**: Animated circular progress indicator +- **Status messages**: + - "Sending..." / "Sending" (during transaction) + - "Sent! 🎉" (on success) + - "Failed to send" (on error) +- **Transaction details**: Shows amount, recipient, and timestamp +- **Auto-close**: Automatically closes after 5 seconds on success +- **Action buttons**: + - "Dismiss" button (always available) + - "Send Tip" button (if tip feature enabled) + - "Retry" button (on error) + +### User Flow +1. Screen displays immediately after Step 2 +2. Shows progress circle with 50% fill (sending state) +3. Updates to 100% when transaction is pending +4. Shows checkmark when transaction succeeds +5. Displays transaction date/time +6. Auto-closes after 5 seconds (or user can dismiss manually) +7. On error, shows retry button + +### Transaction States +- `TransactionState.sending` - Transaction being processed (50% progress) +- `TransactionState.pending` - Transaction pending on blockchain (100% progress) +- `TransactionState.success` - Transaction confirmed (checkmark, auto-close) +- `TransactionState.fail` - Transaction failed (error message, retry button) + +### Key Components +- `SendProgress` - Main screen widget +- `ProgressCircle` - Animated progress indicator +- `WalletState.inProgressTransaction` - Tracks transaction state +- `TransactionState` enum - Transaction status states + +### State Management +The screen listens to `WalletState.inProgressTransaction` to update the UI: +```dart +final inProgressTransaction = context.select( + (WalletState state) => state.inProgressTransaction, +); +``` + +## Routing Structure + +All routes are nested under `/wallet/:account/`: + +``` +/wallet/:account/send → Step 1: Send To +/wallet/:account/send/:to → Step 2: Send Details +/wallet/:account/send/:to/tip → Step 2: Tip Details (alternative) +/wallet/:account/send/:to/progress → Step 3: Progress +/wallet/:account/send/link → Step 2: Send via Link +/wallet/:account/send/link/progress → Step 3: Link Progress +``` + +## State Management + +### WalletLogic +- Manages transaction state and validation +- Handles address and amount controllers +- Executes `sendTransaction()` method +- Tracks `inProgressTransaction` state + +### ProfilesLogic +- Manages profile search and selection +- Fetches profile data for recipients +- Handles profile suggestions + +### WalletState +- `hasAddress` - Whether valid address is entered +- `hasAmount` - Whether amount is entered +- `invalidAddress` - Address validation error +- `invalidAmount` - Amount validation error +- `inProgressTransaction` - Current transaction being processed +- `inProgressTransactionError` - Transaction error state + +## Models + +### SendTransaction +```dart +class SendTransaction { + String? amount; + String? to; + String? description; + // For tips: + String? tipAmount; + String? tipTo; + String? tipDescription; +} +``` + +## Special Flows + +### Tip Flow +- Uses `tip_details.dart` instead of `send_details.dart` +- Route: `/wallet/:account/send/:to/tip` +- After successful send, can send additional tip +- Tip amount is separate from main transaction + +### Link/Voucher Flow +- Route: `/wallet/:account/send/link` +- Creates shareable voucher/link instead of direct transfer +- Uses `SendLinkProgress` for progress tracking + +### Mint Flow +- Same UI flow but mints new tokens instead of transferring +- Uses `walletLogic.mintTokens()` instead of `sendTransaction()` +- Controlled by `isMinting` flag throughout the flow + +## Error Handling + +- **Invalid address**: Shows error state in address field +- **Insufficient balance**: Shows "Insufficient funds" message +- **Transaction failure**: Shows error in progress screen with retry option +- **Network errors**: Handled by `WalletLogic` and displayed in progress screen + +## Related Files + +- `lib/screens/send/send_to.dart` - Step 1 screen +- `lib/screens/send/send_details.dart` - Step 2 screen (standard) +- `lib/screens/send/tip_details.dart` - Step 2 screen (tips) +- `lib/screens/send/send_progress.dart` - Step 3 screen +- `lib/state/wallet/logic.dart` - Transaction logic +- `lib/state/wallet/state.dart` - Wallet state management +- `lib/models/send_transaction.dart` - Transaction model +- `lib/router/router.dart` - Route definitions + diff --git a/docs/tip_flow.md b/docs/tip_flow.md new file mode 100644 index 00000000..5d9900d6 --- /dev/null +++ b/docs/tip_flow.md @@ -0,0 +1,267 @@ +# Tipping Flow + +This document describes how the tipping feature works in the CitizenWallet app. The tip flow allows users to send an additional tip to a recipient after completing a main transaction. + +## Overview + +The tipping flow is a two-step process: +1. **Main Transaction** - User sends tokens to a recipient +2. **Tip Transaction** - After the main transaction succeeds, user can optionally send an additional tip to the same recipient + +## How Tipping is Initiated + +Tipping can be initiated in two ways: + +### 1. From QR Codes / Deep Links + +When scanning a QR code or opening a deep link that includes a `tipTo` parameter, the app automatically sets up the tip flow. + +**QR Code Format:** +``` +https://app.citizenwallet.xyz/?sendto=0x123...@community&amount=100&tipTo=0x456... +``` + +**Parameters:** +- `tipTo` - The address to receive the tip (required for tip flow) +- `tipAmount` - Optional tip amount (currently not used in UI) +- `tipDescription` - Optional tip description (currently not used in UI) + +**Code Flow:** +```dart +// In WalletLogic.updateFromCapture() +if (parsedData.tip != null) { + _state.setTipTo(parsedData.tip!.to); + _state.setHasTip(true); +} +``` + +### 2. From Progress Screen + +After a successful transaction, if `hasTip` is `true` in the wallet state, the progress screen shows a "Send Tip" button instead of auto-closing. + +## State Management + +### WalletState Properties + +- `tipTo: String?` - The address that will receive the tip +- `hasTip: bool` - Whether the tip flow should be enabled + +### Setting Tip State + +```dart +// Set tip recipient +walletLogic.setTipTo(address); + +// Enable tip flow +walletLogic.setHasTip(true); + +// Clear tip state (after tip is sent) +walletLogic.setHasTip(false); +walletLogic.setTipTo(null); +``` + +## Tip Flow Sequence + +### Step 1: Main Transaction + +1. User scans QR code or opens deep link with `tipTo` parameter +2. `tipTo` is stored in `WalletState` and `hasTip` is set to `true` +3. User completes normal send flow (who → amount → progress) +4. Main transaction is sent and processed + +### Step 2: Progress Screen Behavior + +In `send_progress.dart`, when the transaction succeeds: + +```dart +if (inProgressTransaction.state == TransactionState.success) { + final hasTip = context.read().hasTip; + if (!hasTip) { + // Auto-close after 5 seconds + handleStartCloseScreenTimer(context); + } + // If hasTip is true, show "Send Tip" button instead +} +``` + +**UI Behavior:** +- If `hasTip == false`: Shows "Dismiss" button, auto-closes after 5 seconds +- If `hasTip == true`: Shows "Send Tip" button and "Dismiss" button, does NOT auto-close + +### Step 3: Tip Details Screen + +When user clicks "Send Tip" button: + +**Route:** `/wallet/:account/send/:to/tip` + +**Screen:** `TipDetailsScreen` (`lib/screens/send/tip_details.dart`) + +**Initialization:** +```dart +@override +void initState() { + super.initState(); + final tipTo = context.read().tipTo; + + if (tipTo != null) { + // Load profile for tip recipient + widget.profilesLogic.getProfile(tipTo).then((profile) { + if (profile != null) { + widget.profilesLogic.selectProfile(profile); + } + }); + } +} + +@override +void didChangeDependencies() { + super.didChangeDependencies(); + final tipTo = context.read().tipTo; + if (tipTo != null) { + widget.walletLogic.setHasTip(true); + widget.walletLogic.setHasAddress(true); + } +} +``` + +**Key Differences from Regular Send:** +- Title shows "Send Tip" instead of "Send" +- Uses `tipTo` from state as the recipient (not from address controller) +- Creates `SendTransaction` with `tipAmount`, `tipTo`, and `tipDescription` +- After sending, clears tip state + +### Step 4: Sending the Tip + +When user swipes to complete the tip: + +```dart +void handleSend(BuildContext context, String? selectedAddress, String? tipTo) { + // Validate tipTo is present + if (tipTo == null) { + return; + } + + // Create tip transaction + final sendTip = SendTransaction( + tipAmount: walletLogic.amountController.value.text, + tipTo: tipTo, + tipDescription: walletLogic.messageController.value.text.trim(), + ); + + // Send the tip transaction + walletLogic.sendTransaction( + sendTip.tipAmount!, + sendTip.tipTo!, + message: sendTip.tipDescription!, + ); + + // Clear tip state + widget.walletLogic.setHasTip(false); + widget.walletLogic.setHasAddress(false); + widget.walletLogic.setTipTo(null); + + // Navigate to progress screen + navigator.push('/wallet/${walletLogic.account}/send/$toAccount/progress'); +} +``` + +### Step 5: Tip Progress + +The tip transaction goes through the same progress screen as the main transaction. After the tip succeeds, `hasTip` is now `false`, so the screen will auto-close after 5 seconds. + +## SendTransaction Model + +The `SendTransaction` model supports both regular transactions and tips: + +```dart +class SendTransaction { + // Regular transaction fields + String? to; + String? amount; + String? description; + + // Tip transaction fields + String? tipTo; + String? tipAmount; + String? tipDescription; +} +``` + +## QR Code Parsing + +When parsing QR codes, tip information is extracted: + +```dart +// In lib/utils/qr.dart +ParsedQRData parseSendtoUrl(String raw) { + final tipToParam = receiveUrl.queryParameters['tipTo']; + final tipAmountParam = receiveUrl.queryParameters['tipAmount']; + final tipDescriptionParam = receiveUrl.queryParameters['tipDescription']; + + final tip = tipToParam != null + ? SendDestination( + to: tipToParam, + amount: tipAmountParam, + description: tipDescriptionParam, + ) + : null; + + return ParsedQRData( + address: address, + amount: amountParam, + description: descriptionParam, + tip: tip, // Tip info included in parsed data + ); +} +``` + +## Deep Link Support + +Deep links can include tip information: + +**URL Format:** +``` +https://app.citizenwallet.xyz/?sendto=0x123...@community&amount=100&tipTo=0x456... +``` + +**Router Parsing:** +```dart +// In lib/router/router.dart +final tipTo = uri.queryParameters['tipTo']; +if (sendTo != null) { + String params = 'sendto=$sendTo'; + if (tipTo != null) { + params += '&tipTo=$tipTo'; + } + // ... other params +} +``` + +## Key Files + +- `lib/screens/send/tip_details.dart` - Tip entry screen +- `lib/screens/send/send_progress.dart` - Progress screen with tip button +- `lib/state/wallet/state.dart` - `tipTo` and `hasTip` state +- `lib/state/wallet/logic.dart` - Tip state management methods +- `lib/models/send_transaction.dart` - Transaction model with tip fields +- `lib/utils/qr.dart` - QR code parsing with tip support +- `lib/router/router.dart` - Deep link routing with tip parameter + +## User Experience Flow + +1. **Scan QR with tip**: User scans QR code containing `tipTo` parameter +2. **Send main transaction**: User completes normal send flow +3. **Transaction succeeds**: Progress screen shows "Send Tip" button (instead of auto-closing) +4. **Enter tip**: User clicks "Send Tip", enters tip amount and description +5. **Send tip**: User swipes to send tip transaction +6. **Tip succeeds**: Progress screen shows success, auto-closes after 5 seconds + +## Important Notes + +- The tip is a **separate transaction** from the main transaction +- Tip recipient (`tipTo`) is set from QR/deep link, not from user input +- After sending the tip, the tip state is cleared +- If `hasTip` is `true`, the progress screen does NOT auto-close, allowing user to send tip +- The tip flow uses the same transaction infrastructure as regular sends +- Tip amount and description are entered by the user in `TipDetailsScreen` + From 93ff518110447d72fa4c25e99b3803a3b38487d1 Mon Sep 17 00:00:00 2001 From: kev-techi Date: Thu, 6 Nov 2025 12:14:50 +0100 Subject: [PATCH 03/11] update community files --- assets/config/v4/communities.json | 30 ++++++++----- assets/config/v4/communities.test.json | 60 +++++++++++++++----------- 2 files changed, 55 insertions(+), 35 deletions(-) diff --git a/assets/config/v4/communities.json b/assets/config/v4/communities.json index 7d61b4b6..78462e60 100644 --- a/assets/config/v4/communities.json +++ b/assets/config/v4/communities.json @@ -305,6 +305,13 @@ "url": "https://ipfs.internal.citizenwallet.xyz" }, "plugins": [ + { + "name": "Merchants Map", + "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/sfluv.svg", + "url": "https://app.sfluv.org/map", + "launch_mode": "webview", + "featured": true + }, { "name": "About", "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/sfluv.svg", @@ -328,6 +335,7 @@ "theme": { "primary": "#eb6c6c" }, + "hidden": true, "profile": { "address": "0x05e2Fb34b4548990F96B3ba422eA3EF49D5dAa99", "chain_id": 137 @@ -462,6 +470,7 @@ "theme": { "primary": "#009393" }, + "hidden": true, "profile": { "address": "0x898C2737f2Cb52622711A89D85A1D5E0B881BDeA", "chain_id": 137 @@ -515,11 +524,11 @@ }, { "community": { - "name": "Breadchain Community Token", - "description": "BREAD is a digital community token and solidarity primitive developed by Breadchain Cooperative that generates yield for post-capitalist organizations", - "url": "https://breadchain.xyz/", + "name": "Bread Cooperative", + "description": "Financial tools today. Solidarity forever.", + "url": "https://bread.coop", "alias": "bread", - "logo": "https://assets.citizenwallet.xyz/wallet-config/_images/bread.svg", + "logo": "https://assets.citizenwallet.xyz/wallet-config/_images/bread2.png", "profile": { "address": "0x6b3a1f4277391526413F583c23D5B9EF4d2fE986", "chain_id": 100 @@ -536,7 +545,7 @@ "tokens": { "100:0xa555d5344f6fb6c65da19e403cb4c1ec4a1a5ee3": { "standard": "erc20", - "name": "Breadchain Community Token", + "name": "Bread Cooperative Community Token", "address": "0xa555d5344f6fb6c65da19e403cb4c1ec4a1a5ee3", "symbol": "BREAD", "decimals": 18, @@ -578,14 +587,14 @@ "plugins": [ { "name": "Market", - "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/bread.svg", + "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/bread2.png", "url": "https://marketplace.citizenwallet.xyz/bread", "launch_mode": "webview", "signature": true }, { "name": "Breadcon 2025 Faucet", - "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/bread.svg", + "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/bread2.png", "url": "https://faucet.citizenwallet.xyz/breadcon2025", "launch_mode": "webview", "signature": true, @@ -593,7 +602,7 @@ }, { "name": "Japan Demo", - "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/bread.svg", + "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/bread2.png", "url": "https://hackatsuonvoting.vercel.app", "launch_mode": "webview", "signature": true, @@ -601,7 +610,7 @@ }, { "name": "Japan Demo", - "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/bread.svg", + "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/bread2.png", "url": "https://grow-kesennuma.vercel.app", "launch_mode": "webview", "signature": true, @@ -609,7 +618,7 @@ }, { "name": "Mutual Vend", - "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/bread.svg", + "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/bread2.png", "url": "https://mutual-vend.com", "launch_mode": "webview", "signature": true, @@ -714,6 +723,7 @@ "theme": { "primary": "#ff4c02" }, + "hidden": true, "profile": { "address": "0x13Dd4B3cD2f2Be3eb41cD0C3E2ce9F8d8C93A451", "chain_id": 8453 diff --git a/assets/config/v4/communities.test.json b/assets/config/v4/communities.test.json index f61f3b08..ab671e0c 100644 --- a/assets/config/v4/communities.test.json +++ b/assets/config/v4/communities.test.json @@ -305,6 +305,13 @@ "url": "https://ipfs.internal.citizenwallet.xyz" }, "plugins": [ + { + "name": "Merchants Map", + "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/sfluv.svg", + "url": "https://app.sfluv.org/map", + "launch_mode": "webview", + "featured": true + }, { "name": "About", "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/sfluv.svg", @@ -328,6 +335,7 @@ "theme": { "primary": "#eb6c6c" }, + "hidden": true, "profile": { "address": "0x05e2Fb34b4548990F96B3ba422eA3EF49D5dAa99", "chain_id": 137 @@ -462,6 +470,7 @@ "theme": { "primary": "#009393" }, + "hidden": true, "profile": { "address": "0x898C2737f2Cb52622711A89D85A1D5E0B881BDeA", "chain_id": 137 @@ -515,11 +524,11 @@ }, { "community": { - "name": "Breadchain Community Token", - "description": "BREAD is a digital community token and solidarity primitive developed by Breadchain Cooperative that generates yield for post-capitalist organizations", - "url": "https://breadchain.xyz/", + "name": "Bread Cooperative", + "description": "Financial tools today. Solidarity forever.", + "url": "https://bread.coop", "alias": "bread", - "logo": "https://assets.citizenwallet.xyz/wallet-config/_images/bread.svg", + "logo": "https://assets.citizenwallet.xyz/wallet-config/_images/bread2.png", "profile": { "address": "0x6b3a1f4277391526413F583c23D5B9EF4d2fE986", "chain_id": 100 @@ -536,7 +545,7 @@ "tokens": { "100:0xa555d5344f6fb6c65da19e403cb4c1ec4a1a5ee3": { "standard": "erc20", - "name": "Breadchain Community Token", + "name": "Bread Cooperative Community Token", "address": "0xa555d5344f6fb6c65da19e403cb4c1ec4a1a5ee3", "symbol": "BREAD", "decimals": 18, @@ -578,42 +587,42 @@ "plugins": [ { "name": "Market", - "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/bread.svg", + "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/bread2.png", "url": "https://marketplace.citizenwallet.xyz/bread", "launch_mode": "webview", "signature": true }, { "name": "Breadcon 2025 Faucet", - "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/bread.svg", + "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/bread2.png", "url": "https://faucet.citizenwallet.xyz/breadcon2025", "launch_mode": "webview", "signature": true, "hidden": true }, { - "name": "Japan Demo", - "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/bread.svg", - "url": "https://hackatsuonvoting.vercel.app", - "launch_mode": "webview", - "signature": true, - "hidden": true + "name": "Japan Demo", + "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/bread2.png", + "url": "https://hackatsuonvoting.vercel.app", + "launch_mode": "webview", + "signature": true, + "hidden": true }, { - "name": "Japan Demo", - "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/bread.svg", - "url": "https://grow-kesennuma.vercel.app", - "launch_mode": "webview", - "signature": true, - "hidden": true + "name": "Japan Demo", + "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/bread2.png", + "url": "https://grow-kesennuma.vercel.app", + "launch_mode": "webview", + "signature": true, + "hidden": true }, { - "name": "Mutual Vend", - "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/bread.svg", - "url": "https://mutual-vend.com", - "launch_mode": "webview", - "signature": true, - "hidden": true + "name": "Mutual Vend", + "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/bread2.png", + "url": "https://mutual-vend.com", + "launch_mode": "webview", + "signature": true, + "hidden": true } ], "config_location": "https://config.internal.citizenwallet.xyz/v4/bread.citizenwallet.xyz.json", @@ -714,6 +723,7 @@ "theme": { "primary": "#ff4c02" }, + "hidden": true, "profile": { "address": "0x13Dd4B3cD2f2Be3eb41cD0C3E2ce9F8d8C93A451", "chain_id": 8453 From c99e2b1d264a450ae1f0b7a3ba9d8ffc7bc12600 Mon Sep 17 00:00:00 2001 From: kev-techi Date: Thu, 6 Nov 2025 12:14:56 +0100 Subject: [PATCH 04/11] bump --- ios/Podfile.lock | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index c30fca89..b35942fc 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -265,43 +265,43 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: AppAuth: d4f13a8fe0baf391b2108511793e4b479691fb73 AppCheckCore: cc8fd0a3a230ddd401f326489c99990b013f0c4f - audioplayers_darwin: ccf9c770ee768abb07e26d90af093f7bab1c12ab - connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd - credential_manager: e32c96e222e95368067810f7529eea06d9c5c171 + audioplayers_darwin: 877d9a4d06331c5c374595e46e16453ac7eafa40 + connectivity_plus: 2a701ffec2c0ae28a48cf7540e279787e77c447d + credential_manager: feb21034894e469e3686461dc96fb24bb7d350e4 DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 - file_picker: 6d4bf27b7318804adf4ddc1827dbc5cd4b78042d + file_picker: be9a674155d9f334323856cb266e0d145d75d5c0 Firebase: 1fe1c0a7d9aaea32efe01fbea5f0ebd8d70e53a2 - firebase_core: ba71b44041571da878cb624ce0d80250bcbe58ad - firebase_messaging: 13129fe2ca166d1ed2d095062d76cee88943d067 + firebase_core: 3c2f323cae65c97a636a05a23b17730ef93df2cf + firebase_messaging: 456e01ff29a451c90097d0b45925551d5be0c143 FirebaseCore: 8344daef5e2661eb004b177488d6f9f0f24251b7 FirebaseCoreInternal: ef4505d2afb1d0ebbc33162cb3795382904b5679 FirebaseInstallations: 9980995bdd06ec8081dfb6ab364162bdd64245c3 FirebaseMessaging: 2b9f56aa4ed286e1f0ce2ee1d413aabb8f9f5cb9 Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 - flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99 - flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13 - google_sign_in_ios: b48bb9af78576358a168361173155596c845f0b9 + flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4 + flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12 + google_sign_in_ios: 7411fab6948df90490dc4620ecbcabdc3ca04017 GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 GoogleSignIn: ce8c89bb9b37fb624b92e7514cc67335d1e277e4 GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 GTMAppAuth: f69bd07d68cd3b766125f7e072c45d7340dea0de GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6 - icloud_storage: e55639f0c0d7cb2b0ba9c0b3d5968ccca9cd9aa2 - mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93 + icloud_storage: d9ac7a33ced81df08ba7ea1bf3099cc0ee58f60a + mobile_scanner: 77265f3dc8d580810e91849d4a0811a90467ed5e nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 - nfc_manager: 9c40fe22528ab871ca11e52ea8b95790e9d92ca2 + nfc_manager: d7da7cb781f7744b94df5fe9dbca904ac4a0939e OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94 - package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 - path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 - reown_yttrium: cee334ade64725b1d83f7b34c706a6aae2696d58 + reown_yttrium: c0e87e5965fa60a3559564cc35cffbba22976089 SDWebImage: 9f177d83116802728e122410fb25ad88f5c7608a - share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a - shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 - sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 + share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f + shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 + sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 - url_launcher_ios: 694010445543906933d732453a59da0a173ae33d + url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe YttriumWrapper: 31e937fe9fbe0f1314d2ca6be9ce9b379a059966 PODFILE CHECKSUM: f90b7b7d52ec0d905039aa6f51266424548151c7 From 2d9f8730e67dbd4745b2eee9252759da40fef02c Mon Sep 17 00:00:00 2001 From: kev-techi Date: Thu, 6 Nov 2025 12:15:04 +0100 Subject: [PATCH 05/11] fix tipping --- lib/screens/send/tip_details.dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/screens/send/tip_details.dart b/lib/screens/send/tip_details.dart index 7ed89116..163ccd9a 100644 --- a/lib/screens/send/tip_details.dart +++ b/lib/screens/send/tip_details.dart @@ -67,6 +67,10 @@ class _TipDetailsScreenState extends State { super.initState(); _sendTransaction = widget.sendTransaction ?? SendTransaction(); + // Clear amount controller when tip screen initializes + // to ensure hasAmount state is reset + widget.walletLogic.clearAmountController(); + WidgetsBinding.instance.addPostFrameCallback((_) { final walletLogic = widget.walletLogic; final tipTo = context.read().tipTo; @@ -95,6 +99,8 @@ class _TipDetailsScreenState extends State { if (tipTo != null) { widget.walletLogic.setHasTip(true); widget.walletLogic.setHasAddress(true); + // Reset amount state since tip screen starts with empty amount + widget.walletLogic.updateAmount(unlimited: widget.isMinting); } } From f4501dcccb25910d45334d5ab15f2ea875e2b38c Mon Sep 17 00:00:00 2001 From: kev-techi Date: Thu, 6 Nov 2025 12:24:55 +0100 Subject: [PATCH 06/11] fix issue with pending transaction not being cleared --- lib/screens/send/tip_details.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/screens/send/tip_details.dart b/lib/screens/send/tip_details.dart index 163ccd9a..a1feea4f 100644 --- a/lib/screens/send/tip_details.dart +++ b/lib/screens/send/tip_details.dart @@ -297,6 +297,7 @@ class _TipDetailsScreenState extends State { }); if (sent == true) { + walletLogic.clearInProgressTransaction(); walletLogic.clearInputControllers(); walletLogic.resetInputErrorState(); widget.profilesLogic.clearSearch(); From b67a545b4d8815d929361ae6b7fa5726cfa45546 Mon Sep 17 00:00:00 2001 From: kev-techi Date: Thu, 6 Nov 2025 12:56:25 +0100 Subject: [PATCH 07/11] feature some other plugins --- assets/config/v4/communities.json | 17 ++++++++++++----- assets/config/v4/communities.test.json | 17 ++++++++++++----- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/assets/config/v4/communities.json b/assets/config/v4/communities.json index 78462e60..1e3f8688 100644 --- a/assets/config/v4/communities.json +++ b/assets/config/v4/communities.json @@ -159,7 +159,8 @@ "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/wallet.pay.brussels.png", "url": "https://checkout.pay.brussels/topup", "action": "topup", - "signature": true + "signature": true, + "featured": true } ], "config_location": "https://wallet.pay.brussels/config/community.json", @@ -306,11 +307,12 @@ }, "plugins": [ { - "name": "Merchants Map", + "name": "Map", "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/sfluv.svg", "url": "https://app.sfluv.org/map", "launch_mode": "webview", - "featured": true + "featured": true, + "signature": true }, { "name": "About", @@ -529,6 +531,9 @@ "url": "https://bread.coop", "alias": "bread", "logo": "https://assets.citizenwallet.xyz/wallet-config/_images/bread2.png", + "theme": { + "primary": "#ea6023" + }, "profile": { "address": "0x6b3a1f4277391526413F583c23D5B9EF4d2fE986", "chain_id": 100 @@ -590,7 +595,8 @@ "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/bread2.png", "url": "https://marketplace.citizenwallet.xyz/bread", "launch_mode": "webview", - "signature": true + "signature": true, + "featured": true }, { "name": "Breadcon 2025 Faucet", @@ -961,7 +967,8 @@ "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/myrt.png", "url": "https://marketplace.citizenwallet.xyz/seldesalm", "launch_mode": "webview", - "signature": true + "signature": true, + "featured": true } ], "config_location": "https://seldesalm.citizenwallet.xyz/config/community.json", diff --git a/assets/config/v4/communities.test.json b/assets/config/v4/communities.test.json index ab671e0c..e3d83def 100644 --- a/assets/config/v4/communities.test.json +++ b/assets/config/v4/communities.test.json @@ -159,7 +159,8 @@ "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/wallet.pay.brussels.png", "url": "https://checkout.pay.brussels/topup", "action": "topup", - "signature": true + "signature": true, + "featured": true } ], "config_location": "https://wallet.pay.brussels/config/community.json", @@ -306,11 +307,12 @@ }, "plugins": [ { - "name": "Merchants Map", + "name": "Map", "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/sfluv.svg", "url": "https://app.sfluv.org/map", "launch_mode": "webview", - "featured": true + "featured": true, + "signature": true }, { "name": "About", @@ -529,6 +531,9 @@ "url": "https://bread.coop", "alias": "bread", "logo": "https://assets.citizenwallet.xyz/wallet-config/_images/bread2.png", + "theme": { + "primary": "#ea6023" + }, "profile": { "address": "0x6b3a1f4277391526413F583c23D5B9EF4d2fE986", "chain_id": 100 @@ -590,7 +595,8 @@ "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/bread2.png", "url": "https://marketplace.citizenwallet.xyz/bread", "launch_mode": "webview", - "signature": true + "signature": true, + "featured": true }, { "name": "Breadcon 2025 Faucet", @@ -961,7 +967,8 @@ "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/myrt.png", "url": "https://marketplace.citizenwallet.xyz/seldesalm", "launch_mode": "webview", - "signature": true + "signature": true, + "featured": true } ], "config_location": "https://seldesalm.citizenwallet.xyz/config/community.json", From 401e43b585cb1153a2242ade6478a21c0843ab4b Mon Sep 17 00:00:00 2001 From: kev-techi Date: Thu, 6 Nov 2025 12:56:33 +0100 Subject: [PATCH 08/11] document wallet screen --- docs/wallet_screen.md | 416 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 416 insertions(+) create mode 100644 docs/wallet_screen.md diff --git a/docs/wallet_screen.md b/docs/wallet_screen.md new file mode 100644 index 00000000..77f78955 --- /dev/null +++ b/docs/wallet_screen.md @@ -0,0 +1,416 @@ +# Wallet Screen + +This document describes the main wallet screen (`lib/screens/wallet/screen.dart`), which serves as the primary interface for viewing wallet balance, transactions, and performing wallet actions. + +## Overview + +The wallet screen is the central hub of the CitizenWallet app. It displays: +- Wallet balance and currency information +- User profile (avatar, username) +- Transaction history +- Action buttons (Send, Receive, Plugins, etc.) +- QR code for receiving funds (when balance is zero and no transactions) +- Account switching and WalletConnect session management + +## Architecture + +### Main Components + +1. **`WalletScreen`** (`screen.dart`) + - Main stateful widget that orchestrates the entire screen + - Manages lifecycle, navigation, and state coordination + - Handles deep linking, QR scanning, and user interactions + +2. **`WalletScrollView`** (`wallet_scroll_view.dart`) + - Custom scrollable view using `CustomScrollView` with slivers + - Implements pull-to-refresh functionality + - Contains persistent header with wallet actions + - Renders transaction list and empty states + +3. **`WalletActions`** (`wallet_actions.dart`) + - Persistent header component that shrinks on scroll + - Displays profile avatar, username, balance, and action buttons + - Animates based on scroll position using `progressiveClamp` + +4. **`TransactionRow`** (`transaction_row.dart`) + - Individual transaction list item + - Shows transaction details: sender/receiver, amount, date, status + - Handles profile loading and voucher display + +5. **`MoreActionsSheet`** (`more_actions_sheet.dart`) + - Bottom sheet modal for additional actions + - Displays plugins, vouchers, and minting options + +### State Management + +The wallet screen uses Provider for state management: + +- **`WalletState`** - Wallet data, balance, transactions, loading states +- **`ProfileState`** - Current user's profile information +- **`ProfilesState`** - Cached profiles for transaction participants +- **`VoucherState`** - Voucher data and loading states +- **`WalletConnectState`** - WalletConnect session management + +### Logic Classes + +- **`WalletLogic`** - Core wallet operations (loading, transactions, sending) +- **`ProfileLogic`** - Profile management and loading +- **`ProfilesLogic`** - Profile search and caching +- **`VoucherLogic`** - Voucher operations +- **`WalletKitLogic`** - WalletConnect integration + +## Key Features + +### 1. Wallet Loading + +When the screen initializes (`onLoad()`): + +```dart +await _logic.openWallet( + _address!, + _alias!, + (bool hasChanged) async { + _logic.requestWalletActions(); + await _logic.loadTransactions(); + _profileLogic.loadProfile(online: online); + _voucherLogic.fetchVouchers(); + await _profileLogic.loadProfileLink(); + await _logic.evaluateWalletActions(); + }, +); +``` + +**Process:** +1. Opens wallet with address and alias +2. Requests available wallet actions (plugins, vouchers, etc.) +3. Loads transaction history +4. Loads user profile +5. Fetches vouchers +6. Generates profile link for QR code +7. Evaluates which action buttons to show + +### 2. Scroll Behavior + +The screen uses a `CustomScrollView` with: +- **Persistent Header**: `WalletActions` component that shrinks from 420px to 300px +- **Pull-to-Refresh**: Refreshes transactions and balance +- **Infinite Scroll**: Loads more transactions when scrolling near bottom +- **Scroll-to-Top**: Tapping header scrolls to top + +**Scroll Detection:** +```dart +void onScrollUpdate() { + if (_scrollController.position.pixels >= + _scrollController.position.maxScrollExtent - 300) { + _logic.loadAdditionalTransactions(10); + } +} +``` + +### 3. Action Buttons + +The wallet displays dynamic action buttons based on configuration: + +**Primary Actions (Always Visible):** +- **Send** - Navigate to send screen +- **Receive** - Navigate to receive screen + +**Dynamic Actions (Context-Dependent):** +- **More** - Shows additional actions in bottom sheet +- **Vouchers** - If vouchers are available +- **Mint** - If user has minter role +- **Plugins** - If plugins are configured (shows first plugin or "More") + +**Button State:** +- Disabled during loading, first load, or when sending +- Shows loading state during transaction sending +- Dynamically sized based on scroll position + +### 4. Transaction Display + +**Transaction States:** +- **Queued** - Failed transactions that can be retried/edited/deleted +- **In Progress** - Currently processing transactions +- **Completed** - Confirmed transactions +- **Failed** - Transactions that failed with error messages + +**Transaction List:** +- Shows queued transactions first (if any) +- Displays in-progress transaction if relevant +- Lists completed transactions chronologically +- Loads more transactions on scroll (pagination) + +**Transaction Row Features:** +- Profile avatars for known addresses +- Voucher indicators for voucher transactions +- Amount with currency logo +- Relative time (< 7 days) or formatted date +- Error messages for failed transactions +- Tap to view transaction details + +### 5. QR Code Display + +When wallet balance is zero and no transactions exist, shows QR code: + +**Two Modes:** +1. **Citizen Wallet** (default) - Profile link QR code +2. **External Wallet** - Wallet address QR code + +**Profile Link:** +- Generated via `ProfileLogic.loadProfileLink()` +- Links to wallet profile for easy sharing +- Includes community alias and wallet address + +### 6. Deep Linking & QR Scanning + +The wallet screen handles multiple deep link types: + +**QR Code Formats:** +- **WalletConnect** (`wc:...`) - WalletConnect pairing +- **Plugin** - Plugin URLs +- **Standard URL** - Web URLs with plugin parameters +- **Send/Receive** - Transaction parameters +- **Voucher** - Voucher redemption +- **Deep Link** - Custom deep link actions + +**QR Scan Flow:** +```dart +void handleQRScan() async { + final result = await showCupertinoModalPopup( + context: context, + builder: (_) => const ScannerModal(), + ); + + // Parse and handle QR result + // - WalletConnect pairing + // - Plugin loading + // - Send/receive navigation + // - Voucher handling +} +``` + +**Deep Link Handling:** +- `plugin` - Opens plugin in webview +- `onboarding` - Onboarding flow +- Custom deep links - Navigate to specific screens + +### 7. Account Switching + +Users can switch between multiple accounts: + +```dart +void handleOpenAccountSwitcher() async { + final (address, alias) = await navigator.push('/wallet/$_address/accounts'); + + if (address != _address || alias != _alias) { + // Reset state + _profileLogic.resetAll(); + _address = address; + _alias = alias; + navigator.replace('/wallet/$address?alias=$alias'); + } +} +``` + +**Process:** +1. Opens account selection modal +2. User selects different account +3. Resets all state (profile, vouchers, etc.) +4. Loads new wallet +5. Updates route + +### 8. WalletConnect Integration + +The screen manages WalletConnect sessions: + +**Features:** +- Shows connection indicator when active sessions exist +- Displays disconnect modal for session management +- Handles WalletConnect QR codes +- Integrates with `WalletKitLogic` + +**Session Management:** +```dart +void handleDisconnect() async { + await showCupertinoModalPopup( + context: context, + builder: (context) => WalletConnectSessionsModal( + onDisconnect: (topic) async { + await _walletKitLogic.disconnectSession(topic: topic); + }, + ), + ); +} +``` + +### 9. Failed Transaction Handling + +When transactions fail, users can: + +**Options:** +- **Retry** - Resubmit the transaction +- **Edit** - Modify transaction parameters +- **Delete** - Remove from queue + +**Flow:** +```dart +void handleFailedTransaction(String id, bool blockSending) async { + final option = await showCupertinoModalPopup( + context: context, + builder: (context) => CupertinoActionSheet( + actions: [ + CupertinoActionSheetAction( + onPressed: () => Navigator.pop('retry'), + child: Text('Retry'), + ), + // ... edit, delete options + ], + ), + ); + + if (option == 'retry') { + _logic.retryTransaction(id); + } else if (option == 'edit') { + _logic.prepareEditQueuedTransaction(id); + navigator.push('/wallet/$_address/send/$addr'); + } else if (option == 'delete') { + _logic.removeQueuedTransaction(id); + } +} +``` + +### 10. Plugin Integration + +Plugins can be launched from the wallet screen: + +**Plugin Launch Modes:** +- **Webview** - Opens in modal webview +- **Connected Webview** - Webview with wallet integration +- **External** - Opens in external browser/app + +**Connected Webview:** +- Allows plugins to request transactions +- Handles `sendto` and `calldata` URL formats +- Shows transaction confirmation modals +- Redirects back to plugin after transaction + +## User Interactions + +### Navigation Flows + +1. **Send Flow:** + ``` + Wallet Screen → Send Screen → Send Details → Transaction Processing + ``` + +2. **Receive Flow:** + ``` + Wallet Screen → Receive Screen (QR code display) + ``` + +3. **Transaction Details:** + ``` + Wallet Screen → Transaction Details Screen + ``` + +4. **Profile Edit:** + ``` + Wallet Screen → Profile Edit Modal + ``` + +5. **Vouchers:** + ``` + Wallet Screen → Vouchers Screen + ``` + +6. **Mint:** + ``` + Wallet Screen → Mint Screen + ``` + +### Gestures + +- **Pull-to-Refresh** - Refreshes transactions and balance +- **Tap Header** - Scrolls to top +- **Tap Transaction** - Opens transaction details +- **Tap Profile Avatar** - Opens profile edit +- **Long Press** - (Future: Copy address, etc.) + +## State Lifecycle + +### Initialization + +1. `initState()` - Sets up observers, controllers, logic instances +2. `onLoad()` - Loads wallet, transactions, profile +3. `WidgetsBindingObserver` - Handles app lifecycle changes + +### Updates + +- `didUpdateWidget()` - Handles route parameter changes +- State changes trigger rebuilds via Provider +- Scroll position updates trigger header animations + +### Cleanup + +- `dispose()` - Removes observers, controllers, pauses fetching +- Logic instances handle their own cleanup + +## Performance Considerations + +### Optimizations + +1. **Lazy Loading:** + - Transactions loaded in batches + - Profiles loaded on-demand + - Images cached via profile service + +2. **State Selectors:** + - Uses `context.select()` for granular rebuilds + - Only rebuilds components when relevant state changes + +3. **Scroll Performance:** + - Uses `SliverList` for efficient scrolling + - Transactions rendered on-demand + - Profile images loaded asynchronously + +4. **Background Operations:** + - Transaction fetching paused when screen not visible + - Profile loading paused during navigation + - Event service manages connection lifecycle + +## Error Handling + +### Network Errors + +- Shows offline banner when disconnected +- Retries failed transactions +- Caches data for offline viewing + +### Transaction Errors + +- Displays error messages in transaction rows +- Allows retry/edit/delete for failed transactions +- Shows toast notifications for critical errors + +### Loading Errors + +- Graceful degradation when data unavailable +- Loading indicators during async operations +- Error states with retry options + +## Configuration + +The wallet screen behavior is controlled by: + +- **Community Config** - Defines available plugins, actions +- **Wallet State** - Current wallet, balance, transactions +- **User Permissions** - Minter role, plugin access +- **Network Status** - Online/offline state + +## Related Documentation + +- [Send Flow](./send_flow.md) - Token sending process +- [Tip Flow](./tip_flow.md) - Tipping functionality +- [State Management](../docs/state.md) - State management patterns +- [Models](../docs/models.md) - Data models + From 5cd8e5f455e49f5ea10eba643394c2f360c5d8ff Mon Sep 17 00:00:00 2001 From: kev-techi Date: Thu, 6 Nov 2025 12:56:41 +0100 Subject: [PATCH 09/11] display featured button --- lib/screens/wallet/more_actions_sheet.dart | 13 +- lib/screens/wallet/wallet_actions.dart | 293 +++++++++++++-------- lib/services/config/config.dart | 6 +- lib/state/wallet/selectors.dart | 66 +++++ lib/widgets/wallet/action_button.dart | 33 ++- 5 files changed, 294 insertions(+), 117 deletions(-) diff --git a/lib/screens/wallet/more_actions_sheet.dart b/lib/screens/wallet/more_actions_sheet.dart index 64302947..082aec18 100644 --- a/lib/screens/wallet/more_actions_sheet.dart +++ b/lib/screens/wallet/more_actions_sheet.dart @@ -4,7 +4,6 @@ import 'package:citizenwallet/state/wallet/state.dart'; import 'package:citizenwallet/theme/provider.dart'; import 'package:citizenwallet/widgets/coin_logo.dart'; import 'package:flutter/cupertino.dart'; -import 'package:flutter_svg/svg.dart'; import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; import 'package:citizenwallet/l10n/app_localizations.dart'; @@ -26,7 +25,17 @@ class MoreActionsSheet extends StatelessWidget { @override Widget build(BuildContext context) { final plugins = context.select(selectVisiblePlugins); + final featuredPlugins = context.select(selectFeaturedPlugins); final actions = context.select((WalletState state) => state.walletActions); + + // Get the featured plugin that's displayed as a button (first featured plugin) + final displayedFeaturedPlugin = featuredPlugins.isNotEmpty ? featuredPlugins.first : null; + + // Filter out the displayed featured plugin from the plugins list + // Compare by URL since it's unique for each plugin + final pluginsToShow = displayedFeaturedPlugin != null + ? plugins.where((plugin) => plugin.url != displayedFeaturedPlugin.url).toList() + : plugins; final navigator = GoRouter.of(context); @@ -82,7 +91,7 @@ class MoreActionsSheet extends StatelessWidget { ) ]; case ActionButtonType.plugins: - return plugins.map((plugin) { + return pluginsToShow.map((plugin) { return _buildSheetItem( context, plugin.name, diff --git a/lib/screens/wallet/wallet_actions.dart b/lib/screens/wallet/wallet_actions.dart index 77d743ac..12ae17d3 100644 --- a/lib/screens/wallet/wallet_actions.dart +++ b/lib/screens/wallet/wallet_actions.dart @@ -62,6 +62,8 @@ class _WalletActionsState extends State { final showActionButton = !walletActionsLoading && isWalletReady; final actionButton = context.select(selectActionButtonToShow); final plugins = context.select(selectVisiblePlugins); + final featuredPlugins = context.select(selectFeaturedPlugins); + final featuredPlugin = featuredPlugins.isNotEmpty ? featuredPlugins.first : null; final onePlugin = plugins.isNotEmpty ? plugins.first : null; final imageSmall = context.select((ProfileState state) => state.imageSmall); @@ -103,18 +105,41 @@ class _WalletActionsState extends State { final profileCircleSize = progressiveClamp(2, 65, widget.shrink); const coinNameSize = 20.0; - final buttonSeparator = (1 - widget.shrink) < 0.9 + // Calculate number of buttons to determine sizing + int buttonCount = 2; // Send and Receive are always present + if (showActionButton && featuredPlugin != null) { + buttonCount++; + } + if (showActionButton && actionButton != null) { + buttonCount++; + } + + // Adjust button size and spacing based on button count + final baseButtonSeparator = (1 - widget.shrink) < 0.9 ? 10.0 : progressiveClamp(10, 40, widget.shrink); - + final buttonSeparator = buttonCount > 3 + ? (baseButtonSeparator * 0.5).clamp(5.0, 20.0) + : baseButtonSeparator; + final buttonBarHeight = (1 - widget.shrink) < 0.7 ? 60.0 : progressiveClamp(40, 120, widget.shrink); - final buttonSize = (1 - widget.shrink) < 0.9 ? 60.0 : 80.0; - final buttonIconSize = (1 - widget.shrink) < 0.9 ? 20.0 : 40.0; + + // Reduce button size when there are more buttons + final baseButtonSize = (1 - widget.shrink) < 0.9 ? 60.0 : 80.0; + final buttonSize = buttonCount > 3 + ? (baseButtonSize * 0.85).clamp(50.0, 70.0) + : baseButtonSize; + + final baseButtonIconSize = (1 - widget.shrink) < 0.9 ? 20.0 : 40.0; + final buttonIconSize = buttonCount > 3 + ? (baseButtonIconSize * 0.85).clamp(18.0, 35.0) + : baseButtonIconSize; + final buttonFontSize = (1 - widget.shrink) < 0.9 ? 12.0 - : progressiveClamp(10, 14, widget.shrink); + : progressiveClamp(10, buttonCount > 3 ? 12 : 14, widget.shrink); return Stack( alignment: Alignment.topCenter, @@ -294,133 +319,195 @@ class _WalletActionsState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, children: [ - WalletActionButton( - key: const Key('send_action_button'), - icon: CupertinoIcons.arrow_up, - buttonSize: buttonSize, - buttonIconSize: buttonIconSize, - buttonFontSize: buttonFontSize, - shrink: widget.shrink, - text: sendLoading - ? AppLocalizations.of(context)!.sending - : AppLocalizations.of(context)!.send, - loading: sendLoading, - disabled: blockSending, - onPressed: widget.handleSendScreen, + Flexible( + child: WalletActionButton( + key: const Key('send_action_button'), + icon: CupertinoIcons.arrow_up, + buttonSize: buttonSize, + buttonIconSize: buttonIconSize, + buttonFontSize: buttonFontSize, + shrink: widget.shrink, + buttonCount: buttonCount, + text: sendLoading + ? AppLocalizations.of(context)!.sending + : AppLocalizations.of(context)!.send, + loading: sendLoading, + disabled: blockSending, + onPressed: widget.handleSendScreen, + ), ), SizedBox( width: buttonSeparator, ), - WalletActionButton( - key: const Key('receive_action_button'), - icon: CupertinoIcons.arrow_down, - buttonSize: buttonSize, - buttonIconSize: buttonIconSize, - buttonFontSize: buttonFontSize, - shrink: widget.shrink, - text: AppLocalizations.of(context)!.receive, - loading: sendLoading, - disabled: blockReceive, - onPressed: widget.handleReceive, + Flexible( + child: WalletActionButton( + key: const Key('receive_action_button'), + icon: CupertinoIcons.arrow_down, + buttonSize: buttonSize, + buttonIconSize: buttonIconSize, + buttonFontSize: buttonFontSize, + shrink: widget.shrink, + buttonCount: buttonCount, + text: AppLocalizations.of(context)!.receive, + loading: sendLoading, + disabled: blockReceive, + onPressed: widget.handleReceive, + ), ), SizedBox( width: buttonSeparator, ), + // Featured Plugin Button (3rd position) + if (showActionButton && featuredPlugin != null) ...[ + Flexible( + child: WalletActionButton( + key: Key('featured_plugin_action_button_${featuredPlugin.name}'), + customIcon: featuredPlugin.icon != null + ? SvgPicture.network( + featuredPlugin.icon!, + semanticsLabel: '${featuredPlugin.name} icon', + height: buttonIconSize, + width: buttonIconSize, + placeholderBuilder: (_) => Icon( + CupertinoIcons.link, + size: buttonIconSize, + color: sendLoading + ? Theme.of(context) + .colors + .subtleEmphasis + : Theme.of(context).colors.black, + ), + ) + : null, + buttonSize: buttonSize, + buttonIconSize: buttonIconSize, + buttonFontSize: buttonFontSize, + shrink: widget.shrink, + buttonCount: buttonCount, + text: featuredPlugin.name, + alt: true, + loading: sendLoading, + disabled: sendLoading, + onPressed: () => widget.handlePlugin!(featuredPlugin), + ), + ), + SizedBox( + width: buttonSeparator, + ), + ], + // 4th Button: More, Vouchers, Mint, or other plugins if (!showActionButton || actionButton == null) ...[ - WalletActionButton( - icon: null, - buttonSize: buttonSize, - buttonIconSize: buttonIconSize, - buttonFontSize: buttonFontSize, - shrink: widget.shrink, - text: '', - loading: false, - disabled: true, - onPressed: () => {}, - alt: true, + Flexible( + child: WalletActionButton( + icon: null, + buttonSize: buttonSize, + buttonIconSize: buttonIconSize, + buttonFontSize: buttonFontSize, + shrink: widget.shrink, + buttonCount: buttonCount, + text: '', + loading: false, + disabled: true, + onPressed: () => {}, + alt: true, + ), ), ], if (showActionButton && actionButton?.buttonType == ActionButtonType.more) ...[ - WalletActionButton( - key: const Key('more_action_button'), - icon: CupertinoIcons.ellipsis, - buttonSize: buttonSize, - buttonIconSize: buttonIconSize, - buttonFontSize: buttonFontSize, - shrink: widget.shrink, - text: AppLocalizations.of(context)!.more, - loading: false, - disabled: false, - onPressed: widget.handleShowMore, - alt: true, + Flexible( + child: WalletActionButton( + key: const Key('more_action_button'), + icon: CupertinoIcons.ellipsis, + buttonSize: buttonSize, + buttonIconSize: buttonIconSize, + buttonFontSize: buttonFontSize, + shrink: widget.shrink, + buttonCount: buttonCount, + text: AppLocalizations.of(context)!.more, + loading: false, + disabled: false, + onPressed: widget.handleShowMore, + alt: true, + ), ), ], if (showActionButton && actionButton?.buttonType == ActionButtonType.vouchers) ...[ - WalletActionButton( - key: const Key('vouchers_action_button'), - icon: CupertinoIcons.ticket, - buttonSize: buttonSize, - buttonIconSize: buttonIconSize, - buttonFontSize: buttonFontSize, - shrink: widget.shrink, - text: AppLocalizations.of(context)!.vouchers, - alt: true, - disabled: sendLoading, - onPressed: widget.handleVouchers, + Flexible( + child: WalletActionButton( + key: const Key('vouchers_action_button'), + icon: CupertinoIcons.ticket, + buttonSize: buttonSize, + buttonIconSize: buttonIconSize, + buttonFontSize: buttonFontSize, + shrink: widget.shrink, + buttonCount: buttonCount, + text: AppLocalizations.of(context)!.vouchers, + alt: true, + disabled: sendLoading, + onPressed: widget.handleVouchers, + ), ), ], if (showActionButton && actionButton?.buttonType == ActionButtonType.minter) ...[ - WalletActionButton( - key: const Key('minter_action_button'), - icon: CupertinoIcons.hammer, - buttonSize: buttonSize, - buttonIconSize: buttonIconSize, - buttonFontSize: buttonFontSize, - shrink: widget.shrink, - text: AppLocalizations.of(context)!.mint, - alt: true, - disabled: sendLoading, - onPressed: widget.handleMint, + Flexible( + child: WalletActionButton( + key: const Key('minter_action_button'), + icon: CupertinoIcons.hammer, + buttonSize: buttonSize, + buttonIconSize: buttonIconSize, + buttonFontSize: buttonFontSize, + shrink: widget.shrink, + buttonCount: buttonCount, + text: AppLocalizations.of(context)!.mint, + alt: true, + disabled: sendLoading, + onPressed: widget.handleMint, + ), ), ], if (showActionButton && actionButton?.buttonType == - ActionButtonType.plugins) ...[ - WalletActionButton( - key: const Key('plugin_action_button'), - customIcon: onePlugin!.icon != null - ? SvgPicture.network( - onePlugin.icon!, - semanticsLabel: '${onePlugin.name} icon', - height: buttonIconSize, - width: buttonIconSize, - placeholderBuilder: (_) => Icon( - CupertinoIcons.arrow_down, - size: buttonIconSize, - color: sendLoading - ? Theme.of(context) - .colors - .subtleEmphasis - : Theme.of(context).colors.black, - ), - ) - : null, - buttonSize: buttonSize, - buttonIconSize: buttonIconSize, - buttonFontSize: buttonFontSize, - shrink: widget.shrink, - text: onePlugin.name, - alt: true, - loading: sendLoading, - disabled: sendLoading, - onPressed: () => widget.handlePlugin!(onePlugin), + ActionButtonType.plugins && + featuredPlugin == null) ...[ + Flexible( + child: WalletActionButton( + key: const Key('plugin_action_button'), + customIcon: onePlugin!.icon != null + ? SvgPicture.network( + onePlugin.icon!, + semanticsLabel: '${onePlugin.name} icon', + height: buttonIconSize, + width: buttonIconSize, + placeholderBuilder: (_) => Icon( + CupertinoIcons.arrow_down, + size: buttonIconSize, + color: sendLoading + ? Theme.of(context) + .colors + .subtleEmphasis + : Theme.of(context).colors.black, + ), + ) + : null, + buttonSize: buttonSize, + buttonIconSize: buttonIconSize, + buttonFontSize: buttonFontSize, + shrink: widget.shrink, + buttonCount: buttonCount, + text: onePlugin.name, + alt: true, + loading: sendLoading, + disabled: sendLoading, + onPressed: () => widget.handlePlugin!(onePlugin), + ), ) ], ], diff --git a/lib/services/config/config.dart b/lib/services/config/config.dart index a31a9ca8..01b02434 100644 --- a/lib/services/config/config.dart +++ b/lib/services/config/config.dart @@ -369,6 +369,7 @@ class PluginConfig { final String? action; final bool hidden; final bool signature; + final bool featured; PluginConfig({ required this.name, @@ -378,6 +379,7 @@ class PluginConfig { this.action, this.hidden = false, this.signature = false, + this.featured = false, }); factory PluginConfig.fromJson(Map json) { @@ -391,6 +393,7 @@ class PluginConfig { action: json['action'], hidden: json['hidden'] ?? false, signature: json['signature'] ?? false, + featured: json['featured'] ?? false, ); } @@ -408,13 +411,14 @@ class PluginConfig { if (action != null) 'action': action, 'hidden': hidden, 'signature': signature, + 'featured': featured, }; } // to string @override String toString() { - return 'PluginConfig{name: $name, icon: $icon, url: $url, launchMode: $launchMode, action: $action, hidden: $hidden, signature: $signature}'; + return 'PluginConfig{name: $name, icon: $icon, url: $url, launchMode: $launchMode, action: $action, hidden: $hidden, signature: $signature, featured: $featured}'; } } diff --git a/lib/state/wallet/selectors.dart b/lib/state/wallet/selectors.dart index 98a82e19..b760b9d3 100644 --- a/lib/state/wallet/selectors.dart +++ b/lib/state/wallet/selectors.dart @@ -87,6 +87,67 @@ ActionButton? selectActionButtonToShow(WalletState state) { ); if (moreButton != null) { + // Count items that would be in the More menu + int moreMenuItemsCount = 0; + + // Count vouchers (always 1 if present) + final hasVouchers = state.walletActions.any( + (action) => action.buttonType == ActionButtonType.vouchers, + ); + if (hasVouchers) moreMenuItemsCount++; + + // Count minter (1 if present) + final hasMinter = state.walletActions.any( + (action) => action.buttonType == ActionButtonType.minter, + ); + if (hasMinter) moreMenuItemsCount++; + + // Count plugins (excluding featured plugin displayed as button) + final plugins = selectVisiblePlugins(state); + final featuredPlugins = selectFeaturedPlugins(state); + final displayedFeaturedPlugin = featuredPlugins.isNotEmpty ? featuredPlugins.first : null; + + final pluginsToShow = displayedFeaturedPlugin != null + ? plugins.where((plugin) => plugin.url != displayedFeaturedPlugin.url).toList() + : plugins; + + // If plugins action exists, count the plugins that would be shown + final hasPlugins = state.walletActions.any( + (action) => action.buttonType == ActionButtonType.plugins, + ); + if (hasPlugins && pluginsToShow.isNotEmpty) { + moreMenuItemsCount += pluginsToShow.length; + } + + // If More menu would be empty, don't show More button + if (moreMenuItemsCount == 0) { + // Return the last action (excluding More button) + return state.walletActions + .where((action) => action.buttonType != ActionButtonType.more) + .lastOrNull; + } + + // If More menu would only have 1 item, show that item directly instead + if (moreMenuItemsCount == 1) { + // Return the single action that would be in More menu + if (hasVouchers) { + return state.walletActions.firstWhere( + (action) => action.buttonType == ActionButtonType.vouchers, + ); + } + if (hasMinter) { + return state.walletActions.firstWhere( + (action) => action.buttonType == ActionButtonType.minter, + ); + } + if (hasPlugins && pluginsToShow.isNotEmpty) { + return state.walletActions.firstWhere( + (action) => action.buttonType == ActionButtonType.plugins, + ); + } + } + + // Otherwise, show More button return moreButton; } @@ -95,3 +156,8 @@ ActionButton? selectActionButtonToShow(WalletState state) { List selectVisiblePlugins(WalletState state) => (state.wallet?.plugins ?? []).where((plugin) => !plugin.hidden).toList(); + +List selectFeaturedPlugins(WalletState state) => + (state.wallet?.plugins ?? []) + .where((plugin) => !plugin.hidden && plugin.featured) + .toList(); diff --git a/lib/widgets/wallet/action_button.dart b/lib/widgets/wallet/action_button.dart index 977be806..207caa72 100644 --- a/lib/widgets/wallet/action_button.dart +++ b/lib/widgets/wallet/action_button.dart @@ -14,6 +14,7 @@ class WalletActionButton extends StatelessWidget { final bool loading; final bool disabled; final void Function()? onPressed; + final int? buttonCount; // Number of buttons to help calculate available space const WalletActionButton({ super.key, @@ -28,13 +29,19 @@ class WalletActionButton extends StatelessWidget { this.alt = false, this.loading = false, this.disabled = false, + this.buttonCount, required this.onPressed, }); @override Widget build(BuildContext context) { final small = (1 - shrink) < 0.90; - final buttonWidth = small ? 110.0 : buttonSize; + // Adjust button width based on button count when in small state + // With 4 buttons, each button needs to be narrower + final baseButtonWidth = small ? 110.0 : buttonSize; + final buttonWidth = (buttonCount != null && buttonCount! > 3 && small) + ? (baseButtonWidth * 0.75).clamp(70.0, 90.0) + : baseButtonWidth; final color = alt ? Theme.of(context).colors.surfacePrimary.resolveFrom(context) @@ -85,22 +92,26 @@ class WalletActionButton extends StatelessWidget { ? Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, children: [ - Text( - text, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontWeight: FontWeight.bold, - color: color, - fontSize: 14, + Flexible( + child: Text( + text, + maxLines: 1, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.center, + style: TextStyle( + fontWeight: FontWeight.bold, + color: color, + fontSize: (buttonCount != null && buttonCount! > 3) ? 12.0 : 14.0, + ), ), ), - const SizedBox(width: 10), + SizedBox(width: (buttonCount != null && buttonCount! > 3) ? 6.0 : 10.0), customIcon ?? Icon( icon, - size: 18, + size: (buttonCount != null && buttonCount! > 3) ? 16.0 : 18.0, color: color, ), ], From 7f7c48b78b69703c6c59c11ccec9301e7cb13ce0 Mon Sep 17 00:00:00 2001 From: kev-techi Date: Thu, 6 Nov 2025 13:13:30 +0100 Subject: [PATCH 10/11] fix receive link invalid separator + improve scanning of invalid separator --- docs/qr_scanning.md | 469 ++++++++++++++++++++++++++++++++++++ lib/state/wallet/logic.dart | 17 +- lib/utils/qr.dart | 45 +++- 3 files changed, 517 insertions(+), 14 deletions(-) create mode 100644 docs/qr_scanning.md diff --git a/docs/qr_scanning.md b/docs/qr_scanning.md new file mode 100644 index 00000000..93b4450b --- /dev/null +++ b/docs/qr_scanning.md @@ -0,0 +1,469 @@ +# QR Code Scanning Implementation + +This document describes how QR code scanning is implemented in the CitizenWallet app. + +## Overview + +The app uses the `mobile_scanner` package to scan QR codes. Scanned QR codes are parsed and handled based on their format, supporting multiple QR code types including wallet addresses, EIP-681 URIs, send/receive URLs, calldata transactions, and WalletConnect connections. + +## Components + +### Scanner Widgets + +#### `ScannerModal` (`lib/widgets/scanner/scanner_modal.dart`) + +A full-screen modal that displays the camera scanner interface. This is the primary entry point for QR scanning in the app. + +**Features:** +- Full-screen camera view with QR code detection +- Torch/flashlight toggle (if available) +- Manual text entry option (when `confirm` is true) +- Success animation on detection +- Lifecycle-aware (pauses/resumes with app state) + +**Usage:** +```dart +final result = await showCupertinoModalPopup( + context: context, + builder: (_) => const ScannerModal( + modalKey: 'wallet-qr-scanner', + ), +); +``` + +The modal returns the scanned QR code string, or `null` if dismissed. + +#### `Scanner` (`lib/widgets/scanner/scanner.dart`) + +A smaller, embedded scanner widget that can be used within other screens. Similar functionality to `ScannerModal` but designed for inline use. + +### QR Code Parsing + +#### `parseQRFormat` (`lib/utils/qr.dart`) + +Detects the format of a QR code string and returns a `QRFormat` enum value: + +- `address` - Plain Ethereum address (starts with `0x`) +- `eip681` - EIP-681 URI format (`ethereum:address@chainId`) +- `eip681Transfer` - EIP-681 transfer format (`ethereum:.../transfer`) +- `receiveUrl` - Receive URL with compressed params +- `sendtoUrl` - Send-to URL format (`https://...?sendto=...`) +- `sendtoUrlWithEIP681` - Send-to URL with embedded EIP-681 +- `calldataUrl` - Calldata transaction URL (`https://...?calldata=...`) +- `plugin` - Plugin URL format +- `url` - Generic HTTP/HTTPS URL +- `unsupported` - Unknown format + +#### `parseQRCode` (`lib/utils/qr.dart`) + +Parses a QR code string based on its format and returns a `ParsedQRData` object containing: +- `address` - Recipient address +- `amount` - Transaction amount (if specified) +- `description` - Transaction description/message +- `alias` - Community alias +- `tip` - Tip information (if present) +- `calldata` - Contract call data (for calldata transactions) + +## QR Code Formats + +### 1. Plain Address +``` +0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb5 +``` +- Directly used as recipient address +- No amount or description + +### 2. EIP-681 Format +``` +ethereum:0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb5@1?value=1000000000000000000 +``` +- Standard Ethereum URI format +- Supports chain ID in address (`address@chainId`) +- Amount in `value` parameter (in wei) + +### 3. EIP-681 Transfer +``` +ethereum:0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb5/transfer?address=0x...&uint256=100 +``` +- ERC-20 token transfer format +- `address` parameter specifies token contract +- `uint256` parameter specifies amount + +### 4. Send-to URL +``` +https://app.citizenwallet.xyz/?sendto=username@community&amount=100&description=Hello +``` +- Community-specific send format +- Supports username resolution +- Includes amount and description +- Can include tip information (`tipTo`, `tipAmount`, `tipDescription`) + +### 5. Calldata URL +``` +https://app.citizenwallet.xyz/?calldata=0x...&address=0x...&value=0 +``` +- For smart contract interactions +- `calldata` contains the function call data +- `address` is the contract address +- `value` is the ETH value to send + +### 6. Receive URL +``` +https://app.citizenwallet.xyz/#/?receiveParams=compressed_data +``` +- Compressed receive parameters +- Used for generating receive QR codes +- Can be decompressed to extract address, amount, alias, etc. + +### 7. Plugin URL +``` +https://app.citizenwallet.xyz/#/?dl=plugin&alias=community&plugin=encoded_url +``` +- Opens a plugin/webview +- `alias` specifies the community +- `plugin` contains the encoded plugin URL + +## Scanning Flow + +### 1. Wallet Screen (`lib/screens/wallet/screen.dart`) + +When a QR code is scanned from the wallet screen: + +```910:1003:lib/screens/wallet/screen.dart + void handleQRScan() async { + _profileLogic.pause(); + _profilesLogic.pause(); + _voucherLogic.pause(); + + final result = await showCupertinoModalPopup( + context: context, + barrierDismissible: true, + builder: (_) => const ScannerModal( + modalKey: 'wallet-qr-scanner', + ), + ); + + if (result == null) { + _profileLogic.resume(); + _profilesLogic.resume(); + _voucherLogic.pause(); + return; + } + + if (result.startsWith('wc:')) { + try { + if (_walletKitLogic.connectClient == null) { + await _walletKitLogic.initialize(); + } + + await _walletKitLogic.registerWallet(_address!); + await _walletKitLogic.pairWithDapp(result); + await _walletKitLogic.approveSession(); + + _profileLogic.resume(); + _profilesLogic.resume(); + _voucherLogic.resume(); + return; + } catch (e) { + _profileLogic.resume(); + _profilesLogic.resume(); + _voucherLogic.resume(); + return; + } + } + + final format = parseQRFormat(result); + if (format == QRFormat.plugin) { + final parsed = parseQRCode(result); + final pluginUrl = parsed.description; + final alias = parsed.alias; + if (pluginUrl == null || alias == null) { + _profileLogic.resume(); + _profilesLogic.resume(); + _voucherLogic.resume(); + return; + } + + if (alias != _alias) { + _address = null; + _alias = null; + _deepLink = 'plugin'; + _deepLinkParams = encodeParams(pluginUrl); + + final (address, newAlias) = await handleLoadFromParams( + null, + overrideAlias: alias, + ); + + if (address == null || newAlias == null) { + _profileLogic.resume(); + _profilesLogic.resume(); + _voucherLogic.resume(); + return; + } + + _address = address; + _alias = newAlias; + + onLoad(); + return; + } + + final pluginConfig = await _logic.getPluginConfig(alias, pluginUrl); + if (pluginConfig == null) { + _profileLogic.resume(); + _profilesLogic.resume(); + _voucherLogic.resume(); + return; + } + + await handlePlugin(pluginConfig); + + _profileLogic.resume(); + _profilesLogic.resume(); + _voucherLogic.resume(); + return; + } +``` + +**Handling Steps:** +1. **WalletConnect** - If QR starts with `wc:`, initiates WalletConnect pairing +2. **Plugin URLs** - Opens plugin/webview if format is `plugin` +3. **Regular URLs** - Opens connected webview modal for generic URLs +4. **Address/Transaction QR codes** - Parses and navigates to send screen or updates receive screen + +### 2. Send Screen (`lib/state/wallet/logic.dart`) + +When scanning from the send screen, the QR code is parsed and used to populate the send form: + +```1760:1848:lib/state/wallet/logic.dart + Future handleQRScan(String raw) async { + try { + final format = parseQRFormat(raw); + + if (format == QRFormat.unsupported) { + _state.setInvalidScanMessage('Unsupported QR code format'); + return null; + } + + if (format == QRFormat.voucher) { + _state.setInvalidScanMessage('Vouchers cannot be used for transfers'); + return null; + } + + if (format == QRFormat.url && !raw.contains('sendto=') && !raw.contains('calldata=')) { + _state.setInvalidScanMessage('Invalid QR code format'); + return null; + } + + final parsedData = parseQRCode(raw); + + if (parsedData.address.isEmpty) { + throw QREmptyException(); + } + + if (format == QRFormat.eip681 || format == QRFormat.eip681Transfer) { + final url = Uri.parse(raw); + final chainIdParam = url.authority.split('@').last; + final chainId = int.tryParse(chainIdParam); + + if (chainId != null) { + final token = _wallet.currency; + if (token.chainId != chainId) { + _notificationsLogic + .show('Wrong chain ID. Expected ${token.chainId}, got $chainId'); + throw QRInvalidException(); + } + } + + try { + final tokenAddress = url.queryParameters['address']; + if (tokenAddress != null) { + final token = _wallet.currency; + if (token.contract != null && + token.contract!.toLowerCase() != tokenAddress.toLowerCase()) { + _notificationsLogic + .show('Wrong token contract. Expected ${token.contract}, got $tokenAddress'); + throw QRInvalidException(); + } + } + } catch (e) { + if (e is QRInvalidException) { + rethrow; + } + _notificationsLogic + .show('Invalid token contract or community configuration'); + throw QRInvalidException(); + } + } + + if (parsedData.amount != null) { + if (format == QRFormat.eip681Transfer) { + final amount = fromDoubleUnit( + parsedData.amount!, + decimals: _wallet.currency.decimals, + ); + _amountController.text = amount; + } else { + _amountController.text = parsedData.amount!; + } + updateAmount(); + } + + String addressToUse = ''; + try { + EthereumAddress.fromHex(parsedData.address).hexEip55; + addressToUse = parsedData.address; + } catch (_) { + String username = parsedData.address; + ProfileV1? profile = await _wallet.getProfileByUsername(username); + if (profile != null) { + addressToUse = profile.account; + } else { + addressToUse = parsedData.address; + } + } + + updateAddressFromHexCapture(addressToUse); + + if (parsedData.description != null) { + _messageController.text = parsedData.description!; + } else { + _messageController.text = parseMessageFromReceiveParams(raw) ?? ''; + } + + // Handle tip information if present + if (parsedData.tip != null) { + _state.setTipTo(parsedData.tip!.to); + _state.setHasTip(true); + } + + return addressToUse; + } on QREmptyException catch (e) { + _state.setInvalidScanMessage(e.message); + } on QRInvalidException catch (e) { + _state.setInvalidScanMessage(e.message); + } on QRAliasMismatchException catch (e) { + _state.setInvalidScanMessage(e.message); + } on QRMissingAddressException catch (e) { + _state.setInvalidScanMessage(e.message); + } catch (_) { + // + } + + return null; + } +``` + +**Processing Steps:** +1. Validates QR format +2. Parses QR data (address, amount, description, etc.) +3. Validates chain ID for EIP-681 formats +4. Validates token contract for token transfers +5. Resolves username to address if needed +6. Populates form fields (amount, address, description) +7. Handles tip information if present + +### 3. Connected WebView (`lib/widgets/webview/connected_webview_modal.dart`) + +When a webview detects a QR code format in navigation, it intercepts and shows a confirmation modal: + +```85:119:lib/widgets/webview/connected_webview_modal.dart + Future shouldOverrideUrlLoading( + InAppWebViewController controller, NavigationAction action) async { + final uri = Uri.parse(action.request.url.toString()); + + if (uri.scheme != 'http' && uri.scheme != 'https') { + widget.walletLogic.launchPluginUrl(uri.toString()); + + return NavigationActionPolicy.CANCEL; + } + + if (uri.toString().startsWith(widget.closeUrl)) { + handleClose(); + + return NavigationActionPolicy.CANCEL; + } + + if (uri.toString().startsWith(widget.redirectUrl) && + !uri.toString().startsWith(widget.pluginUrl)) { + final format = parseQRFormat(uri.toString()); + + switch (format) { + case QRFormat.sendtoUrl: + handleDisplaySendActionModal(uri); + break; + case QRFormat.calldataUrl: + handleDisplayCallDataActionModal(uri); + break; + default: + } + + return NavigationActionPolicy.CANCEL; + } + + return NavigationActionPolicy.ALLOW; + } +``` + +**WebView Actions:** +- **Send-to URLs** - Shows `ConnectedWebViewSendModal` for confirmation +- **Calldata URLs** - Shows `ConnectedWebViewCallDataModal` for contract calls +- **Close URLs** - Closes the webview modal + +## Transaction Modals + +### Send Modal (`lib/widgets/webview/connected_webview_send_modal.dart`) + +Displays transaction details and allows user to confirm: +- Recipient profile information +- Amount and currency +- Description +- Slide-to-confirm interaction + +On confirmation, executes the transaction and redirects to success URL with transaction hash. + +### Calldata Modal (`lib/widgets/webview/connected_webview_calldata_modal.dart`) + +Displays contract call details: +- Contract address +- Value (ETH amount) +- Calldata (encoded function call) +- Slide-to-confirm interaction + +On confirmation, executes the contract call and redirects to success URL with transaction hash. + +## Error Handling + +The app defines several QR-related exceptions: + +- `QREmptyException` - QR code has no address +- `QRInvalidException` - QR code format is invalid +- `QRAliasMismatchException` - QR code alias doesn't match current community +- `QRMissingAddressException` - QR code is missing required address + +These exceptions are caught and displayed to the user via the wallet state's `invalidScanMessage`. + +## Lifecycle Management + +Both `Scanner` and `ScannerModal` implement `WidgetsBindingObserver` to handle app lifecycle changes: + +- **Paused/Hidden** - Scanner stops +- **Resumed** - Scanner restarts and re-subscribes to barcode events + +This ensures the camera is properly managed when the app goes to background. + +## Dependencies + +- `mobile_scanner` - QR code scanning +- `flutter_inappwebview` - WebView for plugin URLs +- `web3dart` - Ethereum address validation +- `go_router` - Navigation + +## Testing + +QR parsing logic is tested in `test/services/wallet/qr_test.dart`. The tests cover: +- Format detection +- Parsing different QR formats +- Edge cases and error handling + diff --git a/lib/state/wallet/logic.dart b/lib/state/wallet/logic.dart index 5223cac0..81b1264e 100644 --- a/lib/state/wallet/logic.dart +++ b/lib/state/wallet/logic.dart @@ -1866,8 +1866,12 @@ class WalletLogic extends WidgetsBindingObserver { final url = communityConfig.community.walletUrl(deepLinkURL); if (onlyHex != null && onlyHex) { - _state.updateReceiveQR( - '$url?sendto=${_wallet.account.hexEip55}@${communityConfig.community.alias}'); + // walletUrl returns format like: https://app.citizenwallet.xyz/#/?alias=... + // So we need to append with & since there's already a ? in the fragment + final separator = url.contains('?') ? '&' : '?'; + final params = + 'sendto=${_wallet.account.hexEip55}@${communityConfig.community.alias}'; + _state.updateReceiveQR('$url$separator$params'); return; } @@ -1885,7 +1889,8 @@ class WalletLogic extends WidgetsBindingObserver { } if (_messageController.value.text.isNotEmpty) { - params += '&description=${_messageController.value.text}'; + params += + '&description=${Uri.encodeComponent(_messageController.value.text)}'; } // Add tipTo parameter if it exists in the state @@ -1894,7 +1899,11 @@ class WalletLogic extends WidgetsBindingObserver { params += '&tipTo=$tipTo'; } - _state.updateReceiveQR('$url&$params'); + // Check if URL already has query parameters in the fragment + // walletUrl returns format like: https://app.citizenwallet.xyz/#/?alias=... + // So we need to append with & since there's already a ? in the fragment + final separator = url.contains('?') ? '&' : '?'; + _state.updateReceiveQR('$url$separator$params'); return; } catch (_) {} diff --git a/lib/utils/qr.dart b/lib/utils/qr.dart index bf7ec4ba..fb0507a7 100644 --- a/lib/utils/qr.dart +++ b/lib/utils/qr.dart @@ -123,25 +123,50 @@ ParsedQRData parseSendtoUrlWithEIP681(String raw) { // parse the sendto url // raw is the URL from the QR code, eg. https://example.com/?sendto=:username@:communitySlug&amount=100&description=Hello ParsedQRData parseSendtoUrl(String raw) { - final cleanRaw = raw.replaceFirst('/#/', '/'); - final decodedRaw = Uri.decodeComponent(cleanRaw); + final parsedUri = Uri.parse(raw); - final receiveUrl = Uri.parse(decodedRaw); + // Extract query parameters from fragment if present (like /#/?sendto=...) + final fragment = parsedUri.fragment; + String queryString = fragment; + if (fragment.startsWith('/?')) { + queryString = fragment.substring(2); + } - final sendToParam = receiveUrl.queryParameters['sendto']; - final amountParam = receiveUrl.queryParameters['amount']; - final descriptionParam = receiveUrl.queryParameters['description']; + // Handle malformed query strings (convert ? to & after the first one) + // e.g., "alias=...?sendto=..." becomes "alias=...&sendto=..." + if (queryString.contains('?')) { + final firstQuestionMark = queryString.indexOf('?'); + queryString = queryString.substring(0, firstQuestionMark) + + '&' + + queryString.substring(firstQuestionMark + 1).replaceAll('?', '&'); + } - final tipToParam = receiveUrl.queryParameters['tipTo']; - final tipAmountParam = receiveUrl.queryParameters['tipAmount']; - final tipDescriptionParam = receiveUrl.queryParameters['tipDescription']; + Uri uriData = Uri.parse('temp://temp?$queryString'); + + // If fragment is empty or has no query params, use the main URI + if (fragment.isEmpty || uriData.queryParameters.isEmpty) { + uriData = parsedUri; + } + + final sendToParam = uriData.queryParameters['sendto']; + final amountParam = uriData.queryParameters['amount']; + final descriptionParam = uriData.queryParameters['description']; + + final tipToParam = uriData.queryParameters['tipTo']; + final tipAmountParam = uriData.queryParameters['tipAmount']; + final tipDescriptionParam = uriData.queryParameters['tipDescription']; if (sendToParam == null) { return ParsedQRData(address: ''); } final address = sendToParam.split('@').first; - final alias = sendToParam.split('@').last; + // Extract alias from sendto parameter (after @) as fallback + final aliasFromSendto = sendToParam.contains('@') + ? sendToParam.split('@').last + : null; + // Use explicit alias parameter if present, otherwise fallback to alias from sendto + final alias = uriData.queryParameters['alias'] ?? aliasFromSendto; final tip = tipToParam != null ? SendDestination( From 8cd2d3f657dff409e8bc82d4c30281a89fa1b155 Mon Sep 17 00:00:00 2001 From: kev-techi Date: Thu, 6 Nov 2025 15:42:25 +0100 Subject: [PATCH 11/11] 16kb android build --- android/app/build.gradle | 4 +- ios/Podfile.lock | 78 +++++------ pubspec.lock | 288 +++++++++++++++++---------------------- pubspec.yaml | 2 +- 4 files changed, 161 insertions(+), 211 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 3f68756f..f5a2eb71 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -31,9 +31,9 @@ if (keystorePropertiesFile.exists()) { android { // compileSdkVersion flutter.compileSdkVersion - compileSdkVersion 35 + compileSdkVersion 36 // ndkVersion flutter.ndkVersion - ndkVersion = "27.0.12077973" + ndkVersion = "28.2.13676358" compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 diff --git a/ios/Podfile.lock b/ios/Podfile.lock index b35942fc..f1ccaa00 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -49,37 +49,37 @@ PODS: - file_picker (0.0.1): - DKImagePickerController/PhotoGallery - Flutter - - Firebase/CoreOnly (11.10.0): - - FirebaseCore (~> 11.10.0) - - Firebase/Messaging (11.10.0): + - Firebase/CoreOnly (11.15.0): + - FirebaseCore (~> 11.15.0) + - Firebase/Messaging (11.15.0): - Firebase/CoreOnly - - FirebaseMessaging (~> 11.10.0) - - firebase_core (3.13.1): - - Firebase/CoreOnly (= 11.10.0) + - FirebaseMessaging (~> 11.15.0) + - firebase_core (3.15.2): + - Firebase/CoreOnly (= 11.15.0) - Flutter - - firebase_messaging (15.2.6): - - Firebase/Messaging (= 11.10.0) + - firebase_messaging (15.2.10): + - Firebase/Messaging (= 11.15.0) - firebase_core - Flutter - - FirebaseCore (11.10.0): - - FirebaseCoreInternal (~> 11.10.0) - - GoogleUtilities/Environment (~> 8.0) - - GoogleUtilities/Logger (~> 8.0) - - FirebaseCoreInternal (11.10.0): - - "GoogleUtilities/NSData+zlib (~> 8.0)" - - FirebaseInstallations (11.10.0): - - FirebaseCore (~> 11.10.0) - - GoogleUtilities/Environment (~> 8.0) - - GoogleUtilities/UserDefaults (~> 8.0) + - FirebaseCore (11.15.0): + - FirebaseCoreInternal (~> 11.15.0) + - GoogleUtilities/Environment (~> 8.1) + - GoogleUtilities/Logger (~> 8.1) + - FirebaseCoreInternal (11.15.0): + - "GoogleUtilities/NSData+zlib (~> 8.1)" + - FirebaseInstallations (11.15.0): + - FirebaseCore (~> 11.15.0) + - GoogleUtilities/Environment (~> 8.1) + - GoogleUtilities/UserDefaults (~> 8.1) - PromisesObjC (~> 2.4) - - FirebaseMessaging (11.10.0): - - FirebaseCore (~> 11.10.0) + - FirebaseMessaging (11.15.0): + - FirebaseCore (~> 11.15.0) - FirebaseInstallations (~> 11.0) - GoogleDataTransport (~> 10.0) - - GoogleUtilities/AppDelegateSwizzler (~> 8.0) - - GoogleUtilities/Environment (~> 8.0) - - GoogleUtilities/Reachability (~> 8.0) - - GoogleUtilities/UserDefaults (~> 8.0) + - GoogleUtilities/AppDelegateSwizzler (~> 8.1) + - GoogleUtilities/Environment (~> 8.1) + - GoogleUtilities/Reachability (~> 8.1) + - GoogleUtilities/UserDefaults (~> 8.1) - nanopb (~> 3.30910.0) - Flutter (1.0.0) - flutter_inappwebview_ios (0.0.1): @@ -156,9 +156,6 @@ PODS: - Flutter - FlutterMacOS - PromisesObjC (2.4.0) - - reown_yttrium (0.0.1): - - Flutter - - YttriumWrapper (= 0.8.35) - SDWebImage (5.21.2): - SDWebImage/Core (= 5.21.2) - SDWebImage/Core (5.21.2) @@ -173,7 +170,6 @@ PODS: - SwiftyGif (5.4.5) - url_launcher_ios (0.0.1): - Flutter - - YttriumWrapper (0.8.35) DEPENDENCIES: - audioplayers_darwin (from `.symlinks/plugins/audioplayers_darwin/ios`) @@ -191,7 +187,6 @@ DEPENDENCIES: - nfc_manager (from `.symlinks/plugins/nfc_manager/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - - reown_yttrium (from `.symlinks/plugins/reown_yttrium/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) @@ -218,7 +213,6 @@ SPEC REPOS: - PromisesObjC - SDWebImage - SwiftyGif - - YttriumWrapper EXTERNAL SOURCES: audioplayers_darwin: @@ -251,8 +245,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/package_info_plus/ios" path_provider_foundation: :path: ".symlinks/plugins/path_provider_foundation/darwin" - reown_yttrium: - :path: ".symlinks/plugins/reown_yttrium/ios" share_plus: :path: ".symlinks/plugins/share_plus/ios" shared_preferences_foundation: @@ -271,13 +263,13 @@ SPEC CHECKSUMS: DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 file_picker: be9a674155d9f334323856cb266e0d145d75d5c0 - Firebase: 1fe1c0a7d9aaea32efe01fbea5f0ebd8d70e53a2 - firebase_core: 3c2f323cae65c97a636a05a23b17730ef93df2cf - firebase_messaging: 456e01ff29a451c90097d0b45925551d5be0c143 - FirebaseCore: 8344daef5e2661eb004b177488d6f9f0f24251b7 - FirebaseCoreInternal: ef4505d2afb1d0ebbc33162cb3795382904b5679 - FirebaseInstallations: 9980995bdd06ec8081dfb6ab364162bdd64245c3 - FirebaseMessaging: 2b9f56aa4ed286e1f0ce2ee1d413aabb8f9f5cb9 + Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e + firebase_core: 99a37263b3c27536063a7b601d9e2a49400a433c + firebase_messaging: bf6697c61f31c7cc0f654131212ff04c0115c2c7 + FirebaseCore: efb3893e5b94f32b86e331e3bd6dadf18b66568e + FirebaseCoreInternal: 9afa45b1159304c963da48addb78275ef701c6b4 + FirebaseInstallations: 317270fec08a5d418fdbc8429282238cab3ac843 + FirebaseMessaging: 3b26e2cee503815e01c3701236b020aa9b576f09 Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4 flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12 @@ -293,16 +285,14 @@ SPEC CHECKSUMS: nfc_manager: d7da7cb781f7744b94df5fe9dbca904ac4a0939e OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94 package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 + path_provider_foundation: 0b743cbb62d8e47eab856f09262bb8c1ddcfe6ba PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 - reown_yttrium: c0e87e5965fa60a3559564cc35cffbba22976089 SDWebImage: 9f177d83116802728e122410fb25ad88f5c7608a share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f - shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 + shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6 sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 - url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe - YttriumWrapper: 31e937fe9fbe0f1314d2ca6be9ce9b379a059966 + url_launcher_ios: bb13df5870e8c4234ca12609d04010a21be43dfa PODFILE CHECKSUM: f90b7b7d52ec0d905039aa6f51266424548151c7 diff --git a/pubspec.lock b/pubspec.lock index 1c61ba3d..caf39144 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -13,18 +13,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: e55636ed79578b9abca5fecf9437947798f5ef7456308b5cb85720b793eac92f + sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f url: "https://pub.dev" source: hosted - version: "82.0.0" + version: "85.0.0" _flutterfire_internals: dependency: transitive description: name: _flutterfire_internals - sha256: "214e6f07e2a44f45972e0365c7b537eaeaddb4598db0778dd4ac64b4acd3f5b1" + sha256: ff0a84a2734d9e1089f8aedd5c0af0061b82fb94e95260d943404e0ef2134b11 url: "https://pub.dev" source: hosted - version: "1.3.55" + version: "1.3.59" adaptive_number: dependency: transitive description: @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: analyzer - sha256: "904ae5bb474d32c38fb9482e2d925d5454cda04ddd0e55d2e6826bc72f6ba8c0" + sha256: "974859dc0ff5f37bc4313244b3218c791810d03ab3470a579580279ba971a48d" url: "https://pub.dev" source: hosted - version: "7.4.5" + version: "7.7.1" archive: dependency: "direct main" description: @@ -61,10 +61,10 @@ packages: dependency: transitive description: name: asn1lib - sha256: "0511d6be23b007e95105ae023db599aea731df604608978dada7f9faf2637623" + sha256: "9a8f69025044eb466b9b60ef3bc3ac99b4dc6c158ae9c56d25eeccf5bc56d024" url: "https://pub.dev" source: hosted - version: "1.6.4" + version: "1.6.5" async: dependency: "direct main" description: @@ -161,14 +161,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" - buffer: - dependency: transitive - description: - name: buffer - sha256: "389da2ec2c16283c8787e0adaede82b1842102f8c8aae2f49003a766c5c6b3d1" - url: "https://pub.dev" - source: hosted - version: "1.2.3" cached_network_image: dependency: "direct main" description: @@ -221,10 +213,10 @@ packages: dependency: transitive description: name: checked_yaml - sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" url: "https://pub.dev" source: hosted - version: "2.0.3" + version: "2.0.4" cli_config: dependency: transitive description: @@ -261,10 +253,10 @@ packages: dependency: transitive description: name: connectivity_plus - sha256: "051849e2bd7c7b3bc5844ea0d096609ddc3a859890ec3a9ac4a65a2620cc1f99" + sha256: b5e72753cf63becce2c61fd04dfe0f1c430cc5278b53a1342dc5ad839eab29ec url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "6.1.5" connectivity_plus_platform_interface: dependency: transitive description: @@ -294,10 +286,10 @@ packages: dependency: transitive description: name: coverage - sha256: "4b8701e48a58f7712492c9b1f7ba0bb9d525644dd66d023b62e1fc8cdb560c8a" + sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d" url: "https://pub.dev" source: hosted - version: "1.14.0" + version: "1.15.0" credential_manager: dependency: "direct main" description: @@ -310,18 +302,18 @@ packages: dependency: transitive description: name: cross_file - sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" + sha256: "942a4791cd385a68ccb3b32c71c427aba508a1bb949b86dff2adbe4049f16239" url: "https://pub.dev" source: hosted - version: "0.3.4+2" + version: "0.3.5" crypto: dependency: "direct main" description: name: crypto - sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.0.7" cryptography: dependency: "direct main" description: @@ -394,22 +386,6 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.3" - equatable: - dependency: transitive - description: - name: equatable - sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" - url: "https://pub.dev" - source: hosted - version: "2.0.7" - eth_sig_util: - dependency: transitive - description: - name: eth_sig_util - sha256: "20fdc5ce3864e70e5ade1c1cd03cce4ef01018db00adab107303f9055d26b01a" - url: "https://pub.dev" - source: hosted - version: "0.0.9" event: dependency: transitive description: @@ -454,58 +430,58 @@ packages: dependency: "direct main" description: name: file_picker - sha256: ef9908739bdd9c476353d6adff72e88fd00c625f5b959ae23f7567bd5137db0a + sha256: f2d9f173c2c14635cc0e9b14c143c49ef30b4934e8d1d274d6206fcb0086a06f url: "https://pub.dev" source: hosted - version: "10.2.0" + version: "10.3.3" firebase_core: dependency: "direct main" description: name: firebase_core - sha256: "8cfe3c900512399ce8d50fcc817e5758ff8615eeb6fa5c846a4cc47bbf6353b6" + sha256: "7be63a3f841fc9663342f7f3a011a42aef6a61066943c90b1c434d79d5c995c5" url: "https://pub.dev" source: hosted - version: "3.13.1" + version: "3.15.2" firebase_core_platform_interface: dependency: transitive description: name: firebase_core_platform_interface - sha256: d7253d255ff10f85cfd2adaba9ac17bae878fa3ba577462451163bd9f1d1f0bf + sha256: cccb4f572325dc14904c02fcc7db6323ad62ba02536833dddb5c02cac7341c64 url: "https://pub.dev" source: hosted - version: "5.4.0" + version: "6.0.2" firebase_core_web: dependency: transitive description: name: firebase_core_web - sha256: ddd72baa6f727e5b23f32d9af23d7d453d67946f380bd9c21daf474ee0f7326e + sha256: "0ed0dc292e8f9ac50992e2394e9d336a0275b6ae400d64163fdf0a8a8b556c37" url: "https://pub.dev" source: hosted - version: "2.23.0" + version: "2.24.1" firebase_messaging: dependency: "direct main" description: name: firebase_messaging - sha256: "38111089e511f03daa2c66b4c3614c16421b7d78c84ee04331a0a65b47df4542" + sha256: "60be38574f8b5658e2f22b7e311ff2064bea835c248424a383783464e8e02fcc" url: "https://pub.dev" source: hosted - version: "15.2.6" + version: "15.2.10" firebase_messaging_platform_interface: dependency: transitive description: name: firebase_messaging_platform_interface - sha256: ba254769982e5f439e534eed68856181c74e2b3f417c8188afad2bb440807cc7 + sha256: "685e1771b3d1f9c8502771ccc9f91485b376ffe16d553533f335b9183ea99754" url: "https://pub.dev" source: hosted - version: "4.6.6" + version: "4.6.10" firebase_messaging_web: dependency: transitive description: name: firebase_messaging_web - sha256: dba89137272aac39e95f71408ba25c33fb8ed903cd5fa8d1e49b5cd0d96069e0 + sha256: "0d1be17bc89ed3ff5001789c92df678b2e963a51b6fa2bdb467532cc9dbed390" url: "https://pub.dev" source: hosted - version: "3.10.6" + version: "3.10.10" fixnum: dependency: transitive description: @@ -603,10 +579,10 @@ packages: dependency: "direct dev" description: name: flutter_launcher_icons - sha256: bfa04787c85d80ecb3f8777bde5fc10c3de809240c48fa061a2c2bf15ea5211c + sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7" url: "https://pub.dev" source: hosted - version: "0.14.3" + version: "0.14.4" flutter_lints: dependency: "direct dev" description: @@ -632,10 +608,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: f948e346c12f8d5480d2825e03de228d0eb8c3a737e4cdaa122267b89c022b5e + sha256: "306f0596590e077338312f38837f595c04f28d6cdeeac392d3d74df2f0003687" url: "https://pub.dev" source: hosted - version: "2.0.28" + version: "2.0.32" flutter_secure_storage: dependency: "direct main" description: @@ -688,10 +664,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: d44bf546b13025ec7353091516f6881f1d4c633993cb109c3916c3a0159dadf1 + sha256: "055de8921be7b8e8b98a233c7a5ef84b3a6fcc32f46f1ebf5b9bb3576d108355" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.2.2" flutter_test: dependency: "direct dev" description: flutter @@ -706,10 +682,10 @@ packages: dependency: transitive description: name: freezed_annotation - sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 + sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8" url: "https://pub.dev" source: hosted - version: "2.4.4" + version: "3.1.0" frontend_server_client: dependency: transitive description: @@ -730,10 +706,10 @@ packages: dependency: "direct main" description: name: go_router - sha256: "0b1e06223bee260dee31a171fb1153e306907563a0b0225e8c1733211911429a" + sha256: b453934c36e289cef06525734d1e676c1f91da9e22e2017d9dcab6ce0f999175 url: "https://pub.dev" source: hosted - version: "15.1.2" + version: "15.1.3" google_identity_services_web: dependency: transitive description: @@ -826,10 +802,10 @@ packages: dependency: "direct main" description: name: http - sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" + sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007 url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.5.0" http_multi_server: dependency: transitive description: @@ -946,10 +922,10 @@ packages: dependency: transitive description: name: logger - sha256: be4b23575aac7ebf01f225a241eb7f6b5641eeaf43c6a8613510fc2f8cf187d1 + sha256: a7967e31b703831a893bbc3c3dd11db08126fe5f369b5c648a36f821979f5be3 url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.6.2" logging: dependency: transitive description: @@ -962,10 +938,10 @@ packages: dependency: "direct main" description: name: lottie - sha256: c5fa04a80a620066c15cf19cc44773e19e9b38e989ff23ea32e5903ef1015950 + sha256: "8ae0be46dbd9e19641791dc12ee480d34e1fd3f84c749adc05f3ad9342b71b95" url: "https://pub.dev" source: hosted - version: "3.3.1" + version: "3.3.2" markdown: dependency: transitive description: @@ -1010,10 +986,10 @@ packages: dependency: "direct main" description: name: mobile_scanner - sha256: "72f06a071aa8b14acea3ab43ea7949eefe4a2469731ae210e006ba330a033a8c" + sha256: "023a71afb4d7cfb5529d0f2636aa8b43db66257905b9486d702085989769c5f2" url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "7.1.3" mocktail: dependency: "direct dev" description: @@ -1030,6 +1006,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.0" + msgpack_dart: + dependency: transitive + description: + name: msgpack_dart + sha256: c2d235ed01f364719b5296aecf43ac330f0d7bc865fa134d0d7910a40454dffb + url: "https://pub.dev" + source: hosted + version: "1.0.1" nested: dependency: transitive description: @@ -1090,18 +1074,18 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191" + sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968" url: "https://pub.dev" source: hosted - version: "8.3.0" + version: "8.3.1" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c" + sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086" url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.1" path: dependency: "direct main" description: @@ -1130,18 +1114,18 @@ packages: dependency: transitive description: name: path_provider_android - sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 + sha256: e122c5ea805bb6773bb12ce667611265980940145be920cd09a4b0ec0285cb16 url: "https://pub.dev" source: hosted - version: "2.2.17" + version: "2.2.20" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" + sha256: efaec349ddfc181528345c56f8eda9d6cccd71c177511b132c6a0ddaefaa2738 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.3" path_provider_linux: dependency: transitive description: @@ -1170,10 +1154,10 @@ packages: dependency: transitive description: name: petitparser - sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" + sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" url: "https://pub.dev" source: hosted - version: "6.1.0" + version: "7.0.1" platform: dependency: transitive description: @@ -1202,18 +1186,18 @@ packages: dependency: transitive description: name: pool - sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" url: "https://pub.dev" source: hosted - version: "1.5.1" + version: "1.5.2" posix: dependency: transitive description: name: posix - sha256: f0d7856b6ca1887cfa6d1d394056a296ae33489db914e365e2044fdada449e62 + sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61" url: "https://pub.dev" source: hosted - version: "6.0.2" + version: "6.0.3" process_run: dependency: transitive description: @@ -1226,10 +1210,10 @@ packages: dependency: "direct main" description: name: provider - sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84" + sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" url: "https://pub.dev" source: hosted - version: "6.1.5" + version: "6.1.5+1" pub_semver: dependency: transitive description: @@ -1238,14 +1222,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" - pubspec_parse: - dependency: transitive - description: - name: pubspec_parse - sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" - url: "https://pub.dev" - source: hosted - version: "1.5.0" qr: dependency: transitive description: @@ -1274,34 +1250,26 @@ packages: dependency: transitive description: name: reown_core - sha256: "171ce977b563b7b2fe0a17393517a4743ab758152d6c0c5158921a7ed3746326" + sha256: "8d5d14b4e8d008b09ec9db964ab8913b4fc17000d666eb1fece20a80a4b5e37a" url: "https://pub.dev" source: hosted - version: "1.1.5" + version: "1.3.6" reown_sign: dependency: transitive description: name: reown_sign - sha256: eb24d5b201635b0adbe15182c4cd2f831642dde05f59f033dac8de3bd423495d + sha256: c2fec55ed3d0042d0802c80d7fc36f9b1937eac6ef9c2907dae3b8146188cffa url: "https://pub.dev" source: hosted - version: "1.1.6" + version: "1.3.7" reown_walletkit: dependency: "direct main" description: name: reown_walletkit - sha256: e639dbfac019270380b8f80105e11364f4962ff78fdc95d01aee16ea6f27fb2a + sha256: f96cc9f6e264138b3411fd1635e06d437d5912d7a21ef28af21974e2680dfd70 url: "https://pub.dev" source: hosted - version: "1.1.5+1" - reown_yttrium: - dependency: transitive - description: - name: reown_yttrium - sha256: e59d9e8dbad8e2c420c201719da12bca528c38ecab9571364d5b6d18715ce773 - url: "https://pub.dev" - source: hosted - version: "0.0.1" + version: "1.3.7" rxdart: dependency: "direct main" description: @@ -1322,18 +1290,18 @@ packages: dependency: "direct main" description: name: share_plus - sha256: b2961506569e28948d75ec346c28775bb111986bb69dc6a20754a457e3d97fa0 + sha256: d7dc0630a923883c6328ca31b89aa682bacbf2f8304162d29f7c6aaff03a27a1 url: "https://pub.dev" source: hosted - version: "11.0.0" + version: "11.1.0" share_plus_platform_interface: dependency: transitive description: name: share_plus_platform_interface - sha256: "1032d392bc5d2095a77447a805aa3f804d2ae6a4d5eef5e6ebb3bd94c1bc19ef" + sha256: "88023e53a13429bd65d8e85e11a9b484f49d4c190abbd96c7932b74d6927cc9a" url: "https://pub.dev" source: hosted - version: "6.0.0" + version: "6.1.0" shared_preferences: dependency: "direct main" description: @@ -1346,18 +1314,18 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac" + sha256: "34266009473bf71d748912da4bf62d439185226c03e01e2d9687bc65bbfcb713" url: "https://pub.dev" source: hosted - version: "2.4.10" + version: "2.4.15" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + sha256: "1c33a907142607c40a7542768ec9badfd16293bac51da3a4482623d15845f88b" url: "https://pub.dev" source: hosted - version: "2.5.4" + version: "2.5.5" shared_preferences_linux: dependency: transitive description: @@ -1468,14 +1436,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.1" - sprintf: - dependency: transitive - description: - name: sprintf - sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" - url: "https://pub.dev" - source: hosted - version: "7.0.0" sqflite: dependency: "direct main" description: @@ -1488,34 +1448,34 @@ packages: dependency: transitive description: name: sqflite_android - sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b" + sha256: ecd684501ebc2ae9a83536e8b15731642b9570dc8623e0073d227d0ee2bfea88 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2+2" sqflite_common: dependency: "direct main" description: name: sqflite_common - sha256: "84731e8bfd8303a3389903e01fb2141b6e59b5973cacbb0929021df08dddbe8b" + sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6" url: "https://pub.dev" source: hosted - version: "2.5.5" + version: "2.5.6" sqflite_common_ffi: dependency: transitive description: name: sqflite_common_ffi - sha256: "1f3ef3888d3bfbb47785cc1dda0dc7dd7ebd8c1955d32a9e8e9dae1e38d1c4c1" + sha256: "9faa2fedc5385ef238ce772589f7718c24cdddd27419b609bb9c6f703ea27988" url: "https://pub.dev" source: hosted - version: "2.3.5" + version: "2.3.6" sqflite_common_ffi_web: dependency: "direct main" description: name: sqflite_common_ffi_web - sha256: "983cf7b33b16e6bc086c8e09f6a1fae69d34cdb167d7acaf64cbd3515942d4e6" + sha256: "793c1ff5b0c95ac618e7731e209db99e96abff59ad3432a3c91bd2b1454a00d5" url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.0.1+2" sqflite_darwin: dependency: transitive description: @@ -1536,10 +1496,10 @@ packages: dependency: transitive description: name: sqlite3 - sha256: c0503c69b44d5714e6abbf4c1f51a3c3cc42b75ce785f44404765e4635481d38 + sha256: "3145bd74dcdb4fd6f5c6dda4d4e4490a8087d7f286a14dee5d37087290f0f8a2" url: "https://pub.dev" source: hosted - version: "2.7.6" + version: "2.9.4" stack_trace: dependency: transitive description: @@ -1576,10 +1536,10 @@ packages: dependency: transitive description: name: synchronized - sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6" + sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 url: "https://pub.dev" source: hosted - version: "3.3.1" + version: "3.4.0" term_glyph: dependency: transitive description: @@ -1648,26 +1608,26 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 url: "https://pub.dev" source: hosted - version: "6.3.1" + version: "6.3.2" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "8582d7f6fe14d2652b4c45c9b6c14c0b678c2af2d083a11b604caeba51930d79" + sha256: "5c8b6c2d89a78f5a1cca70a73d9d5f86c701b36b42f9c9dac7bad592113c28e9" url: "https://pub.dev" source: hosted - version: "6.3.16" + version: "6.3.24" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb" + sha256: "6b63f1441e4f653ae799166a72b50b1767321ecc263a57aadf825a7a2a5477d9" url: "https://pub.dev" source: hosted - version: "6.3.3" + version: "6.3.5" url_launcher_linux: dependency: transitive description: @@ -1680,10 +1640,10 @@ packages: dependency: transitive description: name: url_launcher_macos - sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" + sha256: "8262208506252a3ed4ff5c0dc1e973d2c0e0ef337d0a074d35634da5d44397c9" url: "https://pub.dev" source: hosted - version: "3.2.2" + version: "3.2.4" url_launcher_platform_interface: dependency: transitive description: @@ -1720,18 +1680,18 @@ packages: dependency: transitive description: name: uuid - sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8 url: "https://pub.dev" source: hosted - version: "4.5.1" + version: "4.5.2" vector_graphics: dependency: transitive description: name: vector_graphics - sha256: "44cc7104ff32563122a929e4620cf3efd584194eec6d1d913eb5ba593dbcf6de" + sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6 url: "https://pub.dev" source: hosted - version: "1.1.18" + version: "1.1.19" vector_graphics_codec: dependency: transitive description: @@ -1744,10 +1704,10 @@ packages: dependency: transitive description: name: vector_graphics_compiler - sha256: "557a315b7d2a6dbb0aaaff84d857967ce6bdc96a63dc6ee2a57ce5a6ee5d3331" + sha256: d354a7ec6931e6047785f4db12a1f61ec3d43b207fc0790f863818543f8ff0dc url: "https://pub.dev" source: hosted - version: "1.1.17" + version: "1.1.19" vector_math: dependency: transitive description: @@ -1760,10 +1720,10 @@ packages: dependency: transitive description: name: vm_service - sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" url: "https://pub.dev" source: hosted - version: "15.0.0" + version: "15.0.2" wallet: dependency: transitive description: @@ -1776,10 +1736,10 @@ packages: dependency: transitive description: name: watcher - sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" + sha256: "592ab6e2892f67760543fb712ff0177f4ec76c031f02f5b4ff8d3fc5eb9fb61a" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.4" web: dependency: transitive description: @@ -1824,10 +1784,10 @@ packages: dependency: transitive description: name: win32 - sha256: "329edf97fdd893e0f1e3b9e88d6a0e627128cc17cc316a8d67fda8f1451178ba" + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e url: "https://pub.dev" source: hosted - version: "5.13.0" + version: "5.15.0" x25519: dependency: transitive description: @@ -1848,10 +1808,10 @@ packages: dependency: transitive description: name: xml - sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" url: "https://pub.dev" source: hosted - version: "6.5.0" + version: "6.6.1" yaml: dependency: transitive description: @@ -1861,5 +1821,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.8.0-0 <4.0.0" - flutter: ">=3.29.0" + dart: ">=3.9.0 <4.0.0" + flutter: ">=3.35.0" diff --git a/pubspec.yaml b/pubspec.yaml index acaff689..9a8c05fd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: citizenwallet -version: 2.0.28+283 +version: 2.0.29+286 publish_to: none description: A mobile wallet for your community. environment: