Skip to content

Commit

Permalink
feat: add vc creation and verification flow
Browse files Browse the repository at this point in the history
  • Loading branch information
wesbillman committed Jan 5, 2024
1 parent e4dd4e2 commit e3ce33f
Show file tree
Hide file tree
Showing 19 changed files with 403 additions and 82 deletions.
31 changes: 22 additions & 9 deletions frontend/lib/features/account/account_page.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_starter/features/account/account_did_page.dart';
import 'package:flutter_starter/features/account/account_vc_page.dart';

class AccountPage extends StatelessWidget {
const AccountPage({super.key});
Expand All @@ -11,15 +12,27 @@ class AccountPage extends StatelessWidget {
body: ListView(
children: [
ListTile(
title: const Text('My DID'),
trailing: const Icon(Icons.chevron_right),
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const AccountDidPage(),
),
);
}),
title: const Text('My DID'),
trailing: const Icon(Icons.chevron_right),
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const AccountDidPage(),
),
);
},
),
ListTile(
title: const Text('My verifiable credential'),
trailing: const Icon(Icons.chevron_right),
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const AccountVCPage(),
),
);
},
),
],
),
);
Expand Down
1 change: 1 addition & 0 deletions frontend/lib/features/account/account_providers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:web5_flutter/web5_flutter.dart';

final didProvider = Provider<Did>((ref) => throw UnimplementedError());
final vcProvider = StateProvider<String?>((ref) => null);
20 changes: 20 additions & 0 deletions frontend/lib/features/account/account_vc_page.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import 'package:flutter/material.dart';
import 'package:flutter_starter/features/account/account_providers.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

class AccountVCPage extends HookConsumerWidget {
const AccountVCPage({super.key});

@override
Widget build(BuildContext context, WidgetRef ref) {
final vc = ref.watch(vcProvider);

return Scaffold(
appBar: AppBar(title: const Text('My VC')),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: Center(child: SelectableText(vc ?? '')),
),
);
}
}
6 changes: 4 additions & 2 deletions frontend/lib/features/app/app.dart
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import 'package:flutter/material.dart';
import 'package:flutter_starter/features/app/app_tabs.dart';
import 'package:flutter_starter/features/onboarding/onboarding_welcome_page.dart';
import 'package:flutter_starter/l10n/app_localizations.dart';
import 'package:flutter_starter/shared/theme/theme.dart';

class App extends StatelessWidget {
const App({super.key});
final bool onboarding;
const App({required this.onboarding, super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'DIDPay',
theme: lightTheme(context),
darkTheme: darkTheme(context),
home: const AppTabs(),
home: onboarding ? const OnboardingWelcomePage() : const AppTabs(),
localizationsDelegates: Loc.localizationsDelegates,
supportedLocales: const [
Locale('en', ''),
Expand Down
48 changes: 20 additions & 28 deletions frontend/lib/features/home/home_page.dart
Original file line number Diff line number Diff line change
@@ -1,41 +1,33 @@
import 'package:flutter/material.dart';
import 'package:flutter_starter/features/pfis/pfi_providers.dart';
import 'package:flutter_starter/features/pfis/pfi_verification_page.dart';
import 'package:flutter_starter/l10n/app_localizations.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:web5_flutter/web5_flutter.dart';

class HomePage extends HookConsumerWidget {
const HomePage({super.key});

@override
Widget build(BuildContext context, WidgetRef ref) {
final pfis = ref.watch(pfisProvider);
return Scaffold(
appBar: AppBar(title: const Text('Home')),
body: ListView(
appBar: AppBar(title: Text(Loc.of(context).home)),
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
...pfis.map(
(pfi) => ListTile(
title: Text(pfi.name),
subtitle: Text(pfi.didUri),
trailing: const Icon(Icons.chevron_right),
onTap: () async {
final result = await DidDht.resolve(pfi.didUri);
final widgetService = result.didDocument?.service
?.firstWhere((e) => e.type == 'kyc-widget');
if (widgetService?.serviceEndpoint != null) {
// ignore: use_build_context_synchronously
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => PfiVerificationPage(
widgetUri: widgetService!.serviceEndpoint,
),
),
);
}
},
),
)
const SizedBox(height: 40),
Text(
'Balance: \$0.00 USD',
style: Theme.of(context).textTheme.titleLarge,
textAlign: TextAlign.center,
),
const Spacer(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
FilledButton(
onPressed: () {}, child: Text(Loc.of(context).deposit)),
FilledButton(onPressed: () {}, child: Text(Loc.of(context).send)),
],
),
const SizedBox(height: 40),
],
),
);
Expand Down
50 changes: 50 additions & 0 deletions frontend/lib/features/onboarding/onboarding_welcome_page.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import 'package:flutter/material.dart';
import 'package:flutter_starter/features/pfis/pfis_page.dart';
import 'package:flutter_starter/l10n/app_localizations.dart';

class OnboardingWelcomePage extends StatelessWidget {
const OnboardingWelcomePage({super.key});

@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 40),
Text(
Loc.of(context).welcomeToDIDPay,
style: Theme.of(context).textTheme.displayMedium,
textAlign: TextAlign.center,
),
const SizedBox(height: 80),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: Text(
Loc.of(context).toSendMoney,
style: Theme.of(context).textTheme.titleLarge,
textAlign: TextAlign.center,
),
),
const Spacer(),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: FilledButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const PfisPage(),
fullscreenDialog: true,
),
);
},
child: Text(Loc.of(context).getStarted),
),
),
],
),
),
);
}
}
120 changes: 120 additions & 0 deletions frontend/lib/features/pfis/pfi_confirmation_page.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import 'dart:convert';

import 'package:collection/collection.dart';
import 'package:flutter_starter/features/account/account_providers.dart';
import 'package:flutter_starter/services/service_providers.dart';
import 'package:flutter_starter/shared/constants.dart';
import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_starter/features/app/app_tabs.dart';
import 'package:flutter_starter/features/pfis/pfi.dart';
import 'package:flutter_starter/l10n/app_localizations.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:web5_flutter/web5_flutter.dart';

class PfiConfirmationPage extends HookConsumerWidget {
final Pfi pfi;
final String transactionId;

const PfiConfirmationPage({
required this.pfi,
required this.transactionId,
super.key,
});

@override
Widget build(BuildContext context, WidgetRef ref) {
final vcJwt = ref.watch(vcProvider);

useEffect(() {
verifyCredential(ref);
return null;
}, []);

return Scaffold(
body: SafeArea(
child: vcJwt == null ? verifying(context) : verified(context, vcJwt),
),
);
}

Widget verifying(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(height: 40),
Text(
'Verifying your credentials...',
style: Theme.of(context).textTheme.titleMedium,
textAlign: TextAlign.center,
),
const SizedBox(height: 40),
const Center(child: CircularProgressIndicator())
],
);
}

Widget verified(BuildContext context, String vcJwt) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 40),
Expanded(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Your credentials have been verified!',
style: Theme.of(context).textTheme.titleMedium,
textAlign: TextAlign.center,
),
const SizedBox(height: 40),
Icon(Icons.check_circle,
size: 80, color: Theme.of(context).colorScheme.primary),
],
),
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: FilledButton(
onPressed: () {
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (context) => const AppTabs()),
(Route<dynamic> route) => false,
);
},
child: Text(Loc.of(context).done),
),
),
],
);
}

Future<void> verifyCredential(WidgetRef ref) async {
final result = await DidDht.resolve(pfi.didUri);
final pfiService =
result.didDocument?.service?.firstWhereOrNull((e) => e.type == 'PFI');

if (pfiService == null) {
// Add real error handling here...
throw Exception('PFI service endpoint not found');
}

var uri = Uri.parse(
'${pfiService.serviceEndpoint}/credential?transaction_id=$transactionId');
final response = await http.get(uri);
if (response.statusCode != 200) {
// Add real error handling here...
throw Exception('Failed to get credential');
}

final jsonResponse = json.decode(response.body);
ref.read(secureStorageProvider).write(
key: Constants.verifiableCredentialKey, value: jsonResponse['jwt']);
ref.read(vcProvider.notifier).state = jsonResponse['jwt'];
}
}
Loading

0 comments on commit e3ce33f

Please sign in to comment.