Refractor accounts section to use common code from packages/accounts

This commit is contained in:
AmanRajSinghMourya
2025-08-01 11:26:20 +05:30
parent d99871a25a
commit 3dcf63c41a
29 changed files with 584 additions and 1741 deletions

View File

@@ -1,11 +0,0 @@
enum TwoFactorType { totp, passkey }
// ToString for TwoFactorType
String twoFactorTypeToString(TwoFactorType type) {
switch (type) {
case TwoFactorType.totp:
return "totp";
case TwoFactorType.passkey:
return "passkey";
}
}

View File

@@ -1,9 +0,0 @@
class DeleteChallengeResponse {
final bool allowDelete;
final String encryptedChallenge;
DeleteChallengeResponse({
required this.allowDelete,
required this.encryptedChallenge,
});
}

View File

@@ -1,45 +0,0 @@
class Sessions {
final List<Session> sessions;
Sessions(
this.sessions,
);
factory Sessions.fromMap(Map<String, dynamic> map) {
if (map["sessions"] == null) {
throw Exception('\'map["sessions"]\' must not be null');
}
return Sessions(
List<Session>.from(map['sessions']?.map((x) => Session.fromMap(x))),
);
}
}
class Session {
final String token;
final int creationTime;
final String ip;
final String ua;
final String prettyUA;
final int lastUsedTime;
Session(
this.token,
this.creationTime,
this.ip,
this.ua,
this.prettyUA,
this.lastUsedTime,
);
factory Session.fromMap(Map<String, dynamic> map) {
return Session(
map['token'],
map['creationTime'],
map['ip'],
map['ua'],
map['prettyUA'],
map['lastUsedTime'],
);
}
}

View File

@@ -1,25 +0,0 @@
class SetKeysRequest {
final String kekSalt;
final String encryptedKey;
final String keyDecryptionNonce;
final int memLimit;
final int opsLimit;
SetKeysRequest({
required this.kekSalt,
required this.encryptedKey,
required this.keyDecryptionNonce,
required this.memLimit,
required this.opsLimit,
});
Map<String, dynamic> toMap() {
return {
'kekSalt': kekSalt,
'encryptedKey': encryptedKey,
'keyDecryptionNonce': keyDecryptionNonce,
'memLimit': memLimit,
'opsLimit': opsLimit,
};
}
}

View File

@@ -1,22 +0,0 @@
class SetRecoveryKeyRequest {
final String masterKeyEncryptedWithRecoveryKey;
final String masterKeyDecryptionNonce;
final String recoveryKeyEncryptedWithMasterKey;
final String recoveryKeyDecryptionNonce;
SetRecoveryKeyRequest(
this.masterKeyEncryptedWithRecoveryKey,
this.masterKeyDecryptionNonce,
this.recoveryKeyEncryptedWithMasterKey,
this.recoveryKeyDecryptionNonce,
);
Map<String, dynamic> toMap() {
return {
'masterKeyEncryptedWithRecoveryKey': masterKeyEncryptedWithRecoveryKey,
'masterKeyDecryptionNonce': masterKeyDecryptionNonce,
'recoveryKeyEncryptedWithMasterKey': recoveryKeyEncryptedWithMasterKey,
'recoveryKeyDecryptionNonce': recoveryKeyDecryptionNonce,
};
}
}

View File

@@ -1,65 +0,0 @@
const freeProductID = "free";
const stripe = "stripe";
const appStore = "appstore";
const playStore = "playstore";
class Subscription {
final String productID;
final int storage;
final String originalTransactionID;
final String paymentProvider;
final int expiryTime;
final String price;
final String period;
final Attributes? attributes;
Subscription({
required this.productID,
required this.storage,
required this.originalTransactionID,
required this.paymentProvider,
required this.expiryTime,
required this.price,
required this.period,
this.attributes,
});
bool isValid() {
return expiryTime > DateTime.now().microsecondsSinceEpoch;
}
bool isYearlyPlan() {
return 'year' == period;
}
static fromMap(Map<String, dynamic>? map) {
if (map == null) return null;
return Subscription(
productID: map['productID'],
storage: map['storage'],
originalTransactionID: map['originalTransactionID'],
paymentProvider: map['paymentProvider'],
expiryTime: map['expiryTime'],
price: map['price'],
period: map['period'],
attributes: map["attributes"] != null
? Attributes.fromJson(map["attributes"])
: null,
);
}
}
class Attributes {
bool? isCancelled;
String? customerID;
Attributes({
this.isCancelled,
this.customerID,
});
Attributes.fromJson(dynamic json) {
isCancelled = json["isCancelled"];
customerID = json["customerID"];
}
}

View File

@@ -1,7 +0,0 @@
import 'dart:async';
typedef FutureVoidCallback = Future<void> Function();
typedef BoolCallBack = bool Function();
typedef FutureVoidCallbackParamStr = Future<void> Function(String);
typedef VoidCallbackParamStr = void Function(String);
typedef FutureOrVoidCallback = FutureOr<void> Function();

View File

@@ -1,146 +0,0 @@
import 'dart:convert';
import 'dart:math';
import 'package:collection/collection.dart';
import 'package:ente_auth/models/subscription.dart';
class UserDetails {
final String email;
final int usage;
final int fileCount;
final int sharedCollectionsCount;
final Subscription subscription;
final FamilyData? familyData;
final ProfileData? profileData;
UserDetails(
this.email,
this.usage,
this.fileCount,
this.sharedCollectionsCount,
this.subscription,
this.familyData,
this.profileData,
);
bool isPartOfFamily() {
return familyData?.members?.isNotEmpty ?? false;
}
bool isFamilyAdmin() {
assert(isPartOfFamily(), "verify user is part of family before calling");
final FamilyMember currentUserMember = familyData!.members!
.firstWhere((element) => element.email.trim() == email.trim());
return currentUserMember.isAdmin;
}
// getFamilyOrPersonalUsage will return total usage for family if user
// belong to family group. Otherwise, it will return storage consumed by
// current user
int getFamilyOrPersonalUsage() {
return isPartOfFamily() ? familyData!.getTotalUsage() : usage;
}
int getFreeStorage() {
return max(
isPartOfFamily()
? (familyData!.storage - familyData!.getTotalUsage())
: (subscription.storage - (usage)),
0,
);
}
int getTotalStorage() {
return isPartOfFamily() ? familyData!.storage : subscription.storage;
}
factory UserDetails.fromMap(Map<String, dynamic> map) {
return UserDetails(
map['email'] as String,
map['usage'] as int,
(map['fileCount'] ?? 0) as int,
(map['sharedCollectionsCount'] ?? 0) as int,
Subscription.fromMap(map['subscription']),
FamilyData.fromMap(map['familyData']),
ProfileData.fromJson(map['profileData']),
);
}
}
class FamilyMember {
final String email;
final int usage;
final String id;
final bool isAdmin;
FamilyMember(this.email, this.usage, this.id, this.isAdmin);
factory FamilyMember.fromMap(Map<String, dynamic> map) {
return FamilyMember(
(map['email'] ?? '') as String,
map['usage'] as int,
map['id'] as String,
map['isAdmin'] as bool,
);
}
}
class ProfileData {
bool canDisableEmailMFA;
bool isEmailMFAEnabled;
bool isTwoFactorEnabled;
// Constructor with default values
ProfileData({
this.canDisableEmailMFA = false,
this.isEmailMFAEnabled = false,
this.isTwoFactorEnabled = false,
});
// Factory method to create ProfileData instance from JSON
factory ProfileData.fromJson(Map<String, dynamic>? json) {
if (json == null) null;
return ProfileData(
canDisableEmailMFA: json!['canDisableEmailMFA'] ?? false,
isEmailMFAEnabled: json['isEmailMFAEnabled'] ?? false,
isTwoFactorEnabled: json['isTwoFactorEnabled'] ?? false,
);
}
// Method to convert ProfileData instance to JSON
Map<String, dynamic> toJson() {
return {
'canDisableEmailMFA': canDisableEmailMFA,
'isEmailMFAEnabled': isEmailMFAEnabled,
'isTwoFactorEnabled': isTwoFactorEnabled,
};
}
String toJsonString() => json.encode(toJson());
}
class FamilyData {
final List<FamilyMember>? members;
// Storage available based on the family plan
final int storage;
final int expiryTime;
FamilyData(this.members, this.storage, this.expiryTime);
int getTotalUsage() {
return members!.map((e) => e.usage).toList().sum;
}
static fromMap(Map<String, dynamic>? map) {
if (map == null) return null;
assert(map['members'] != null && map['members'].length >= 0);
final members = List<FamilyMember>.from(
map['members'].map((x) => FamilyMember.fromMap(x)),
);
return FamilyData(
members,
map['storage'] as int,
map['expiryTime'] as int,
);
}
}

View File

@@ -1,6 +1,7 @@
import 'dart:async';
import 'dart:io';
import 'package:ente_accounts/pages/email_entry_page.dart';
import 'package:ente_auth/app/view/app.dart';
import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/ente_theme_data.dart';
@@ -8,7 +9,6 @@ import 'package:ente_auth/events/trigger_logout_event.dart';
import "package:ente_auth/l10n/l10n.dart";
import 'package:ente_auth/locale.dart';
import 'package:ente_auth/theme/text_style.dart';
import 'package:ente_auth/ui/account/email_entry_page.dart';
import 'package:ente_auth/ui/account/login_page.dart';
import 'package:ente_auth/ui/account/logout_dialog.dart';
import 'package:ente_auth/ui/account/password_entry_page.dart';
@@ -260,7 +260,7 @@ class _OnboardingPageState extends State<OnboardingPage> {
void _navigateToSignUpPage() {
Widget page;
if (Configuration.instance.getEncryptedToken() == null) {
page = const EmailEntryPage();
page = EmailEntryPage(Configuration.instance);
} else {
// No key
if (Configuration.instance.getKeyAttributes() == null) {

View File

@@ -1,10 +1,10 @@
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:ente_accounts/ente_accounts.dart';
import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/core/errors.dart';
import 'package:ente_auth/models/billing_plan.dart';
import 'package:ente_auth/models/subscription.dart';
import 'package:ente_auth/models/billing_plan.dart';
import 'package:ente_network/network.dart';
import 'package:logging/logging.dart';

View File

@@ -4,17 +4,19 @@ import "dart:math";
import 'package:bip39/bip39.dart' as bip39;
import 'package:dio/dio.dart';
import 'package:ente_accounts/models/delete_account.dart';
import 'package:ente_accounts/models/sessions.dart';
import 'package:ente_accounts/models/set_keys_request.dart';
import 'package:ente_accounts/models/set_recovery_key_request.dart';
import 'package:ente_accounts/models/two_factor.dart';
import 'package:ente_accounts/models/user_details.dart';
import 'package:ente_accounts/pages/two_factor_authentication_page.dart';
import 'package:ente_accounts/pages/two_factor_recovery_page.dart';
import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/core/constants.dart';
import 'package:ente_auth/core/errors.dart';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/models/account/two_factor.dart';
import 'package:ente_auth/models/api/user/srp.dart';
import 'package:ente_auth/models/delete_account.dart';
import 'package:ente_auth/models/sessions.dart';
import 'package:ente_auth/models/set_keys_request.dart';
import 'package:ente_auth/models/set_recovery_key_request.dart';
import 'package:ente_auth/models/user_details.dart';
import 'package:ente_auth/models/api/user/srp.dart';
import 'package:ente_auth/ui/account/login_page.dart';
import 'package:ente_auth/ui/account/ott_verification_page.dart';
import 'package:ente_auth/ui/account/password_entry_page.dart';
@@ -23,8 +25,6 @@ import 'package:ente_auth/ui/account/recovery_page.dart';
import 'package:ente_auth/ui/common/progress_dialog.dart';
import 'package:ente_auth/ui/home_page.dart';
import 'package:ente_auth/ui/passkey_page.dart';
import 'package:ente_auth/ui/two_factor_authentication_page.dart';
import 'package:ente_auth/ui/two_factor_recovery_page.dart';
import 'package:ente_auth/utils/dialog_util.dart';
import 'package:ente_auth/utils/toast_util.dart';
import 'package:ente_base/models/key_attributes.dart';

View File

@@ -1,82 +0,0 @@
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/services/user_service.dart';
import 'package:ente_auth/utils/dialog_util.dart';
import 'package:ente_auth/utils/email_util.dart';
import 'package:flutter/material.dart';
class ChangeEmailDialog extends StatefulWidget {
const ChangeEmailDialog({super.key});
@override
State<ChangeEmailDialog> createState() => _ChangeEmailDialogState();
}
class _ChangeEmailDialogState extends State<ChangeEmailDialog> {
String _email = "";
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
return AlertDialog(
title: Text(l10n.enterNewEmailHint),
content: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextFormField(
decoration: InputDecoration(
hintText: l10n.email,
hintStyle: const TextStyle(
color: Colors.white30,
),
contentPadding: const EdgeInsets.all(12),
),
onChanged: (value) {
setState(() {
_email = value;
});
},
autocorrect: false,
keyboardType: TextInputType.emailAddress,
initialValue: _email,
autofocus: true,
),
],
),
),
actions: [
TextButton(
child: Text(
l10n.cancel,
style: const TextStyle(
color: Colors.redAccent,
),
),
onPressed: () {
Navigator.pop(context);
},
),
TextButton(
child: Text(
l10n.verify,
style: const TextStyle(
color: Colors.purple,
),
),
onPressed: () {
if (!isValidEmail(_email)) {
showErrorDialog(
context,
l10n.invalidEmailTitle,
l10n.invalidEmailMessage,
);
return;
}
UserService.instance.sendOtt(context, _email, isChangeEmail: true);
},
),
],
);
}
}

View File

@@ -1,251 +0,0 @@
import 'dart:convert';
import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/models/delete_account.dart';
import 'package:ente_auth/services/user_service.dart';
import 'package:ente_auth/ui/common/dialogs.dart';
import 'package:ente_auth/ui/common/gradient_button.dart';
import 'package:ente_auth/utils/email_util.dart';
import 'package:ente_auth/utils/platform_util.dart';
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
import 'package:ente_lock_screen/local_authentication_service.dart';
import 'package:flutter/material.dart';
class DeleteAccountPage extends StatelessWidget {
const DeleteAccountPage({
super.key,
});
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
return Scaffold(
appBar: AppBar(
elevation: 0,
title: Text(l10n.deleteAccount),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
color: Theme.of(context).iconTheme.color,
onPressed: () {
Navigator.of(context).pop();
},
),
),
body: Padding(
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 24),
child: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.asset(
'assets/broken_heart.png',
width: 200,
),
const SizedBox(
height: 24,
),
Center(
child: Text(
l10n.deleteAccountQuery,
style: Theme.of(context).textTheme.titleMedium,
),
),
const SizedBox(
height: 12,
),
RichText(
// textAlign: TextAlign.center,
text: TextSpan(
children: const [
TextSpan(text: "Please write to us at "),
TextSpan(
text: "feedback@ente.io",
style: TextStyle(color: Color.fromRGBO(29, 185, 84, 1)),
),
TextSpan(
text: ", maybe there is a way we can help.",
),
],
style: Theme.of(context).textTheme.titleMedium,
),
),
const SizedBox(
height: 24,
),
GradientButton(
text: l10n.yesSendFeedbackAction,
iconData: Icons.check,
onTap: () async {
await sendEmail(
context,
to: 'feedback@ente.io',
subject: '[Feedback]',
);
},
),
const Padding(
padding: EdgeInsets.symmetric(vertical: 8),
),
InkWell(
child: SizedBox(
width: double.infinity,
child: OutlinedButton.icon(
style: OutlinedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
side: const BorderSide(
color: Colors.redAccent,
),
padding: const EdgeInsets.symmetric(
vertical: 18,
horizontal: 10,
),
backgroundColor: Colors.white,
),
label: Text(
l10n.noDeleteAccountAction,
style: const TextStyle(
color: Colors.redAccent, // same for both themes
),
textAlign: TextAlign.center,
),
onPressed: () async => {await _initiateDelete(context)},
icon: const Icon(
Icons.no_accounts,
color: Colors.redAccent,
),
),
),
),
],
),
),
),
);
}
Future<void> _initiateDelete(BuildContext context) async {
final deleteChallengeResponse =
await UserService.instance.getDeleteChallenge(context);
if (deleteChallengeResponse == null) {
return;
}
if (deleteChallengeResponse.allowDelete) {
await _confirmAndDelete(context, deleteChallengeResponse);
} else {
await _requestEmailForDeletion(context);
}
}
Future<void> _confirmAndDelete(
BuildContext context,
DeleteChallengeResponse response,
) async {
final l10n = context.l10n;
final hasAuthenticated =
await LocalAuthenticationService.instance.requestLocalAuthentication(
context,
l10n.initiateAccountDeleteTitle,
);
await PlatformUtil.refocusWindows();
if (hasAuthenticated) {
final choice = await showChoiceDialogOld(
context,
l10n.confirmAccountDeleteTitle,
l10n.confirmAccountDeleteMessage,
firstAction: l10n.cancel,
secondAction: l10n.delete,
firstActionColor: Theme.of(context).colorScheme.onSurface,
secondActionColor: Colors.red,
);
if (choice != DialogUserChoice.secondChoice) {
return;
}
final decryptChallenge = CryptoUtil.openSealSync(
CryptoUtil.base642bin(response.encryptedChallenge),
CryptoUtil.base642bin(
Configuration.instance.getKeyAttributes()!.publicKey,
),
Configuration.instance.getSecretKey()!,
);
final challengeResponseStr = utf8.decode(decryptChallenge);
await UserService.instance.deleteAccount(context, challengeResponseStr);
}
}
Future<void> _requestEmailForDeletion(BuildContext context) async {
final l10n = context.l10n;
final AlertDialog alert = AlertDialog(
title: Text(
l10n.deleteAccount,
style: const TextStyle(
color: Colors.red,
),
),
content: RichText(
text: TextSpan(
children: [
const TextSpan(
text: "Please send an email to ",
),
TextSpan(
text: "account-deletion@ente.io",
style: TextStyle(
color: Colors.orange[300],
),
),
const TextSpan(
text:
" from your registered email address.\n\nYour request will be processed within 72 hours.",
),
],
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface,
height: 1.5,
fontSize: 16,
),
),
),
actions: [
TextButton(
child: Text(
l10n.sendEmail,
style: const TextStyle(
color: Colors.red,
),
),
onPressed: () async {
Navigator.of(context, rootNavigator: true).pop('dialog');
await sendEmail(
context,
to: 'account-deletion@ente.io',
subject: '[Delete account]',
);
},
),
TextButton(
child: Text(
l10n.ok,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface,
),
),
onPressed: () {
Navigator.of(context, rootNavigator: true).pop('dialog');
},
),
],
);
// ignore: unawaited_futures
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,228 +0,0 @@
import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/ente_theme_data.dart';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/models/sessions.dart';
import 'package:ente_auth/services/user_service.dart';
import 'package:ente_auth/ui/common/loading_widget.dart';
import 'package:ente_auth/utils/date_time_util.dart';
import 'package:ente_auth/utils/dialog_util.dart';
import 'package:ente_auth/utils/toast_util.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
class SessionsPage extends StatefulWidget {
const SessionsPage({super.key});
@override
State<SessionsPage> createState() => _SessionsPageState();
}
class _SessionsPageState extends State<SessionsPage> {
Sessions? _sessions;
final Logger _logger = Logger("SessionsPageState");
@override
void initState() {
_fetchActiveSessions().onError((error, stackTrace) {
showToast(context, "Failed to fetch active sessions");
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0,
title: Text(context.l10n.activeSessions),
),
body: _getBody(),
);
}
Widget _getBody() {
if (_sessions == null) {
return const Center(child: EnteLoadingWidget());
}
final List<Widget> rows = [];
rows.add(const Padding(padding: EdgeInsets.all(4)));
for (final session in _sessions!.sessions) {
rows.add(_getSessionWidget(session));
}
return SingleChildScrollView(
child: Column(
children: rows,
),
);
}
Widget _getSessionWidget(Session session) {
final lastUsedTime =
DateTime.fromMicrosecondsSinceEpoch(session.lastUsedTime);
return Column(
children: [
InkWell(
onTap: () async {
_showSessionTerminationDialog(session);
},
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_getUAWidget(session),
const Padding(padding: EdgeInsets.all(4)),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Text(
session.ip,
style: TextStyle(
color: Theme.of(context)
.colorScheme
.onSurface
.withValues(alpha: 0.8),
fontSize: 14,
),
),
),
const Padding(padding: EdgeInsets.all(8)),
Flexible(
child: Text(
getFormattedTime(lastUsedTime),
style: TextStyle(
color: Theme.of(context)
.colorScheme
.onSurface
.withValues(alpha: 0.8),
fontSize: 12,
),
),
),
],
),
],
),
),
),
const Divider(),
],
);
}
Future<void> _terminateSession(Session session) async {
final dialog = createProgressDialog(context, context.l10n.pleaseWait);
await dialog.show();
try {
await UserService.instance.terminateSession(session.token);
await _fetchActiveSessions();
await dialog.hide();
} catch (e) {
await dialog.hide();
_logger.severe('failed to terminate');
// ignore: unawaited_futures
showErrorDialog(
context,
context.l10n.oops,
context.l10n.somethingWentWrongPleaseTryAgain,
);
}
}
Future<void> _fetchActiveSessions() async {
_sessions = await UserService.instance.getActiveSessions().onError((e, s) {
_logger.severe("failed to fetch active sessions", e, s);
throw e!;
});
if (_sessions != null) {
_sessions!.sessions.sort((first, second) {
return second.lastUsedTime.compareTo(first.lastUsedTime);
});
if (mounted) {
setState(() {});
}
}
}
void _showSessionTerminationDialog(Session session) {
final isLoggingOutFromThisDevice =
session.token == Configuration.instance.getToken();
Widget text;
if (isLoggingOutFromThisDevice) {
text = Text(
context.l10n.thisWillLogYouOutOfThisDevice,
);
} else {
text = SingleChildScrollView(
child: Column(
children: [
Text(
context.l10n.thisWillLogYouOutOfTheFollowingDevice,
),
const Padding(padding: EdgeInsets.all(8)),
Text(
session.ua,
style: Theme.of(context).textTheme.bodySmall,
),
],
),
);
}
final AlertDialog alert = AlertDialog(
title: Text(context.l10n.terminateSession),
content: text,
actions: [
TextButton(
child: Text(
context.l10n.terminate,
style: const TextStyle(
color: Colors.red,
),
),
onPressed: () async {
Navigator.of(context, rootNavigator: true).pop('dialog');
if (isLoggingOutFromThisDevice) {
await UserService.instance.logout(context);
} else {
await _terminateSession(session);
}
},
),
TextButton(
child: Text(
context.l10n.cancel,
style: TextStyle(
color: isLoggingOutFromThisDevice
? Theme.of(context).colorScheme.alternativeColor
: Theme.of(context).colorScheme.defaultTextColor,
),
),
onPressed: () {
Navigator.of(context, rootNavigator: true).pop('dialog');
},
),
],
);
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
Widget _getUAWidget(Session session) {
if (session.token == Configuration.instance.getToken()) {
return Text(
context.l10n.thisDevice,
style: TextStyle(
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.alternativeColor,
),
);
}
return Text(session.prettyUA);
}
}

View File

@@ -1,9 +1,9 @@
import 'package:ente_auth/ente_theme_data.dart';
import 'package:ente_auth/models/typedefs.dart';
import 'package:ente_auth/ente_theme_data.dart';
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
import 'package:ente_auth/ui/components/dialog_widget.dart';
import 'package:ente_auth/ui/components/models/button_result.dart';
import 'package:ente_auth/ui/components/models/button_type.dart';
import 'package:ente_base/typedefs.dart';
import 'package:flutter/material.dart';
enum DialogUserChoice { firstChoice, secondChoice }

View File

@@ -1,5 +1,4 @@
import 'package:ente_auth/models/execution_states.dart';
import 'package:ente_auth/models/typedefs.dart';
import 'package:ente_auth/models/execution_states.dart';
import 'package:ente_auth/theme/colors.dart';
import 'package:ente_auth/theme/ente_theme.dart';
import 'package:ente_auth/theme/text_style.dart';
@@ -9,6 +8,7 @@ import 'package:ente_auth/ui/components/models/button_type.dart';
import 'package:ente_auth/ui/components/models/custom_button_style.dart';
import 'package:ente_auth/utils/debouncer.dart';
import "package:ente_auth/utils/dialog_util.dart";
import 'package:ente_base/typedefs.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';

View File

@@ -1,7 +1,6 @@
import 'dart:math';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/models/typedefs.dart';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/theme/colors.dart';
import 'package:ente_auth/theme/effects.dart';
import 'package:ente_auth/theme/ente_theme.dart';
@@ -11,6 +10,7 @@ import 'package:ente_auth/ui/components/models/button_result.dart';
import 'package:ente_auth/ui/components/models/button_type.dart';
import 'package:ente_auth/ui/components/separators.dart';
import 'package:ente_auth/ui/components/text_input_widget.dart';
import 'package:ente_base/typedefs.dart';
import 'package:flutter/material.dart';
///Will return null if dismissed by tapping outside

View File

@@ -1,8 +1,8 @@
import 'package:ente_auth/models/execution_states.dart';
import 'package:ente_auth/models/typedefs.dart';
import 'package:ente_auth/models/execution_states.dart';
import 'package:ente_auth/theme/ente_theme.dart';
import 'package:ente_auth/ui/components/menu_item_child_widgets.dart';
import 'package:ente_auth/utils/debouncer.dart';
import 'package:ente_base/typedefs.dart';
import 'package:expandable/expandable.dart';
import 'package:flutter/material.dart';

View File

@@ -1,9 +1,9 @@
import 'package:ente_auth/models/execution_states.dart';
import 'package:ente_auth/models/typedefs.dart';
import 'package:ente_auth/models/execution_states.dart';
import 'package:ente_auth/theme/ente_theme.dart';
import 'package:ente_auth/ui/common/loading_widget.dart';
import 'package:ente_auth/ui/components/separators.dart';
import 'package:ente_auth/utils/debouncer.dart';
import 'package:ente_base/typedefs.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:logging/logging.dart';

View File

@@ -1,9 +1,9 @@
import 'package:ente_auth/ente_theme_data.dart';
import 'package:ente_auth/models/execution_states.dart';
import 'package:ente_auth/models/typedefs.dart';
import 'package:ente_auth/models/execution_states.dart';
import 'package:ente_auth/theme/colors.dart';
import 'package:ente_auth/ui/common/loading_widget.dart';
import 'package:ente_auth/utils/debouncer.dart';
import 'package:ente_base/typedefs.dart';
import 'package:flutter/material.dart';
typedef OnChangedCallBack = void Function(bool);

View File

@@ -1,14 +1,14 @@
import 'dart:convert';
import 'package:app_links/app_links.dart';
import 'package:ente_accounts/models/two_factor.dart';
import 'package:ente_accounts/pages/two_factor_authentication_page.dart';
import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/core/errors.dart';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/models/account/two_factor.dart';
import 'package:ente_auth/services/user_service.dart';
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
import 'package:ente_auth/ui/components/models/button_type.dart';
import 'package:ente_auth/ui/two_factor_authentication_page.dart';
import 'package:ente_auth/utils/dialog_util.dart';
import 'package:ente_auth/utils/navigation_util.dart';
import 'package:ente_auth/utils/toast_util.dart';

View File

@@ -1,9 +1,9 @@
import 'package:ente_accounts/pages/change_email_dialog.dart';
import 'package:ente_accounts/pages/delete_account_page.dart';
import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/services/user_service.dart';
import 'package:ente_auth/theme/ente_theme.dart';
import 'package:ente_auth/ui/account/change_email_dialog.dart';
import 'package:ente_auth/ui/account/delete_account_page.dart';
import 'package:ente_auth/ui/account/password_entry_page.dart';
import 'package:ente_auth/ui/account/recovery_key_page.dart';
import 'package:ente_auth/ui/components/captioned_text_widget.dart';
@@ -151,8 +151,9 @@ class AccountSectionWidget extends StatelessWidget {
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async {
final config = Configuration.instance;
// ignore: unawaited_futures
routeToPage(context, const DeleteAccountPage());
routeToPage(context, DeleteAccountPage(config));
},
),
sectionOptionSpacing,

View File

@@ -1,8 +1,8 @@
import 'dart:io';
import 'package:ente_accounts/models/user_details.dart';
import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/models/user_details.dart';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/services/preference_service.dart';
import 'package:ente_auth/services/user_service.dart';
import 'package:ente_auth/ui/components/banner_widget.dart';

View File

@@ -1,22 +1,22 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:ente_accounts/models/user_details.dart';
import 'package:ente_accounts/pages/sessions_page.dart';
import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/models/user_details.dart';
import 'package:ente_auth/services/passkey_service.dart';
import 'package:ente_auth/services/user_service.dart';
import 'package:ente_auth/theme/ente_theme.dart';
import 'package:ente_auth/ui/account/request_pwd_verification_page.dart';
import 'package:ente_auth/ui/account/sessions_page.dart';
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
import 'package:ente_auth/ui/components/captioned_text_widget.dart';
import 'package:ente_auth/ui/components/expandable_menu_item_widget.dart';
import 'package:ente_auth/ui/components/menu_item_widget.dart';
import 'package:ente_auth/ui/components/models/button_result.dart';
import 'package:ente_auth/ui/components/toggle_switch_widget.dart';
import 'package:ente_auth/ui/settings/common_settings.dart';
import 'package:ente_auth/utils/dialog_util.dart';
import 'package:ente_auth/ui/settings/common_settings.dart';
import 'package:ente_auth/utils/dialog_util.dart';
import 'package:ente_auth/utils/navigation_util.dart';
import 'package:ente_auth/utils/platform_util.dart';
import 'package:ente_auth/utils/toast_util.dart';
@@ -128,7 +128,7 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) {
return const SessionsPage();
return SessionsPage(Configuration.instance);
},
),
);

View File

@@ -1,161 +0,0 @@
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/models/account/two_factor.dart';
import 'package:ente_auth/services/user_service.dart';
import 'package:ente_auth/ui/lifecycle_event_handler.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:pinput/pinput.dart';
class TwoFactorAuthenticationPage extends StatefulWidget {
final String sessionID;
const TwoFactorAuthenticationPage(this.sessionID, {super.key});
@override
State<TwoFactorAuthenticationPage> createState() =>
_TwoFactorAuthenticationPageState();
}
class _TwoFactorAuthenticationPageState
extends State<TwoFactorAuthenticationPage> {
final _pinController = TextEditingController();
final _pinPutDecoration = PinTheme(
height: 45,
width: 45,
decoration: BoxDecoration(
border: Border.all(color: const Color.fromRGBO(45, 194, 98, 1.0)),
borderRadius: BorderRadius.circular(15.0),
),
);
String _code = "";
late LifecycleEventHandler _lifecycleEventHandler;
@override
void initState() {
_lifecycleEventHandler = LifecycleEventHandler(
resumeCallBack: () async {
if (mounted) {
final data = await Clipboard.getData(Clipboard.kTextPlain);
if (data != null && data.text != null && data.text!.length == 6) {
_pinController.text = data.text!;
}
}
},
);
WidgetsBinding.instance.addObserver(_lifecycleEventHandler);
super.initState();
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(_lifecycleEventHandler);
super.dispose();
}
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
return Scaffold(
appBar: AppBar(
title: Text(
l10n.twoFactorAuthTitle,
),
),
body: _getBody(),
);
}
Widget _getBody() {
final l10n = context.l10n;
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: [
Text(
l10n.enterCodeHint,
style: const TextStyle(
height: 1.4,
fontSize: 16,
),
textAlign: TextAlign.center,
),
const Padding(padding: EdgeInsets.all(32)),
Padding(
padding: const EdgeInsets.fromLTRB(40, 0, 40, 0),
child: Pinput(
length: 6,
onCompleted: (String code) {
_verifyTwoFactorCode(code);
},
onChanged: (String pin) {
setState(() {
_code = pin;
});
},
controller: _pinController,
submittedPinTheme: _pinPutDecoration.copyWith(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.0),
border: Border.all(
color: const Color.fromRGBO(45, 194, 98, 0.5),
),
),
),
defaultPinTheme: _pinPutDecoration,
followingPinTheme: _pinPutDecoration.copyWith(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.0),
border: Border.all(
color: const Color.fromRGBO(45, 194, 98, 0.5),
),
),
),
autofocus: true,
),
),
const Padding(padding: EdgeInsets.all(24)),
Container(
padding: const EdgeInsets.fromLTRB(80, 0, 80, 0),
width: double.infinity,
height: 64,
child: OutlinedButton(
onPressed: _code.length == 6
? () async {
await _verifyTwoFactorCode(_code);
}
: null,
child: Text(l10n.verify),
),
),
const Padding(padding: EdgeInsets.all(30)),
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
UserService.instance.recoverTwoFactor(
context,
widget.sessionID,
TwoFactorType.totp,
);
},
child: Container(
padding: const EdgeInsets.all(10),
child: Center(
child: Text(
l10n.lostDeviceTitle,
style: const TextStyle(
decoration: TextDecoration.underline,
fontSize: 12,
),
),
),
),
),
],
);
}
Future<void> _verifyTwoFactorCode(String code) async {
await UserService.instance.verifyTwoFactor(context, widget.sessionID, code);
}
}

View File

@@ -1,113 +0,0 @@
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/models/account/two_factor.dart';
import 'package:ente_auth/services/user_service.dart';
import 'package:ente_auth/theme/ente_theme.dart';
import 'package:ente_auth/utils/email_util.dart';
import 'package:flutter/material.dart';
class TwoFactorRecoveryPage extends StatefulWidget {
final String sessionID;
final String encryptedSecret;
final String secretDecryptionNonce;
final TwoFactorType type;
const TwoFactorRecoveryPage(
this.type,
this.sessionID,
this.encryptedSecret,
this.secretDecryptionNonce, {
super.key,
});
@override
State<TwoFactorRecoveryPage> createState() => _TwoFactorRecoveryPageState();
}
class _TwoFactorRecoveryPageState extends State<TwoFactorRecoveryPage> {
final _recoveryKey = TextEditingController();
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
return Scaffold(
appBar: AppBar(
title: Text(
l10n.recoverAccount,
style: const TextStyle(
fontSize: 18,
),
),
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(60, 0, 60, 0),
child: TextFormField(
decoration: InputDecoration(
hintText: l10n.enterRecoveryKeyHint,
contentPadding: const EdgeInsets.all(20),
),
style: const TextStyle(
fontSize: 14,
fontFeatures: [FontFeature.tabularFigures()],
),
controller: _recoveryKey,
autofocus: false,
autocorrect: false,
keyboardType: TextInputType.multiline,
maxLines: null,
onChanged: (_) {
setState(() {});
},
),
),
const Padding(padding: EdgeInsets.all(24)),
Container(
padding: const EdgeInsets.fromLTRB(80, 0, 80, 0),
width: double.infinity,
height: 64,
child: OutlinedButton(
onPressed: _recoveryKey.text.isNotEmpty
? () async {
await UserService.instance.removeTwoFactor(
context,
widget.type,
widget.sessionID,
_recoveryKey.text,
widget.encryptedSecret,
widget.secretDecryptionNonce,
);
}
: null,
child: Text(l10n.recover),
),
),
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () async {
await openSupportPage(null, null);
},
child: Container(
padding: const EdgeInsets.all(40),
child: Center(
child: Text(
l10n.noRecoveryKeyTitle,
style: TextStyle(
decoration: TextDecoration.underline,
fontSize: 12,
color: getEnteColorScheme(context)
.textBase
.withValues(alpha: 0.9),
),
),
),
),
),
],
),
);
}
}

View File

@@ -3,8 +3,7 @@ import 'dart:math';
import 'package:confetti/confetti.dart';
import "package:dio/dio.dart";
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/models/typedefs.dart';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/theme/colors.dart';
import 'package:ente_auth/ui/common/loading_widget.dart';
import 'package:ente_auth/ui/common/progress_dialog.dart';
@@ -16,6 +15,7 @@ import 'package:ente_auth/ui/components/models/button_result.dart';
import 'package:ente_auth/ui/components/models/button_type.dart';
import 'package:ente_auth/utils/email_util.dart';
import 'package:ente_auth/utils/platform_util.dart';
import 'package:ente_base/typedefs.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

View File

@@ -337,19 +337,21 @@ packages:
flutter_inappwebview:
dependency: transitive
description:
name: flutter_inappwebview
sha256: "80092d13d3e29b6227e25b67973c67c7210bd5e35c4b747ca908e31eb71a46d5"
url: "https://pub.dev"
source: hosted
version: "6.1.5"
path: flutter_inappwebview
ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
resolved-ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
url: "https://github.com/pichillilorenzo/flutter_inappwebview.git"
source: git
version: "6.2.0-beta.3"
flutter_inappwebview_android:
dependency: transitive
description:
name: flutter_inappwebview_android
sha256: "62557c15a5c2db5d195cb3892aab74fcaec266d7b86d59a6f0027abd672cddba"
url: "https://pub.dev"
source: hosted
version: "1.1.3"
path: flutter_inappwebview_android
ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
resolved-ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
url: "https://github.com/pichillilorenzo/flutter_inappwebview.git"
source: git
version: "1.2.0-beta.3"
flutter_inappwebview_internal_annotations:
dependency: transitive
description:
@@ -361,43 +363,48 @@ packages:
flutter_inappwebview_ios:
dependency: transitive
description:
name: flutter_inappwebview_ios
sha256: "5818cf9b26cf0cbb0f62ff50772217d41ea8d3d9cc00279c45f8aabaa1b4025d"
url: "https://pub.dev"
source: hosted
version: "1.1.2"
path: flutter_inappwebview_ios
ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
resolved-ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
url: "https://github.com/pichillilorenzo/flutter_inappwebview.git"
source: git
version: "1.2.0-beta.3"
flutter_inappwebview_macos:
dependency: transitive
description:
name: flutter_inappwebview_macos
sha256: c1fbb86af1a3738e3541364d7d1866315ffb0468a1a77e34198c9be571287da1
url: "https://pub.dev"
source: hosted
version: "1.1.2"
path: flutter_inappwebview_macos
ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
resolved-ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
url: "https://github.com/pichillilorenzo/flutter_inappwebview.git"
source: git
version: "1.2.0-beta.3"
flutter_inappwebview_platform_interface:
dependency: transitive
description:
name: flutter_inappwebview_platform_interface
sha256: cf5323e194096b6ede7a1ca808c3e0a078e4b33cc3f6338977d75b4024ba2500
url: "https://pub.dev"
source: hosted
version: "1.3.0+1"
path: flutter_inappwebview_platform_interface
ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
resolved-ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
url: "https://github.com/pichillilorenzo/flutter_inappwebview.git"
source: git
version: "1.4.0-beta.3"
flutter_inappwebview_web:
dependency: transitive
description:
name: flutter_inappwebview_web
sha256: "55f89c83b0a0d3b7893306b3bb545ba4770a4df018204917148ebb42dc14a598"
url: "https://pub.dev"
source: hosted
version: "1.1.2"
path: flutter_inappwebview_web
ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
resolved-ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
url: "https://github.com/pichillilorenzo/flutter_inappwebview.git"
source: git
version: "1.2.0-beta.3"
flutter_inappwebview_windows:
dependency: transitive
description:
name: flutter_inappwebview_windows
sha256: "8b4d3a46078a2cdc636c4a3d10d10f2a16882f6be607962dbfff8874d1642055"
url: "https://pub.dev"
source: hosted
version: "0.6.0"
path: flutter_inappwebview_windows
ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
resolved-ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
url: "https://github.com/pichillilorenzo/flutter_inappwebview.git"
source: git
version: "0.7.0-beta.3"
flutter_lints:
dependency: "direct dev"
description: