mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-11-16 17:27:37 +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)
|
||||
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) {
|
||||
raw = type.raw;
|
||||
}
|
||||
|
@ -25,6 +26,9 @@ class Contact extends HiveObject with Keyable {
|
|||
@HiveField(2, defaultValue: 0)
|
||||
late int raw;
|
||||
|
||||
@HiveField(3)
|
||||
DateTime lastChange;
|
||||
|
||||
CryptoCurrency get type => CryptoCurrency.deserialize(raw: raw);
|
||||
|
||||
@override
|
||||
|
@ -36,6 +40,5 @@ class Contact extends HiveObject with Keyable {
|
|||
@override
|
||||
int get hashCode => key.hashCode;
|
||||
|
||||
void updateCryptoCurrency({required CryptoCurrency currency}) =>
|
||||
raw = currency.raw;
|
||||
void updateCryptoCurrency({required CryptoCurrency currency}) => 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: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';
|
||||
|
||||
class ContactRecord = ContactRecordBase with _$ContactRecord;
|
||||
|
||||
abstract class ContactRecordBase extends Record<Contact>
|
||||
with Store
|
||||
implements ContactBase {
|
||||
abstract class ContactRecordBase extends Record<Contact> with Store implements ContactBase {
|
||||
ContactRecordBase(Box<Contact> source, Contact original)
|
||||
: name = original.name,
|
||||
address = original.address,
|
||||
type = original.type,
|
||||
super(source, original);
|
||||
lastChange = original.lastChange,
|
||||
super(source, original);
|
||||
|
||||
@override
|
||||
@observable
|
||||
|
@ -30,14 +29,14 @@ abstract class ContactRecordBase extends Record<Contact>
|
|||
@observable
|
||||
CryptoCurrency type;
|
||||
|
||||
DateTime? lastChange;
|
||||
|
||||
@override
|
||||
void toBind(Contact original) {
|
||||
reaction((_) => name, (String name) => original.name = name);
|
||||
reaction((_) => address, (String address) => original.address = address);
|
||||
reaction(
|
||||
(_) => type,
|
||||
(CryptoCurrency currency) =>
|
||||
original.updateCryptoCurrency(currency: currency));
|
||||
reaction((_) => type,
|
||||
(CryptoCurrency currency) => original.updateCryptoCurrency(currency: currency));
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -25,7 +25,9 @@ class PreferencesKey {
|
|||
static const disableBulletinKey = 'disable_bulletin';
|
||||
static const defaultBuyProvider = 'default_buy_provider';
|
||||
static const walletListOrder = 'wallet_list_order';
|
||||
static const contactListOrder = 'contact_list_order';
|
||||
static const walletListAscending = 'wallet_list_ascending';
|
||||
static const contactListAscending = 'contact_list_ascending';
|
||||
static const currentFiatApiModeKey = 'current_fiat_api_mode';
|
||||
static const failedTotpTokenTrials = 'failed_token_trials';
|
||||
static const disableExchangeKey = 'disable_exchange';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
enum WalletListOrderType {
|
||||
enum FilterListOrderType {
|
||||
CreationDate,
|
||||
Alphabetical,
|
||||
GroupByType,
|
||||
|
@ -9,13 +9,13 @@ enum WalletListOrderType {
|
|||
@override
|
||||
String toString() {
|
||||
switch (this) {
|
||||
case WalletListOrderType.CreationDate:
|
||||
case FilterListOrderType.CreationDate:
|
||||
return S.current.creation_date;
|
||||
case WalletListOrderType.Alphabetical:
|
||||
case FilterListOrderType.Alphabetical:
|
||||
return S.current.alphabetical;
|
||||
case WalletListOrderType.GroupByType:
|
||||
case FilterListOrderType.GroupByType:
|
||||
return S.current.group_by_type;
|
||||
case WalletListOrderType.Custom:
|
||||
case FilterListOrderType.Custom:
|
||||
return S.current.custom_drag;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,21 +1,24 @@
|
|||
import 'package:cake_wallet/core/auth_service.dart';
|
||||
import 'package:cake_wallet/entities/contact_base.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/exchange_page_theme.dart';
|
||||
import 'package:cake_wallet/utils/show_bar.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/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 {
|
||||
ContactListPage(this.contactListViewModel, this.authService);
|
||||
|
@ -74,45 +77,101 @@ class ContactListPage extends BasePage {
|
|||
}
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
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;
|
||||
Widget body(BuildContext context) => ContactPageBody(contactListViewModel: contactListViewModel);
|
||||
}
|
||||
|
||||
if (sectionIndex == 0) {
|
||||
title = S.current.contact_list_wallets;
|
||||
}
|
||||
class ContactPageBody extends StatefulWidget {
|
||||
const ContactPageBody({required this.contactListViewModel});
|
||||
|
||||
return Container(
|
||||
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 ContactListViewModel contactListViewModel;
|
||||
|
||||
final contact = contacts[index];
|
||||
final content = generateRaw(context, contact);
|
||||
return contactListViewModel.isEditable
|
||||
? Slidable(
|
||||
key: Key('${contact.key}'),
|
||||
endActionPane: _actionPane(context, contact),
|
||||
child: content,
|
||||
)
|
||||
: content;
|
||||
},
|
||||
);
|
||||
}));
|
||||
@override
|
||||
State<ContactPageBody> createState() => _ContactPageBodyState();
|
||||
}
|
||||
|
||||
class _ContactPageBodyState extends State<ContactPageBody> with SingleTickerProviderStateMixin {
|
||||
late TabController _tabController;
|
||||
|
||||
@override
|
||||
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) {
|
||||
|
@ -123,7 +182,7 @@ class ContactListPage extends BasePage {
|
|||
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
if (!contactListViewModel.isEditable) {
|
||||
if (!widget.contactListViewModel.isEditable) {
|
||||
Navigator.of(context).pop(contact);
|
||||
return;
|
||||
}
|
||||
|
@ -143,8 +202,7 @@ class ContactListPage extends BasePage {
|
|||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
currencyIcon,
|
||||
Expanded(
|
||||
child: Padding(
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 12),
|
||||
child: Text(
|
||||
contact.name,
|
||||
|
@ -154,13 +212,215 @@ class ContactListPage extends BasePage {
|
|||
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 {
|
||||
return await showPopUp<bool>(
|
||||
context: context,
|
||||
|
@ -190,32 +450,4 @@ class ContactListPage extends BasePage {
|
|||
}) ??
|
||||
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,
|
||||
});
|
||||
|
||||
final WalletListOrderType? initalType;
|
||||
final FilterListOrderType? initalType;
|
||||
final bool initalAscending;
|
||||
final Function(bool, WalletListOrderType) onClose;
|
||||
final Function(bool, FilterListOrderType) onClose;
|
||||
|
||||
@override
|
||||
FilterListWidgetState createState() => FilterListWidgetState();
|
||||
|
@ -28,7 +28,7 @@ class FilterListWidget extends StatefulWidget {
|
|||
|
||||
class FilterListWidgetState extends State<FilterListWidget> {
|
||||
late bool ascending;
|
||||
late WalletListOrderType? type;
|
||||
late FilterListOrderType? type;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -37,7 +37,7 @@ class FilterListWidgetState extends State<FilterListWidget> {
|
|||
type = widget.initalType;
|
||||
}
|
||||
|
||||
void setSelectedOrderType(WalletListOrderType? orderType) {
|
||||
void setSelectedOrderType(FilterListOrderType? orderType) {
|
||||
setState(() {
|
||||
type = orderType;
|
||||
});
|
||||
|
@ -72,7 +72,7 @@ class FilterListWidgetState extends State<FilterListWidget> {
|
|||
),
|
||||
),
|
||||
),
|
||||
if (type != WalletListOrderType.Custom) ...[
|
||||
if (type != FilterListOrderType.Custom) ...[
|
||||
sectionDivider,
|
||||
SettingsChoicesCell(
|
||||
ChoicesListItem<ListOrderMode>(
|
||||
|
@ -89,10 +89,10 @@ class FilterListWidgetState extends State<FilterListWidget> {
|
|||
],
|
||||
sectionDivider,
|
||||
RadioListTile(
|
||||
value: WalletListOrderType.CreationDate,
|
||||
value: FilterListOrderType.CreationDate,
|
||||
groupValue: type,
|
||||
title: Text(
|
||||
WalletListOrderType.CreationDate.toString(),
|
||||
FilterListOrderType.CreationDate.toString(),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
fontSize: 16,
|
||||
|
@ -104,10 +104,10 @@ class FilterListWidgetState extends State<FilterListWidget> {
|
|||
activeColor: Theme.of(context).primaryColor,
|
||||
),
|
||||
RadioListTile(
|
||||
value: WalletListOrderType.Alphabetical,
|
||||
value: FilterListOrderType.Alphabetical,
|
||||
groupValue: type,
|
||||
title: Text(
|
||||
WalletListOrderType.Alphabetical.toString(),
|
||||
FilterListOrderType.Alphabetical.toString(),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
fontSize: 16,
|
||||
|
@ -119,10 +119,10 @@ class FilterListWidgetState extends State<FilterListWidget> {
|
|||
activeColor: Theme.of(context).primaryColor,
|
||||
),
|
||||
RadioListTile(
|
||||
value: WalletListOrderType.GroupByType,
|
||||
value: FilterListOrderType.GroupByType,
|
||||
groupValue: type,
|
||||
title: Text(
|
||||
WalletListOrderType.GroupByType.toString(),
|
||||
FilterListOrderType.GroupByType.toString(),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
fontSize: 16,
|
||||
|
@ -134,10 +134,10 @@ class FilterListWidgetState extends State<FilterListWidget> {
|
|||
activeColor: Theme.of(context).primaryColor,
|
||||
),
|
||||
RadioListTile(
|
||||
value: WalletListOrderType.Custom,
|
||||
value: FilterListOrderType.Custom,
|
||||
groupValue: type,
|
||||
title: Text(
|
||||
WalletListOrderType.Custom.toString(),
|
||||
FilterListOrderType.Custom.toString(),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
fontSize: 16,
|
||||
|
|
|
@ -7,13 +7,17 @@ class FilteredList extends StatefulWidget {
|
|||
required this.list,
|
||||
required this.itemBuilder,
|
||||
required this.updateFunction,
|
||||
this.canReorder = true,
|
||||
this.shrinkWrap = false,
|
||||
this.physics,
|
||||
});
|
||||
|
||||
final ObservableList<dynamic> list;
|
||||
final Widget Function(BuildContext, int) itemBuilder;
|
||||
final Function updateFunction;
|
||||
final bool canReorder;
|
||||
final bool shrinkWrap;
|
||||
final ScrollPhysics? physics;
|
||||
|
||||
@override
|
||||
FilteredListState createState() => FilteredListState();
|
||||
|
@ -22,21 +26,31 @@ class FilteredList extends StatefulWidget {
|
|||
class FilteredListState extends State<FilteredList> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Observer(
|
||||
builder: (_) => ReorderableListView.builder(
|
||||
shrinkWrap: widget.shrinkWrap,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
itemBuilder: widget.itemBuilder,
|
||||
itemCount: widget.list.length,
|
||||
onReorder: (int oldIndex, int newIndex) {
|
||||
if (oldIndex < newIndex) {
|
||||
newIndex -= 1;
|
||||
}
|
||||
final dynamic item = widget.list.removeAt(oldIndex);
|
||||
widget.list.insert(newIndex, item);
|
||||
widget.updateFunction();
|
||||
},
|
||||
),
|
||||
);
|
||||
if (widget.canReorder) {
|
||||
return Observer(
|
||||
builder: (_) => ReorderableListView.builder(
|
||||
shrinkWrap: widget.shrinkWrap,
|
||||
physics: widget.physics ?? const BouncingScrollPhysics(),
|
||||
itemBuilder: widget.itemBuilder,
|
||||
itemCount: widget.list.length,
|
||||
onReorder: (int oldIndex, int newIndex) {
|
||||
if (oldIndex < newIndex) {
|
||||
newIndex -= 1;
|
||||
}
|
||||
final dynamic item = widget.list.removeAt(oldIndex);
|
||||
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(
|
||||
initalType: walletListViewModel.orderType,
|
||||
initalAscending: walletListViewModel.ascending,
|
||||
onClose: (bool ascending, WalletListOrderType type) async {
|
||||
onClose: (bool ascending, FilterListOrderType type) async {
|
||||
walletListViewModel.setAscending(ascending);
|
||||
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 initialDisableBuy,
|
||||
required bool initialDisableSell,
|
||||
required FilterListOrderType initialWalletListOrder,
|
||||
required FilterListOrderType initialContactListOrder,
|
||||
required bool initialDisableBulletin,
|
||||
required WalletListOrderType initialWalletListOrder,
|
||||
required bool initialWalletListAscending,
|
||||
required bool initialContactListAscending,
|
||||
required FiatApiMode initialFiatMode,
|
||||
required bool initialAllowBiometricalAuthentication,
|
||||
required String initialTotpSecretKey,
|
||||
|
@ -149,7 +151,9 @@ abstract class SettingsStoreBase with Store {
|
|||
disableSell = initialDisableSell,
|
||||
disableBulletin = initialDisableBulletin,
|
||||
walletListOrder = initialWalletListOrder,
|
||||
contactListOrder = initialContactListOrder,
|
||||
walletListAscending = initialWalletListAscending,
|
||||
contactListAscending = initialContactListAscending,
|
||||
shouldShowMarketPlaceInDashboard = initialShouldShowMarketPlaceInDashboard,
|
||||
exchangeStatus = initialExchangeStatus,
|
||||
currentTheme = initialTheme,
|
||||
|
@ -324,14 +328,24 @@ abstract class SettingsStoreBase with Store {
|
|||
|
||||
reaction(
|
||||
(_) => walletListOrder,
|
||||
(WalletListOrderType walletListOrder) =>
|
||||
(FilterListOrderType walletListOrder) =>
|
||||
sharedPreferences.setInt(PreferencesKey.walletListOrder, walletListOrder.index));
|
||||
|
||||
reaction(
|
||||
(_) => contactListOrder,
|
||||
(FilterListOrderType contactListOrder) =>
|
||||
sharedPreferences.setInt(PreferencesKey.contactListOrder, contactListOrder.index));
|
||||
|
||||
reaction(
|
||||
(_) => walletListAscending,
|
||||
(bool walletListAscending) =>
|
||||
sharedPreferences.setBool(PreferencesKey.walletListAscending, walletListAscending));
|
||||
|
||||
reaction(
|
||||
(_) => contactListAscending,
|
||||
(bool contactListAscending) =>
|
||||
sharedPreferences.setBool(PreferencesKey.contactListAscending, contactListAscending));
|
||||
|
||||
reaction(
|
||||
(_) => autoGenerateSubaddressStatus,
|
||||
(AutoGenerateSubaddressStatus autoGenerateSubaddressStatus) => sharedPreferences.setInt(
|
||||
|
@ -645,15 +659,21 @@ abstract class SettingsStoreBase with Store {
|
|||
@observable
|
||||
bool disableSell;
|
||||
|
||||
@observable
|
||||
FilterListOrderType contactListOrder;
|
||||
|
||||
@observable
|
||||
bool disableBulletin;
|
||||
|
||||
@observable
|
||||
WalletListOrderType walletListOrder;
|
||||
FilterListOrderType walletListOrder;
|
||||
|
||||
@observable
|
||||
bool walletListAscending;
|
||||
|
||||
@observable
|
||||
bool contactListAscending;
|
||||
|
||||
@observable
|
||||
bool allowBiometricalAuthentication;
|
||||
|
||||
|
@ -907,9 +927,13 @@ abstract class SettingsStoreBase with Store {
|
|||
final disableSell = sharedPreferences.getBool(PreferencesKey.disableSellKey) ?? false;
|
||||
final disableBulletin = sharedPreferences.getBool(PreferencesKey.disableBulletinKey) ?? false;
|
||||
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 =
|
||||
sharedPreferences.getBool(PreferencesKey.walletListAscending) ?? true;
|
||||
final contactListAscending =
|
||||
sharedPreferences.getBool(PreferencesKey.contactListAscending) ?? true;
|
||||
final currentFiatApiMode = FiatApiMode.deserialize(
|
||||
raw: sharedPreferences.getInt(PreferencesKey.currentFiatApiModeKey) ??
|
||||
FiatApiMode.enabled.raw);
|
||||
|
@ -1200,6 +1224,8 @@ abstract class SettingsStoreBase with Store {
|
|||
initialDisableBulletin: disableBulletin,
|
||||
initialWalletListOrder: walletListOrder,
|
||||
initialWalletListAscending: walletListAscending,
|
||||
initialContactListOrder: contactListOrder,
|
||||
initialContactListAscending: contactListAscending,
|
||||
initialFiatMode: currentFiatApiMode,
|
||||
initialAllowBiometricalAuthentication: allowBiometricalAuthentication,
|
||||
initialCake2FAPresetOptions: selectedCake2FAPreset,
|
||||
|
@ -1348,9 +1374,11 @@ abstract class SettingsStoreBase with Store {
|
|||
disableBulletin =
|
||||
sharedPreferences.getBool(PreferencesKey.disableBulletinKey) ?? disableBulletin;
|
||||
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;
|
||||
|
||||
contactListAscending = sharedPreferences.getBool(PreferencesKey.contactListAscending) ?? true;
|
||||
shouldShowMarketPlaceInDashboard =
|
||||
sharedPreferences.getBool(PreferencesKey.shouldShowMarketPlaceInDashboard) ??
|
||||
shouldShowMarketPlaceInDashboard;
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
import 'dart:async';
|
||||
|
||||
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_record.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/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_type.dart';
|
||||
import 'package:hive/hive.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';
|
||||
|
||||
|
@ -75,6 +77,8 @@ abstract class ContactListViewModelBase with Store {
|
|||
_subscription = contactSource.bindToListWithTransform(
|
||||
contacts, (Contact contact) => ContactRecord(contactSource, contact),
|
||||
initialFire: true);
|
||||
|
||||
setOrderType(settingsStore.contactListOrder);
|
||||
}
|
||||
|
||||
String _createName(String walletName, String label, {int? key = null}) {
|
||||
|
@ -93,6 +97,10 @@ abstract class ContactListViewModelBase with Store {
|
|||
|
||||
bool get isEditable => _currency == null;
|
||||
|
||||
FilterListOrderType? get orderType => settingsStore.contactListOrder;
|
||||
|
||||
bool get ascending => settingsStore.contactListAscending;
|
||||
|
||||
@computed
|
||||
bool get shouldRequireTOTP2FAForAddingContacts =>
|
||||
settingsStore.shouldRequireTOTP2FAForAddingContacts;
|
||||
|
@ -118,4 +126,70 @@ abstract class ContactListViewModelBase with Store {
|
|||
_currency?.toString() == element.type.tag ||
|
||||
_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:mobx/mobx.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:cw_core/crypto_currency.dart';
|
||||
|
||||
|
@ -17,7 +17,9 @@ abstract class ContactViewModelBase with Store {
|
|||
_contact = contact,
|
||||
name = contact?.name ?? '',
|
||||
address = contact?.address ?? '',
|
||||
currency = contact?.type;
|
||||
currency = contact?.type,
|
||||
lastChange = contact?.lastChange;
|
||||
|
||||
|
||||
@observable
|
||||
ExecutionState state;
|
||||
|
@ -31,6 +33,8 @@ abstract class ContactViewModelBase with Store {
|
|||
@observable
|
||||
CryptoCurrency? currency;
|
||||
|
||||
DateTime? lastChange;
|
||||
|
||||
@computed
|
||||
bool get isReady =>
|
||||
name.isNotEmpty &&
|
||||
|
@ -51,20 +55,32 @@ abstract class ContactViewModelBase with Store {
|
|||
Future<void> save() async {
|
||||
try {
|
||||
state = IsExecutingState();
|
||||
final now = DateTime.now();
|
||||
|
||||
if (doesContactNameExist(name)) {
|
||||
state = FailureState(S.current.contact_name_exists);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_contact != null && _contact!.original.isInBox) {
|
||||
_contact?.name = name;
|
||||
_contact?.address = address;
|
||||
_contact?.type = currency!;
|
||||
_contact?.lastChange = now;
|
||||
await _contact?.save();
|
||||
} else {
|
||||
await _contacts
|
||||
.add(Contact(name: name, address: address, type: currency!));
|
||||
.add(Contact(name: name, address: address, type: currency!, lastChange: now));
|
||||
}
|
||||
|
||||
lastChange = now;
|
||||
state = ExecutedSuccessfullyState();
|
||||
} catch (e) {
|
||||
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);
|
||||
}
|
||||
|
||||
WalletListOrderType? get orderType => _appStore.settingsStore.walletListOrder;
|
||||
FilterListOrderType? get orderType => _appStore.settingsStore.walletListOrder;
|
||||
|
||||
bool get ascending => _appStore.settingsStore.walletListAscending;
|
||||
|
||||
|
@ -108,7 +108,7 @@ abstract class WalletListViewModelBase with Store {
|
|||
return;
|
||||
}
|
||||
|
||||
_appStore.settingsStore.walletListOrder = WalletListOrderType.Custom;
|
||||
_appStore.settingsStore.walletListOrder = FilterListOrderType.Custom;
|
||||
|
||||
// make a copy of the walletInfoSource:
|
||||
List<WalletInfo> walletInfoSourceCopy = _walletInfoSource.values.toList();
|
||||
|
@ -186,22 +186,22 @@ abstract class WalletListViewModelBase with Store {
|
|||
_appStore.settingsStore.walletListAscending = ascending;
|
||||
}
|
||||
|
||||
Future<void> setOrderType(WalletListOrderType? type) async {
|
||||
Future<void> setOrderType(FilterListOrderType? type) async {
|
||||
if (type == null) return;
|
||||
|
||||
_appStore.settingsStore.walletListOrder = type;
|
||||
|
||||
switch (type) {
|
||||
case WalletListOrderType.CreationDate:
|
||||
case FilterListOrderType.CreationDate:
|
||||
await sortByCreationDate();
|
||||
break;
|
||||
case WalletListOrderType.Alphabetical:
|
||||
case FilterListOrderType.Alphabetical:
|
||||
await sortAlphabetically();
|
||||
break;
|
||||
case WalletListOrderType.GroupByType:
|
||||
case FilterListOrderType.GroupByType:
|
||||
await sortGroupByType();
|
||||
break;
|
||||
case WalletListOrderType.Custom:
|
||||
case FilterListOrderType.Custom:
|
||||
default:
|
||||
await reorderAccordingToWalletList();
|
||||
break;
|
||||
|
|
|
@ -937,5 +937,6 @@
|
|||
"you_pay": "انت تدفع",
|
||||
"you_will_get": "حول الى",
|
||||
"you_will_send": "تحويل من",
|
||||
"yy": "YY"
|
||||
}
|
||||
"yy": "YY",
|
||||
"contact_name_exists": " .ﻒﻠﺘﺨﻣ ﻢﺳﺍ ﺭﺎﻴﺘﺧﺍ ءﺎﺟﺮﻟﺍ .ﻞﻌﻔﻟﺎﺑ ﺓﺩﻮﺟﻮﻣ ﻢﺳﻻﺍ ﺍﺬﻬﺑ ﻝﺎﺼﺗﺍ ﺔﻬﺟ"
|
||||
}
|
||||
|
|
|
@ -937,5 +937,6 @@
|
|||
"you_pay": "Вие плащате",
|
||||
"you_will_get": "Обръщане в",
|
||||
"you_will_send": "Обръщане от",
|
||||
"yy": "гг"
|
||||
}
|
||||
"yy": "гг",
|
||||
"contact_name_exists": "Вече съществува контакт с това име. Моля, изберете друго име."
|
||||
}
|
||||
|
|
|
@ -937,5 +937,6 @@
|
|||
"you_pay": "Zaplatíte",
|
||||
"you_will_get": "Směnit na",
|
||||
"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_will_get": "Convert to",
|
||||
"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_will_get": "Convertir a",
|
||||
"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_will_get": "Convertir vers",
|
||||
"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_will_get": "Maida zuwa",
|
||||
"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_will_get": "में बदलें",
|
||||
"you_will_send": "से रूपांतरित करें",
|
||||
"yy": "वाईवाई"
|
||||
}
|
||||
"yy": "वाईवाई",
|
||||
"contact_name_exists": "उस नाम का एक संपर्क पहले से मौजूद है. कृपया कोई भिन्न नाम चुनें."
|
||||
}
|
||||
|
|
|
@ -937,5 +937,6 @@
|
|||
"you_pay": "Vi plaćate",
|
||||
"you_will_get": "Razmijeni u",
|
||||
"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_will_get": "Konversi ke",
|
||||
"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_will_get": "Converti a",
|
||||
"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_will_get": "に変換",
|
||||
"you_will_send": "から変換",
|
||||
"yy": "YY"
|
||||
}
|
||||
"yy": "YY",
|
||||
"contact_name_exists": "その名前の連絡先はすでに存在します。別の名前を選択してください。"
|
||||
}
|
||||
|
|
|
@ -939,5 +939,6 @@
|
|||
"you_will_get": "로 변환하다",
|
||||
"you_will_send": "다음에서 변환",
|
||||
"YY": "YY",
|
||||
"yy": "YY"
|
||||
}
|
||||
"yy": "YY",
|
||||
"contact_name_exists": "해당 이름을 가진 연락처가 이미 존재합니다. 다른 이름을 선택하세요."
|
||||
}
|
||||
|
|
|
@ -937,5 +937,6 @@
|
|||
"you_pay": "သင်ပေးချေပါ။",
|
||||
"you_will_get": "သို့ပြောင်းပါ။",
|
||||
"you_will_send": "မှပြောင်းပါ။",
|
||||
"yy": "YY"
|
||||
}
|
||||
"yy": "YY",
|
||||
"contact_name_exists": "ထိုအမည်နှင့် အဆက်အသွယ်တစ်ခု ရှိနှင့်ပြီးဖြစ်သည်။ အခြားအမည်တစ်ခုကို ရွေးပါ။"
|
||||
}
|
||||
|
|
|
@ -938,5 +938,6 @@
|
|||
"you_pay": "U betaalt",
|
||||
"you_will_get": "Converteren naar",
|
||||
"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_will_get": "Konwertuj na",
|
||||
"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_send": "Converter de",
|
||||
"yy": "aa"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -938,5 +938,6 @@
|
|||
"you_pay": "Вы платите",
|
||||
"you_will_get": "Конвертировать в",
|
||||
"you_will_send": "Конвертировать из",
|
||||
"yy": "ГГ"
|
||||
}
|
||||
"yy": "ГГ",
|
||||
"contact_name_exists": "Контакт с таким именем уже существует. Пожалуйста, выберите другое имя."
|
||||
}
|
||||
|
|
|
@ -937,5 +937,6 @@
|
|||
"you_pay": "คุณจ่าย",
|
||||
"you_will_get": "แปลงเป็น",
|
||||
"you_will_send": "แปลงจาก",
|
||||
"yy": "ปี"
|
||||
}
|
||||
"yy": "ปี",
|
||||
"contact_name_exists": "มีผู้ติดต่อชื่อนั้นอยู่แล้ว โปรดเลือกชื่ออื่น"
|
||||
}
|
||||
|
|
|
@ -938,4 +938,4 @@
|
|||
"you_will_get": "I-convert sa",
|
||||
"you_will_send": "I-convert mula sa",
|
||||
"yy": "YY"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -937,5 +937,6 @@
|
|||
"you_pay": "Şu kadar ödeyeceksin: ",
|
||||
"you_will_get": "Biçimine 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_will_get": "Конвертувати в",
|
||||
"you_will_send": "Конвертувати з",
|
||||
"yy": "YY"
|
||||
}
|
||||
"yy": "YY",
|
||||
"contact_name_exists": "Контакт із такою назвою вже існує. Виберіть інше ім'я."
|
||||
}
|
||||
|
|
|
@ -939,5 +939,6 @@
|
|||
"you_pay": "تم ادا کرو",
|
||||
"you_will_get": "میں تبدیل کریں۔",
|
||||
"you_will_send": "سے تبدیل کریں۔",
|
||||
"yy": "YY"
|
||||
}
|
||||
"yy": "YY",
|
||||
"contact_name_exists": " ۔ﮟﯾﺮﮐ ﺐﺨﺘﻨﻣ ﻡﺎﻧ ﻒﻠﺘﺨﻣ ﮏﯾﺍ ﻡﺮﮐ ﮦﺍﺮﺑ ۔ﮯﮨ ﺩﻮﺟﻮﻣ ﮯﺳ ﮯﻠﮩﭘ ﮧﻄﺑﺍﺭ ﮏﯾﺍ ﮫﺗﺎﺳ ﮯﮐ ﻡﺎﻧ ﺱﺍ"
|
||||
}
|
||||
|
|
|
@ -938,5 +938,6 @@
|
|||
"you_pay": "Ẹ sàn",
|
||||
"you_will_get": "Ṣe pàṣípààrọ̀ sí",
|
||||
"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_will_get": "转换到",
|
||||
"you_will_send": "转换自",
|
||||
"yy": "YY"
|
||||
}
|
||||
"yy": "YY",
|
||||
"contact_name_exists": "已存在具有该名称的联系人。请选择不同的名称。"
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue