mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-06-28 20:39:51 +00:00
Cw 514 add sort functionality for addressbook mywallets and contacts (#1309)
* add sort function to contact list * fix UI * prevent duplicate contact names * dispose contact source subscription * fix custom order issue * update the address book UI * fix saving custom order * fix merge conflict issue * review fixes [skip ci] * revert to single scroll for entire page * tabBarView address book --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
parent
459f0d352d
commit
109d9b458e
38 changed files with 588 additions and 236 deletions
|
@ -7,7 +7,8 @@ part 'contact.g.dart';
|
||||||
|
|
||||||
@HiveType(typeId: Contact.typeId)
|
@HiveType(typeId: Contact.typeId)
|
||||||
class Contact extends HiveObject with Keyable {
|
class Contact extends HiveObject with Keyable {
|
||||||
Contact({required this.name, required this.address, CryptoCurrency? type}) {
|
Contact({required this.name, required this.address, CryptoCurrency? type, DateTime? lastChange})
|
||||||
|
: lastChange = lastChange ?? DateTime.now() {
|
||||||
if (type != null) {
|
if (type != null) {
|
||||||
raw = type.raw;
|
raw = type.raw;
|
||||||
}
|
}
|
||||||
|
@ -25,6 +26,9 @@ class Contact extends HiveObject with Keyable {
|
||||||
@HiveField(2, defaultValue: 0)
|
@HiveField(2, defaultValue: 0)
|
||||||
late int raw;
|
late int raw;
|
||||||
|
|
||||||
|
@HiveField(3)
|
||||||
|
DateTime lastChange;
|
||||||
|
|
||||||
CryptoCurrency get type => CryptoCurrency.deserialize(raw: raw);
|
CryptoCurrency get type => CryptoCurrency.deserialize(raw: raw);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -36,6 +40,5 @@ class Contact extends HiveObject with Keyable {
|
||||||
@override
|
@override
|
||||||
int get hashCode => key.hashCode;
|
int get hashCode => key.hashCode;
|
||||||
|
|
||||||
void updateCryptoCurrency({required CryptoCurrency currency}) =>
|
void updateCryptoCurrency({required CryptoCurrency currency}) => raw = currency.raw;
|
||||||
raw = currency.raw;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,21 @@
|
||||||
|
import 'package:cake_wallet/entities/contact.dart';
|
||||||
|
import 'package:cake_wallet/entities/contact_base.dart';
|
||||||
|
import 'package:cake_wallet/entities/record.dart';
|
||||||
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:cake_wallet/entities/contact.dart';
|
|
||||||
import 'package:cw_core/crypto_currency.dart';
|
|
||||||
import 'package:cake_wallet/entities/record.dart';
|
|
||||||
import 'package:cake_wallet/entities/contact_base.dart';
|
|
||||||
|
|
||||||
part 'contact_record.g.dart';
|
part 'contact_record.g.dart';
|
||||||
|
|
||||||
class ContactRecord = ContactRecordBase with _$ContactRecord;
|
class ContactRecord = ContactRecordBase with _$ContactRecord;
|
||||||
|
|
||||||
abstract class ContactRecordBase extends Record<Contact>
|
abstract class ContactRecordBase extends Record<Contact> with Store implements ContactBase {
|
||||||
with Store
|
|
||||||
implements ContactBase {
|
|
||||||
ContactRecordBase(Box<Contact> source, Contact original)
|
ContactRecordBase(Box<Contact> source, Contact original)
|
||||||
: name = original.name,
|
: name = original.name,
|
||||||
address = original.address,
|
address = original.address,
|
||||||
type = original.type,
|
type = original.type,
|
||||||
super(source, original);
|
lastChange = original.lastChange,
|
||||||
|
super(source, original);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@observable
|
@observable
|
||||||
|
@ -30,14 +29,14 @@ abstract class ContactRecordBase extends Record<Contact>
|
||||||
@observable
|
@observable
|
||||||
CryptoCurrency type;
|
CryptoCurrency type;
|
||||||
|
|
||||||
|
DateTime? lastChange;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void toBind(Contact original) {
|
void toBind(Contact original) {
|
||||||
reaction((_) => name, (String name) => original.name = name);
|
reaction((_) => name, (String name) => original.name = name);
|
||||||
reaction((_) => address, (String address) => original.address = address);
|
reaction((_) => address, (String address) => original.address = address);
|
||||||
reaction(
|
reaction((_) => type,
|
||||||
(_) => type,
|
(CryptoCurrency currency) => original.updateCryptoCurrency(currency: currency));
|
||||||
(CryptoCurrency currency) =>
|
|
||||||
original.updateCryptoCurrency(currency: currency));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -25,7 +25,9 @@ class PreferencesKey {
|
||||||
static const disableBulletinKey = 'disable_bulletin';
|
static const disableBulletinKey = 'disable_bulletin';
|
||||||
static const defaultBuyProvider = 'default_buy_provider';
|
static const defaultBuyProvider = 'default_buy_provider';
|
||||||
static const walletListOrder = 'wallet_list_order';
|
static const walletListOrder = 'wallet_list_order';
|
||||||
|
static const contactListOrder = 'contact_list_order';
|
||||||
static const walletListAscending = 'wallet_list_ascending';
|
static const walletListAscending = 'wallet_list_ascending';
|
||||||
|
static const contactListAscending = 'contact_list_ascending';
|
||||||
static const currentFiatApiModeKey = 'current_fiat_api_mode';
|
static const currentFiatApiModeKey = 'current_fiat_api_mode';
|
||||||
static const failedTotpTokenTrials = 'failed_token_trials';
|
static const failedTotpTokenTrials = 'failed_token_trials';
|
||||||
static const disableExchangeKey = 'disable_exchange';
|
static const disableExchangeKey = 'disable_exchange';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
|
||||||
enum WalletListOrderType {
|
enum FilterListOrderType {
|
||||||
CreationDate,
|
CreationDate,
|
||||||
Alphabetical,
|
Alphabetical,
|
||||||
GroupByType,
|
GroupByType,
|
||||||
|
@ -9,13 +9,13 @@ enum WalletListOrderType {
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case WalletListOrderType.CreationDate:
|
case FilterListOrderType.CreationDate:
|
||||||
return S.current.creation_date;
|
return S.current.creation_date;
|
||||||
case WalletListOrderType.Alphabetical:
|
case FilterListOrderType.Alphabetical:
|
||||||
return S.current.alphabetical;
|
return S.current.alphabetical;
|
||||||
case WalletListOrderType.GroupByType:
|
case FilterListOrderType.GroupByType:
|
||||||
return S.current.group_by_type;
|
return S.current.group_by_type;
|
||||||
case WalletListOrderType.Custom:
|
case FilterListOrderType.Custom:
|
||||||
return S.current.custom_drag;
|
return S.current.custom_drag;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,24 @@
|
||||||
import 'package:cake_wallet/core/auth_service.dart';
|
import 'package:cake_wallet/core/auth_service.dart';
|
||||||
import 'package:cake_wallet/entities/contact_base.dart';
|
import 'package:cake_wallet/entities/contact_base.dart';
|
||||||
import 'package:cake_wallet/entities/contact_record.dart';
|
import 'package:cake_wallet/entities/contact_record.dart';
|
||||||
|
import 'package:cake_wallet/entities/wallet_list_order_types.dart';
|
||||||
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
import 'package:cake_wallet/routes.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/dashboard/widgets/filter_list_widget.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/wallet_list/filtered_list.dart';
|
||||||
|
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||||
|
import 'package:cake_wallet/src/widgets/standard_list.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart';
|
import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart';
|
||||||
import 'package:cake_wallet/utils/show_bar.dart';
|
import 'package:cake_wallet/utils/show_bar.dart';
|
||||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
|
||||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
|
||||||
import 'package:cake_wallet/routes.dart';
|
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
|
||||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
|
||||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
|
||||||
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
|
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
|
||||||
import 'package:cake_wallet/src/widgets/collapsible_standart_list.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||||
|
|
||||||
class ContactListPage extends BasePage {
|
class ContactListPage extends BasePage {
|
||||||
ContactListPage(this.contactListViewModel, this.authService);
|
ContactListPage(this.contactListViewModel, this.authService);
|
||||||
|
@ -74,45 +77,101 @@ class ContactListPage extends BasePage {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget body(BuildContext context) {
|
Widget body(BuildContext context) => ContactPageBody(contactListViewModel: contactListViewModel);
|
||||||
return Container(
|
}
|
||||||
padding: EdgeInsets.all(20.0),
|
|
||||||
child: Observer(builder: (_) {
|
|
||||||
final contacts = contactListViewModel.contactsToShow;
|
|
||||||
final walletContacts = contactListViewModel.walletContactsToShow;
|
|
||||||
return CollapsibleSectionList(
|
|
||||||
sectionCount: 2,
|
|
||||||
sectionTitleBuilder: (int sectionIndex) {
|
|
||||||
var title = S.current.contact_list_contacts;
|
|
||||||
|
|
||||||
if (sectionIndex == 0) {
|
class ContactPageBody extends StatefulWidget {
|
||||||
title = S.current.contact_list_wallets;
|
const ContactPageBody({required this.contactListViewModel});
|
||||||
}
|
|
||||||
|
|
||||||
return Container(
|
final ContactListViewModel contactListViewModel;
|
||||||
padding: EdgeInsets.only(bottom: 10),
|
|
||||||
child: Text(title, style: TextStyle(fontSize: 36)));
|
|
||||||
},
|
|
||||||
itemCounter: (int sectionIndex) =>
|
|
||||||
sectionIndex == 0 ? walletContacts.length : contacts.length,
|
|
||||||
itemBuilder: (int sectionIndex, index) {
|
|
||||||
if (sectionIndex == 0) {
|
|
||||||
final walletInfo = walletContacts[index];
|
|
||||||
return generateRaw(context, walletInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
final contact = contacts[index];
|
@override
|
||||||
final content = generateRaw(context, contact);
|
State<ContactPageBody> createState() => _ContactPageBodyState();
|
||||||
return contactListViewModel.isEditable
|
}
|
||||||
? Slidable(
|
|
||||||
key: Key('${contact.key}'),
|
class _ContactPageBodyState extends State<ContactPageBody> with SingleTickerProviderStateMixin {
|
||||||
endActionPane: _actionPane(context, contact),
|
late TabController _tabController;
|
||||||
child: content,
|
|
||||||
)
|
@override
|
||||||
: content;
|
void initState() {
|
||||||
},
|
super.initState();
|
||||||
);
|
_tabController = TabController(length: 2, vsync: this);
|
||||||
}));
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_tabController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 24),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: TabBar(
|
||||||
|
controller: _tabController,
|
||||||
|
splashFactory: NoSplash.splashFactory,
|
||||||
|
indicatorSize: TabBarIndicatorSize.label,
|
||||||
|
isScrollable: true,
|
||||||
|
labelStyle: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontFamily: 'Lato',
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Theme.of(context).appBarTheme.titleTextStyle!.color,
|
||||||
|
),
|
||||||
|
unselectedLabelStyle: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontFamily: 'Lato',
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Theme.of(context).appBarTheme.titleTextStyle!.color?.withOpacity(0.5)),
|
||||||
|
labelColor: Theme.of(context).appBarTheme.titleTextStyle!.color,
|
||||||
|
indicatorColor: Theme.of(context).appBarTheme.titleTextStyle!.color,
|
||||||
|
indicatorPadding: EdgeInsets.zero,
|
||||||
|
labelPadding: EdgeInsets.only(right: 24),
|
||||||
|
tabAlignment: TabAlignment.center,
|
||||||
|
dividerColor: Colors.transparent,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
tabs: [
|
||||||
|
Tab(text: S.of(context).wallets),
|
||||||
|
Tab(text: S.of(context).contact_list_contacts),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: TabBarView(
|
||||||
|
controller: _tabController,
|
||||||
|
children: [
|
||||||
|
_buildWalletContacts(context),
|
||||||
|
ContactListBody(
|
||||||
|
contactListViewModel: widget.contactListViewModel,
|
||||||
|
tabController: _tabController),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildWalletContacts(BuildContext context) {
|
||||||
|
final walletContacts = widget.contactListViewModel.walletContactsToShow;
|
||||||
|
|
||||||
|
return ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: walletContacts.length * 2,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
if (index.isOdd) {
|
||||||
|
return StandardListSeparator();
|
||||||
|
} else {
|
||||||
|
final walletInfo = walletContacts[index ~/ 2];
|
||||||
|
return generateRaw(context, walletInfo);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget generateRaw(BuildContext context, ContactBase contact) {
|
Widget generateRaw(BuildContext context, ContactBase contact) {
|
||||||
|
@ -123,7 +182,7 @@ class ContactListPage extends BasePage {
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
if (!contactListViewModel.isEditable) {
|
if (!widget.contactListViewModel.isEditable) {
|
||||||
Navigator.of(context).pop(contact);
|
Navigator.of(context).pop(contact);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -143,8 +202,7 @@ class ContactListPage extends BasePage {
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
currencyIcon,
|
currencyIcon,
|
||||||
Expanded(
|
Padding(
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.only(left: 12),
|
padding: EdgeInsets.only(left: 12),
|
||||||
child: Text(
|
child: Text(
|
||||||
contact.name,
|
contact.name,
|
||||||
|
@ -154,13 +212,215 @@ class ContactListPage extends BasePage {
|
||||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
))
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> showNameAndAddressDialog(BuildContext context, String name, String address) async {
|
||||||
|
return await showPopUp<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertWithTwoActions(
|
||||||
|
alertTitle: name,
|
||||||
|
alertContent: address,
|
||||||
|
rightButtonText: S.of(context).copy,
|
||||||
|
leftButtonText: S.of(context).cancel,
|
||||||
|
actionRightButton: () => Navigator.of(context).pop(true),
|
||||||
|
actionLeftButton: () => Navigator.of(context).pop(false));
|
||||||
|
}) ??
|
||||||
|
false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ContactListBody extends StatefulWidget {
|
||||||
|
ContactListBody({required this.contactListViewModel, required this.tabController});
|
||||||
|
|
||||||
|
final ContactListViewModel contactListViewModel;
|
||||||
|
final TabController tabController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ContactListBody> createState() => _ContactListBodyState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ContactListBodyState extends State<ContactListBody> {
|
||||||
|
bool _isContactsTabActive = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
widget.tabController.addListener(_handleTabChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleTabChange() {
|
||||||
|
setState(() {
|
||||||
|
_isContactsTabActive = widget.tabController.index == 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
widget.tabController.removeListener(_handleTabChange);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final contacts = widget.contactListViewModel.contacts;
|
||||||
|
return Scaffold(
|
||||||
|
body: Container(
|
||||||
|
child: FilteredList(
|
||||||
|
list: contacts,
|
||||||
|
updateFunction: widget.contactListViewModel.reorderAccordingToContactList,
|
||||||
|
canReorder: widget.contactListViewModel.isEditable,
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final contact = contacts[index];
|
||||||
|
final contactContent =
|
||||||
|
generateContactRaw(context, contact, contacts.length == index + 1);
|
||||||
|
return GestureDetector(
|
||||||
|
key: Key('${contact.name}'),
|
||||||
|
onTap: () async {
|
||||||
|
if (!widget.contactListViewModel.isEditable) {
|
||||||
|
Navigator.of(context).pop(contact);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final isCopied =
|
||||||
|
await showNameAndAddressDialog(context, contact.name, contact.address);
|
||||||
|
|
||||||
|
if (isCopied) {
|
||||||
|
await Clipboard.setData(ClipboardData(text: contact.address));
|
||||||
|
await showBar<void>(context, S.of(context).copied_to_clipboard);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
child: widget.contactListViewModel.isEditable
|
||||||
|
? Slidable(
|
||||||
|
key: Key('${contact.key}'),
|
||||||
|
endActionPane: _actionPane(context, contact),
|
||||||
|
child: contactContent)
|
||||||
|
: contactContent,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
floatingActionButton:
|
||||||
|
_isContactsTabActive ? filterButtonWidget(context, widget.contactListViewModel) : null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget generateContactRaw(BuildContext context, ContactRecord contact, bool isLast) {
|
||||||
|
final image = contact.type.iconPath;
|
||||||
|
final currencyIcon = image != null
|
||||||
|
? Image.asset(image, height: 24, width: 24)
|
||||||
|
: const SizedBox(height: 24, width: 24);
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
key: Key('${contact.name}'),
|
||||||
|
padding: const EdgeInsets.only(top: 16, bottom: 16, right: 24),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
currencyIcon,
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.only(left: 12),
|
||||||
|
child: Text(
|
||||||
|
contact.name,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
|
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
StandardListSeparator()
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionPane _actionPane(BuildContext context, ContactRecord contact) => ActionPane(
|
||||||
|
motion: const ScrollMotion(),
|
||||||
|
extentRatio: 0.4,
|
||||||
|
children: [
|
||||||
|
SlidableAction(
|
||||||
|
onPressed: (_) async => await Navigator.of(context)
|
||||||
|
.pushNamed(Routes.addressBookAddContact, arguments: contact),
|
||||||
|
backgroundColor: Colors.blue,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
icon: Icons.edit,
|
||||||
|
label: S.of(context).edit,
|
||||||
|
),
|
||||||
|
SlidableAction(
|
||||||
|
onPressed: (_) async {
|
||||||
|
final isDelete = await showAlertDialog(context);
|
||||||
|
|
||||||
|
if (isDelete) {
|
||||||
|
await widget.contactListViewModel.delete(contact);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
icon: CupertinoIcons.delete,
|
||||||
|
label: S.of(context).delete,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget filterButtonWidget(BuildContext context, ContactListViewModel contactListViewModel) {
|
||||||
|
final filterIcon = Image.asset('assets/images/filter_icon.png',
|
||||||
|
color: Theme.of(context).appBarTheme.titleTextStyle!.color);
|
||||||
|
return MergeSemantics(
|
||||||
|
child: SizedBox(
|
||||||
|
height: 58,
|
||||||
|
width: 58,
|
||||||
|
child: ButtonTheme(
|
||||||
|
minWidth: double.minPositive,
|
||||||
|
child: Semantics(
|
||||||
|
container: true,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () async {
|
||||||
|
await showPopUp<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => FilterListWidget(
|
||||||
|
initalType: contactListViewModel.orderType,
|
||||||
|
initalAscending: contactListViewModel.ascending,
|
||||||
|
onClose: (bool ascending, FilterListOrderType type) async {
|
||||||
|
contactListViewModel.setAscending(ascending);
|
||||||
|
await contactListViewModel.setOrderType(type);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Semantics(
|
||||||
|
label: 'Transaction Filter',
|
||||||
|
button: true,
|
||||||
|
enabled: true,
|
||||||
|
child: Container(
|
||||||
|
height: 36,
|
||||||
|
width: 36,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: Theme.of(context).extension<ExchangePageTheme>()!.buttonBackgroundColor,
|
||||||
|
),
|
||||||
|
child: filterIcon,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<bool> showAlertDialog(BuildContext context) async {
|
Future<bool> showAlertDialog(BuildContext context) async {
|
||||||
return await showPopUp<bool>(
|
return await showPopUp<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
|
@ -190,32 +450,4 @@ class ContactListPage extends BasePage {
|
||||||
}) ??
|
}) ??
|
||||||
false;
|
false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ActionPane _actionPane(BuildContext context, ContactRecord contact) => ActionPane(
|
|
||||||
motion: const ScrollMotion(),
|
|
||||||
extentRatio: 0.4,
|
|
||||||
children: [
|
|
||||||
SlidableAction(
|
|
||||||
onPressed: (_) async => await Navigator.of(context)
|
|
||||||
.pushNamed(Routes.addressBookAddContact, arguments: contact),
|
|
||||||
backgroundColor: Colors.blue,
|
|
||||||
foregroundColor: Colors.white,
|
|
||||||
icon: Icons.edit,
|
|
||||||
label: S.of(context).edit,
|
|
||||||
),
|
|
||||||
SlidableAction(
|
|
||||||
onPressed: (_) async {
|
|
||||||
final isDelete = await showAlertDialog(context);
|
|
||||||
|
|
||||||
if (isDelete) {
|
|
||||||
await contactListViewModel.delete(contact);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
backgroundColor: Colors.red,
|
|
||||||
foregroundColor: Colors.white,
|
|
||||||
icon: CupertinoIcons.delete,
|
|
||||||
label: S.of(context).delete,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,9 +18,9 @@ class FilterListWidget extends StatefulWidget {
|
||||||
required this.onClose,
|
required this.onClose,
|
||||||
});
|
});
|
||||||
|
|
||||||
final WalletListOrderType? initalType;
|
final FilterListOrderType? initalType;
|
||||||
final bool initalAscending;
|
final bool initalAscending;
|
||||||
final Function(bool, WalletListOrderType) onClose;
|
final Function(bool, FilterListOrderType) onClose;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FilterListWidgetState createState() => FilterListWidgetState();
|
FilterListWidgetState createState() => FilterListWidgetState();
|
||||||
|
@ -28,7 +28,7 @@ class FilterListWidget extends StatefulWidget {
|
||||||
|
|
||||||
class FilterListWidgetState extends State<FilterListWidget> {
|
class FilterListWidgetState extends State<FilterListWidget> {
|
||||||
late bool ascending;
|
late bool ascending;
|
||||||
late WalletListOrderType? type;
|
late FilterListOrderType? type;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
@ -37,7 +37,7 @@ class FilterListWidgetState extends State<FilterListWidget> {
|
||||||
type = widget.initalType;
|
type = widget.initalType;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setSelectedOrderType(WalletListOrderType? orderType) {
|
void setSelectedOrderType(FilterListOrderType? orderType) {
|
||||||
setState(() {
|
setState(() {
|
||||||
type = orderType;
|
type = orderType;
|
||||||
});
|
});
|
||||||
|
@ -72,7 +72,7 @@ class FilterListWidgetState extends State<FilterListWidget> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (type != WalletListOrderType.Custom) ...[
|
if (type != FilterListOrderType.Custom) ...[
|
||||||
sectionDivider,
|
sectionDivider,
|
||||||
SettingsChoicesCell(
|
SettingsChoicesCell(
|
||||||
ChoicesListItem<ListOrderMode>(
|
ChoicesListItem<ListOrderMode>(
|
||||||
|
@ -89,10 +89,10 @@ class FilterListWidgetState extends State<FilterListWidget> {
|
||||||
],
|
],
|
||||||
sectionDivider,
|
sectionDivider,
|
||||||
RadioListTile(
|
RadioListTile(
|
||||||
value: WalletListOrderType.CreationDate,
|
value: FilterListOrderType.CreationDate,
|
||||||
groupValue: type,
|
groupValue: type,
|
||||||
title: Text(
|
title: Text(
|
||||||
WalletListOrderType.CreationDate.toString(),
|
FilterListOrderType.CreationDate.toString(),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
|
@ -104,10 +104,10 @@ class FilterListWidgetState extends State<FilterListWidget> {
|
||||||
activeColor: Theme.of(context).primaryColor,
|
activeColor: Theme.of(context).primaryColor,
|
||||||
),
|
),
|
||||||
RadioListTile(
|
RadioListTile(
|
||||||
value: WalletListOrderType.Alphabetical,
|
value: FilterListOrderType.Alphabetical,
|
||||||
groupValue: type,
|
groupValue: type,
|
||||||
title: Text(
|
title: Text(
|
||||||
WalletListOrderType.Alphabetical.toString(),
|
FilterListOrderType.Alphabetical.toString(),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
|
@ -119,10 +119,10 @@ class FilterListWidgetState extends State<FilterListWidget> {
|
||||||
activeColor: Theme.of(context).primaryColor,
|
activeColor: Theme.of(context).primaryColor,
|
||||||
),
|
),
|
||||||
RadioListTile(
|
RadioListTile(
|
||||||
value: WalletListOrderType.GroupByType,
|
value: FilterListOrderType.GroupByType,
|
||||||
groupValue: type,
|
groupValue: type,
|
||||||
title: Text(
|
title: Text(
|
||||||
WalletListOrderType.GroupByType.toString(),
|
FilterListOrderType.GroupByType.toString(),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
|
@ -134,10 +134,10 @@ class FilterListWidgetState extends State<FilterListWidget> {
|
||||||
activeColor: Theme.of(context).primaryColor,
|
activeColor: Theme.of(context).primaryColor,
|
||||||
),
|
),
|
||||||
RadioListTile(
|
RadioListTile(
|
||||||
value: WalletListOrderType.Custom,
|
value: FilterListOrderType.Custom,
|
||||||
groupValue: type,
|
groupValue: type,
|
||||||
title: Text(
|
title: Text(
|
||||||
WalletListOrderType.Custom.toString(),
|
FilterListOrderType.Custom.toString(),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
|
|
|
@ -7,13 +7,17 @@ class FilteredList extends StatefulWidget {
|
||||||
required this.list,
|
required this.list,
|
||||||
required this.itemBuilder,
|
required this.itemBuilder,
|
||||||
required this.updateFunction,
|
required this.updateFunction,
|
||||||
|
this.canReorder = true,
|
||||||
this.shrinkWrap = false,
|
this.shrinkWrap = false,
|
||||||
|
this.physics,
|
||||||
});
|
});
|
||||||
|
|
||||||
final ObservableList<dynamic> list;
|
final ObservableList<dynamic> list;
|
||||||
final Widget Function(BuildContext, int) itemBuilder;
|
final Widget Function(BuildContext, int) itemBuilder;
|
||||||
final Function updateFunction;
|
final Function updateFunction;
|
||||||
|
final bool canReorder;
|
||||||
final bool shrinkWrap;
|
final bool shrinkWrap;
|
||||||
|
final ScrollPhysics? physics;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FilteredListState createState() => FilteredListState();
|
FilteredListState createState() => FilteredListState();
|
||||||
|
@ -22,21 +26,31 @@ class FilteredList extends StatefulWidget {
|
||||||
class FilteredListState extends State<FilteredList> {
|
class FilteredListState extends State<FilteredList> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Observer(
|
if (widget.canReorder) {
|
||||||
builder: (_) => ReorderableListView.builder(
|
return Observer(
|
||||||
shrinkWrap: widget.shrinkWrap,
|
builder: (_) => ReorderableListView.builder(
|
||||||
physics: const BouncingScrollPhysics(),
|
shrinkWrap: widget.shrinkWrap,
|
||||||
itemBuilder: widget.itemBuilder,
|
physics: widget.physics ?? const BouncingScrollPhysics(),
|
||||||
itemCount: widget.list.length,
|
itemBuilder: widget.itemBuilder,
|
||||||
onReorder: (int oldIndex, int newIndex) {
|
itemCount: widget.list.length,
|
||||||
if (oldIndex < newIndex) {
|
onReorder: (int oldIndex, int newIndex) {
|
||||||
newIndex -= 1;
|
if (oldIndex < newIndex) {
|
||||||
}
|
newIndex -= 1;
|
||||||
final dynamic item = widget.list.removeAt(oldIndex);
|
}
|
||||||
widget.list.insert(newIndex, item);
|
final dynamic item = widget.list.removeAt(oldIndex);
|
||||||
widget.updateFunction();
|
widget.list.insert(newIndex, item);
|
||||||
},
|
widget.updateFunction();
|
||||||
),
|
},
|
||||||
);
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Observer(
|
||||||
|
builder: (_) => ListView.builder(
|
||||||
|
physics: widget.physics ?? const BouncingScrollPhysics(),
|
||||||
|
itemBuilder: widget.itemBuilder,
|
||||||
|
itemCount: widget.list.length,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,7 +59,7 @@ class WalletListPage extends BasePage {
|
||||||
builder: (context) => FilterListWidget(
|
builder: (context) => FilterListWidget(
|
||||||
initalType: walletListViewModel.orderType,
|
initalType: walletListViewModel.orderType,
|
||||||
initalAscending: walletListViewModel.ascending,
|
initalAscending: walletListViewModel.ascending,
|
||||||
onClose: (bool ascending, WalletListOrderType type) async {
|
onClose: (bool ascending, FilterListOrderType type) async {
|
||||||
walletListViewModel.setAscending(ascending);
|
walletListViewModel.setAscending(ascending);
|
||||||
await walletListViewModel.setOrderType(type);
|
await walletListViewModel.setOrderType(type);
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
import 'package:cake_wallet/src/widgets/standard_list.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class CollapsibleSectionList extends SectionStandardList {
|
|
||||||
CollapsibleSectionList(
|
|
||||||
{required int sectionCount,
|
|
||||||
required int Function(int sectionIndex) itemCounter,
|
|
||||||
required Widget Function(int sectionIndex, int itemIndex) itemBuilder,
|
|
||||||
Widget Function(int sectionIndex)? sectionTitleBuilder,
|
|
||||||
bool hasTopSeparator = false})
|
|
||||||
: super(
|
|
||||||
hasTopSeparator: hasTopSeparator,
|
|
||||||
sectionCount: sectionCount,
|
|
||||||
itemCounter: itemCounter,
|
|
||||||
itemBuilder: itemBuilder,
|
|
||||||
sectionTitleBuilder: sectionTitleBuilder);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget buildTitle(List<Widget> items, int sectionIndex) {
|
|
||||||
if (sectionTitleBuilder == null) {
|
|
||||||
throw Exception('Cannot to build title. sectionTitleBuilder is null');
|
|
||||||
}
|
|
||||||
return sectionTitleBuilder!.call(sectionIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Widget> buildSection(int itemCount, List<Widget> items, int sectionIndex) {
|
|
||||||
final List<Widget> section = [];
|
|
||||||
|
|
||||||
for (var itemIndex = 0; itemIndex < itemCount; itemIndex++) {
|
|
||||||
final item = itemBuilder(sectionIndex, itemIndex);
|
|
||||||
|
|
||||||
section.add(StandardListSeparator());
|
|
||||||
|
|
||||||
section.add(item);
|
|
||||||
}
|
|
||||||
return section;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -62,9 +62,11 @@ abstract class SettingsStoreBase with Store {
|
||||||
required bool initialAppSecure,
|
required bool initialAppSecure,
|
||||||
required bool initialDisableBuy,
|
required bool initialDisableBuy,
|
||||||
required bool initialDisableSell,
|
required bool initialDisableSell,
|
||||||
|
required FilterListOrderType initialWalletListOrder,
|
||||||
|
required FilterListOrderType initialContactListOrder,
|
||||||
required bool initialDisableBulletin,
|
required bool initialDisableBulletin,
|
||||||
required WalletListOrderType initialWalletListOrder,
|
|
||||||
required bool initialWalletListAscending,
|
required bool initialWalletListAscending,
|
||||||
|
required bool initialContactListAscending,
|
||||||
required FiatApiMode initialFiatMode,
|
required FiatApiMode initialFiatMode,
|
||||||
required bool initialAllowBiometricalAuthentication,
|
required bool initialAllowBiometricalAuthentication,
|
||||||
required String initialTotpSecretKey,
|
required String initialTotpSecretKey,
|
||||||
|
@ -149,7 +151,9 @@ abstract class SettingsStoreBase with Store {
|
||||||
disableSell = initialDisableSell,
|
disableSell = initialDisableSell,
|
||||||
disableBulletin = initialDisableBulletin,
|
disableBulletin = initialDisableBulletin,
|
||||||
walletListOrder = initialWalletListOrder,
|
walletListOrder = initialWalletListOrder,
|
||||||
|
contactListOrder = initialContactListOrder,
|
||||||
walletListAscending = initialWalletListAscending,
|
walletListAscending = initialWalletListAscending,
|
||||||
|
contactListAscending = initialContactListAscending,
|
||||||
shouldShowMarketPlaceInDashboard = initialShouldShowMarketPlaceInDashboard,
|
shouldShowMarketPlaceInDashboard = initialShouldShowMarketPlaceInDashboard,
|
||||||
exchangeStatus = initialExchangeStatus,
|
exchangeStatus = initialExchangeStatus,
|
||||||
currentTheme = initialTheme,
|
currentTheme = initialTheme,
|
||||||
|
@ -324,14 +328,24 @@ abstract class SettingsStoreBase with Store {
|
||||||
|
|
||||||
reaction(
|
reaction(
|
||||||
(_) => walletListOrder,
|
(_) => walletListOrder,
|
||||||
(WalletListOrderType walletListOrder) =>
|
(FilterListOrderType walletListOrder) =>
|
||||||
sharedPreferences.setInt(PreferencesKey.walletListOrder, walletListOrder.index));
|
sharedPreferences.setInt(PreferencesKey.walletListOrder, walletListOrder.index));
|
||||||
|
|
||||||
|
reaction(
|
||||||
|
(_) => contactListOrder,
|
||||||
|
(FilterListOrderType contactListOrder) =>
|
||||||
|
sharedPreferences.setInt(PreferencesKey.contactListOrder, contactListOrder.index));
|
||||||
|
|
||||||
reaction(
|
reaction(
|
||||||
(_) => walletListAscending,
|
(_) => walletListAscending,
|
||||||
(bool walletListAscending) =>
|
(bool walletListAscending) =>
|
||||||
sharedPreferences.setBool(PreferencesKey.walletListAscending, walletListAscending));
|
sharedPreferences.setBool(PreferencesKey.walletListAscending, walletListAscending));
|
||||||
|
|
||||||
|
reaction(
|
||||||
|
(_) => contactListAscending,
|
||||||
|
(bool contactListAscending) =>
|
||||||
|
sharedPreferences.setBool(PreferencesKey.contactListAscending, contactListAscending));
|
||||||
|
|
||||||
reaction(
|
reaction(
|
||||||
(_) => autoGenerateSubaddressStatus,
|
(_) => autoGenerateSubaddressStatus,
|
||||||
(AutoGenerateSubaddressStatus autoGenerateSubaddressStatus) => sharedPreferences.setInt(
|
(AutoGenerateSubaddressStatus autoGenerateSubaddressStatus) => sharedPreferences.setInt(
|
||||||
|
@ -645,15 +659,21 @@ abstract class SettingsStoreBase with Store {
|
||||||
@observable
|
@observable
|
||||||
bool disableSell;
|
bool disableSell;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
FilterListOrderType contactListOrder;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
bool disableBulletin;
|
bool disableBulletin;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
WalletListOrderType walletListOrder;
|
FilterListOrderType walletListOrder;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
bool walletListAscending;
|
bool walletListAscending;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
bool contactListAscending;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
bool allowBiometricalAuthentication;
|
bool allowBiometricalAuthentication;
|
||||||
|
|
||||||
|
@ -907,9 +927,13 @@ abstract class SettingsStoreBase with Store {
|
||||||
final disableSell = sharedPreferences.getBool(PreferencesKey.disableSellKey) ?? false;
|
final disableSell = sharedPreferences.getBool(PreferencesKey.disableSellKey) ?? false;
|
||||||
final disableBulletin = sharedPreferences.getBool(PreferencesKey.disableBulletinKey) ?? false;
|
final disableBulletin = sharedPreferences.getBool(PreferencesKey.disableBulletinKey) ?? false;
|
||||||
final walletListOrder =
|
final walletListOrder =
|
||||||
WalletListOrderType.values[sharedPreferences.getInt(PreferencesKey.walletListOrder) ?? 0];
|
FilterListOrderType.values[sharedPreferences.getInt(PreferencesKey.walletListOrder) ?? 0];
|
||||||
|
final contactListOrder =
|
||||||
|
FilterListOrderType.values[sharedPreferences.getInt(PreferencesKey.contactListOrder) ?? 0];
|
||||||
final walletListAscending =
|
final walletListAscending =
|
||||||
sharedPreferences.getBool(PreferencesKey.walletListAscending) ?? true;
|
sharedPreferences.getBool(PreferencesKey.walletListAscending) ?? true;
|
||||||
|
final contactListAscending =
|
||||||
|
sharedPreferences.getBool(PreferencesKey.contactListAscending) ?? true;
|
||||||
final currentFiatApiMode = FiatApiMode.deserialize(
|
final currentFiatApiMode = FiatApiMode.deserialize(
|
||||||
raw: sharedPreferences.getInt(PreferencesKey.currentFiatApiModeKey) ??
|
raw: sharedPreferences.getInt(PreferencesKey.currentFiatApiModeKey) ??
|
||||||
FiatApiMode.enabled.raw);
|
FiatApiMode.enabled.raw);
|
||||||
|
@ -1200,6 +1224,8 @@ abstract class SettingsStoreBase with Store {
|
||||||
initialDisableBulletin: disableBulletin,
|
initialDisableBulletin: disableBulletin,
|
||||||
initialWalletListOrder: walletListOrder,
|
initialWalletListOrder: walletListOrder,
|
||||||
initialWalletListAscending: walletListAscending,
|
initialWalletListAscending: walletListAscending,
|
||||||
|
initialContactListOrder: contactListOrder,
|
||||||
|
initialContactListAscending: contactListAscending,
|
||||||
initialFiatMode: currentFiatApiMode,
|
initialFiatMode: currentFiatApiMode,
|
||||||
initialAllowBiometricalAuthentication: allowBiometricalAuthentication,
|
initialAllowBiometricalAuthentication: allowBiometricalAuthentication,
|
||||||
initialCake2FAPresetOptions: selectedCake2FAPreset,
|
initialCake2FAPresetOptions: selectedCake2FAPreset,
|
||||||
|
@ -1348,9 +1374,11 @@ abstract class SettingsStoreBase with Store {
|
||||||
disableBulletin =
|
disableBulletin =
|
||||||
sharedPreferences.getBool(PreferencesKey.disableBulletinKey) ?? disableBulletin;
|
sharedPreferences.getBool(PreferencesKey.disableBulletinKey) ?? disableBulletin;
|
||||||
walletListOrder =
|
walletListOrder =
|
||||||
WalletListOrderType.values[sharedPreferences.getInt(PreferencesKey.walletListOrder) ?? 0];
|
FilterListOrderType.values[sharedPreferences.getInt(PreferencesKey.walletListOrder) ?? 0];
|
||||||
|
contactListOrder =
|
||||||
|
FilterListOrderType.values[sharedPreferences.getInt(PreferencesKey.contactListOrder) ?? 0];
|
||||||
walletListAscending = sharedPreferences.getBool(PreferencesKey.walletListAscending) ?? true;
|
walletListAscending = sharedPreferences.getBool(PreferencesKey.walletListAscending) ?? true;
|
||||||
|
contactListAscending = sharedPreferences.getBool(PreferencesKey.contactListAscending) ?? true;
|
||||||
shouldShowMarketPlaceInDashboard =
|
shouldShowMarketPlaceInDashboard =
|
||||||
sharedPreferences.getBool(PreferencesKey.shouldShowMarketPlaceInDashboard) ??
|
sharedPreferences.getBool(PreferencesKey.shouldShowMarketPlaceInDashboard) ??
|
||||||
shouldShowMarketPlaceInDashboard;
|
shouldShowMarketPlaceInDashboard;
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
|
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
|
||||||
|
import 'package:cake_wallet/entities/contact.dart';
|
||||||
import 'package:cake_wallet/entities/contact_base.dart';
|
import 'package:cake_wallet/entities/contact_base.dart';
|
||||||
|
import 'package:cake_wallet/entities/contact_record.dart';
|
||||||
import 'package:cake_wallet/entities/wallet_contact.dart';
|
import 'package:cake_wallet/entities/wallet_contact.dart';
|
||||||
|
import 'package:cake_wallet/entities/wallet_list_order_types.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/store/settings_store.dart';
|
import 'package:cake_wallet/store/settings_store.dart';
|
||||||
|
import 'package:cake_wallet/utils/mobx.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:cake_wallet/entities/contact_record.dart';
|
|
||||||
import 'package:cake_wallet/entities/contact.dart';
|
|
||||||
import 'package:cake_wallet/utils/mobx.dart';
|
|
||||||
import 'package:cw_core/crypto_currency.dart';
|
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
|
|
||||||
part 'contact_list_view_model.g.dart';
|
part 'contact_list_view_model.g.dart';
|
||||||
|
|
||||||
|
@ -75,6 +77,8 @@ abstract class ContactListViewModelBase with Store {
|
||||||
_subscription = contactSource.bindToListWithTransform(
|
_subscription = contactSource.bindToListWithTransform(
|
||||||
contacts, (Contact contact) => ContactRecord(contactSource, contact),
|
contacts, (Contact contact) => ContactRecord(contactSource, contact),
|
||||||
initialFire: true);
|
initialFire: true);
|
||||||
|
|
||||||
|
setOrderType(settingsStore.contactListOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
String _createName(String walletName, String label, {int? key = null}) {
|
String _createName(String walletName, String label, {int? key = null}) {
|
||||||
|
@ -93,6 +97,10 @@ abstract class ContactListViewModelBase with Store {
|
||||||
|
|
||||||
bool get isEditable => _currency == null;
|
bool get isEditable => _currency == null;
|
||||||
|
|
||||||
|
FilterListOrderType? get orderType => settingsStore.contactListOrder;
|
||||||
|
|
||||||
|
bool get ascending => settingsStore.contactListAscending;
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
bool get shouldRequireTOTP2FAForAddingContacts =>
|
bool get shouldRequireTOTP2FAForAddingContacts =>
|
||||||
settingsStore.shouldRequireTOTP2FAForAddingContacts;
|
settingsStore.shouldRequireTOTP2FAForAddingContacts;
|
||||||
|
@ -118,4 +126,70 @@ abstract class ContactListViewModelBase with Store {
|
||||||
_currency?.toString() == element.type.tag ||
|
_currency?.toString() == element.type.tag ||
|
||||||
_currency?.tag == element.type.toString();
|
_currency?.tag == element.type.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void dispose() async {
|
||||||
|
_subscription?.cancel();
|
||||||
|
final List<Contact> contactsSourceCopy = contacts.map((e) => e.original).toList();
|
||||||
|
await reorderContacts(contactsSourceCopy);
|
||||||
|
}
|
||||||
|
|
||||||
|
void reorderAccordingToContactList() =>
|
||||||
|
settingsStore.contactListOrder = FilterListOrderType.Custom;
|
||||||
|
|
||||||
|
Future<void> reorderContacts(List<Contact> contactCopy) async {
|
||||||
|
await contactSource.deleteAll(contactCopy.map((e) => e.key).toList());
|
||||||
|
await contactSource.addAll(contactCopy);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> sortGroupByType() async {
|
||||||
|
List<Contact> contactsSourceCopy = contactSource.values.toList();
|
||||||
|
|
||||||
|
contactsSourceCopy.sort((a, b) => ascending
|
||||||
|
? a.type.toString().compareTo(b.type.toString())
|
||||||
|
: b.type.toString().compareTo(a.type.toString()));
|
||||||
|
|
||||||
|
await reorderContacts(contactsSourceCopy);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> sortAlphabetically() async {
|
||||||
|
List<Contact> contactsSourceCopy = contactSource.values.toList();
|
||||||
|
|
||||||
|
contactsSourceCopy
|
||||||
|
.sort((a, b) => ascending ? a.name.compareTo(b.name) : b.name.compareTo(a.name));
|
||||||
|
|
||||||
|
await reorderContacts(contactsSourceCopy);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> sortByCreationDate() async {
|
||||||
|
List<Contact> contactsSourceCopy = contactSource.values.toList();
|
||||||
|
|
||||||
|
contactsSourceCopy.sort((a, b) =>
|
||||||
|
ascending ? a.lastChange.compareTo(b.lastChange) : b.lastChange.compareTo(a.lastChange));
|
||||||
|
|
||||||
|
await reorderContacts(contactsSourceCopy);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setAscending(bool ascending) => settingsStore.contactListAscending = ascending;
|
||||||
|
|
||||||
|
Future<void> setOrderType(FilterListOrderType? type) async {
|
||||||
|
if (type == null) return;
|
||||||
|
|
||||||
|
settingsStore.contactListOrder = type;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case FilterListOrderType.CreationDate:
|
||||||
|
await sortByCreationDate();
|
||||||
|
break;
|
||||||
|
case FilterListOrderType.Alphabetical:
|
||||||
|
await sortAlphabetically();
|
||||||
|
break;
|
||||||
|
case FilterListOrderType.GroupByType:
|
||||||
|
await sortGroupByType();
|
||||||
|
break;
|
||||||
|
case FilterListOrderType.Custom:
|
||||||
|
default:
|
||||||
|
reorderAccordingToContactList();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'package:cake_wallet/entities/contact_record.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:cake_wallet/core/execution_state.dart';
|
import 'package:cake_wallet/core/execution_state.dart';
|
||||||
import 'package:cw_core/wallet_base.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/entities/contact.dart';
|
import 'package:cake_wallet/entities/contact.dart';
|
||||||
import 'package:cw_core/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
|
||||||
|
@ -17,7 +17,9 @@ abstract class ContactViewModelBase with Store {
|
||||||
_contact = contact,
|
_contact = contact,
|
||||||
name = contact?.name ?? '',
|
name = contact?.name ?? '',
|
||||||
address = contact?.address ?? '',
|
address = contact?.address ?? '',
|
||||||
currency = contact?.type;
|
currency = contact?.type,
|
||||||
|
lastChange = contact?.lastChange;
|
||||||
|
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
ExecutionState state;
|
ExecutionState state;
|
||||||
|
@ -31,6 +33,8 @@ abstract class ContactViewModelBase with Store {
|
||||||
@observable
|
@observable
|
||||||
CryptoCurrency? currency;
|
CryptoCurrency? currency;
|
||||||
|
|
||||||
|
DateTime? lastChange;
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
bool get isReady =>
|
bool get isReady =>
|
||||||
name.isNotEmpty &&
|
name.isNotEmpty &&
|
||||||
|
@ -51,20 +55,32 @@ abstract class ContactViewModelBase with Store {
|
||||||
Future<void> save() async {
|
Future<void> save() async {
|
||||||
try {
|
try {
|
||||||
state = IsExecutingState();
|
state = IsExecutingState();
|
||||||
|
final now = DateTime.now();
|
||||||
|
|
||||||
|
if (doesContactNameExist(name)) {
|
||||||
|
state = FailureState(S.current.contact_name_exists);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (_contact != null && _contact!.original.isInBox) {
|
if (_contact != null && _contact!.original.isInBox) {
|
||||||
_contact?.name = name;
|
_contact?.name = name;
|
||||||
_contact?.address = address;
|
_contact?.address = address;
|
||||||
_contact?.type = currency!;
|
_contact?.type = currency!;
|
||||||
|
_contact?.lastChange = now;
|
||||||
await _contact?.save();
|
await _contact?.save();
|
||||||
} else {
|
} else {
|
||||||
await _contacts
|
await _contacts
|
||||||
.add(Contact(name: name, address: address, type: currency!));
|
.add(Contact(name: name, address: address, type: currency!, lastChange: now));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lastChange = now;
|
||||||
state = ExecutedSuccessfullyState();
|
state = ExecutedSuccessfullyState();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
state = FailureState(e.toString());
|
state = FailureState(e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
bool doesContactNameExist(String name) {
|
||||||
|
return _contacts.values.any((contact) => contact.name == name);
|
||||||
|
}
|
||||||
|
}
|
|
@ -76,7 +76,7 @@ abstract class WalletListViewModelBase with Store {
|
||||||
await _appStore.changeCurrentWallet(wallet);
|
await _appStore.changeCurrentWallet(wallet);
|
||||||
}
|
}
|
||||||
|
|
||||||
WalletListOrderType? get orderType => _appStore.settingsStore.walletListOrder;
|
FilterListOrderType? get orderType => _appStore.settingsStore.walletListOrder;
|
||||||
|
|
||||||
bool get ascending => _appStore.settingsStore.walletListAscending;
|
bool get ascending => _appStore.settingsStore.walletListAscending;
|
||||||
|
|
||||||
|
@ -108,7 +108,7 @@ abstract class WalletListViewModelBase with Store {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_appStore.settingsStore.walletListOrder = WalletListOrderType.Custom;
|
_appStore.settingsStore.walletListOrder = FilterListOrderType.Custom;
|
||||||
|
|
||||||
// make a copy of the walletInfoSource:
|
// make a copy of the walletInfoSource:
|
||||||
List<WalletInfo> walletInfoSourceCopy = _walletInfoSource.values.toList();
|
List<WalletInfo> walletInfoSourceCopy = _walletInfoSource.values.toList();
|
||||||
|
@ -186,22 +186,22 @@ abstract class WalletListViewModelBase with Store {
|
||||||
_appStore.settingsStore.walletListAscending = ascending;
|
_appStore.settingsStore.walletListAscending = ascending;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setOrderType(WalletListOrderType? type) async {
|
Future<void> setOrderType(FilterListOrderType? type) async {
|
||||||
if (type == null) return;
|
if (type == null) return;
|
||||||
|
|
||||||
_appStore.settingsStore.walletListOrder = type;
|
_appStore.settingsStore.walletListOrder = type;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case WalletListOrderType.CreationDate:
|
case FilterListOrderType.CreationDate:
|
||||||
await sortByCreationDate();
|
await sortByCreationDate();
|
||||||
break;
|
break;
|
||||||
case WalletListOrderType.Alphabetical:
|
case FilterListOrderType.Alphabetical:
|
||||||
await sortAlphabetically();
|
await sortAlphabetically();
|
||||||
break;
|
break;
|
||||||
case WalletListOrderType.GroupByType:
|
case FilterListOrderType.GroupByType:
|
||||||
await sortGroupByType();
|
await sortGroupByType();
|
||||||
break;
|
break;
|
||||||
case WalletListOrderType.Custom:
|
case FilterListOrderType.Custom:
|
||||||
default:
|
default:
|
||||||
await reorderAccordingToWalletList();
|
await reorderAccordingToWalletList();
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -937,5 +937,6 @@
|
||||||
"you_pay": "انت تدفع",
|
"you_pay": "انت تدفع",
|
||||||
"you_will_get": "حول الى",
|
"you_will_get": "حول الى",
|
||||||
"you_will_send": "تحويل من",
|
"you_will_send": "تحويل من",
|
||||||
"yy": "YY"
|
"yy": "YY",
|
||||||
}
|
"contact_name_exists": " .ﻒﻠﺘﺨﻣ ﻢﺳﺍ ﺭﺎﻴﺘﺧﺍ ءﺎﺟﺮﻟﺍ .ﻞﻌﻔﻟﺎﺑ ﺓﺩﻮﺟﻮﻣ ﻢﺳﻻﺍ ﺍﺬﻬﺑ ﻝﺎﺼﺗﺍ ﺔﻬﺟ"
|
||||||
|
}
|
||||||
|
|
|
@ -937,5 +937,6 @@
|
||||||
"you_pay": "Вие плащате",
|
"you_pay": "Вие плащате",
|
||||||
"you_will_get": "Обръщане в",
|
"you_will_get": "Обръщане в",
|
||||||
"you_will_send": "Обръщане от",
|
"you_will_send": "Обръщане от",
|
||||||
"yy": "гг"
|
"yy": "гг",
|
||||||
}
|
"contact_name_exists": "Вече съществува контакт с това име. Моля, изберете друго име."
|
||||||
|
}
|
||||||
|
|
|
@ -937,5 +937,6 @@
|
||||||
"you_pay": "Zaplatíte",
|
"you_pay": "Zaplatíte",
|
||||||
"you_will_get": "Směnit na",
|
"you_will_get": "Směnit na",
|
||||||
"you_will_send": "Směnit z",
|
"you_will_send": "Směnit z",
|
||||||
"yy": "YY"
|
"yy": "YY",
|
||||||
}
|
"contact_name_exists": "Kontakt s tímto jménem již existuje. Vyberte prosím jiný název."
|
||||||
|
}
|
||||||
|
|
|
@ -940,5 +940,6 @@
|
||||||
"you_pay": "You Pay",
|
"you_pay": "You Pay",
|
||||||
"you_will_get": "Convert to",
|
"you_will_get": "Convert to",
|
||||||
"you_will_send": "Convert from",
|
"you_will_send": "Convert from",
|
||||||
"yy": "YY"
|
"yy": "YY",
|
||||||
}
|
"contact_name_exists": "A contact with that name already exists. Please choose a different name."
|
||||||
|
}
|
||||||
|
|
|
@ -938,5 +938,6 @@
|
||||||
"you_pay": "Tú pagas",
|
"you_pay": "Tú pagas",
|
||||||
"you_will_get": "Convertir a",
|
"you_will_get": "Convertir a",
|
||||||
"you_will_send": "Convertir de",
|
"you_will_send": "Convertir de",
|
||||||
"yy": "YY"
|
"yy": "YY",
|
||||||
|
"contact_name_exists": "Ya existe un contacto con ese nombre. Elija un nombre diferente."
|
||||||
}
|
}
|
||||||
|
|
|
@ -937,5 +937,6 @@
|
||||||
"you_pay": "Vous payez",
|
"you_pay": "Vous payez",
|
||||||
"you_will_get": "Convertir vers",
|
"you_will_get": "Convertir vers",
|
||||||
"you_will_send": "Convertir depuis",
|
"you_will_send": "Convertir depuis",
|
||||||
"yy": "AA"
|
"yy": "AA",
|
||||||
}
|
"contact_name_exists": "Un contact portant ce nom existe déjà. Veuillez choisir un autre nom."
|
||||||
|
}
|
||||||
|
|
|
@ -939,5 +939,6 @@
|
||||||
"you_pay": "Ka Bayar",
|
"you_pay": "Ka Bayar",
|
||||||
"you_will_get": "Maida zuwa",
|
"you_will_get": "Maida zuwa",
|
||||||
"you_will_send": "Maida daga",
|
"you_will_send": "Maida daga",
|
||||||
"yy": "YY"
|
"yy": "YY",
|
||||||
}
|
"contact_name_exists": "An riga an sami lamba tare da wannan sunan. Da fatan za a zaɓi suna daban."
|
||||||
|
}
|
||||||
|
|
|
@ -939,5 +939,6 @@
|
||||||
"you_pay": "आप भुगतान करते हैं",
|
"you_pay": "आप भुगतान करते हैं",
|
||||||
"you_will_get": "में बदलें",
|
"you_will_get": "में बदलें",
|
||||||
"you_will_send": "से रूपांतरित करें",
|
"you_will_send": "से रूपांतरित करें",
|
||||||
"yy": "वाईवाई"
|
"yy": "वाईवाई",
|
||||||
}
|
"contact_name_exists": "उस नाम का एक संपर्क पहले से मौजूद है. कृपया कोई भिन्न नाम चुनें."
|
||||||
|
}
|
||||||
|
|
|
@ -937,5 +937,6 @@
|
||||||
"you_pay": "Vi plaćate",
|
"you_pay": "Vi plaćate",
|
||||||
"you_will_get": "Razmijeni u",
|
"you_will_get": "Razmijeni u",
|
||||||
"you_will_send": "Razmijeni iz",
|
"you_will_send": "Razmijeni iz",
|
||||||
"yy": "GG"
|
"yy": "GG",
|
||||||
}
|
"contact_name_exists": "Kontakt s tim imenom već postoji. Odaberite drugo ime."
|
||||||
|
}
|
||||||
|
|
|
@ -940,5 +940,6 @@
|
||||||
"you_pay": "Anda Membayar",
|
"you_pay": "Anda Membayar",
|
||||||
"you_will_get": "Konversi ke",
|
"you_will_get": "Konversi ke",
|
||||||
"you_will_send": "Konversi dari",
|
"you_will_send": "Konversi dari",
|
||||||
"yy": "YY"
|
"yy": "YY",
|
||||||
}
|
"contact_name_exists": "Kontak dengan nama tersebut sudah ada. Silakan pilih nama lain."
|
||||||
|
}
|
||||||
|
|
|
@ -940,5 +940,6 @@
|
||||||
"you_pay": "Tu paghi",
|
"you_pay": "Tu paghi",
|
||||||
"you_will_get": "Converti a",
|
"you_will_get": "Converti a",
|
||||||
"you_will_send": "Conveti da",
|
"you_will_send": "Conveti da",
|
||||||
"yy": "YY"
|
"yy": "YY",
|
||||||
}
|
"contact_name_exists": "Esiste già un contatto con quel nome. Scegli un nome diverso."
|
||||||
|
}
|
||||||
|
|
|
@ -938,5 +938,6 @@
|
||||||
"you_pay": "あなたが支払う",
|
"you_pay": "あなたが支払う",
|
||||||
"you_will_get": "に変換",
|
"you_will_get": "に変換",
|
||||||
"you_will_send": "から変換",
|
"you_will_send": "から変換",
|
||||||
"yy": "YY"
|
"yy": "YY",
|
||||||
}
|
"contact_name_exists": "その名前の連絡先はすでに存在します。別の名前を選択してください。"
|
||||||
|
}
|
||||||
|
|
|
@ -939,5 +939,6 @@
|
||||||
"you_will_get": "로 변환하다",
|
"you_will_get": "로 변환하다",
|
||||||
"you_will_send": "다음에서 변환",
|
"you_will_send": "다음에서 변환",
|
||||||
"YY": "YY",
|
"YY": "YY",
|
||||||
"yy": "YY"
|
"yy": "YY",
|
||||||
}
|
"contact_name_exists": "해당 이름을 가진 연락처가 이미 존재합니다. 다른 이름을 선택하세요."
|
||||||
|
}
|
||||||
|
|
|
@ -937,5 +937,6 @@
|
||||||
"you_pay": "သင်ပေးချေပါ။",
|
"you_pay": "သင်ပေးချေပါ။",
|
||||||
"you_will_get": "သို့ပြောင်းပါ။",
|
"you_will_get": "သို့ပြောင်းပါ။",
|
||||||
"you_will_send": "မှပြောင်းပါ။",
|
"you_will_send": "မှပြောင်းပါ။",
|
||||||
"yy": "YY"
|
"yy": "YY",
|
||||||
}
|
"contact_name_exists": "ထိုအမည်နှင့် အဆက်အသွယ်တစ်ခု ရှိနှင့်ပြီးဖြစ်သည်။ အခြားအမည်တစ်ခုကို ရွေးပါ။"
|
||||||
|
}
|
||||||
|
|
|
@ -938,5 +938,6 @@
|
||||||
"you_pay": "U betaalt",
|
"you_pay": "U betaalt",
|
||||||
"you_will_get": "Converteren naar",
|
"you_will_get": "Converteren naar",
|
||||||
"you_will_send": "Converteren van",
|
"you_will_send": "Converteren van",
|
||||||
"yy": "JJ"
|
"yy": "JJ",
|
||||||
}
|
"contact_name_exists": "Er bestaat al een contact met die naam. Kies een andere naam."
|
||||||
|
}
|
||||||
|
|
|
@ -937,5 +937,6 @@
|
||||||
"you_pay": "Płacisz",
|
"you_pay": "Płacisz",
|
||||||
"you_will_get": "Konwertuj na",
|
"you_will_get": "Konwertuj na",
|
||||||
"you_will_send": "Konwertuj z",
|
"you_will_send": "Konwertuj z",
|
||||||
"yy": "RR"
|
"yy": "RR",
|
||||||
}
|
"contact_name_exists": "Kontakt o tej nazwie już istnieje. Proszę wybrać inną nazwę."
|
||||||
|
}
|
||||||
|
|
|
@ -941,4 +941,4 @@
|
||||||
"you_will_get": "Converter para",
|
"you_will_get": "Converter para",
|
||||||
"you_will_send": "Converter de",
|
"you_will_send": "Converter de",
|
||||||
"yy": "aa"
|
"yy": "aa"
|
||||||
}
|
}
|
||||||
|
|
|
@ -938,5 +938,6 @@
|
||||||
"you_pay": "Вы платите",
|
"you_pay": "Вы платите",
|
||||||
"you_will_get": "Конвертировать в",
|
"you_will_get": "Конвертировать в",
|
||||||
"you_will_send": "Конвертировать из",
|
"you_will_send": "Конвертировать из",
|
||||||
"yy": "ГГ"
|
"yy": "ГГ",
|
||||||
}
|
"contact_name_exists": "Контакт с таким именем уже существует. Пожалуйста, выберите другое имя."
|
||||||
|
}
|
||||||
|
|
|
@ -937,5 +937,6 @@
|
||||||
"you_pay": "คุณจ่าย",
|
"you_pay": "คุณจ่าย",
|
||||||
"you_will_get": "แปลงเป็น",
|
"you_will_get": "แปลงเป็น",
|
||||||
"you_will_send": "แปลงจาก",
|
"you_will_send": "แปลงจาก",
|
||||||
"yy": "ปี"
|
"yy": "ปี",
|
||||||
}
|
"contact_name_exists": "มีผู้ติดต่อชื่อนั้นอยู่แล้ว โปรดเลือกชื่ออื่น"
|
||||||
|
}
|
||||||
|
|
|
@ -938,4 +938,4 @@
|
||||||
"you_will_get": "I-convert sa",
|
"you_will_get": "I-convert sa",
|
||||||
"you_will_send": "I-convert mula sa",
|
"you_will_send": "I-convert mula sa",
|
||||||
"yy": "YY"
|
"yy": "YY"
|
||||||
}
|
}
|
||||||
|
|
|
@ -937,5 +937,6 @@
|
||||||
"you_pay": "Şu kadar ödeyeceksin: ",
|
"you_pay": "Şu kadar ödeyeceksin: ",
|
||||||
"you_will_get": "Biçimine dönüştür:",
|
"you_will_get": "Biçimine dönüştür:",
|
||||||
"you_will_send": "Biçiminden dönüştür:",
|
"you_will_send": "Biçiminden dönüştür:",
|
||||||
"yy": "YY"
|
"yy": "YY",
|
||||||
}
|
"contact_name_exists": "Bu isimde bir kişi zaten mevcut. Lütfen farklı bir ad seçin."
|
||||||
|
}
|
||||||
|
|
|
@ -938,5 +938,6 @@
|
||||||
"you_pay": "Ви платите",
|
"you_pay": "Ви платите",
|
||||||
"you_will_get": "Конвертувати в",
|
"you_will_get": "Конвертувати в",
|
||||||
"you_will_send": "Конвертувати з",
|
"you_will_send": "Конвертувати з",
|
||||||
"yy": "YY"
|
"yy": "YY",
|
||||||
}
|
"contact_name_exists": "Контакт із такою назвою вже існує. Виберіть інше ім'я."
|
||||||
|
}
|
||||||
|
|
|
@ -939,5 +939,6 @@
|
||||||
"you_pay": "تم ادا کرو",
|
"you_pay": "تم ادا کرو",
|
||||||
"you_will_get": "میں تبدیل کریں۔",
|
"you_will_get": "میں تبدیل کریں۔",
|
||||||
"you_will_send": "سے تبدیل کریں۔",
|
"you_will_send": "سے تبدیل کریں۔",
|
||||||
"yy": "YY"
|
"yy": "YY",
|
||||||
}
|
"contact_name_exists": " ۔ﮟﯾﺮﮐ ﺐﺨﺘﻨﻣ ﻡﺎﻧ ﻒﻠﺘﺨﻣ ﮏﯾﺍ ﻡﺮﮐ ﮦﺍﺮﺑ ۔ﮯﮨ ﺩﻮﺟﻮﻣ ﮯﺳ ﮯﻠﮩﭘ ﮧﻄﺑﺍﺭ ﮏﯾﺍ ﮫﺗﺎﺳ ﮯﮐ ﻡﺎﻧ ﺱﺍ"
|
||||||
|
}
|
||||||
|
|
|
@ -938,5 +938,6 @@
|
||||||
"you_pay": "Ẹ sàn",
|
"you_pay": "Ẹ sàn",
|
||||||
"you_will_get": "Ṣe pàṣípààrọ̀ sí",
|
"you_will_get": "Ṣe pàṣípààrọ̀ sí",
|
||||||
"you_will_send": "Ṣe pàṣípààrọ̀ láti",
|
"you_will_send": "Ṣe pàṣípààrọ̀ láti",
|
||||||
"yy": "Ọd"
|
"yy": "Ọd",
|
||||||
}
|
"contact_name_exists": "Olubasọrọ pẹlu orukọ yẹn ti wa tẹlẹ. Jọwọ yan orukọ ti o yatọ."
|
||||||
|
}
|
||||||
|
|
|
@ -937,5 +937,6 @@
|
||||||
"you_pay": "你付钱",
|
"you_pay": "你付钱",
|
||||||
"you_will_get": "转换到",
|
"you_will_get": "转换到",
|
||||||
"you_will_send": "转换自",
|
"you_will_send": "转换自",
|
||||||
"yy": "YY"
|
"yy": "YY",
|
||||||
}
|
"contact_name_exists": "已存在具有该名称的联系人。请选择不同的名称。"
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue