mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-19 05:58:50 +02:00
Merge branch 'main' into CAKE-192-implement-unstoppable-domain
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||
|
||||
part 'buy_amount_view_model.g.dart';
|
||||
|
||||
class BuyAmountViewModel = BuyAmountViewModelBase with _$BuyAmountViewModel;
|
||||
|
||||
abstract class BuyAmountViewModelBase with Store {
|
||||
BuyAmountViewModelBase() : amount = '';
|
||||
|
||||
@observable
|
||||
String amount;
|
||||
|
||||
FiatCurrency get fiatCurrency => FiatCurrency.usd;
|
||||
|
||||
@computed
|
||||
double get doubleAmount {
|
||||
double _amount;
|
||||
|
||||
try {
|
||||
_amount = double.parse(amount.replaceAll(',', '.')) ?? 0.0;
|
||||
} catch (e) {
|
||||
_amount = 0.0;
|
||||
}
|
||||
|
||||
return _amount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import 'package:cake_wallet/buy/buy_amount.dart';
|
||||
import 'package:cake_wallet/buy/buy_provider.dart';
|
||||
import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||
import 'package:cake_wallet/view_model/buy/buy_amount_view_model.dart';
|
||||
|
||||
class BuyItem {
|
||||
BuyItem({this.provider, this.buyAmountViewModel});
|
||||
|
||||
final BuyProvider provider;
|
||||
final BuyAmountViewModel buyAmountViewModel;
|
||||
|
||||
double get amount => buyAmountViewModel.doubleAmount;
|
||||
|
||||
FiatCurrency get fiatCurrency => buyAmountViewModel.fiatCurrency;
|
||||
|
||||
Future<BuyAmount> get buyAmount async {
|
||||
BuyAmount _buyAmount;
|
||||
|
||||
try {
|
||||
_buyAmount = await provider
|
||||
.calculateAmount(amount?.toString(), fiatCurrency.title);
|
||||
} catch (e) {
|
||||
_buyAmount = BuyAmount(sourceAmount: 0.0, destAmount: 0.0);
|
||||
print(e.toString());
|
||||
}
|
||||
|
||||
return _buyAmount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
import 'package:cake_wallet/buy/buy_provider.dart';
|
||||
import 'package:cake_wallet/buy/moonpay/moonpay_buy_provider.dart';
|
||||
import 'package:cake_wallet/buy/wyre/wyre_buy_provider.dart';
|
||||
import 'package:cake_wallet/entities/crypto_currency.dart';
|
||||
import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/view_model/buy/buy_item.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:cake_wallet/buy/order.dart';
|
||||
import 'package:cake_wallet/store/dashboard/orders_store.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/core/wallet_base.dart';
|
||||
import 'buy_amount_view_model.dart';
|
||||
|
||||
part 'buy_view_model.g.dart';
|
||||
|
||||
class BuyViewModel = BuyViewModelBase with _$BuyViewModel;
|
||||
|
||||
abstract class BuyViewModelBase with Store {
|
||||
BuyViewModelBase(this.ordersSource, this.ordersStore, this.settingsStore,
|
||||
this.buyAmountViewModel, {@required this.wallet}) {
|
||||
|
||||
_fetchBuyItems();
|
||||
|
||||
isRunning = false;
|
||||
isDisabled = true;
|
||||
isShowProviderButtons = false;
|
||||
}
|
||||
|
||||
final Box<Order> ordersSource;
|
||||
final OrdersStore ordersStore;
|
||||
final SettingsStore settingsStore;
|
||||
final BuyAmountViewModel buyAmountViewModel;
|
||||
final WalletBase wallet;
|
||||
|
||||
@observable
|
||||
BuyProvider selectedProvider;
|
||||
|
||||
@observable
|
||||
List<BuyItem> items;
|
||||
|
||||
@observable
|
||||
bool isRunning;
|
||||
|
||||
@observable
|
||||
bool isDisabled;
|
||||
|
||||
@observable
|
||||
bool isShowProviderButtons;
|
||||
|
||||
WalletType get type => wallet.type;
|
||||
|
||||
double get doubleAmount => buyAmountViewModel.doubleAmount;
|
||||
|
||||
FiatCurrency get fiatCurrency => buyAmountViewModel.fiatCurrency;
|
||||
|
||||
CryptoCurrency get cryptoCurrency => walletTypeToCryptoCurrency(type);
|
||||
|
||||
Future <String> fetchUrl() async {
|
||||
String _url = '';
|
||||
|
||||
try {
|
||||
_url = await selectedProvider
|
||||
?.requestUrl(doubleAmount?.toString(), fiatCurrency.title);
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
|
||||
return _url;
|
||||
}
|
||||
|
||||
Future<void> saveOrder(String orderId) async {
|
||||
try {
|
||||
final order = await selectedProvider?.findOrderById(orderId);
|
||||
order.from = fiatCurrency.title;
|
||||
order.to = cryptoCurrency.title;
|
||||
await ordersSource.add(order);
|
||||
ordersStore.setOrder(order);
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
void reset() {
|
||||
buyAmountViewModel.amount = '';
|
||||
selectedProvider = null;
|
||||
}
|
||||
|
||||
Future<void> _fetchBuyItems() async {
|
||||
final List<BuyProvider> _providerList = [WyreBuyProvider(wallet: wallet)];
|
||||
|
||||
var isMoonPayEnabled = false;
|
||||
try {
|
||||
isMoonPayEnabled = await MoonPayBuyProvider.onEnabled();
|
||||
} catch (e) {
|
||||
isMoonPayEnabled = false;
|
||||
print(e.toString());
|
||||
}
|
||||
|
||||
if (isMoonPayEnabled) {
|
||||
_providerList.add(MoonPayBuyProvider(wallet: wallet));
|
||||
}
|
||||
|
||||
items = _providerList.map((provider) =>
|
||||
BuyItem(provider: provider, buyAmountViewModel: buyAmountViewModel))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
import 'package:cake_wallet/core/transaction_history.dart';
|
||||
import 'package:cake_wallet/entities/balance.dart';
|
||||
import 'package:cake_wallet/entities/order.dart';
|
||||
import 'package:cake_wallet/entities/push_notifications_service.dart';
|
||||
import 'package:cake_wallet/buy/order.dart';
|
||||
import 'package:cake_wallet/entities/transaction_history.dart';
|
||||
import 'package:cake_wallet/exchange/trade_state.dart';
|
||||
import 'package:cake_wallet/monero/account.dart';
|
||||
import 'package:cake_wallet/monero/monero_balance.dart';
|
||||
import 'package:cake_wallet/monero/monero_transaction_info.dart';
|
||||
@@ -17,7 +20,9 @@ import 'package:cake_wallet/view_model/dashboard/order_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/transaction_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/action_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/wyre_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/action_list_display_mode.dart';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/core/wallet_base.dart';
|
||||
@@ -42,9 +47,9 @@ abstract class DashboardViewModelBase with Store {
|
||||
this.tradeFilterStore,
|
||||
this.transactionFilterStore,
|
||||
this.settingsStore,
|
||||
this.ordersSource,
|
||||
this.ordersStore,
|
||||
this.wyreViewModel}) {
|
||||
PushNotificationsService pushNotificationsService}) {
|
||||
_pushNotificationsService = pushNotificationsService;
|
||||
filterItems = {
|
||||
S.current.transactions: [
|
||||
FilterItem(
|
||||
@@ -79,8 +84,6 @@ abstract class DashboardViewModelBase with Store {
|
||||
]
|
||||
};
|
||||
|
||||
isRunningWebView = false;
|
||||
|
||||
name = appStore.wallet?.name;
|
||||
wallet ??= appStore.wallet;
|
||||
type = wallet.type;
|
||||
@@ -133,6 +136,8 @@ abstract class DashboardViewModelBase with Store {
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
Future.delayed(Duration(seconds: 2), () => _pushNotificationsService.init());
|
||||
}
|
||||
|
||||
@observable
|
||||
@@ -147,9 +152,6 @@ abstract class DashboardViewModelBase with Store {
|
||||
@observable
|
||||
String subname;
|
||||
|
||||
@observable
|
||||
bool isRunningWebView;
|
||||
|
||||
@computed
|
||||
String get address => wallet.address;
|
||||
|
||||
@@ -205,8 +207,6 @@ abstract class DashboardViewModelBase with Store {
|
||||
|
||||
bool get hasRescan => wallet.type == WalletType.monero;
|
||||
|
||||
Box<Order> ordersSource;
|
||||
|
||||
BalanceViewModel balanceViewModel;
|
||||
|
||||
AppStore appStore;
|
||||
@@ -221,24 +221,24 @@ abstract class DashboardViewModelBase with Store {
|
||||
|
||||
TransactionFilterStore transactionFilterStore;
|
||||
|
||||
WyreViewModel wyreViewModel;
|
||||
|
||||
Map<String, List<FilterItem>> filterItems;
|
||||
|
||||
bool get isBuyEnabled => settingsStore.isBitcoinBuyEnabled;
|
||||
|
||||
PushNotificationsService _pushNotificationsService;
|
||||
|
||||
ReactionDisposer _onMoneroAccountChangeReaction;
|
||||
|
||||
ReactionDisposer _onMoneroBalanceChangeReaction;
|
||||
|
||||
@observable
|
||||
bool isOutdatedElectrumWallet;
|
||||
|
||||
Future<void> reconnect() async {
|
||||
final node = appStore.settingsStore.getCurrentNode(wallet.type);
|
||||
await wallet.connectToNode(node: node);
|
||||
}
|
||||
|
||||
@observable
|
||||
bool isOutdatedElectrumWallet;
|
||||
|
||||
@action
|
||||
void _onWalletChange(
|
||||
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:cake_wallet/entities/order.dart';
|
||||
import 'package:cake_wallet/buy/order.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/action_list_item.dart';
|
||||
import 'package:cake_wallet/entities/balance_display_mode.dart';
|
||||
|
||||
@@ -45,7 +45,7 @@ abstract class ExchangeTradeViewModelBase with Store {
|
||||
|
||||
_updateTrade();
|
||||
|
||||
_timer = Timer.periodic(Duration(seconds: 20), (_) async => _updateTrade());
|
||||
timer = Timer.periodic(Duration(seconds: 20), (_) async => _updateTrade());
|
||||
}
|
||||
|
||||
final WalletBase wallet;
|
||||
@@ -71,7 +71,7 @@ abstract class ExchangeTradeViewModelBase with Store {
|
||||
|
||||
ExchangeProvider _provider;
|
||||
|
||||
Timer _timer;
|
||||
Timer timer;
|
||||
|
||||
@action
|
||||
Future confirmSending() async {
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import 'dart:async';
|
||||
import 'package:cake_wallet/entities/order.dart';
|
||||
import 'package:cake_wallet/buy/buy_provider.dart';
|
||||
import 'package:cake_wallet/buy/buy_provider_description.dart';
|
||||
import 'package:cake_wallet/buy/order.dart';
|
||||
import 'package:cake_wallet/utils/date_formatter.dart';
|
||||
import 'package:cake_wallet/view_model/wyre_view_model.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/trade_details/track_trade_list_item.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:cake_wallet/core/wallet_base.dart';
|
||||
import 'package:cake_wallet/buy/moonpay/moonpay_buy_provider.dart';
|
||||
import 'package:cake_wallet/buy/wyre/wyre_buy_provider.dart';
|
||||
|
||||
part 'order_details_view_model.g.dart';
|
||||
|
||||
@@ -14,16 +18,27 @@ class OrderDetailsViewModel = OrderDetailsViewModelBase
|
||||
with _$OrderDetailsViewModel;
|
||||
|
||||
abstract class OrderDetailsViewModelBase with Store {
|
||||
OrderDetailsViewModelBase({this.wyreViewModel, Order orderForDetails}) {
|
||||
OrderDetailsViewModelBase({WalletBase wallet, Order orderForDetails}) {
|
||||
order = orderForDetails;
|
||||
|
||||
if (order.provider != null) {
|
||||
switch (order.provider) {
|
||||
case BuyProviderDescription.wyre:
|
||||
_provider = WyreBuyProvider(wallet: wallet);
|
||||
break;
|
||||
case BuyProviderDescription.moonPay:
|
||||
_provider = MoonPayBuyProvider(wallet: wallet);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
items = ObservableList<StandartListItem>();
|
||||
|
||||
_updateItems();
|
||||
|
||||
_updateOrder();
|
||||
|
||||
_timer = Timer.periodic(Duration(seconds: 20), (_) async => _updateOrder());
|
||||
timer = Timer.periodic(Duration(seconds: 20), (_) async => _updateOrder());
|
||||
}
|
||||
|
||||
@observable
|
||||
@@ -32,21 +47,25 @@ abstract class OrderDetailsViewModelBase with Store {
|
||||
@observable
|
||||
ObservableList<StandartListItem> items;
|
||||
|
||||
WyreViewModel wyreViewModel;
|
||||
BuyProvider _provider;
|
||||
|
||||
Timer _timer;
|
||||
Timer timer;
|
||||
|
||||
@action
|
||||
Future<void> _updateOrder() async {
|
||||
try {
|
||||
final updatedOrder =
|
||||
await wyreViewModel.wyreService.findOrderById(order.id);
|
||||
|
||||
updatedOrder.receiveAddress = order.receiveAddress;
|
||||
updatedOrder.walletId = order.walletId;
|
||||
order = updatedOrder;
|
||||
|
||||
_updateItems();
|
||||
if (_provider != null) {
|
||||
final updatedOrder = await _provider.findOrderById(order.id);
|
||||
updatedOrder.from = order.from;
|
||||
updatedOrder.to = order.to;
|
||||
updatedOrder.receiveAddress = order.receiveAddress;
|
||||
updatedOrder.walletId = order.walletId;
|
||||
if (order.provider != null) {
|
||||
updatedOrder.providerRaw = order.provider.raw;
|
||||
}
|
||||
order = updatedOrder;
|
||||
_updateItems();
|
||||
}
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
@@ -54,8 +73,6 @@ abstract class OrderDetailsViewModelBase with Store {
|
||||
|
||||
void _updateItems() {
|
||||
final dateFormat = DateFormatter.withCurrentLocal();
|
||||
final buildURL =
|
||||
wyreViewModel.trackUrl + '${order.transferId}';
|
||||
|
||||
items?.clear();
|
||||
|
||||
@@ -68,18 +85,37 @@ abstract class OrderDetailsViewModelBase with Store {
|
||||
value: order.state != null
|
||||
? order.state.toString()
|
||||
: S.current.trade_details_fetching),
|
||||
TrackTradeListItem(
|
||||
title: 'Track',
|
||||
value: buildURL,
|
||||
onTap: () {
|
||||
launch(buildURL);
|
||||
}),
|
||||
StandartListItem(
|
||||
title: S.current.trade_details_created_at,
|
||||
value: dateFormat.format(order.createdAt).toString()),
|
||||
StandartListItem(
|
||||
title: S.current.trade_details_pair,
|
||||
value: '${order.from} → ${order.to}')
|
||||
]);
|
||||
|
||||
if (order.provider != null) {
|
||||
items.add(
|
||||
StandartListItem(
|
||||
title: 'Buy provider',
|
||||
value: order.provider.title)
|
||||
);
|
||||
}
|
||||
|
||||
if (_provider?.trackUrl?.isNotEmpty ?? false) {
|
||||
final buildURL = _provider.trackUrl + '${order.transferId}';
|
||||
items.add(
|
||||
TrackTradeListItem(
|
||||
title: 'Track',
|
||||
value: buildURL,
|
||||
onTap: () => launch(buildURL)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
items.add(
|
||||
StandartListItem(
|
||||
title: S.current.trade_details_created_at,
|
||||
value: dateFormat.format(order.createdAt).toString())
|
||||
);
|
||||
|
||||
items.add(
|
||||
StandartListItem(
|
||||
title: S.current.trade_details_pair,
|
||||
value: '${order.from} → ${order.to}')
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,15 @@ abstract class SupportViewModelBase with Store {
|
||||
title: 'Email',
|
||||
linkTitle: 'support@cakewallet.com',
|
||||
link: 'mailto:support@cakewallet.com'),
|
||||
LinkListItem(
|
||||
title: 'Website',
|
||||
linkTitle: 'cakewallet.com',
|
||||
link: 'https://cakewallet.com'),
|
||||
LinkListItem(
|
||||
title: 'GitHub',
|
||||
icon: 'assets/images/github.png',
|
||||
linkTitle: S.current.apk_update,
|
||||
link: 'https://github.com/cake-tech/cake_wallet/releases'),
|
||||
LinkListItem(
|
||||
title: 'Telegram',
|
||||
icon: 'assets/images/Telegram.png',
|
||||
|
||||
@@ -39,7 +39,7 @@ abstract class TradeDetailsViewModelBase with Store {
|
||||
|
||||
_updateTrade();
|
||||
|
||||
_timer = Timer.periodic(Duration(seconds: 20), (_) async => _updateTrade());
|
||||
timer = Timer.periodic(Duration(seconds: 20), (_) async => _updateTrade());
|
||||
}
|
||||
|
||||
final Box<Trade> trades;
|
||||
@@ -52,7 +52,7 @@ abstract class TradeDetailsViewModelBase with Store {
|
||||
|
||||
ExchangeProvider _provider;
|
||||
|
||||
Timer _timer;
|
||||
Timer timer;
|
||||
|
||||
@action
|
||||
Future<void> _updateTrade() async {
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import 'package:cake_wallet/bitcoin/electrum_transaction_info.dart';
|
||||
import 'package:cake_wallet/core/wallet_base.dart';
|
||||
import 'package:cake_wallet/entities/transaction_info.dart';
|
||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
||||
import 'package:cake_wallet/monero/monero_transaction_info.dart';
|
||||
import 'package:cake_wallet/monero/monero_wallet.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/blockexplorer_list_item.dart';
|
||||
import 'package:cake_wallet/entities/transaction_direction.dart';
|
||||
import 'package:cake_wallet/utils/date_formatter.dart';
|
||||
import 'package:cake_wallet/entities/transaction_description.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
@@ -23,6 +26,7 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
TransactionDetailsViewModelBase(
|
||||
{this.transactionInfo,
|
||||
this.transactionDescriptionBox,
|
||||
this.wallet,
|
||||
this.settingsStore})
|
||||
: items = [] {
|
||||
showRecipientAddress = settingsStore?.shouldSaveRecipientAddress ?? false;
|
||||
@@ -51,6 +55,25 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
StandartListItem(title: S.current.transaction_key, value: tx.key));
|
||||
}
|
||||
|
||||
if ((tx.direction == TransactionDirection.incoming)&&
|
||||
(wallet is MoneroWallet)) {
|
||||
try {
|
||||
final accountIndex = tx.accountIndex;
|
||||
final addressIndex = tx.addressIndex;
|
||||
final _wallet = wallet as MoneroWallet;
|
||||
final address =
|
||||
_wallet.getTransactionAddress(accountIndex, addressIndex);
|
||||
if (address?.isNotEmpty ?? false) {
|
||||
_items.add(
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_recipient_address,
|
||||
value: address));
|
||||
}
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
items.addAll(_items);
|
||||
}
|
||||
|
||||
@@ -124,6 +147,7 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
final TransactionInfo transactionInfo;
|
||||
final Box<TransactionDescription> transactionDescriptionBox;
|
||||
final SettingsStore settingsStore;
|
||||
final WalletBase wallet;
|
||||
|
||||
final List<TransactionDetailsListItem> items;
|
||||
bool showRecipientAddress;
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import 'package:cake_wallet/entities/wyre_service.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:cake_wallet/entities/order.dart';
|
||||
import 'package:cake_wallet/store/dashboard/orders_store.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'wyre_view_model.g.dart';
|
||||
|
||||
class WyreViewModel = WyreViewModelBase with _$WyreViewModel;
|
||||
|
||||
abstract class WyreViewModelBase with Store {
|
||||
WyreViewModelBase(this.ordersSource, this.ordersStore,
|
||||
{@required this.wyreService});
|
||||
|
||||
Future<String> get wyreUrl => wyreService.getWyreUrl();
|
||||
|
||||
String get trackUrl => wyreService.trackUrl;
|
||||
|
||||
final Box<Order> ordersSource;
|
||||
final OrdersStore ordersStore;
|
||||
|
||||
final WyreService wyreService;
|
||||
|
||||
Future<void> saveOrder(String orderId) async {
|
||||
try {
|
||||
final order = await wyreService.findOrderById(orderId);
|
||||
await ordersSource.add(order);
|
||||
ordersStore.setOrder(order);
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user