mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2025-04-25 09:39:12 -04:00
ui cleanup
This commit is contained in:
parent
d460a0388c
commit
77c68aa45f
@ -50,7 +50,6 @@
|
|||||||
"edit_account_page": {
|
"edit_account_page": {
|
||||||
"titlebar": "Edit Account",
|
"titlebar": "Edit Account",
|
||||||
"header": "Account Profile",
|
"header": "Account Profile",
|
||||||
"update": "Update",
|
|
||||||
"instructions": "This information will be shared with the people you invite to connect with you on VeilidChat.",
|
"instructions": "This information will be shared with the people you invite to connect with you on VeilidChat.",
|
||||||
"error": "Account modification error",
|
"error": "Account modification error",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
@ -64,7 +63,6 @@
|
|||||||
"destroy_account_description": "Destroy account, removing it completely from all devices everywhere",
|
"destroy_account_description": "Destroy account, removing it completely from all devices everywhere",
|
||||||
"destroy_account_confirm_message": "This action is PERMANENT, and your VeilidChat account will no longer be recoverable with the recovery key. Restoring from backups will not recover your account!",
|
"destroy_account_confirm_message": "This action is PERMANENT, and your VeilidChat account will no longer be recoverable with the recovery key. Restoring from backups will not recover your account!",
|
||||||
"destroy_account_confirm_message_details": "You will lose access to:\n • Your entire message history\n • Your contacts\n • This will not remove your messages you have sent from other people's devices\n",
|
"destroy_account_confirm_message_details": "You will lose access to:\n • Your entire message history\n • Your contacts\n • This will not remove your messages you have sent from other people's devices\n",
|
||||||
"confirm_are_you_sure": "Are you sure you want to do this?",
|
|
||||||
"failed_to_remove_title": "Failed to remove account",
|
"failed_to_remove_title": "Failed to remove account",
|
||||||
"try_again_network": "Try again when you have a more stable network connection",
|
"try_again_network": "Try again when you have a more stable network connection",
|
||||||
"failed_to_destroy_title": "Failed to destroy account",
|
"failed_to_destroy_title": "Failed to destroy account",
|
||||||
@ -84,6 +82,12 @@
|
|||||||
"view": "View",
|
"view": "View",
|
||||||
"share": "Share"
|
"share": "Share"
|
||||||
},
|
},
|
||||||
|
"confirmation": {
|
||||||
|
"confirm": "Confirm",
|
||||||
|
"discard_changes": "Discard changes?",
|
||||||
|
"are_you_sure_discard": "Are you sure you want to discard your changes?",
|
||||||
|
"are_you_sure": "Are you sure you want to do this?"
|
||||||
|
},
|
||||||
"button": {
|
"button": {
|
||||||
"ok": "Ok",
|
"ok": "Ok",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
@ -95,10 +99,10 @@
|
|||||||
"close": "Close",
|
"close": "Close",
|
||||||
"yes": "Yes",
|
"yes": "Yes",
|
||||||
"no": "No",
|
"no": "No",
|
||||||
|
"update": "Update",
|
||||||
"waiting_for_network": "Waiting For Network"
|
"waiting_for_network": "Waiting For Network"
|
||||||
},
|
},
|
||||||
"toast": {
|
"toast": {
|
||||||
"confirm": "Confirm",
|
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"info": "Info"
|
"info": "Info"
|
||||||
},
|
},
|
||||||
@ -142,9 +146,7 @@
|
|||||||
"form_nickname": "Nickname",
|
"form_nickname": "Nickname",
|
||||||
"form_notes": "Notes",
|
"form_notes": "Notes",
|
||||||
"form_fingerprint": "Fingerprint",
|
"form_fingerprint": "Fingerprint",
|
||||||
"form_show_availability": "Show availability",
|
"form_show_availability": "Show availability"
|
||||||
"save": "Save",
|
|
||||||
"save_disabled": "Save"
|
|
||||||
},
|
},
|
||||||
"availability": {
|
"availability": {
|
||||||
"unspecified": "Unspecified",
|
"unspecified": "Unspecified",
|
||||||
@ -172,18 +174,21 @@
|
|||||||
"create_invitation_dialog": {
|
"create_invitation_dialog": {
|
||||||
"title": "Create Contact Invitation",
|
"title": "Create Contact Invitation",
|
||||||
"me": "me",
|
"me": "me",
|
||||||
"fingerprint": "Fingerprint:",
|
"recipient_name": "Contact Name",
|
||||||
|
"recipient_hint": "Enter the recipient's name",
|
||||||
|
"recipient_helper": "Name of the person you are inviting to chat",
|
||||||
|
"message_hint": "Enter message for contact (optional)",
|
||||||
|
"message_label": "Message",
|
||||||
|
"message_helper": "Message to send with invitation",
|
||||||
|
"fingerprint": "Fingerprint",
|
||||||
"connect_with_me": "Connect with {name} on VeilidChat!",
|
"connect_with_me": "Connect with {name} on VeilidChat!",
|
||||||
"enter_message_hint": "Enter message for contact (optional)",
|
|
||||||
"message_to_contact": "Message to send with invitation (not encrypted)",
|
|
||||||
"generate": "Generate Invitation",
|
"generate": "Generate Invitation",
|
||||||
"message": "Message",
|
|
||||||
"unlocked": "Unlocked",
|
"unlocked": "Unlocked",
|
||||||
"pin": "PIN",
|
"pin": "PIN",
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
"protect_this_invitation": "Protect this invitation:",
|
"protect_this_invitation": "Protect this invitation:",
|
||||||
"note": "Note:",
|
"note": "Note:",
|
||||||
"note_text": "Contact invitations can be used by anyone. Make sure you send the invitation to your contact over a secure medium, and preferably use a password or pin to ensure that they are the only ones who can unlock the invitation and accept it.",
|
"note_text": "Do not post contact invitations publicly.\n\nContact invitations can be used by anyone. Make sure you send the invitation to your contact over a secure medium, and preferably use a password or pin to ensure that they are the only ones who can unlock the invitation and accept it.",
|
||||||
"pin_description": "Choose a PIN to protect the contact invite.\n\nThis level of security is appropriate only for casual connections in public environments for 'shoulder surfing' protection.",
|
"pin_description": "Choose a PIN to protect the contact invite.\n\nThis level of security is appropriate only for casual connections in public environments for 'shoulder surfing' protection.",
|
||||||
"password_description": "Choose a strong password to protect the contact invite.\n\nThis level of security is appropriate when you must be sure the contact invitation is only accepted by its intended recipient. Share this password over a different medium than the invite itself.",
|
"password_description": "Choose a strong password to protect the contact invite.\n\nThis level of security is appropriate when you must be sure the contact invitation is only accepted by its intended recipient. Share this password over a different medium than the invite itself.",
|
||||||
"pin_does_not_match": "PIN does not match",
|
"pin_does_not_match": "PIN does not match",
|
||||||
@ -193,6 +198,7 @@
|
|||||||
"invitation_copied": "Invitation Copied"
|
"invitation_copied": "Invitation Copied"
|
||||||
},
|
},
|
||||||
"invitation_dialog": {
|
"invitation_dialog": {
|
||||||
|
"to": "To",
|
||||||
"message_from_contact": "Message from contact",
|
"message_from_contact": "Message from contact",
|
||||||
"validating": "Validating...",
|
"validating": "Validating...",
|
||||||
"failed_to_accept": "Failed to accept contact invitation",
|
"failed_to_accept": "Failed to accept contact invitation",
|
||||||
|
1
assets/images/grid.svg
Normal file
1
assets/images/grid.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 5.7 KiB |
@ -1,7 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:async_tools/async_tools.dart';
|
import 'package:async_tools/async_tools.dart';
|
||||||
import 'package:protobuf/protobuf.dart';
|
|
||||||
import 'package:veilid_support/veilid_support.dart';
|
import 'package:veilid_support/veilid_support.dart';
|
||||||
|
|
||||||
import '../../proto/proto.dart' as proto;
|
import '../../proto/proto.dart' as proto;
|
||||||
@ -47,53 +46,30 @@ class AccountRecordCubit extends DefaultDHTRecordCubit<AccountRecordState> {
|
|||||||
// Public Interface
|
// Public Interface
|
||||||
|
|
||||||
void updateAccount(
|
void updateAccount(
|
||||||
AccountSpec accountSpec, Future<void> Function() onSuccess) {
|
AccountSpec accountSpec, Future<void> Function() onChanged) {
|
||||||
_sspUpdate.updateState((accountSpec, onSuccess), (state) async {
|
_sspUpdate.updateState((accountSpec, onChanged), (state) async {
|
||||||
await _updateAccountAsync(state.$1, state.$2);
|
await _updateAccountAsync(state.$1, state.$2);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _updateAccountAsync(
|
Future<void> _updateAccountAsync(
|
||||||
AccountSpec accountSpec, Future<void> Function() onSuccess) async {
|
AccountSpec accountSpec, Future<void> Function() onChanged) async {
|
||||||
var changed = false;
|
var changed = true;
|
||||||
|
|
||||||
await record?.eventualUpdateProtobuf(proto.Account.fromBuffer, (old) async {
|
await record?.eventualUpdateProtobuf(proto.Account.fromBuffer, (old) async {
|
||||||
changed = false;
|
|
||||||
if (old == null) {
|
if (old == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final newAccount = old.deepCopy()
|
final oldAccountSpec = AccountSpec.fromProto(old);
|
||||||
..profile.name = accountSpec.name
|
changed = oldAccountSpec != accountSpec;
|
||||||
..profile.pronouns = accountSpec.pronouns
|
if (!changed) {
|
||||||
..profile.about = accountSpec.about
|
return null;
|
||||||
..profile.availability = accountSpec.availability
|
}
|
||||||
..profile.status = accountSpec.status
|
|
||||||
//..profile.avatar =
|
|
||||||
..profile.timestamp = Veilid.instance.now().toInt64()
|
|
||||||
..invisible = accountSpec.invisible
|
|
||||||
..autodetectAway = accountSpec.autoAway
|
|
||||||
..autoAwayTimeoutMin = accountSpec.autoAwayTimeout
|
|
||||||
..freeMessage = accountSpec.freeMessage
|
|
||||||
..awayMessage = accountSpec.awayMessage
|
|
||||||
..busyMessage = accountSpec.busyMessage;
|
|
||||||
|
|
||||||
if (newAccount.profile != old.profile ||
|
return accountSpec.updateProto(old);
|
||||||
newAccount.invisible != old.invisible ||
|
|
||||||
newAccount.autodetectAway != old.autodetectAway ||
|
|
||||||
newAccount.autoAwayTimeoutMin != old.autoAwayTimeoutMin ||
|
|
||||||
newAccount.freeMessage != old.freeMessage ||
|
|
||||||
newAccount.busyMessage != old.busyMessage ||
|
|
||||||
newAccount.awayMessage != old.awayMessage) {
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
if (changed) {
|
|
||||||
return newAccount;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
});
|
});
|
||||||
if (changed) {
|
if (changed) {
|
||||||
await onSuccess();
|
await onChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
import 'package:flutter/widgets.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
import 'package:protobuf/protobuf.dart';
|
||||||
|
import 'package:veilid_support/veilid_support.dart';
|
||||||
|
|
||||||
import '../../proto/proto.dart' as proto;
|
import '../../proto/proto.dart' as proto;
|
||||||
|
|
||||||
/// Profile and Account configurable fields
|
/// Profile and Account configurable fields
|
||||||
/// Some are publicly visible via the proto.Profile
|
/// Some are publicly visible via the proto.Profile
|
||||||
/// Some are privately held as proto.Account configurations
|
/// Some are privately held as proto.Account configurations
|
||||||
class AccountSpec {
|
@immutable
|
||||||
AccountSpec(
|
class AccountSpec extends Equatable {
|
||||||
|
const AccountSpec(
|
||||||
{required this.name,
|
{required this.name,
|
||||||
required this.pronouns,
|
required this.pronouns,
|
||||||
required this.about,
|
required this.about,
|
||||||
@ -19,37 +23,99 @@ class AccountSpec {
|
|||||||
required this.autoAway,
|
required this.autoAway,
|
||||||
required this.autoAwayTimeout});
|
required this.autoAwayTimeout});
|
||||||
|
|
||||||
|
const AccountSpec.empty()
|
||||||
|
: name = '',
|
||||||
|
pronouns = '',
|
||||||
|
about = '',
|
||||||
|
availability = proto.Availability.AVAILABILITY_FREE,
|
||||||
|
invisible = false,
|
||||||
|
freeMessage = '',
|
||||||
|
awayMessage = '',
|
||||||
|
busyMessage = '',
|
||||||
|
avatar = null,
|
||||||
|
autoAway = false,
|
||||||
|
autoAwayTimeout = 15;
|
||||||
|
|
||||||
|
AccountSpec.fromProto(proto.Account p)
|
||||||
|
: name = p.profile.name,
|
||||||
|
pronouns = p.profile.pronouns,
|
||||||
|
about = p.profile.about,
|
||||||
|
availability = p.profile.availability,
|
||||||
|
invisible = p.invisible,
|
||||||
|
freeMessage = p.freeMessage,
|
||||||
|
awayMessage = p.awayMessage,
|
||||||
|
busyMessage = p.busyMessage,
|
||||||
|
avatar = p.profile.hasAvatar() ? p.profile.avatar : null,
|
||||||
|
autoAway = p.autodetectAway,
|
||||||
|
autoAwayTimeout = p.autoAwayTimeoutMin;
|
||||||
|
|
||||||
String get status {
|
String get status {
|
||||||
late final String status;
|
late final String status;
|
||||||
switch (availability) {
|
switch (availability) {
|
||||||
case proto.Availability.AVAILABILITY_AWAY:
|
case proto.Availability.AVAILABILITY_AWAY:
|
||||||
status = awayMessage;
|
status = awayMessage;
|
||||||
break;
|
|
||||||
case proto.Availability.AVAILABILITY_BUSY:
|
case proto.Availability.AVAILABILITY_BUSY:
|
||||||
status = busyMessage;
|
status = busyMessage;
|
||||||
break;
|
|
||||||
case proto.Availability.AVAILABILITY_FREE:
|
case proto.Availability.AVAILABILITY_FREE:
|
||||||
status = freeMessage;
|
status = freeMessage;
|
||||||
break;
|
|
||||||
case proto.Availability.AVAILABILITY_UNSPECIFIED:
|
case proto.Availability.AVAILABILITY_UNSPECIFIED:
|
||||||
case proto.Availability.AVAILABILITY_OFFLINE:
|
case proto.Availability.AVAILABILITY_OFFLINE:
|
||||||
status = '';
|
status = '';
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<proto.Account> updateProto(proto.Account old) async {
|
||||||
|
final newProto = old.deepCopy()
|
||||||
|
..profile.name = name
|
||||||
|
..profile.pronouns = pronouns
|
||||||
|
..profile.about = about
|
||||||
|
..profile.availability = availability
|
||||||
|
..profile.status = status
|
||||||
|
..profile.timestamp = Veilid.instance.now().toInt64()
|
||||||
|
..invisible = invisible
|
||||||
|
..autodetectAway = autoAway
|
||||||
|
..autoAwayTimeoutMin = autoAwayTimeout
|
||||||
|
..freeMessage = freeMessage
|
||||||
|
..awayMessage = awayMessage
|
||||||
|
..busyMessage = busyMessage;
|
||||||
|
|
||||||
|
final newAvatar = avatar;
|
||||||
|
if (newAvatar != null) {
|
||||||
|
newProto.profile.avatar = newAvatar;
|
||||||
|
} else {
|
||||||
|
newProto.profile.clearAvatar();
|
||||||
|
}
|
||||||
|
|
||||||
|
return newProto;
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
String name;
|
final String name;
|
||||||
String pronouns;
|
final String pronouns;
|
||||||
String about;
|
final String about;
|
||||||
proto.Availability availability;
|
final proto.Availability availability;
|
||||||
bool invisible;
|
final bool invisible;
|
||||||
String freeMessage;
|
final String freeMessage;
|
||||||
String awayMessage;
|
final String awayMessage;
|
||||||
String busyMessage;
|
final String busyMessage;
|
||||||
ImageProvider? avatar;
|
final proto.DataReference? avatar;
|
||||||
bool autoAway;
|
final bool autoAway;
|
||||||
int autoAwayTimeout;
|
final int autoAwayTimeout;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
name,
|
||||||
|
pronouns,
|
||||||
|
about,
|
||||||
|
availability,
|
||||||
|
invisible,
|
||||||
|
freeMessage,
|
||||||
|
awayMessage,
|
||||||
|
busyMessage,
|
||||||
|
avatar,
|
||||||
|
autoAway,
|
||||||
|
autoAwayTimeout
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:async_tools/async_tools.dart';
|
||||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -10,17 +11,18 @@ import 'package:veilid_support/veilid_support.dart';
|
|||||||
|
|
||||||
import '../../layout/default_app_bar.dart';
|
import '../../layout/default_app_bar.dart';
|
||||||
import '../../notifications/notifications.dart';
|
import '../../notifications/notifications.dart';
|
||||||
import '../../proto/proto.dart' as proto;
|
|
||||||
import '../../theme/theme.dart';
|
import '../../theme/theme.dart';
|
||||||
import '../../tools/tools.dart';
|
import '../../tools/tools.dart';
|
||||||
import '../../veilid_processor/veilid_processor.dart';
|
import '../../veilid_processor/veilid_processor.dart';
|
||||||
import '../account_manager.dart';
|
import '../account_manager.dart';
|
||||||
import 'edit_profile_form.dart';
|
import 'edit_profile_form.dart';
|
||||||
|
|
||||||
|
const _kDoBackArrow = 'doBackArrow';
|
||||||
|
|
||||||
class EditAccountPage extends StatefulWidget {
|
class EditAccountPage extends StatefulWidget {
|
||||||
const EditAccountPage(
|
const EditAccountPage(
|
||||||
{required this.superIdentityRecordKey,
|
{required this.superIdentityRecordKey,
|
||||||
required this.existingAccount,
|
required this.initialValue,
|
||||||
required this.accountRecord,
|
required this.accountRecord,
|
||||||
super.key});
|
super.key});
|
||||||
|
|
||||||
@ -28,7 +30,7 @@ class EditAccountPage extends StatefulWidget {
|
|||||||
State createState() => _EditAccountPageState();
|
State createState() => _EditAccountPageState();
|
||||||
|
|
||||||
final TypedKey superIdentityRecordKey;
|
final TypedKey superIdentityRecordKey;
|
||||||
final proto.Account existingAccount;
|
final AccountSpec initialValue;
|
||||||
final OwnedDHTRecordPointer accountRecord;
|
final OwnedDHTRecordPointer accountRecord;
|
||||||
@override
|
@override
|
||||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
@ -36,8 +38,7 @@ class EditAccountPage extends StatefulWidget {
|
|||||||
properties
|
properties
|
||||||
..add(DiagnosticsProperty<TypedKey>(
|
..add(DiagnosticsProperty<TypedKey>(
|
||||||
'superIdentityRecordKey', superIdentityRecordKey))
|
'superIdentityRecordKey', superIdentityRecordKey))
|
||||||
..add(DiagnosticsProperty<proto.Account>(
|
..add(DiagnosticsProperty<AccountSpec>('initialValue', initialValue))
|
||||||
'existingAccount', existingAccount))
|
|
||||||
..add(DiagnosticsProperty<OwnedDHTRecordPointer>(
|
..add(DiagnosticsProperty<OwnedDHTRecordPointer>(
|
||||||
'accountRecord', accountRecord));
|
'accountRecord', accountRecord));
|
||||||
}
|
}
|
||||||
@ -49,36 +50,14 @@ class _EditAccountPageState extends WindowSetupState<EditAccountPage> {
|
|||||||
titleBarStyle: TitleBarStyle.normal,
|
titleBarStyle: TitleBarStyle.normal,
|
||||||
orientationCapability: OrientationCapability.portraitOnly);
|
orientationCapability: OrientationCapability.portraitOnly);
|
||||||
|
|
||||||
Widget _editAccountForm(BuildContext context,
|
EditProfileForm _editAccountForm(BuildContext context) => EditProfileForm(
|
||||||
{required Future<void> Function(AccountSpec) onUpdate}) =>
|
|
||||||
EditProfileForm(
|
|
||||||
header: translate('edit_account_page.header'),
|
header: translate('edit_account_page.header'),
|
||||||
instructions: translate('edit_account_page.instructions'),
|
instructions: translate('edit_account_page.instructions'),
|
||||||
submitText: translate('edit_account_page.update'),
|
submitText: translate('button.update'),
|
||||||
submitDisabledText: translate('button.waiting_for_network'),
|
submitDisabledText: translate('button.waiting_for_network'),
|
||||||
onUpdate: onUpdate,
|
onSubmit: _onSubmit,
|
||||||
initialValueCallback: (key) => switch (key) {
|
onModifiedState: _onModifiedState,
|
||||||
EditProfileForm.formFieldName => widget.existingAccount.profile.name,
|
initialValue: widget.initialValue,
|
||||||
EditProfileForm.formFieldPronouns =>
|
|
||||||
widget.existingAccount.profile.pronouns,
|
|
||||||
EditProfileForm.formFieldAbout =>
|
|
||||||
widget.existingAccount.profile.about,
|
|
||||||
EditProfileForm.formFieldAvailability =>
|
|
||||||
widget.existingAccount.profile.availability,
|
|
||||||
EditProfileForm.formFieldFreeMessage =>
|
|
||||||
widget.existingAccount.freeMessage,
|
|
||||||
EditProfileForm.formFieldAwayMessage =>
|
|
||||||
widget.existingAccount.awayMessage,
|
|
||||||
EditProfileForm.formFieldBusyMessage =>
|
|
||||||
widget.existingAccount.busyMessage,
|
|
||||||
EditProfileForm.formFieldAvatar =>
|
|
||||||
widget.existingAccount.profile.avatar,
|
|
||||||
EditProfileForm.formFieldAutoAway =>
|
|
||||||
widget.existingAccount.autodetectAway,
|
|
||||||
EditProfileForm.formFieldAutoAwayTimeout =>
|
|
||||||
widget.existingAccount.autoAwayTimeoutMin.toString(),
|
|
||||||
String() => throw UnimplementedError(),
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Future<void> _onRemoveAccount() async {
|
Future<void> _onRemoveAccount() async {
|
||||||
@ -88,8 +67,7 @@ class _EditAccountPageState extends WindowSetupState<EditAccountPage> {
|
|||||||
child: Column(mainAxisSize: MainAxisSize.min, children: [
|
child: Column(mainAxisSize: MainAxisSize.min, children: [
|
||||||
Text(translate('edit_account_page.remove_account_confirm_message'))
|
Text(translate('edit_account_page.remove_account_confirm_message'))
|
||||||
.paddingLTRB(24, 24, 24, 0),
|
.paddingLTRB(24, 24, 24, 0),
|
||||||
Text(translate('edit_account_page.confirm_are_you_sure'))
|
Text(translate('confirmation.are_you_sure')).paddingAll(8),
|
||||||
.paddingAll(8),
|
|
||||||
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
|
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
@ -156,8 +134,7 @@ class _EditAccountPageState extends WindowSetupState<EditAccountPage> {
|
|||||||
Text(translate(
|
Text(translate(
|
||||||
'edit_account_page.destroy_account_confirm_message_details'))
|
'edit_account_page.destroy_account_confirm_message_details'))
|
||||||
.paddingLTRB(24, 24, 24, 0),
|
.paddingLTRB(24, 24, 24, 0),
|
||||||
Text(translate('edit_account_page.confirm_are_you_sure'))
|
Text(translate('confirmation.are_you_sure')).paddingAll(8),
|
||||||
.paddingAll(8),
|
|
||||||
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
|
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
@ -214,26 +191,51 @@ class _EditAccountPageState extends WindowSetupState<EditAccountPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onUpdate(AccountSpec accountSpec) async {
|
void _onModifiedState(bool isModified) {
|
||||||
// Look up account cubit for this specific account
|
setState(() {
|
||||||
final perAccountCollectionBlocMapCubit =
|
_isModified = isModified;
|
||||||
context.read<PerAccountCollectionBlocMapCubit>();
|
|
||||||
final accountRecordCubit = await perAccountCollectionBlocMapCubit.operate(
|
|
||||||
widget.superIdentityRecordKey,
|
|
||||||
closure: (c) async => c.accountRecordCubit);
|
|
||||||
if (accountRecordCubit == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update account profile DHT record
|
|
||||||
// This triggers ConversationCubits to update
|
|
||||||
accountRecordCubit.updateAccount(accountSpec, () async {
|
|
||||||
// Update local account profile
|
|
||||||
await AccountRepository.instance
|
|
||||||
.updateLocalAccount(widget.superIdentityRecordKey, accountSpec);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> _onSubmit(AccountSpec accountSpec) async {
|
||||||
|
try {
|
||||||
|
setState(() {
|
||||||
|
_isInAsyncCall = true;
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
// Look up account cubit for this specific account
|
||||||
|
final perAccountCollectionBlocMapCubit =
|
||||||
|
context.read<PerAccountCollectionBlocMapCubit>();
|
||||||
|
final accountRecordCubit = await perAccountCollectionBlocMapCubit
|
||||||
|
.operate(widget.superIdentityRecordKey,
|
||||||
|
closure: (c) async => c.accountRecordCubit);
|
||||||
|
if (accountRecordCubit == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update account profile DHT record
|
||||||
|
// This triggers ConversationCubits to update
|
||||||
|
accountRecordCubit.updateAccount(accountSpec, () async {
|
||||||
|
// Update local account profile
|
||||||
|
await AccountRepository.instance
|
||||||
|
.updateLocalAccount(widget.superIdentityRecordKey, accountSpec);
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} finally {
|
||||||
|
setState(() {
|
||||||
|
_isInAsyncCall = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} on Exception catch (e, st) {
|
||||||
|
if (mounted) {
|
||||||
|
await showErrorStacktraceModal(
|
||||||
|
context: context, error: e, stackTrace: st);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final displayModalHUD = _isInAsyncCall;
|
final displayModalHUD = _isInAsyncCall;
|
||||||
@ -246,9 +248,23 @@ class _EditAccountPageState extends WindowSetupState<EditAccountPage> {
|
|||||||
? IconButton(
|
? IconButton(
|
||||||
icon: const Icon(Icons.arrow_back),
|
icon: const Icon(Icons.arrow_back),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pop(context);
|
singleFuture((this, _kDoBackArrow), () async {
|
||||||
},
|
if (_isModified) {
|
||||||
)
|
final ok = await showConfirmModal(
|
||||||
|
context: context,
|
||||||
|
title:
|
||||||
|
translate('confirmation.discard_changes'),
|
||||||
|
text: translate(
|
||||||
|
'confirmation.are_you_sure_discard'));
|
||||||
|
if (!ok) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (context.mounted) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
: null,
|
: null,
|
||||||
actions: [
|
actions: [
|
||||||
const SignalStrengthMeterWidget(),
|
const SignalStrengthMeterWidget(),
|
||||||
@ -261,10 +277,7 @@ class _EditAccountPageState extends WindowSetupState<EditAccountPage> {
|
|||||||
]),
|
]),
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
child: Column(children: [
|
child: Column(children: [
|
||||||
_editAccountForm(
|
_editAccountForm(context).paddingLTRB(0, 0, 0, 32),
|
||||||
context,
|
|
||||||
onUpdate: _onUpdate,
|
|
||||||
).paddingLTRB(0, 0, 0, 32),
|
|
||||||
OptionBox(
|
OptionBox(
|
||||||
instructions:
|
instructions:
|
||||||
translate('edit_account_page.remove_account_description'),
|
translate('edit_account_page.remove_account_description'),
|
||||||
@ -286,4 +299,5 @@ class _EditAccountPageState extends WindowSetupState<EditAccountPage> {
|
|||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
bool _isInAsyncCall = false;
|
bool _isInAsyncCall = false;
|
||||||
|
bool _isModified = false;
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ import '../../theme/theme.dart';
|
|||||||
import '../../veilid_processor/veilid_processor.dart';
|
import '../../veilid_processor/veilid_processor.dart';
|
||||||
import '../models/models.dart';
|
import '../models/models.dart';
|
||||||
|
|
||||||
const _kDoUpdateSubmit = 'doUpdateSubmit';
|
const _kDoSubmitEditProfile = 'doSubmitEditProfile';
|
||||||
|
|
||||||
class EditProfileForm extends StatefulWidget {
|
class EditProfileForm extends StatefulWidget {
|
||||||
const EditProfileForm({
|
const EditProfileForm({
|
||||||
@ -21,9 +21,9 @@ class EditProfileForm extends StatefulWidget {
|
|||||||
required this.instructions,
|
required this.instructions,
|
||||||
required this.submitText,
|
required this.submitText,
|
||||||
required this.submitDisabledText,
|
required this.submitDisabledText,
|
||||||
required this.initialValueCallback,
|
required this.initialValue,
|
||||||
this.onUpdate,
|
required this.onSubmit,
|
||||||
this.onSubmit,
|
this.onModifiedState,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -32,11 +32,11 @@ class EditProfileForm extends StatefulWidget {
|
|||||||
|
|
||||||
final String header;
|
final String header;
|
||||||
final String instructions;
|
final String instructions;
|
||||||
final Future<void> Function(AccountSpec)? onUpdate;
|
final Future<bool> Function(AccountSpec) onSubmit;
|
||||||
final Future<void> Function(AccountSpec)? onSubmit;
|
final void Function(bool)? onModifiedState;
|
||||||
final String submitText;
|
final String submitText;
|
||||||
final String submitDisabledText;
|
final String submitDisabledText;
|
||||||
final Object Function(String key) initialValueCallback;
|
final AccountSpec initialValue;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
@ -44,14 +44,13 @@ class EditProfileForm extends StatefulWidget {
|
|||||||
properties
|
properties
|
||||||
..add(StringProperty('header', header))
|
..add(StringProperty('header', header))
|
||||||
..add(StringProperty('instructions', instructions))
|
..add(StringProperty('instructions', instructions))
|
||||||
..add(ObjectFlagProperty<Future<void> Function(AccountSpec)?>.has(
|
|
||||||
'onUpdate', onUpdate))
|
|
||||||
..add(StringProperty('submitText', submitText))
|
..add(StringProperty('submitText', submitText))
|
||||||
..add(StringProperty('submitDisabledText', submitDisabledText))
|
..add(StringProperty('submitDisabledText', submitDisabledText))
|
||||||
..add(ObjectFlagProperty<Object Function(String key)?>.has(
|
..add(ObjectFlagProperty<Future<bool> Function(AccountSpec)>.has(
|
||||||
'initialValueCallback', initialValueCallback))
|
'onSubmit', onSubmit))
|
||||||
..add(ObjectFlagProperty<Future<void> Function(AccountSpec)?>.has(
|
..add(ObjectFlagProperty<void Function(bool p1)?>.has(
|
||||||
'onSubmit', onSubmit));
|
'onModifiedState', onModifiedState))
|
||||||
|
..add(DiagnosticsProperty<AccountSpec>('initialValue', initialValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
static const String formFieldName = 'name';
|
static const String formFieldName = 'name';
|
||||||
@ -71,8 +70,9 @@ class _EditProfileFormState extends State<EditProfileForm> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
_autoAwayEnabled =
|
_savedValue = widget.initialValue;
|
||||||
widget.initialValueCallback(EditProfileForm.formFieldAutoAway) as bool;
|
_currentValueName = widget.initialValue.name;
|
||||||
|
_currentValueAutoAway = widget.initialValue.autoAway;
|
||||||
|
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
@ -82,13 +82,10 @@ class _EditProfileFormState extends State<EditProfileForm> {
|
|||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final scale = theme.extension<ScaleScheme>()!;
|
final scale = theme.extension<ScaleScheme>()!;
|
||||||
|
|
||||||
final initialValueX =
|
|
||||||
widget.initialValueCallback(EditProfileForm.formFieldAvailability)
|
|
||||||
as proto.Availability;
|
|
||||||
final initialValue =
|
final initialValue =
|
||||||
initialValueX == proto.Availability.AVAILABILITY_UNSPECIFIED
|
_savedValue.availability == proto.Availability.AVAILABILITY_UNSPECIFIED
|
||||||
? proto.Availability.AVAILABILITY_FREE
|
? proto.Availability.AVAILABILITY_FREE
|
||||||
: initialValueX;
|
: _savedValue.availability;
|
||||||
|
|
||||||
final availabilities = [
|
final availabilities = [
|
||||||
proto.Availability.AVAILABILITY_FREE,
|
proto.Availability.AVAILABILITY_FREE,
|
||||||
@ -109,7 +106,7 @@ class _EditProfileFormState extends State<EditProfileForm> {
|
|||||||
value: x,
|
value: x,
|
||||||
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||||
AvailabilityWidget.availabilityIcon(
|
AvailabilityWidget.availabilityIcon(
|
||||||
x, scale.primaryScale.primaryText),
|
x, scale.primaryScale.appText),
|
||||||
Text(x == proto.Availability.AVAILABILITY_OFFLINE
|
Text(x == proto.Availability.AVAILABILITY_OFFLINE
|
||||||
? translate('availability.always_show_offline')
|
? translate('availability.always_show_offline')
|
||||||
: AvailabilityWidget.availabilityName(x))
|
: AvailabilityWidget.availabilityName(x))
|
||||||
@ -138,6 +135,12 @@ class _EditProfileFormState extends State<EditProfileForm> {
|
|||||||
.fields[EditProfileForm.formFieldAwayMessage]!.value as String;
|
.fields[EditProfileForm.formFieldAwayMessage]!.value as String;
|
||||||
final busyMessage = _formKey.currentState!
|
final busyMessage = _formKey.currentState!
|
||||||
.fields[EditProfileForm.formFieldBusyMessage]!.value as String;
|
.fields[EditProfileForm.formFieldBusyMessage]!.value as String;
|
||||||
|
|
||||||
|
const proto.DataReference? avatar = null;
|
||||||
|
// final avatar = _formKey.currentState!
|
||||||
|
// .fields[EditProfileForm.formFieldAvatar]!.value
|
||||||
|
//as proto.DataReference?;
|
||||||
|
|
||||||
final autoAway = _formKey
|
final autoAway = _formKey
|
||||||
.currentState!.fields[EditProfileForm.formFieldAutoAway]!.value as bool;
|
.currentState!.fields[EditProfileForm.formFieldAutoAway]!.value as bool;
|
||||||
final autoAwayTimeoutString = _formKey.currentState!
|
final autoAwayTimeoutString = _formKey.currentState!
|
||||||
@ -153,11 +156,21 @@ class _EditProfileFormState extends State<EditProfileForm> {
|
|||||||
freeMessage: freeMessage,
|
freeMessage: freeMessage,
|
||||||
awayMessage: awayMessage,
|
awayMessage: awayMessage,
|
||||||
busyMessage: busyMessage,
|
busyMessage: busyMessage,
|
||||||
avatar: null,
|
avatar: avatar,
|
||||||
autoAway: autoAway,
|
autoAway: autoAway,
|
||||||
autoAwayTimeout: autoAwayTimeout);
|
autoAwayTimeout: autoAwayTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if everything is the same and update state
|
||||||
|
void _onChanged() {
|
||||||
|
final currentValue = _makeAccountSpec();
|
||||||
|
_isModified = currentValue != _savedValue;
|
||||||
|
final onModifiedState = widget.onModifiedState;
|
||||||
|
if (onModifiedState != null) {
|
||||||
|
onModifiedState(_isModified);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Widget _editProfileForm(
|
Widget _editProfileForm(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
) {
|
) {
|
||||||
@ -176,24 +189,32 @@ class _EditProfileFormState extends State<EditProfileForm> {
|
|||||||
return FormBuilder(
|
return FormBuilder(
|
||||||
key: _formKey,
|
key: _formKey,
|
||||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||||
|
onChanged: _onChanged,
|
||||||
child: Column(
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
AvatarWidget(
|
Row(children: [
|
||||||
name: _formKey.currentState?.value[EditProfileForm.formFieldName]
|
const Spacer(),
|
||||||
as String? ??
|
AvatarWidget(
|
||||||
'?',
|
name: _currentValueName,
|
||||||
size: 128,
|
size: 128,
|
||||||
borderColor: border,
|
borderColor: border,
|
||||||
foregroundColor: scale.primaryScale.primaryText,
|
foregroundColor: scale.primaryScale.primaryText,
|
||||||
backgroundColor: scale.primaryScale.primary,
|
backgroundColor: scale.primaryScale.primary,
|
||||||
scaleConfig: scaleConfig,
|
scaleConfig: scaleConfig,
|
||||||
textStyle: theme.textTheme.titleLarge!.copyWith(fontSize: 64),
|
textStyle: theme.textTheme.titleLarge!.copyWith(fontSize: 64),
|
||||||
).paddingLTRB(0, 0, 0, 16),
|
).paddingLTRB(0, 0, 0, 16),
|
||||||
|
const Spacer()
|
||||||
|
]),
|
||||||
FormBuilderTextField(
|
FormBuilderTextField(
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
name: EditProfileForm.formFieldName,
|
name: EditProfileForm.formFieldName,
|
||||||
initialValue: widget
|
initialValue: _savedValue.name,
|
||||||
.initialValueCallback(EditProfileForm.formFieldName) as String,
|
onChanged: (x) {
|
||||||
|
setState(() {
|
||||||
|
_currentValueName = x ?? '';
|
||||||
|
});
|
||||||
|
},
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||||
labelText: translate('account.form_name'),
|
labelText: translate('account.form_name'),
|
||||||
@ -204,23 +225,20 @@ class _EditProfileFormState extends State<EditProfileForm> {
|
|||||||
FormBuilderValidators.required(),
|
FormBuilderValidators.required(),
|
||||||
]),
|
]),
|
||||||
textInputAction: TextInputAction.next,
|
textInputAction: TextInputAction.next,
|
||||||
).onFocusChange(_onFocusChange),
|
),
|
||||||
FormBuilderTextField(
|
FormBuilderTextField(
|
||||||
name: EditProfileForm.formFieldPronouns,
|
name: EditProfileForm.formFieldPronouns,
|
||||||
initialValue:
|
initialValue: _savedValue.pronouns,
|
||||||
widget.initialValueCallback(EditProfileForm.formFieldPronouns)
|
|
||||||
as String,
|
|
||||||
maxLength: 64,
|
maxLength: 64,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||||
labelText: translate('account.form_pronouns'),
|
labelText: translate('account.form_pronouns'),
|
||||||
hintText: translate('account.empty_pronouns')),
|
hintText: translate('account.empty_pronouns')),
|
||||||
textInputAction: TextInputAction.next,
|
textInputAction: TextInputAction.next,
|
||||||
).onFocusChange(_onFocusChange),
|
),
|
||||||
FormBuilderTextField(
|
FormBuilderTextField(
|
||||||
name: EditProfileForm.formFieldAbout,
|
name: EditProfileForm.formFieldAbout,
|
||||||
initialValue: widget
|
initialValue: _savedValue.about,
|
||||||
.initialValueCallback(EditProfileForm.formFieldAbout) as String,
|
|
||||||
maxLength: 1024,
|
maxLength: 1024,
|
||||||
maxLines: 8,
|
maxLines: 8,
|
||||||
minLines: 1,
|
minLines: 1,
|
||||||
@ -229,74 +247,69 @@ class _EditProfileFormState extends State<EditProfileForm> {
|
|||||||
labelText: translate('account.form_about'),
|
labelText: translate('account.form_about'),
|
||||||
hintText: translate('account.empty_about')),
|
hintText: translate('account.empty_about')),
|
||||||
textInputAction: TextInputAction.newline,
|
textInputAction: TextInputAction.newline,
|
||||||
).onFocusChange(_onFocusChange),
|
),
|
||||||
_availabilityDropDown(context)
|
_availabilityDropDown(context).paddingLTRB(0, 0, 0, 16),
|
||||||
.paddingLTRB(0, 0, 0, 16)
|
|
||||||
.onFocusChange(_onFocusChange),
|
|
||||||
FormBuilderTextField(
|
FormBuilderTextField(
|
||||||
name: EditProfileForm.formFieldFreeMessage,
|
name: EditProfileForm.formFieldFreeMessage,
|
||||||
initialValue: widget.initialValueCallback(
|
initialValue: _savedValue.freeMessage,
|
||||||
EditProfileForm.formFieldFreeMessage) as String,
|
|
||||||
maxLength: 128,
|
maxLength: 128,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||||
labelText: translate('account.form_free_message'),
|
labelText: translate('account.form_free_message'),
|
||||||
hintText: translate('account.empty_free_message')),
|
hintText: translate('account.empty_free_message')),
|
||||||
textInputAction: TextInputAction.next,
|
textInputAction: TextInputAction.next,
|
||||||
).onFocusChange(_onFocusChange),
|
),
|
||||||
FormBuilderTextField(
|
FormBuilderTextField(
|
||||||
name: EditProfileForm.formFieldAwayMessage,
|
name: EditProfileForm.formFieldAwayMessage,
|
||||||
initialValue: widget.initialValueCallback(
|
initialValue: _savedValue.awayMessage,
|
||||||
EditProfileForm.formFieldAwayMessage) as String,
|
|
||||||
maxLength: 128,
|
maxLength: 128,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||||
labelText: translate('account.form_away_message'),
|
labelText: translate('account.form_away_message'),
|
||||||
hintText: translate('account.empty_away_message')),
|
hintText: translate('account.empty_away_message')),
|
||||||
textInputAction: TextInputAction.next,
|
textInputAction: TextInputAction.next,
|
||||||
).onFocusChange(_onFocusChange),
|
),
|
||||||
FormBuilderTextField(
|
FormBuilderTextField(
|
||||||
name: EditProfileForm.formFieldBusyMessage,
|
name: EditProfileForm.formFieldBusyMessage,
|
||||||
initialValue: widget.initialValueCallback(
|
initialValue: _savedValue.busyMessage,
|
||||||
EditProfileForm.formFieldBusyMessage) as String,
|
|
||||||
maxLength: 128,
|
maxLength: 128,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||||
labelText: translate('account.form_busy_message'),
|
labelText: translate('account.form_busy_message'),
|
||||||
hintText: translate('account.empty_busy_message')),
|
hintText: translate('account.empty_busy_message')),
|
||||||
textInputAction: TextInputAction.next,
|
textInputAction: TextInputAction.next,
|
||||||
).onFocusChange(_onFocusChange),
|
),
|
||||||
FormBuilderCheckbox(
|
FormBuilderCheckbox(
|
||||||
name: EditProfileForm.formFieldAutoAway,
|
name: EditProfileForm.formFieldAutoAway,
|
||||||
initialValue:
|
initialValue: _savedValue.autoAway,
|
||||||
widget.initialValueCallback(EditProfileForm.formFieldAutoAway)
|
|
||||||
as bool,
|
|
||||||
side: BorderSide(color: scale.primaryScale.border, width: 2),
|
side: BorderSide(color: scale.primaryScale.border, width: 2),
|
||||||
|
checkColor: scale.primaryScale.borderText,
|
||||||
|
activeColor: scale.primaryScale.border,
|
||||||
title: Text(translate('account.form_auto_away'),
|
title: Text(translate('account.form_auto_away'),
|
||||||
style: textTheme.labelMedium),
|
style: textTheme.labelMedium),
|
||||||
onChanged: (v) {
|
onChanged: (v) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_autoAwayEnabled = v ?? false;
|
_currentValueAutoAway = v ?? false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
).onFocusChange(_onFocusChange),
|
),
|
||||||
FormBuilderTextField(
|
FormBuilderTextField(
|
||||||
name: EditProfileForm.formFieldAutoAwayTimeout,
|
name: EditProfileForm.formFieldAutoAwayTimeout,
|
||||||
enabled: _autoAwayEnabled,
|
enabled: _currentValueAutoAway,
|
||||||
initialValue: widget.initialValueCallback(
|
initialValue: _savedValue.autoAwayTimeout.toString(),
|
||||||
EditProfileForm.formFieldAutoAwayTimeout) as String,
|
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: translate('account.form_auto_away_timeout'),
|
labelText: translate('account.form_auto_away_timeout'),
|
||||||
),
|
),
|
||||||
validator: FormBuilderValidators.positiveNumber(),
|
validator: FormBuilderValidators.positiveNumber(),
|
||||||
textInputAction: TextInputAction.next,
|
textInputAction: TextInputAction.next,
|
||||||
).onFocusChange(_onFocusChange),
|
),
|
||||||
Row(children: [
|
Row(children: [
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
Text(widget.instructions).toCenter().flexible(flex: 6),
|
Text(widget.instructions).toCenter().flexible(flex: 6),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
]).paddingSymmetric(vertical: 4),
|
]).paddingSymmetric(vertical: 16),
|
||||||
if (widget.onSubmit != null)
|
Row(children: [
|
||||||
|
const Spacer(),
|
||||||
Builder(builder: (context) {
|
Builder(builder: (context) {
|
||||||
final networkReady = context
|
final networkReady = context
|
||||||
.watch<ConnectionStateCubit>()
|
.watch<ConnectionStateCubit>()
|
||||||
@ -307,7 +320,7 @@ class _EditProfileFormState extends State<EditProfileForm> {
|
|||||||
false;
|
false;
|
||||||
|
|
||||||
return ElevatedButton(
|
return ElevatedButton(
|
||||||
onPressed: networkReady ? _doSubmit : null,
|
onPressed: (networkReady && _isModified) ? _doSubmit : null,
|
||||||
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||||
const Icon(Icons.check, size: 16).paddingLTRB(0, 0, 4, 0),
|
const Icon(Icons.check, size: 16).paddingLTRB(0, 0, 4, 0),
|
||||||
Text(networkReady
|
Text(networkReady
|
||||||
@ -317,36 +330,24 @@ class _EditProfileFormState extends State<EditProfileForm> {
|
|||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
const Spacer()
|
||||||
|
])
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onFocusChange(bool focused) {
|
|
||||||
if (!focused) {
|
|
||||||
_doUpdate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _doUpdate() {
|
|
||||||
final onUpdate = widget.onUpdate;
|
|
||||||
if (onUpdate != null) {
|
|
||||||
singleFuture((this, _kDoUpdateSubmit), () async {
|
|
||||||
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
|
||||||
final aus = _makeAccountSpec();
|
|
||||||
await onUpdate(aus);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _doSubmit() {
|
void _doSubmit() {
|
||||||
final onSubmit = widget.onSubmit;
|
final onSubmit = widget.onSubmit;
|
||||||
if (onSubmit != null) {
|
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
||||||
singleFuture((this, _kDoUpdateSubmit), () async {
|
singleFuture((this, _kDoSubmitEditProfile), () async {
|
||||||
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
final updatedAccountSpec = _makeAccountSpec();
|
||||||
final aus = _makeAccountSpec();
|
final saved = await onSubmit(updatedAccountSpec);
|
||||||
await onSubmit(aus);
|
if (saved) {
|
||||||
|
setState(() {
|
||||||
|
_savedValue = updatedAccountSpec;
|
||||||
|
});
|
||||||
|
_onChanged();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -358,5 +359,8 @@ class _EditProfileFormState extends State<EditProfileForm> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
late bool _autoAwayEnabled;
|
late AccountSpec _savedValue;
|
||||||
|
late bool _currentValueAutoAway;
|
||||||
|
late String _currentValueName;
|
||||||
|
bool _isModified = false;
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ import 'package:go_router/go_router.dart';
|
|||||||
|
|
||||||
import '../../layout/default_app_bar.dart';
|
import '../../layout/default_app_bar.dart';
|
||||||
import '../../notifications/cubits/notifications_cubit.dart';
|
import '../../notifications/cubits/notifications_cubit.dart';
|
||||||
import '../../proto/proto.dart' as proto;
|
|
||||||
import '../../theme/theme.dart';
|
import '../../theme/theme.dart';
|
||||||
import '../../tools/tools.dart';
|
import '../../tools/tools.dart';
|
||||||
import '../../veilid_processor/veilid_processor.dart';
|
import '../../veilid_processor/veilid_processor.dart';
|
||||||
@ -28,33 +27,6 @@ class _NewAccountPageState extends WindowSetupState<NewAccountPage> {
|
|||||||
titleBarStyle: TitleBarStyle.normal,
|
titleBarStyle: TitleBarStyle.normal,
|
||||||
orientationCapability: OrientationCapability.portraitOnly);
|
orientationCapability: OrientationCapability.portraitOnly);
|
||||||
|
|
||||||
Object _defaultAccountValues(String key) {
|
|
||||||
switch (key) {
|
|
||||||
case EditProfileForm.formFieldName:
|
|
||||||
return '';
|
|
||||||
case EditProfileForm.formFieldPronouns:
|
|
||||||
return '';
|
|
||||||
case EditProfileForm.formFieldAbout:
|
|
||||||
return '';
|
|
||||||
case EditProfileForm.formFieldAvailability:
|
|
||||||
return proto.Availability.AVAILABILITY_FREE;
|
|
||||||
case EditProfileForm.formFieldFreeMessage:
|
|
||||||
return '';
|
|
||||||
case EditProfileForm.formFieldAwayMessage:
|
|
||||||
return '';
|
|
||||||
case EditProfileForm.formFieldBusyMessage:
|
|
||||||
return '';
|
|
||||||
// case EditProfileForm.formFieldAvatar:
|
|
||||||
// return null;
|
|
||||||
case EditProfileForm.formFieldAutoAway:
|
|
||||||
return false;
|
|
||||||
case EditProfileForm.formFieldAutoAwayTimeout:
|
|
||||||
return '15';
|
|
||||||
default:
|
|
||||||
throw StateError('missing form element');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _newAccountForm(
|
Widget _newAccountForm(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
) =>
|
) =>
|
||||||
@ -63,10 +35,10 @@ class _NewAccountPageState extends WindowSetupState<NewAccountPage> {
|
|||||||
instructions: translate('new_account_page.instructions'),
|
instructions: translate('new_account_page.instructions'),
|
||||||
submitText: translate('new_account_page.create'),
|
submitText: translate('new_account_page.create'),
|
||||||
submitDisabledText: translate('button.waiting_for_network'),
|
submitDisabledText: translate('button.waiting_for_network'),
|
||||||
initialValueCallback: _defaultAccountValues,
|
initialValue: const AccountSpec.empty(),
|
||||||
onSubmit: _onSubmit);
|
onSubmit: _onSubmit);
|
||||||
|
|
||||||
Future<void> _onSubmit(AccountSpec accountSpec) async {
|
Future<bool> _onSubmit(AccountSpec accountSpec) async {
|
||||||
// dismiss the keyboard by unfocusing the textfield
|
// dismiss the keyboard by unfocusing the textfield
|
||||||
FocusScope.of(context).unfocus();
|
FocusScope.of(context).unfocus();
|
||||||
|
|
||||||
@ -88,13 +60,15 @@ class _NewAccountPageState extends WindowSetupState<NewAccountPage> {
|
|||||||
context.read<NotificationsCubit>().error(
|
context.read<NotificationsCubit>().error(
|
||||||
text: translate('new_account_page.network_is_offline'),
|
text: translate('new_account_page.network_is_offline'),
|
||||||
title: translate('new_account_page.error'));
|
title: translate('new_account_page.error'));
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final writableSuperIdentity = await AccountRepository.instance
|
final writableSuperIdentity = await AccountRepository.instance
|
||||||
.createWithNewSuperIdentity(accountSpec);
|
.createWithNewSuperIdentity(accountSpec);
|
||||||
GoRouterHelper(context).pushReplacement('/new_account/recovery_key',
|
GoRouterHelper(context).pushReplacement('/new_account/recovery_key',
|
||||||
extra: [writableSuperIdentity, accountSpec.name]);
|
extra: [writableSuperIdentity, accountSpec.name]);
|
||||||
|
|
||||||
|
return true;
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -108,6 +82,7 @@ class _NewAccountPageState extends WindowSetupState<NewAccountPage> {
|
|||||||
context: context, error: e, stackTrace: st);
|
context: context, error: e, stackTrace: st);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
46
lib/app.dart
46
lib/app.dart
@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:flutter_translate/flutter_translate.dart';
|
import 'package:flutter_translate/flutter_translate.dart';
|
||||||
import 'package:form_builder_validators/form_builder_validators.dart';
|
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
@ -163,23 +164,34 @@ class VeilidChatApp extends StatelessWidget {
|
|||||||
scale.primaryScale.subtleBackground,
|
scale.primaryScale.subtleBackground,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return DecoratedBox(
|
return Stack(
|
||||||
decoration: BoxDecoration(gradient: gradient),
|
fit: StackFit.expand,
|
||||||
child: MaterialApp.router(
|
alignment: Alignment.center,
|
||||||
scrollBehavior: const ScrollBehaviorModified(),
|
children: [
|
||||||
debugShowCheckedModeBanner: false,
|
DecoratedBox(
|
||||||
routerConfig: context.read<RouterCubit>().router(),
|
decoration: BoxDecoration(gradient: gradient)),
|
||||||
title: translate('app.title'),
|
SvgPicture.asset(
|
||||||
theme: theme,
|
'assets/images/grid.svg',
|
||||||
localizationsDelegates: [
|
fit: BoxFit.cover,
|
||||||
GlobalMaterialLocalizations.delegate,
|
colorFilter: overlayFilter,
|
||||||
GlobalWidgetsLocalizations.delegate,
|
),
|
||||||
FormBuilderLocalizations.delegate,
|
MaterialApp.router(
|
||||||
localizationDelegate
|
scrollBehavior: const ScrollBehaviorModified(),
|
||||||
],
|
debugShowCheckedModeBanner: false,
|
||||||
supportedLocales: localizationDelegate.supportedLocales,
|
routerConfig: context.read<RouterCubit>().router(),
|
||||||
locale: localizationDelegate.currentLocale,
|
title: translate('app.title'),
|
||||||
));
|
theme: theme,
|
||||||
|
localizationsDelegates: [
|
||||||
|
GlobalMaterialLocalizations.delegate,
|
||||||
|
GlobalWidgetsLocalizations.delegate,
|
||||||
|
FormBuilderLocalizations.delegate,
|
||||||
|
localizationDelegate
|
||||||
|
],
|
||||||
|
supportedLocales:
|
||||||
|
localizationDelegate.supportedLocales,
|
||||||
|
locale: localizationDelegate.currentLocale,
|
||||||
|
)
|
||||||
|
]);
|
||||||
})),
|
})),
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
|
@ -147,8 +147,7 @@ class ChatComponentWidget extends StatelessWidget {
|
|||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
DecoratedBox(
|
DecoratedBox(
|
||||||
decoration:
|
decoration: const BoxDecoration(color: Colors.transparent),
|
||||||
BoxDecoration(color: scale.primaryScale.subtleBackground),
|
|
||||||
child: NotificationListener<ScrollNotification>(
|
child: NotificationListener<ScrollNotification>(
|
||||||
onNotification: (notification) {
|
onNotification: (notification) {
|
||||||
if (chatComponentCubit.scrollOffset != 0) {
|
if (chatComponentCubit.scrollOffset != 0) {
|
||||||
|
@ -16,7 +16,7 @@ class NoConversationWidget extends StatelessWidget {
|
|||||||
|
|
||||||
return DecoratedBox(
|
return DecoratedBox(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: scale.primaryScale.appBackground,
|
color: scale.primaryScale.appBackground.withAlpha(192),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
@ -24,14 +24,14 @@ class NoConversationWidget extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
Icons.diversity_3,
|
Icons.diversity_3,
|
||||||
color: scale.primaryScale.subtleBorder,
|
color: scale.primaryScale.appText.withAlpha(127),
|
||||||
size: 48,
|
size: 48,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
translate('chat.start_a_conversation'),
|
translate('chat.start_a_conversation'),
|
||||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
color: scale.primaryScale.subtleBorder,
|
color: scale.primaryScale.appText.withAlpha(127),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -44,20 +44,22 @@ class ChatSingleContactItemWidget extends StatelessWidget {
|
|||||||
: _contact.profile.availability;
|
: _contact.profile.availability;
|
||||||
|
|
||||||
final scaleTileTheme = scaleTheme.tileTheme(
|
final scaleTileTheme = scaleTheme.tileTheme(
|
||||||
disabled: _disabled,
|
disabled: _disabled,
|
||||||
selected: selected,
|
selected: selected,
|
||||||
scaleKind: ScaleKind.secondary);
|
);
|
||||||
|
|
||||||
final avatar = AvatarWidget(
|
final avatar = AvatarWidget(
|
||||||
name: name,
|
name: name,
|
||||||
size: 34,
|
size: 34,
|
||||||
borderColor: scaleTileTheme.borderColor,
|
borderColor: scaleTheme.config.useVisualIndicators
|
||||||
|
? scaleTheme.scheme.primaryScale.primaryText
|
||||||
|
: scaleTheme.scheme.primaryScale.subtleBorder,
|
||||||
foregroundColor: _disabled
|
foregroundColor: _disabled
|
||||||
? scaleTheme.scheme.grayScale.primaryText
|
? scaleTheme.scheme.grayScale.primaryText
|
||||||
: scaleTheme.scheme.secondaryScale.primaryText,
|
: scaleTheme.scheme.primaryScale.primaryText,
|
||||||
backgroundColor: _disabled
|
backgroundColor: _disabled
|
||||||
? scaleTheme.scheme.grayScale.primary
|
? scaleTheme.scheme.grayScale.primary
|
||||||
: scaleTheme.scheme.secondaryScale.primary,
|
: scaleTheme.scheme.primaryScale.primary,
|
||||||
scaleConfig: scaleTheme.config,
|
scaleConfig: scaleTheme.config,
|
||||||
textStyle: theme.textTheme.titleLarge!,
|
textStyle: theme.textTheme.titleLarge!,
|
||||||
);
|
);
|
||||||
@ -66,7 +68,7 @@ class ChatSingleContactItemWidget extends StatelessWidget {
|
|||||||
key: ValueKey(_localConversationRecordKey),
|
key: ValueKey(_localConversationRecordKey),
|
||||||
disabled: _disabled,
|
disabled: _disabled,
|
||||||
selected: selected,
|
selected: selected,
|
||||||
tileScale: ScaleKind.secondary,
|
tileScale: ScaleKind.primary,
|
||||||
title: title,
|
title: title,
|
||||||
subtitle: subtitle,
|
subtitle: subtitle,
|
||||||
leading: avatar,
|
leading: avatar,
|
||||||
|
@ -59,6 +59,7 @@ class ContactInvitationListCubit
|
|||||||
{required proto.Profile profile,
|
{required proto.Profile profile,
|
||||||
required EncryptionKeyType encryptionKeyType,
|
required EncryptionKeyType encryptionKeyType,
|
||||||
required String encryptionKey,
|
required String encryptionKey,
|
||||||
|
required String recipient,
|
||||||
required String message,
|
required String message,
|
||||||
required Timestamp? expiration}) async {
|
required Timestamp? expiration}) async {
|
||||||
final pool = DHTRecordPool.instance;
|
final pool = DHTRecordPool.instance;
|
||||||
@ -154,7 +155,8 @@ class ContactInvitationListCubit
|
|||||||
..localConversationRecordKey = localConversation.key.toProto()
|
..localConversationRecordKey = localConversation.key.toProto()
|
||||||
..expiration = expiration?.toInt64() ?? Int64.ZERO
|
..expiration = expiration?.toInt64() ?? Int64.ZERO
|
||||||
..invitation = signedContactInvitationBytes
|
..invitation = signedContactInvitationBytes
|
||||||
..message = message;
|
..message = message
|
||||||
|
..recipient = recipient;
|
||||||
|
|
||||||
// Add ContactInvitationRecord to account's list
|
// Add ContactInvitationRecord to account's list
|
||||||
await operateWriteEventual((writer) async {
|
await operateWriteEventual((writer) async {
|
||||||
|
@ -5,5 +5,5 @@ import 'package:veilid_support/veilid_support.dart';
|
|||||||
|
|
||||||
class InvitationGeneratorCubit extends FutureCubit<(Uint8List, TypedKey)> {
|
class InvitationGeneratorCubit extends FutureCubit<(Uint8List, TypedKey)> {
|
||||||
InvitationGeneratorCubit(super.fut);
|
InvitationGeneratorCubit(super.fut);
|
||||||
InvitationGeneratorCubit.value(super.v) : super.value();
|
InvitationGeneratorCubit.value(super.state) : super.value();
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:auto_size_text/auto_size_text.dart';
|
||||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||||
import 'package:basic_utils/basic_utils.dart';
|
import 'package:basic_utils/basic_utils.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
@ -20,11 +21,13 @@ import '../contact_invitation.dart';
|
|||||||
class ContactInvitationDisplayDialog extends StatelessWidget {
|
class ContactInvitationDisplayDialog extends StatelessWidget {
|
||||||
const ContactInvitationDisplayDialog._({
|
const ContactInvitationDisplayDialog._({
|
||||||
required this.locator,
|
required this.locator,
|
||||||
|
required this.recipient,
|
||||||
required this.message,
|
required this.message,
|
||||||
required this.fingerprint,
|
required this.fingerprint,
|
||||||
});
|
});
|
||||||
|
|
||||||
final Locator locator;
|
final Locator locator;
|
||||||
|
final String recipient;
|
||||||
final String message;
|
final String message;
|
||||||
final String fingerprint;
|
final String fingerprint;
|
||||||
|
|
||||||
@ -32,18 +35,22 @@ class ContactInvitationDisplayDialog extends StatelessWidget {
|
|||||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
super.debugFillProperties(properties);
|
super.debugFillProperties(properties);
|
||||||
properties
|
properties
|
||||||
|
..add(StringProperty('recipient', recipient))
|
||||||
..add(StringProperty('message', message))
|
..add(StringProperty('message', message))
|
||||||
..add(DiagnosticsProperty<Locator>('locator', locator))
|
..add(DiagnosticsProperty<Locator>('locator', locator))
|
||||||
..add(StringProperty('fingerprint', fingerprint));
|
..add(StringProperty('fingerprint', fingerprint));
|
||||||
}
|
}
|
||||||
|
|
||||||
String makeTextInvite(String message, Uint8List data) {
|
String makeTextInvite(String recipient, String message, Uint8List data) {
|
||||||
final invite = StringUtils.addCharAtPosition(
|
final invite = StringUtils.addCharAtPosition(
|
||||||
base64UrlNoPadEncode(data), '\n', 40,
|
base64UrlNoPadEncode(data), '\n', 40,
|
||||||
repeat: true);
|
repeat: true);
|
||||||
|
final to = recipient.isNotEmpty
|
||||||
|
? '${translate('invitiation_dialog.to')}: $recipient\n'
|
||||||
|
: '';
|
||||||
final msg = message.isNotEmpty ? '$message\n' : '';
|
final msg = message.isNotEmpty ? '$message\n' : '';
|
||||||
|
return '$to'
|
||||||
return '$msg'
|
'$msg'
|
||||||
'--- BEGIN VEILIDCHAT CONTACT INVITE ----\n'
|
'--- BEGIN VEILIDCHAT CONTACT INVITE ----\n'
|
||||||
'$invite\n'
|
'$invite\n'
|
||||||
'---- END VEILIDCHAT CONTACT INVITE -----\n'
|
'---- END VEILIDCHAT CONTACT INVITE -----\n'
|
||||||
@ -62,6 +69,10 @@ class ContactInvitationDisplayDialog extends StatelessWidget {
|
|||||||
final cardsize =
|
final cardsize =
|
||||||
min<double>(MediaQuery.of(context).size.shortestSide - 48.0, 400);
|
min<double>(MediaQuery.of(context).size.shortestSide - 48.0, 400);
|
||||||
|
|
||||||
|
final fingerprintText =
|
||||||
|
'${translate('create_invitation_dialog.fingerprint')}\n'
|
||||||
|
'$fingerprint';
|
||||||
|
|
||||||
return BlocListener<ContactInvitationListCubit,
|
return BlocListener<ContactInvitationListCubit,
|
||||||
ContactInvitiationListState>(
|
ContactInvitiationListState>(
|
||||||
bloc: locator<ContactInvitationListCubit>(),
|
bloc: locator<ContactInvitationListCubit>(),
|
||||||
@ -110,14 +121,21 @@ class ContactInvitationDisplayDialog extends StatelessWidget {
|
|||||||
errorCorrectLevel:
|
errorCorrectLevel:
|
||||||
QrErrorCorrectLevel.L)),
|
QrErrorCorrectLevel.L)),
|
||||||
).expanded(),
|
).expanded(),
|
||||||
Text(message,
|
if (recipient.isNotEmpty)
|
||||||
softWrap: true,
|
AutoSizeText(recipient,
|
||||||
style: textTheme.labelLarge!
|
softWrap: true,
|
||||||
.copyWith(color: Colors.black))
|
maxLines: 2,
|
||||||
.paddingAll(8),
|
style: textTheme.labelLarge!
|
||||||
Text(
|
.copyWith(color: Colors.black))
|
||||||
'${translate('create_invitation_dialog.fingerprint')}\n'
|
.paddingAll(8),
|
||||||
'$fingerprint',
|
if (message.isNotEmpty)
|
||||||
|
Text(message,
|
||||||
|
softWrap: true,
|
||||||
|
maxLines: 2,
|
||||||
|
style: textTheme.labelMedium!
|
||||||
|
.copyWith(color: Colors.black))
|
||||||
|
.paddingAll(8),
|
||||||
|
Text(fingerprintText,
|
||||||
softWrap: true,
|
softWrap: true,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: textTheme.labelSmall!.copyWith(
|
style: textTheme.labelSmall!.copyWith(
|
||||||
@ -137,7 +155,8 @@ class ContactInvitationDisplayDialog extends StatelessWidget {
|
|||||||
text: translate('create_invitation_dialog'
|
text: translate('create_invitation_dialog'
|
||||||
'.invitation_copied'));
|
'.invitation_copied'));
|
||||||
await Clipboard.setData(ClipboardData(
|
await Clipboard.setData(ClipboardData(
|
||||||
text: makeTextInvite(message, data.$1)));
|
text: makeTextInvite(
|
||||||
|
recipient, message, data.$1)));
|
||||||
},
|
},
|
||||||
).paddingAll(16),
|
).paddingAll(16),
|
||||||
]),
|
]),
|
||||||
@ -148,6 +167,7 @@ class ContactInvitationDisplayDialog extends StatelessWidget {
|
|||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
required Locator locator,
|
required Locator locator,
|
||||||
required InvitationGeneratorCubit Function(BuildContext) create,
|
required InvitationGeneratorCubit Function(BuildContext) create,
|
||||||
|
required String recipient,
|
||||||
required String message,
|
required String message,
|
||||||
}) async {
|
}) async {
|
||||||
final fingerprint =
|
final fingerprint =
|
||||||
@ -159,6 +179,7 @@ class ContactInvitationDisplayDialog extends StatelessWidget {
|
|||||||
create: create,
|
create: create,
|
||||||
child: ContactInvitationDisplayDialog._(
|
child: ContactInvitationDisplayDialog._(
|
||||||
locator: locator,
|
locator: locator,
|
||||||
|
recipient: recipient,
|
||||||
message: message,
|
message: message,
|
||||||
fingerprint: fingerprint,
|
fingerprint: fingerprint,
|
||||||
)));
|
)));
|
||||||
|
@ -37,14 +37,19 @@ class ContactInvitationItemWidget extends StatelessWidget {
|
|||||||
final tileDisabled =
|
final tileDisabled =
|
||||||
disabled || context.watch<ContactInvitationListCubit>().isBusy;
|
disabled || context.watch<ContactInvitationListCubit>().isBusy;
|
||||||
|
|
||||||
|
var title = translate('contact_list.invitation');
|
||||||
|
if (contactInvitationRecord.recipient.isNotEmpty) {
|
||||||
|
title = contactInvitationRecord.recipient;
|
||||||
|
} else if (contactInvitationRecord.message.isNotEmpty) {
|
||||||
|
title = contactInvitationRecord.message;
|
||||||
|
}
|
||||||
|
|
||||||
return SliderTile(
|
return SliderTile(
|
||||||
key: ObjectKey(contactInvitationRecord),
|
key: ObjectKey(contactInvitationRecord),
|
||||||
disabled: tileDisabled,
|
disabled: tileDisabled,
|
||||||
selected: selected,
|
selected: selected,
|
||||||
tileScale: ScaleKind.primary,
|
tileScale: ScaleKind.primary,
|
||||||
title: contactInvitationRecord.message.isEmpty
|
title: title,
|
||||||
? translate('contact_list.invitation')
|
|
||||||
: contactInvitationRecord.message,
|
|
||||||
leading: const Icon(Icons.person_add),
|
leading: const Icon(Icons.person_add),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
if (!context.mounted) {
|
if (!context.mounted) {
|
||||||
@ -53,6 +58,7 @@ class ContactInvitationItemWidget extends StatelessWidget {
|
|||||||
await ContactInvitationDisplayDialog.show(
|
await ContactInvitationDisplayDialog.show(
|
||||||
context: context,
|
context: context,
|
||||||
locator: context.read,
|
locator: context.read,
|
||||||
|
recipient: contactInvitationRecord.recipient,
|
||||||
message: contactInvitationRecord.message,
|
message: contactInvitationRecord.message,
|
||||||
create: (context) => InvitationGeneratorCubit.value((
|
create: (context) => InvitationGeneratorCubit.value((
|
||||||
Uint8List.fromList(contactInvitationRecord.invitation),
|
Uint8List.fromList(contactInvitationRecord.invitation),
|
||||||
@ -62,7 +68,7 @@ class ContactInvitationItemWidget extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
endActions: [
|
endActions: [
|
||||||
SliderTileAction(
|
SliderTileAction(
|
||||||
icon: Icons.delete,
|
// icon: Icons.delete,
|
||||||
label: translate('button.delete'),
|
label: translate('button.delete'),
|
||||||
actionScale: ScaleKind.tertiary,
|
actionScale: ScaleKind.tertiary,
|
||||||
onPressed: (context) async {
|
onPressed: (context) async {
|
||||||
|
@ -18,7 +18,7 @@ class CreateInvitationDialog extends StatefulWidget {
|
|||||||
const CreateInvitationDialog._({required this.locator});
|
const CreateInvitationDialog._({required this.locator});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
CreateInvitationDialogState createState() => CreateInvitationDialogState();
|
State<CreateInvitationDialog> createState() => _CreateInvitationDialogState();
|
||||||
|
|
||||||
static Future<void> show(BuildContext context) async {
|
static Future<void> show(BuildContext context) async {
|
||||||
await StyledDialog.show<void>(
|
await StyledDialog.show<void>(
|
||||||
@ -36,8 +36,9 @@ class CreateInvitationDialog extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CreateInvitationDialogState extends State<CreateInvitationDialog> {
|
class _CreateInvitationDialogState extends State<CreateInvitationDialog> {
|
||||||
late final TextEditingController _messageTextController;
|
late final TextEditingController _messageTextController;
|
||||||
|
late final TextEditingController _recipientTextController;
|
||||||
|
|
||||||
EncryptionKeyType _encryptionKeyType = EncryptionKeyType.none;
|
EncryptionKeyType _encryptionKeyType = EncryptionKeyType.none;
|
||||||
String _encryptionKey = '';
|
String _encryptionKey = '';
|
||||||
@ -51,6 +52,7 @@ class CreateInvitationDialogState extends State<CreateInvitationDialog> {
|
|||||||
_messageTextController = TextEditingController(
|
_messageTextController = TextEditingController(
|
||||||
text: translate('create_invitation_dialog.connect_with_me',
|
text: translate('create_invitation_dialog.connect_with_me',
|
||||||
args: {'name': name}));
|
args: {'name': name}));
|
||||||
|
_recipientTextController = TextEditingController();
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,6 +156,7 @@ class CreateInvitationDialogState extends State<CreateInvitationDialog> {
|
|||||||
profile: profile,
|
profile: profile,
|
||||||
encryptionKeyType: _encryptionKeyType,
|
encryptionKeyType: _encryptionKeyType,
|
||||||
encryptionKey: _encryptionKey,
|
encryptionKey: _encryptionKey,
|
||||||
|
recipient: _recipientTextController.text,
|
||||||
message: _messageTextController.text,
|
message: _messageTextController.text,
|
||||||
expiration: _expiration);
|
expiration: _expiration);
|
||||||
|
|
||||||
@ -162,6 +165,7 @@ class CreateInvitationDialogState extends State<CreateInvitationDialog> {
|
|||||||
await ContactInvitationDisplayDialog.show(
|
await ContactInvitationDisplayDialog.show(
|
||||||
context: context,
|
context: context,
|
||||||
locator: widget.locator,
|
locator: widget.locator,
|
||||||
|
recipient: _recipientTextController.text,
|
||||||
message: _messageTextController.text,
|
message: _messageTextController.text,
|
||||||
create: (context) => InvitationGeneratorCubit(generator));
|
create: (context) => InvitationGeneratorCubit(generator));
|
||||||
}
|
}
|
||||||
@ -176,6 +180,7 @@ class CreateInvitationDialogState extends State<CreateInvitationDialog> {
|
|||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
//final scale = theme.extension<ScaleScheme>()!;
|
//final scale = theme.extension<ScaleScheme>()!;
|
||||||
final textTheme = theme.textTheme;
|
final textTheme = theme.textTheme;
|
||||||
|
|
||||||
return ConstrainedBox(
|
return ConstrainedBox(
|
||||||
constraints:
|
constraints:
|
||||||
BoxConstraints(maxHeight: maxDialogHeight, maxWidth: maxDialogWidth),
|
BoxConstraints(maxHeight: maxDialogHeight, maxWidth: maxDialogWidth),
|
||||||
@ -185,19 +190,34 @@ class CreateInvitationDialogState extends State<CreateInvitationDialog> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
TextField(
|
||||||
translate('create_invitation_dialog.message_to_contact'),
|
controller: _recipientTextController,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
inputFormatters: [
|
||||||
|
LengthLimitingTextInputFormatter(128),
|
||||||
|
],
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText:
|
||||||
|
translate('create_invitation_dialog.recipient_hint'),
|
||||||
|
labelText:
|
||||||
|
translate('create_invitation_dialog.recipient_name'),
|
||||||
|
helperText:
|
||||||
|
translate('create_invitation_dialog.recipient_helper')),
|
||||||
).paddingAll(8),
|
).paddingAll(8),
|
||||||
|
const SizedBox(height: 10),
|
||||||
TextField(
|
TextField(
|
||||||
controller: _messageTextController,
|
controller: _messageTextController,
|
||||||
inputFormatters: [
|
inputFormatters: [
|
||||||
LengthLimitingTextInputFormatter(128),
|
LengthLimitingTextInputFormatter(128),
|
||||||
],
|
],
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
//border: const OutlineInputBorder(),
|
hintText: translate('create_invitation_dialog.message_hint'),
|
||||||
hintText:
|
labelText:
|
||||||
translate('create_invitation_dialog.enter_message_hint'),
|
translate('create_invitation_dialog.message_label'),
|
||||||
labelText: translate('create_invitation_dialog.message')),
|
helperText:
|
||||||
|
translate('create_invitation_dialog.message_helper')),
|
||||||
).paddingAll(8),
|
).paddingAll(8),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
Text(translate('create_invitation_dialog.protect_this_invitation'),
|
Text(translate('create_invitation_dialog.protect_this_invitation'),
|
||||||
@ -228,7 +248,9 @@ class CreateInvitationDialogState extends State<CreateInvitationDialog> {
|
|||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: _onGenerateButtonPressed,
|
onPressed: _recipientTextController.text.isNotEmpty
|
||||||
|
? _onGenerateButtonPressed
|
||||||
|
: null,
|
||||||
child: Text(
|
child: Text(
|
||||||
translate('create_invitation_dialog.generate'),
|
translate('create_invitation_dialog.generate'),
|
||||||
).paddingAll(16),
|
).paddingAll(16),
|
||||||
@ -244,11 +266,4 @@ class CreateInvitationDialogState extends State<CreateInvitationDialog> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
|
||||||
super.debugFillProperties(properties);
|
|
||||||
properties.add(DiagnosticsProperty<TextEditingController>(
|
|
||||||
'messageTextController', _messageTextController));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,71 +0,0 @@
|
|||||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_translate/flutter_translate.dart';
|
|
||||||
|
|
||||||
import '../../theme/theme.dart';
|
|
||||||
import 'create_invitation_dialog.dart';
|
|
||||||
import 'paste_invitation_dialog.dart';
|
|
||||||
import 'scan_invitation_dialog.dart';
|
|
||||||
|
|
||||||
Widget newContactBottomSheetBuilder(
|
|
||||||
BuildContext sheetContext, BuildContext context) {
|
|
||||||
final theme = Theme.of(sheetContext);
|
|
||||||
final scale = theme.extension<ScaleScheme>()!;
|
|
||||||
|
|
||||||
return KeyboardListener(
|
|
||||||
focusNode: FocusNode(),
|
|
||||||
onKeyEvent: (ke) {
|
|
||||||
if (ke.logicalKey == LogicalKeyboardKey.escape) {
|
|
||||||
Navigator.pop(sheetContext);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: styledBottomSheet(
|
|
||||||
context: context,
|
|
||||||
title: translate('add_contact_sheet.new_contact'),
|
|
||||||
child: SizedBox(
|
|
||||||
height: 160,
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
||||||
children: [
|
|
||||||
Column(children: [
|
|
||||||
IconButton(
|
|
||||||
onPressed: () async {
|
|
||||||
Navigator.pop(sheetContext);
|
|
||||||
await CreateInvitationDialog.show(context);
|
|
||||||
},
|
|
||||||
iconSize: 64,
|
|
||||||
icon: const Icon(Icons.contact_page),
|
|
||||||
color: scale.primaryScale.hoverBorder),
|
|
||||||
Text(
|
|
||||||
translate('add_contact_sheet.create_invite'),
|
|
||||||
)
|
|
||||||
]),
|
|
||||||
Column(children: [
|
|
||||||
IconButton(
|
|
||||||
onPressed: () async {
|
|
||||||
Navigator.pop(sheetContext);
|
|
||||||
await ScanInvitationDialog.show(context);
|
|
||||||
},
|
|
||||||
iconSize: 64,
|
|
||||||
icon: const Icon(Icons.qr_code_scanner),
|
|
||||||
color: scale.primaryScale.hoverBorder),
|
|
||||||
Text(
|
|
||||||
translate('add_contact_sheet.scan_invite'),
|
|
||||||
)
|
|
||||||
]),
|
|
||||||
Column(children: [
|
|
||||||
IconButton(
|
|
||||||
onPressed: () async {
|
|
||||||
Navigator.pop(sheetContext);
|
|
||||||
await PasteInvitationDialog.show(context);
|
|
||||||
},
|
|
||||||
iconSize: 64,
|
|
||||||
icon: const Icon(Icons.paste),
|
|
||||||
color: scale.primaryScale.hoverBorder),
|
|
||||||
Text(
|
|
||||||
translate('add_contact_sheet.paste_invite'),
|
|
||||||
)
|
|
||||||
])
|
|
||||||
]).paddingAll(16))));
|
|
||||||
}
|
|
@ -3,6 +3,5 @@ export 'contact_invitation_item_widget.dart';
|
|||||||
export 'contact_invitation_list_widget.dart';
|
export 'contact_invitation_list_widget.dart';
|
||||||
export 'create_invitation_dialog.dart';
|
export 'create_invitation_dialog.dart';
|
||||||
export 'invitation_dialog.dart';
|
export 'invitation_dialog.dart';
|
||||||
export 'new_contact_bottom_sheet.dart';
|
|
||||||
export 'paste_invitation_dialog.dart';
|
export 'paste_invitation_dialog.dart';
|
||||||
export 'scan_invitation_dialog.dart';
|
export 'scan_invitation_dialog.dart';
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
export 'cubits/cubits.dart';
|
export 'cubits/cubits.dart';
|
||||||
|
export 'models/models.dart';
|
||||||
export 'views/views.dart';
|
export 'views/views.dart';
|
||||||
|
@ -8,6 +8,7 @@ import 'package:veilid_support/veilid_support.dart';
|
|||||||
import '../../account_manager/account_manager.dart';
|
import '../../account_manager/account_manager.dart';
|
||||||
import '../../proto/proto.dart' as proto;
|
import '../../proto/proto.dart' as proto;
|
||||||
import '../../tools/tools.dart';
|
import '../../tools/tools.dart';
|
||||||
|
import '../models/models.dart';
|
||||||
|
|
||||||
//////////////////////////////////////////////////
|
//////////////////////////////////////////////////
|
||||||
// Mutable state for per-account contacts
|
// Mutable state for per-account contacts
|
||||||
@ -81,9 +82,7 @@ class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
|
|||||||
|
|
||||||
Future<void> updateContactFields({
|
Future<void> updateContactFields({
|
||||||
required TypedKey localConversationRecordKey,
|
required TypedKey localConversationRecordKey,
|
||||||
String? nickname,
|
required ContactSpec updatedContactSpec,
|
||||||
String? notes,
|
|
||||||
bool? showAvailability,
|
|
||||||
}) async {
|
}) async {
|
||||||
// Update contact's locally-modifiable fields
|
// Update contact's locally-modifiable fields
|
||||||
await operateWriteEventual((writer) async {
|
await operateWriteEventual((writer) async {
|
||||||
@ -92,17 +91,7 @@ class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
|
|||||||
if (c != null &&
|
if (c != null &&
|
||||||
c.localConversationRecordKey.toVeilid() ==
|
c.localConversationRecordKey.toVeilid() ==
|
||||||
localConversationRecordKey) {
|
localConversationRecordKey) {
|
||||||
final newContact = c.deepCopy();
|
final newContact = await updatedContactSpec.updateProto(c);
|
||||||
|
|
||||||
if (nickname != null) {
|
|
||||||
newContact.nickname = nickname;
|
|
||||||
}
|
|
||||||
if (notes != null) {
|
|
||||||
newContact.notes = notes;
|
|
||||||
}
|
|
||||||
if (showAvailability != null) {
|
|
||||||
newContact.showAvailability = showAvailability;
|
|
||||||
}
|
|
||||||
|
|
||||||
final updated = await writer.tryWriteItemProtobuf(
|
final updated = await writer.tryWriteItemProtobuf(
|
||||||
proto.Contact.fromBuffer, pos, newContact);
|
proto.Contact.fromBuffer, pos, newContact);
|
||||||
|
37
lib/contacts/models/contact_spec.dart
Normal file
37
lib/contacts/models/contact_spec.dart
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:protobuf/protobuf.dart';
|
||||||
|
|
||||||
|
import '../../proto/proto.dart' as proto;
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
class ContactSpec extends Equatable {
|
||||||
|
const ContactSpec({
|
||||||
|
required this.nickname,
|
||||||
|
required this.notes,
|
||||||
|
required this.showAvailability,
|
||||||
|
});
|
||||||
|
|
||||||
|
ContactSpec.fromProto(proto.Contact p)
|
||||||
|
: nickname = p.nickname,
|
||||||
|
notes = p.notes,
|
||||||
|
showAvailability = p.showAvailability;
|
||||||
|
|
||||||
|
Future<proto.Contact> updateProto(proto.Contact old) async {
|
||||||
|
final newProto = old.deepCopy()
|
||||||
|
..nickname = nickname
|
||||||
|
..notes = notes
|
||||||
|
..showAvailability = showAvailability;
|
||||||
|
|
||||||
|
return newProto;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
final String nickname;
|
||||||
|
final String notes;
|
||||||
|
final bool showAvailability;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [nickname, notes, showAvailability];
|
||||||
|
}
|
1
lib/contacts/models/models.dart
Normal file
1
lib/contacts/models/models.dart
Normal file
@ -0,0 +1 @@
|
|||||||
|
export 'contact_spec.dart';
|
@ -10,11 +10,11 @@ class AvailabilityWidget extends StatelessWidget {
|
|||||||
{required this.availability,
|
{required this.availability,
|
||||||
required this.color,
|
required this.color,
|
||||||
this.vertical = true,
|
this.vertical = true,
|
||||||
this.iconSize = 32,
|
this.iconSize = 24,
|
||||||
super.key});
|
super.key});
|
||||||
|
|
||||||
static Widget availabilityIcon(proto.Availability availability, Color color,
|
static Widget availabilityIcon(proto.Availability availability, Color color,
|
||||||
{double size = 32}) {
|
{double size = 24}) {
|
||||||
late final Widget iconData;
|
late final Widget iconData;
|
||||||
switch (availability) {
|
switch (availability) {
|
||||||
case proto.Availability.AVAILABILITY_AWAY:
|
case proto.Availability.AVAILABILITY_AWAY:
|
||||||
@ -70,7 +70,7 @@ class AvailabilityWidget extends StatelessWidget {
|
|||||||
])
|
])
|
||||||
: Row(mainAxisSize: MainAxisSize.min, children: [
|
: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||||
icon,
|
icon,
|
||||||
Text(name, style: textTheme.labelSmall!.copyWith(color: color))
|
Text(name, style: textTheme.labelLarge!.copyWith(color: color))
|
||||||
.paddingLTRB(8, 0, 0, 0)
|
.paddingLTRB(8, 0, 0, 0)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:flutter_translate/flutter_translate.dart';
|
||||||
|
|
||||||
import '../../proto/proto.dart' as proto;
|
import '../../proto/proto.dart' as proto;
|
||||||
|
import '../../tools/tools.dart';
|
||||||
import '../contacts.dart';
|
import '../contacts.dart';
|
||||||
|
|
||||||
class ContactDetailsWidget extends StatefulWidget {
|
class ContactDetailsWidget extends StatefulWidget {
|
||||||
const ContactDetailsWidget({required this.contact, super.key});
|
const ContactDetailsWidget(
|
||||||
final proto.Contact contact;
|
{required this.contact, this.onModifiedState, super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ContactDetailsWidget> createState() => _ContactDetailsWidgetState();
|
State<ContactDetailsWidget> createState() => _ContactDetailsWidgetState();
|
||||||
@ -15,8 +17,14 @@ class ContactDetailsWidget extends StatefulWidget {
|
|||||||
@override
|
@override
|
||||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
super.debugFillProperties(properties);
|
super.debugFillProperties(properties);
|
||||||
properties.add(DiagnosticsProperty<proto.Contact>('contact', contact));
|
properties
|
||||||
|
..add(DiagnosticsProperty<proto.Contact>('contact', contact))
|
||||||
|
..add(ObjectFlagProperty<void Function(bool p1)?>.has(
|
||||||
|
'onModifiedState', onModifiedState));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final proto.Contact contact;
|
||||||
|
final void Function(bool)? onModifiedState;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ContactDetailsWidgetState extends State<ContactDetailsWidget>
|
class _ContactDetailsWidgetState extends State<ContactDetailsWidget>
|
||||||
@ -24,18 +32,21 @@ class _ContactDetailsWidgetState extends State<ContactDetailsWidget>
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => SingleChildScrollView(
|
Widget build(BuildContext context) => SingleChildScrollView(
|
||||||
child: EditContactForm(
|
child: EditContactForm(
|
||||||
formKey: GlobalKey(),
|
|
||||||
contact: widget.contact,
|
contact: widget.contact,
|
||||||
onSubmit: (fbs) async {
|
submitText: translate('button.update'),
|
||||||
|
submitDisabledText: translate('button.waiting_for_network'),
|
||||||
|
onModifiedState: widget.onModifiedState,
|
||||||
|
onSubmit: (updatedContactSpec) async {
|
||||||
final contactList = context.read<ContactListCubit>();
|
final contactList = context.read<ContactListCubit>();
|
||||||
await contactList.updateContactFields(
|
try {
|
||||||
localConversationRecordKey:
|
await contactList.updateContactFields(
|
||||||
widget.contact.localConversationRecordKey.toVeilid(),
|
localConversationRecordKey:
|
||||||
nickname: fbs.currentState
|
widget.contact.localConversationRecordKey.toVeilid(),
|
||||||
?.value[EditContactForm.formFieldNickname] as String,
|
updatedContactSpec: updatedContactSpec);
|
||||||
notes: fbs.currentState?.value[EditContactForm.formFieldNotes]
|
} on Exception catch (e) {
|
||||||
as String,
|
log.debug('error updating contact: $e', e);
|
||||||
showAvailability: fbs.currentState
|
return false;
|
||||||
?.value[EditContactForm.formFieldShowAvailability] as bool);
|
}
|
||||||
|
return true;
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ class ContactItemWidget extends StatelessWidget {
|
|||||||
size: 34,
|
size: 34,
|
||||||
borderColor: _disabled
|
borderColor: _disabled
|
||||||
? scale.grayScale.primaryText
|
? scale.grayScale.primaryText
|
||||||
: scale.primaryScale.primaryText,
|
: scale.primaryScale.subtleBorder,
|
||||||
foregroundColor: _disabled
|
foregroundColor: _disabled
|
||||||
? scale.grayScale.primaryText
|
? scale.grayScale.primaryText
|
||||||
: scale.primaryScale.primaryText,
|
: scale.primaryScale.primaryText,
|
||||||
@ -71,7 +71,7 @@ class ContactItemWidget extends StatelessWidget {
|
|||||||
endActions: [
|
endActions: [
|
||||||
if (_onDoubleTap != null)
|
if (_onDoubleTap != null)
|
||||||
SliderTileAction(
|
SliderTileAction(
|
||||||
icon: Icons.edit,
|
//icon: Icons.edit,
|
||||||
label: translate('button.edit'),
|
label: translate('button.edit'),
|
||||||
actionScale: ScaleKind.secondary,
|
actionScale: ScaleKind.secondary,
|
||||||
onPressed: (_context) =>
|
onPressed: (_context) =>
|
||||||
@ -81,7 +81,7 @@ class ContactItemWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
if (_onDelete != null)
|
if (_onDelete != null)
|
||||||
SliderTileAction(
|
SliderTileAction(
|
||||||
icon: Icons.delete,
|
//icon: Icons.delete,
|
||||||
label: translate('button.delete'),
|
label: translate('button.delete'),
|
||||||
actionScale: ScaleKind.tertiary,
|
actionScale: ScaleKind.tertiary,
|
||||||
onPressed: (_context) =>
|
onPressed: (_context) =>
|
||||||
|
@ -74,13 +74,10 @@ class _ContactsBrowserState extends State<ContactsBrowser>
|
|||||||
|
|
||||||
final menuIconColor = scaleConfig.preferBorders
|
final menuIconColor = scaleConfig.preferBorders
|
||||||
? scale.primaryScale.hoverBorder
|
? scale.primaryScale.hoverBorder
|
||||||
: scale.primaryScale.borderText;
|
: scale.primaryScale.hoverBorder;
|
||||||
final menuBackgroundColor = scaleConfig.preferBorders
|
final menuBackgroundColor = scaleConfig.preferBorders
|
||||||
? scale.primaryScale.elementBackground
|
? scale.primaryScale.elementBackground
|
||||||
: scale.primaryScale.border;
|
: scale.primaryScale.elementBackground;
|
||||||
// final menuHoverColor = scaleConfig.preferBorders
|
|
||||||
// ? scale.primaryScale.hoverElementBackground
|
|
||||||
// : scale.primaryScale.hoverBorder;
|
|
||||||
|
|
||||||
final menuBorderColor = scale.primaryScale.hoverBorder;
|
final menuBorderColor = scale.primaryScale.hoverBorder;
|
||||||
|
|
||||||
@ -149,13 +146,12 @@ class _ContactsBrowserState extends State<ContactsBrowser>
|
|||||||
},
|
},
|
||||||
iconSize: 32,
|
iconSize: 32,
|
||||||
icon: const Icon(Icons.contact_page),
|
icon: const Icon(Icons.contact_page),
|
||||||
color: scale.primaryScale.hoverBorder,
|
color: menuIconColor,
|
||||||
),
|
),
|
||||||
Text(translate('add_contact_sheet.create_invite'),
|
Text(translate('add_contact_sheet.create_invite'),
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: textTheme.labelSmall!
|
style: textTheme.labelSmall!.copyWith(color: menuIconColor))
|
||||||
.copyWith(color: scale.primaryScale.hoverBorder))
|
|
||||||
]),
|
]),
|
||||||
StarMenu(
|
StarMenu(
|
||||||
items: receiveInviteMenuItems,
|
items: receiveInviteMenuItems,
|
||||||
@ -171,13 +167,12 @@ class _ContactsBrowserState extends State<ContactsBrowser>
|
|||||||
icon: ImageIcon(
|
icon: ImageIcon(
|
||||||
const AssetImage('assets/images/handshake.png'),
|
const AssetImage('assets/images/handshake.png'),
|
||||||
size: 32,
|
size: 32,
|
||||||
color: scale.primaryScale.hoverBorder,
|
color: menuIconColor,
|
||||||
)),
|
)),
|
||||||
Text(translate('add_contact_sheet.receive_invite'),
|
Text(translate('add_contact_sheet.receive_invite'),
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: textTheme.labelSmall!
|
style: textTheme.labelSmall!.copyWith(color: menuIconColor))
|
||||||
.copyWith(color: scale.primaryScale.hoverBorder))
|
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
]).paddingAll(16);
|
]).paddingAll(16);
|
||||||
@ -274,8 +269,9 @@ class _ContactsBrowserState extends State<ContactsBrowser>
|
|||||||
case ContactsBrowserElementKind.invitation:
|
case ContactsBrowserElementKind.invitation:
|
||||||
final invitation = element.invitation!;
|
final invitation = element.invitation!;
|
||||||
return invitation.message
|
return invitation.message
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.contains(lowerValue);
|
.contains(lowerValue) ||
|
||||||
|
invitation.recipient.toLowerCase().contains(lowerValue);
|
||||||
}
|
}
|
||||||
}).toList()
|
}).toList()
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:async_tools/async_tools.dart';
|
||||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -11,6 +12,8 @@ import '../../proto/proto.dart' as proto;
|
|||||||
import '../../theme/theme.dart';
|
import '../../theme/theme.dart';
|
||||||
import '../contacts.dart';
|
import '../contacts.dart';
|
||||||
|
|
||||||
|
const _kDoBackArrow = 'doBackArrow';
|
||||||
|
|
||||||
class ContactsDialog extends StatefulWidget {
|
class ContactsDialog extends StatefulWidget {
|
||||||
const ContactsDialog._({required this.modalContext});
|
const ContactsDialog._({required this.modalContext});
|
||||||
|
|
||||||
@ -44,13 +47,8 @@ class _ContactsDialogState extends State<ContactsDialog> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
// final textTheme = theme.textTheme;
|
|
||||||
final scale = theme.extension<ScaleScheme>()!;
|
final scale = theme.extension<ScaleScheme>()!;
|
||||||
final scaleConfig = theme.extension<ScaleConfig>()!;
|
final appBarIconColor = scale.primaryScale.borderText;
|
||||||
|
|
||||||
final appBarIconColor = scaleConfig.useVisualIndicators
|
|
||||||
? scale.secondaryScale.border
|
|
||||||
: scale.secondaryScale.borderText;
|
|
||||||
|
|
||||||
final enableSplit = !isMobileWidth(context);
|
final enableSplit = !isMobileWidth(context);
|
||||||
final enableLeft = enableSplit || _selectedContact == null;
|
final enableLeft = enableSplit || _selectedContact == null;
|
||||||
@ -63,20 +61,22 @@ class _ContactsDialogState extends State<ContactsDialog> {
|
|||||||
title: Text(!enableSplit && enableRight
|
title: Text(!enableSplit && enableRight
|
||||||
? translate('contacts_dialog.edit_contact')
|
? translate('contacts_dialog.edit_contact')
|
||||||
: translate('contacts_dialog.contacts')),
|
: translate('contacts_dialog.contacts')),
|
||||||
leading: Navigator.canPop(context)
|
leading: IconButton(
|
||||||
? IconButton(
|
icon: const Icon(Icons.arrow_back),
|
||||||
icon: const Icon(Icons.arrow_back),
|
onPressed: () {
|
||||||
onPressed: () {
|
singleFuture((this, _kDoBackArrow), () async {
|
||||||
if (!enableSplit && enableRight) {
|
final confirmed = await _onContactSelected(null);
|
||||||
setState(() {
|
if (!enableSplit && enableRight) {
|
||||||
_selectedContact = null;
|
} else {
|
||||||
});
|
if (confirmed) {
|
||||||
} else {
|
if (context.mounted) {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
)
|
}
|
||||||
: null,
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
actions: [
|
actions: [
|
||||||
if (_selectedContact != null)
|
if (_selectedContact != null)
|
||||||
FittedBox(
|
FittedBox(
|
||||||
@ -85,9 +85,10 @@ class _ContactsDialogState extends State<ContactsDialog> {
|
|||||||
Column(mainAxisSize: MainAxisSize.min, children: [
|
Column(mainAxisSize: MainAxisSize.min, children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.chat_bubble),
|
icon: const Icon(Icons.chat_bubble),
|
||||||
|
color: appBarIconColor,
|
||||||
tooltip: translate('contacts_dialog.new_chat'),
|
tooltip: translate('contacts_dialog.new_chat'),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await onChatStarted(_selectedContact!);
|
await _onChatStarted(_selectedContact!);
|
||||||
}),
|
}),
|
||||||
Text(translate('contacts_dialog.new_chat'),
|
Text(translate('contacts_dialog.new_chat'),
|
||||||
style: theme.textTheme.labelSmall!
|
style: theme.textTheme.labelSmall!
|
||||||
@ -100,10 +101,11 @@ class _ContactsDialogState extends State<ContactsDialog> {
|
|||||||
Column(mainAxisSize: MainAxisSize.min, children: [
|
Column(mainAxisSize: MainAxisSize.min, children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.close),
|
icon: const Icon(Icons.close),
|
||||||
|
color: appBarIconColor,
|
||||||
tooltip:
|
tooltip:
|
||||||
translate('contacts_dialog.close_contact'),
|
translate('contacts_dialog.close_contact'),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await onContactSelected(null);
|
await _onContactSelected(null);
|
||||||
}),
|
}),
|
||||||
Text(translate('contacts_dialog.close_contact'),
|
Text(translate('contacts_dialog.close_contact'),
|
||||||
style: theme.textTheme.labelSmall!
|
style: theme.textTheme.labelSmall!
|
||||||
@ -115,41 +117,68 @@ class _ContactsDialogState extends State<ContactsDialog> {
|
|||||||
|
|
||||||
return ColoredBox(
|
return ColoredBox(
|
||||||
color: scale.primaryScale.appBackground,
|
color: scale.primaryScale.appBackground,
|
||||||
child: Row(children: [
|
child: Row(
|
||||||
Offstage(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
offstage: !enableLeft,
|
children: [
|
||||||
child: SizedBox(
|
Offstage(
|
||||||
width: enableLeft && !enableRight
|
offstage: !enableLeft,
|
||||||
? maxWidth
|
child: SizedBox(
|
||||||
: (maxWidth / 3).clamp(200, 500),
|
width: enableLeft && !enableRight
|
||||||
child: DecoratedBox(
|
? maxWidth
|
||||||
decoration: BoxDecoration(
|
: (maxWidth / 3).clamp(200, 500),
|
||||||
color: scale.primaryScale.subtleBackground),
|
child: DecoratedBox(
|
||||||
child: ContactsBrowser(
|
decoration: BoxDecoration(
|
||||||
selectedContactRecordKey: _selectedContact
|
color: scale
|
||||||
?.localConversationRecordKey
|
.primaryScale.subtleBackground),
|
||||||
.toVeilid(),
|
child: ContactsBrowser(
|
||||||
onContactSelected: onContactSelected,
|
selectedContactRecordKey: _selectedContact
|
||||||
onChatStarted: onChatStarted,
|
?.localConversationRecordKey
|
||||||
).paddingLTRB(8, 0, 8, 8)))),
|
.toVeilid(),
|
||||||
if (enableRight)
|
onContactSelected: _onContactSelected,
|
||||||
if (_selectedContact == null)
|
onChatStarted: _onChatStarted,
|
||||||
const NoContactWidget().expanded()
|
).paddingLTRB(8, 0, 8, 8)))),
|
||||||
else
|
if (enableRight && enableLeft)
|
||||||
ContactDetailsWidget(contact: _selectedContact!)
|
Container(
|
||||||
.paddingAll(8)
|
constraints: const BoxConstraints(
|
||||||
.expanded(),
|
minWidth: 1, maxWidth: 1),
|
||||||
]));
|
color: scale.primaryScale.subtleBorder),
|
||||||
|
if (enableRight)
|
||||||
|
if (_selectedContact == null)
|
||||||
|
const NoContactWidget().expanded()
|
||||||
|
else
|
||||||
|
ContactDetailsWidget(
|
||||||
|
contact: _selectedContact!,
|
||||||
|
onModifiedState: _onModifiedState)
|
||||||
|
.paddingLTRB(16, 16, 16, 16)
|
||||||
|
.expanded(),
|
||||||
|
]));
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> onContactSelected(proto.Contact? contact) async {
|
void _onModifiedState(bool isModified) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_selectedContact = contact;
|
_isModified = isModified;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> onChatStarted(proto.Contact contact) async {
|
Future<bool> _onContactSelected(proto.Contact? contact) async {
|
||||||
|
if (contact != _selectedContact && _isModified) {
|
||||||
|
final ok = await showConfirmModal(
|
||||||
|
context: context,
|
||||||
|
title: translate('confirmation.discard_changes'),
|
||||||
|
text: translate('confirmation.are_you_sure_discard'));
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
_selectedContact = contact;
|
||||||
|
_isModified = false;
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onChatStarted(proto.Contact contact) async {
|
||||||
final chatListCubit = context.read<ChatListCubit>();
|
final chatListCubit = context.read<ChatListCubit>();
|
||||||
await chatListCubit.getOrCreateChatSingleContact(contact: contact);
|
await chatListCubit.getOrCreateChatSingleContact(contact: contact);
|
||||||
|
|
||||||
@ -163,4 +192,5 @@ class _ContactsDialogState extends State<ContactsDialog> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
proto.Contact? _selectedContact;
|
proto.Contact? _selectedContact;
|
||||||
|
bool _isModified = false;
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:async_tools/async_tools.dart';
|
||||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -6,13 +7,18 @@ import 'package:flutter_translate/flutter_translate.dart';
|
|||||||
|
|
||||||
import '../../proto/proto.dart' as proto;
|
import '../../proto/proto.dart' as proto;
|
||||||
import '../../theme/theme.dart';
|
import '../../theme/theme.dart';
|
||||||
|
import '../models/contact_spec.dart';
|
||||||
import 'availability_widget.dart';
|
import 'availability_widget.dart';
|
||||||
|
|
||||||
|
const _kDoSubmitEditContact = 'doSubmitEditContact';
|
||||||
|
|
||||||
class EditContactForm extends StatefulWidget {
|
class EditContactForm extends StatefulWidget {
|
||||||
const EditContactForm({
|
const EditContactForm({
|
||||||
required this.formKey,
|
|
||||||
required this.contact,
|
required this.contact,
|
||||||
this.onSubmit,
|
required this.onSubmit,
|
||||||
|
required this.submitText,
|
||||||
|
required this.submitDisabledText,
|
||||||
|
this.onModifiedState,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -20,19 +26,22 @@ class EditContactForm extends StatefulWidget {
|
|||||||
State createState() => _EditContactFormState();
|
State createState() => _EditContactFormState();
|
||||||
|
|
||||||
final proto.Contact contact;
|
final proto.Contact contact;
|
||||||
final Future<void> Function(GlobalKey<FormBuilderState>)? onSubmit;
|
final String submitText;
|
||||||
final GlobalKey<FormBuilderState> formKey;
|
final String submitDisabledText;
|
||||||
|
final Future<bool> Function(ContactSpec) onSubmit;
|
||||||
|
final void Function(bool)? onModifiedState;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
super.debugFillProperties(properties);
|
super.debugFillProperties(properties);
|
||||||
properties
|
properties
|
||||||
..add(ObjectFlagProperty<
|
..add(ObjectFlagProperty<Future<bool> Function(ContactSpec p1)>.has(
|
||||||
Future<void> Function(
|
'onSubmit', onSubmit))
|
||||||
GlobalKey<FormBuilderState> p1)?>.has('onSubmit', onSubmit))
|
..add(ObjectFlagProperty<void Function(bool p1)?>.has(
|
||||||
|
'onModifiedState', onModifiedState))
|
||||||
..add(DiagnosticsProperty<proto.Contact>('contact', contact))
|
..add(DiagnosticsProperty<proto.Contact>('contact', contact))
|
||||||
..add(
|
..add(StringProperty('submitText', submitText))
|
||||||
DiagnosticsProperty<GlobalKey<FormBuilderState>>('formKey', formKey));
|
..add(StringProperty('submitDisabledText', submitDisabledText));
|
||||||
}
|
}
|
||||||
|
|
||||||
static const String formFieldNickname = 'nickname';
|
static const String formFieldNickname = 'nickname';
|
||||||
@ -41,16 +50,46 @@ class EditContactForm extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _EditContactFormState extends State<EditContactForm> {
|
class _EditContactFormState extends State<EditContactForm> {
|
||||||
|
final _formKey = GlobalKey<FormBuilderState>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
_savedValue = ContactSpec.fromProto(widget.contact);
|
||||||
|
_currentValueNickname = _savedValue.nickname;
|
||||||
|
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _availabilityWidget(proto.Availability availability, Color color) =>
|
ContactSpec _makeContactSpec() {
|
||||||
AvailabilityWidget(availability: availability, color: color);
|
final nickname = _formKey.currentState!
|
||||||
|
.fields[EditContactForm.formFieldNickname]!.value as String;
|
||||||
|
final notes = _formKey
|
||||||
|
.currentState!.fields[EditContactForm.formFieldNotes]!.value as String;
|
||||||
|
final showAvailability = _formKey.currentState!
|
||||||
|
.fields[EditContactForm.formFieldShowAvailability]!.value as bool;
|
||||||
|
|
||||||
@override
|
return ContactSpec(
|
||||||
Widget build(BuildContext context) {
|
nickname: nickname, notes: notes, showAvailability: showAvailability);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if everything is the same and update state
|
||||||
|
void _onChanged() {
|
||||||
|
final currentValue = _makeContactSpec();
|
||||||
|
_isModified = currentValue != _savedValue;
|
||||||
|
final onModifiedState = widget.onModifiedState;
|
||||||
|
if (onModifiedState != null) {
|
||||||
|
onModifiedState(_isModified);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _availabilityWidget(proto.Availability availability, Color color) =>
|
||||||
|
AvailabilityWidget(
|
||||||
|
availability: availability,
|
||||||
|
color: color,
|
||||||
|
vertical: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _editContactForm(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final scale = theme.extension<ScaleScheme>()!;
|
final scale = theme.extension<ScaleScheme>()!;
|
||||||
final scaleConfig = theme.extension<ScaleConfig>()!;
|
final scaleConfig = theme.extension<ScaleConfig>()!;
|
||||||
@ -60,75 +99,94 @@ class _EditContactFormState extends State<EditContactForm> {
|
|||||||
if (scaleConfig.useVisualIndicators && !scaleConfig.preferBorders) {
|
if (scaleConfig.useVisualIndicators && !scaleConfig.preferBorders) {
|
||||||
border = scale.primaryScale.elementBackground;
|
border = scale.primaryScale.elementBackground;
|
||||||
} else {
|
} else {
|
||||||
border = scale.primaryScale.border;
|
border = scale.primaryScale.subtleBorder;
|
||||||
}
|
}
|
||||||
|
|
||||||
return FormBuilder(
|
return FormBuilder(
|
||||||
key: widget.formKey,
|
key: _formKey,
|
||||||
|
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||||
|
onChanged: _onChanged,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
AvatarWidget(
|
styledCard(
|
||||||
name: widget.contact.profile.name,
|
context: context,
|
||||||
size: 128,
|
child: Column(
|
||||||
borderColor: border,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
foregroundColor: scale.primaryScale.primaryText,
|
children: [
|
||||||
backgroundColor: scale.primaryScale.primary,
|
Row(children: [
|
||||||
scaleConfig: scaleConfig,
|
const Spacer(),
|
||||||
textStyle: theme.textTheme.titleLarge!.copyWith(fontSize: 64),
|
AvatarWidget(
|
||||||
).paddingLTRB(0, 0, 0, 16),
|
name: _currentValueNickname.isNotEmpty
|
||||||
SelectableText(widget.contact.profile.name,
|
? _currentValueNickname
|
||||||
style: textTheme.headlineMedium)
|
: widget.contact.profile.name,
|
||||||
.noEditDecoratorLabel(
|
size: 128,
|
||||||
context,
|
borderColor: border,
|
||||||
translate('contact_form.form_name'),
|
foregroundColor: scale.primaryScale.primaryText,
|
||||||
scale: scale.secondaryScale,
|
backgroundColor: scale.primaryScale.primary,
|
||||||
)
|
scaleConfig: scaleConfig,
|
||||||
.paddingSymmetric(vertical: 4),
|
textStyle: theme.textTheme.titleLarge!
|
||||||
SelectableText(widget.contact.profile.pronouns,
|
.copyWith(fontSize: 64),
|
||||||
style: textTheme.headlineSmall)
|
).paddingLTRB(0, 0, 0, 16),
|
||||||
.noEditDecoratorLabel(
|
const Spacer()
|
||||||
context,
|
]),
|
||||||
translate('contact_form.form_pronouns'),
|
SelectableText(widget.contact.profile.name,
|
||||||
scale: scale.secondaryScale,
|
style: textTheme.bodyLarge)
|
||||||
)
|
.noEditDecoratorLabel(
|
||||||
.paddingSymmetric(vertical: 4),
|
context,
|
||||||
Row(mainAxisSize: MainAxisSize.min, children: [
|
translate('contact_form.form_name'),
|
||||||
_availabilityWidget(widget.contact.profile.availability,
|
)
|
||||||
scale.primaryScale.primaryText),
|
.paddingSymmetric(vertical: 4),
|
||||||
SelectableText(widget.contact.profile.status,
|
SelectableText(widget.contact.profile.pronouns,
|
||||||
style: textTheme.bodyMedium)
|
style: textTheme.bodyLarge)
|
||||||
.paddingSymmetric(horizontal: 8)
|
.noEditDecoratorLabel(
|
||||||
])
|
context,
|
||||||
.noEditDecoratorLabel(
|
translate('contact_form.form_pronouns'),
|
||||||
context,
|
)
|
||||||
translate('contact_form.form_status'),
|
.paddingSymmetric(vertical: 4),
|
||||||
scale: scale.secondaryScale,
|
Row(mainAxisSize: MainAxisSize.min, children: [
|
||||||
)
|
_availabilityWidget(
|
||||||
.paddingSymmetric(vertical: 4),
|
widget.contact.profile.availability,
|
||||||
SelectableText(widget.contact.profile.about,
|
scale.primaryScale.appText),
|
||||||
minLines: 1, maxLines: 8, style: textTheme.bodyMedium)
|
SelectableText(widget.contact.profile.status,
|
||||||
.noEditDecoratorLabel(
|
style: textTheme.bodyMedium)
|
||||||
context,
|
.paddingSymmetric(horizontal: 8)
|
||||||
translate('contact_form.form_about'),
|
])
|
||||||
scale: scale.secondaryScale,
|
.noEditDecoratorLabel(
|
||||||
)
|
context,
|
||||||
.paddingSymmetric(vertical: 4),
|
translate('contact_form.form_status'),
|
||||||
SelectableText(
|
)
|
||||||
widget.contact.identityPublicKey.value.toVeilid().toString(),
|
.paddingSymmetric(vertical: 4),
|
||||||
style: textTheme.labelMedium!
|
SelectableText(widget.contact.profile.about,
|
||||||
.copyWith(fontFamily: 'Source Code Pro'))
|
minLines: 1,
|
||||||
.noEditDecoratorLabel(
|
maxLines: 8,
|
||||||
context,
|
style: textTheme.bodyMedium)
|
||||||
translate('contact_form.form_fingerprint'),
|
.noEditDecoratorLabel(
|
||||||
scale: scale.secondaryScale,
|
context,
|
||||||
)
|
translate('contact_form.form_about'),
|
||||||
.paddingSymmetric(vertical: 4),
|
)
|
||||||
Divider(color: border).paddingLTRB(8, 0, 8, 8),
|
.paddingSymmetric(vertical: 4),
|
||||||
|
SelectableText(
|
||||||
|
widget.contact.identityPublicKey.value
|
||||||
|
.toVeilid()
|
||||||
|
.toString(),
|
||||||
|
style: textTheme.bodyMedium!
|
||||||
|
.copyWith(fontFamily: 'Source Code Pro'))
|
||||||
|
.noEditDecoratorLabel(
|
||||||
|
context,
|
||||||
|
translate('contact_form.form_fingerprint'),
|
||||||
|
)
|
||||||
|
.paddingSymmetric(vertical: 4),
|
||||||
|
]).paddingAll(16))
|
||||||
|
.paddingLTRB(0, 0, 0, 16),
|
||||||
FormBuilderTextField(
|
FormBuilderTextField(
|
||||||
//autofocus: true,
|
|
||||||
name: EditContactForm.formFieldNickname,
|
name: EditContactForm.formFieldNickname,
|
||||||
initialValue: widget.contact.nickname,
|
initialValue: _currentValueNickname,
|
||||||
|
onChanged: (x) {
|
||||||
|
setState(() {
|
||||||
|
_currentValueNickname = x ?? '';
|
||||||
|
});
|
||||||
|
},
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: translate('contact_form.form_nickname')),
|
labelText: translate('contact_form.form_nickname')),
|
||||||
maxLength: 64,
|
maxLength: 64,
|
||||||
@ -136,14 +194,16 @@ class _EditContactFormState extends State<EditContactForm> {
|
|||||||
),
|
),
|
||||||
FormBuilderCheckbox(
|
FormBuilderCheckbox(
|
||||||
name: EditContactForm.formFieldShowAvailability,
|
name: EditContactForm.formFieldShowAvailability,
|
||||||
initialValue: widget.contact.showAvailability,
|
initialValue: _savedValue.showAvailability,
|
||||||
side: BorderSide(color: scale.primaryScale.border, width: 2),
|
side: BorderSide(color: scale.primaryScale.border, width: 2),
|
||||||
|
checkColor: scale.primaryScale.borderText,
|
||||||
|
activeColor: scale.primaryScale.border,
|
||||||
title: Text(translate('contact_form.form_show_availability'),
|
title: Text(translate('contact_form.form_show_availability'),
|
||||||
style: textTheme.labelMedium),
|
style: textTheme.labelMedium),
|
||||||
),
|
),
|
||||||
FormBuilderTextField(
|
FormBuilderTextField(
|
||||||
name: EditContactForm.formFieldNotes,
|
name: EditContactForm.formFieldNotes,
|
||||||
initialValue: widget.contact.notes,
|
initialValue: _savedValue.notes,
|
||||||
minLines: 1,
|
minLines: 1,
|
||||||
maxLines: 8,
|
maxLines: 8,
|
||||||
maxLength: 1024,
|
maxLength: 1024,
|
||||||
@ -152,24 +212,38 @@ class _EditContactFormState extends State<EditContactForm> {
|
|||||||
textInputAction: TextInputAction.newline,
|
textInputAction: TextInputAction.newline,
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: widget.onSubmit == null
|
onPressed: _isModified ? _doSubmit : null,
|
||||||
? null
|
|
||||||
: () async {
|
|
||||||
if (widget.formKey.currentState?.saveAndValidate() ??
|
|
||||||
false) {
|
|
||||||
await widget.onSubmit!(widget.formKey);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||||
const Icon(Icons.check, size: 16).paddingLTRB(0, 0, 4, 0),
|
const Icon(Icons.check, size: 16).paddingLTRB(0, 0, 4, 0),
|
||||||
Text((widget.onSubmit == null)
|
Text(widget.submitText).paddingLTRB(0, 0, 4, 0)
|
||||||
? translate('contact_form.save')
|
|
||||||
: translate('contact_form.save'))
|
|
||||||
.paddingLTRB(0, 0, 4, 0)
|
|
||||||
]),
|
]),
|
||||||
).paddingSymmetric(vertical: 4).alignAtCenterRight(),
|
).paddingSymmetric(vertical: 4).alignAtCenter(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _doSubmit() {
|
||||||
|
final onSubmit = widget.onSubmit;
|
||||||
|
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
||||||
|
singleFuture((this, _kDoSubmitEditContact), () async {
|
||||||
|
final updatedContactSpec = _makeContactSpec();
|
||||||
|
final saved = await onSubmit(updatedContactSpec);
|
||||||
|
if (saved) {
|
||||||
|
setState(() {
|
||||||
|
_savedValue = updatedContactSpec;
|
||||||
|
});
|
||||||
|
_onChanged();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => _editContactForm(context);
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
late ContactSpec _savedValue;
|
||||||
|
late String _currentValueNickname;
|
||||||
|
bool _isModified = false;
|
||||||
}
|
}
|
||||||
|
@ -9,12 +9,13 @@ import 'package:go_router/go_router.dart';
|
|||||||
import 'package:veilid_support/veilid_support.dart';
|
import 'package:veilid_support/veilid_support.dart';
|
||||||
|
|
||||||
import '../../../account_manager/account_manager.dart';
|
import '../../../account_manager/account_manager.dart';
|
||||||
import '../../../proto/proto.dart' as proto;
|
|
||||||
import '../../../theme/theme.dart';
|
import '../../../theme/theme.dart';
|
||||||
import '../../../tools/tools.dart';
|
import '../../../tools/tools.dart';
|
||||||
import '../../../veilid_processor/veilid_processor.dart';
|
import '../../../veilid_processor/veilid_processor.dart';
|
||||||
import 'menu_item_widget.dart';
|
import 'menu_item_widget.dart';
|
||||||
|
|
||||||
|
const _scaleKind = ScaleKind.secondary;
|
||||||
|
|
||||||
class DrawerMenu extends StatefulWidget {
|
class DrawerMenu extends StatefulWidget {
|
||||||
const DrawerMenu({super.key});
|
const DrawerMenu({super.key});
|
||||||
|
|
||||||
@ -40,7 +41,7 @@ class _DrawerMenuState extends State<DrawerMenu> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _doEditClick(TypedKey superIdentityRecordKey,
|
void _doEditClick(TypedKey superIdentityRecordKey,
|
||||||
proto.Account existingAccount, OwnedDHTRecordPointer accountRecord) {
|
AccountSpec existingAccount, OwnedDHTRecordPointer accountRecord) {
|
||||||
singleFuture(this, () async {
|
singleFuture(this, () async {
|
||||||
await GoRouterHelper(context).push('/edit_account',
|
await GoRouterHelper(context).push('/edit_account',
|
||||||
extra: [superIdentityRecordKey, existingAccount, accountRecord]);
|
extra: [superIdentityRecordKey, existingAccount, accountRecord]);
|
||||||
@ -58,45 +59,6 @@ class _DrawerMenuState extends State<DrawerMenu> {
|
|||||||
borderRadius: BorderRadius.circular(borderRadius))),
|
borderRadius: BorderRadius.circular(borderRadius))),
|
||||||
child: child);
|
child: child);
|
||||||
|
|
||||||
Widget _makeAvatarWidget({
|
|
||||||
required String name,
|
|
||||||
required double size,
|
|
||||||
required Color borderColor,
|
|
||||||
required Color foregroundColor,
|
|
||||||
required Color backgroundColor,
|
|
||||||
required ScaleConfig scaleConfig,
|
|
||||||
required TextStyle textStyle,
|
|
||||||
ImageProvider<Object>? imageProvider,
|
|
||||||
}) {
|
|
||||||
final abbrev = name.split(' ').map((s) => s.isEmpty ? '' : s[0]).join();
|
|
||||||
late final String shortname;
|
|
||||||
if (abbrev.length >= 3) {
|
|
||||||
shortname = abbrev[0] + abbrev[1] + abbrev[abbrev.length - 1];
|
|
||||||
} else {
|
|
||||||
shortname = abbrev;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
height: size,
|
|
||||||
width: size,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
border: scaleConfig.preferBorders
|
|
||||||
? Border.all(
|
|
||||||
color: borderColor,
|
|
||||||
width: 2 * (size ~/ 32 + 1),
|
|
||||||
strokeAlign: BorderSide.strokeAlignOutside)
|
|
||||||
: null,
|
|
||||||
color: Colors.blue,
|
|
||||||
),
|
|
||||||
child: AvatarImage(
|
|
||||||
//size: 32,
|
|
||||||
backgroundImage: imageProvider,
|
|
||||||
backgroundColor: backgroundColor,
|
|
||||||
foregroundColor: foregroundColor,
|
|
||||||
child: Text(shortname, style: textStyle)));
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _makeAccountWidget(
|
Widget _makeAccountWidget(
|
||||||
{required String name,
|
{required String name,
|
||||||
required bool selected,
|
required bool selected,
|
||||||
@ -173,6 +135,7 @@ class _DrawerMenuState extends State<DrawerMenu> {
|
|||||||
footerButtonIconColor: border,
|
footerButtonIconColor: border,
|
||||||
footerButtonIconHoverColor: hoverBackground,
|
footerButtonIconHoverColor: hoverBackground,
|
||||||
footerButtonIconFocusColor: activeBackground,
|
footerButtonIconFocusColor: activeBackground,
|
||||||
|
minHeight: 48,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,6 +147,7 @@ class _DrawerMenuState extends State<DrawerMenu> {
|
|||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final scaleScheme = theme.extension<ScaleScheme>()!;
|
final scaleScheme = theme.extension<ScaleScheme>()!;
|
||||||
final scaleConfig = theme.extension<ScaleConfig>()!;
|
final scaleConfig = theme.extension<ScaleConfig>()!;
|
||||||
|
final scale = scaleScheme.scale(_scaleKind);
|
||||||
|
|
||||||
final loggedInAccounts = <Widget>[];
|
final loggedInAccounts = <Widget>[];
|
||||||
final loggedOutAccounts = <Widget>[];
|
final loggedOutAccounts = <Widget>[];
|
||||||
@ -197,9 +161,6 @@ class _DrawerMenuState extends State<DrawerMenu> {
|
|||||||
final avAccountRecordState = perAccountState?.avAccountRecordState;
|
final avAccountRecordState = perAccountState?.avAccountRecordState;
|
||||||
if (perAccountState != null && avAccountRecordState != null) {
|
if (perAccountState != null && avAccountRecordState != null) {
|
||||||
// Account is logged in
|
// Account is logged in
|
||||||
final scale = scaleConfig.useVisualIndicators
|
|
||||||
? theme.extension<ScaleScheme>()!.primaryScale
|
|
||||||
: theme.extension<ScaleScheme>()!.tertiaryScale;
|
|
||||||
final loggedInAccount = avAccountRecordState.when(
|
final loggedInAccount = avAccountRecordState.when(
|
||||||
data: (value) => _makeAccountWidget(
|
data: (value) => _makeAccountWidget(
|
||||||
name: value.profile.name,
|
name: value.profile.name,
|
||||||
@ -213,7 +174,7 @@ class _DrawerMenuState extends State<DrawerMenu> {
|
|||||||
footerCallback: () {
|
footerCallback: () {
|
||||||
_doEditClick(
|
_doEditClick(
|
||||||
superIdentityRecordKey,
|
superIdentityRecordKey,
|
||||||
value,
|
AccountSpec.fromProto(value),
|
||||||
perAccountState.accountInfo.userLogin!.accountRecordInfo
|
perAccountState.accountInfo.userLogin!.accountRecordInfo
|
||||||
.accountRecord);
|
.accountRecord);
|
||||||
}),
|
}),
|
||||||
@ -311,13 +272,14 @@ class _DrawerMenuState extends State<DrawerMenu> {
|
|||||||
|
|
||||||
Widget _getBottomButtons() {
|
Widget _getBottomButtons() {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final scale = theme.extension<ScaleScheme>()!;
|
final scaleScheme = theme.extension<ScaleScheme>()!;
|
||||||
final scaleConfig = theme.extension<ScaleConfig>()!;
|
final scaleConfig = theme.extension<ScaleConfig>()!;
|
||||||
|
final scale = scaleScheme.scale(_scaleKind);
|
||||||
|
|
||||||
final settingsButton = _getButton(
|
final settingsButton = _getButton(
|
||||||
icon: const Icon(Icons.settings),
|
icon: const Icon(Icons.settings),
|
||||||
tooltip: translate('menu.settings_tooltip'),
|
tooltip: translate('menu.settings_tooltip'),
|
||||||
scale: scale.tertiaryScale,
|
scale: scale,
|
||||||
scaleConfig: scaleConfig,
|
scaleConfig: scaleConfig,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await GoRouterHelper(context).push('/settings');
|
await GoRouterHelper(context).push('/settings');
|
||||||
@ -326,7 +288,7 @@ class _DrawerMenuState extends State<DrawerMenu> {
|
|||||||
final addButton = _getButton(
|
final addButton = _getButton(
|
||||||
icon: const Icon(Icons.add),
|
icon: const Icon(Icons.add),
|
||||||
tooltip: translate('menu.add_account_tooltip'),
|
tooltip: translate('menu.add_account_tooltip'),
|
||||||
scale: scale.tertiaryScale,
|
scale: scale,
|
||||||
scaleConfig: scaleConfig,
|
scaleConfig: scaleConfig,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await GoRouterHelper(context).push('/new_account');
|
await GoRouterHelper(context).push('/new_account');
|
||||||
@ -340,8 +302,9 @@ class _DrawerMenuState extends State<DrawerMenu> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final scale = theme.extension<ScaleScheme>()!;
|
final scaleScheme = theme.extension<ScaleScheme>()!;
|
||||||
final scaleConfig = theme.extension<ScaleConfig>()!;
|
final scaleConfig = theme.extension<ScaleConfig>()!;
|
||||||
|
final scale = scaleScheme.scale(_scaleKind);
|
||||||
//final textTheme = theme.textTheme;
|
//final textTheme = theme.textTheme;
|
||||||
final localAccounts = context.watch<LocalAccountsCubit>().state;
|
final localAccounts = context.watch<LocalAccountsCubit>().state;
|
||||||
final perAccountCollectionBlocMapState =
|
final perAccountCollectionBlocMapState =
|
||||||
@ -351,8 +314,8 @@ class _DrawerMenuState extends State<DrawerMenu> {
|
|||||||
begin: Alignment.topLeft,
|
begin: Alignment.topLeft,
|
||||||
end: Alignment.bottomRight,
|
end: Alignment.bottomRight,
|
||||||
colors: [
|
colors: [
|
||||||
scale.tertiaryScale.border,
|
scale.border,
|
||||||
scale.tertiaryScale.subtleBorder,
|
scale.subtleBorder,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return DecoratedBox(
|
return DecoratedBox(
|
||||||
@ -360,34 +323,35 @@ class _DrawerMenuState extends State<DrawerMenu> {
|
|||||||
shadows: [
|
shadows: [
|
||||||
if (scaleConfig.useVisualIndicators && !scaleConfig.preferBorders)
|
if (scaleConfig.useVisualIndicators && !scaleConfig.preferBorders)
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: scale.tertiaryScale.primary.darken(80),
|
color: scale.primary.darken(60),
|
||||||
spreadRadius: 2,
|
spreadRadius: 2,
|
||||||
)
|
)
|
||||||
else if (scaleConfig.useVisualIndicators &&
|
else if (scaleConfig.useVisualIndicators &&
|
||||||
scaleConfig.preferBorders)
|
scaleConfig.preferBorders)
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: scale.tertiaryScale.border,
|
color: scale.border,
|
||||||
spreadRadius: 2,
|
spreadRadius: 2,
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: scale.tertiaryScale.primary.darken(40),
|
color: scale.appBackground.darken(60).withAlpha(0x3F),
|
||||||
blurRadius: 6,
|
blurRadius: 16,
|
||||||
|
spreadRadius: 2,
|
||||||
offset: const Offset(
|
offset: const Offset(
|
||||||
0,
|
0,
|
||||||
4,
|
2,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
gradient: scaleConfig.useVisualIndicators ? null : gradient,
|
gradient: scaleConfig.useVisualIndicators ? null : gradient,
|
||||||
color: scaleConfig.useVisualIndicators
|
color: scaleConfig.useVisualIndicators
|
||||||
? (scaleConfig.preferBorders
|
? (scaleConfig.preferBorders
|
||||||
? scale.tertiaryScale.appBackground
|
? scale.appBackground
|
||||||
: scale.tertiaryScale.subtleBorder)
|
: scale.subtleBorder)
|
||||||
: null,
|
: null,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
side: scaleConfig.preferBorders
|
side: scaleConfig.preferBorders
|
||||||
? BorderSide(color: scale.tertiaryScale.primary, width: 2)
|
? BorderSide(color: scale.primary, width: 2)
|
||||||
: BorderSide.none,
|
: BorderSide.none,
|
||||||
borderRadius: BorderRadius.only(
|
borderRadius: BorderRadius.only(
|
||||||
topRight: Radius.circular(16 * scaleConfig.borderRadiusScale),
|
topRight: Radius.circular(16 * scaleConfig.borderRadiusScale),
|
||||||
@ -399,31 +363,31 @@ class _DrawerMenuState extends State<DrawerMenu> {
|
|||||||
child: ColorFiltered(
|
child: ColorFiltered(
|
||||||
colorFilter: ColorFilter.mode(
|
colorFilter: ColorFilter.mode(
|
||||||
theme.brightness == Brightness.light
|
theme.brightness == Brightness.light
|
||||||
? scale.tertiaryScale.primary
|
? scale.primary
|
||||||
: scale.tertiaryScale.border,
|
: scale.border,
|
||||||
scaleConfig.preferBorders
|
scaleConfig.preferBorders
|
||||||
? BlendMode.modulate
|
? BlendMode.modulate
|
||||||
: BlendMode.dst),
|
: BlendMode.dst),
|
||||||
child: Row(children: [
|
child: Row(children: [
|
||||||
SvgPicture.asset(
|
// SvgPicture.asset(
|
||||||
height: 48,
|
// height: 48,
|
||||||
'assets/images/icon.svg',
|
// 'assets/images/icon.svg',
|
||||||
colorFilter: scaleConfig.useVisualIndicators
|
// colorFilter: scaleConfig.useVisualIndicators
|
||||||
? grayColorFilter
|
// ? grayColorFilter
|
||||||
: null)
|
// : null)
|
||||||
.paddingLTRB(0, 0, 16, 0),
|
// .paddingLTRB(0, 0, 16, 0),
|
||||||
SvgPicture.asset(
|
SvgPicture.asset(
|
||||||
height: 48,
|
height: 48,
|
||||||
'assets/images/title.svg',
|
'assets/images/title.svg',
|
||||||
colorFilter: scaleConfig.useVisualIndicators
|
colorFilter: scaleConfig.useVisualIndicators
|
||||||
? grayColorFilter
|
? grayColorFilter
|
||||||
: null),
|
: dodgeFilter),
|
||||||
]))),
|
]))),
|
||||||
Text(translate('menu.accounts'),
|
Text(translate('menu.accounts'),
|
||||||
style: theme.textTheme.titleMedium!.copyWith(
|
style: theme.textTheme.titleMedium!.copyWith(
|
||||||
color: scaleConfig.preferBorders
|
color: scaleConfig.preferBorders
|
||||||
? scale.tertiaryScale.border
|
? scale.border
|
||||||
: scale.tertiaryScale.borderText))
|
: scale.borderText))
|
||||||
.paddingLTRB(0, 16, 0, 16),
|
.paddingLTRB(0, 16, 0, 16),
|
||||||
ListView(
|
ListView(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
@ -438,16 +402,16 @@ class _DrawerMenuState extends State<DrawerMenu> {
|
|||||||
Text('${translate('menu.version')} $packageInfoVersion',
|
Text('${translate('menu.version')} $packageInfoVersion',
|
||||||
style: theme.textTheme.labelMedium!.copyWith(
|
style: theme.textTheme.labelMedium!.copyWith(
|
||||||
color: scaleConfig.preferBorders
|
color: scaleConfig.preferBorders
|
||||||
? scale.tertiaryScale.hoverBorder
|
? scale.hoverBorder
|
||||||
: scale.tertiaryScale.subtleBackground)),
|
: scale.subtleBackground)),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
SignalStrengthMeterWidget(
|
SignalStrengthMeterWidget(
|
||||||
color: scaleConfig.preferBorders
|
color: scaleConfig.preferBorders
|
||||||
? scale.tertiaryScale.hoverBorder
|
? scale.hoverBorder
|
||||||
: scale.tertiaryScale.subtleBackground,
|
: scale.subtleBackground,
|
||||||
inactiveColor: scaleConfig.preferBorders
|
inactiveColor: scaleConfig.preferBorders
|
||||||
? scale.tertiaryScale.border
|
? scale.border
|
||||||
: scale.tertiaryScale.elementBackground,
|
: scale.elementBackground,
|
||||||
),
|
),
|
||||||
])
|
])
|
||||||
]).paddingAll(16),
|
]).paddingAll(16),
|
||||||
|
@ -22,39 +22,42 @@ class MenuItemWidget extends StatelessWidget {
|
|||||||
this.footerButtonIconHoverColor,
|
this.footerButtonIconHoverColor,
|
||||||
this.footerButtonIconFocusColor,
|
this.footerButtonIconFocusColor,
|
||||||
this.footerCallback,
|
this.footerCallback,
|
||||||
|
this.minHeight = 0,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => TextButton(
|
Widget build(BuildContext context) => TextButton(
|
||||||
onPressed: callback,
|
onPressed: callback,
|
||||||
style: TextButton.styleFrom(foregroundColor: foregroundColor).copyWith(
|
style: TextButton.styleFrom(foregroundColor: foregroundColor).copyWith(
|
||||||
backgroundColor: WidgetStateProperty.resolveWith((states) {
|
backgroundColor: WidgetStateProperty.resolveWith((states) {
|
||||||
if (states.contains(WidgetState.hovered)) {
|
if (states.contains(WidgetState.hovered)) {
|
||||||
return backgroundHoverColor;
|
return backgroundHoverColor;
|
||||||
}
|
}
|
||||||
if (states.contains(WidgetState.focused)) {
|
if (states.contains(WidgetState.focused)) {
|
||||||
return backgroundFocusColor;
|
return backgroundFocusColor;
|
||||||
}
|
}
|
||||||
return backgroundColor;
|
return backgroundColor;
|
||||||
}),
|
}),
|
||||||
side: WidgetStateBorderSide.resolveWith((states) {
|
side: WidgetStateBorderSide.resolveWith((states) {
|
||||||
if (states.contains(WidgetState.hovered)) {
|
if (states.contains(WidgetState.hovered)) {
|
||||||
return borderColor != null
|
|
||||||
? BorderSide(width: 2, color: borderHoverColor!)
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
if (states.contains(WidgetState.focused)) {
|
|
||||||
return borderColor != null
|
|
||||||
? BorderSide(width: 2, color: borderFocusColor!)
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
return borderColor != null
|
return borderColor != null
|
||||||
? BorderSide(width: 2, color: borderColor!)
|
? BorderSide(width: 2, color: borderHoverColor!)
|
||||||
: null;
|
: null;
|
||||||
}),
|
}
|
||||||
shape: WidgetStateProperty.all(RoundedRectangleBorder(
|
if (states.contains(WidgetState.focused)) {
|
||||||
borderRadius: BorderRadius.circular(borderRadius ?? 0)))),
|
return borderColor != null
|
||||||
|
? BorderSide(width: 2, color: borderFocusColor!)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
return borderColor != null
|
||||||
|
? BorderSide(width: 2, color: borderColor!)
|
||||||
|
: null;
|
||||||
|
}),
|
||||||
|
shape: WidgetStateProperty.all(RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(borderRadius ?? 0)))),
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(minHeight: minHeight),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
@ -81,7 +84,7 @@ class MenuItemWidget extends StatelessWidget {
|
|||||||
onPressed: footerCallback),
|
onPressed: footerCallback),
|
||||||
],
|
],
|
||||||
).paddingAll(2),
|
).paddingAll(2),
|
||||||
);
|
));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
@ -106,7 +109,8 @@ class MenuItemWidget extends StatelessWidget {
|
|||||||
..add(ColorProperty('borderColor', borderColor))
|
..add(ColorProperty('borderColor', borderColor))
|
||||||
..add(DoubleProperty('borderRadius', borderRadius))
|
..add(DoubleProperty('borderRadius', borderRadius))
|
||||||
..add(ColorProperty('borderHoverColor', borderHoverColor))
|
..add(ColorProperty('borderHoverColor', borderHoverColor))
|
||||||
..add(ColorProperty('borderFocusColor', borderFocusColor));
|
..add(ColorProperty('borderFocusColor', borderFocusColor))
|
||||||
|
..add(DoubleProperty('minHeight', minHeight));
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
@ -129,4 +133,5 @@ class MenuItemWidget extends StatelessWidget {
|
|||||||
final Color? footerButtonIconColor;
|
final Color? footerButtonIconColor;
|
||||||
final Color? footerButtonIconHoverColor;
|
final Color? footerButtonIconHoverColor;
|
||||||
final Color? footerButtonIconFocusColor;
|
final Color? footerButtonIconFocusColor;
|
||||||
|
final double minHeight;
|
||||||
}
|
}
|
||||||
|
@ -96,7 +96,7 @@ class HomeScreenState extends State<HomeScreen>
|
|||||||
),
|
),
|
||||||
Row(mainAxisSize: MainAxisSize.min, children: [
|
Row(mainAxisSize: MainAxisSize.min, children: [
|
||||||
StatefulBuilder(
|
StatefulBuilder(
|
||||||
builder: (context, setState) => Checkbox.adaptive(
|
builder: (context, setState) => Checkbox(
|
||||||
value: displayBetaWarning,
|
value: displayBetaWarning,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -213,7 +213,6 @@ class HomeScreenState extends State<HomeScreen>
|
|||||||
style: theme.textTheme.bodySmall!,
|
style: theme.textTheme.bodySmall!,
|
||||||
child: ZoomDrawer(
|
child: ZoomDrawer(
|
||||||
controller: _zoomDrawerController,
|
controller: _zoomDrawerController,
|
||||||
//menuBackgroundColor: Colors.transparent,
|
|
||||||
menuScreen: Builder(builder: (context) {
|
menuScreen: Builder(builder: (context) {
|
||||||
final zoomDrawer = ZoomDrawer.of(context);
|
final zoomDrawer = ZoomDrawer.of(context);
|
||||||
zoomDrawer!.stateNotifier.addListener(() {
|
zoomDrawer!.stateNotifier.addListener(() {
|
||||||
@ -228,7 +227,7 @@ class HomeScreenState extends State<HomeScreen>
|
|||||||
child: Builder(builder: _buildAccountPageView)),
|
child: Builder(builder: _buildAccountPageView)),
|
||||||
borderRadius: 0,
|
borderRadius: 0,
|
||||||
angle: 0,
|
angle: 0,
|
||||||
mainScreenOverlayColor: theme.shadowColor.withAlpha(0x2F),
|
//mainScreenOverlayColor: theme.shadowColor.withAlpha(0x2F),
|
||||||
openCurve: Curves.fastEaseInToSlowEaseOut,
|
openCurve: Curves.fastEaseInToSlowEaseOut,
|
||||||
// duration: const Duration(milliseconds: 250),
|
// duration: const Duration(milliseconds: 250),
|
||||||
// reverseDuration: const Duration(milliseconds: 250),
|
// reverseDuration: const Duration(milliseconds: 250),
|
||||||
|
@ -130,6 +130,8 @@ Widget buildSettingsPageNotificationPreferences(
|
|||||||
FormBuilderCheckbox(
|
FormBuilderCheckbox(
|
||||||
name: formFieldDisplayBetaWarning,
|
name: formFieldDisplayBetaWarning,
|
||||||
side: BorderSide(color: scale.primaryScale.border, width: 2),
|
side: BorderSide(color: scale.primaryScale.border, width: 2),
|
||||||
|
checkColor: scale.primaryScale.borderText,
|
||||||
|
activeColor: scale.primaryScale.border,
|
||||||
title: Text(translate('settings_page.display_beta_warning'),
|
title: Text(translate('settings_page.display_beta_warning'),
|
||||||
style: textTheme.labelMedium),
|
style: textTheme.labelMedium),
|
||||||
initialValue: notificationsPreference.displayBetaWarning,
|
initialValue: notificationsPreference.displayBetaWarning,
|
||||||
@ -146,6 +148,8 @@ Widget buildSettingsPageNotificationPreferences(
|
|||||||
FormBuilderCheckbox(
|
FormBuilderCheckbox(
|
||||||
name: formFieldEnableBadge,
|
name: formFieldEnableBadge,
|
||||||
side: BorderSide(color: scale.primaryScale.border, width: 2),
|
side: BorderSide(color: scale.primaryScale.border, width: 2),
|
||||||
|
checkColor: scale.primaryScale.borderText,
|
||||||
|
activeColor: scale.primaryScale.border,
|
||||||
title: Text(translate('settings_page.enable_badge'),
|
title: Text(translate('settings_page.enable_badge'),
|
||||||
style: textTheme.labelMedium),
|
style: textTheme.labelMedium),
|
||||||
initialValue: notificationsPreference.enableBadge,
|
initialValue: notificationsPreference.enableBadge,
|
||||||
@ -161,6 +165,8 @@ Widget buildSettingsPageNotificationPreferences(
|
|||||||
FormBuilderCheckbox(
|
FormBuilderCheckbox(
|
||||||
name: formFieldEnableNotifications,
|
name: formFieldEnableNotifications,
|
||||||
side: BorderSide(color: scale.primaryScale.border, width: 2),
|
side: BorderSide(color: scale.primaryScale.border, width: 2),
|
||||||
|
checkColor: scale.primaryScale.borderText,
|
||||||
|
activeColor: scale.primaryScale.border,
|
||||||
title: Text(translate('settings_page.enable_notifications'),
|
title: Text(translate('settings_page.enable_notifications'),
|
||||||
style: textTheme.labelMedium),
|
style: textTheme.labelMedium),
|
||||||
initialValue: notificationsPreference.enableNotifications,
|
initialValue: notificationsPreference.enableNotifications,
|
||||||
|
@ -3024,6 +3024,7 @@ class ContactInvitationRecord extends $pb.GeneratedMessage {
|
|||||||
$fixnum.Int64? expiration,
|
$fixnum.Int64? expiration,
|
||||||
$core.List<$core.int>? invitation,
|
$core.List<$core.int>? invitation,
|
||||||
$core.String? message,
|
$core.String? message,
|
||||||
|
$core.String? recipient,
|
||||||
}) {
|
}) {
|
||||||
final $result = create();
|
final $result = create();
|
||||||
if (contactRequestInbox != null) {
|
if (contactRequestInbox != null) {
|
||||||
@ -3047,6 +3048,9 @@ class ContactInvitationRecord extends $pb.GeneratedMessage {
|
|||||||
if (message != null) {
|
if (message != null) {
|
||||||
$result.message = message;
|
$result.message = message;
|
||||||
}
|
}
|
||||||
|
if (recipient != null) {
|
||||||
|
$result.recipient = recipient;
|
||||||
|
}
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
ContactInvitationRecord._() : super();
|
ContactInvitationRecord._() : super();
|
||||||
@ -3061,6 +3065,7 @@ class ContactInvitationRecord extends $pb.GeneratedMessage {
|
|||||||
..a<$fixnum.Int64>(5, _omitFieldNames ? '' : 'expiration', $pb.PbFieldType.OU6, defaultOrMaker: $fixnum.Int64.ZERO)
|
..a<$fixnum.Int64>(5, _omitFieldNames ? '' : 'expiration', $pb.PbFieldType.OU6, defaultOrMaker: $fixnum.Int64.ZERO)
|
||||||
..a<$core.List<$core.int>>(6, _omitFieldNames ? '' : 'invitation', $pb.PbFieldType.OY)
|
..a<$core.List<$core.int>>(6, _omitFieldNames ? '' : 'invitation', $pb.PbFieldType.OY)
|
||||||
..aOS(7, _omitFieldNames ? '' : 'message')
|
..aOS(7, _omitFieldNames ? '' : 'message')
|
||||||
|
..aOS(8, _omitFieldNames ? '' : 'recipient')
|
||||||
..hasRequiredFields = false
|
..hasRequiredFields = false
|
||||||
;
|
;
|
||||||
|
|
||||||
@ -3162,6 +3167,16 @@ class ContactInvitationRecord extends $pb.GeneratedMessage {
|
|||||||
$core.bool hasMessage() => $_has(6);
|
$core.bool hasMessage() => $_has(6);
|
||||||
@$pb.TagNumber(7)
|
@$pb.TagNumber(7)
|
||||||
void clearMessage() => clearField(7);
|
void clearMessage() => clearField(7);
|
||||||
|
|
||||||
|
/// The recipient sent along with the invitation
|
||||||
|
@$pb.TagNumber(8)
|
||||||
|
$core.String get recipient => $_getSZ(7);
|
||||||
|
@$pb.TagNumber(8)
|
||||||
|
set recipient($core.String v) { $_setString(7, v); }
|
||||||
|
@$pb.TagNumber(8)
|
||||||
|
$core.bool hasRecipient() => $_has(7);
|
||||||
|
@$pb.TagNumber(8)
|
||||||
|
void clearRecipient() => clearField(8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -628,6 +628,7 @@ const ContactInvitationRecord$json = {
|
|||||||
{'1': 'expiration', '3': 5, '4': 1, '5': 4, '10': 'expiration'},
|
{'1': 'expiration', '3': 5, '4': 1, '5': 4, '10': 'expiration'},
|
||||||
{'1': 'invitation', '3': 6, '4': 1, '5': 12, '10': 'invitation'},
|
{'1': 'invitation', '3': 6, '4': 1, '5': 12, '10': 'invitation'},
|
||||||
{'1': 'message', '3': 7, '4': 1, '5': 9, '10': 'message'},
|
{'1': 'message', '3': 7, '4': 1, '5': 9, '10': 'message'},
|
||||||
|
{'1': 'recipient', '3': 8, '4': 1, '5': 9, '10': 'recipient'},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -639,5 +640,6 @@ final $typed_data.Uint8List contactInvitationRecordDescriptor = $convert.base64D
|
|||||||
'NlY3JldBgDIAEoCzIRLnZlaWxpZC5DcnlwdG9LZXlSDHdyaXRlclNlY3JldBJTCh1sb2NhbF9j'
|
'NlY3JldBgDIAEoCzIRLnZlaWxpZC5DcnlwdG9LZXlSDHdyaXRlclNlY3JldBJTCh1sb2NhbF9j'
|
||||||
'b252ZXJzYXRpb25fcmVjb3JkX2tleRgEIAEoCzIQLnZlaWxpZC5UeXBlZEtleVIabG9jYWxDb2'
|
'b252ZXJzYXRpb25fcmVjb3JkX2tleRgEIAEoCzIQLnZlaWxpZC5UeXBlZEtleVIabG9jYWxDb2'
|
||||||
'52ZXJzYXRpb25SZWNvcmRLZXkSHgoKZXhwaXJhdGlvbhgFIAEoBFIKZXhwaXJhdGlvbhIeCgpp'
|
'52ZXJzYXRpb25SZWNvcmRLZXkSHgoKZXhwaXJhdGlvbhgFIAEoBFIKZXhwaXJhdGlvbhIeCgpp'
|
||||||
'bnZpdGF0aW9uGAYgASgMUgppbnZpdGF0aW9uEhgKB21lc3NhZ2UYByABKAlSB21lc3NhZ2U=');
|
'bnZpdGF0aW9uGAYgASgMUgppbnZpdGF0aW9uEhgKB21lc3NhZ2UYByABKAlSB21lc3NhZ2USHA'
|
||||||
|
'oJcmVjaXBpZW50GAggASgJUglyZWNpcGllbnQ=');
|
||||||
|
|
||||||
|
@ -478,4 +478,6 @@ message ContactInvitationRecord {
|
|||||||
bytes invitation = 6;
|
bytes invitation = 6;
|
||||||
// The message sent along with the invitation
|
// The message sent along with the invitation
|
||||||
string message = 7;
|
string message = 7;
|
||||||
|
// The recipient sent along with the invitation
|
||||||
|
string recipient = 8;
|
||||||
}
|
}
|
@ -11,7 +11,6 @@ import 'package:veilid_support/veilid_support.dart';
|
|||||||
|
|
||||||
import '../../../account_manager/account_manager.dart';
|
import '../../../account_manager/account_manager.dart';
|
||||||
import '../../layout/layout.dart';
|
import '../../layout/layout.dart';
|
||||||
import '../../proto/proto.dart' as proto;
|
|
||||||
import '../../settings/settings.dart';
|
import '../../settings/settings.dart';
|
||||||
import '../../tools/tools.dart';
|
import '../../tools/tools.dart';
|
||||||
import '../../veilid_processor/views/developer.dart';
|
import '../../veilid_processor/views/developer.dart';
|
||||||
@ -43,10 +42,8 @@ class RouterCubit extends Cubit<RouterState> {
|
|||||||
case AccountRepositoryChange.localAccounts:
|
case AccountRepositoryChange.localAccounts:
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
hasAnyAccount: accountRepository.getLocalAccounts().isNotEmpty));
|
hasAnyAccount: accountRepository.getLocalAccounts().isNotEmpty));
|
||||||
break;
|
|
||||||
case AccountRepositoryChange.userLogins:
|
case AccountRepositoryChange.userLogins:
|
||||||
case AccountRepositoryChange.activeLocalAccount:
|
case AccountRepositoryChange.activeLocalAccount:
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -72,7 +69,7 @@ class RouterCubit extends Cubit<RouterState> {
|
|||||||
final extra = state.extra! as List<Object?>;
|
final extra = state.extra! as List<Object?>;
|
||||||
return EditAccountPage(
|
return EditAccountPage(
|
||||||
superIdentityRecordKey: extra[0]! as TypedKey,
|
superIdentityRecordKey: extra[0]! as TypedKey,
|
||||||
existingAccount: extra[1]! as proto.Account,
|
initialValue: extra[1]! as AccountSpec,
|
||||||
accountRecord: extra[2]! as OwnedDHTRecordPointer,
|
accountRecord: extra[2]! as OwnedDHTRecordPointer,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -45,6 +45,7 @@ class SettingsPageState extends State<SettingsPage> {
|
|||||||
child: FormBuilder(
|
child: FormBuilder(
|
||||||
key: _formKey,
|
key: _formKey,
|
||||||
child: ListView(
|
child: ListView(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
children: [
|
children: [
|
||||||
buildSettingsPageColorPreferences(
|
buildSettingsPageColorPreferences(
|
||||||
context: context,
|
context: context,
|
||||||
@ -56,6 +57,6 @@ class SettingsPageState extends State<SettingsPage> {
|
|||||||
context: context, onChanged: () => setState(() {})),
|
context: context, onChanged: () => setState(() {})),
|
||||||
].map((x) => x.paddingLTRB(0, 0, 0, 8)).toList(),
|
].map((x) => x.paddingLTRB(0, 0, 0, 8)).toList(),
|
||||||
),
|
),
|
||||||
).paddingSymmetric(horizontal: 24, vertical: 16),
|
).paddingSymmetric(horizontal: 8, vertical: 8),
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ ChatTheme makeChatTheme(
|
|||||||
secondaryColor: scaleConfig.preferBorders
|
secondaryColor: scaleConfig.preferBorders
|
||||||
? scale.secondaryScale.calloutText
|
? scale.secondaryScale.calloutText
|
||||||
: scale.secondaryScale.calloutBackground,
|
: scale.secondaryScale.calloutBackground,
|
||||||
backgroundColor: scale.grayScale.appBackground,
|
backgroundColor: scale.grayScale.appBackground.withAlpha(192),
|
||||||
messageBorderRadius: scaleConfig.borderRadiusScale * 16,
|
messageBorderRadius: scaleConfig.borderRadiusScale * 16,
|
||||||
bubbleBorderSide: scaleConfig.preferBorders
|
bubbleBorderSide: scaleConfig.preferBorders
|
||||||
? BorderSide(
|
? BorderSide(
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'radix_generator.dart';
|
import 'radix_generator.dart';
|
||||||
import 'scale_theme/scale_color.dart';
|
|
||||||
import 'scale_theme/scale_input_decorator_theme.dart';
|
|
||||||
import 'scale_theme/scale_scheme.dart';
|
|
||||||
import 'scale_theme/scale_theme.dart';
|
import 'scale_theme/scale_theme.dart';
|
||||||
|
|
||||||
ScaleColor _contrastScaleColor(
|
ScaleColor _contrastScaleColor(
|
||||||
@ -29,6 +26,7 @@ ScaleColor _contrastScaleColor(
|
|||||||
primaryText: front,
|
primaryText: front,
|
||||||
borderText: back,
|
borderText: back,
|
||||||
dialogBorder: front,
|
dialogBorder: front,
|
||||||
|
dialogBorderText: back,
|
||||||
calloutBackground: front,
|
calloutBackground: front,
|
||||||
calloutText: back,
|
calloutText: back,
|
||||||
);
|
);
|
||||||
@ -246,7 +244,7 @@ ThemeData contrastGenerator({
|
|||||||
TextTheme? customTextTheme,
|
TextTheme? customTextTheme,
|
||||||
}) {
|
}) {
|
||||||
final textTheme = customTextTheme ?? makeRadixTextTheme(brightness);
|
final textTheme = customTextTheme ?? makeRadixTextTheme(brightness);
|
||||||
final scaleScheme = _contrastScaleScheme(
|
final scheme = _contrastScaleScheme(
|
||||||
brightness: brightness,
|
brightness: brightness,
|
||||||
primaryFront: primaryFront,
|
primaryFront: primaryFront,
|
||||||
primaryBack: primaryBack,
|
primaryBack: primaryBack,
|
||||||
@ -259,55 +257,51 @@ ThemeData contrastGenerator({
|
|||||||
errorFront: errorFront,
|
errorFront: errorFront,
|
||||||
errorBack: errorBack,
|
errorBack: errorBack,
|
||||||
);
|
);
|
||||||
final colorScheme = scaleScheme.toColorScheme(
|
|
||||||
brightness,
|
|
||||||
);
|
|
||||||
final scaleTheme = ScaleTheme(
|
|
||||||
textTheme: textTheme, scheme: scaleScheme, config: scaleConfig);
|
|
||||||
|
|
||||||
final baseThemeData = ThemeData.from(
|
final scaleTheme =
|
||||||
colorScheme: colorScheme, textTheme: textTheme, useMaterial3: true);
|
ScaleTheme(textTheme: textTheme, scheme: scheme, config: scaleConfig);
|
||||||
|
|
||||||
|
final baseThemeData = scaleTheme.toThemeData(brightness);
|
||||||
|
|
||||||
|
final elevatedButtonTheme = ElevatedButtonThemeData(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: scheme.primaryScale.elementBackground,
|
||||||
|
foregroundColor: scheme.primaryScale.appText,
|
||||||
|
disabledBackgroundColor:
|
||||||
|
scheme.grayScale.elementBackground.withAlpha(0x7F),
|
||||||
|
disabledForegroundColor: scheme.grayScale.appText.withAlpha(0x7F),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
side: BorderSide(color: scheme.primaryScale.border),
|
||||||
|
borderRadius:
|
||||||
|
BorderRadius.circular(8 * scaleConfig.borderRadiusScale)))
|
||||||
|
.copyWith(side: WidgetStateProperty.resolveWith((states) {
|
||||||
|
if (states.contains(WidgetState.disabled)) {
|
||||||
|
return BorderSide(color: scheme.grayScale.border.withAlpha(0x7F));
|
||||||
|
} else if (states.contains(WidgetState.pressed)) {
|
||||||
|
return BorderSide(
|
||||||
|
color: scheme.primaryScale.border,
|
||||||
|
strokeAlign: BorderSide.strokeAlignOutside);
|
||||||
|
} else if (states.contains(WidgetState.hovered)) {
|
||||||
|
return BorderSide(color: scheme.primaryScale.hoverBorder);
|
||||||
|
} else if (states.contains(WidgetState.focused)) {
|
||||||
|
return BorderSide(color: scheme.primaryScale.hoverBorder, width: 2);
|
||||||
|
}
|
||||||
|
return BorderSide(color: scheme.primaryScale.border);
|
||||||
|
})));
|
||||||
|
|
||||||
final themeData = baseThemeData.copyWith(
|
final themeData = baseThemeData.copyWith(
|
||||||
appBarTheme: baseThemeData.appBarTheme.copyWith(
|
// chipTheme: baseThemeData.chipTheme.copyWith(
|
||||||
backgroundColor: scaleScheme.primaryScale.border,
|
// backgroundColor: scaleScheme.primaryScale.elementBackground,
|
||||||
foregroundColor: scaleScheme.primaryScale.borderText),
|
// selectedColor: scaleScheme.primaryScale.activeElementBackground,
|
||||||
bottomSheetTheme: baseThemeData.bottomSheetTheme.copyWith(
|
// surfaceTintColor: scaleScheme.primaryScale.hoverElementBackground,
|
||||||
elevation: 0,
|
// checkmarkColor: scaleScheme.primaryScale.border,
|
||||||
modalElevation: 0,
|
// side: BorderSide(color: scaleScheme.primaryScale.border)),
|
||||||
shape: RoundedRectangleBorder(
|
elevatedButtonTheme: elevatedButtonTheme,
|
||||||
borderRadius: BorderRadius.only(
|
|
||||||
topLeft: Radius.circular(16 * scaleConfig.borderRadiusScale),
|
|
||||||
topRight:
|
|
||||||
Radius.circular(16 * scaleConfig.borderRadiusScale)))),
|
|
||||||
canvasColor: scaleScheme.primaryScale.subtleBackground,
|
|
||||||
chipTheme: baseThemeData.chipTheme.copyWith(
|
|
||||||
backgroundColor: scaleScheme.primaryScale.elementBackground,
|
|
||||||
selectedColor: scaleScheme.primaryScale.activeElementBackground,
|
|
||||||
surfaceTintColor: scaleScheme.primaryScale.hoverElementBackground,
|
|
||||||
checkmarkColor: scaleScheme.primaryScale.border,
|
|
||||||
side: BorderSide(color: scaleScheme.primaryScale.border)),
|
|
||||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: scaleScheme.primaryScale.elementBackground,
|
|
||||||
foregroundColor: scaleScheme.primaryScale.appText,
|
|
||||||
disabledBackgroundColor: scaleScheme.grayScale.elementBackground,
|
|
||||||
disabledForegroundColor: scaleScheme.grayScale.appText,
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
side: BorderSide(color: scaleScheme.primaryScale.border),
|
|
||||||
borderRadius:
|
|
||||||
BorderRadius.circular(8 * scaleConfig.borderRadiusScale))),
|
|
||||||
),
|
|
||||||
textSelectionTheme: TextSelectionThemeData(
|
textSelectionTheme: TextSelectionThemeData(
|
||||||
cursorColor: scaleScheme.primaryScale.appText,
|
cursorColor: scheme.primaryScale.appText,
|
||||||
selectionColor: scaleScheme.primaryScale.appText.withAlpha(0x7F),
|
selectionColor: scheme.primaryScale.appText.withAlpha(0x7F),
|
||||||
selectionHandleColor: scaleScheme.primaryScale.appText),
|
selectionHandleColor: scheme.primaryScale.appText),
|
||||||
inputDecorationTheme:
|
extensions: <ThemeExtension<dynamic>>[scheme, scaleConfig, scaleTheme]);
|
||||||
ScaleInputDecoratorTheme(scaleScheme, scaleConfig, textTheme),
|
|
||||||
extensions: <ThemeExtension<dynamic>>[
|
|
||||||
scaleScheme,
|
|
||||||
scaleConfig,
|
|
||||||
scaleTheme
|
|
||||||
]);
|
|
||||||
|
|
||||||
return themeData;
|
return themeData;
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:radix_colors/radix_colors.dart';
|
import 'package:radix_colors/radix_colors.dart';
|
||||||
|
|
||||||
import '../../tools/tools.dart';
|
import '../../tools/tools.dart';
|
||||||
import 'scale_theme/scale_color.dart';
|
|
||||||
import 'scale_theme/scale_input_decorator_theme.dart';
|
|
||||||
import 'scale_theme/scale_scheme.dart';
|
|
||||||
import 'scale_theme/scale_theme.dart';
|
import 'scale_theme/scale_theme.dart';
|
||||||
|
|
||||||
enum RadixThemeColor {
|
enum RadixThemeColor {
|
||||||
@ -291,6 +288,7 @@ extension ToScaleColor on RadixColor {
|
|||||||
primaryText: scaleExtra.foregroundText,
|
primaryText: scaleExtra.foregroundText,
|
||||||
borderText: step12,
|
borderText: step12,
|
||||||
dialogBorder: step9,
|
dialogBorder: step9,
|
||||||
|
dialogBorderText: scaleExtra.foregroundText,
|
||||||
calloutBackground: step9,
|
calloutBackground: step9,
|
||||||
calloutText: scaleExtra.foregroundText,
|
calloutText: scaleExtra.foregroundText,
|
||||||
);
|
);
|
||||||
@ -609,7 +607,6 @@ ThemeData radixGenerator(Brightness brightness, RadixThemeColor themeColor) {
|
|||||||
final textTheme = makeRadixTextTheme(brightness);
|
final textTheme = makeRadixTextTheme(brightness);
|
||||||
final radix = _radixScheme(brightness, themeColor);
|
final radix = _radixScheme(brightness, themeColor);
|
||||||
final scaleScheme = radix.toScale();
|
final scaleScheme = radix.toScale();
|
||||||
final colorScheme = scaleScheme.toColorScheme(brightness);
|
|
||||||
final scaleConfig = ScaleConfig(
|
final scaleConfig = ScaleConfig(
|
||||||
useVisualIndicators: false,
|
useVisualIndicators: false,
|
||||||
preferBorders: false,
|
preferBorders: false,
|
||||||
@ -619,68 +616,7 @@ ThemeData radixGenerator(Brightness brightness, RadixThemeColor themeColor) {
|
|||||||
final scaleTheme = ScaleTheme(
|
final scaleTheme = ScaleTheme(
|
||||||
textTheme: textTheme, scheme: scaleScheme, config: scaleConfig);
|
textTheme: textTheme, scheme: scaleScheme, config: scaleConfig);
|
||||||
|
|
||||||
final baseThemeData = ThemeData.from(
|
final themeData = scaleTheme.toThemeData(brightness);
|
||||||
colorScheme: colorScheme, textTheme: textTheme, useMaterial3: true);
|
|
||||||
|
|
||||||
final themeData = baseThemeData.copyWith(
|
|
||||||
scrollbarTheme: baseThemeData.scrollbarTheme.copyWith(
|
|
||||||
thumbColor: WidgetStateProperty.resolveWith((states) {
|
|
||||||
if (states.contains(WidgetState.pressed)) {
|
|
||||||
return scaleScheme.primaryScale.border;
|
|
||||||
} else if (states.contains(WidgetState.hovered)) {
|
|
||||||
return scaleScheme.primaryScale.hoverBorder;
|
|
||||||
}
|
|
||||||
return scaleScheme.primaryScale.subtleBorder;
|
|
||||||
}), trackColor: WidgetStateProperty.resolveWith((states) {
|
|
||||||
if (states.contains(WidgetState.pressed)) {
|
|
||||||
return scaleScheme.primaryScale.activeElementBackground;
|
|
||||||
} else if (states.contains(WidgetState.hovered)) {
|
|
||||||
return scaleScheme.primaryScale.hoverElementBackground;
|
|
||||||
}
|
|
||||||
return scaleScheme.primaryScale.elementBackground;
|
|
||||||
}), trackBorderColor: WidgetStateProperty.resolveWith((states) {
|
|
||||||
if (states.contains(WidgetState.pressed)) {
|
|
||||||
return scaleScheme.primaryScale.subtleBorder;
|
|
||||||
} else if (states.contains(WidgetState.hovered)) {
|
|
||||||
return scaleScheme.primaryScale.subtleBorder;
|
|
||||||
}
|
|
||||||
return scaleScheme.primaryScale.subtleBorder;
|
|
||||||
})),
|
|
||||||
appBarTheme: baseThemeData.appBarTheme.copyWith(
|
|
||||||
backgroundColor: scaleScheme.primaryScale.border,
|
|
||||||
foregroundColor: scaleScheme.primaryScale.borderText),
|
|
||||||
bottomSheetTheme: baseThemeData.bottomSheetTheme.copyWith(
|
|
||||||
elevation: 0,
|
|
||||||
modalElevation: 0,
|
|
||||||
shape: const RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.only(
|
|
||||||
topLeft: Radius.circular(16),
|
|
||||||
topRight: Radius.circular(16)))),
|
|
||||||
canvasColor: scaleScheme.primaryScale.subtleBackground,
|
|
||||||
chipTheme: baseThemeData.chipTheme.copyWith(
|
|
||||||
backgroundColor: scaleScheme.primaryScale.elementBackground,
|
|
||||||
selectedColor: scaleScheme.primaryScale.activeElementBackground,
|
|
||||||
surfaceTintColor: scaleScheme.primaryScale.hoverElementBackground,
|
|
||||||
checkmarkColor: scaleScheme.primaryScale.primary,
|
|
||||||
side: BorderSide(color: scaleScheme.primaryScale.border)),
|
|
||||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: scaleScheme.primaryScale.elementBackground,
|
|
||||||
foregroundColor: scaleScheme.primaryScale.primary,
|
|
||||||
disabledBackgroundColor: scaleScheme.grayScale.elementBackground,
|
|
||||||
disabledForegroundColor: scaleScheme.grayScale.primary,
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
side: BorderSide(color: scaleScheme.primaryScale.border),
|
|
||||||
borderRadius:
|
|
||||||
BorderRadius.circular(8 * scaleConfig.borderRadiusScale))),
|
|
||||||
),
|
|
||||||
inputDecorationTheme:
|
|
||||||
ScaleInputDecoratorTheme(scaleScheme, scaleConfig, textTheme),
|
|
||||||
extensions: <ThemeExtension<dynamic>>[
|
|
||||||
scaleScheme,
|
|
||||||
scaleConfig,
|
|
||||||
scaleTheme
|
|
||||||
]);
|
|
||||||
|
|
||||||
return themeData;
|
return themeData;
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ class ScaleColor {
|
|||||||
required this.primaryText,
|
required this.primaryText,
|
||||||
required this.borderText,
|
required this.borderText,
|
||||||
required this.dialogBorder,
|
required this.dialogBorder,
|
||||||
|
required this.dialogBorderText,
|
||||||
required this.calloutBackground,
|
required this.calloutBackground,
|
||||||
required this.calloutText,
|
required this.calloutText,
|
||||||
});
|
});
|
||||||
@ -36,6 +37,7 @@ class ScaleColor {
|
|||||||
Color primaryText;
|
Color primaryText;
|
||||||
Color borderText;
|
Color borderText;
|
||||||
Color dialogBorder;
|
Color dialogBorder;
|
||||||
|
Color dialogBorderText;
|
||||||
Color calloutBackground;
|
Color calloutBackground;
|
||||||
Color calloutText;
|
Color calloutText;
|
||||||
|
|
||||||
@ -55,6 +57,7 @@ class ScaleColor {
|
|||||||
Color? foregroundText,
|
Color? foregroundText,
|
||||||
Color? borderText,
|
Color? borderText,
|
||||||
Color? dialogBorder,
|
Color? dialogBorder,
|
||||||
|
Color? dialogBorderText,
|
||||||
Color? calloutBackground,
|
Color? calloutBackground,
|
||||||
Color? calloutText,
|
Color? calloutText,
|
||||||
}) =>
|
}) =>
|
||||||
@ -76,6 +79,7 @@ class ScaleColor {
|
|||||||
primaryText: foregroundText ?? this.primaryText,
|
primaryText: foregroundText ?? this.primaryText,
|
||||||
borderText: borderText ?? this.borderText,
|
borderText: borderText ?? this.borderText,
|
||||||
dialogBorder: dialogBorder ?? this.dialogBorder,
|
dialogBorder: dialogBorder ?? this.dialogBorder,
|
||||||
|
dialogBorderText: dialogBorderText ?? this.dialogBorderText,
|
||||||
calloutBackground: calloutBackground ?? this.calloutBackground,
|
calloutBackground: calloutBackground ?? this.calloutBackground,
|
||||||
calloutText: calloutText ?? this.calloutText);
|
calloutText: calloutText ?? this.calloutText);
|
||||||
|
|
||||||
@ -112,6 +116,9 @@ class ScaleColor {
|
|||||||
const Color(0x00000000),
|
const Color(0x00000000),
|
||||||
dialogBorder: Color.lerp(a.dialogBorder, b.dialogBorder, t) ??
|
dialogBorder: Color.lerp(a.dialogBorder, b.dialogBorder, t) ??
|
||||||
const Color(0x00000000),
|
const Color(0x00000000),
|
||||||
|
dialogBorderText:
|
||||||
|
Color.lerp(a.dialogBorderText, b.dialogBorderText, t) ??
|
||||||
|
const Color(0x00000000),
|
||||||
calloutBackground:
|
calloutBackground:
|
||||||
Color.lerp(a.calloutBackground, b.calloutBackground, t) ??
|
Color.lerp(a.calloutBackground, b.calloutBackground, t) ??
|
||||||
const Color(0x00000000),
|
const Color(0x00000000),
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import 'package:animated_custom_dropdown/custom_dropdown.dart';
|
import 'package:animated_custom_dropdown/custom_dropdown.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'scale_scheme.dart';
|
|
||||||
import 'scale_theme.dart';
|
import 'scale_theme.dart';
|
||||||
|
|
||||||
class ScaleCustomDropdownTheme {
|
class ScaleCustomDropdownTheme {
|
||||||
|
@ -1,36 +1,61 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'scale_scheme.dart';
|
|
||||||
import 'scale_theme.dart';
|
import 'scale_theme.dart';
|
||||||
|
|
||||||
class ScaleInputDecoratorTheme extends InputDecorationTheme {
|
class ScaleInputDecoratorTheme extends InputDecorationTheme {
|
||||||
ScaleInputDecoratorTheme(
|
ScaleInputDecoratorTheme(
|
||||||
this._scaleScheme, ScaleConfig scaleConfig, this._textTheme)
|
this._scaleScheme, ScaleConfig scaleConfig, this._textTheme)
|
||||||
: super(
|
: hintAlpha = scaleConfig.preferBorders ? 127 : 255,
|
||||||
border: OutlineInputBorder(
|
super(
|
||||||
borderSide: BorderSide(color: _scaleScheme.primaryScale.border),
|
contentPadding: const EdgeInsets.all(8),
|
||||||
borderRadius:
|
labelStyle: TextStyle(color: _scaleScheme.primaryScale.subtleText),
|
||||||
BorderRadius.circular(8 * scaleConfig.borderRadiusScale)),
|
floatingLabelStyle:
|
||||||
contentPadding: const EdgeInsets.all(8),
|
TextStyle(color: _scaleScheme.primaryScale.subtleText),
|
||||||
labelStyle: TextStyle(
|
border: OutlineInputBorder(
|
||||||
color: _scaleScheme.primaryScale.subtleText.withAlpha(127)),
|
borderSide: BorderSide(color: _scaleScheme.primaryScale.border),
|
||||||
floatingLabelStyle:
|
borderRadius:
|
||||||
TextStyle(color: _scaleScheme.primaryScale.subtleText),
|
BorderRadius.circular(8 * scaleConfig.borderRadiusScale)),
|
||||||
focusedBorder: OutlineInputBorder(
|
enabledBorder: OutlineInputBorder(
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(color: _scaleScheme.primaryScale.border),
|
||||||
color: _scaleScheme.primaryScale.hoverBorder, width: 2),
|
borderRadius:
|
||||||
borderRadius:
|
BorderRadius.circular(8 * scaleConfig.borderRadiusScale)),
|
||||||
BorderRadius.circular(8 * scaleConfig.borderRadiusScale)));
|
disabledBorder: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: _scaleScheme.grayScale.border.withAlpha(0x7F)),
|
||||||
|
borderRadius:
|
||||||
|
BorderRadius.circular(8 * scaleConfig.borderRadiusScale)),
|
||||||
|
errorBorder: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(color: _scaleScheme.errorScale.border),
|
||||||
|
borderRadius:
|
||||||
|
BorderRadius.circular(8 * scaleConfig.borderRadiusScale)),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: _scaleScheme.primaryScale.hoverBorder, width: 2),
|
||||||
|
borderRadius:
|
||||||
|
BorderRadius.circular(8 * scaleConfig.borderRadiusScale)),
|
||||||
|
hoverColor:
|
||||||
|
_scaleScheme.primaryScale.hoverElementBackground.withAlpha(0x7F),
|
||||||
|
filled: true,
|
||||||
|
focusedErrorBorder: OutlineInputBorder(
|
||||||
|
borderSide:
|
||||||
|
BorderSide(color: _scaleScheme.errorScale.border, width: 2),
|
||||||
|
borderRadius:
|
||||||
|
BorderRadius.circular(8 * scaleConfig.borderRadiusScale)),
|
||||||
|
);
|
||||||
|
|
||||||
final ScaleScheme _scaleScheme;
|
final ScaleScheme _scaleScheme;
|
||||||
final TextTheme _textTheme;
|
final TextTheme _textTheme;
|
||||||
|
final int hintAlpha;
|
||||||
|
final int disabledAlpha = 127;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TextStyle? get hintStyle => WidgetStateTextStyle.resolveWith((states) {
|
TextStyle? get hintStyle => WidgetStateTextStyle.resolveWith((states) {
|
||||||
if (states.contains(WidgetState.disabled)) {
|
if (states.contains(WidgetState.disabled)) {
|
||||||
return TextStyle(color: _scaleScheme.grayScale.border);
|
return TextStyle(color: _scaleScheme.grayScale.border);
|
||||||
}
|
}
|
||||||
return TextStyle(color: _scaleScheme.primaryScale.border);
|
return TextStyle(
|
||||||
|
color: _scaleScheme.primaryScale.border.withAlpha(hintAlpha));
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -46,7 +71,7 @@ class ScaleInputDecoratorTheme extends InputDecorationTheme {
|
|||||||
WidgetStateBorderSide.resolveWith((states) {
|
WidgetStateBorderSide.resolveWith((states) {
|
||||||
if (states.contains(WidgetState.disabled)) {
|
if (states.contains(WidgetState.disabled)) {
|
||||||
return BorderSide(
|
return BorderSide(
|
||||||
color: _scaleScheme.grayScale.border.withAlpha(127));
|
color: _scaleScheme.grayScale.border.withAlpha(disabledAlpha));
|
||||||
}
|
}
|
||||||
if (states.contains(WidgetState.error)) {
|
if (states.contains(WidgetState.error)) {
|
||||||
if (states.contains(WidgetState.hovered)) {
|
if (states.contains(WidgetState.hovered)) {
|
||||||
@ -71,7 +96,7 @@ class ScaleInputDecoratorTheme extends InputDecorationTheme {
|
|||||||
BorderSide? get outlineBorder => WidgetStateBorderSide.resolveWith((states) {
|
BorderSide? get outlineBorder => WidgetStateBorderSide.resolveWith((states) {
|
||||||
if (states.contains(WidgetState.disabled)) {
|
if (states.contains(WidgetState.disabled)) {
|
||||||
return BorderSide(
|
return BorderSide(
|
||||||
color: _scaleScheme.grayScale.border.withAlpha(127));
|
color: _scaleScheme.grayScale.border.withAlpha(disabledAlpha));
|
||||||
}
|
}
|
||||||
if (states.contains(WidgetState.error)) {
|
if (states.contains(WidgetState.error)) {
|
||||||
if (states.contains(WidgetState.hovered)) {
|
if (states.contains(WidgetState.hovered)) {
|
||||||
@ -97,7 +122,7 @@ class ScaleInputDecoratorTheme extends InputDecorationTheme {
|
|||||||
@override
|
@override
|
||||||
Color? get prefixIconColor => WidgetStateColor.resolveWith((states) {
|
Color? get prefixIconColor => WidgetStateColor.resolveWith((states) {
|
||||||
if (states.contains(WidgetState.disabled)) {
|
if (states.contains(WidgetState.disabled)) {
|
||||||
return _scaleScheme.primaryScale.primary.withAlpha(127);
|
return _scaleScheme.primaryScale.primary.withAlpha(disabledAlpha);
|
||||||
}
|
}
|
||||||
if (states.contains(WidgetState.error)) {
|
if (states.contains(WidgetState.error)) {
|
||||||
return _scaleScheme.errorScale.primary;
|
return _scaleScheme.errorScale.primary;
|
||||||
@ -108,7 +133,7 @@ class ScaleInputDecoratorTheme extends InputDecorationTheme {
|
|||||||
@override
|
@override
|
||||||
Color? get suffixIconColor => WidgetStateColor.resolveWith((states) {
|
Color? get suffixIconColor => WidgetStateColor.resolveWith((states) {
|
||||||
if (states.contains(WidgetState.disabled)) {
|
if (states.contains(WidgetState.disabled)) {
|
||||||
return _scaleScheme.primaryScale.primary.withAlpha(127);
|
return _scaleScheme.primaryScale.primary.withAlpha(disabledAlpha);
|
||||||
}
|
}
|
||||||
if (states.contains(WidgetState.error)) {
|
if (states.contains(WidgetState.error)) {
|
||||||
return _scaleScheme.errorScale.primary;
|
return _scaleScheme.errorScale.primary;
|
||||||
@ -121,7 +146,39 @@ class ScaleInputDecoratorTheme extends InputDecorationTheme {
|
|||||||
final textStyle = _textTheme.bodyLarge ?? const TextStyle();
|
final textStyle = _textTheme.bodyLarge ?? const TextStyle();
|
||||||
if (states.contains(WidgetState.disabled)) {
|
if (states.contains(WidgetState.disabled)) {
|
||||||
return textStyle.copyWith(
|
return textStyle.copyWith(
|
||||||
color: _scaleScheme.grayScale.border.withAlpha(127));
|
color: _scaleScheme.grayScale.border.withAlpha(disabledAlpha));
|
||||||
|
}
|
||||||
|
if (states.contains(WidgetState.error)) {
|
||||||
|
if (states.contains(WidgetState.hovered)) {
|
||||||
|
return textStyle.copyWith(
|
||||||
|
color: _scaleScheme.errorScale.hoverBorder);
|
||||||
|
}
|
||||||
|
if (states.contains(WidgetState.focused)) {
|
||||||
|
return textStyle.copyWith(
|
||||||
|
color: _scaleScheme.errorScale.hoverBorder);
|
||||||
|
}
|
||||||
|
return textStyle.copyWith(
|
||||||
|
color: _scaleScheme.errorScale.subtleBorder);
|
||||||
|
}
|
||||||
|
if (states.contains(WidgetState.hovered)) {
|
||||||
|
return textStyle.copyWith(
|
||||||
|
color: _scaleScheme.primaryScale.hoverBorder);
|
||||||
|
}
|
||||||
|
if (states.contains(WidgetState.focused)) {
|
||||||
|
return textStyle.copyWith(
|
||||||
|
color: _scaleScheme.primaryScale.hoverBorder);
|
||||||
|
}
|
||||||
|
return textStyle.copyWith(
|
||||||
|
color: _scaleScheme.primaryScale.border.withAlpha(hintAlpha));
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
TextStyle? get floatingLabelStyle =>
|
||||||
|
WidgetStateTextStyle.resolveWith((states) {
|
||||||
|
final textStyle = _textTheme.bodyLarge ?? const TextStyle();
|
||||||
|
if (states.contains(WidgetState.disabled)) {
|
||||||
|
return textStyle.copyWith(
|
||||||
|
color: _scaleScheme.grayScale.border.withAlpha(disabledAlpha));
|
||||||
}
|
}
|
||||||
if (states.contains(WidgetState.error)) {
|
if (states.contains(WidgetState.error)) {
|
||||||
if (states.contains(WidgetState.hovered)) {
|
if (states.contains(WidgetState.hovered)) {
|
||||||
@ -146,18 +203,14 @@ class ScaleInputDecoratorTheme extends InputDecorationTheme {
|
|||||||
return textStyle.copyWith(color: _scaleScheme.primaryScale.border);
|
return textStyle.copyWith(color: _scaleScheme.primaryScale.border);
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
|
||||||
TextStyle? get floatingLabelStyle => labelStyle;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TextStyle? get helperStyle => WidgetStateTextStyle.resolveWith((states) {
|
TextStyle? get helperStyle => WidgetStateTextStyle.resolveWith((states) {
|
||||||
final textStyle = _textTheme.bodySmall ?? const TextStyle();
|
final textStyle = _textTheme.bodySmall ?? const TextStyle();
|
||||||
if (states.contains(WidgetState.disabled)) {
|
if (states.contains(WidgetState.disabled)) {
|
||||||
return textStyle.copyWith(
|
return textStyle.copyWith(color: _scaleScheme.grayScale.border);
|
||||||
color: _scaleScheme.grayScale.border.withAlpha(127));
|
|
||||||
}
|
}
|
||||||
return textStyle.copyWith(
|
return textStyle.copyWith(
|
||||||
color: _scaleScheme.secondaryScale.border.withAlpha(127));
|
color: _scaleScheme.primaryScale.border.withAlpha(hintAlpha));
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -165,6 +218,14 @@ class ScaleInputDecoratorTheme extends InputDecorationTheme {
|
|||||||
final textStyle = _textTheme.bodySmall ?? const TextStyle();
|
final textStyle = _textTheme.bodySmall ?? const TextStyle();
|
||||||
return textStyle.copyWith(color: _scaleScheme.errorScale.primary);
|
return textStyle.copyWith(color: _scaleScheme.errorScale.primary);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
|
super.debugFillProperties(properties);
|
||||||
|
properties
|
||||||
|
..add(IntProperty('disabledAlpha', disabledAlpha))
|
||||||
|
..add(IntProperty('hintAlpha', hintAlpha));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ScaleInputDecoratorThemeExt on ScaleTheme {
|
extension ScaleInputDecoratorThemeExt on ScaleTheme {
|
||||||
|
@ -76,8 +76,8 @@ class ScaleScheme extends ThemeExtension<ScaleScheme> {
|
|||||||
|
|
||||||
ColorScheme toColorScheme(Brightness brightness) => ColorScheme(
|
ColorScheme toColorScheme(Brightness brightness) => ColorScheme(
|
||||||
brightness: brightness,
|
brightness: brightness,
|
||||||
primary: primaryScale.primary, // reviewed
|
primary: primaryScale.primary,
|
||||||
onPrimary: primaryScale.primaryText, // reviewed
|
onPrimary: primaryScale.primaryText,
|
||||||
// primaryContainer: primaryScale.hoverElementBackground,
|
// primaryContainer: primaryScale.hoverElementBackground,
|
||||||
// onPrimaryContainer: primaryScale.subtleText,
|
// onPrimaryContainer: primaryScale.subtleText,
|
||||||
secondary: secondaryScale.primary,
|
secondary: secondaryScale.primary,
|
||||||
@ -92,15 +92,12 @@ class ScaleScheme extends ThemeExtension<ScaleScheme> {
|
|||||||
onError: errorScale.primaryText,
|
onError: errorScale.primaryText,
|
||||||
// errorContainer: errorScale.hoverElementBackground,
|
// errorContainer: errorScale.hoverElementBackground,
|
||||||
// onErrorContainer: errorScale.subtleText,
|
// onErrorContainer: errorScale.subtleText,
|
||||||
background: grayScale.appBackground, // reviewed
|
surface: primaryScale.appBackground,
|
||||||
onBackground: grayScale.appText, // reviewed
|
onSurface: primaryScale.appText,
|
||||||
surface: primaryScale.appBackground, // reviewed
|
|
||||||
onSurface: primaryScale.appText, // reviewed
|
|
||||||
surfaceVariant: secondaryScale.appBackground,
|
|
||||||
onSurfaceVariant: secondaryScale.appText,
|
onSurfaceVariant: secondaryScale.appText,
|
||||||
outline: primaryScale.border,
|
outline: primaryScale.border,
|
||||||
outlineVariant: secondaryScale.border,
|
outlineVariant: secondaryScale.border,
|
||||||
shadow: primaryScale.primary.darken(80),
|
shadow: primaryScale.appBackground.darken(60),
|
||||||
//scrim: primaryScale.background,
|
//scrim: primaryScale.background,
|
||||||
// inverseSurface: primaryScale.subtleText,
|
// inverseSurface: primaryScale.subtleText,
|
||||||
// onInverseSurface: primaryScale.subtleBackground,
|
// onInverseSurface: primaryScale.subtleBackground,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'scale_input_decorator_theme.dart';
|
||||||
import 'scale_scheme.dart';
|
import 'scale_scheme.dart';
|
||||||
|
|
||||||
export 'scale_color.dart';
|
export 'scale_color.dart';
|
||||||
@ -41,4 +42,86 @@ class ScaleTheme extends ThemeExtension<ScaleTheme> {
|
|||||||
scheme: scheme.lerp(other.scheme, t),
|
scheme: scheme.lerp(other.scheme, t),
|
||||||
config: config.lerp(other.config, t));
|
config: config.lerp(other.config, t));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ThemeData toThemeData(Brightness brightness) {
|
||||||
|
final colorScheme = scheme.toColorScheme(brightness);
|
||||||
|
|
||||||
|
final baseThemeData = ThemeData.from(
|
||||||
|
colorScheme: colorScheme, textTheme: textTheme, useMaterial3: true);
|
||||||
|
|
||||||
|
final elevatedButtonTheme = ElevatedButtonThemeData(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: scheme.primaryScale.elementBackground,
|
||||||
|
foregroundColor: scheme.primaryScale.appText,
|
||||||
|
disabledBackgroundColor:
|
||||||
|
scheme.grayScale.elementBackground.withAlpha(0x7F),
|
||||||
|
disabledForegroundColor:
|
||||||
|
scheme.grayScale.primary.withAlpha(0x7F),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
side: BorderSide(color: scheme.primaryScale.border),
|
||||||
|
borderRadius:
|
||||||
|
BorderRadius.circular(8 * config.borderRadiusScale)))
|
||||||
|
.copyWith(side: WidgetStateProperty.resolveWith((states) {
|
||||||
|
if (states.contains(WidgetState.disabled)) {
|
||||||
|
return BorderSide(color: scheme.grayScale.border.withAlpha(0x7F));
|
||||||
|
} else if (states.contains(WidgetState.pressed)) {
|
||||||
|
return BorderSide(
|
||||||
|
color: scheme.primaryScale.border,
|
||||||
|
strokeAlign: BorderSide.strokeAlignOutside);
|
||||||
|
} else if (states.contains(WidgetState.hovered)) {
|
||||||
|
return BorderSide(color: scheme.primaryScale.hoverBorder);
|
||||||
|
} else if (states.contains(WidgetState.focused)) {
|
||||||
|
return BorderSide(color: scheme.primaryScale.hoverBorder, width: 2);
|
||||||
|
}
|
||||||
|
return BorderSide(color: scheme.primaryScale.border);
|
||||||
|
})));
|
||||||
|
|
||||||
|
final themeData = baseThemeData.copyWith(
|
||||||
|
scrollbarTheme: baseThemeData.scrollbarTheme.copyWith(
|
||||||
|
thumbColor: WidgetStateProperty.resolveWith((states) {
|
||||||
|
if (states.contains(WidgetState.pressed)) {
|
||||||
|
return scheme.primaryScale.border;
|
||||||
|
} else if (states.contains(WidgetState.hovered)) {
|
||||||
|
return scheme.primaryScale.hoverBorder;
|
||||||
|
}
|
||||||
|
return scheme.primaryScale.subtleBorder;
|
||||||
|
}), trackColor: WidgetStateProperty.resolveWith((states) {
|
||||||
|
if (states.contains(WidgetState.pressed)) {
|
||||||
|
return scheme.primaryScale.activeElementBackground;
|
||||||
|
} else if (states.contains(WidgetState.hovered)) {
|
||||||
|
return scheme.primaryScale.hoverElementBackground;
|
||||||
|
}
|
||||||
|
return scheme.primaryScale.elementBackground;
|
||||||
|
}), trackBorderColor: WidgetStateProperty.resolveWith((states) {
|
||||||
|
if (states.contains(WidgetState.pressed)) {
|
||||||
|
return scheme.primaryScale.subtleBorder;
|
||||||
|
} else if (states.contains(WidgetState.hovered)) {
|
||||||
|
return scheme.primaryScale.subtleBorder;
|
||||||
|
}
|
||||||
|
return scheme.primaryScale.subtleBorder;
|
||||||
|
})),
|
||||||
|
appBarTheme: baseThemeData.appBarTheme.copyWith(
|
||||||
|
backgroundColor: scheme.primaryScale.border,
|
||||||
|
foregroundColor: scheme.primaryScale.borderText),
|
||||||
|
bottomSheetTheme: baseThemeData.bottomSheetTheme.copyWith(
|
||||||
|
elevation: 0,
|
||||||
|
modalElevation: 0,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(16 * config.borderRadiusScale),
|
||||||
|
topRight: Radius.circular(16 * config.borderRadiusScale)))),
|
||||||
|
canvasColor: scheme.primaryScale.subtleBackground,
|
||||||
|
chipTheme: baseThemeData.chipTheme.copyWith(
|
||||||
|
backgroundColor: scheme.primaryScale.elementBackground,
|
||||||
|
selectedColor: scheme.primaryScale.activeElementBackground,
|
||||||
|
surfaceTintColor: scheme.primaryScale.hoverElementBackground,
|
||||||
|
checkmarkColor: scheme.primaryScale.primary,
|
||||||
|
side: BorderSide(color: scheme.primaryScale.border)),
|
||||||
|
elevatedButtonTheme: elevatedButtonTheme,
|
||||||
|
inputDecorationTheme:
|
||||||
|
ScaleInputDecoratorTheme(scheme, config, textTheme),
|
||||||
|
extensions: <ThemeExtension<dynamic>>[scheme, config, this]);
|
||||||
|
|
||||||
|
return themeData;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,10 @@ extension ScaleTileThemeExt on ScaleTheme {
|
|||||||
|
|
||||||
final shapeBorder = RoundedRectangleBorder(
|
final shapeBorder = RoundedRectangleBorder(
|
||||||
side: config.useVisualIndicators
|
side: config.useVisualIndicators
|
||||||
? BorderSide(width: 2, color: borderColor, strokeAlign: 0)
|
? BorderSide(
|
||||||
|
width: 2,
|
||||||
|
color: borderColor,
|
||||||
|
)
|
||||||
: BorderSide.none,
|
: BorderSide.none,
|
||||||
borderRadius: BorderRadius.circular(8 * config.borderRadiusScale));
|
borderRadius: BorderRadius.circular(8 * config.borderRadiusScale));
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'scale_scheme.dart';
|
|
||||||
import 'scale_theme.dart';
|
import 'scale_theme.dart';
|
||||||
|
|
||||||
enum ScaleToastKind {
|
enum ScaleToastKind {
|
||||||
@ -35,14 +34,6 @@ extension ScaleToastThemeExt on ScaleTheme {
|
|||||||
ScaleToastTheme toastTheme(ScaleToastKind kind) {
|
ScaleToastTheme toastTheme(ScaleToastKind kind) {
|
||||||
final toastScaleColor = scheme.scale(ScaleKind.tertiary);
|
final toastScaleColor = scheme.scale(ScaleKind.tertiary);
|
||||||
|
|
||||||
Icon icon;
|
|
||||||
switch (kind) {
|
|
||||||
case ScaleToastKind.info:
|
|
||||||
icon = const Icon(Icons.info, size: 32);
|
|
||||||
case ScaleToastKind.error:
|
|
||||||
icon = const Icon(Icons.dangerous, size: 32);
|
|
||||||
}
|
|
||||||
|
|
||||||
final primaryColor = toastScaleColor.calloutText;
|
final primaryColor = toastScaleColor.calloutText;
|
||||||
final borderColor = toastScaleColor.border;
|
final borderColor = toastScaleColor.border;
|
||||||
final backgroundColor = config.useVisualIndicators
|
final backgroundColor = config.useVisualIndicators
|
||||||
@ -54,6 +45,13 @@ extension ScaleToastThemeExt on ScaleTheme {
|
|||||||
final titleColor = config.useVisualIndicators
|
final titleColor = config.useVisualIndicators
|
||||||
? toastScaleColor.calloutBackground
|
? toastScaleColor.calloutBackground
|
||||||
: toastScaleColor.calloutText;
|
: toastScaleColor.calloutText;
|
||||||
|
Icon icon;
|
||||||
|
switch (kind) {
|
||||||
|
case ScaleToastKind.info:
|
||||||
|
icon = Icon(Icons.info, size: 32, color: primaryColor);
|
||||||
|
case ScaleToastKind.error:
|
||||||
|
icon = Icon(Icons.dangerous, size: 32, color: primaryColor);
|
||||||
|
}
|
||||||
|
|
||||||
return ScaleToastTheme(
|
return ScaleToastTheme(
|
||||||
primaryColor: primaryColor,
|
primaryColor: primaryColor,
|
||||||
|
@ -5,7 +5,7 @@ import 'package:flutter/widgets.dart';
|
|||||||
import '../theme.dart';
|
import '../theme.dart';
|
||||||
|
|
||||||
class AvatarWidget extends StatelessWidget {
|
class AvatarWidget extends StatelessWidget {
|
||||||
AvatarWidget({
|
const AvatarWidget({
|
||||||
required String name,
|
required String name,
|
||||||
required double size,
|
required double size,
|
||||||
required Color borderColor,
|
required Color borderColor,
|
||||||
@ -38,15 +38,11 @@ class AvatarWidget extends StatelessWidget {
|
|||||||
height: _size,
|
height: _size,
|
||||||
width: _size,
|
width: _size,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
border: _scaleConfig.useVisualIndicators
|
border: Border.all(
|
||||||
? Border.all(
|
color: _borderColor,
|
||||||
color: _borderColor,
|
width: 1 * (_size ~/ 32 + 1),
|
||||||
width: 1 * (_size ~/ 32 + 1),
|
strokeAlign: BorderSide.strokeAlignOutside)),
|
||||||
strokeAlign: BorderSide.strokeAlignOutside)
|
|
||||||
: null,
|
|
||||||
color: _borderColor,
|
|
||||||
),
|
|
||||||
child: AvatarImage(
|
child: AvatarImage(
|
||||||
//size: 32,
|
//size: 32,
|
||||||
backgroundImage: _imageProvider,
|
backgroundImage: _imageProvider,
|
||||||
@ -55,14 +51,15 @@ class AvatarWidget extends StatelessWidget {
|
|||||||
? _foregroundColor
|
? _foregroundColor
|
||||||
: _backgroundColor,
|
: _backgroundColor,
|
||||||
child: Text(
|
child: Text(
|
||||||
shortname,
|
shortname.isNotEmpty ? shortname : '?',
|
||||||
|
softWrap: false,
|
||||||
style: _textStyle.copyWith(
|
style: _textStyle.copyWith(
|
||||||
color: _scaleConfig.useVisualIndicators &&
|
color: _scaleConfig.useVisualIndicators &&
|
||||||
!_scaleConfig.preferBorders
|
!_scaleConfig.preferBorders
|
||||||
? _backgroundColor
|
? _backgroundColor
|
||||||
: _foregroundColor,
|
: _foregroundColor,
|
||||||
),
|
),
|
||||||
)));
|
).fit().paddingAll(_size / 16)));
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -125,16 +125,21 @@ class SliderTile extends StatelessWidget {
|
|||||||
child: ListTile(
|
child: ListTile(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
dense: true,
|
dense: true,
|
||||||
visualDensity: const VisualDensity(vertical: -4),
|
visualDensity:
|
||||||
|
const VisualDensity(horizontal: -4, vertical: -4),
|
||||||
title: Text(
|
title: Text(
|
||||||
title,
|
title,
|
||||||
overflow: TextOverflow.fade,
|
overflow: TextOverflow.fade,
|
||||||
softWrap: false,
|
softWrap: false,
|
||||||
),
|
),
|
||||||
subtitle: subtitle.isNotEmpty ? Text(subtitle) : null,
|
subtitle: subtitle.isNotEmpty ? Text(subtitle) : null,
|
||||||
|
minTileHeight: 48,
|
||||||
iconColor: scaleTileTheme.textColor,
|
iconColor: scaleTileTheme.textColor,
|
||||||
textColor: scaleTileTheme.textColor,
|
textColor: scaleTileTheme.textColor,
|
||||||
leading: FittedBox(child: leading),
|
leading:
|
||||||
trailing: FittedBox(child: trailing))))));
|
leading != null ? FittedBox(child: leading) : null,
|
||||||
|
trailing: trailing != null
|
||||||
|
? FittedBox(child: trailing)
|
||||||
|
: null)))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,16 +94,11 @@ Future<void> showErrorModal(
|
|||||||
{required BuildContext context,
|
{required BuildContext context,
|
||||||
required String title,
|
required String title,
|
||||||
required String text}) async {
|
required String text}) async {
|
||||||
// final theme = Theme.of(context);
|
|
||||||
// final scale = theme.extension<ScaleScheme>()!;
|
|
||||||
// final scaleConfig = theme.extension<ScaleConfig>()!;
|
|
||||||
|
|
||||||
await Alert(
|
await Alert(
|
||||||
context: context,
|
context: context,
|
||||||
style: _alertStyle(context),
|
style: _alertStyle(context),
|
||||||
useRootNavigator: false,
|
useRootNavigator: false,
|
||||||
type: AlertType.error,
|
type: AlertType.error,
|
||||||
//style: AlertStyle(),
|
|
||||||
title: title,
|
title: title,
|
||||||
desc: text,
|
desc: text,
|
||||||
buttons: [
|
buttons: [
|
||||||
@ -122,10 +117,6 @@ Future<void> showErrorModal(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|
||||||
//backgroundColor: Colors.black,
|
|
||||||
//titleColor: Colors.white,
|
|
||||||
//textColor: Colors.white,
|
|
||||||
).show();
|
).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,16 +135,11 @@ Future<void> showWarningModal(
|
|||||||
{required BuildContext context,
|
{required BuildContext context,
|
||||||
required String title,
|
required String title,
|
||||||
required String text}) async {
|
required String text}) async {
|
||||||
// final theme = Theme.of(context);
|
|
||||||
// final scale = theme.extension<ScaleScheme>()!;
|
|
||||||
// final scaleConfig = theme.extension<ScaleConfig>()!;
|
|
||||||
|
|
||||||
await Alert(
|
await Alert(
|
||||||
context: context,
|
context: context,
|
||||||
style: _alertStyle(context),
|
style: _alertStyle(context),
|
||||||
useRootNavigator: false,
|
useRootNavigator: false,
|
||||||
type: AlertType.warning,
|
type: AlertType.warning,
|
||||||
//style: AlertStyle(),
|
|
||||||
title: title,
|
title: title,
|
||||||
desc: text,
|
desc: text,
|
||||||
buttons: [
|
buttons: [
|
||||||
@ -172,10 +158,6 @@ Future<void> showWarningModal(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|
||||||
//backgroundColor: Colors.black,
|
|
||||||
//titleColor: Colors.white,
|
|
||||||
//textColor: Colors.white,
|
|
||||||
).show();
|
).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,16 +165,11 @@ Future<void> showWarningWidgetModal(
|
|||||||
{required BuildContext context,
|
{required BuildContext context,
|
||||||
required String title,
|
required String title,
|
||||||
required Widget child}) async {
|
required Widget child}) async {
|
||||||
// final theme = Theme.of(context);
|
|
||||||
// final scale = theme.extension<ScaleScheme>()!;
|
|
||||||
// final scaleConfig = theme.extension<ScaleConfig>()!;
|
|
||||||
|
|
||||||
await Alert(
|
await Alert(
|
||||||
context: context,
|
context: context,
|
||||||
style: _alertStyle(context),
|
style: _alertStyle(context),
|
||||||
useRootNavigator: false,
|
useRootNavigator: false,
|
||||||
type: AlertType.warning,
|
type: AlertType.warning,
|
||||||
//style: AlertStyle(),
|
|
||||||
title: title,
|
title: title,
|
||||||
content: child,
|
content: child,
|
||||||
buttons: [
|
buttons: [
|
||||||
@ -211,10 +188,6 @@ Future<void> showWarningWidgetModal(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|
||||||
//backgroundColor: Colors.black,
|
|
||||||
//titleColor: Colors.white,
|
|
||||||
//textColor: Colors.white,
|
|
||||||
).show();
|
).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,10 +195,6 @@ Future<bool> showConfirmModal(
|
|||||||
{required BuildContext context,
|
{required BuildContext context,
|
||||||
required String title,
|
required String title,
|
||||||
required String text}) async {
|
required String text}) async {
|
||||||
// final theme = Theme.of(context);
|
|
||||||
// final scale = theme.extension<ScaleScheme>()!;
|
|
||||||
// final scaleConfig = theme.extension<ScaleConfig>()!;
|
|
||||||
|
|
||||||
var confirm = false;
|
var confirm = false;
|
||||||
|
|
||||||
await Alert(
|
await Alert(
|
||||||
@ -266,10 +235,6 @@ Future<bool> showConfirmModal(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|
||||||
//backgroundColor: Colors.black,
|
|
||||||
//titleColor: Colors.white,
|
|
||||||
//textColor: Colors.white,
|
|
||||||
).show();
|
).show();
|
||||||
|
|
||||||
return confirm;
|
return confirm;
|
||||||
|
@ -21,7 +21,7 @@ class StyledDialog extends StatelessWidget {
|
|||||||
Radius.circular(16 * scaleConfig.borderRadiusScale)),
|
Radius.circular(16 * scaleConfig.borderRadiusScale)),
|
||||||
),
|
),
|
||||||
contentPadding: const EdgeInsets.all(4),
|
contentPadding: const EdgeInsets.all(4),
|
||||||
backgroundColor: scale.primaryScale.dialogBorder,
|
backgroundColor: scale.primaryScale.border,
|
||||||
title: Text(
|
title: Text(
|
||||||
title,
|
title,
|
||||||
style: textTheme.titleMedium!
|
style: textTheme.titleMedium!
|
||||||
|
@ -114,14 +114,13 @@ extension LabelExt on Widget {
|
|||||||
{ScaleColor? scale}) {
|
{ScaleColor? scale}) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final scaleScheme = theme.extension<ScaleScheme>()!;
|
final scaleScheme = theme.extension<ScaleScheme>()!;
|
||||||
// final scaleConfig = theme.extension<ScaleConfig>()!;
|
|
||||||
scale = scale ?? scaleScheme.primaryScale;
|
scale = scale ?? scaleScheme.primaryScale;
|
||||||
|
|
||||||
return Wrap(crossAxisAlignment: WrapCrossAlignment.center, children: [
|
return Wrap(crossAxisAlignment: WrapCrossAlignment.end, children: [
|
||||||
Text(
|
Text(
|
||||||
'$label:',
|
'$label:',
|
||||||
style: theme.textTheme.titleLarge!.copyWith(color: scale.border),
|
style: theme.textTheme.bodyLarge!.copyWith(color: scale.hoverBorder),
|
||||||
).paddingLTRB(0, 0, 8, 8),
|
).paddingLTRB(0, 0, 8, 0),
|
||||||
this
|
this
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -431,6 +430,31 @@ Widget styledTitleContainer({
|
|||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget styledCard({
|
||||||
|
required BuildContext context,
|
||||||
|
required Widget child,
|
||||||
|
Color? borderColor,
|
||||||
|
Color? backgroundColor,
|
||||||
|
Color? titleColor,
|
||||||
|
}) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final scale = theme.extension<ScaleScheme>()!;
|
||||||
|
final scaleConfig = theme.extension<ScaleConfig>()!;
|
||||||
|
|
||||||
|
return DecoratedBox(
|
||||||
|
decoration: ShapeDecoration(
|
||||||
|
color: backgroundColor ?? scale.primaryScale.elementBackground,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
side: (scaleConfig.useVisualIndicators || scaleConfig.preferBorders)
|
||||||
|
? BorderSide(
|
||||||
|
color: borderColor ?? scale.primaryScale.border, width: 2)
|
||||||
|
: BorderSide.none,
|
||||||
|
borderRadius:
|
||||||
|
BorderRadius.circular(12 * scaleConfig.borderRadiusScale),
|
||||||
|
)),
|
||||||
|
child: child.paddingAll(4));
|
||||||
|
}
|
||||||
|
|
||||||
Widget styledBottomSheet({
|
Widget styledBottomSheet({
|
||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
required String title,
|
required String title,
|
||||||
@ -500,6 +524,12 @@ const grayColorFilter = ColorFilter.matrix(<double>[
|
|||||||
0,
|
0,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const dodgeFilter =
|
||||||
|
ColorFilter.mode(Color.fromARGB(96, 255, 255, 255), BlendMode.srcIn);
|
||||||
|
|
||||||
|
const overlayFilter =
|
||||||
|
ColorFilter.mode(Color.fromARGB(127, 255, 255, 255), BlendMode.dstIn);
|
||||||
|
|
||||||
Container clipBorder({
|
Container clipBorder({
|
||||||
required bool clipEnabled,
|
required bool clipEnabled,
|
||||||
required bool borderEnabled,
|
required bool borderEnabled,
|
||||||
@ -510,16 +540,17 @@ Container clipBorder({
|
|||||||
// ignore: avoid_unnecessary_containers, use_decorated_box
|
// ignore: avoid_unnecessary_containers, use_decorated_box
|
||||||
Container(
|
Container(
|
||||||
decoration: ShapeDecoration(
|
decoration: ShapeDecoration(
|
||||||
color: borderColor,
|
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: clipEnabled
|
side: borderEnabled && clipEnabled
|
||||||
? BorderRadius.circular(borderRadius)
|
? BorderSide(color: borderColor, width: 2)
|
||||||
: BorderRadius.zero,
|
: BorderSide.none,
|
||||||
)),
|
borderRadius: clipEnabled
|
||||||
|
? BorderRadius.circular(borderRadius)
|
||||||
|
: BorderRadius.zero,
|
||||||
|
)),
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.antiAliasWithSaveLayer,
|
||||||
borderRadius: clipEnabled
|
borderRadius: clipEnabled
|
||||||
? BorderRadius.circular(borderRadius)
|
? BorderRadius.circular(borderRadius - 2)
|
||||||
: BorderRadius.zero,
|
: BorderRadius.zero,
|
||||||
child: child)
|
child: child));
|
||||||
.paddingAll(clipEnabled && borderEnabled ? 2 : 0));
|
|
||||||
|
@ -216,7 +216,7 @@ class _DeveloperPageState extends State<DeveloperPage> {
|
|||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final confirm = await showConfirmModal(
|
final confirm = await showConfirmModal(
|
||||||
context: context,
|
context: context,
|
||||||
title: translate('toast.confirm'),
|
title: translate('confirmation.confirm'),
|
||||||
text: translate('developer.are_you_sure_clear'),
|
text: translate('developer.are_you_sure_clear'),
|
||||||
);
|
);
|
||||||
if (confirm && context.mounted) {
|
if (confirm && context.mounted) {
|
||||||
@ -224,7 +224,7 @@ class _DeveloperPageState extends State<DeveloperPage> {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
SizedBox.fromSize(
|
SizedBox.fromSize(
|
||||||
size: const Size(120, 48),
|
size: const Size(140, 48),
|
||||||
child: CustomDropdown<LogLevelDropdownItem>(
|
child: CustomDropdown<LogLevelDropdownItem>(
|
||||||
items: _logLevelDropdownItems,
|
items: _logLevelDropdownItems,
|
||||||
initialItem: _logLevelDropdownItems
|
initialItem: _logLevelDropdownItems
|
||||||
|
@ -96,6 +96,14 @@ packages:
|
|||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "0.1.7"
|
version: "0.1.7"
|
||||||
|
auto_size_text:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: auto_size_text
|
||||||
|
sha256: "3f5261cd3fb5f2a9ab4e2fc3fba84fd9fcaac8821f20a1d4e71f557521b22599"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.0"
|
||||||
awesome_extensions:
|
awesome_extensions:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -16,6 +16,7 @@ dependencies:
|
|||||||
ansicolor: ^2.0.3
|
ansicolor: ^2.0.3
|
||||||
archive: ^4.0.4
|
archive: ^4.0.4
|
||||||
async_tools: ^0.1.7
|
async_tools: ^0.1.7
|
||||||
|
auto_size_text: ^3.0.0
|
||||||
awesome_extensions: ^2.0.21
|
awesome_extensions: ^2.0.21
|
||||||
badges: ^3.1.2
|
badges: ^3.1.2
|
||||||
basic_utils: ^5.8.2
|
basic_utils: ^5.8.2
|
||||||
@ -158,14 +159,16 @@ flutter:
|
|||||||
- assets/i18n/en.json
|
- assets/i18n/en.json
|
||||||
# Launcher icon
|
# Launcher icon
|
||||||
- assets/launcher/icon.png
|
- assets/launcher/icon.png
|
||||||
# Images
|
# Vector Images
|
||||||
- assets/images/splash.svg
|
- assets/images/grid.svg
|
||||||
- assets/images/icon.svg
|
- assets/images/icon.svg
|
||||||
|
- assets/images/splash.svg
|
||||||
- assets/images/title.svg
|
- assets/images/title.svg
|
||||||
- assets/images/vlogo.svg
|
- assets/images/vlogo.svg
|
||||||
|
# Raster Images
|
||||||
- assets/images/ellet.png
|
- assets/images/ellet.png
|
||||||
- assets/images/toilet.png
|
|
||||||
- assets/images/handshake.png
|
- assets/images/handshake.png
|
||||||
|
- assets/images/toilet.png
|
||||||
# Printing
|
# Printing
|
||||||
- assets/js/pdf/3.2.146/pdf.min.js
|
- assets/js/pdf/3.2.146/pdf.min.js
|
||||||
# Sounds
|
# Sounds
|
||||||
|
Loading…
x
Reference in New Issue
Block a user