Skip to content

Commit

Permalink
feat: fetch offerings using dap (#211)
Browse files Browse the repository at this point in the history
  • Loading branch information
ethan-tbd authored Jun 25, 2024
1 parent ba1b412 commit 01351af
Show file tree
Hide file tree
Showing 13 changed files with 289 additions and 113 deletions.
4 changes: 2 additions & 2 deletions lib/features/account/account_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ class AccountPage extends HookConsumerWidget {
final pfis = ref.watch(pfisProvider);
final credentials = ref.watch(vcsProvider);
final featureFlags = ref.watch(featureFlagsProvider);
const dap = '@username/didpay.me';
final dap = Loc.of(context).placeholderDap;

return Scaffold(
body: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildProfile(context, dap),
const Center(child: Text(dap)),
Center(child: Text(dap)),
const SizedBox(height: Grid.lg),
Expanded(
child: SingleChildScrollView(
Expand Down
55 changes: 36 additions & 19 deletions lib/features/dap/dap_form.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import 'package:dap/dap.dart';
import 'package:didpay/features/did/did_qr_tile.dart';
import 'package:didpay/features/dap/dap_qr_tile.dart';
import 'package:didpay/l10n/app_localizations.dart';
import 'package:didpay/shared/next_button.dart';
import 'package:didpay/shared/theme/grid.dart';
Expand All @@ -9,15 +9,20 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';

class DapForm extends HookConsumerWidget {
final String buttonTitle;
final void Function(String) onSubmit;
final ValueNotifier<AsyncValue<Dap>?> dap;
final void Function(List<MoneyAddress>) onSubmit;

DapForm({required this.buttonTitle, required this.onSubmit, super.key});
DapForm({
required this.buttonTitle,
required this.dap,
required this.onSubmit,
super.key,
});

final _formKey = GlobalKey<FormState>();

@override
Widget build(BuildContext context, WidgetRef ref) {
final dap = useState<Dap?>(null);
final errorText = useState<String?>(null);
final focusNode = useFocusNode();

Expand All @@ -40,16 +45,14 @@ class DapForm extends HookConsumerWidget {
focusNode: focusNode,
controller: textController,
onTap: () => errorText.value = null,
onTapOutside: (_) async => _updateErrorText(
onTapOutside: (_) async => _parseDap(
textController.text,
errorMessage,
dap,
errorText,
).then((_) => focusNode.unfocus()),
onFieldSubmitted: (_) async => _updateErrorText(
onFieldSubmitted: (_) async => _parseDap(
textController.text,
errorMessage,
dap,
errorText,
),
enableSuggestions: false,
Expand All @@ -66,21 +69,18 @@ class DapForm extends HookConsumerWidget {
),
),
),
DidQrTile(
title: Loc.of(context).dontKnowTheirDap,
didTextController: textController,
DapQrTile(
dapTextController: textController,
errorText: errorText,
),
NextButton(
onPressed: () async => _updateErrorText(
onPressed: () => _parseDap(
textController.text,
errorMessage,
dap,
errorText,
).then(
(_) => errorText.value == null
? onSubmit(textController.text)
: null,
(parsedDap) =>
errorText.value == null ? _resolveDap(parsedDap) : null,
),
title: buttonTitle,
),
Expand All @@ -89,17 +89,34 @@ class DapForm extends HookConsumerWidget {
);
}

Future<void> _updateErrorText(
Future<Dap?> _parseDap(
String inputText,
String errorMessage,
ValueNotifier<Dap?> dap,
ValueNotifier<String?> errorText,
) async {
try {
dap.value = Dap.parse(inputText);
final parsedDap = Dap.parse(inputText);
errorText.value = null;
return parsedDap;
} on Exception {
errorText.value = errorMessage;
}
return null;
}

Future<void> _resolveDap(Dap? parsedDap) async {
if (parsedDap == null) return;
try {
dap.value = const AsyncValue.loading();
final result = await DapResolver().resolve(parsedDap);

await Future.delayed(const Duration(milliseconds: 500));

dap.value = AsyncValue.data(result.dap);
onSubmit(result.moneyAddresses);
} on Exception catch (e) {
dap.value =
AsyncError('${e.runtimeType}: Invalid DAP', StackTrace.current);
}
}
}
94 changes: 94 additions & 0 deletions lib/features/dap/dap_qr_tile.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import 'package:didpay/features/device/device_info_service.dart';
import 'package:didpay/features/did/did_qr_tabs.dart';
import 'package:didpay/l10n/app_localizations.dart';
import 'package:didpay/shared/theme/grid.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:web5/web5.dart';

class DapQrTile extends HookConsumerWidget {
final TextEditingController dapTextController;
final ValueNotifier<String?>? errorText;

const DapQrTile({
required this.dapTextController,
this.errorText,
super.key,
});

@override
Widget build(BuildContext context, WidgetRef ref) {
final isPhysicalDevice = useState(true);

useEffect(
() {
Future.microtask(
() async => isPhysicalDevice.value =
await ref.read(deviceInfoServiceProvider).isPhysicalDevice(),
);
return null;
},
[],
);

return Padding(
padding: const EdgeInsets.symmetric(vertical: Grid.xxs),
child: ListTile(
leading: const Icon(Icons.qr_code),
title: Text(
Loc.of(context).dontKnowTheirDap,
style: Theme.of(context).textTheme.bodyMedium,
),
trailing: const Icon(Icons.chevron_right),
onTap: () => isPhysicalDevice.value
? _scanQrCode(
context,
dapTextController,
errorText,
Loc.of(context).noDapQrCodeFound,
)
: _simulateScanQrCode(
context,
dapTextController,
errorText,
),
),
);
}

Future<void> _scanQrCode(
BuildContext context,
TextEditingController dapTextController,
ValueNotifier<String?>? errorText,
String errorMessage,
) async {
final qrValue = await Navigator.of(context).push<String>(
MaterialPageRoute(
builder: (context) => DidQrTabs(dap: Loc.of(context).placeholderDap),
),
);

final isValid = qrValue != null &&
await DidResolver.resolve(qrValue).then((result) => !result.hasError());
dapTextController.text = isValid ? qrValue : '';
errorText?.value = isValid ? null : errorMessage;
}

Future<void> _simulateScanQrCode(
BuildContext context,
TextEditingController didTextController,
ValueNotifier<String?>? errorText,
) async {
ScaffoldMessenger.of(context).removeCurrentSnackBar();

ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(Loc.of(context).simulatedQrCodeScan),
),
);

didTextController.text = '@moegrammer/didpay.me';
errorText?.value = null;
}
}
1 change: 0 additions & 1 deletion lib/features/did/did_form.dart
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ class DidForm extends HookConsumerWidget {
),
),
DidQrTile(
title: Loc.of(context).dontKnowTheirDid,
didTextController: textController,
errorText: errorText,
),
Expand Down
6 changes: 2 additions & 4 deletions lib/features/did/did_qr_tile.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,10 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:web5/web5.dart';

class DidQrTile extends HookConsumerWidget {
final String title;
final TextEditingController didTextController;
final ValueNotifier<String?>? errorText;

const DidQrTile({
required this.title,
required this.didTextController,
this.errorText,
super.key,
Expand All @@ -39,7 +37,7 @@ class DidQrTile extends HookConsumerWidget {
child: ListTile(
leading: const Icon(Icons.qr_code),
title: Text(
title,
Loc.of(context).dontKnowTheirDid,
style: Theme.of(context).textTheme.bodyMedium,
),
trailing: const Icon(Icons.chevron_right),
Expand Down Expand Up @@ -67,7 +65,7 @@ class DidQrTile extends HookConsumerWidget {
) async {
final qrValue = await Navigator.of(context).push<String>(
MaterialPageRoute(
builder: (context) => const DidQrTabs(dap: '[email protected]'),
builder: (context) => DidQrTabs(dap: Loc.of(context).placeholderDap),
),
);

Expand Down
Loading

0 comments on commit 01351af

Please sign in to comment.