Skip to content

Commit

Permalink
Add Address Book
Browse files Browse the repository at this point in the history
  • Loading branch information
konstantinullrich committed May 1, 2024
1 parent 45e8c6d commit 821f3a9
Show file tree
Hide file tree
Showing 15 changed files with 480 additions and 15 deletions.
6 changes: 3 additions & 3 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:frankencoin_wallet/generated/i18n.dart';
import 'package:frankencoin_wallet/src/colors.dart';
import 'package:frankencoin_wallet/src/core/default_nodes.dart';
import 'package:frankencoin_wallet/src/di.dart';
import 'package:frankencoin_wallet/src/entites/address_book_entry.dart';
import 'package:frankencoin_wallet/src/entites/balance_info.dart';
import 'package:frankencoin_wallet/src/entites/node.dart';
import 'package:frankencoin_wallet/src/screens/router.dart';
Expand All @@ -20,7 +21,8 @@ Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();

final dir = await getApplicationSupportDirectory();
final isar = await Isar.open([BalanceInfoSchema, NodeSchema],
final isar = await Isar.open(
[AddressBookEntrySchema, BalanceInfoSchema, NodeSchema],
directory: dir.path, inspector: false);
final sharedPreferences = await SharedPreferences.getInstance();

Expand Down Expand Up @@ -57,8 +59,6 @@ class FankencoinApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {


return Observer(builder: (_) {
final language = getIt.get<SettingsStore>().language;

Expand Down
5 changes: 5 additions & 0 deletions lib/src/di.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:frankencoin_wallet/src/core/dfx_service.dart';
import 'package:frankencoin_wallet/src/core/frankencoin_pay/frankencoin_pay_service.dart';
import 'package:frankencoin_wallet/src/core/walletconnect_service.dart';
import 'package:frankencoin_wallet/src/stores/address_book_store.dart';
import 'package:frankencoin_wallet/src/stores/app_store.dart';
import 'package:frankencoin_wallet/src/stores/frankencoin_pay_store.dart';
import 'package:frankencoin_wallet/src/stores/settings_store.dart';
import 'package:frankencoin_wallet/src/view_model/address_book_view_model.dart';
import 'package:frankencoin_wallet/src/view_model/balance_view_model.dart';
import 'package:frankencoin_wallet/src/view_model/equity_view_model.dart';
import 'package:frankencoin_wallet/src/view_model/fps_asset_view_model.dart';
Expand All @@ -28,6 +30,7 @@ void setupDependencyInjection(
getIt.registerSingleton<BottomSheetService>(BottomSheetServiceImpl());
getIt.registerSingleton(AppStore(getIt.get<SettingsStore>()));
getIt.registerSingleton(FrankencoinPayStore(sharedPreferences));
getIt.registerSingleton(AddressBookStore(getIt.get<Isar>()));
getIt.registerSingleton(DFXService(getIt.get<AppStore>()));

getIt.registerSingleton(WalletConnectWalletService(
Expand All @@ -45,4 +48,6 @@ void setupDependencyInjection(
() => EquityViewModel(getIt.get<AppStore>(), getIt.get<SendViewModel>()));
getIt.registerFactory<FPSAssetViewModel>(() =>
FPSAssetViewModel(getIt.get<AppStore>(), getIt.get<BalanceViewModel>()));
getIt.registerFactory<AddressBookViewModel>(
() => AddressBookViewModel(getIt.get<AddressBookStore>()));
}
21 changes: 21 additions & 0 deletions lib/src/entites/address_book_entry.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import 'package:frankencoin_wallet/src/utils/fast_hash.dart';
import 'package:isar/isar.dart';

part 'address_book_entry.g.dart';

@collection
class AddressBookEntry {
AddressBookEntry({
this.chainId,
required this.address,
required this.name,
});

Id get id => fastHash("$address${chainId != null ? "@$chainId" : ""}");

int? chainId;

String address;

String name;
}
178 changes: 178 additions & 0 deletions lib/src/screens/address_book/add_contact_page.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:frankencoin_wallet/generated/i18n.dart';
import 'package:frankencoin_wallet/src/colors.dart';
import 'package:frankencoin_wallet/src/core/bottom_sheet_service.dart';
import 'package:frankencoin_wallet/src/di.dart';
import 'package:frankencoin_wallet/src/entites/address_book_entry.dart';
import 'package:frankencoin_wallet/src/screens/base_page.dart';
import 'package:frankencoin_wallet/src/utils/device_info.dart';
import 'package:frankencoin_wallet/src/view_model/address_book_view_model.dart';
import 'package:frankencoin_wallet/src/wallet/payment_uri.dart';
import 'package:frankencoin_wallet/src/widgets/qr_scan_dialog.dart';
import 'package:frankencoin_wallet/src/widgets/wallet_connect/bottom_sheet_message_display.dart';

class AddContactPage extends BasePage {
final AddressBookViewModel addressBookVM;
final String? initialName;
final String? initialAddress;

AddContactPage(this.addressBookVM,
{this.initialName, this.initialAddress, super.key});

@override
String get title => S.current.add_contact;

@override
Widget body(BuildContext context) => _AddContactPageBody(
addressBookVM: addressBookVM,
initialName: initialName,
initialAddress: initialAddress,
);
}

class _AddContactPageBody extends StatefulWidget {
final AddressBookViewModel addressBookVM;
final String? initialName;
final String? initialAddress;

const _AddContactPageBody({
required this.addressBookVM,
this.initialName,
this.initialAddress,
});

@override
State<StatefulWidget> createState() => _AddContactPageBodyState();
}

class _AddContactPageBodyState extends State<_AddContactPageBody> {
final TextEditingController _nameController = TextEditingController();
final TextEditingController _addressController = TextEditingController();

final _bottomSheetService = getIt.get<BottomSheetService>();

bool _isLoading = false;

@override
void initState() {
super.initState();

_nameController.text = widget.initialName ?? "";
_addressController.text = widget.initialAddress ?? "";
}

@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
children: [
Padding(
padding:
const EdgeInsets.only(left: 26, right: 26, top: 10, bottom: 10),
child: CupertinoTextField(
controller: _nameController,
placeholder: S.of(context).name,
suffix: const SizedBox(height: 52),
),
),
Padding(
padding:
const EdgeInsets.only(left: 26, right: 26, top: 10, bottom: 10),
child: CupertinoTextField(
controller: _addressController,
placeholder: S.of(context).address,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.deny(RegExp(r" ")),
],
suffix: SizedBox(
height: 52,
child: Padding(
padding: const EdgeInsets.only(top: 2, bottom: 2),
child: Row(
children: [
IconButton(
onPressed: _pasteText,
icon: const Icon(Icons.paste),
),
if (DeviceInfo.instance.isMobile)
IconButton(
onPressed: () => _presentQRScanner(context),
icon: const Icon(Icons.qr_code),
),
],
),
),
),
),
),
Padding(
padding: const EdgeInsets.only(top: 20, bottom: 20),
child: CupertinoButton(
onPressed: _isLoading ? null : _save,
color: FrankencoinColors.frRed,
child: _isLoading
? const CupertinoActivityIndicator()
: Text(
S.of(context).save,
style: const TextStyle(fontSize: 16),
),
),
),
],
));
}

Future<void> _save() async {
if (_isLoading) return;

setState(() => _isLoading = true);

final address = _addressController.text.trim();

if (!RegExp(r'^(0x)?[0-9a-f]{40}$', caseSensitive: false)
.hasMatch(address)) {
setState(() => _isLoading = false);
_bottomSheetService.queueBottomSheet(
isModalDismissible: true,
widget:
BottomSheetMessageDisplayWidget(message: S.current.invalid_address),
);
return;
}

await widget.addressBookVM.saveEntry(
AddressBookEntry(address: address, name: _nameController.text.trim()));
setState(() => _isLoading = false);

Navigator.of(context).pop();
}

Future<void> _pasteText() async {
final value = await Clipboard.getData('text/plain');

if (value?.text?.isNotEmpty ?? false) {
_addressController.text = value!.text!;
}
}

Future<void> _presentQRScanner(BuildContext context) async {
String address = await showDialog(
context: context,
builder: (dialogContext) => QRScanDialog(
validateQR: (code, _) =>
RegExp(r'(\b0x[a-fA-F0-9]{40}\b)').hasMatch(code!),
onData: (code, _) =>
Navigator.of(dialogContext, rootNavigator: true).pop(code),
),
);

if (address.startsWith("0x")) {
_addressController.text = address;
} else {
final uri = ERC681URI.fromString(address);
_addressController.text = uri.address;
}
}
}
67 changes: 67 additions & 0 deletions lib/src/screens/address_book/address_book_page.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:frankencoin_wallet/generated/i18n.dart';
import 'package:frankencoin_wallet/src/screens/address_book/widgets/address_book_entry_card.dart';
import 'package:frankencoin_wallet/src/screens/base_page.dart';
import 'package:frankencoin_wallet/src/screens/routes.dart';
import 'package:frankencoin_wallet/src/view_model/address_book_view_model.dart';

class AddressBookPage extends BasePage {
final AddressBookViewModel addressBookVM;
final bool isSelector;

AddressBookPage(this.addressBookVM, {super.key, this.isSelector = false}) {
addressBookVM.loadEntries();
}

@override
String get title => S.current.contacts;

@override
Widget? trailing(BuildContext context) => MergeSemantics(
child: SizedBox(
height: 37,
width: 37,
child: ButtonTheme(
minWidth: double.minPositive,
child: Semantics(
label: S.of(context).add_contact,
child: TextButton(
style: ButtonStyle(
overlayColor: MaterialStateColor.resolveWith(
(states) => Colors.transparent),
),
onPressed: () =>
Navigator.of(context).pushNamed(Routes.addressBookAdd),
child: Icon(
Icons.add,
color: pageIconColor(context),
size: 24,
),
),
),
),
),
);

@override
Widget body(BuildContext context) => SingleChildScrollView(
child: Observer(
builder: (_) => Column(
children: addressBookVM.entries
.map((e) => AddressBookEntryCard(
onTap: isSelector
? () => Navigator.of(context).pop(e)
: null,
onDelete: isSelector
? null
: () => addressBookVM.deleteEntry(e),
title: e.name,
address: e.address,
backgroundColor: const Color.fromRGBO(5, 8, 23, 1),
))
.toList(),
),
),
);
}
Loading

0 comments on commit 821f3a9

Please sign in to comment.