Skip to content

Commit

Permalink
Merge pull request #1670 from nextcloud/feat/nextcloud/drop_account
Browse files Browse the repository at this point in the history
  • Loading branch information
provokateurin authored Feb 29, 2024
2 parents 9b6f4e3 + f3d9966 commit fda3bfe
Show file tree
Hide file tree
Showing 47 changed files with 1,920 additions and 43 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,6 @@
[submodule "external/nextcloud-openapi-extractor"]
path = external/nextcloud-openapi-extractor
url = https://github.com/nextcloud/openapi-extractor.git
[submodule "external/nextcloud-drop_account"]
path = external/nextcloud-drop_account
url = https://framagit.org/framasoft/nextcloud/drop_account.git
1 change: 1 addition & 0 deletions external/nextcloud-drop_account
Submodule nextcloud-drop_account added at 7829dd
11 changes: 11 additions & 0 deletions packages/neon_framework/lib/l10n/en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@
"globalOptionsNavigationMode": "Navigation mode",
"globalOptionsNavigationModeDrawer": "Drawer",
"globalOptionsNavigationModeDrawerAlwaysVisible": "Drawer always visible",
"accountOptionsDeleteOnServer": "Delete account on the server",
"accountOptionsRemove": "Remove account",
"accountOptionsRemoveConfirm": "Are you sure you want to remove the account {id}?",
"@accountOptionsRemoveConfirm": {
Expand All @@ -166,6 +167,16 @@
}
}
},
"accountOptionsRemoveLocal": "Remove the account from the device",
"accountOptionsRemoveRemote": "Request deleting the account on the server",
"accountOptionsRemoveRemoteDelay": "The account will be deleted after {time}",
"@accountOptionsRemoveRemoteDelay": {
"placeholders": {
"time": {
"type": "String"
}
}
},
"accountOptionsCategoryStorageInfo": "Storage info",
"accountOptionsQuotaUsedOf": "{used} used of {total} ({relative}%)",
"@accountOptionsQuotaUsedOf": {
Expand Down
24 changes: 24 additions & 0 deletions packages/neon_framework/lib/l10n/localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,12 @@ abstract class NeonLocalizations {
/// **'Drawer always visible'**
String get globalOptionsNavigationModeDrawerAlwaysVisible;

/// No description provided for @accountOptionsDeleteOnServer.
///
/// In en, this message translates to:
/// **'Delete account on the server'**
String get accountOptionsDeleteOnServer;

/// No description provided for @accountOptionsRemove.
///
/// In en, this message translates to:
Expand All @@ -677,6 +683,24 @@ abstract class NeonLocalizations {
/// **'Are you sure you want to remove the account {id}?'**
String accountOptionsRemoveConfirm(String id);

/// No description provided for @accountOptionsRemoveLocal.
///
/// In en, this message translates to:
/// **'Remove the account from the device'**
String get accountOptionsRemoveLocal;

/// No description provided for @accountOptionsRemoveRemote.
///
/// In en, this message translates to:
/// **'Request deleting the account on the server'**
String get accountOptionsRemoveRemote;

/// No description provided for @accountOptionsRemoveRemoteDelay.
///
/// In en, this message translates to:
/// **'The account will be deleted after {time}'**
String accountOptionsRemoveRemoteDelay(String time);

/// No description provided for @accountOptionsCategoryStorageInfo.
///
/// In en, this message translates to:
Expand Down
14 changes: 14 additions & 0 deletions packages/neon_framework/lib/l10n/localizations_en.dart
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,9 @@ class NeonLocalizationsEn extends NeonLocalizations {
@override
String get globalOptionsNavigationModeDrawerAlwaysVisible => 'Drawer always visible';

@override
String get accountOptionsDeleteOnServer => 'Delete account on the server';

@override
String get accountOptionsRemove => 'Remove account';

Expand All @@ -340,6 +343,17 @@ class NeonLocalizationsEn extends NeonLocalizations {
return 'Are you sure you want to remove the account $id?';
}

@override
String get accountOptionsRemoveLocal => 'Remove the account from the device';

@override
String get accountOptionsRemoveRemote => 'Request deleting the account on the server';

@override
String accountOptionsRemoveRemoteDelay(String time) {
return 'The account will be deleted after $time';
}

@override
String get accountOptionsCategoryStorageInfo => 'Storage info';

Expand Down
44 changes: 25 additions & 19 deletions packages/neon_framework/lib/src/pages/account_settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import 'package:neon_framework/src/theme/dialog.dart';
import 'package:neon_framework/src/utils/adaptive.dart';
import 'package:neon_framework/src/widgets/dialog.dart';
import 'package:neon_framework/src/widgets/error.dart';
import 'package:url_launcher/url_launcher.dart';

/// Account settings page.
///
Expand Down Expand Up @@ -45,32 +46,37 @@ class AccountSettingsPage extends StatelessWidget {
actions: [
IconButton(
onPressed: () async {
final decision = await showAdaptiveDialog<bool>(
final decision = await showAdaptiveDialog<AccountDeletion>(
context: context,
builder: (context) => NeonConfirmationDialog(
icon: const Icon(Icons.logout),
title: NeonLocalizations.of(context).accountOptionsRemove,
content: Text(
NeonLocalizations.of(context).accountOptionsRemoveConfirm(account.humanReadableID),
),
builder: (context) => NeonAccountDeletionDialog(
account: account,
),
);

if (decision ?? false) {
final isActive = bloc.activeAccount.valueOrNull == account;
switch (decision) {
case null:
break;
case AccountDeletion.remote:
await launchUrl(
account.serverURL.replace(
path: '${account.serverURL.path}/index.php/settings/user/drop_account',
),
);
case AccountDeletion.local:
final isActive = bloc.activeAccount.valueOrNull == account;

options.reset();
bloc.removeAccount(account);
options.reset();
bloc.removeAccount(account);

if (!context.mounted) {
return;
}
if (!context.mounted) {
return;
}

if (isActive) {
const HomeRoute().go(context);
} else {
Navigator.of(context).pop();
}
if (isActive) {
const HomeRoute().go(context);
} else {
Navigator.of(context).pop();
}
}
},
tooltip: NeonLocalizations.of(context).accountOptionsRemove,
Expand Down
138 changes: 138 additions & 0 deletions packages/neon_framework/lib/src/widgets/dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import 'package:meta/meta.dart';
import 'package:neon_framework/blocs.dart';
import 'package:neon_framework/src/models/account.dart';
import 'package:neon_framework/src/utils/global_options.dart';
import 'package:neon_framework/src/utils/relative_time.dart';
import 'package:neon_framework/src/utils/user_status_clear_at.dart';
import 'package:neon_framework/src/widgets/account_tile.dart';
import 'package:neon_framework/src/widgets/error.dart';
import 'package:neon_framework/src/widgets/linear_progress_indicator.dart';
import 'package:neon_framework/src/widgets/user_status_icon.dart';
import 'package:neon_framework/theme.dart';
import 'package:neon_framework/utils.dart';
import 'package:nextcloud/core.dart' as core;
import 'package:nextcloud/user_status.dart' as user_status;
import 'package:url_launcher/url_launcher_string.dart';

Expand Down Expand Up @@ -517,6 +519,142 @@ class NeonAccountSelectionDialog extends StatelessWidget {
}
}

/// The way the account will be deleted.
@internal
enum AccountDeletion {
/// The account is removed from the app.
local,

/// The account is deleted on the server.
remote,
}

@internal

/// Displays a confirmation dialog for deleting the [account].
///
/// If the `drop_account` app is enabled the user can also choose to delete the account on the server
/// instead of only logging out the account.
///
/// Will pop a value of type [AccountDeletion] or null if the user canceled the dialog.
class NeonAccountDeletionDialog extends StatefulWidget {
const NeonAccountDeletionDialog({
required this.account,
super.key,
});

final Account account;

@override
State<NeonAccountDeletionDialog> createState() => _NeonAccountDeletionDialogState();
}

class _NeonAccountDeletionDialogState extends State<NeonAccountDeletionDialog> {
core.DropAccountCapabilities_DropAccount? dropAccountCapabilities;
AccountDeletion value = AccountDeletion.local;

void update(AccountDeletion value) {
setState(() {
this.value = value;
});
}

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

NeonProvider.of<AccountsBloc>(context).getCapabilitiesBlocFor(widget.account).capabilities.listen((result) {
setState(() {
dropAccountCapabilities = result.data?.capabilities.dropAccountCapabilities?.dropAccount;
if (!(dropAccountCapabilities?.enabled ?? false)) {
value = AccountDeletion.local;
}
});
});
}

@override
Widget build(BuildContext context) {
final localizations = NeonLocalizations.of(context);

const icon = Icon(Icons.logout);
final title = localizations.accountOptionsRemove;
final confirmAction = NeonDialogAction(
isDestructiveAction: true,
onPressed: () {
Navigator.of(context).pop(value);
},
child: Text(
localizations.actionContinue,
textAlign: TextAlign.end,
),
);
final declineAction = NeonDialogAction(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(
localizations.actionCancel,
textAlign: TextAlign.end,
),
);

final capabilities = dropAccountCapabilities;
if (capabilities == null) {
return NeonConfirmationDialog(
icon: icon,
title: title,
content: Text(localizations.accountOptionsRemoveConfirm(widget.account.humanReadableID)),
confirmAction: confirmAction,
declineAction: declineAction,
);
}

Widget? subtitle;
final details = capabilities.details;
if (details != null) {
subtitle = Text(details);
} else if (capabilities.delay.enabled) {
subtitle = Text(
localizations.accountOptionsRemoveRemoteDelay(
Duration(hours: capabilities.delay.hours).formatRelative(
localizations,
includeSign: false,
),
),
);
}

return NeonDialog(
icon: icon,
title: Text(title),
content: SingleChildScrollView(
child: Column(
children: [
RadioListTile(
value: AccountDeletion.local,
groupValue: value,
onChanged: (value) => update(value!),
title: Text(localizations.accountOptionsRemoveLocal),
),
RadioListTile<AccountDeletion>(
value: AccountDeletion.remote,
groupValue: value,
onChanged: capabilities.enabled ? (value) => update(value!) : null,
title: Text(localizations.accountOptionsRemoveRemote),
subtitle: subtitle,
),
],
),
),
actions: [
declineAction,
confirmAction,
],
);
}
}

/// A [NeonDialog] to inform the user about the UnifiedPush feature of neon.
@internal
class NeonUnifiedPushDialog extends StatelessWidget {
Expand Down
3 changes: 2 additions & 1 deletion packages/neon_framework/lib/widgets.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export 'package:neon_framework/src/widgets/custom_background.dart';
export 'package:neon_framework/src/widgets/dialog.dart' hide NeonAccountSelectionDialog, NeonUnifiedPushDialog;
export 'package:neon_framework/src/widgets/dialog.dart'
hide AccountDeletion, NeonAccountDeletionDialog, NeonAccountSelectionDialog, NeonUnifiedPushDialog;
export 'package:neon_framework/src/widgets/error.dart';
export 'package:neon_framework/src/widgets/image.dart' hide NeonImage;
export 'package:neon_framework/src/widgets/linear_progress_indicator.dart';
Expand Down
Loading

0 comments on commit fda3bfe

Please sign in to comment.