From 70ec225a7d34352833f2d8e3fe1fee7894c81678 Mon Sep 17 00:00:00 2001 From: Nutcake Date: Sat, 6 Jan 2024 13:28:55 +0100 Subject: [PATCH] Initial adjustments to datamodels and hub interface --- lib/apis/contact_api.dart | 14 +- lib/clients/messaging_client.dart | 49 ++++--- .../users/{friend.dart => contact.dart} | 52 ++++---- lib/models/users/contact_status.dart | 14 ++ lib/models/users/friend_status.dart | 14 -- lib/models/users/user.dart | 10 ++ lib/widgets/friends/friend_list_tile.dart | 6 +- lib/widgets/friends/friends_list.dart | 4 +- lib/widgets/friends/user_list_tile.dart | 123 +++++++++--------- lib/widgets/friends/user_search.dart | 44 +++---- lib/widgets/messages/message_input_bar.dart | 6 +- lib/widgets/messages/messages_list.dart | 6 +- pubspec.yaml | 2 +- windows/installer-script.nsi | 16 +++ windows/runner/main.cpp | 4 +- windows/runner/resources/app_icon.ico | Bin 33772 -> 270398 bytes 16 files changed, 201 insertions(+), 163 deletions(-) rename lib/models/users/{friend.dart => contact.dart} (60%) create mode 100644 lib/models/users/contact_status.dart delete mode 100644 lib/models/users/friend_status.dart create mode 100644 windows/installer-script.nsi diff --git a/lib/apis/contact_api.dart b/lib/apis/contact_api.dart index 1cd6517..b4cc9fe 100644 --- a/lib/apis/contact_api.dart +++ b/lib/apis/contact_api.dart @@ -1,28 +1,28 @@ import 'dart:convert'; import 'package:recon/clients/api_client.dart'; -import 'package:recon/models/users/friend.dart'; -import 'package:recon/models/users/friend_status.dart'; +import 'package:recon/models/users/contact.dart'; +import 'package:recon/models/users/contact_status.dart'; import 'package:recon/models/users/user.dart'; import 'package:recon/models/users/user_profile.dart'; import 'package:recon/models/users/user_status.dart'; class ContactApi { - static Future> getFriendsList(ApiClient client, {DateTime? lastStatusUpdate}) async { + static Future> getFriendsList(ApiClient client, {DateTime? lastStatusUpdate}) async { final response = await client.get("/users/${client.userId}/contacts${lastStatusUpdate != null ? "?lastStatusUpdate=${lastStatusUpdate.toUtc().toIso8601String()}" : ""}"); client.checkResponse(response); final data = jsonDecode(response.body) as List; - return data.map((e) => Friend.fromMap(e)).toList(); + return data.map((e) => Contact.fromMap(e)).toList(); } static Future addUserAsFriend(ApiClient client, {required User user}) async { - final friend = Friend( + final friend = Contact( id: user.id, - username: user.username, + contactUsername: user.username, ownerId: client.userId, userStatus: UserStatus.empty(), userProfile: UserProfile.empty(), - contactStatus: FriendStatus.accepted, + friendStatus: ContactStatus.accepted, latestMessageTime: DateTime.now(), ); final body = jsonEncode(friend.toMap(shallow: true)); diff --git a/lib/clients/messaging_client.dart b/lib/clients/messaging_client.dart index fc54cdb..21fbd5f 100644 --- a/lib/clients/messaging_client.dart +++ b/lib/clients/messaging_client.dart @@ -12,8 +12,9 @@ import 'package:recon/hub_manager.dart'; import 'package:recon/models/hub_events.dart'; import 'package:recon/models/message.dart'; import 'package:recon/models/session.dart'; -import 'package:recon/models/users/friend.dart'; +import 'package:recon/models/users/contact.dart'; import 'package:recon/models/users/online_status.dart'; +import 'package:recon/models/users/user.dart'; import 'package:recon/models/users/user_status.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; @@ -21,7 +22,6 @@ import 'package:hive_flutter/hive_flutter.dart'; import 'package:logging/logging.dart'; import 'package:package_info_plus/package_info_plus.dart'; - class MessagingClient extends ChangeNotifier { static const Duration _autoRefreshDuration = Duration(seconds: 10); static const Duration _unreadSafeguardDuration = Duration(seconds: 120); @@ -30,7 +30,7 @@ class MessagingClient extends ChangeNotifier { static const String _lastUpdateKey = "__last-update-time"; final ApiClient _apiClient; - final List _sortedFriendsCache = []; // Keep a sorted copy so as to not have to sort during build() + final List _sortedFriendsCache = []; // Keep a sorted copy so as to not have to sort during build() final Map _messageCache = {}; final Map> _unreads = {}; final Logger _logger = Logger("Messaging"); @@ -39,7 +39,7 @@ class MessagingClient extends ChangeNotifier { final Map _sessionMap = {}; final Set _knownSessionKeys = {}; final SettingsClient _settingsClient; - Friend? selectedFriend; + Contact? selectedFriend; Timer? _statusHeartbeat; Timer? _autoRefresh; @@ -49,11 +49,13 @@ class MessagingClient extends ChangeNotifier { UserStatus get userStatus => _userStatus; - MessagingClient({required ApiClient apiClient, required NotificationClient notificationClient, required SettingsClient settingsClient}) + MessagingClient( + {required ApiClient apiClient, + required NotificationClient notificationClient, + required SettingsClient settingsClient}) : _apiClient = apiClient, _notificationClient = notificationClient, - _settingsClient = settingsClient - { + _settingsClient = settingsClient { debugPrint("mClient created: $hashCode"); Hive.openBox(_messageBoxKey).then((box) async { await box.delete(_lastUpdateKey); @@ -75,16 +77,16 @@ class MessagingClient extends ChangeNotifier { String? get initStatus => _initStatus; - List get cachedFriends => _sortedFriendsCache; + List get cachedFriends => _sortedFriendsCache; - List getUnreadsForFriend(Friend friend) => _unreads[friend.id] ?? []; + List getUnreadsForFriend(Contact friend) => _unreads[friend.id] ?? []; - bool friendHasUnreads(Friend friend) => _unreads.containsKey(friend.id); + bool friendHasUnreads(Contact friend) => _unreads.containsKey(friend.id); bool messageIsUnread(Message message) => _unreads[message.senderId]?.any((element) => element.id == message.id) ?? false; - Friend? getAsFriend(String userId) => Friend.fromMapOrNull(Hive.box(_messageBoxKey).get(userId)); + Contact? getAsFriend(String userId) => Contact.fromMapOrNull(Hive.box(_messageBoxKey).get(userId)); MessageCache? getUserMessageCache(String userId) => _messageCache[userId]; @@ -106,7 +108,7 @@ class MessagingClient extends ChangeNotifier { final friends = await ContactApi.getFriendsList(_apiClient, lastStatusUpdate: lastUpdateUtc); for (final friend in friends) { - await _updateContact(friend); + await _updateContactLocal(friend); } _initStatus = ""; @@ -153,7 +155,7 @@ class MessagingClient extends ChangeNotifier { final self = getAsFriend(_apiClient.userId); if (self != null) { - await _updateContact(self.copyWith(userStatus: _userStatus)); + await _updateContactLocal(self.copyWith(userStatus: _userStatus)); } notifyListeners(); } @@ -206,7 +208,7 @@ class MessagingClient extends ChangeNotifier { final friend = getAsFriend(userId); if (friend == null) return; final newStatus = await UserApi.getUserStatus(_apiClient, userId: userId); - await _updateContact(friend.copyWith(userStatus: newStatus)); + await _updateContactLocal(friend.copyWith(userStatus: newStatus)); notifyListeners(); } @@ -215,6 +217,10 @@ class MessagingClient extends ChangeNotifier { notifyListeners(); } + void addUserAsFriend(User user) { + _hubManager.send("UpdateContact", arguments: [user.asContactRequest(_apiClient.userId)]); + } + Future _refreshUnreads() async { try { final unreadMessages = await MessageApi.getUserMessages(_apiClient, unreadOnly: true); @@ -233,7 +239,7 @@ class MessagingClient extends ChangeNotifier { }); } - Future _updateContact(Friend friend) async { + Future _updateContactLocal(Contact friend) async { final box = Hive.box(_messageBoxKey); box.put(friend.id, friend.toMap()); final lastStatusUpdate = box.get(_lastUpdateKey); @@ -271,16 +277,17 @@ class MessagingClient extends ChangeNotifier { "InitializeStatus", responseHandler: (Map data) async { final rawContacts = data["contacts"] as List; - final contacts = rawContacts.map((e) => Friend.fromMap(e)).toList(); + final contacts = rawContacts.map((e) => Contact.fromMap(e)).toList(); for (final contact in contacts) { - await _updateContact(contact); + await _updateContactLocal(contact); } _initStatus = ""; notifyListeners(); await _refreshUnreads(); _unreadSafeguard = Timer.periodic(_unreadSafeguardDuration, (timer) => _refreshUnreads()); _hubManager.send("RequestStatus", arguments: [null, false]); - final lastOnline = OnlineStatus.values.elementAtOrNull(_settingsClient.currentSettings.lastOnlineStatus.valueOrDefault); + final lastOnline = + OnlineStatus.values.elementAtOrNull(_settingsClient.currentSettings.lastOnlineStatus.valueOrDefault); await setOnlineStatus(lastOnline ?? OnlineStatus.online); _statusHeartbeat = Timer.periodic(_statusHeartbeatDuration, (timer) { setOnlineStatus(_userStatus.onlineStatus); @@ -332,10 +339,12 @@ class MessagingClient extends ChangeNotifier { var status = UserStatus.fromMap(statusUpdate); final sessionMap = createSessionMap(status.hashSalt); status = status.copyWith( - decodedSessions: status.sessions.map((e) => sessionMap[e.sessionHash] ?? Session.none().copyWith(accessLevel: e.accessLevel)).toList()); + decodedSessions: status.sessions + .map((e) => sessionMap[e.sessionHash] ?? Session.none().copyWith(accessLevel: e.accessLevel)) + .toList()); final friend = getAsFriend(statusUpdate["userId"])?.copyWith(userStatus: status); if (friend != null) { - _updateContact(friend); + _updateContactLocal(friend); } for (var session in status.sessions) { if (session.broadcastKey != null && _knownSessionKeys.add(session.broadcastKey ?? "")) { diff --git a/lib/models/users/friend.dart b/lib/models/users/contact.dart similarity index 60% rename from lib/models/users/friend.dart rename to lib/models/users/contact.dart index 35a92e8..df020c4 100644 --- a/lib/models/users/friend.dart +++ b/lib/models/users/contact.dart @@ -1,70 +1,70 @@ import 'package:recon/auxiliary.dart'; import 'package:recon/models/users/user_profile.dart'; -import 'package:recon/models/users/friend_status.dart'; +import 'package:recon/models/users/contact_status.dart'; import 'package:recon/models/users/online_status.dart'; import 'package:recon/models/users/user_status.dart'; -class Friend implements Comparable { +class Contact implements Comparable { static const _emptyId = "-1"; static const _resoniteBotId = "U-Resonite"; final String id; - final String username; + final String contactUsername; final String ownerId; final UserStatus userStatus; final UserProfile userProfile; - final FriendStatus contactStatus; + final ContactStatus friendStatus; final DateTime latestMessageTime; - const Friend({required this.id, required this.username, required this.ownerId, required this.userStatus, required this.userProfile, - required this.contactStatus, required this.latestMessageTime, + const Contact({required this.id, required this.contactUsername, required this.ownerId, required this.userStatus, required this.userProfile, + required this.friendStatus, required this.latestMessageTime, }); bool get isHeadless => userStatus.outputDevice == "Headless"; - factory Friend.fromMap(Map map) { + factory Contact.fromMap(Map map) { var userStatus = map["userStatus"] == null ? UserStatus.empty() : UserStatus.fromMap(map["userStatus"]); - return Friend( + return Contact( id: map["id"], - username: map["contactUsername"], + contactUsername: map["contactUsername"], ownerId: map["ownerId"] ?? map["id"], // Neos bot status is always offline but should be displayed as online userStatus: map["id"] == _resoniteBotId ? userStatus.copyWith(onlineStatus: OnlineStatus.online) : userStatus, userProfile: UserProfile.fromMap(map["profile"] ?? {}), - contactStatus: FriendStatus.fromString(map["contactStatus"]), + friendStatus: ContactStatus.fromString(map["contactStatus"]), latestMessageTime: map["latestMessageTime"] == null ? DateTime.fromMillisecondsSinceEpoch(0) : DateTime.parse(map["latestMessageTime"]), ); } - static Friend? fromMapOrNull(Map? map) { + static Contact? fromMapOrNull(Map? map) { if (map == null) return null; - return Friend.fromMap(map); + return Contact.fromMap(map); } - factory Friend.empty() { - return Friend( + factory Contact.empty() { + return Contact( id: _emptyId, - username: "", + contactUsername: "", ownerId: "", userStatus: UserStatus.empty(), userProfile: UserProfile.empty(), - contactStatus: FriendStatus.none, + friendStatus: ContactStatus.none, latestMessageTime: DateTimeX.epoch ); } bool get isEmpty => id == _emptyId; - Friend copyWith({ - String? id, String? username, String? ownerId, UserStatus? userStatus, UserProfile? userProfile, - FriendStatus? contactStatus, DateTime? latestMessageTime}) { - return Friend( + Contact copyWith({ + String? id, String? contactUsername, String? ownerId, UserStatus? userStatus, UserProfile? userProfile, + ContactStatus? friendStatus, DateTime? latestMessageTime}) { + return Contact( id: id ?? this.id, - username: username ?? this.username, + contactUsername: contactUsername ?? this.contactUsername, ownerId: ownerId ?? this.ownerId, userStatus: userStatus ?? this.userStatus, userProfile: userProfile ?? this.userProfile, - contactStatus: contactStatus ?? this.contactStatus, + friendStatus: friendStatus ?? this.friendStatus, latestMessageTime: latestMessageTime ?? this.latestMessageTime, ); } @@ -72,17 +72,17 @@ class Friend implements Comparable { Map toMap({bool shallow=false}) { return { "id": id, - "contactUsername": username, + "contactUsername": contactUsername, "ownerId": ownerId, "userStatus": userStatus.toMap(shallow: shallow), "profile": userProfile.toMap(), - "contactStatus": contactStatus.name, + "contactStatus": friendStatus.name, "latestMessageTime": latestMessageTime.toIso8601String(), }; } @override - int compareTo(covariant Friend other) { - return username.compareTo(other.username); + int compareTo(covariant Contact other) { + return contactUsername.compareTo(other.contactUsername); } } diff --git a/lib/models/users/contact_status.dart b/lib/models/users/contact_status.dart new file mode 100644 index 0000000..57bfe28 --- /dev/null +++ b/lib/models/users/contact_status.dart @@ -0,0 +1,14 @@ +enum ContactStatus { + none, + searchResult, + requested, + ignored, + blocked, + accepted; + + factory ContactStatus.fromString(String text) { + return ContactStatus.values.firstWhere((element) => element.name.toLowerCase() == text.toLowerCase(), + orElse: () => ContactStatus.none, + ); + } +} diff --git a/lib/models/users/friend_status.dart b/lib/models/users/friend_status.dart deleted file mode 100644 index 1177b2d..0000000 --- a/lib/models/users/friend_status.dart +++ /dev/null @@ -1,14 +0,0 @@ -enum FriendStatus { - none, - searchResult, - requested, - ignored, - blocked, - accepted; - - factory FriendStatus.fromString(String text) { - return FriendStatus.values.firstWhere((element) => element.name.toLowerCase() == text.toLowerCase(), - orElse: () => FriendStatus.none, - ); - } -} diff --git a/lib/models/users/user.dart b/lib/models/users/user.dart index 91ca1d1..b1e0b7b 100644 --- a/lib/models/users/user.dart +++ b/lib/models/users/user.dart @@ -1,3 +1,4 @@ +import 'package:recon/models/users/contact_status.dart'; import 'package:recon/models/users/user_profile.dart'; class User { @@ -31,4 +32,13 @@ class User { "profile": userProfile?.toMap(), }; } + + Map asContactRequest(String ownerId) { + return { + "ownerId": ownerId, + "id": id, + "contactUsername": username, + "contactStatus": ContactStatus.accepted.name, + }; + } } \ No newline at end of file diff --git a/lib/widgets/friends/friend_list_tile.dart b/lib/widgets/friends/friend_list_tile.dart index 87a748d..a2a7d34 100644 --- a/lib/widgets/friends/friend_list_tile.dart +++ b/lib/widgets/friends/friend_list_tile.dart @@ -4,7 +4,7 @@ import 'package:provider/provider.dart'; import 'package:recon/auxiliary.dart'; import 'package:recon/clients/messaging_client.dart'; import 'package:recon/models/message.dart'; -import 'package:recon/models/users/friend.dart'; +import 'package:recon/models/users/contact.dart'; import 'package:recon/models/users/online_status.dart'; import 'package:recon/widgets/formatted_text.dart'; import 'package:recon/widgets/friends/friend_online_status_indicator.dart'; @@ -14,7 +14,7 @@ import 'package:recon/widgets/messages/messages_list.dart'; class FriendListTile extends StatelessWidget { const FriendListTile({required this.friend, required this.unreads, this.onTap, super.key}); - final Friend friend; + final Contact friend; final int unreads; final Function? onTap; @@ -38,7 +38,7 @@ class FriendListTile extends StatelessWidget { : null, title: Row( children: [ - Text(friend.username), + Text(friend.contactUsername), if (friend.isHeadless) Padding( padding: const EdgeInsets.only(left: 8), diff --git a/lib/widgets/friends/friends_list.dart b/lib/widgets/friends/friends_list.dart index 3fd2430..c162c36 100644 --- a/lib/widgets/friends/friends_list.dart +++ b/lib/widgets/friends/friends_list.dart @@ -45,9 +45,9 @@ class _FriendsListState extends State with AutomaticKeepAliveClient var friends = List.from(mClient.cachedFriends); // Explicit copy. if (_searchFilter.isNotEmpty) { friends = friends - .where((element) => element.username.toLowerCase().contains(_searchFilter.toLowerCase())) + .where((element) => element.contactUsername.toLowerCase().contains(_searchFilter.toLowerCase())) .toList(); - friends.sort((a, b) => a.username.length.compareTo(b.username.length)); + friends.sort((a, b) => a.contactUsername.length.compareTo(b.contactUsername.length)); } return ListView.builder( physics: const BouncingScrollPhysics(decelerationRate: ScrollDecelerationRate.fast), diff --git a/lib/widgets/friends/user_list_tile.dart b/lib/widgets/friends/user_list_tile.dart index 2cd3999..0d6b01d 100644 --- a/lib/widgets/friends/user_list_tile.dart +++ b/lib/widgets/friends/user_list_tile.dart @@ -1,13 +1,20 @@ +import 'package:provider/provider.dart'; import 'package:recon/apis/contact_api.dart'; import 'package:recon/auxiliary.dart'; import 'package:recon/client_holder.dart'; +import 'package:recon/clients/messaging_client.dart'; import 'package:recon/models/users/user.dart'; import 'package:recon/widgets/generic_avatar.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; class UserListTile extends StatefulWidget { - const UserListTile({required this.user, required this.isFriend, required this.onChanged, super.key}); + const UserListTile({ + required this.user, + required this.isFriend, + required this.onChanged, + super.key, + }); final User user; final bool isFriend; @@ -24,24 +31,20 @@ class _UserListTileState extends State { @override Widget build(BuildContext context) { - final colorScheme = Theme - .of(context) - .colorScheme; - final style = _localAdded ? IconButton.styleFrom( - foregroundColor: colorScheme.onBackground, - side: BorderSide( - color: colorScheme.error, - width: 2 - ), - ) : IconButton.styleFrom( - foregroundColor: colorScheme.onBackground, - side: BorderSide( - color: colorScheme.primary, - width: 2 - ), - ); + final colorScheme = Theme.of(context).colorScheme; + final style = _localAdded + ? IconButton.styleFrom( + foregroundColor: colorScheme.onBackground, + side: BorderSide(color: colorScheme.error, width: 2), + ) + : IconButton.styleFrom( + foregroundColor: colorScheme.onBackground, + side: BorderSide(color: colorScheme.primary, width: 2), + ); return ListTile( - leading: GenericAvatar(imageUri: Aux.resdbToHttp(widget.user.userProfile?.iconUrl),), + leading: GenericAvatar( + imageUri: Aux.resdbToHttp(widget.user.userProfile?.iconUrl), + ), title: Text(widget.user.username), subtitle: Text(_regDateFormat.format(widget.user.registrationDate)), trailing: IconButton( @@ -49,48 +52,48 @@ class _UserListTileState extends State { iconSize: 20, icon: _localAdded ? const Icon(Icons.person_remove) : const Icon(Icons.person_add), style: style, - onPressed: _loading ? null : () async { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("Sorry, this feature is not yet available"))); - return; - setState(() { - _loading = true; - }); - try { - if (_localAdded) { - await ContactApi.removeUserAsFriend(ClientHolder - .of(context) - .apiClient, user: widget.user); - } else { - await ContactApi.addUserAsFriend(ClientHolder - .of(context) - .apiClient, user: widget.user); - } - setState(() { - _loading = false; - _localAdded = !_localAdded; - }); - widget.onChanged?.call(); - } catch (e, s) { - FlutterError.reportError(FlutterErrorDetails(exception: e, stack: s)); - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - duration: const Duration(seconds: 5), - content: Text( - "Something went wrong: $e", - softWrap: true, - maxLines: null, - ), - ), - ); - } - setState(() { - _loading = false; - }); - return; - } - }, + onPressed: _loading + ? null + : () async { + final mClient = Provider.of(context, listen: false); + setState(() { + _loading = true; + }); + try { + if (_localAdded) { + await ContactApi.removeUserAsFriend( + ClientHolder.of(context).apiClient, + user: widget.user, + ); + } else { + mClient.addUserAsFriend(widget.user); + } + setState(() { + _loading = false; + _localAdded = !_localAdded; + }); + widget.onChanged?.call(); + } catch (e, s) { + FlutterError.reportError(FlutterErrorDetails(exception: e, stack: s)); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + duration: const Duration(seconds: 5), + content: Text( + "Something went wrong: $e", + softWrap: true, + maxLines: null, + ), + ), + ); + } + setState(() { + _loading = false; + }); + return; + } + }, ), ); } -} \ No newline at end of file +} diff --git a/lib/widgets/friends/user_search.dart b/lib/widgets/friends/user_search.dart index fa36fe2..f694272 100644 --- a/lib/widgets/friends/user_search.dart +++ b/lib/widgets/friends/user_search.dart @@ -29,24 +29,19 @@ class _UserSearchState extends State { late Future?>? _usersFuture = _emptySearch; Future> get _emptySearch => - Future(() => - throw const SearchError( - message: "Start typing to search for users", icon: Icons.search) - ); + Future(() => throw const SearchError(message: "Start typing to search for users", icon: Icons.search)); void _querySearch(BuildContext context, String needle) { if (needle.isEmpty) { _usersFuture = _emptySearch; return; } - _usersFuture = UserApi.searchUsers(ClientHolder - .of(context) - .apiClient, needle: needle).then((value) { + _usersFuture = UserApi.searchUsers(ClientHolder.of(context).apiClient, needle: needle).then((value) { final res = value.toList(); - if (res.isEmpty) throw SearchError(message: "No user found with username '$needle'", icon: Icons.search_off); - res.sort( - (a, b) => a.username.length.compareTo(b.username.length) - ); + if (res.isEmpty) { + throw SearchError(message: "No user found with username '$needle'", icon: Icons.search_off); + } + res.sort((a, b) => a.username.length.compareTo(b.username.length)); return res; }); } @@ -72,9 +67,13 @@ class _UserSearchState extends State { itemCount: users.length, itemBuilder: (context, index) { final user = users[index]; - return UserListTile(user: user, onChanged: () { - mClient.refreshFriendsList(); - }, isFriend: mClient.getAsFriend(user.id) != null,); + return UserListTile( + user: user, + onChanged: () { + mClient.refreshFriendsList(); + }, + isFriend: mClient.getAsFriend(user.id) != null, + ); }, ); } else if (snapshot.hasError) { @@ -85,9 +84,13 @@ class _UserSearchState extends State { iconOverride: err.icon, ); } else { - FlutterError.reportError( - FlutterErrorDetails(exception: snapshot.error!, stack: snapshot.stackTrace)); - return DefaultErrorWidget(title: "${snapshot.error}",); + FlutterError.reportError(FlutterErrorDetails( + exception: snapshot.error!, + stack: snapshot.stackTrace, + )); + return DefaultErrorWidget( + title: "${snapshot.error}", + ); } } else { return const Column( @@ -106,10 +109,7 @@ class _UserSearchState extends State { isDense: true, hintText: "Search for users...", contentPadding: const EdgeInsets.all(16), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(24) - ) - ), + border: OutlineInputBorder(borderRadius: BorderRadius.circular(24))), autocorrect: false, controller: _searchInputController, onChanged: (String value) { @@ -136,4 +136,4 @@ class _UserSearchState extends State { ), ); } -} \ No newline at end of file +} diff --git a/lib/widgets/messages/message_input_bar.dart b/lib/widgets/messages/message_input_bar.dart index ebe1a35..41f55eb 100644 --- a/lib/widgets/messages/message_input_bar.dart +++ b/lib/widgets/messages/message_input_bar.dart @@ -13,7 +13,7 @@ import 'package:recon/client_holder.dart'; import 'package:recon/clients/api_client.dart'; import 'package:recon/clients/messaging_client.dart'; import 'package:recon/models/message.dart'; -import 'package:recon/models/users/friend.dart'; +import 'package:recon/models/users/contact.dart'; import 'package:recon/widgets/messages/message_attachment_list.dart'; import 'package:record/record.dart'; @@ -21,7 +21,7 @@ class MessageInputBar extends StatefulWidget { const MessageInputBar({this.disabled = false, required this.recipient, this.onMessageSent, super.key}); final bool disabled; - final Friend recipient; + final Contact recipient; final Function()? onMessageSent; @override @@ -403,7 +403,7 @@ class _MessageInputBarState extends State { style: Theme.of(context).textTheme.bodyLarge, decoration: InputDecoration( isDense: true, - hintText: _isRecording ? "" : "Message ${widget.recipient.username}...", + hintText: _isRecording ? "" : "Message ${widget.recipient.contactUsername}...", hintMaxLines: 1, contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), fillColor: Colors.black26, diff --git a/lib/widgets/messages/messages_list.dart b/lib/widgets/messages/messages_list.dart index a3311c8..f37d6ea 100644 --- a/lib/widgets/messages/messages_list.dart +++ b/lib/widgets/messages/messages_list.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:recon/clients/audio_cache_client.dart'; import 'package:recon/clients/messaging_client.dart'; -import 'package:recon/models/users/friend.dart'; +import 'package:recon/models/users/contact.dart'; import 'package:recon/widgets/default_error_widget.dart'; import 'package:recon/widgets/friends/friend_online_status_indicator.dart'; import 'package:recon/widgets/messages/message_input_bar.dart'; @@ -54,7 +54,7 @@ class _MessagesListState extends State with SingleTickerProviderSt Widget build(BuildContext context) { final appBarColor = Theme.of(context).colorScheme.surface; return Consumer(builder: (context, mClient, _) { - final friend = mClient.selectedFriend ?? Friend.empty(); + final friend = mClient.selectedFriend ?? Contact.empty(); final cache = mClient.getUserMessageCache(friend.id); final sessions = friend.userStatus.decodedSessions.where((element) => element.isVisible).toList(); return Scaffold( @@ -66,7 +66,7 @@ class _MessagesListState extends State with SingleTickerProviderSt const SizedBox( width: 8, ), - Text(friend.username), + Text(friend.contactUsername), if (friend.isHeadless) Padding( padding: const EdgeInsets.only(left: 12), diff --git a/pubspec.yaml b/pubspec.yaml index 88b7998..82ccde5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 0.11.0-beta+1 +version: 0.11.1-beta+1 environment: sdk: ">=3.0.1" diff --git a/windows/installer-script.nsi b/windows/installer-script.nsi new file mode 100644 index 0000000..9cafcbc --- /dev/null +++ b/windows/installer-script.nsi @@ -0,0 +1,16 @@ +OutFile "ReCon-Installer.exe" + +# define the directory to install to, the desktop in this case as specified +# by the predefined $DESKTOP variable +InstallDir $DESKTOP + +# default section +Section + +# define the output path for this file +SetOutPath $INSTDIR + +# define what to install and place it in the output path +File test.txt + +SectionEnd \ No newline at end of file diff --git a/windows/runner/main.cpp b/windows/runner/main.cpp index d97c34f..b710c33 100644 --- a/windows/runner/main.cpp +++ b/windows/runner/main.cpp @@ -26,8 +26,8 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, FlutterWindow window(project); Win32Window::Point origin(10, 10); - Win32Window::Size size(1280, 720); - if (!window.Create(L"recon", origin, size)) { + Win32Window::Size size(480, 900); + if (!window.Create(L"ReCon", origin, size)) { return EXIT_FAILURE; } window.SetQuitOnClose(true); diff --git a/windows/runner/resources/app_icon.ico b/windows/runner/resources/app_icon.ico index c04e20caf6370ebb9253ad831cc31de4a9c965f6..a65eba98cef8566420e980cbad5359b04ecba60c 100644 GIT binary patch literal 270398 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 literal 33772 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