mirror of
https://github.com/ente-io/ente.git
synced 2025-05-03 12:12:28 +00:00
558 lines
22 KiB
Dart
558 lines
22 KiB
Dart
import "dart:async";
|
|
|
|
import "package:flutter/foundation.dart";
|
|
import 'package:flutter/material.dart';
|
|
import "package:flutter_svg/flutter_svg.dart";
|
|
import 'package:photos/core/configuration.dart';
|
|
import "package:photos/emergency/emergency_service.dart";
|
|
import "package:photos/emergency/model.dart";
|
|
import "package:photos/emergency/other_contact_page.dart";
|
|
import "package:photos/emergency/select_contact_page.dart";
|
|
import "package:photos/generated/l10n.dart";
|
|
import "package:photos/l10n/l10n.dart";
|
|
import "package:photos/theme/colors.dart";
|
|
import 'package:photos/theme/ente_theme.dart';
|
|
import "package:photos/ui/common/loading_widget.dart";
|
|
import "package:photos/ui/components/action_sheet_widget.dart";
|
|
import "package:photos/ui/components/buttons/button_widget.dart";
|
|
import 'package:photos/ui/components/captioned_text_widget.dart';
|
|
import 'package:photos/ui/components/divider_widget.dart';
|
|
import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart';
|
|
import 'package:photos/ui/components/menu_section_title.dart';
|
|
import "package:photos/ui/components/models/button_type.dart";
|
|
import "package:photos/ui/components/notification_widget.dart";
|
|
import 'package:photos/ui/components/title_bar_title_widget.dart';
|
|
import 'package:photos/ui/components/title_bar_widget.dart';
|
|
import "package:photos/ui/sharing/user_avator_widget.dart";
|
|
import "package:photos/utils/navigation_util.dart";
|
|
import "package:photos/utils/toast_util.dart";
|
|
|
|
class EmergencyPage extends StatefulWidget {
|
|
const EmergencyPage({
|
|
super.key,
|
|
});
|
|
|
|
@override
|
|
State<EmergencyPage> createState() => _EmergencyPageState();
|
|
}
|
|
|
|
class _EmergencyPageState extends State<EmergencyPage> {
|
|
late int currentUserID;
|
|
EmergencyInfo? info;
|
|
|
|
bool hasTrustedContact = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
currentUserID = Configuration.instance.getUserID()!;
|
|
// set info to null after 5 second
|
|
Future.delayed(
|
|
const Duration(seconds: 0),
|
|
() async {
|
|
unawaited(_fetchData());
|
|
},
|
|
);
|
|
}
|
|
|
|
Future<void> _fetchData() async {
|
|
try {
|
|
final result = await EmergencyContactService.instance.getInfo();
|
|
if (mounted) {
|
|
setState(() {
|
|
info = result;
|
|
if (info != null) {
|
|
hasTrustedContact = info!.contacts.isNotEmpty;
|
|
}
|
|
});
|
|
}
|
|
} catch (e) {
|
|
showShortToast(
|
|
context,
|
|
S.of(context).somethingWentWrong,
|
|
);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final colorScheme = getEnteColorScheme(context);
|
|
final currentUserID = Configuration.instance.getUserID()!;
|
|
final List<EmergencyContact> othersTrustedContacts =
|
|
info?.othersEmergencyContact ?? [];
|
|
final List<EmergencyContact> trustedContacts = info?.contacts ?? [];
|
|
|
|
return Scaffold(
|
|
body: CustomScrollView(
|
|
primary: false,
|
|
slivers: <Widget>[
|
|
TitleBarWidget(
|
|
flexibleSpaceTitle: TitleBarTitleWidget(
|
|
title: S.of(context).legacy,
|
|
),
|
|
),
|
|
if (info == null)
|
|
const SliverFillRemaining(
|
|
hasScrollBody: false,
|
|
child: Center(
|
|
child: EnteLoadingWidget(),
|
|
),
|
|
),
|
|
if (info != null)
|
|
if (info!.recoverSessions.isNotEmpty)
|
|
SliverPadding(
|
|
padding: const EdgeInsets.only(
|
|
top: 20,
|
|
left: 16,
|
|
right: 16,
|
|
),
|
|
sliver: SliverList(
|
|
delegate: SliverChildBuilderDelegate(
|
|
(context, index) {
|
|
if (index == 0) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: 16.0),
|
|
child: NotificationWidget(
|
|
startIcon: Icons.warning_amber_rounded,
|
|
text: context.l10n.recoveryWarning,
|
|
actionIcon: null,
|
|
onTap: () {},
|
|
),
|
|
);
|
|
}
|
|
final RecoverySessions recoverSession =
|
|
info!.recoverSessions[index - 1];
|
|
return MenuItemWidget(
|
|
captionedTextWidget: CaptionedTextWidget(
|
|
title: recoverSession.emergencyContact.email,
|
|
makeTextBold: recoverSession.status.isNotEmpty,
|
|
textColor: colorScheme.warning500,
|
|
),
|
|
leadingIconWidget: UserAvatarWidget(
|
|
recoverSession.emergencyContact,
|
|
currentUserID: currentUserID,
|
|
),
|
|
leadingIconSize: 24,
|
|
menuItemColor: colorScheme.fillFaint,
|
|
singleBorderRadius: 8,
|
|
trailingIcon: Icons.chevron_right,
|
|
onTap: () async {
|
|
await showRejectRecoveryDialog(recoverSession);
|
|
},
|
|
);
|
|
},
|
|
childCount: 1 + info!.recoverSessions.length,
|
|
),
|
|
),
|
|
),
|
|
if (info != null)
|
|
SliverPadding(
|
|
padding: const EdgeInsets.only(
|
|
top: 16,
|
|
left: 16,
|
|
right: 16,
|
|
bottom: 8,
|
|
),
|
|
sliver: SliverList(
|
|
delegate: SliverChildBuilderDelegate(
|
|
(context, index) {
|
|
if (index == 0 && trustedContacts.isNotEmpty) {
|
|
return MenuSectionTitle(
|
|
title: S.of(context).trustedContacts,
|
|
);
|
|
} else if (index > 0 && index <= trustedContacts.length) {
|
|
final listIndex = index - 1;
|
|
final contact = trustedContacts[listIndex];
|
|
return Column(
|
|
children: [
|
|
MenuItemWidget(
|
|
captionedTextWidget: CaptionedTextWidget(
|
|
title: contact.emergencyContact.email,
|
|
subTitle: contact.isPendingInvite() ? "⚠" : null,
|
|
makeTextBold: contact.isPendingInvite(),
|
|
),
|
|
leadingIconSize: 24.0,
|
|
surfaceExecutionStates: false,
|
|
alwaysShowSuccessState: false,
|
|
leadingIconWidget: UserAvatarWidget(
|
|
contact.emergencyContact,
|
|
type: AvatarType.mini,
|
|
currentUserID: currentUserID,
|
|
),
|
|
menuItemColor:
|
|
getEnteColorScheme(context).fillFaint,
|
|
trailingIcon: Icons.chevron_right,
|
|
trailingIconIsMuted: true,
|
|
onTap: () async {
|
|
await showRevokeOrRemoveDialog(context, contact);
|
|
},
|
|
isTopBorderRadiusRemoved: listIndex > 0,
|
|
isBottomBorderRadiusRemoved: true,
|
|
singleBorderRadius: 8,
|
|
),
|
|
DividerWidget(
|
|
dividerType: DividerType.menu,
|
|
bgColor: getEnteColorScheme(context).fillFaint,
|
|
),
|
|
],
|
|
);
|
|
} else if (index == (1 + trustedContacts.length)) {
|
|
if (trustedContacts.isEmpty) {
|
|
return Column(
|
|
children: [
|
|
const SizedBox(height: 20),
|
|
Text(
|
|
context.l10n.legacyPageDesc,
|
|
style: getEnteTextTheme(context).body,
|
|
),
|
|
SizedBox(
|
|
height: 200,
|
|
width: 200,
|
|
child: SvgPicture.asset(
|
|
getEnteColorScheme(context).backdropBase ==
|
|
backgroundBaseDark
|
|
? "assets/icons/legacy-light.svg"
|
|
: "assets/icons/legacy-dark.svg",
|
|
width: 156,
|
|
height: 152,
|
|
),
|
|
),
|
|
Text(
|
|
context.l10n.legacyPageDesc2,
|
|
style: getEnteTextTheme(context).smallMuted,
|
|
),
|
|
const SizedBox(height: 16),
|
|
ButtonWidget(
|
|
buttonType: ButtonType.primary,
|
|
labelText: S.of(context).addTrustedContact,
|
|
shouldSurfaceExecutionStates: false,
|
|
onTap: () async {
|
|
await routeToPage(
|
|
context,
|
|
AddContactPage(info!),
|
|
forceCustomPageRoute: true,
|
|
);
|
|
unawaited(_fetchData());
|
|
},
|
|
),
|
|
],
|
|
);
|
|
}
|
|
return MenuItemWidget(
|
|
captionedTextWidget: CaptionedTextWidget(
|
|
title: trustedContacts.isNotEmpty
|
|
? S.of(context).addMore
|
|
: S.of(context).addTrustedContact,
|
|
makeTextBold: true,
|
|
),
|
|
leadingIcon: Icons.add_outlined,
|
|
surfaceExecutionStates: false,
|
|
menuItemColor: getEnteColorScheme(context).fillFaint,
|
|
onTap: () async {
|
|
await routeToPage(
|
|
context,
|
|
AddContactPage(info!),
|
|
forceCustomPageRoute: true,
|
|
);
|
|
unawaited(_fetchData());
|
|
},
|
|
isTopBorderRadiusRemoved: trustedContacts.isNotEmpty,
|
|
singleBorderRadius: 8,
|
|
);
|
|
}
|
|
return const SizedBox.shrink();
|
|
},
|
|
childCount: 1 + trustedContacts.length + 1,
|
|
),
|
|
),
|
|
),
|
|
if (info != null && info!.othersEmergencyContact.isNotEmpty)
|
|
SliverPadding(
|
|
padding: const EdgeInsets.only(top: 0, left: 16, right: 16),
|
|
sliver: SliverList(
|
|
delegate: SliverChildBuilderDelegate(
|
|
(context, index) {
|
|
if (index == 0 && (othersTrustedContacts.isNotEmpty)) {
|
|
return Column(
|
|
children: [
|
|
const Padding(
|
|
padding: EdgeInsets.symmetric(vertical: 8),
|
|
child: DividerWidget(
|
|
dividerType: DividerType.solid,
|
|
),
|
|
),
|
|
MenuSectionTitle(
|
|
title: context.l10n.legacyAccounts,
|
|
),
|
|
],
|
|
);
|
|
} else if (index > 0 &&
|
|
index <= othersTrustedContacts.length) {
|
|
final listIndex = index - 1;
|
|
final currentUser = othersTrustedContacts[listIndex];
|
|
final isLastItem = index == othersTrustedContacts.length;
|
|
return Column(
|
|
children: [
|
|
MenuItemWidget(
|
|
captionedTextWidget: CaptionedTextWidget(
|
|
title: currentUser.user.email,
|
|
makeTextBold: currentUser.isPendingInvite(),
|
|
subTitle:
|
|
currentUser.isPendingInvite() ? "⚠" : null,
|
|
),
|
|
leadingIconSize: 24.0,
|
|
leadingIconWidget: UserAvatarWidget(
|
|
currentUser.user,
|
|
type: AvatarType.mini,
|
|
currentUserID: currentUserID,
|
|
),
|
|
menuItemColor:
|
|
getEnteColorScheme(context).fillFaint,
|
|
trailingIcon: Icons.chevron_right,
|
|
trailingIconIsMuted: true,
|
|
onTap: () async {
|
|
if (currentUser.isPendingInvite()) {
|
|
await showAcceptOrDeclineDialog(
|
|
context,
|
|
currentUser,
|
|
);
|
|
} else {
|
|
await Navigator.of(context).push(
|
|
MaterialPageRoute(
|
|
builder: (BuildContext context) {
|
|
return OtherContactPage(
|
|
contact: currentUser,
|
|
emergencyInfo: info!,
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
// await routeToPage(
|
|
// context,
|
|
// OtherContactPage(
|
|
// contact: currentUser,
|
|
// emergencyInfo: info!,
|
|
// ),
|
|
// );
|
|
if (mounted) {
|
|
unawaited(_fetchData());
|
|
}
|
|
}
|
|
},
|
|
isTopBorderRadiusRemoved: listIndex > 0,
|
|
isBottomBorderRadiusRemoved: !isLastItem,
|
|
singleBorderRadius: 8,
|
|
surfaceExecutionStates: false,
|
|
),
|
|
isLastItem
|
|
? const SizedBox.shrink()
|
|
: DividerWidget(
|
|
dividerType: DividerType.menu,
|
|
bgColor:
|
|
getEnteColorScheme(context).fillFaint,
|
|
),
|
|
],
|
|
);
|
|
}
|
|
return const SizedBox.shrink();
|
|
},
|
|
childCount: 1 + othersTrustedContacts.length + 1,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<void> showRevokeOrRemoveDialog(
|
|
BuildContext context,
|
|
EmergencyContact contact,
|
|
) async {
|
|
if (contact.isPendingInvite()) {
|
|
await showActionSheet(
|
|
context: context,
|
|
body:
|
|
"You have invited ${contact.emergencyContact.email} to be a trusted contact",
|
|
bodyHighlight: "They are yet to accept your invite",
|
|
buttons: [
|
|
ButtonWidget(
|
|
labelText: S.of(context).removeInvite,
|
|
buttonType: ButtonType.critical,
|
|
buttonSize: ButtonSize.large,
|
|
buttonAction: ButtonAction.first,
|
|
shouldStickToDarkTheme: true,
|
|
shouldSurfaceExecutionStates: true,
|
|
shouldShowSuccessConfirmation: false,
|
|
onTap: () async {
|
|
await EmergencyContactService.instance
|
|
.updateContact(contact, ContactState.userRevokedContact);
|
|
info?.contacts.remove(contact);
|
|
if (mounted) {
|
|
setState(() {});
|
|
unawaited(_fetchData());
|
|
}
|
|
},
|
|
isInAlert: true,
|
|
),
|
|
ButtonWidget(
|
|
labelText: S.of(context).cancel,
|
|
buttonType: ButtonType.tertiary,
|
|
buttonSize: ButtonSize.large,
|
|
buttonAction: ButtonAction.second,
|
|
shouldStickToDarkTheme: true,
|
|
isInAlert: true,
|
|
),
|
|
],
|
|
);
|
|
} else {
|
|
await showActionSheet(
|
|
context: context,
|
|
body:
|
|
"You have added ${contact.emergencyContact.email} as a trusted contact",
|
|
bodyHighlight: "They have accepted your invite",
|
|
buttons: [
|
|
ButtonWidget(
|
|
labelText: S.of(context).remove,
|
|
buttonType: ButtonType.critical,
|
|
buttonSize: ButtonSize.large,
|
|
buttonAction: ButtonAction.second,
|
|
shouldStickToDarkTheme: true,
|
|
shouldSurfaceExecutionStates: true,
|
|
shouldShowSuccessConfirmation: false,
|
|
onTap: () async {
|
|
await EmergencyContactService.instance
|
|
.updateContact(contact, ContactState.userRevokedContact);
|
|
info?.contacts.remove(contact);
|
|
if (mounted) {
|
|
setState(() {});
|
|
unawaited(_fetchData());
|
|
}
|
|
},
|
|
isInAlert: true,
|
|
),
|
|
ButtonWidget(
|
|
labelText: S.of(context).cancel,
|
|
buttonType: ButtonType.tertiary,
|
|
buttonSize: ButtonSize.large,
|
|
buttonAction: ButtonAction.third,
|
|
shouldStickToDarkTheme: true,
|
|
isInAlert: true,
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<void> showAcceptOrDeclineDialog(
|
|
BuildContext context,
|
|
EmergencyContact contact,
|
|
) async {
|
|
await showActionSheet(
|
|
context: context,
|
|
buttons: [
|
|
ButtonWidget(
|
|
labelText: S.of(context).acceptTrustInvite,
|
|
buttonType: ButtonType.primary,
|
|
buttonSize: ButtonSize.large,
|
|
shouldStickToDarkTheme: true,
|
|
buttonAction: ButtonAction.first,
|
|
onTap: () async {
|
|
await EmergencyContactService.instance
|
|
.updateContact(contact, ContactState.contactAccepted);
|
|
final updatedContact =
|
|
contact.copyWith(state: ContactState.contactAccepted);
|
|
info?.othersEmergencyContact.remove(contact);
|
|
info?.othersEmergencyContact.add(updatedContact);
|
|
if (mounted) {
|
|
setState(() {});
|
|
}
|
|
},
|
|
isInAlert: true,
|
|
),
|
|
ButtonWidget(
|
|
labelText: S.of(context).declineTrustInvite,
|
|
buttonType: ButtonType.critical,
|
|
buttonSize: ButtonSize.large,
|
|
buttonAction: ButtonAction.second,
|
|
shouldStickToDarkTheme: true,
|
|
onTap: () async {
|
|
await EmergencyContactService.instance
|
|
.updateContact(contact, ContactState.contactDenied);
|
|
info?.othersEmergencyContact.remove(contact);
|
|
if (mounted) {
|
|
setState(() {});
|
|
}
|
|
},
|
|
isInAlert: true,
|
|
),
|
|
ButtonWidget(
|
|
labelText: S.of(context).cancel,
|
|
buttonType: ButtonType.tertiary,
|
|
buttonSize: ButtonSize.large,
|
|
buttonAction: ButtonAction.third,
|
|
shouldStickToDarkTheme: true,
|
|
isInAlert: true,
|
|
),
|
|
],
|
|
body: S.of(context).legacyInvite(contact.user.email),
|
|
actionSheetType: ActionSheetType.defaultActionSheet,
|
|
);
|
|
return;
|
|
}
|
|
|
|
Future<void> showRejectRecoveryDialog(RecoverySessions session) async {
|
|
final String emergencyContactEmail = session.emergencyContact.email;
|
|
await showActionSheet(
|
|
context: context,
|
|
buttons: [
|
|
ButtonWidget(
|
|
labelText: context.l10n.rejectRecovery,
|
|
buttonSize: ButtonSize.large,
|
|
shouldStickToDarkTheme: true,
|
|
buttonType: ButtonType.critical,
|
|
buttonAction: ButtonAction.first,
|
|
onTap: () async {
|
|
await EmergencyContactService.instance.rejectRecovery(session);
|
|
info?.recoverSessions
|
|
.removeWhere((element) => element.id == session.id);
|
|
if (mounted) {
|
|
setState(() {});
|
|
}
|
|
unawaited(_fetchData());
|
|
},
|
|
isInAlert: true,
|
|
),
|
|
if (kDebugMode)
|
|
ButtonWidget(
|
|
labelText: "Approve recovery (to be removed)",
|
|
buttonType: ButtonType.primary,
|
|
buttonSize: ButtonSize.large,
|
|
buttonAction: ButtonAction.second,
|
|
shouldStickToDarkTheme: true,
|
|
onTap: () async {
|
|
await EmergencyContactService.instance.approveRecovery(session);
|
|
if (mounted) {
|
|
setState(() {});
|
|
}
|
|
unawaited(_fetchData());
|
|
},
|
|
isInAlert: true,
|
|
),
|
|
ButtonWidget(
|
|
labelText: S.of(context).cancel,
|
|
buttonType: ButtonType.tertiary,
|
|
buttonSize: ButtonSize.large,
|
|
buttonAction: ButtonAction.third,
|
|
shouldStickToDarkTheme: true,
|
|
isInAlert: true,
|
|
),
|
|
],
|
|
body: context.l10n.recoveryWarningBody(emergencyContactEmail),
|
|
actionSheetType: ActionSheetType.defaultActionSheet,
|
|
);
|
|
return;
|
|
}
|
|
}
|