Skip to content

Commit 1717d76

Browse files
Merge pull request #758 from threefoldtech/development_delete_user
Support deleting account
2 parents 60419bc + 2fd0a73 commit 1717d76

8 files changed

Lines changed: 215 additions & 77 deletions

File tree

app/lib/screens/preference_screen.dart

Lines changed: 118 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
import 'dart:io';
2+
13
import 'package:flutter/material.dart';
24
import 'package:flutter/scheduler.dart';
35
import 'package:flutter/services.dart';
6+
import 'package:flutter_pkid/flutter_pkid.dart';
47
import 'package:flutter_riverpod/flutter_riverpod.dart';
8+
import 'package:http/http.dart';
59
import 'package:package_info_plus/package_info_plus.dart';
610
import 'package:threebotlogin/app_config.dart';
711
import 'package:threebotlogin/apps/free_flow_pages/ffp_events.dart';
@@ -15,10 +19,14 @@ import 'package:threebotlogin/screens/authentication_screen.dart';
1519
import 'package:threebotlogin/screens/change_pin_screen.dart';
1620
import 'package:threebotlogin/screens/main_screen.dart';
1721
import 'package:threebotlogin/services/fingerprint_service.dart';
22+
import 'package:threebotlogin/services/open_kyc_service.dart';
23+
import 'package:threebotlogin/services/pkid_service.dart';
1824
import 'package:threebotlogin/services/shared_preference_service.dart';
25+
import 'package:threebotlogin/services/wallet_service.dart';
1926
import 'package:threebotlogin/widgets/custom_dialog.dart';
2027
import 'package:threebotlogin/widgets/layout_drawer.dart';
2128
import 'package:threebotlogin/providers/theme_provider.dart';
29+
import 'package:threebotlogin/widgets/wallets/warning_dialog.dart';
2230
import 'package:url_launcher/url_launcher.dart';
2331

2432
class PreferenceScreen extends ConsumerStatefulWidget {
@@ -47,6 +55,7 @@ class _PreferenceScreenState extends ConsumerState<PreferenceScreen> {
4755
Globals globals = Globals();
4856

4957
MaterialColor thiscolor = Colors.green;
58+
bool deleteLoading = false;
5059

5160
@override
5261
void initState() {
@@ -164,7 +173,7 @@ class _PreferenceScreenState extends ConsumerState<PreferenceScreen> {
164173
}),
165174
ListTile(
166175
leading: const Icon(Icons.lock),
167-
title: const Text('Change pincode'),
176+
title: const Text('Change PIN'),
168177
onTap: () async {
169178
_changePincode();
170179
},
@@ -241,25 +250,37 @@ class _PreferenceScreenState extends ConsumerState<PreferenceScreen> {
241250
title: const Text('Terms and conditions'),
242251
onTap: () async => {await _showTermsAndConds()},
243252
),
253+
ListTile(
254+
leading: const Icon(Icons.logout_outlined),
255+
title: Text(
256+
'Log Out',
257+
style: Theme.of(context)
258+
.textTheme
259+
.bodyLarge!
260+
.copyWith(color: Theme.of(context).colorScheme.onSurface),
261+
),
262+
onTap: _showDialog,
263+
),
244264
ExpansionTile(
245265
title: const Text(
246266
'Advanced settings',
247267
),
248268
children: <Widget>[
249269
ListTile(
250-
leading: const Icon(Icons.person),
270+
leading: Icon(
271+
Icons.remove_circle,
272+
color: Theme.of(context).colorScheme.error,
273+
),
251274
title: Text(
252-
'Remove Account From Device',
275+
'Delete Account',
253276
style: Theme.of(context)
254277
.textTheme
255278
.bodyLarge!
256279
.copyWith(color: Theme.of(context).colorScheme.error),
257280
),
258-
trailing: Icon(
259-
Icons.remove_circle,
260-
color: Theme.of(context).colorScheme.error,
261-
),
262-
onTap: _showDialog,
281+
onTap: () {
282+
_showDialog(delete: true);
283+
},
263284
),
264285
],
265286
),
@@ -311,67 +332,99 @@ class _PreferenceScreenState extends ConsumerState<PreferenceScreen> {
311332
);
312333
}
313334

314-
void _showDialog() {
335+
void _showDialog({delete = false}) {
336+
String title = 'Log Out';
337+
String message = 'Are you sure you want to log out?';
338+
if (delete) {
339+
title = 'Are you sure?';
340+
message =
341+
"If you confirm, your account will be deleted. You won't be able to recover your account.";
342+
}
343+
preferenceContext = context;
315344
showDialog(
316345
context: context,
317-
builder: (BuildContext context) => CustomDialog(
318-
type: DialogType.Warning,
319-
image: Icons.warning,
320-
title: 'Are you sure?',
321-
description:
322-
'If you confirm, your account will be removed from this device. You can always recover your account with your username and phrase.',
323-
actions: <Widget>[
324-
TextButton(
325-
child: const Text('Cancel'),
326-
onPressed: () {
327-
Navigator.pop(context);
328-
},
329-
),
330-
TextButton(
331-
child: Text(
332-
'Yes',
333-
style: Theme.of(context)
334-
.textTheme
335-
.bodyMedium!
336-
.copyWith(color: Theme.of(context).colorScheme.warning),
337-
),
338-
onPressed: () async {
339-
// try {
340-
// String deviceID = await _listener.getToken();
341-
// removeDeviceId(deviceID);
342-
// } catch (e) {}
343-
Events().emit(CloseSocketEvent());
344-
Events().emit(FfpClearCacheEvent());
345-
bool result = await clearData();
346-
if (result) {
347-
Navigator.pop(context);
348-
await Navigator.pushReplacement(
349-
context,
350-
MaterialPageRoute(
351-
builder: (context) => const MainScreen(
352-
initDone: true, registered: false)));
353-
} else {
354-
showDialog(
355-
context: preferenceContext!,
356-
builder: (BuildContext context) => CustomDialog(
357-
type: DialogType.Error,
358-
title: 'Error',
359-
description:
360-
'Something went wrong when trying to remove your account.',
361-
actions: <Widget>[
362-
TextButton(
363-
child: const Text('Close'),
364-
onPressed: () {
365-
Navigator.pop(context);
366-
},
367-
)
368-
],
346+
builder: (BuildContext context) => WarningDialogWidget(
347+
title: title,
348+
description: message,
349+
onAgree: () async {
350+
deleteLoading = true;
351+
setState(() {});
352+
if (delete) {
353+
String? pin = await getPin();
354+
bool? authenticated = await Navigator.push(
355+
context,
356+
MaterialPageRoute(
357+
builder: (context) => AuthenticationScreen(
358+
correctPin: pin!,
359+
userMessage: 'Please enter your PIN code',
369360
),
370-
);
361+
));
362+
363+
if (authenticated == null || !authenticated) {
364+
deleteLoading = false;
365+
setState(() {});
366+
return false;
367+
}
368+
}
369+
Events().emit(CloseSocketEvent());
370+
Events().emit(FfpClearCacheEvent());
371+
bool deleted = true;
372+
if (delete) {
373+
try {
374+
Response response = await deleteUser();
375+
if (response.statusCode != HttpStatus.noContent) {
376+
deleted = false;
371377
}
372-
},
373-
),
374-
],
378+
} catch (e) {
379+
print('Failed to delete user due to $e');
380+
deleted = false;
381+
}
382+
if (deleted) {
383+
final seedPhrase = await getPhrase();
384+
FlutterPkid client = await getPkidClient(seedPhrase: seedPhrase!);
385+
await client.setPKidDoc('email', '');
386+
await client.setPKidDoc('phone', '');
387+
await saveWalletsToPkid([]);
388+
}
389+
}
390+
bool result = false;
391+
if (deleted) {
392+
result = await clearData();
393+
if (result) {
394+
Navigator.pop(context);
395+
await Navigator.pushReplacement(
396+
context,
397+
MaterialPageRoute(
398+
builder: (context) =>
399+
const MainScreen(initDone: true, registered: false)));
400+
}
401+
}
402+
if (!result || !deleted) {
403+
await showDialog(
404+
context: preferenceContext!,
405+
builder: (BuildContext context) => CustomDialog(
406+
type: DialogType.Error,
407+
title: 'Error',
408+
description:
409+
'Something went wrong when trying to remove your account.',
410+
actions: <Widget>[
411+
TextButton(
412+
child: const Text('Close'),
413+
onPressed: () {
414+
Navigator.pop(context);
415+
},
416+
)
417+
],
418+
),
419+
);
420+
deleteLoading = false;
421+
setState(() {});
422+
return false;
423+
}
424+
deleteLoading = false;
425+
setState(() {});
426+
return true;
427+
},
375428
),
376429
);
377430
}

app/lib/screens/recover_screen.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ class _RecoverScreenState extends State<RecoverScreen> {
109109
Widget build(BuildContext context) {
110110
return Scaffold(
111111
appBar: AppBar(
112-
title: const Text('Recover Account'),
112+
title: const Text('Log In'),
113113
),
114114
body: Container(
115115
padding: const EdgeInsets.all(20.0),
@@ -189,7 +189,7 @@ class _RecoverScreenState extends State<RecoverScreen> {
189189
ElevatedButton(
190190
style: ElevatedButton.styleFrom(),
191191
child: Text(
192-
'Recover Account',
192+
'Log In',
193193
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
194194
color: Theme.of(context).colorScheme.onPrimaryContainer),
195195
),

app/lib/screens/unregistered_screen.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ class _UnregisteredScreenState extends State<UnregisteredScreen>
5151
context: context,
5252
builder: (BuildContext context) => const CustomDialog(
5353
image: Icons.check,
54-
title: 'Recovered',
55-
description: 'Your account has been recovered.',
54+
title: 'Logged In',
55+
description: 'You have logged In successfully.',
5656
),
5757
);
5858
await Future.delayed(
@@ -101,7 +101,7 @@ class _UnregisteredScreenState extends State<UnregisteredScreen>
101101
mainAxisAlignment: MainAxisAlignment.center,
102102
children: <Widget>[
103103
Text(
104-
'Sign Up',
104+
'Create Account',
105105
style: Theme.of(context)
106106
.textTheme
107107
.titleMedium!
@@ -129,7 +129,7 @@ class _UnregisteredScreenState extends State<UnregisteredScreen>
129129
mainAxisAlignment: MainAxisAlignment.center,
130130
children: <Widget>[
131131
Text(
132-
'Recover Account',
132+
'Log In',
133133
style: Theme.of(context)
134134
.textTheme
135135
.titleMedium!

app/lib/services/open_kyc_service.dart

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ Future<Response> sendVerificationSms() async {
102102
return http.post(url, body: encodedBody, headers: requestHeaders);
103103
}
104104

105-
// TODO: Remove this method and user update user data
105+
// TODO: Remove this method and use update user data
106106
Future<Response> updateEmailAddressOfUser() async {
107107
String timestamp = DateTime.now().millisecondsSinceEpoch.toString();
108108
Uint8List sk = await getPrivateKey();
@@ -155,3 +155,25 @@ Future<Response> updateUserData(String field, String value) async {
155155

156156
return http.post(url, headers: loginRequestHeaders, body: encodedBody);
157157
}
158+
159+
Future<Response> deleteUser() async {
160+
String timestamp = DateTime.now().millisecondsSinceEpoch.toString();
161+
Uint8List sk = await getPrivateKey();
162+
163+
Map<String, String> payload = {
164+
'timestamp': timestamp,
165+
'intention': 'delete-user'
166+
};
167+
String signedPayload = await signData(jsonEncode(payload), sk);
168+
169+
Map<String, String> loginRequestHeaders = {
170+
'Content-type': 'application/json',
171+
'Jimber-Authorization': signedPayload
172+
};
173+
174+
final doubleName = await getDoubleName();
175+
Uri url = Uri.parse('$threeBotApiUrl/users/$doubleName');
176+
logger.i('Sending call: ${url.toString()}');
177+
178+
return http.delete(url, headers: loginRequestHeaders);
179+
}

app/lib/services/wallet_service.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ Future<void> addWallet(String walletName, String walletSecret,
121121
seed: walletSecret,
122122
type: type));
123123

124-
await _saveWalletsToPkid(wallets);
124+
await saveWalletsToPkid(wallets);
125125
}
126126

127127
Future<void> editWallet(String oldName, String newName) async {
@@ -132,16 +132,16 @@ Future<void> editWallet(String oldName, String newName) async {
132132
break;
133133
}
134134
}
135-
await _saveWalletsToPkid(wallets);
135+
await saveWalletsToPkid(wallets);
136136
}
137137

138138
Future<void> deleteWallet(String walletName) async {
139139
List<PkidWallet> wallets = await _getPkidWallets();
140140
wallets = wallets.where((w) => w.name != walletName).toList();
141-
await _saveWalletsToPkid(wallets);
141+
await saveWalletsToPkid(wallets);
142142
}
143143

144-
Future<void> _saveWalletsToPkid(List<PkidWallet> wallets) async {
144+
Future<void> saveWalletsToPkid(List<PkidWallet> wallets) async {
145145
FlutterPkid client = await _getPkidClient();
146146
final encodedWallets = json.encode(wallets.map((w) => w.toMap()).toList());
147147
await client.setPKidDoc('purse', encodedWallets);

backend/database.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,14 @@ def update_user(double_name, field, value):
632632
except Error as e:
633633
print(e)
634634

635+
def delete_user(double_name):
636+
delete_sql = f'DELETE FROM users WHERE double_name =?'
637+
try:
638+
cursor = conn.cursor()
639+
cursor.execute(delete_sql, (double_name,))
640+
conn.commit()
641+
except Error as e:
642+
print(e)
635643

636644

637645
def create_db(conn):

backend/routes/misc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
@api_misc.route("/minimumversion", methods=["get"])
1111
def minimum_version_handler():
1212
response = Response(
13-
response=json.dumps({"android": 182, "ios": 182}), mimetype="application/json"
13+
response=json.dumps({"android": 178, "ios": 178}), mimetype="application/json"
1414
)
1515
return response
1616

0 commit comments

Comments
 (0)