Compare commits
7 Commits
f2658aea44
...
SEND003
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dfbf36bf04 | ||
|
|
b16c295094 | ||
|
|
336687eccf | ||
| f478219990 | |||
|
|
bf39b1d401 | ||
| f7bf3138ac | |||
|
|
7cb747f9a9 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -9,3 +9,4 @@ untranslated.txt
|
|||||||
generate_protos.sh
|
generate_protos.sh
|
||||||
update_dep.sh
|
update_dep.sh
|
||||||
.vscode/
|
.vscode/
|
||||||
|
GeneratedPluginRegistrant.swift
|
||||||
@@ -55,3 +55,6 @@ key_management:
|
|||||||
namespace: ""
|
namespace: ""
|
||||||
mount_path: kv
|
mount_path: kv
|
||||||
key_prefix: gateway/chain/wallets
|
key_prefix: gateway/chain/wallets
|
||||||
|
|
||||||
|
cache:
|
||||||
|
wallet_balance_ttl_seconds: 120
|
||||||
|
|||||||
@@ -34,9 +34,10 @@ type Imp struct {
|
|||||||
|
|
||||||
type config struct {
|
type config struct {
|
||||||
*grpcapp.Config `yaml:",inline"`
|
*grpcapp.Config `yaml:",inline"`
|
||||||
Chains []chainConfig `yaml:"chains"`
|
Chains []chainConfig `yaml:"chains"`
|
||||||
ServiceWallet serviceWalletConfig `yaml:"service_wallet"`
|
ServiceWallet serviceWalletConfig `yaml:"service_wallet"`
|
||||||
KeyManagement keymanager.Config `yaml:"key_management"`
|
KeyManagement keymanager.Config `yaml:"key_management"`
|
||||||
|
Settings gatewayservice.CacheSettings `yaml:"cache"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type chainConfig struct {
|
type chainConfig struct {
|
||||||
@@ -111,6 +112,7 @@ func (i *Imp) Start() error {
|
|||||||
gatewayservice.WithServiceWallet(walletConfig),
|
gatewayservice.WithServiceWallet(walletConfig),
|
||||||
gatewayservice.WithKeyManager(keyManager),
|
gatewayservice.WithKeyManager(keyManager),
|
||||||
gatewayservice.WithTransferExecutor(executor),
|
gatewayservice.WithTransferExecutor(executor),
|
||||||
|
gatewayservice.WithSettings(cfg.Settings),
|
||||||
}
|
}
|
||||||
return gatewayservice.NewService(logger, repo, producer, opts...), nil
|
return gatewayservice.NewService(logger, repo, producer, opts...), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/tech/sendico/gateway/chain/internal/service/gateway/shared"
|
||||||
|
"github.com/tech/sendico/gateway/chain/storage/model"
|
||||||
"github.com/tech/sendico/pkg/api/routers/gsresponse"
|
"github.com/tech/sendico/pkg/api/routers/gsresponse"
|
||||||
"github.com/tech/sendico/pkg/merrors"
|
"github.com/tech/sendico/pkg/merrors"
|
||||||
"github.com/tech/sendico/pkg/mservice"
|
"github.com/tech/sendico/pkg/mservice"
|
||||||
@@ -14,6 +17,8 @@ import (
|
|||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const fallbackBalanceCacheTTL = 2 * time.Minute
|
||||||
|
|
||||||
type getWalletBalanceCommand struct {
|
type getWalletBalanceCommand struct {
|
||||||
deps Deps
|
deps Deps
|
||||||
}
|
}
|
||||||
@@ -48,30 +53,88 @@ func (c *getWalletBalanceCommand) Execute(ctx context.Context, req *chainv1.GetW
|
|||||||
|
|
||||||
balance, chainErr := onChainWalletBalance(ctx, c.deps, wallet)
|
balance, chainErr := onChainWalletBalance(ctx, c.deps, wallet)
|
||||||
if chainErr != nil {
|
if chainErr != nil {
|
||||||
c.deps.Logger.Warn("on-chain balance fetch failed, falling back to stored balance", zap.Error(chainErr), zap.String("wallet_ref", walletRef))
|
c.deps.Logger.Warn("on-chain balance fetch failed, attempting cached balance", zap.Error(chainErr), zap.String("wallet_ref", walletRef))
|
||||||
stored, err := c.deps.Storage.Wallets().GetBalance(ctx, walletRef)
|
stored, err := c.deps.Storage.Wallets().GetBalance(ctx, walletRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, merrors.ErrNoData) {
|
if errors.Is(err, merrors.ErrNoData) {
|
||||||
c.deps.Logger.Warn("stored balance not found", zap.String("wallet_ref", walletRef))
|
c.deps.Logger.Warn("cached balance not found", zap.String("wallet_ref", walletRef))
|
||||||
return gsresponse.NotFound[chainv1.GetWalletBalanceResponse](c.deps.Logger, mservice.ChainGateway, err)
|
return gsresponse.Auto[chainv1.GetWalletBalanceResponse](c.deps.Logger, mservice.ChainGateway, chainErr)
|
||||||
}
|
}
|
||||||
return gsresponse.Auto[chainv1.GetWalletBalanceResponse](c.deps.Logger, mservice.ChainGateway, err)
|
return gsresponse.Auto[chainv1.GetWalletBalanceResponse](c.deps.Logger, mservice.ChainGateway, err)
|
||||||
}
|
}
|
||||||
|
if c.isCachedBalanceStale(stored) {
|
||||||
|
c.deps.Logger.Warn("cached balance is stale",
|
||||||
|
zap.String("wallet_ref", walletRef),
|
||||||
|
zap.Time("calculated_at", stored.CalculatedAt),
|
||||||
|
zap.Duration("ttl", c.cacheTTL()),
|
||||||
|
)
|
||||||
|
return gsresponse.Auto[chainv1.GetWalletBalanceResponse](c.deps.Logger, mservice.ChainGateway, chainErr)
|
||||||
|
}
|
||||||
return gsresponse.Success(&chainv1.GetWalletBalanceResponse{Balance: toProtoWalletBalance(stored)})
|
return gsresponse.Success(&chainv1.GetWalletBalanceResponse{Balance: toProtoWalletBalance(stored)})
|
||||||
}
|
}
|
||||||
|
|
||||||
return gsresponse.Success(&chainv1.GetWalletBalanceResponse{Balance: onChainBalanceToProto(balance)})
|
calculatedAt := c.now()
|
||||||
|
c.persistCachedBalance(ctx, walletRef, balance, calculatedAt)
|
||||||
|
|
||||||
|
return gsresponse.Success(&chainv1.GetWalletBalanceResponse{
|
||||||
|
Balance: onChainBalanceToProto(balance, calculatedAt),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func onChainBalanceToProto(balance *moneyv1.Money) *chainv1.WalletBalance {
|
func onChainBalanceToProto(balance *moneyv1.Money, calculatedAt time.Time) *chainv1.WalletBalance {
|
||||||
if balance == nil {
|
if balance == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
zero := &moneyv1.Money{Currency: balance.Currency, Amount: "0"}
|
zero := zeroMoney(balance.Currency)
|
||||||
return &chainv1.WalletBalance{
|
return &chainv1.WalletBalance{
|
||||||
Available: balance,
|
Available: balance,
|
||||||
PendingInbound: zero,
|
PendingInbound: zero,
|
||||||
PendingOutbound: zero,
|
PendingOutbound: zero,
|
||||||
CalculatedAt: timestamppb.Now(),
|
CalculatedAt: timestamppb.New(calculatedAt.UTC()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *getWalletBalanceCommand) persistCachedBalance(ctx context.Context, walletRef string, available *moneyv1.Money, calculatedAt time.Time) {
|
||||||
|
if available == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
record := &model.WalletBalance{
|
||||||
|
WalletRef: walletRef,
|
||||||
|
Available: shared.CloneMoney(available),
|
||||||
|
PendingInbound: zeroMoney(available.Currency),
|
||||||
|
PendingOutbound: zeroMoney(available.Currency),
|
||||||
|
CalculatedAt: calculatedAt,
|
||||||
|
}
|
||||||
|
if err := c.deps.Storage.Wallets().SaveBalance(ctx, record); err != nil {
|
||||||
|
c.deps.Logger.Warn("failed to cache wallet balance", zap.String("wallet_ref", walletRef), zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *getWalletBalanceCommand) isCachedBalanceStale(balance *model.WalletBalance) bool {
|
||||||
|
if balance == nil || balance.CalculatedAt.IsZero() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return c.now().After(balance.CalculatedAt.Add(c.cacheTTL()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *getWalletBalanceCommand) cacheTTL() time.Duration {
|
||||||
|
if c.deps.BalanceCacheTTL > 0 {
|
||||||
|
return c.deps.BalanceCacheTTL
|
||||||
|
}
|
||||||
|
// Fallback to sane default if not configured.
|
||||||
|
return fallbackBalanceCacheTTL
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *getWalletBalanceCommand) now() time.Time {
|
||||||
|
if c.deps.Clock != nil {
|
||||||
|
return c.deps.Clock.Now().UTC()
|
||||||
|
}
|
||||||
|
return time.Now().UTC()
|
||||||
|
}
|
||||||
|
|
||||||
|
func zeroMoney(currency string) *moneyv1.Money {
|
||||||
|
if strings.TrimSpace(currency) == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &moneyv1.Money{Currency: currency, Amount: "0"}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ package wallet
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/tech/sendico/gateway/chain/internal/keymanager"
|
"github.com/tech/sendico/gateway/chain/internal/keymanager"
|
||||||
"github.com/tech/sendico/gateway/chain/internal/service/gateway/shared"
|
"github.com/tech/sendico/gateway/chain/internal/service/gateway/shared"
|
||||||
"github.com/tech/sendico/gateway/chain/storage"
|
"github.com/tech/sendico/gateway/chain/storage"
|
||||||
|
clockpkg "github.com/tech/sendico/pkg/clock"
|
||||||
"github.com/tech/sendico/pkg/mlogger"
|
"github.com/tech/sendico/pkg/mlogger"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,6 +16,8 @@ type Deps struct {
|
|||||||
Networks map[string]shared.Network
|
Networks map[string]shared.Network
|
||||||
KeyManager keymanager.Manager
|
KeyManager keymanager.Manager
|
||||||
Storage storage.Repository
|
Storage storage.Repository
|
||||||
|
Clock clockpkg.Clock
|
||||||
|
BalanceCacheTTL time.Duration
|
||||||
EnsureRepository func(context.Context) error
|
EnsureRepository func(context.Context) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,3 +67,10 @@ func WithClock(clk clockpkg.Clock) Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithSettings applies gateway settings.
|
||||||
|
func WithSettings(settings CacheSettings) Option {
|
||||||
|
return func(s *Service) {
|
||||||
|
s.settings = settings.withDefaults()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ type Service struct {
|
|||||||
producer msg.Producer
|
producer msg.Producer
|
||||||
clock clockpkg.Clock
|
clock clockpkg.Clock
|
||||||
|
|
||||||
|
settings CacheSettings
|
||||||
|
|
||||||
networks map[string]shared.Network
|
networks map[string]shared.Network
|
||||||
serviceWallet shared.ServiceWallet
|
serviceWallet shared.ServiceWallet
|
||||||
keyManager keymanager.Manager
|
keyManager keymanager.Manager
|
||||||
@@ -52,6 +54,7 @@ func NewService(logger mlogger.Logger, repo storage.Repository, producer msg.Pro
|
|||||||
storage: repo,
|
storage: repo,
|
||||||
producer: producer,
|
producer: producer,
|
||||||
clock: clockpkg.System{},
|
clock: clockpkg.System{},
|
||||||
|
settings: defaultSettings(),
|
||||||
networks: map[string]shared.Network{},
|
networks: map[string]shared.Network{},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,6 +72,7 @@ func NewService(logger mlogger.Logger, repo storage.Repository, producer msg.Pro
|
|||||||
if svc.networks == nil {
|
if svc.networks == nil {
|
||||||
svc.networks = map[string]shared.Network{}
|
svc.networks = map[string]shared.Network{}
|
||||||
}
|
}
|
||||||
|
svc.settings = svc.settings.withDefaults()
|
||||||
|
|
||||||
svc.commands = commands.NewRegistry(commands.RegistryDeps{
|
svc.commands = commands.NewRegistry(commands.RegistryDeps{
|
||||||
Wallet: commandsWalletDeps(svc),
|
Wallet: commandsWalletDeps(svc),
|
||||||
@@ -130,6 +134,8 @@ func commandsWalletDeps(s *Service) wallet.Deps {
|
|||||||
Networks: s.networks,
|
Networks: s.networks,
|
||||||
KeyManager: s.keyManager,
|
KeyManager: s.keyManager,
|
||||||
Storage: s.storage,
|
Storage: s.storage,
|
||||||
|
Clock: s.clock,
|
||||||
|
BalanceCacheTTL: s.settings.walletBalanceCacheTTL(),
|
||||||
EnsureRepository: s.ensureRepository,
|
EnsureRepository: s.ensureRepository,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
30
api/gateway/chain/internal/service/gateway/settings.go
Normal file
30
api/gateway/chain/internal/service/gateway/settings.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package gateway
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
const defaultWalletBalanceCacheTTL = 120 * time.Second
|
||||||
|
|
||||||
|
// CacheSettings holds tunable gateway behaviour.
|
||||||
|
type CacheSettings struct {
|
||||||
|
WalletBalanceCacheTTLSeconds int `yaml:"wallet_balance_ttl_seconds"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultSettings() CacheSettings {
|
||||||
|
return CacheSettings{
|
||||||
|
WalletBalanceCacheTTLSeconds: int(defaultWalletBalanceCacheTTL.Seconds()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s CacheSettings) withDefaults() CacheSettings {
|
||||||
|
if s.WalletBalanceCacheTTLSeconds <= 0 {
|
||||||
|
s.WalletBalanceCacheTTLSeconds = int(defaultWalletBalanceCacheTTL.Seconds())
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s CacheSettings) walletBalanceCacheTTL() time.Duration {
|
||||||
|
if s.WalletBalanceCacheTTLSeconds <= 0 {
|
||||||
|
return defaultWalletBalanceCacheTTL
|
||||||
|
}
|
||||||
|
return time.Duration(s.WalletBalanceCacheTTLSeconds) * time.Second
|
||||||
|
}
|
||||||
@@ -27,6 +27,7 @@ class AccountProvider extends ChangeNotifier {
|
|||||||
Resource<Account?> get resource => _resource;
|
Resource<Account?> get resource => _resource;
|
||||||
late LocaleProvider _localeProvider;
|
late LocaleProvider _localeProvider;
|
||||||
PendingLogin? _pendingLogin;
|
PendingLogin? _pendingLogin;
|
||||||
|
Future<void>? _restoreFuture;
|
||||||
|
|
||||||
Account? get account => _resource.data;
|
Account? get account => _resource.data;
|
||||||
PendingLogin? get pendingLogin => _pendingLogin;
|
PendingLogin? get pendingLogin => _pendingLogin;
|
||||||
@@ -34,6 +35,7 @@ class AccountProvider extends ChangeNotifier {
|
|||||||
bool get isLoading => _resource.isLoading;
|
bool get isLoading => _resource.isLoading;
|
||||||
Object? get error => _resource.error;
|
Object? get error => _resource.error;
|
||||||
bool get isReady => (!isLoading) && (account != null);
|
bool get isReady => (!isLoading) && (account != null);
|
||||||
|
Future<void>? get restoreFuture => _restoreFuture;
|
||||||
|
|
||||||
Account? currentUser() {
|
Account? currentUser() {
|
||||||
final acc = account;
|
final acc = account;
|
||||||
@@ -220,4 +222,12 @@ class AccountProvider extends ChangeNotifier {
|
|||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> restoreIfPossible() {
|
||||||
|
return _restoreFuture ??= () async {
|
||||||
|
final hasAuth = await AuthorizationService.isAuthorizationStored();
|
||||||
|
if (!hasAuth) return;
|
||||||
|
await restore();
|
||||||
|
}();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
112
frontend/pweb/lib/app/router/payout_routes.dart
Normal file
112
frontend/pweb/lib/app/router/payout_routes.dart
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/widgets/sidebar/destinations.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class PayoutRoutes {
|
||||||
|
static const dashboard = 'dashboard';
|
||||||
|
static const sendPayout = payment;
|
||||||
|
static const recipients = 'payout-recipients';
|
||||||
|
static const addRecipient = 'payout-add-recipient';
|
||||||
|
static const payment = 'payout-payment';
|
||||||
|
static const settings = 'payout-settings';
|
||||||
|
static const reports = 'payout-reports';
|
||||||
|
static const methods = 'payout-methods';
|
||||||
|
static const editWallet = 'payout-edit-wallet';
|
||||||
|
static const walletTopUp = 'payout-wallet-top-up';
|
||||||
|
|
||||||
|
static const dashboardPath = '/dashboard';
|
||||||
|
static const recipientsPath = '/dashboard/recipients';
|
||||||
|
static const addRecipientPath = '/dashboard/recipients/add';
|
||||||
|
static const paymentPath = '/dashboard/payment';
|
||||||
|
static const settingsPath = '/dashboard/settings';
|
||||||
|
static const reportsPath = '/dashboard/reports';
|
||||||
|
static const methodsPath = '/dashboard/methods';
|
||||||
|
static const editWalletPath = '/dashboard/methods/edit';
|
||||||
|
static const walletTopUpPath = '/dashboard/wallet/top-up';
|
||||||
|
|
||||||
|
static String nameFor(PayoutDestination destination) {
|
||||||
|
switch (destination) {
|
||||||
|
case PayoutDestination.dashboard:
|
||||||
|
return dashboard;
|
||||||
|
case PayoutDestination.sendPayout:
|
||||||
|
return payment;
|
||||||
|
case PayoutDestination.recipients:
|
||||||
|
return recipients;
|
||||||
|
case PayoutDestination.addrecipient:
|
||||||
|
return addRecipient;
|
||||||
|
case PayoutDestination.payment:
|
||||||
|
return payment;
|
||||||
|
case PayoutDestination.settings:
|
||||||
|
return settings;
|
||||||
|
case PayoutDestination.reports:
|
||||||
|
return reports;
|
||||||
|
case PayoutDestination.methods:
|
||||||
|
return methods;
|
||||||
|
case PayoutDestination.editwallet:
|
||||||
|
return editWallet;
|
||||||
|
case PayoutDestination.walletTopUp:
|
||||||
|
return walletTopUp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static String pathFor(PayoutDestination destination) {
|
||||||
|
switch (destination) {
|
||||||
|
case PayoutDestination.dashboard:
|
||||||
|
return dashboardPath;
|
||||||
|
case PayoutDestination.sendPayout:
|
||||||
|
return paymentPath;
|
||||||
|
case PayoutDestination.recipients:
|
||||||
|
return recipientsPath;
|
||||||
|
case PayoutDestination.addrecipient:
|
||||||
|
return addRecipientPath;
|
||||||
|
case PayoutDestination.payment:
|
||||||
|
return paymentPath;
|
||||||
|
case PayoutDestination.settings:
|
||||||
|
return settingsPath;
|
||||||
|
case PayoutDestination.reports:
|
||||||
|
return reportsPath;
|
||||||
|
case PayoutDestination.methods:
|
||||||
|
return methodsPath;
|
||||||
|
case PayoutDestination.editwallet:
|
||||||
|
return editWalletPath;
|
||||||
|
case PayoutDestination.walletTopUp:
|
||||||
|
return walletTopUpPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static PayoutDestination? destinationFor(String? routeName) {
|
||||||
|
switch (routeName) {
|
||||||
|
case dashboard:
|
||||||
|
return PayoutDestination.dashboard;
|
||||||
|
case sendPayout:
|
||||||
|
return PayoutDestination.payment;
|
||||||
|
case recipients:
|
||||||
|
return PayoutDestination.recipients;
|
||||||
|
case addRecipient:
|
||||||
|
return PayoutDestination.addrecipient;
|
||||||
|
case payment:
|
||||||
|
return PayoutDestination.payment;
|
||||||
|
case settings:
|
||||||
|
return PayoutDestination.settings;
|
||||||
|
case reports:
|
||||||
|
return PayoutDestination.reports;
|
||||||
|
case methods:
|
||||||
|
return PayoutDestination.methods;
|
||||||
|
case editWallet:
|
||||||
|
return PayoutDestination.editwallet;
|
||||||
|
case walletTopUp:
|
||||||
|
return PayoutDestination.walletTopUp;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PayoutNavigation on BuildContext {
|
||||||
|
void goToPayout(PayoutDestination destination) => goNamed(PayoutRoutes.nameFor(destination));
|
||||||
|
|
||||||
|
void pushToPayout(PayoutDestination destination) => pushNamed(PayoutRoutes.nameFor(destination));
|
||||||
|
}
|
||||||
160
frontend/pweb/lib/app/router/payout_shell.dart
Normal file
160
frontend/pweb/lib/app/router/payout_shell.dart
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/provider/recipient/provider.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/app/router/pages.dart';
|
||||||
|
import 'package:pweb/app/router/payout_routes.dart';
|
||||||
|
import 'package:pweb/pages/address_book/form/page.dart';
|
||||||
|
import 'package:pweb/pages/address_book/page/page.dart';
|
||||||
|
import 'package:pweb/pages/dashboard/dashboard.dart';
|
||||||
|
import 'package:pweb/pages/payment_methods/page.dart';
|
||||||
|
import 'package:pweb/pages/payout_page/page.dart';
|
||||||
|
import 'package:pweb/pages/payout_page/wallet/edit/page.dart';
|
||||||
|
import 'package:pweb/pages/report/page.dart';
|
||||||
|
import 'package:pweb/pages/settings/profile/page.dart';
|
||||||
|
import 'package:pweb/pages/wallet_top_up/page.dart';
|
||||||
|
import 'package:pweb/providers/page_selector.dart';
|
||||||
|
import 'package:pweb/widgets/error/snackbar.dart';
|
||||||
|
import 'package:pweb/widgets/sidebar/destinations.dart';
|
||||||
|
import 'package:pweb/widgets/sidebar/page.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
|
RouteBase payoutShellRoute() => ShellRoute(
|
||||||
|
builder: (context, state, child) => PageSelector(
|
||||||
|
child: child,
|
||||||
|
routerState: state,
|
||||||
|
),
|
||||||
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
name: PayoutRoutes.dashboard,
|
||||||
|
path: routerPage(Pages.dashboard),
|
||||||
|
pageBuilder: (context, _) => NoTransitionPage(
|
||||||
|
child: DashboardPage(
|
||||||
|
onRecipientSelected: (recipient) => context
|
||||||
|
.read<PageSelectorProvider>()
|
||||||
|
.selectRecipient(context, recipient),
|
||||||
|
onGoToPaymentWithoutRecipient: (type) => context
|
||||||
|
.read<PageSelectorProvider>()
|
||||||
|
.startPaymentWithoutRecipient(context, type),
|
||||||
|
onTopUp: (wallet) => context
|
||||||
|
.read<PageSelectorProvider>()
|
||||||
|
.openWalletTopUp(context, wallet),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
name: PayoutRoutes.recipients,
|
||||||
|
path: PayoutRoutes.recipientsPath,
|
||||||
|
pageBuilder: (context, _) {
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
|
return NoTransitionPage(
|
||||||
|
child: RecipientAddressBookPage(
|
||||||
|
onRecipientSelected: (recipient) => context
|
||||||
|
.read<PageSelectorProvider>()
|
||||||
|
.selectRecipient(context, recipient, fromList: true),
|
||||||
|
onAddRecipient: () => context
|
||||||
|
.read<PageSelectorProvider>()
|
||||||
|
.goToAddRecipient(context),
|
||||||
|
onEditRecipient: (recipient) => context
|
||||||
|
.read<PageSelectorProvider>()
|
||||||
|
.editRecipient(context, recipient, fromList: true),
|
||||||
|
onDeleteRecipient: (recipient) => executeActionWithNotification(
|
||||||
|
context: context,
|
||||||
|
action: () async =>
|
||||||
|
context.read<RecipientsProvider>().delete(recipient.id),
|
||||||
|
successMessage: loc.recipientDeletedSuccessfully,
|
||||||
|
errorMessage: loc.errorDeleteRecipient,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
name: PayoutRoutes.addRecipient,
|
||||||
|
path: PayoutRoutes.addRecipientPath,
|
||||||
|
pageBuilder: (context, _) {
|
||||||
|
final selector = context.read<PageSelectorProvider>();
|
||||||
|
final recipient = selector.recipientProvider.currentObject;
|
||||||
|
return NoTransitionPage(
|
||||||
|
child: AdressBookRecipientForm(
|
||||||
|
recipient: recipient,
|
||||||
|
onSaved: (_) => selector.selectPage(
|
||||||
|
context,
|
||||||
|
PayoutDestination.recipients,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
name: PayoutRoutes.payment,
|
||||||
|
path: PayoutRoutes.paymentPath,
|
||||||
|
pageBuilder: (context, _) => NoTransitionPage(
|
||||||
|
child: PaymentPage(
|
||||||
|
onBack: (_) => context
|
||||||
|
.read<PageSelectorProvider>()
|
||||||
|
.goBackFromPayment(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
name: PayoutRoutes.settings,
|
||||||
|
path: PayoutRoutes.settingsPath,
|
||||||
|
pageBuilder: (_, __) => const NoTransitionPage(
|
||||||
|
child: ProfileSettingsPage(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
name: PayoutRoutes.reports,
|
||||||
|
path: PayoutRoutes.reportsPath,
|
||||||
|
pageBuilder: (_, __) => const NoTransitionPage(
|
||||||
|
child: OperationHistoryPage(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
name: PayoutRoutes.methods,
|
||||||
|
path: PayoutRoutes.methodsPath,
|
||||||
|
pageBuilder: (context, _) => NoTransitionPage(
|
||||||
|
child: PaymentConfigPage(
|
||||||
|
onWalletTap: (wallet) => context
|
||||||
|
.read<PageSelectorProvider>()
|
||||||
|
.selectWallet(context, wallet),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
name: PayoutRoutes.editWallet,
|
||||||
|
path: PayoutRoutes.editWalletPath,
|
||||||
|
pageBuilder: (context, _) {
|
||||||
|
final provider = context.read<PageSelectorProvider>();
|
||||||
|
final wallet = provider.walletsProvider.selectedWallet;
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
|
return NoTransitionPage(
|
||||||
|
child: wallet != null
|
||||||
|
? WalletEditPage(
|
||||||
|
onBack: () => provider.goBackFromWalletEdit(context),
|
||||||
|
)
|
||||||
|
: Center(child: Text(loc.noWalletSelected)),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
name: PayoutRoutes.walletTopUp,
|
||||||
|
path: PayoutRoutes.walletTopUpPath,
|
||||||
|
pageBuilder: (context, _) => NoTransitionPage(
|
||||||
|
child: WalletTopUpPage(
|
||||||
|
onBack: () => context
|
||||||
|
.read<PageSelectorProvider>()
|
||||||
|
.goBackFromWalletTopUp(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
import 'package:pweb/app/router/pages.dart';
|
|
||||||
import 'package:pweb/app/router/page_params.dart';
|
import 'package:pweb/app/router/page_params.dart';
|
||||||
|
import 'package:pweb/app/router/pages.dart';
|
||||||
|
import 'package:pweb/app/router/payout_shell.dart';
|
||||||
|
import 'package:pweb/app/router/payout_routes.dart';
|
||||||
import 'package:pweb/pages/2fa/page.dart';
|
import 'package:pweb/pages/2fa/page.dart';
|
||||||
|
import 'package:pweb/pages/errors/not_found.dart';
|
||||||
|
import 'package:pweb/pages/login/page.dart';
|
||||||
import 'package:pweb/pages/signup/page.dart';
|
import 'package:pweb/pages/signup/page.dart';
|
||||||
import 'package:pweb/pages/verification/page.dart';
|
import 'package:pweb/pages/verification/page.dart';
|
||||||
import 'package:pweb/widgets/sidebar/page.dart';
|
|
||||||
import 'package:pweb/pages/login/page.dart';
|
|
||||||
import 'package:pweb/pages/errors/not_found.dart';
|
|
||||||
|
|
||||||
|
|
||||||
GoRouter createRouter() => GoRouter(
|
GoRouter createRouter() => GoRouter(
|
||||||
@@ -16,40 +17,33 @@ GoRouter createRouter() => GoRouter(
|
|||||||
GoRoute(
|
GoRoute(
|
||||||
name: Pages.root.name,
|
name: Pages.root.name,
|
||||||
path: routerPage(Pages.root),
|
path: routerPage(Pages.root),
|
||||||
builder: (_, _) => const LoginPage(),
|
builder: (_, __) => const LoginPage(),
|
||||||
routes: [
|
|
||||||
GoRoute(
|
|
||||||
name: Pages.login.name,
|
|
||||||
path: routerPage(Pages.login),
|
|
||||||
builder: (_, _) => const LoginPage(),
|
|
||||||
),
|
|
||||||
GoRoute(
|
|
||||||
name: Pages.dashboard.name,
|
|
||||||
path: routerPage(Pages.dashboard),
|
|
||||||
builder: (_, _) => const PageSelector(),
|
|
||||||
),
|
|
||||||
GoRoute(
|
|
||||||
name: Pages.sfactor.name,
|
|
||||||
path: routerPage(Pages.sfactor),
|
|
||||||
builder: (context, _) => TwoFactorCodePage(
|
|
||||||
onVerificationSuccess: () {
|
|
||||||
// trigger organization load
|
|
||||||
context.goNamed(Pages.dashboard.name);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
GoRoute(
|
|
||||||
name: Pages.signup.name,
|
|
||||||
path: routerPage(Pages.signup),
|
|
||||||
builder: (_, _) => const SignUpPage(),
|
|
||||||
),
|
|
||||||
GoRoute(
|
|
||||||
name: Pages.verify.name,
|
|
||||||
path: '${routerPage(Pages.verify)}${routerAddParam(PageParams.token)}',
|
|
||||||
builder: (_, state) => AccountVerificationPage(token: state.pathParameters[PageParams.token.name]!),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
name: Pages.login.name,
|
||||||
|
path: routerPage(Pages.login),
|
||||||
|
builder: (_, __) => const LoginPage(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
name: Pages.sfactor.name,
|
||||||
|
path: routerPage(Pages.sfactor),
|
||||||
|
builder: (context, _) => TwoFactorCodePage(
|
||||||
|
onVerificationSuccess: () => context.goNamed(PayoutRoutes.dashboard),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
name: Pages.signup.name,
|
||||||
|
path: routerPage(Pages.signup),
|
||||||
|
builder: (_, __) => const SignUpPage(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
name: Pages.verify.name,
|
||||||
|
path: '${routerPage(Pages.verify)}${routerAddParam(PageParams.token)}',
|
||||||
|
builder: (_, state) => AccountVerificationPage(
|
||||||
|
token: state.pathParameters[PageParams.token.name]!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
payoutShellRoute(),
|
||||||
],
|
],
|
||||||
errorBuilder: (_, _) => const NotFoundPage(),
|
errorBuilder: (_, __) => const NotFoundPage(),
|
||||||
);
|
);
|
||||||
@@ -21,6 +21,10 @@ extension WalletUiMapper on domain.WalletModel {
|
|||||||
currency: currency,
|
currency: currency,
|
||||||
isHidden: true,
|
isHidden: true,
|
||||||
calculatedAt: balance?.calculatedAt ?? DateTime.now(),
|
calculatedAt: balance?.calculatedAt ?? DateTime.now(),
|
||||||
|
depositAddress: depositAddress,
|
||||||
|
network: asset.chain,
|
||||||
|
tokenSymbol: asset.tokenSymbol,
|
||||||
|
contractAddress: asset.contractAddress,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -469,6 +469,17 @@
|
|||||||
"walletNameUpdateFailed": "Failed to update wallet name",
|
"walletNameUpdateFailed": "Failed to update wallet name",
|
||||||
"walletNameSaved": "Wallet name saved",
|
"walletNameSaved": "Wallet name saved",
|
||||||
"topUpBalance": "Top Up Balance",
|
"topUpBalance": "Top Up Balance",
|
||||||
|
"walletTopUpTitle": "Add funds to wallet",
|
||||||
|
"walletTopUpDetailsTitle": "Funding details",
|
||||||
|
"walletTopUpDescription": "Send funds to this address to increase your wallet balance.",
|
||||||
|
"walletTopUpAssetLabel": "Asset",
|
||||||
|
"walletTopUpNetworkLabel": "Network",
|
||||||
|
"walletTopUpAddressLabel": "Deposit address",
|
||||||
|
"walletTopUpQrLabel": "QR code for deposit",
|
||||||
|
"walletTopUpHint": "Only send funds on the specified network. Deposits may take a few minutes to confirm.",
|
||||||
|
"walletTopUpUnavailable": "Top-up details are unavailable for this wallet yet.",
|
||||||
|
"copyAddress": "Copy address",
|
||||||
|
"addressCopied": "Address copied",
|
||||||
"addFunctionality": "Add functionality",
|
"addFunctionality": "Add functionality",
|
||||||
"walletHistoryEmpty": "No history yet",
|
"walletHistoryEmpty": "No history yet",
|
||||||
"colType": "Type",
|
"colType": "Type",
|
||||||
|
|||||||
@@ -470,6 +470,17 @@
|
|||||||
"walletNameUpdateFailed": "Не удалось обновить название кошелька",
|
"walletNameUpdateFailed": "Не удалось обновить название кошелька",
|
||||||
"walletNameSaved": "Название кошелька сохранено",
|
"walletNameSaved": "Название кошелька сохранено",
|
||||||
"topUpBalance": "Пополнить баланс",
|
"topUpBalance": "Пополнить баланс",
|
||||||
|
"walletTopUpTitle": "Пополнение кошелька",
|
||||||
|
"walletTopUpDetailsTitle": "Данные для пополнения",
|
||||||
|
"walletTopUpDescription": "Отправьте средства на этот адрес, чтобы пополнить баланс кошелька.",
|
||||||
|
"walletTopUpAssetLabel": "Актив",
|
||||||
|
"walletTopUpNetworkLabel": "Сеть",
|
||||||
|
"walletTopUpAddressLabel": "Адрес для пополнения",
|
||||||
|
"walletTopUpQrLabel": "QR-код для пополнения",
|
||||||
|
"walletTopUpHint": "Отправляйте средства только в указанной сети. Подтверждение может занять несколько минут.",
|
||||||
|
"walletTopUpUnavailable": "Данные для пополнения пока недоступны для этого кошелька.",
|
||||||
|
"copyAddress": "Скопировать адрес",
|
||||||
|
"addressCopied": "Адрес скопирован",
|
||||||
"addFunctionality": "Добавить функциональность",
|
"addFunctionality": "Добавить функциональность",
|
||||||
"walletHistoryEmpty": "История пуста",
|
"walletHistoryEmpty": "История пуста",
|
||||||
"colType": "Тип",
|
"colType": "Тип",
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ class Wallet {
|
|||||||
final Currency currency;
|
final Currency currency;
|
||||||
final bool isHidden;
|
final bool isHidden;
|
||||||
final DateTime calculatedAt;
|
final DateTime calculatedAt;
|
||||||
|
final String? depositAddress;
|
||||||
|
final String? network;
|
||||||
|
final String? tokenSymbol;
|
||||||
|
final String? contractAddress;
|
||||||
|
|
||||||
Wallet({
|
Wallet({
|
||||||
required this.id,
|
required this.id,
|
||||||
@@ -18,6 +22,10 @@ class Wallet {
|
|||||||
required this.currency,
|
required this.currency,
|
||||||
required this.calculatedAt,
|
required this.calculatedAt,
|
||||||
this.isHidden = true,
|
this.isHidden = true,
|
||||||
|
this.depositAddress,
|
||||||
|
this.network,
|
||||||
|
this.tokenSymbol,
|
||||||
|
this.contractAddress,
|
||||||
});
|
});
|
||||||
|
|
||||||
Wallet copyWith({
|
Wallet copyWith({
|
||||||
@@ -27,6 +35,10 @@ class Wallet {
|
|||||||
Currency? currency,
|
Currency? currency,
|
||||||
String? walletUserID,
|
String? walletUserID,
|
||||||
bool? isHidden,
|
bool? isHidden,
|
||||||
|
String? depositAddress,
|
||||||
|
String? network,
|
||||||
|
String? tokenSymbol,
|
||||||
|
String? contractAddress,
|
||||||
}) => Wallet(
|
}) => Wallet(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
name: name ?? this.name,
|
name: name ?? this.name,
|
||||||
@@ -35,5 +47,9 @@ class Wallet {
|
|||||||
walletUserID: walletUserID ?? this.walletUserID,
|
walletUserID: walletUserID ?? this.walletUserID,
|
||||||
isHidden: isHidden ?? this.isHidden,
|
isHidden: isHidden ?? this.isHidden,
|
||||||
calculatedAt: calculatedAt,
|
calculatedAt: calculatedAt,
|
||||||
|
depositAddress: depositAddress ?? this.depositAddress,
|
||||||
|
network: network ?? this.network,
|
||||||
|
tokenSymbol: tokenSymbol ?? this.tokenSymbol,
|
||||||
|
contractAddress: contractAddress ?? this.contractAddress,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/models/wallet.dart';
|
||||||
import 'package:pweb/pages/dashboard/buttons/balance/carousel.dart';
|
import 'package:pweb/pages/dashboard/buttons/balance/carousel.dart';
|
||||||
import 'package:pweb/providers/wallets.dart';
|
import 'package:pweb/providers/wallets.dart';
|
||||||
|
|
||||||
@@ -9,7 +10,9 @@ import 'package:pweb/generated/i18n/app_localizations.dart';
|
|||||||
|
|
||||||
|
|
||||||
class BalanceWidget extends StatelessWidget {
|
class BalanceWidget extends StatelessWidget {
|
||||||
const BalanceWidget({super.key});
|
final ValueChanged<Wallet> onTopUp;
|
||||||
|
|
||||||
|
const BalanceWidget({super.key, required this.onTopUp});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -30,6 +33,7 @@ class BalanceWidget extends StatelessWidget {
|
|||||||
WalletCarousel(
|
WalletCarousel(
|
||||||
wallets: wallets,
|
wallets: wallets,
|
||||||
onWalletChanged: walletsProvider.selectWallet,
|
onWalletChanged: walletsProvider.selectWallet,
|
||||||
|
onTopUp: onTopUp,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,10 +12,12 @@ import 'package:pweb/providers/wallets.dart';
|
|||||||
|
|
||||||
class WalletCard extends StatelessWidget {
|
class WalletCard extends StatelessWidget {
|
||||||
final Wallet wallet;
|
final Wallet wallet;
|
||||||
|
final VoidCallback onTopUp;
|
||||||
|
|
||||||
const WalletCard({
|
const WalletCard({
|
||||||
super.key,
|
super.key,
|
||||||
required this.wallet,
|
required this.wallet,
|
||||||
|
required this.onTopUp,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -43,7 +45,7 @@ class WalletCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
BalanceAddFunds(
|
BalanceAddFunds(
|
||||||
onTopUp: () {
|
onTopUp: () {
|
||||||
// TODO: Implement top-up functionality
|
onTopUp();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -12,11 +12,13 @@ import 'package:pweb/providers/carousel.dart';
|
|||||||
class WalletCarousel extends StatefulWidget {
|
class WalletCarousel extends StatefulWidget {
|
||||||
final List<Wallet> wallets;
|
final List<Wallet> wallets;
|
||||||
final ValueChanged<Wallet> onWalletChanged;
|
final ValueChanged<Wallet> onWalletChanged;
|
||||||
|
final ValueChanged<Wallet> onTopUp;
|
||||||
|
|
||||||
const WalletCarousel({
|
const WalletCarousel({
|
||||||
super.key,
|
super.key,
|
||||||
required this.wallets,
|
required this.wallets,
|
||||||
required this.onWalletChanged,
|
required this.onWalletChanged,
|
||||||
|
required this.onTopUp,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -33,6 +35,11 @@ class _WalletCarouselState extends State<WalletCarousel> {
|
|||||||
_pageController = PageController(
|
_pageController = PageController(
|
||||||
viewportFraction: WalletCardConfig.viewportFraction,
|
viewportFraction: WalletCardConfig.viewportFraction,
|
||||||
);
|
);
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (widget.wallets.isNotEmpty) {
|
||||||
|
widget.onWalletChanged(widget.wallets[_currentPage]);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -83,7 +90,10 @@ class _WalletCarouselState extends State<WalletCarousel> {
|
|||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: WalletCardConfig.cardPadding,
|
padding: WalletCardConfig.cardPadding,
|
||||||
child: WalletCard(wallet: widget.wallets[index]),
|
child: WalletCard(
|
||||||
|
wallet: widget.wallets[index],
|
||||||
|
onTopUp: () => widget.onTopUp(widget.wallets[index]),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:pshared/models/payment/type.dart';
|
import 'package:pshared/models/payment/type.dart';
|
||||||
import 'package:pshared/models/recipient/recipient.dart';
|
import 'package:pshared/models/recipient/recipient.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/models/wallet.dart';
|
||||||
import 'package:pweb/pages/dashboard/buttons/balance/balance.dart';
|
import 'package:pweb/pages/dashboard/buttons/balance/balance.dart';
|
||||||
import 'package:pweb/pages/dashboard/buttons/buttons.dart';
|
import 'package:pweb/pages/dashboard/buttons/buttons.dart';
|
||||||
import 'package:pweb/pages/dashboard/payouts/multiple/title.dart';
|
import 'package:pweb/pages/dashboard/payouts/multiple/title.dart';
|
||||||
@@ -22,11 +23,13 @@ class AppSpacing {
|
|||||||
class DashboardPage extends StatefulWidget {
|
class DashboardPage extends StatefulWidget {
|
||||||
final ValueChanged<Recipient> onRecipientSelected;
|
final ValueChanged<Recipient> onRecipientSelected;
|
||||||
final void Function(PaymentType type) onGoToPaymentWithoutRecipient;
|
final void Function(PaymentType type) onGoToPaymentWithoutRecipient;
|
||||||
|
final ValueChanged<Wallet> onTopUp;
|
||||||
|
|
||||||
const DashboardPage({
|
const DashboardPage({
|
||||||
super.key,
|
super.key,
|
||||||
required this.onRecipientSelected,
|
required this.onRecipientSelected,
|
||||||
required this.onGoToPaymentWithoutRecipient,
|
required this.onGoToPaymentWithoutRecipient,
|
||||||
|
required this.onTopUp,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -75,7 +78,9 @@ class _DashboardPageState extends State<DashboardPage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: AppSpacing.medium),
|
const SizedBox(height: AppSpacing.medium),
|
||||||
BalanceWidget(),
|
BalanceWidget(
|
||||||
|
onTopUp: widget.onTopUp,
|
||||||
|
),
|
||||||
const SizedBox(height: AppSpacing.small),
|
const SizedBox(height: AppSpacing.small),
|
||||||
if (_showContainerMultiple) TitleMultiplePayout(),
|
if (_showContainerMultiple) TitleMultiplePayout(),
|
||||||
const SizedBox(height: AppSpacing.medium),
|
const SizedBox(height: AppSpacing.medium),
|
||||||
|
|||||||
@@ -17,15 +17,34 @@ class AccountLoader extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => Consumer<AccountProvider>(builder: (context, provider, _) {
|
Widget build(BuildContext context) => Consumer<AccountProvider>(builder: (context, provider, _) {
|
||||||
if (provider.isLoading) return const Center(child: CircularProgressIndicator());
|
if (provider.account != null) {
|
||||||
if (provider.error != null) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
postNotifyUserOfErrorX(
|
});
|
||||||
context: context,
|
return child;
|
||||||
errorSituation: AppLocalizations.of(context)!.errorLogin,
|
|
||||||
exception: provider.error!,
|
|
||||||
);
|
|
||||||
navigateAndReplace(context, Pages.login);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (provider.error != null) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
postNotifyUserOfErrorX(
|
||||||
|
context: context,
|
||||||
|
errorSituation: AppLocalizations.of(context)!.errorLogin,
|
||||||
|
exception: provider.error!,
|
||||||
|
);
|
||||||
|
navigateAndReplace(context, Pages.login);
|
||||||
|
});
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provider.restoreFuture == null) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
provider.restoreIfPossible().catchError((error, stack) {
|
||||||
|
debugPrint('Account restore failed: $error');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provider.isLoading) return const Center(child: CircularProgressIndicator());
|
||||||
if (provider.account == null) {
|
if (provider.account == null) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) => navigateAndReplace(context, Pages.login));
|
WidgetsBinding.instance.addPostFrameCallback((_) => navigateAndReplace(context, Pages.login));
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ class _PaymentPageState extends State<PaymentPage> {
|
|||||||
final recipientProvider = context.read<RecipientsProvider>();
|
final recipientProvider = context.read<RecipientsProvider>();
|
||||||
|
|
||||||
recipientProvider.setCurrentObject(recipient.id);
|
recipientProvider.setCurrentObject(recipient.id);
|
||||||
pageSelector.selectRecipient(recipient);
|
pageSelector.selectRecipient(context, recipient);
|
||||||
_flowProvider.reset(pageSelector);
|
_flowProvider.reset(pageSelector);
|
||||||
_clearSearchField();
|
_clearSearchField();
|
||||||
}
|
}
|
||||||
@@ -72,7 +72,7 @@ class _PaymentPageState extends State<PaymentPage> {
|
|||||||
final recipientProvider = context.read<RecipientsProvider>();
|
final recipientProvider = context.read<RecipientsProvider>();
|
||||||
|
|
||||||
recipientProvider.setCurrentObject(null);
|
recipientProvider.setCurrentObject(null);
|
||||||
pageSelector.selectRecipient(null);
|
pageSelector.selectRecipient(context, null);
|
||||||
_flowProvider.reset(pageSelector);
|
_flowProvider.reset(pageSelector);
|
||||||
_clearSearchField();
|
_clearSearchField();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ class PaymentBackButton extends StatelessWidget {
|
|||||||
if (onBack != null) {
|
if (onBack != null) {
|
||||||
onBack!(pageSelector.selectedRecipient);
|
onBack!(pageSelector.selectedRecipient);
|
||||||
} else {
|
} else {
|
||||||
pageSelector.goBackFromPayment();
|
pageSelector.goBackFromPayment(context);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class SendPayoutButton extends StatelessWidget {
|
|||||||
final wallet = walletsProvider.selectedWallet;
|
final wallet = walletsProvider.selectedWallet;
|
||||||
|
|
||||||
if (wallet != null) {
|
if (wallet != null) {
|
||||||
pageSelectorProvider.startPaymentFromWallet(wallet);
|
pageSelectorProvider.startPaymentFromWallet(context, wallet);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Text(loc.payoutNavSendPayout),
|
child: Text(loc.payoutNavSendPayout),
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/providers/page_selector.dart';
|
||||||
|
import 'package:pweb/providers/wallets.dart';
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
@@ -15,9 +19,14 @@ class TopUpButton extends StatelessWidget{
|
|||||||
elevation: 0,
|
elevation: 0,
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
final wallet = context.read<WalletsProvider>().selectedWallet;
|
||||||
SnackBar(content: Text(loc.addFunctionality)),
|
if (wallet == null) {
|
||||||
);
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text(loc.noWalletSelected)),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
context.read<PageSelectorProvider>().openWalletTopUp(context, wallet);
|
||||||
},
|
},
|
||||||
child: Text(loc.topUpBalance),
|
child: Text(loc.topUpBalance),
|
||||||
);
|
);
|
||||||
|
|||||||
96
frontend/pweb/lib/pages/wallet_top_up/address_block.dart
Normal file
96
frontend/pweb/lib/pages/wallet_top_up/address_block.dart
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
import 'package:qr_flutter/qr_flutter.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/utils/dimensions.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class WalletTopUpAddressBlock extends StatelessWidget {
|
||||||
|
final String address;
|
||||||
|
final AppDimensions dimensions;
|
||||||
|
|
||||||
|
const WalletTopUpAddressBlock({
|
||||||
|
super.key,
|
||||||
|
required this.address,
|
||||||
|
required this.dimensions,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
loc.walletTopUpAddressLabel,
|
||||||
|
style: theme.textTheme.titleSmall,
|
||||||
|
),
|
||||||
|
TextButton.icon(
|
||||||
|
icon: const Icon(Icons.copy, size: 16),
|
||||||
|
label: Text(loc.copyAddress),
|
||||||
|
onPressed: () {
|
||||||
|
Clipboard.setData(ClipboardData(text: address));
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text(loc.addressCopied)),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: EdgeInsets.all(dimensions.paddingMedium),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(dimensions.borderRadiusSmall),
|
||||||
|
border: Border.all(color: theme.colorScheme.outlineVariant),
|
||||||
|
color: theme.colorScheme.surfaceVariant.withOpacity(0.4),
|
||||||
|
),
|
||||||
|
child: SelectableText(
|
||||||
|
address,
|
||||||
|
style: theme.textTheme.bodyLarge?.copyWith(
|
||||||
|
fontFeatures: const [FontFeature.tabularFigures()],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: dimensions.paddingLarge),
|
||||||
|
Text(
|
||||||
|
loc.walletTopUpQrLabel,
|
||||||
|
style: theme.textTheme.titleSmall,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.all(dimensions.paddingMedium),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(dimensions.borderRadiusSmall),
|
||||||
|
border: Border.all(color: theme.colorScheme.outlineVariant),
|
||||||
|
color: theme.colorScheme.surfaceVariant.withOpacity(0.4),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: QrImageView(
|
||||||
|
data: address,
|
||||||
|
backgroundColor: theme.colorScheme.onSecondary,
|
||||||
|
eyeStyle: QrEyeStyle(
|
||||||
|
eyeShape: QrEyeShape.square,
|
||||||
|
color: theme.colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
dataModuleStyle: QrDataModuleStyle(
|
||||||
|
dataModuleShape: QrDataModuleShape.square,
|
||||||
|
color: theme.colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
size: 220,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
76
frontend/pweb/lib/pages/wallet_top_up/content.dart
Normal file
76
frontend/pweb/lib/pages/wallet_top_up/content.dart
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/models/wallet.dart';
|
||||||
|
import 'package:pweb/pages/wallet_top_up/details.dart';
|
||||||
|
import 'package:pweb/pages/wallet_top_up/header.dart';
|
||||||
|
import 'package:pweb/pages/wallet_top_up/meta.dart';
|
||||||
|
import 'package:pweb/utils/currency.dart';
|
||||||
|
import 'package:pweb/utils/dimensions.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class WalletTopUpContent extends StatelessWidget {
|
||||||
|
final Wallet wallet;
|
||||||
|
final VoidCallback onBack;
|
||||||
|
|
||||||
|
const WalletTopUpContent({
|
||||||
|
super.key,
|
||||||
|
required this.wallet,
|
||||||
|
required this.onBack,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final dimensions = AppDimensions();
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
|
final address = _resolveAddress(wallet);
|
||||||
|
final network = wallet.network?.trim();
|
||||||
|
final assetLabel = wallet.tokenSymbol ?? currencyCodeToSymbol(wallet.currency);
|
||||||
|
|
||||||
|
return Align(
|
||||||
|
alignment: Alignment.topCenter,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 960),
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: dimensions.paddingLarge),
|
||||||
|
child: Material(
|
||||||
|
elevation: dimensions.elevationSmall,
|
||||||
|
color: theme.colorScheme.onSecondary,
|
||||||
|
borderRadius: BorderRadius.circular(dimensions.borderRadiusMedium),
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.all(dimensions.paddingXLarge),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
WalletTopUpHeader(
|
||||||
|
onBack: onBack,
|
||||||
|
walletName: wallet.name,
|
||||||
|
),
|
||||||
|
SizedBox(height: dimensions.paddingLarge),
|
||||||
|
WalletTopUpMeta(
|
||||||
|
assetLabel: assetLabel,
|
||||||
|
network: network,
|
||||||
|
walletId: wallet.walletUserID,
|
||||||
|
),
|
||||||
|
SizedBox(height: dimensions.paddingXLarge),
|
||||||
|
WalletTopUpDetails(
|
||||||
|
address: address,
|
||||||
|
dimensions: dimensions,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _resolveAddress(Wallet wallet) {
|
||||||
|
final candidate = wallet.depositAddress?.trim();
|
||||||
|
if (candidate == null || candidate.isEmpty) return null;
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
60
frontend/pweb/lib/pages/wallet_top_up/details.dart
Normal file
60
frontend/pweb/lib/pages/wallet_top_up/details.dart
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/pages/wallet_top_up/address_block.dart';
|
||||||
|
import 'package:pweb/utils/dimensions.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class WalletTopUpDetails extends StatelessWidget {
|
||||||
|
final String? address;
|
||||||
|
final AppDimensions dimensions;
|
||||||
|
|
||||||
|
const WalletTopUpDetails({
|
||||||
|
super.key,
|
||||||
|
required this.address,
|
||||||
|
required this.dimensions,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
loc.walletTopUpDetailsTitle,
|
||||||
|
style: theme.textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Text(
|
||||||
|
loc.walletTopUpDescription,
|
||||||
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: dimensions.paddingLarge),
|
||||||
|
if (address == null || address!.isEmpty)
|
||||||
|
Text(
|
||||||
|
loc.walletTopUpUnavailable,
|
||||||
|
style: theme.textTheme.bodyMedium,
|
||||||
|
)
|
||||||
|
else ...[
|
||||||
|
WalletTopUpAddressBlock(
|
||||||
|
address: address!,
|
||||||
|
dimensions: dimensions,
|
||||||
|
),
|
||||||
|
SizedBox(height: dimensions.paddingLarge),
|
||||||
|
Text(
|
||||||
|
loc.walletTopUpHint,
|
||||||
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
47
frontend/pweb/lib/pages/wallet_top_up/header.dart
Normal file
47
frontend/pweb/lib/pages/wallet_top_up/header.dart
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class WalletTopUpHeader extends StatelessWidget {
|
||||||
|
final VoidCallback onBack;
|
||||||
|
final String walletName;
|
||||||
|
|
||||||
|
const WalletTopUpHeader({
|
||||||
|
super.key,
|
||||||
|
required this.onBack,
|
||||||
|
required this.walletName,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back),
|
||||||
|
onPressed: onBack,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
loc.walletTopUpTitle,
|
||||||
|
style: theme.textTheme.titleLarge,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
walletName,
|
||||||
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
48
frontend/pweb/lib/pages/wallet_top_up/info_chip.dart
Normal file
48
frontend/pweb/lib/pages/wallet_top_up/info_chip.dart
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/utils/dimensions.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class WalletTopUpInfoChip extends StatelessWidget {
|
||||||
|
final String label;
|
||||||
|
final String value;
|
||||||
|
|
||||||
|
const WalletTopUpInfoChip({
|
||||||
|
super.key,
|
||||||
|
required this.label,
|
||||||
|
required this.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final dimensions = AppDimensions();
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.all(dimensions.paddingMedium),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(dimensions.borderRadiusSmall),
|
||||||
|
border: Border.all(color: theme.colorScheme.outlineVariant),
|
||||||
|
color: theme.colorScheme.surfaceVariant.withOpacity(0.4),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: theme.textTheme.labelMedium?.copyWith(
|
||||||
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
value,
|
||||||
|
style: theme.textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
37
frontend/pweb/lib/pages/wallet_top_up/meta.dart
Normal file
37
frontend/pweb/lib/pages/wallet_top_up/meta.dart
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/pages/wallet_top_up/info_chip.dart';
|
||||||
|
import 'package:pweb/utils/dimensions.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class WalletTopUpMeta extends StatelessWidget {
|
||||||
|
final String assetLabel;
|
||||||
|
final String walletId;
|
||||||
|
final String? network;
|
||||||
|
|
||||||
|
const WalletTopUpMeta({
|
||||||
|
super.key,
|
||||||
|
required this.assetLabel,
|
||||||
|
required this.walletId,
|
||||||
|
this.network,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
|
final dimensions = AppDimensions();
|
||||||
|
|
||||||
|
return Wrap(
|
||||||
|
spacing: dimensions.paddingLarge,
|
||||||
|
runSpacing: dimensions.paddingLarge,
|
||||||
|
children: [
|
||||||
|
WalletTopUpInfoChip(label: loc.walletTopUpAssetLabel, value: assetLabel),
|
||||||
|
if (network != null && network!.isNotEmpty)
|
||||||
|
WalletTopUpInfoChip(label: loc.walletTopUpNetworkLabel, value: network!),
|
||||||
|
WalletTopUpInfoChip(label: loc.walletId, value: walletId),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
44
frontend/pweb/lib/pages/wallet_top_up/page.dart
Normal file
44
frontend/pweb/lib/pages/wallet_top_up/page.dart
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/pages/wallet_top_up/content.dart';
|
||||||
|
import 'package:pweb/providers/wallets.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class WalletTopUpPage extends StatelessWidget {
|
||||||
|
final VoidCallback onBack;
|
||||||
|
|
||||||
|
const WalletTopUpPage({super.key, required this.onBack});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
|
return Consumer<WalletsProvider>(
|
||||||
|
builder: (context, provider, child) {
|
||||||
|
if (provider.isLoading) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provider.error != null) {
|
||||||
|
return Center(
|
||||||
|
child: Text(loc.notificationError(provider.error.toString())),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final wallet = provider.selectedWallet;
|
||||||
|
if (wallet == null) {
|
||||||
|
return Center(child: Text(loc.noWalletSelected));
|
||||||
|
}
|
||||||
|
|
||||||
|
return WalletTopUpContent(
|
||||||
|
wallet: wallet,
|
||||||
|
onBack: onBack,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ import 'package:pshared/provider/recipient/provider.dart';
|
|||||||
import 'package:pweb/models/wallet.dart';
|
import 'package:pweb/models/wallet.dart';
|
||||||
import 'package:pweb/providers/wallets.dart';
|
import 'package:pweb/providers/wallets.dart';
|
||||||
//import 'package:pweb/services/amplitude.dart';
|
//import 'package:pweb/services/amplitude.dart';
|
||||||
|
import 'package:pweb/app/router/payout_routes.dart';
|
||||||
import 'package:pweb/widgets/sidebar/destinations.dart';
|
import 'package:pweb/widgets/sidebar/destinations.dart';
|
||||||
|
|
||||||
|
|
||||||
@@ -41,44 +42,93 @@ class PageSelectorProvider extends ChangeNotifier {
|
|||||||
methodsProvider = methodsProv;
|
methodsProvider = methodsProv;
|
||||||
}
|
}
|
||||||
|
|
||||||
void selectPage(PayoutDestination dest) {
|
void syncDestination(PayoutDestination destination) {
|
||||||
_selected = dest;
|
if (_selected == destination) return;
|
||||||
|
_selected = destination;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void selectRecipient(Recipient? recipient, {bool fromList = false}) {
|
void selectPage(
|
||||||
|
BuildContext context,
|
||||||
|
PayoutDestination dest, {
|
||||||
|
bool replace = true,
|
||||||
|
}) {
|
||||||
|
_selected = dest;
|
||||||
|
notifyListeners();
|
||||||
|
_navigateTo(context, dest, replace: replace);
|
||||||
|
}
|
||||||
|
|
||||||
|
void selectRecipient(
|
||||||
|
BuildContext context,
|
||||||
|
Recipient? recipient, {
|
||||||
|
bool fromList = false,
|
||||||
|
}) {
|
||||||
|
final previousDestination = _selected;
|
||||||
recipientProvider.setCurrentObject(recipient?.id);
|
recipientProvider.setCurrentObject(recipient?.id);
|
||||||
_cameFromRecipientList = fromList;
|
_cameFromRecipientList = fromList;
|
||||||
_setPreviousDestination();
|
_setPreviousDestination();
|
||||||
_selected = PayoutDestination.payment;
|
_selected = PayoutDestination.payment;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
if (previousDestination != PayoutDestination.payment) {
|
||||||
|
_navigateTo(context, PayoutDestination.payment, replace: false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void editRecipient(Recipient? recipient, {bool fromList = false}) {
|
void editRecipient(
|
||||||
|
BuildContext context,
|
||||||
|
Recipient? recipient, {
|
||||||
|
bool fromList = false,
|
||||||
|
}) {
|
||||||
|
final previousDestination = _selected;
|
||||||
recipientProvider.setCurrentObject(recipient?.id);
|
recipientProvider.setCurrentObject(recipient?.id);
|
||||||
_cameFromRecipientList = fromList;
|
_cameFromRecipientList = fromList;
|
||||||
_selected = PayoutDestination.addrecipient;
|
_selected = PayoutDestination.addrecipient;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
if (previousDestination != PayoutDestination.addrecipient) {
|
||||||
|
_navigateTo(context, PayoutDestination.addrecipient, replace: false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void goToAddRecipient() {
|
void goToAddRecipient(BuildContext context) {
|
||||||
// AmplitudeService.recipientAddStarted();
|
// AmplitudeService.recipientAddStarted();
|
||||||
|
final previousDestination = _selected;
|
||||||
recipientProvider.setCurrentObject(null);
|
recipientProvider.setCurrentObject(null);
|
||||||
_selected = PayoutDestination.addrecipient;
|
_selected = PayoutDestination.addrecipient;
|
||||||
_cameFromRecipientList = false;
|
_cameFromRecipientList = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
if (previousDestination != PayoutDestination.addrecipient) {
|
||||||
|
_navigateTo(context, PayoutDestination.addrecipient, replace: false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void startPaymentWithoutRecipient(PaymentType type) {
|
void startPaymentWithoutRecipient(
|
||||||
|
BuildContext context,
|
||||||
|
PaymentType type,
|
||||||
|
) {
|
||||||
|
final previousDestination = _selected;
|
||||||
recipientProvider.setCurrentObject(null);
|
recipientProvider.setCurrentObject(null);
|
||||||
_type = type;
|
_type = type;
|
||||||
_cameFromRecipientList = false;
|
_cameFromRecipientList = false;
|
||||||
_setPreviousDestination();
|
_setPreviousDestination();
|
||||||
_selected = PayoutDestination.payment;
|
_selected = PayoutDestination.payment;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
if (previousDestination != PayoutDestination.payment) {
|
||||||
|
_navigateTo(context, PayoutDestination.payment, replace: false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void goBackFromPayment() {
|
void goBackFromPayment(BuildContext context) {
|
||||||
|
if (Navigator.of(context).canPop()) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
} else {
|
||||||
|
_navigateTo(
|
||||||
|
context,
|
||||||
|
_previousDestination ??
|
||||||
|
(_cameFromRecipientList
|
||||||
|
? PayoutDestination.recipients
|
||||||
|
: PayoutDestination.dashboard),
|
||||||
|
);
|
||||||
|
}
|
||||||
_selected = _previousDestination ??
|
_selected = _previousDestination ??
|
||||||
(_cameFromRecipientList
|
(_cameFromRecipientList
|
||||||
? PayoutDestination.recipients
|
? PayoutDestination.recipients
|
||||||
@@ -89,22 +139,55 @@ class PageSelectorProvider extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void goBackFromWalletEdit() {
|
void goBackFromWalletEdit(BuildContext context) {
|
||||||
selectPage(PayoutDestination.methods);
|
selectPage(context, PayoutDestination.methods);
|
||||||
}
|
}
|
||||||
|
|
||||||
void selectWallet(Wallet wallet) {
|
void selectWallet(BuildContext context, Wallet wallet) {
|
||||||
|
final previousDestination = _selected;
|
||||||
walletsProvider.selectWallet(wallet);
|
walletsProvider.selectWallet(wallet);
|
||||||
_selected = PayoutDestination.editwallet;
|
_selected = PayoutDestination.editwallet;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
if (previousDestination != PayoutDestination.editwallet) {
|
||||||
|
_navigateTo(context, PayoutDestination.editwallet, replace: false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void startPaymentFromWallet(Wallet wallet) {
|
void startPaymentFromWallet(BuildContext context, Wallet wallet) {
|
||||||
|
final previousDestination = _selected;
|
||||||
_type = PaymentType.wallet;
|
_type = PaymentType.wallet;
|
||||||
_cameFromRecipientList = false;
|
_cameFromRecipientList = false;
|
||||||
_setPreviousDestination();
|
_setPreviousDestination();
|
||||||
_selected = PayoutDestination.payment;
|
_selected = PayoutDestination.payment;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
if (previousDestination != PayoutDestination.payment) {
|
||||||
|
_navigateTo(context, PayoutDestination.payment, replace: false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void openWalletTopUp(BuildContext context, Wallet wallet) {
|
||||||
|
final previousDestination = _selected;
|
||||||
|
_setPreviousDestination();
|
||||||
|
walletsProvider.selectWallet(wallet);
|
||||||
|
_selected = PayoutDestination.walletTopUp;
|
||||||
|
notifyListeners();
|
||||||
|
if (previousDestination != PayoutDestination.walletTopUp) {
|
||||||
|
_navigateTo(context, PayoutDestination.walletTopUp, replace: false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void goBackFromWalletTopUp(BuildContext context) {
|
||||||
|
if (Navigator.of(context).canPop()) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
} else {
|
||||||
|
_navigateTo(
|
||||||
|
context,
|
||||||
|
_previousDestination ?? PayoutDestination.dashboard,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_selected = _previousDestination ?? PayoutDestination.dashboard;
|
||||||
|
_previousDestination = null;
|
||||||
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
PaymentMethod? getPaymentMethodForWallet(Wallet wallet) {
|
PaymentMethod? getPaymentMethodForWallet(Wallet wallet) {
|
||||||
@@ -113,8 +196,7 @@ class PageSelectorProvider extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return methodsProvider.methods.firstWhereOrNull(
|
return methodsProvider.methods.firstWhereOrNull(
|
||||||
(method) => method.type == PaymentType.wallet &&
|
(method) => method.type == PaymentType.wallet && (method.description?.contains(wallet.walletUserID) ?? false),
|
||||||
(method.description?.contains(wallet.walletUserID) ?? false),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,11 +241,24 @@ class PageSelectorProvider extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _setPreviousDestination() {
|
void _setPreviousDestination() {
|
||||||
if (_selected != PayoutDestination.payment) {
|
if (_selected != PayoutDestination.payment &&
|
||||||
|
_selected != PayoutDestination.walletTopUp) {
|
||||||
_previousDestination = _selected;
|
_previousDestination = _selected;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _navigateTo(
|
||||||
|
BuildContext context,
|
||||||
|
PayoutDestination destination, {
|
||||||
|
bool replace = true,
|
||||||
|
}) {
|
||||||
|
if (replace) {
|
||||||
|
context.goToPayout(destination);
|
||||||
|
} else {
|
||||||
|
context.pushToPayout(destination);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Recipient? get selectedRecipient => recipientProvider.currentObject;
|
Recipient? get selectedRecipient => recipientProvider.currentObject;
|
||||||
Wallet? get selectedWallet => walletsProvider.selectedWallet;
|
Wallet? get selectedWallet => walletsProvider.selectedWallet;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'package:pshared/service/wallet.dart' as shared_wallet_service;
|
import 'package:pshared/service/wallet.dart' as shared_wallet_service;
|
||||||
|
|
||||||
import 'package:pweb/models/currency.dart';
|
|
||||||
import 'package:pweb/models/wallet.dart';
|
import 'package:pweb/models/wallet.dart';
|
||||||
import 'package:pweb/data/mappers/wallet_ui.dart';
|
import 'package:pweb/data/mappers/wallet_ui.dart';
|
||||||
|
|
||||||
@@ -10,27 +9,6 @@ abstract class WalletsService {
|
|||||||
Future<double> getBalance(String organizationRef, String walletRef);
|
Future<double> getBalance(String organizationRef, String walletRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
class MockWalletsService implements WalletsService {
|
|
||||||
final List<Wallet> _wallets = [
|
|
||||||
Wallet(id: '1124', walletUserID: 'WA-12345667', name: 'Main Wallet', balance: 10000000.0, currency: Currency.rub, calculatedAt: DateTime.now()),
|
|
||||||
Wallet(id: '2124', walletUserID: 'WA-76654321', name: 'Savings', balance: 2500.5, currency: Currency.usd, calculatedAt: DateTime.now()),
|
|
||||||
];
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<Wallet>> getWallets(String _) async {
|
|
||||||
return _wallets;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<double> getBalance(String _, String walletRef) async {
|
|
||||||
final wallet = _wallets.firstWhere(
|
|
||||||
(w) => w.id == walletRef,
|
|
||||||
orElse: () => throw Exception('Wallet not found'),
|
|
||||||
);
|
|
||||||
return wallet.balance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ApiWalletsService implements WalletsService {
|
class ApiWalletsService implements WalletsService {
|
||||||
@override
|
@override
|
||||||
Future<List<Wallet>> getWallets(String organizationRef) async {
|
Future<List<Wallet>> getWallets(String organizationRef) async {
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ enum PayoutDestination {
|
|||||||
methods(Icons.credit_card, 'methods'),
|
methods(Icons.credit_card, 'methods'),
|
||||||
payment(Icons.payment, 'payout'),
|
payment(Icons.payment, 'payout'),
|
||||||
addrecipient(Icons.app_registration, 'add recipient'),
|
addrecipient(Icons.app_registration, 'add recipient'),
|
||||||
editwallet(Icons.wallet, 'edit wallet');
|
editwallet(Icons.wallet, 'edit wallet'),
|
||||||
|
walletTopUp(Icons.qr_code_2_outlined, 'wallet top up');
|
||||||
|
|
||||||
|
|
||||||
const PayoutDestination(this.icon, this.labelKey);
|
const PayoutDestination(this.icon, this.labelKey);
|
||||||
@@ -41,6 +42,8 @@ enum PayoutDestination {
|
|||||||
return loc.addRecipient;
|
return loc.addRecipient;
|
||||||
case PayoutDestination.editwallet:
|
case PayoutDestination.editwallet:
|
||||||
return loc.editWallet;
|
return loc.editWallet;
|
||||||
|
case PayoutDestination.walletTopUp:
|
||||||
|
return loc.walletTopUpTitle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,31 +2,29 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
import 'package:pshared/models/resources.dart';
|
import 'package:pshared/models/resources.dart';
|
||||||
import 'package:pshared/provider/permissions.dart';
|
import 'package:pshared/provider/permissions.dart';
|
||||||
import 'package:pshared/provider/recipient/provider.dart';
|
|
||||||
|
|
||||||
import 'package:pweb/pages/address_book/form/page.dart';
|
|
||||||
import 'package:pweb/pages/address_book/page/page.dart';
|
|
||||||
import 'package:pweb/pages/loader.dart';
|
import 'package:pweb/pages/loader.dart';
|
||||||
import 'package:pweb/pages/payment_methods/page.dart';
|
|
||||||
import 'package:pweb/pages/payout_page/page.dart';
|
|
||||||
import 'package:pweb/pages/payout_page/wallet/edit/page.dart';
|
|
||||||
import 'package:pweb/pages/report/page.dart';
|
|
||||||
import 'package:pweb/pages/settings/profile/page.dart';
|
|
||||||
import 'package:pweb/pages/dashboard/dashboard.dart';
|
|
||||||
import 'package:pweb/providers/page_selector.dart';
|
import 'package:pweb/providers/page_selector.dart';
|
||||||
import 'package:pweb/utils/logout.dart';
|
import 'package:pweb/utils/logout.dart';
|
||||||
import 'package:pweb/widgets/appbar/app_bar.dart';
|
import 'package:pweb/widgets/appbar/app_bar.dart';
|
||||||
import 'package:pweb/widgets/error/snackbar.dart';
|
|
||||||
import 'package:pweb/widgets/sidebar/destinations.dart';
|
import 'package:pweb/widgets/sidebar/destinations.dart';
|
||||||
import 'package:pweb/widgets/sidebar/sidebar.dart';
|
import 'package:pweb/widgets/sidebar/sidebar.dart';
|
||||||
|
import 'package:pweb/app/router/payout_routes.dart';
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
|
||||||
|
|
||||||
|
|
||||||
class PageSelector extends StatelessWidget {
|
class PageSelector extends StatelessWidget {
|
||||||
const PageSelector({super.key});
|
final Widget child;
|
||||||
|
final GoRouterState routerState;
|
||||||
|
|
||||||
|
const PageSelector({
|
||||||
|
super.key,
|
||||||
|
required this.child,
|
||||||
|
required this.routerState,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => PageViewLoader(
|
Widget build(BuildContext context) => PageViewLoader(
|
||||||
@@ -36,88 +34,29 @@ class PageSelector extends StatelessWidget {
|
|||||||
|
|
||||||
final provider = context.watch<PageSelectorProvider>();
|
final provider = context.watch<PageSelectorProvider>();
|
||||||
|
|
||||||
final loc = AppLocalizations.of(context)!;
|
|
||||||
|
|
||||||
final bool restrictedAccess = !permissions.canRead(ResourceType.chainWallets);
|
final bool restrictedAccess = !permissions.canRead(ResourceType.chainWallets);
|
||||||
final allowedDestinations = restrictedAccess
|
final allowedDestinations = restrictedAccess
|
||||||
? <PayoutDestination>{
|
? <PayoutDestination>{
|
||||||
PayoutDestination.settings,
|
PayoutDestination.settings,
|
||||||
PayoutDestination.methods,
|
PayoutDestination.methods,
|
||||||
PayoutDestination.editwallet,
|
PayoutDestination.editwallet,
|
||||||
|
PayoutDestination.walletTopUp,
|
||||||
}
|
}
|
||||||
: PayoutDestination.values.toSet();
|
: PayoutDestination.values.toSet();
|
||||||
|
|
||||||
final selected = allowedDestinations.contains(provider.selected)
|
final routeDestination = _destinationFromState(routerState) ?? provider.selected;
|
||||||
? provider.selected
|
final selected = allowedDestinations.contains(routeDestination)
|
||||||
|
? routeDestination
|
||||||
: (restrictedAccess ? PayoutDestination.settings : PayoutDestination.dashboard);
|
: (restrictedAccess ? PayoutDestination.settings : PayoutDestination.dashboard);
|
||||||
|
|
||||||
if (selected != provider.selected) {
|
if (selected != routeDestination) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) => provider.selectPage(selected));
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
context.goToPayout(selected);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget content;
|
if (provider.selected != selected) {
|
||||||
switch (selected) {
|
provider.syncDestination(selected);
|
||||||
case PayoutDestination.dashboard:
|
|
||||||
content = DashboardPage(
|
|
||||||
onRecipientSelected: (recipient) => provider.selectRecipient(recipient),
|
|
||||||
onGoToPaymentWithoutRecipient: provider.startPaymentWithoutRecipient,
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PayoutDestination.recipients:
|
|
||||||
content = RecipientAddressBookPage(
|
|
||||||
onRecipientSelected: (recipient) =>
|
|
||||||
provider.selectRecipient(recipient, fromList: true),
|
|
||||||
onAddRecipient: provider.goToAddRecipient,
|
|
||||||
onEditRecipient: provider.editRecipient,
|
|
||||||
onDeleteRecipient: (recipient) => executeActionWithNotification(
|
|
||||||
context: context,
|
|
||||||
action: () async => context.read<RecipientsProvider>().delete(recipient.id),
|
|
||||||
successMessage: loc.recipientDeletedSuccessfully,
|
|
||||||
errorMessage: loc.errorDeleteRecipient,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PayoutDestination.addrecipient:
|
|
||||||
final recipient = provider.recipientProvider.currentObject;
|
|
||||||
content = AdressBookRecipientForm(
|
|
||||||
recipient: recipient,
|
|
||||||
onSaved: (_) => provider.selectPage(PayoutDestination.recipients),
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PayoutDestination.payment:
|
|
||||||
content = PaymentPage(
|
|
||||||
onBack: (_) => provider.goBackFromPayment(),
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PayoutDestination.settings:
|
|
||||||
content = ProfileSettingsPage();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PayoutDestination.reports:
|
|
||||||
content = OperationHistoryPage();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PayoutDestination.methods:
|
|
||||||
content = PaymentConfigPage(
|
|
||||||
onWalletTap: provider.selectWallet,
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PayoutDestination.editwallet:
|
|
||||||
final wallet = provider.walletsProvider.selectedWallet;
|
|
||||||
content = wallet != null
|
|
||||||
? WalletEditPage(
|
|
||||||
onBack: provider.goBackFromWalletEdit,
|
|
||||||
)
|
|
||||||
: Center(child: Text(loc.noWalletSelected));
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
content = Text(selected.name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@@ -134,14 +73,49 @@ class PageSelector extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
PayoutSidebar(
|
PayoutSidebar(
|
||||||
selected: selected,
|
selected: selected,
|
||||||
onSelected: provider.selectPage,
|
onSelected: context.goToPayout,
|
||||||
onLogout: () => logoutUtil(context),
|
onLogout: () => logoutUtil(context),
|
||||||
),
|
),
|
||||||
Expanded(child: content),
|
Expanded(child: child),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
|
||||||
|
PayoutDestination? _destinationFromState(GoRouterState state) {
|
||||||
|
final byName = PayoutRoutes.destinationFor(state.name);
|
||||||
|
if (byName != null) return byName;
|
||||||
|
|
||||||
|
final location = state.matchedLocation;
|
||||||
|
if (location.startsWith(PayoutRoutes.editWalletPath)) {
|
||||||
|
return PayoutDestination.editwallet;
|
||||||
|
}
|
||||||
|
if (location.startsWith(PayoutRoutes.walletTopUpPath)) {
|
||||||
|
return PayoutDestination.walletTopUp;
|
||||||
|
}
|
||||||
|
if (location.startsWith(PayoutRoutes.methodsPath)) {
|
||||||
|
return PayoutDestination.methods;
|
||||||
|
}
|
||||||
|
if (location.startsWith(PayoutRoutes.paymentPath)) {
|
||||||
|
return PayoutDestination.payment;
|
||||||
|
}
|
||||||
|
if (location.startsWith(PayoutRoutes.addRecipientPath)) {
|
||||||
|
return PayoutDestination.addrecipient;
|
||||||
|
}
|
||||||
|
if (location.startsWith(PayoutRoutes.recipientsPath)) {
|
||||||
|
return PayoutDestination.recipients;
|
||||||
|
}
|
||||||
|
if (location.startsWith(PayoutRoutes.settingsPath)) {
|
||||||
|
return PayoutDestination.settings;
|
||||||
|
}
|
||||||
|
if (location.startsWith(PayoutRoutes.reportsPath)) {
|
||||||
|
return PayoutDestination.reports;
|
||||||
|
}
|
||||||
|
if (location.startsWith(PayoutRoutes.dashboardPath)) {
|
||||||
|
return PayoutDestination.dashboard;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ dependencies:
|
|||||||
syncfusion_flutter_charts: ^31.2.10
|
syncfusion_flutter_charts: ^31.2.10
|
||||||
flutter_multi_formatter: ^2.13.7
|
flutter_multi_formatter: ^2.13.7
|
||||||
dotted_border: ^3.1.0
|
dotted_border: ^3.1.0
|
||||||
|
qr_flutter: ^4.1.0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -96,7 +97,7 @@ flutter:
|
|||||||
|
|
||||||
# To add assets to your application, add an assets section, like this:
|
# To add assets to your application, add an assets section, like this:
|
||||||
assets:
|
assets:
|
||||||
- resources/logo.png
|
- resources/icon.png
|
||||||
- resources/logo.si
|
- resources/logo.si
|
||||||
|
|
||||||
# An image asset can refer to one or more resolution-specific "variants", see
|
# An image asset can refer to one or more resolution-specific "variants", see
|
||||||
|
|||||||
Reference in New Issue
Block a user