✨ Account contact settings
This commit is contained in:
parent
20e6cc4283
commit
723e17ff47
@ -357,5 +357,44 @@
|
|||||||
"typingHint": {
|
"typingHint": {
|
||||||
"one": "{} is typing...",
|
"one": "{} is typing...",
|
||||||
"other": "{} are typing..."
|
"other": "{} are typing..."
|
||||||
}
|
},
|
||||||
|
"settingsAppearance": "Appearance",
|
||||||
|
"settingsServer": "Server",
|
||||||
|
"settingsBehavior": "Behavior",
|
||||||
|
"settingsDesktop": "Desktop",
|
||||||
|
"settingsKeyboardShortcuts": "Keyboard Shortcuts",
|
||||||
|
"settingsEnterToSendDesktopHint": "Press Enter to send messages, use Shift+Enter for new line.",
|
||||||
|
"settingsHelp": "Settings Help",
|
||||||
|
"settingsHelpContent": "This page allows you to manage your app settings, appearance, and behavior. If you need assistance, please contact support.",
|
||||||
|
"settingsKeyboardShortcutSearch": "Search",
|
||||||
|
"settingsKeyboardShortcutSettings": "Settings",
|
||||||
|
"settingsKeyboardShortcutNewMessage": "New Message",
|
||||||
|
"settingsKeyboardShortcutCloseDialog": "Close Dialog",
|
||||||
|
"close": "Close",
|
||||||
|
"contactMethod": "Contact Method",
|
||||||
|
"contactMethodType": "Contact Type",
|
||||||
|
"contactMethodTypeEmail": "Email",
|
||||||
|
"contactMethodTypePhone": "Phone",
|
||||||
|
"contactMethodTypeAddress": "Address",
|
||||||
|
"contactMethodEmailHint": "Enter your email address",
|
||||||
|
"contactMethodPhoneHint": "Enter your phone number",
|
||||||
|
"contactMethodAddressHint": "Enter your physical address",
|
||||||
|
"contactMethodEmailDescription": "Your email will be used for account recovery and notifications",
|
||||||
|
"contactMethodPhoneDescription": "Your phone number will be used for account recovery and notifications",
|
||||||
|
"contactMethodAddressDescription": "Your physical address will be used for shipping and billing purposes.",
|
||||||
|
"contactMethodVerified": "Verified",
|
||||||
|
"contactMethodUnverified": "Unverified",
|
||||||
|
"contactMethodVerify": "Verify Contact",
|
||||||
|
"contactMethodDelete": "Delete Contact",
|
||||||
|
"contactMethodNew": "New Contact Method",
|
||||||
|
"contactMethodContentEmpty": "Contact content cannot be empty",
|
||||||
|
"contactMethodVerificationSent": "Verification code sent to your contact method",
|
||||||
|
"contactMethodVerificationNeeded": "The contact method is added, but not verified yet. You can verify it by tapping it and select verify.",
|
||||||
|
"accountContactMethod": "Contact Methods",
|
||||||
|
"accountContactMethodDescription": "Manage your contact methods for account recovery and notifications",
|
||||||
|
"authFactorVerificationNeeded": "The auth factor is added, but it is not enabled yet. You can enable it by tapping it and enter the verification code.",
|
||||||
|
"contactMethodPrimary": "Primary",
|
||||||
|
"contactMethodSetPrimary": "Set as Primary",
|
||||||
|
"contactMethodSetPrimaryHint": "Set this contact method as your primary contact method for account recovery and notifications",
|
||||||
|
"contactMethodDeleteHint": "Are you sure to delete this contact method? This action cannot be undone."
|
||||||
}
|
}
|
||||||
|
@ -85,6 +85,24 @@ sealed class SnAccountBadge with _$SnAccountBadge {
|
|||||||
_$SnAccountBadgeFromJson(json);
|
_$SnAccountBadgeFromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
sealed class SnContactMethod with _$SnContactMethod {
|
||||||
|
const factory SnContactMethod({
|
||||||
|
required String id,
|
||||||
|
required int type,
|
||||||
|
required DateTime? verifiedAt,
|
||||||
|
required bool isPrimary,
|
||||||
|
required String content,
|
||||||
|
required String accountId,
|
||||||
|
required DateTime createdAt,
|
||||||
|
required DateTime updatedAt,
|
||||||
|
required DateTime? deletedAt,
|
||||||
|
}) = _SnContactMethod;
|
||||||
|
|
||||||
|
factory SnContactMethod.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SnContactMethodFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
sealed class SnNotification with _$SnNotification {
|
sealed class SnNotification with _$SnNotification {
|
||||||
const factory SnNotification({
|
const factory SnNotification({
|
||||||
|
@ -746,6 +746,163 @@ as DateTime?,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SnContactMethod {
|
||||||
|
|
||||||
|
String get id; int get type; DateTime? get verifiedAt; bool get isPrimary; String get content; String get accountId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
|
||||||
|
/// Create a copy of SnContactMethod
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnContactMethodCopyWith<SnContactMethod> get copyWith => _$SnContactMethodCopyWithImpl<SnContactMethod>(this as SnContactMethod, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this SnContactMethod to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnContactMethod&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.verifiedAt, verifiedAt) || other.verifiedAt == verifiedAt)&&(identical(other.isPrimary, isPrimary) || other.isPrimary == isPrimary)&&(identical(other.content, content) || other.content == content)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,id,type,verifiedAt,isPrimary,content,accountId,createdAt,updatedAt,deletedAt);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnContactMethod(id: $id, type: $type, verifiedAt: $verifiedAt, isPrimary: $isPrimary, content: $content, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $SnContactMethodCopyWith<$Res> {
|
||||||
|
factory $SnContactMethodCopyWith(SnContactMethod value, $Res Function(SnContactMethod) _then) = _$SnContactMethodCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String id, int type, DateTime? verifiedAt, bool isPrimary, String content, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$SnContactMethodCopyWithImpl<$Res>
|
||||||
|
implements $SnContactMethodCopyWith<$Res> {
|
||||||
|
_$SnContactMethodCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final SnContactMethod _self;
|
||||||
|
final $Res Function(SnContactMethod) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnContactMethod
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? type = null,Object? verifiedAt = freezed,Object? isPrimary = null,Object? content = null,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,verifiedAt: freezed == verifiedAt ? _self.verifiedAt : verifiedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,isPrimary: null == isPrimary ? _self.isPrimary : isPrimary // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,content: null == content ? _self.content : content // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class _SnContactMethod implements SnContactMethod {
|
||||||
|
const _SnContactMethod({required this.id, required this.type, required this.verifiedAt, required this.isPrimary, required this.content, required this.accountId, required this.createdAt, required this.updatedAt, required this.deletedAt});
|
||||||
|
factory _SnContactMethod.fromJson(Map<String, dynamic> json) => _$SnContactMethodFromJson(json);
|
||||||
|
|
||||||
|
@override final String id;
|
||||||
|
@override final int type;
|
||||||
|
@override final DateTime? verifiedAt;
|
||||||
|
@override final bool isPrimary;
|
||||||
|
@override final String content;
|
||||||
|
@override final String accountId;
|
||||||
|
@override final DateTime createdAt;
|
||||||
|
@override final DateTime updatedAt;
|
||||||
|
@override final DateTime? deletedAt;
|
||||||
|
|
||||||
|
/// Create a copy of SnContactMethod
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$SnContactMethodCopyWith<_SnContactMethod> get copyWith => __$SnContactMethodCopyWithImpl<_SnContactMethod>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$SnContactMethodToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnContactMethod&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.verifiedAt, verifiedAt) || other.verifiedAt == verifiedAt)&&(identical(other.isPrimary, isPrimary) || other.isPrimary == isPrimary)&&(identical(other.content, content) || other.content == content)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,id,type,verifiedAt,isPrimary,content,accountId,createdAt,updatedAt,deletedAt);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnContactMethod(id: $id, type: $type, verifiedAt: $verifiedAt, isPrimary: $isPrimary, content: $content, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$SnContactMethodCopyWith<$Res> implements $SnContactMethodCopyWith<$Res> {
|
||||||
|
factory _$SnContactMethodCopyWith(_SnContactMethod value, $Res Function(_SnContactMethod) _then) = __$SnContactMethodCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
String id, int type, DateTime? verifiedAt, bool isPrimary, String content, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$SnContactMethodCopyWithImpl<$Res>
|
||||||
|
implements _$SnContactMethodCopyWith<$Res> {
|
||||||
|
__$SnContactMethodCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _SnContactMethod _self;
|
||||||
|
final $Res Function(_SnContactMethod) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnContactMethod
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? type = null,Object? verifiedAt = freezed,Object? isPrimary = null,Object? content = null,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||||
|
return _then(_SnContactMethod(
|
||||||
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,verifiedAt: freezed == verifiedAt ? _self.verifiedAt : verifiedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,isPrimary: null == isPrimary ? _self.isPrimary : isPrimary // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,content: null == content ? _self.content : content // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$SnNotification {
|
mixin _$SnNotification {
|
||||||
|
|
||||||
|
@ -157,6 +157,38 @@ Map<String, dynamic> _$SnAccountBadgeToJson(_SnAccountBadge instance) =>
|
|||||||
'deleted_at': instance.deletedAt?.toIso8601String(),
|
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_SnContactMethod _$SnContactMethodFromJson(Map<String, dynamic> json) =>
|
||||||
|
_SnContactMethod(
|
||||||
|
id: json['id'] as String,
|
||||||
|
type: (json['type'] as num).toInt(),
|
||||||
|
verifiedAt:
|
||||||
|
json['verified_at'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['verified_at'] as String),
|
||||||
|
isPrimary: json['is_primary'] as bool,
|
||||||
|
content: json['content'] as String,
|
||||||
|
accountId: json['account_id'] as String,
|
||||||
|
createdAt: DateTime.parse(json['created_at'] as String),
|
||||||
|
updatedAt: DateTime.parse(json['updated_at'] as String),
|
||||||
|
deletedAt:
|
||||||
|
json['deleted_at'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['deleted_at'] as String),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$SnContactMethodToJson(_SnContactMethod instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'type': instance.type,
|
||||||
|
'verified_at': instance.verifiedAt?.toIso8601String(),
|
||||||
|
'is_primary': instance.isPrimary,
|
||||||
|
'content': instance.content,
|
||||||
|
'account_id': instance.accountId,
|
||||||
|
'created_at': instance.createdAt.toIso8601String(),
|
||||||
|
'updated_at': instance.updatedAt.toIso8601String(),
|
||||||
|
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||||
|
};
|
||||||
|
|
||||||
_SnNotification _$SnNotificationFromJson(Map<String, dynamic> json) =>
|
_SnNotification _$SnNotificationFromJson(Map<String, dynamic> json) =>
|
||||||
_SnNotification(
|
_SnNotification(
|
||||||
createdAt: DateTime.parse(json['created_at'] as String),
|
createdAt: DateTime.parse(json['created_at'] as String),
|
||||||
|
@ -11,6 +11,7 @@ import 'package:flutter_otp_text_field/flutter_otp_text_field.dart';
|
|||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/models/auth.dart';
|
import 'package:island/models/auth.dart';
|
||||||
|
import 'package:island/models/user.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/pods/userinfo.dart';
|
import 'package:island/pods/userinfo.dart';
|
||||||
import 'package:island/screens/auth/captcha.dart';
|
import 'package:island/screens/auth/captcha.dart';
|
||||||
@ -35,6 +36,15 @@ Future<List<SnAuthFactor>> authFactors(Ref ref) async {
|
|||||||
return res.data.map<SnAuthFactor>((e) => SnAuthFactor.fromJson(e)).toList();
|
return res.data.map<SnAuthFactor>((e) => SnAuthFactor.fromJson(e)).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
Future<List<SnContactMethod>> contactMethods(Ref ref) async {
|
||||||
|
final client = ref.read(apiClientProvider);
|
||||||
|
final resp = await client.get('/accounts/me/contacts');
|
||||||
|
return resp.data
|
||||||
|
.map<SnContactMethod>((e) => SnContactMethod.fromJson(e))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
class AccountSettingsScreen extends HookConsumerWidget {
|
class AccountSettingsScreen extends HookConsumerWidget {
|
||||||
const AccountSettingsScreen({super.key});
|
const AccountSettingsScreen({super.key});
|
||||||
@ -52,6 +62,7 @@ class AccountSettingsScreen extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
if (!confirm || !context.mounted) return;
|
if (!confirm || !context.mounted) return;
|
||||||
try {
|
try {
|
||||||
|
showLoadingModal(context);
|
||||||
final client = ref.read(apiClientProvider);
|
final client = ref.read(apiClientProvider);
|
||||||
await client.delete('/accounts/me');
|
await client.delete('/accounts/me');
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
@ -59,6 +70,8 @@ class AccountSettingsScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showErrorAlert(err);
|
showErrorAlert(err);
|
||||||
|
} finally {
|
||||||
|
if (context.mounted) hideLoadingModal(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,6 +86,7 @@ class AccountSettingsScreen extends HookConsumerWidget {
|
|||||||
).push(MaterialPageRoute(builder: (context) => CaptchaScreen()));
|
).push(MaterialPageRoute(builder: (context) => CaptchaScreen()));
|
||||||
if (captchaTk == null) return;
|
if (captchaTk == null) return;
|
||||||
try {
|
try {
|
||||||
|
showLoadingModal(context);
|
||||||
final userInfo = ref.read(userInfoProvider);
|
final userInfo = ref.read(userInfoProvider);
|
||||||
final client = ref.read(apiClientProvider);
|
final client = ref.read(apiClientProvider);
|
||||||
await client.post(
|
await client.post(
|
||||||
@ -84,6 +98,8 @@ class AccountSettingsScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showErrorAlert(err);
|
showErrorAlert(err);
|
||||||
|
} finally {
|
||||||
|
if (context.mounted) hideLoadingModal(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,6 +224,112 @@ class AccountSettingsScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
ExpansionTile(
|
||||||
|
leading: const Icon(
|
||||||
|
Symbols.contact_mail,
|
||||||
|
).alignment(Alignment.centerLeft).width(48),
|
||||||
|
title: Text('accountContactMethod').tr(),
|
||||||
|
subtitle: Text('accountContactMethodDescription').tr().fontSize(12),
|
||||||
|
tilePadding: const EdgeInsets.only(left: 24, right: 17),
|
||||||
|
children: [
|
||||||
|
ref
|
||||||
|
.watch(contactMethodsProvider)
|
||||||
|
.when(
|
||||||
|
data:
|
||||||
|
(contacts) => Column(
|
||||||
|
children: [
|
||||||
|
for (final contact in contacts)
|
||||||
|
ListTile(
|
||||||
|
minLeadingWidth: 48,
|
||||||
|
contentPadding: const EdgeInsets.only(
|
||||||
|
left: 16,
|
||||||
|
right: 17,
|
||||||
|
top: 2,
|
||||||
|
bottom: 4,
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
contact.content,
|
||||||
|
style:
|
||||||
|
contact.verifiedAt == null
|
||||||
|
? TextStyle(
|
||||||
|
decoration: TextDecoration.lineThrough,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
subtitle: Text(
|
||||||
|
contact.type == 0
|
||||||
|
? 'contactMethodTypeEmail'.tr()
|
||||||
|
: 'contactMethodTypePhone'.tr(),
|
||||||
|
style:
|
||||||
|
contact.verifiedAt == null
|
||||||
|
? TextStyle(
|
||||||
|
decoration: TextDecoration.lineThrough,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
leading: CircleAvatar(
|
||||||
|
backgroundColor:
|
||||||
|
contact.verifiedAt == null
|
||||||
|
? Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.secondaryContainer
|
||||||
|
: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.primaryContainer,
|
||||||
|
child: Icon(
|
||||||
|
contact.type == 0
|
||||||
|
? Symbols.mail
|
||||||
|
: Symbols.phone,
|
||||||
|
),
|
||||||
|
).padding(top: 4),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
isThreeLine: false,
|
||||||
|
onTap: () {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder:
|
||||||
|
(context) =>
|
||||||
|
_ContactMethodSheet(contact: contact),
|
||||||
|
).then((value) {
|
||||||
|
if (value == true) {
|
||||||
|
ref.invalidate(contactMethodsProvider);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (contacts.isNotEmpty) const Divider(height: 1),
|
||||||
|
ListTile(
|
||||||
|
minLeadingWidth: 48,
|
||||||
|
contentPadding: const EdgeInsets.only(
|
||||||
|
left: 24,
|
||||||
|
right: 17,
|
||||||
|
),
|
||||||
|
title: Text('contactMethodNew').tr(),
|
||||||
|
leading: const Icon(Symbols.add),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
onTap: () {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder:
|
||||||
|
(context) => const _ContactMethodNewSheet(),
|
||||||
|
).then((value) {
|
||||||
|
if (value == true) {
|
||||||
|
ref.invalidate(contactMethodsProvider);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
error:
|
||||||
|
(err, _) => ResponseErrorWidget(
|
||||||
|
error: err,
|
||||||
|
onRetry: () => ref.invalidate(contactMethodsProvider),
|
||||||
|
),
|
||||||
|
loading: () => const ResponseLoadingWidget(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
final dangerZoneSettings = [
|
final dangerZoneSettings = [
|
||||||
@ -363,11 +485,14 @@ class _AuthFactorSheet extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
if (!confirm || !context.mounted) return;
|
if (!confirm || !context.mounted) return;
|
||||||
try {
|
try {
|
||||||
|
showLoadingModal(context);
|
||||||
final client = ref.read(apiClientProvider);
|
final client = ref.read(apiClientProvider);
|
||||||
await client.delete('/accounts/me/factors/${factor.id}');
|
await client.delete('/accounts/me/factors/${factor.id}');
|
||||||
if (context.mounted) Navigator.pop(context, true);
|
if (context.mounted) Navigator.pop(context, true);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showErrorAlert(err);
|
showErrorAlert(err);
|
||||||
|
} finally {
|
||||||
|
if (context.mounted) hideLoadingModal(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -378,11 +503,14 @@ class _AuthFactorSheet extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
if (!confirm || !context.mounted) return;
|
if (!confirm || !context.mounted) return;
|
||||||
try {
|
try {
|
||||||
|
showLoadingModal(context);
|
||||||
final client = ref.read(apiClientProvider);
|
final client = ref.read(apiClientProvider);
|
||||||
await client.post('/accounts/me/factors/${factor.id}/disable');
|
await client.post('/accounts/me/factors/${factor.id}/disable');
|
||||||
if (context.mounted) Navigator.pop(context, true);
|
if (context.mounted) Navigator.pop(context, true);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showErrorAlert(err);
|
showErrorAlert(err);
|
||||||
|
} finally {
|
||||||
|
if (context.mounted) hideLoadingModal(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -431,6 +559,7 @@ class _AuthFactorSheet extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
showLoadingModal(context);
|
||||||
final client = ref.read(apiClientProvider);
|
final client = ref.read(apiClientProvider);
|
||||||
await client.post(
|
await client.post(
|
||||||
'/accounts/me/factors/${factor.id}/enable',
|
'/accounts/me/factors/${factor.id}/enable',
|
||||||
@ -439,6 +568,8 @@ class _AuthFactorSheet extends HookConsumerWidget {
|
|||||||
if (context.mounted) Navigator.pop(context, true);
|
if (context.mounted) Navigator.pop(context, true);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showErrorAlert(err);
|
showErrorAlert(err);
|
||||||
|
} finally {
|
||||||
|
if (context.mounted) hideLoadingModal(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -464,13 +595,13 @@ class _AuthFactorSheet extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
if (factor.enabledAt == null)
|
if (factor.enabledAt == null)
|
||||||
Badge(
|
Badge(
|
||||||
label: Text('Disabled'),
|
label: Text('authFactorDisabled'.tr()),
|
||||||
textColor: Theme.of(context).colorScheme.onSecondary,
|
textColor: Theme.of(context).colorScheme.onSecondary,
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondary,
|
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
Badge(
|
Badge(
|
||||||
label: Text('Enabled'),
|
label: Text('authFactorEnabled'.tr()),
|
||||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
@ -515,6 +646,7 @@ class _AuthFactorNewSheet extends HookConsumerWidget {
|
|||||||
|
|
||||||
Future<void> addFactor() async {
|
Future<void> addFactor() async {
|
||||||
try {
|
try {
|
||||||
|
showLoadingModal(context);
|
||||||
final apiClient = ref.read(apiClientProvider);
|
final apiClient = ref.read(apiClientProvider);
|
||||||
final resp = await apiClient.post(
|
final resp = await apiClient.post(
|
||||||
'/accounts/me/factors',
|
'/accounts/me/factors',
|
||||||
@ -522,11 +654,15 @@ class _AuthFactorNewSheet extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
final factor = SnAuthFactor.fromJson(resp.data);
|
final factor = SnAuthFactor.fromJson(resp.data);
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
|
hideLoadingModal(context);
|
||||||
if (factor.type == 3) {
|
if (factor.type == 3) {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => _AuthFactorNewAdditonalSheet(factor: factor),
|
builder: (context) => _AuthFactorNewAdditonalSheet(factor: factor),
|
||||||
).then((_) {
|
).then((_) {
|
||||||
|
if (context.mounted) {
|
||||||
|
showSnackBar(context, 'contactMethodVerificationNeeded'.tr());
|
||||||
|
}
|
||||||
if (context.mounted) Navigator.pop(context, true);
|
if (context.mounted) Navigator.pop(context, true);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -534,6 +670,7 @@ class _AuthFactorNewSheet extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showErrorAlert(err);
|
showErrorAlert(err);
|
||||||
|
if (context.mounted) hideLoadingModal(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -660,3 +797,273 @@ class _AuthFactorNewAdditonalSheet extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _ContactMethodSheet extends HookConsumerWidget {
|
||||||
|
final SnContactMethod contact;
|
||||||
|
const _ContactMethodSheet({required this.contact});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
Future<void> deleteContactMethod() async {
|
||||||
|
final confirm = await showConfirmAlert(
|
||||||
|
'contactMethodDeleteHint'.tr(),
|
||||||
|
'contactMethodDelete'.tr(),
|
||||||
|
);
|
||||||
|
if (!confirm || !context.mounted) return;
|
||||||
|
try {
|
||||||
|
showLoadingModal(context);
|
||||||
|
final client = ref.read(apiClientProvider);
|
||||||
|
await client.delete('/accounts/me/contacts/${contact.id}');
|
||||||
|
if (context.mounted) Navigator.pop(context, true);
|
||||||
|
} catch (err) {
|
||||||
|
showErrorAlert(err);
|
||||||
|
} finally {
|
||||||
|
if (context.mounted) hideLoadingModal(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> verifyContactMethod() async {
|
||||||
|
try {
|
||||||
|
showLoadingModal(context);
|
||||||
|
final client = ref.read(apiClientProvider);
|
||||||
|
await client.post('/accounts/me/contacts/${contact.id}/verify');
|
||||||
|
if (context.mounted) {
|
||||||
|
showSnackBar(context, 'contactMethodVerificationSent'.tr());
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
showErrorAlert(err);
|
||||||
|
} finally {
|
||||||
|
if (context.mounted) hideLoadingModal(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setContactMethodAsPrimary() async {
|
||||||
|
try {
|
||||||
|
showLoadingModal(context);
|
||||||
|
final client = ref.read(apiClientProvider);
|
||||||
|
await client.post('/accounts/me/contacts/${contact.id}/primary');
|
||||||
|
if (context.mounted) Navigator.pop(context, true);
|
||||||
|
} catch (err) {
|
||||||
|
showErrorAlert(err);
|
||||||
|
} finally {
|
||||||
|
if (context.mounted) hideLoadingModal(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SheetScaffold(
|
||||||
|
titleText: 'contactMethod'.tr(),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(switch (contact.type) {
|
||||||
|
0 => Symbols.mail,
|
||||||
|
1 => Symbols.phone,
|
||||||
|
_ => Symbols.home,
|
||||||
|
}, size: 32),
|
||||||
|
const Gap(8),
|
||||||
|
Text(switch (contact.type) {
|
||||||
|
0 => 'contactMethodTypeEmail'.tr(),
|
||||||
|
1 => 'contactMethodTypePhone'.tr(),
|
||||||
|
_ => 'contactMethodTypeAddress'.tr(),
|
||||||
|
}),
|
||||||
|
const Gap(4),
|
||||||
|
Text(
|
||||||
|
contact.content,
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
const Gap(10),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
if (contact.verifiedAt == null)
|
||||||
|
Badge(
|
||||||
|
label: Text('contactMethodUnverified'.tr()),
|
||||||
|
textColor: Theme.of(context).colorScheme.onSecondary,
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||||
|
)
|
||||||
|
else
|
||||||
|
Badge(
|
||||||
|
label: Text('contactMethodVerified'.tr()),
|
||||||
|
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
if (contact.isPrimary)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 8.0),
|
||||||
|
child: Badge(
|
||||||
|
label: Text('contactMethodPrimary'.tr()),
|
||||||
|
textColor: Theme.of(context).colorScheme.onTertiary,
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.tertiary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(all: 20),
|
||||||
|
const Divider(height: 1),
|
||||||
|
if (contact.verifiedAt == null)
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Symbols.verified),
|
||||||
|
title: Text('contactMethodVerify').tr(),
|
||||||
|
onTap: verifyContactMethod,
|
||||||
|
contentPadding: EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
),
|
||||||
|
if (contact.verifiedAt != null && !contact.isPrimary)
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Symbols.star),
|
||||||
|
title: Text('contactMethodSetPrimary').tr(),
|
||||||
|
onTap: setContactMethodAsPrimary,
|
||||||
|
contentPadding: EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Symbols.delete),
|
||||||
|
title: Text('contactMethodDelete').tr(),
|
||||||
|
onTap: deleteContactMethod,
|
||||||
|
contentPadding: EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ContactMethodNewSheet extends HookConsumerWidget {
|
||||||
|
const _ContactMethodNewSheet();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final contactType = useState<int>(0);
|
||||||
|
final contentController = useTextEditingController();
|
||||||
|
|
||||||
|
Future<void> addContactMethod() async {
|
||||||
|
if (contentController.text.isEmpty) {
|
||||||
|
showSnackBar(context, 'contactMethodContentEmpty'.tr());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
showLoadingModal(context);
|
||||||
|
final apiClient = ref.read(apiClientProvider);
|
||||||
|
await apiClient.post(
|
||||||
|
'/accounts/me/contacts',
|
||||||
|
data: {'type': contactType.value, 'content': contentController.text},
|
||||||
|
);
|
||||||
|
if (context.mounted) {
|
||||||
|
showSnackBar(context, 'contactMethodVerificationNeeded'.tr());
|
||||||
|
Navigator.pop(context, true);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
showErrorAlert(err);
|
||||||
|
} finally {
|
||||||
|
if (context.mounted) hideLoadingModal(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SheetScaffold(
|
||||||
|
titleText: 'contactMethodNew'.tr(),
|
||||||
|
child: Column(
|
||||||
|
spacing: 16,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
DropdownButtonFormField<int>(
|
||||||
|
value: contactType.value,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'contactMethodType'.tr(),
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
items: [
|
||||||
|
DropdownMenuItem<int>(
|
||||||
|
value: 0,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Symbols.mail),
|
||||||
|
const Gap(8),
|
||||||
|
Text('contactMethodTypeEmail'.tr()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DropdownMenuItem<int>(
|
||||||
|
value: 1,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Symbols.phone),
|
||||||
|
const Gap(8),
|
||||||
|
Text('contactMethodTypePhone'.tr()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DropdownMenuItem<int>(
|
||||||
|
value: 2,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Symbols.home),
|
||||||
|
const Gap(8),
|
||||||
|
Text('contactMethodTypeAddress'.tr()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value != null) {
|
||||||
|
contactType.value = value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
TextField(
|
||||||
|
controller: contentController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
prefixIcon: Icon(switch (contactType.value) {
|
||||||
|
0 => Symbols.mail,
|
||||||
|
1 => Symbols.phone,
|
||||||
|
_ => Symbols.home,
|
||||||
|
}),
|
||||||
|
labelText: switch (contactType.value) {
|
||||||
|
0 => 'contactMethodTypeEmail'.tr(),
|
||||||
|
1 => 'contactMethodTypePhone'.tr(),
|
||||||
|
_ => 'contactMethodTypeAddress'.tr(),
|
||||||
|
},
|
||||||
|
hintText: switch (contactType.value) {
|
||||||
|
0 => 'contactMethodEmailHint'.tr(),
|
||||||
|
1 => 'contactMethodPhoneHint'.tr(),
|
||||||
|
_ => 'contactMethodAddressHint'.tr(),
|
||||||
|
},
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
keyboardType: switch (contactType.value) {
|
||||||
|
0 => TextInputType.emailAddress,
|
||||||
|
1 => TextInputType.phone,
|
||||||
|
_ => TextInputType.multiline,
|
||||||
|
},
|
||||||
|
maxLines: switch (contactType.value) {
|
||||||
|
2 => 3,
|
||||||
|
_ => 1,
|
||||||
|
},
|
||||||
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
child:
|
||||||
|
Text(switch (contactType.value) {
|
||||||
|
0 => 'contactMethodEmailDescription',
|
||||||
|
1 => 'contactMethodPhoneDescription',
|
||||||
|
_ => 'contactMethodAddressDescription',
|
||||||
|
}).tr(),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
TextButton.icon(
|
||||||
|
onPressed: addContactMethod,
|
||||||
|
icon: Icon(Symbols.add),
|
||||||
|
label: Text('create').tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 20, vertical: 24),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -25,5 +25,24 @@ final authFactorsProvider =
|
|||||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
// ignore: unused_element
|
// ignore: unused_element
|
||||||
typedef AuthFactorsRef = AutoDisposeFutureProviderRef<List<SnAuthFactor>>;
|
typedef AuthFactorsRef = AutoDisposeFutureProviderRef<List<SnAuthFactor>>;
|
||||||
|
String _$contactMethodsHash() => r'4d7952fc196dce4dc646314565a49c115fd1d292';
|
||||||
|
|
||||||
|
/// See also [contactMethods].
|
||||||
|
@ProviderFor(contactMethods)
|
||||||
|
final contactMethodsProvider =
|
||||||
|
AutoDisposeFutureProvider<List<SnContactMethod>>.internal(
|
||||||
|
contactMethods,
|
||||||
|
name: r'contactMethodsProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$contactMethodsHash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
|
typedef ContactMethodsRef = AutoDisposeFutureProviderRef<List<SnContactMethod>>;
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||||
|
@ -158,7 +158,7 @@ class SettingsScreen extends HookConsumerWidget {
|
|||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
child: Text('Cancel').tr(),
|
child: Text('cancel').tr(),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
@ -167,7 +167,7 @@ class SettingsScreen extends HookConsumerWidget {
|
|||||||
.setAppColorScheme(selectedColor.value);
|
.setAppColorScheme(selectedColor.value);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
child: Text('Confirm').tr(),
|
child: Text('confirm').tr(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@ -402,19 +402,19 @@ class SettingsScreen extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
_ShortcutRow(
|
_ShortcutRow(
|
||||||
shortcut: 'Ctrl+F',
|
shortcut: 'Ctrl+F',
|
||||||
description: 'Search',
|
description: 'settingsKeyboardShortcutSearch'.tr(),
|
||||||
),
|
),
|
||||||
_ShortcutRow(
|
_ShortcutRow(
|
||||||
shortcut: 'Ctrl+,',
|
shortcut: 'Ctrl+,',
|
||||||
description: 'Settings',
|
description: 'settingsKeyboardShortcutSettings'.tr(),
|
||||||
),
|
),
|
||||||
_ShortcutRow(
|
_ShortcutRow(
|
||||||
shortcut: 'Ctrl+N',
|
shortcut: 'Ctrl+N',
|
||||||
description: 'New Message',
|
description: 'settingsKeyboardShortcutNewMessage'.tr(),
|
||||||
),
|
),
|
||||||
_ShortcutRow(
|
_ShortcutRow(
|
||||||
shortcut: 'Esc',
|
shortcut: 'Esc',
|
||||||
description: 'Close Dialog',
|
description: 'settingsKeyboardShortcutCloseDialog'.tr(),
|
||||||
),
|
),
|
||||||
// Add more shortcuts as needed
|
// Add more shortcuts as needed
|
||||||
],
|
],
|
||||||
@ -423,7 +423,7 @@ class SettingsScreen extends HookConsumerWidget {
|
|||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
child: Text('Close').tr(),
|
child: Text('close').tr(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -445,10 +445,10 @@ class SettingsScreen extends HookConsumerWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_SettingsSection(
|
_SettingsSection(
|
||||||
title: 'Appearance',
|
title: 'settingsAppearance'.tr(),
|
||||||
children: appearanceSettings,
|
children: appearanceSettings,
|
||||||
),
|
),
|
||||||
_SettingsSection(title: 'Server', children: serverSettings),
|
_SettingsSection(title: 'settingsServer'.tr(), children: serverSettings),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -457,12 +457,12 @@ class SettingsScreen extends HookConsumerWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_SettingsSection(
|
_SettingsSection(
|
||||||
title: 'Behavior',
|
title: 'settingsBehavior'.tr(),
|
||||||
children: behaviorSettings,
|
children: behaviorSettings,
|
||||||
),
|
),
|
||||||
if (desktopSettings.isNotEmpty)
|
if (desktopSettings.isNotEmpty)
|
||||||
_SettingsSection(
|
_SettingsSection(
|
||||||
title: 'Desktop',
|
title: 'settingsDesktop'.tr(),
|
||||||
children: desktopSettings,
|
children: desktopSettings,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -475,11 +475,11 @@ class SettingsScreen extends HookConsumerWidget {
|
|||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_SettingsSection(title: 'Appearance', children: appearanceSettings),
|
_SettingsSection(title: 'settingsAppearance'.tr(), children: appearanceSettings),
|
||||||
_SettingsSection(title: 'Server', children: serverSettings),
|
_SettingsSection(title: 'settingsServer'.tr(), children: serverSettings),
|
||||||
_SettingsSection(title: 'Behavior', children: behaviorSettings),
|
_SettingsSection(title: 'settingsBehavior'.tr(), children: behaviorSettings),
|
||||||
if (desktopSettings.isNotEmpty)
|
if (desktopSettings.isNotEmpty)
|
||||||
_SettingsSection(title: 'Desktop', children: desktopSettings),
|
_SettingsSection(title: 'settingsDesktop'.tr(), children: desktopSettings),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -488,7 +488,7 @@ class SettingsScreen extends HookConsumerWidget {
|
|||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
noBackground: false,
|
noBackground: false,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text('Settings').tr(),
|
title: Text('settings').tr(),
|
||||||
actions:
|
actions:
|
||||||
isDesktop
|
isDesktop
|
||||||
? [
|
? [
|
||||||
@ -505,7 +505,7 @@ class SettingsScreen extends HookConsumerWidget {
|
|||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
child: Text('Close').tr(),
|
child: Text('close').tr(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user