diff --git a/android/app/.gitignore b/android/app/.gitignore
new file mode 100644
index 0000000..0e60033
--- /dev/null
+++ b/android/app/.gitignore
@@ -0,0 +1 @@
+.cxx
diff --git a/assets/i18n/en.json b/assets/i18n/en.json
index d45a746..61bb802 100644
--- a/assets/i18n/en.json
+++ b/assets/i18n/en.json
@@ -50,7 +50,6 @@
"edit_account_page": {
"titlebar": "Edit Account",
"header": "Account Profile",
- "update": "Update",
"instructions": "This information will be shared with the people you invite to connect with you on VeilidChat.",
"error": "Account modification error",
"name": "Name",
@@ -64,9 +63,9 @@
"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_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": "Failed to remove account.\n\nTry again when you have a more stable network connection.",
- "failed_to_destroy": "Failed to destroy account.\n\nTry again when you have a more stable network connection.",
+ "failed_to_remove_title": "Failed to remove account",
+ "try_again_network": "Try again when you have a more stable network connection",
+ "failed_to_destroy_title": "Failed to destroy account",
"account_removed": "Account removed successfully",
"account_destroyed": "Account destroyed successfully"
},
@@ -83,6 +82,12 @@
"view": "View",
"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": {
"ok": "Ok",
"cancel": "Cancel",
@@ -94,10 +99,11 @@
"close": "Close",
"yes": "Yes",
"no": "No",
- "waiting_for_network": "Waiting For Network"
+ "update": "Update",
+ "waiting_for_network": "Waiting For Network",
+ "chat": "Chat"
},
"toast": {
- "confirm": "Confirm",
"error": "Error",
"info": "Info"
},
@@ -122,7 +128,7 @@
"contacts": "Contacts",
"edit_contact": "Edit Contact",
"invitations": "Invitations",
- "no_contact_selected": "Double-click a contact to edit it",
+ "no_contact_selected": "Select a contact to view or edit",
"new_chat": "Open Chat",
"close_contact": "Close Contact"
},
@@ -141,9 +147,7 @@
"form_nickname": "Nickname",
"form_notes": "Notes",
"form_fingerprint": "Fingerprint",
- "form_show_availability": "Show availability",
- "save": "Save",
- "save_disabled": "Save"
+ "form_show_availability": "Show availability"
},
"availability": {
"unspecified": "Unspecified",
@@ -171,18 +175,21 @@
"create_invitation_dialog": {
"title": "Create Contact Invitation",
"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!",
- "enter_message_hint": "Enter message for contact (optional)",
- "message_to_contact": "Message to send with invitation (not encrypted)",
"generate": "Generate Invitation",
- "message": "Message",
"unlocked": "Unlocked",
"pin": "PIN",
"password": "Password",
"protect_this_invitation": "Protect this invitation:",
"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.",
"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",
@@ -192,6 +199,7 @@
"invitation_copied": "Invitation Copied"
},
"invitation_dialog": {
+ "to": "To",
"message_from_contact": "Message from contact",
"validating": "Validating...",
"failed_to_accept": "Failed to accept contact invitation",
@@ -278,6 +286,7 @@
"delivery": "Delivery",
"enable_badge": "Enable icon 'badge' bubble",
"enable_notifications": "Enable notifications",
+ "enable_wallpaper": "Enable wallpaper",
"message_notification_content": "Message notification content",
"invitation_accepted": "On invitation accept/reject",
"message_received": "On message received",
diff --git a/assets/images/grid.svg b/assets/images/grid.svg
new file mode 100644
index 0000000..f30d577
--- /dev/null
+++ b/assets/images/grid.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/images/wallpaper/arctic.svg b/assets/images/wallpaper/arctic.svg
new file mode 100644
index 0000000..ebcaee4
--- /dev/null
+++ b/assets/images/wallpaper/arctic.svg
@@ -0,0 +1,13 @@
+
+
+
diff --git a/assets/images/wallpaper/babydoll.svg b/assets/images/wallpaper/babydoll.svg
new file mode 100644
index 0000000..55f28a3
--- /dev/null
+++ b/assets/images/wallpaper/babydoll.svg
@@ -0,0 +1,663 @@
+
+
+
diff --git a/assets/images/wallpaper/eggplant.svg b/assets/images/wallpaper/eggplant.svg
new file mode 100644
index 0000000..48e6ad3
--- /dev/null
+++ b/assets/images/wallpaper/eggplant.svg
@@ -0,0 +1,495 @@
+
+
+
diff --git a/assets/images/wallpaper/elite.svg b/assets/images/wallpaper/elite.svg
new file mode 100644
index 0000000..606d21f
--- /dev/null
+++ b/assets/images/wallpaper/elite.svg
@@ -0,0 +1,6 @@
+
+
+
diff --git a/assets/images/wallpaper/forest.svg b/assets/images/wallpaper/forest.svg
new file mode 100644
index 0000000..fb61069
--- /dev/null
+++ b/assets/images/wallpaper/forest.svg
@@ -0,0 +1,6888 @@
+
+
+
diff --git a/assets/images/wallpaper/garden.svg b/assets/images/wallpaper/garden.svg
new file mode 100644
index 0000000..f4e6372
--- /dev/null
+++ b/assets/images/wallpaper/garden.svg
@@ -0,0 +1,8182 @@
+
+
+
diff --git a/assets/images/wallpaper/gold.svg b/assets/images/wallpaper/gold.svg
new file mode 100644
index 0000000..16e5ab5
--- /dev/null
+++ b/assets/images/wallpaper/gold.svg
@@ -0,0 +1,26 @@
+
+
+
diff --git a/assets/images/wallpaper/grim.svg b/assets/images/wallpaper/grim.svg
new file mode 100644
index 0000000..7c3968f
--- /dev/null
+++ b/assets/images/wallpaper/grim.svg
@@ -0,0 +1,928 @@
+
+
+
diff --git a/assets/images/wallpaper/lapis.svg b/assets/images/wallpaper/lapis.svg
new file mode 100644
index 0000000..c78fa58
--- /dev/null
+++ b/assets/images/wallpaper/lapis.svg
@@ -0,0 +1,39 @@
+
+
+
diff --git a/assets/images/wallpaper/lime.svg b/assets/images/wallpaper/lime.svg
new file mode 100644
index 0000000..d65222e
--- /dev/null
+++ b/assets/images/wallpaper/lime.svg
@@ -0,0 +1,25 @@
+
+
+
diff --git a/assets/images/wallpaper/scarlet.svg b/assets/images/wallpaper/scarlet.svg
new file mode 100644
index 0000000..7047ca7
--- /dev/null
+++ b/assets/images/wallpaper/scarlet.svg
@@ -0,0 +1,349 @@
+
+
+
diff --git a/assets/images/wallpaper/vapor.svg b/assets/images/wallpaper/vapor.svg
new file mode 100644
index 0000000..34bfe59
--- /dev/null
+++ b/assets/images/wallpaper/vapor.svg
@@ -0,0 +1,25 @@
+
+
+
diff --git a/build.yaml b/build.yaml
index 950fe95..77b9050 100644
--- a/build.yaml
+++ b/build.yaml
@@ -1,6 +1,9 @@
targets:
$default:
builders:
+ freezed:
+ options:
+ generic_argument_factories: true
json_serializable:
options:
explicit_to_json: true
diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index 8b3e7d0..3e31b44 100644
--- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -48,6 +48,7 @@
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
+ enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
diff --git a/lib/account_manager/cubits/account_record_cubit.dart b/lib/account_manager/cubits/account_record_cubit.dart
index 9a73246..16ab2e0 100644
--- a/lib/account_manager/cubits/account_record_cubit.dart
+++ b/lib/account_manager/cubits/account_record_cubit.dart
@@ -1,7 +1,6 @@
import 'dart:async';
import 'package:async_tools/async_tools.dart';
-import 'package:protobuf/protobuf.dart';
import 'package:veilid_support/veilid_support.dart';
import '../../proto/proto.dart' as proto;
@@ -47,53 +46,30 @@ class AccountRecordCubit extends DefaultDHTRecordCubit {
// Public Interface
void updateAccount(
- AccountSpec accountSpec, Future Function() onSuccess) {
- _sspUpdate.updateState((accountSpec, onSuccess), (state) async {
+ AccountSpec accountSpec, Future Function() onChanged) {
+ _sspUpdate.updateState((accountSpec, onChanged), (state) async {
await _updateAccountAsync(state.$1, state.$2);
});
}
Future _updateAccountAsync(
- AccountSpec accountSpec, Future Function() onSuccess) async {
- var changed = false;
-
+ AccountSpec accountSpec, Future Function() onChanged) async {
+ var changed = true;
await record?.eventualUpdateProtobuf(proto.Account.fromBuffer, (old) async {
- changed = false;
if (old == null) {
return null;
}
- final newAccount = old.deepCopy()
- ..profile.name = accountSpec.name
- ..profile.pronouns = accountSpec.pronouns
- ..profile.about = accountSpec.about
- ..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;
+ final oldAccountSpec = AccountSpec.fromProto(old);
+ changed = oldAccountSpec != accountSpec;
+ if (!changed) {
+ return null;
+ }
- if (newAccount.profile != old.profile ||
- 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;
+ return accountSpec.updateProto(old);
});
if (changed) {
- await onSuccess();
+ await onChanged();
}
}
diff --git a/lib/account_manager/models/account_spec.dart b/lib/account_manager/models/account_spec.dart
index 539b8d0..918e192 100644
--- a/lib/account_manager/models/account_spec.dart
+++ b/lib/account_manager/models/account_spec.dart
@@ -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;
/// Profile and Account configurable fields
/// Some are publicly visible via the proto.Profile
/// Some are privately held as proto.Account configurations
-class AccountSpec {
- AccountSpec(
+@immutable
+class AccountSpec extends Equatable {
+ const AccountSpec(
{required this.name,
required this.pronouns,
required this.about,
@@ -19,37 +23,99 @@ class AccountSpec {
required this.autoAway,
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 {
late final String status;
switch (availability) {
case proto.Availability.AVAILABILITY_AWAY:
status = awayMessage;
- break;
case proto.Availability.AVAILABILITY_BUSY:
status = busyMessage;
- break;
case proto.Availability.AVAILABILITY_FREE:
status = freeMessage;
- break;
case proto.Availability.AVAILABILITY_UNSPECIFIED:
case proto.Availability.AVAILABILITY_OFFLINE:
status = '';
- break;
}
return status;
}
+ Future 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;
- String pronouns;
- String about;
- proto.Availability availability;
- bool invisible;
- String freeMessage;
- String awayMessage;
- String busyMessage;
- ImageProvider? avatar;
- bool autoAway;
- int autoAwayTimeout;
+ final String name;
+ final String pronouns;
+ final String about;
+ final proto.Availability availability;
+ final bool invisible;
+ final String freeMessage;
+ final String awayMessage;
+ final String busyMessage;
+ final proto.DataReference? avatar;
+ final bool autoAway;
+ final int autoAwayTimeout;
+
+ @override
+ List