Compare commits
2 commits
main
...
contact-ma
Author | SHA1 | Date | |
---|---|---|---|
|
0c3b735a0d | ||
|
70ec225a7d |
23 changed files with 585 additions and 408 deletions
|
@ -1,37 +1,13 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:recon/clients/api_client.dart';
|
import 'package:recon/clients/api_client.dart';
|
||||||
import 'package:recon/models/users/friend.dart';
|
import 'package:recon/models/users/contact.dart';
|
||||||
import 'package:recon/models/users/friend_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 {
|
class ContactApi {
|
||||||
static Future<List<Friend>> getFriendsList(ApiClient client, {DateTime? lastStatusUpdate}) async {
|
static Future<List<Contact>> getFriendsList(ApiClient client, {DateTime? lastStatusUpdate}) async {
|
||||||
final response = await client.get("/users/${client.userId}/contacts${lastStatusUpdate != null ? "?lastStatusUpdate=${lastStatusUpdate.toUtc().toIso8601String()}" : ""}");
|
final response = await client.get("/users/${client.userId}/contacts${lastStatusUpdate != null ? "?lastStatusUpdate=${lastStatusUpdate.toUtc().toIso8601String()}" : ""}");
|
||||||
client.checkResponse(response);
|
client.checkResponse(response);
|
||||||
final data = jsonDecode(response.body) as List;
|
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<void> addUserAsFriend(ApiClient client, {required User user}) async {
|
|
||||||
final friend = Friend(
|
|
||||||
id: user.id,
|
|
||||||
username: user.username,
|
|
||||||
ownerId: client.userId,
|
|
||||||
userStatus: UserStatus.empty(),
|
|
||||||
userProfile: UserProfile.empty(),
|
|
||||||
contactStatus: FriendStatus.accepted,
|
|
||||||
latestMessageTime: DateTime.now(),
|
|
||||||
);
|
|
||||||
final body = jsonEncode(friend.toMap(shallow: true));
|
|
||||||
final response = await client.put("/users/${client.userId}/contacts/${user.id}", body: body);
|
|
||||||
client.checkResponse(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<void> removeUserAsFriend(ApiClient client, {required User user}) async {
|
|
||||||
final response = await client.delete("/users/${client.userId}/friends/${user.id}");
|
|
||||||
client.checkResponse(response);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -9,11 +9,14 @@ import 'package:recon/clients/notification_client.dart';
|
||||||
import 'package:recon/clients/settings_client.dart';
|
import 'package:recon/clients/settings_client.dart';
|
||||||
import 'package:recon/crypto_helper.dart';
|
import 'package:recon/crypto_helper.dart';
|
||||||
import 'package:recon/hub_manager.dart';
|
import 'package:recon/hub_manager.dart';
|
||||||
|
import 'package:recon/models/broadcast_group.dart';
|
||||||
import 'package:recon/models/hub_events.dart';
|
import 'package:recon/models/hub_events.dart';
|
||||||
import 'package:recon/models/message.dart';
|
import 'package:recon/models/message.dart';
|
||||||
import 'package:recon/models/session.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/contact_status.dart';
|
||||||
import 'package:recon/models/users/online_status.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:recon/models/users/user_status.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
@ -21,7 +24,6 @@ import 'package:hive_flutter/hive_flutter.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
|
|
||||||
|
|
||||||
class MessagingClient extends ChangeNotifier {
|
class MessagingClient extends ChangeNotifier {
|
||||||
static const Duration _autoRefreshDuration = Duration(seconds: 10);
|
static const Duration _autoRefreshDuration = Duration(seconds: 10);
|
||||||
static const Duration _unreadSafeguardDuration = Duration(seconds: 120);
|
static const Duration _unreadSafeguardDuration = Duration(seconds: 120);
|
||||||
|
@ -30,7 +32,7 @@ class MessagingClient extends ChangeNotifier {
|
||||||
static const String _lastUpdateKey = "__last-update-time";
|
static const String _lastUpdateKey = "__last-update-time";
|
||||||
|
|
||||||
final ApiClient _apiClient;
|
final ApiClient _apiClient;
|
||||||
final List<Friend> _sortedFriendsCache = []; // Keep a sorted copy so as to not have to sort during build()
|
final List<Contact> _sortedFriendsCache = []; // Keep a sorted copy so as to not have to sort during build()
|
||||||
final Map<String, MessageCache> _messageCache = {};
|
final Map<String, MessageCache> _messageCache = {};
|
||||||
final Map<String, List<Message>> _unreads = {};
|
final Map<String, List<Message>> _unreads = {};
|
||||||
final Logger _logger = Logger("Messaging");
|
final Logger _logger = Logger("Messaging");
|
||||||
|
@ -39,7 +41,7 @@ class MessagingClient extends ChangeNotifier {
|
||||||
final Map<String, Session> _sessionMap = {};
|
final Map<String, Session> _sessionMap = {};
|
||||||
final Set<String> _knownSessionKeys = {};
|
final Set<String> _knownSessionKeys = {};
|
||||||
final SettingsClient _settingsClient;
|
final SettingsClient _settingsClient;
|
||||||
Friend? selectedFriend;
|
Contact? selectedFriend;
|
||||||
|
|
||||||
Timer? _statusHeartbeat;
|
Timer? _statusHeartbeat;
|
||||||
Timer? _autoRefresh;
|
Timer? _autoRefresh;
|
||||||
|
@ -49,11 +51,13 @@ class MessagingClient extends ChangeNotifier {
|
||||||
|
|
||||||
UserStatus get userStatus => _userStatus;
|
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,
|
: _apiClient = apiClient,
|
||||||
_notificationClient = notificationClient,
|
_notificationClient = notificationClient,
|
||||||
_settingsClient = settingsClient
|
_settingsClient = settingsClient {
|
||||||
{
|
|
||||||
debugPrint("mClient created: $hashCode");
|
debugPrint("mClient created: $hashCode");
|
||||||
Hive.openBox(_messageBoxKey).then((box) async {
|
Hive.openBox(_messageBoxKey).then((box) async {
|
||||||
await box.delete(_lastUpdateKey);
|
await box.delete(_lastUpdateKey);
|
||||||
|
@ -75,16 +79,16 @@ class MessagingClient extends ChangeNotifier {
|
||||||
|
|
||||||
String? get initStatus => _initStatus;
|
String? get initStatus => _initStatus;
|
||||||
|
|
||||||
List<Friend> get cachedFriends => _sortedFriendsCache;
|
List<Contact> get cachedFriends => _sortedFriendsCache;
|
||||||
|
|
||||||
List<Message> getUnreadsForFriend(Friend friend) => _unreads[friend.id] ?? [];
|
List<Message> getUnreadsForContact(Contact contact) => _unreads[contact.id] ?? [];
|
||||||
|
|
||||||
bool friendHasUnreads(Friend friend) => _unreads.containsKey(friend.id);
|
bool contactHasUnreads(Contact contact) => _unreads.containsKey(contact.id);
|
||||||
|
|
||||||
bool messageIsUnread(Message message) =>
|
bool messageIsUnread(Message message) =>
|
||||||
_unreads[message.senderId]?.any((element) => element.id == message.id) ?? false;
|
_unreads[message.senderId]?.any((element) => element.id == message.id) ?? false;
|
||||||
|
|
||||||
Friend? getAsFriend(String userId) => Friend.fromMapOrNull(Hive.box(_messageBoxKey).get(userId));
|
Contact? getAsContact(String userId) => Contact.fromMapOrNull(Hive.box(_messageBoxKey).get(userId));
|
||||||
|
|
||||||
MessageCache? getUserMessageCache(String userId) => _messageCache[userId];
|
MessageCache? getUserMessageCache(String userId) => _messageCache[userId];
|
||||||
|
|
||||||
|
@ -106,7 +110,7 @@ class MessagingClient extends ChangeNotifier {
|
||||||
|
|
||||||
final friends = await ContactApi.getFriendsList(_apiClient, lastStatusUpdate: lastUpdateUtc);
|
final friends = await ContactApi.getFriendsList(_apiClient, lastStatusUpdate: lastUpdateUtc);
|
||||||
for (final friend in friends) {
|
for (final friend in friends) {
|
||||||
await _updateContact(friend);
|
await _updateContactLocal(friend);
|
||||||
}
|
}
|
||||||
|
|
||||||
_initStatus = "";
|
_initStatus = "";
|
||||||
|
@ -128,7 +132,7 @@ class MessagingClient extends ChangeNotifier {
|
||||||
clearUnreadsForUser(batch.senderId);
|
clearUnreadsForUser(batch.senderId);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setOnlineStatus(OnlineStatus status) async {
|
Future<void> setOnlineStatus(OnlineStatus status, {String? target}) async {
|
||||||
final pkginfo = await PackageInfo.fromPlatform();
|
final pkginfo = await PackageInfo.fromPlatform();
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
_userStatus = _userStatus.copyWith(
|
_userStatus = _userStatus.copyWith(
|
||||||
|
@ -145,15 +149,16 @@ class MessagingClient extends ChangeNotifier {
|
||||||
arguments: [
|
arguments: [
|
||||||
_userStatus.toMap(),
|
_userStatus.toMap(),
|
||||||
{
|
{
|
||||||
"group": 1,
|
"group": target == null ? BroadcastGroup.allContacts.index : BroadcastGroup.specificContacts.index,
|
||||||
"targetIds": null,
|
"targetIds": target,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
if (target == null) {
|
||||||
final self = getAsFriend(_apiClient.userId);
|
final self = getAsContact(_apiClient.userId);
|
||||||
if (self != null) {
|
if (self != null) {
|
||||||
await _updateContact(self.copyWith(userStatus: _userStatus));
|
await _updateContactLocal(self.copyWith(userStatus: _userStatus));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
@ -168,7 +173,10 @@ class MessagingClient extends ChangeNotifier {
|
||||||
}
|
}
|
||||||
messages.sort();
|
messages.sort();
|
||||||
_sortFriendsCache();
|
_sortFriendsCache();
|
||||||
_notificationClient.showUnreadMessagesNotification(messages.reversed);
|
_notificationClient.showUnreadMessagesNotification(
|
||||||
|
messages.reversed,
|
||||||
|
getAsContact,
|
||||||
|
);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,10 +211,10 @@ class MessagingClient extends ChangeNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateFriendStatus(String userId) async {
|
Future<void> updateFriendStatus(String userId) async {
|
||||||
final friend = getAsFriend(userId);
|
final friend = getAsContact(userId);
|
||||||
if (friend == null) return;
|
if (friend == null) return;
|
||||||
final newStatus = await UserApi.getUserStatus(_apiClient, userId: userId);
|
final newStatus = await UserApi.getUserStatus(_apiClient, userId: userId);
|
||||||
await _updateContact(friend.copyWith(userStatus: newStatus));
|
await _updateContactLocal(friend.copyWith(userStatus: newStatus));
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,6 +223,30 @@ class MessagingClient extends ChangeNotifier {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void addUserAsFriend(User user) {
|
||||||
|
_hubManager.send(
|
||||||
|
"UpdateContact",
|
||||||
|
arguments: [
|
||||||
|
user.asContactRequest(
|
||||||
|
ownerId: _apiClient.userId,
|
||||||
|
contactStatus: ContactStatus.accepted,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeUserAsFriend(User user) {
|
||||||
|
_hubManager.send(
|
||||||
|
"UpdateContact",
|
||||||
|
arguments: [
|
||||||
|
user.asContactRequest(
|
||||||
|
ownerId: _apiClient.userId,
|
||||||
|
contactStatus: ContactStatus.ignored,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _refreshUnreads() async {
|
Future<void> _refreshUnreads() async {
|
||||||
try {
|
try {
|
||||||
final unreadMessages = await MessageApi.getUserMessages(_apiClient, unreadOnly: true);
|
final unreadMessages = await MessageApi.getUserMessages(_apiClient, unreadOnly: true);
|
||||||
|
@ -224,8 +256,12 @@ class MessagingClient extends ChangeNotifier {
|
||||||
|
|
||||||
void _sortFriendsCache() {
|
void _sortFriendsCache() {
|
||||||
_sortedFriendsCache.sort((a, b) {
|
_sortedFriendsCache.sort((a, b) {
|
||||||
var aVal = friendHasUnreads(a) ? -3 : 0;
|
if (a.isContactRequest != b.isContactRequest) {
|
||||||
var bVal = friendHasUnreads(b) ? -3 : 0;
|
return a.isContactRequest ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var aVal = contactHasUnreads(a) ? -3 : 0;
|
||||||
|
var bVal = contactHasUnreads(b) ? -3 : 0;
|
||||||
|
|
||||||
aVal -= a.latestMessageTime.compareTo(b.latestMessageTime);
|
aVal -= a.latestMessageTime.compareTo(b.latestMessageTime);
|
||||||
aVal += a.userStatus.onlineStatus.compareTo(b.userStatus.onlineStatus) * 2;
|
aVal += a.userStatus.onlineStatus.compareTo(b.userStatus.onlineStatus) * 2;
|
||||||
|
@ -233,7 +269,7 @@ class MessagingClient extends ChangeNotifier {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _updateContact(Friend friend) async {
|
Future<void> _updateContactLocal(Contact friend) async {
|
||||||
final box = Hive.box(_messageBoxKey);
|
final box = Hive.box(_messageBoxKey);
|
||||||
box.put(friend.id, friend.toMap());
|
box.put(friend.id, friend.toMap());
|
||||||
final lastStatusUpdate = box.get(_lastUpdateKey);
|
final lastStatusUpdate = box.get(_lastUpdateKey);
|
||||||
|
@ -265,22 +301,24 @@ class MessagingClient extends ChangeNotifier {
|
||||||
_hubManager.setHandler(EventTarget.receiveStatusUpdate, _onReceiveStatusUpdate);
|
_hubManager.setHandler(EventTarget.receiveStatusUpdate, _onReceiveStatusUpdate);
|
||||||
_hubManager.setHandler(EventTarget.receiveSessionUpdate, _onReceiveSessionUpdate);
|
_hubManager.setHandler(EventTarget.receiveSessionUpdate, _onReceiveSessionUpdate);
|
||||||
_hubManager.setHandler(EventTarget.removeSession, _onRemoveSession);
|
_hubManager.setHandler(EventTarget.removeSession, _onRemoveSession);
|
||||||
|
_hubManager.setHandler(EventTarget.contactAddedOrUpdated, _onContactAddedOrUpdated);
|
||||||
|
|
||||||
await _hubManager.start();
|
await _hubManager.start();
|
||||||
_hubManager.send(
|
_hubManager.send(
|
||||||
"InitializeStatus",
|
"InitializeStatus",
|
||||||
responseHandler: (Map data) async {
|
responseHandler: (Map data) async {
|
||||||
final rawContacts = data["contacts"] as List;
|
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) {
|
for (final contact in contacts) {
|
||||||
await _updateContact(contact);
|
await _updateContactLocal(contact);
|
||||||
}
|
}
|
||||||
_initStatus = "";
|
_initStatus = "";
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
await _refreshUnreads();
|
await _refreshUnreads();
|
||||||
_unreadSafeguard = Timer.periodic(_unreadSafeguardDuration, (timer) => _refreshUnreads());
|
_unreadSafeguard = Timer.periodic(_unreadSafeguardDuration, (timer) => _refreshUnreads());
|
||||||
_hubManager.send("RequestStatus", arguments: [null, false]);
|
_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);
|
await setOnlineStatus(lastOnline ?? OnlineStatus.online);
|
||||||
_statusHeartbeat = Timer.periodic(_statusHeartbeatDuration, (timer) {
|
_statusHeartbeat = Timer.periodic(_statusHeartbeatDuration, (timer) {
|
||||||
setOnlineStatus(_userStatus.onlineStatus);
|
setOnlineStatus(_userStatus.onlineStatus);
|
||||||
|
@ -293,6 +331,51 @@ class MessagingClient extends ChangeNotifier {
|
||||||
return _sessionMap.map((key, value) => MapEntry(CryptoHelper.idHash(value.id + salt), value));
|
return _sessionMap.map((key, value) => MapEntry(CryptoHelper.idHash(value.id + salt), value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onContactAddedOrUpdated(List args) {
|
||||||
|
bool shouldUpdateRequestCount = false;
|
||||||
|
bool shouldRegisterNewContact = false;
|
||||||
|
for (var request in args) {
|
||||||
|
final newContact = Contact.fromMap(request);
|
||||||
|
Contact? existingContact = getAsContact(newContact.id);
|
||||||
|
if (existingContact != null) {
|
||||||
|
if (!existingContact.isAccepted && newContact.isAccepted) {
|
||||||
|
shouldRegisterNewContact = true;
|
||||||
|
}
|
||||||
|
if (newContact.isContactRequest != existingContact.isContactRequest) {
|
||||||
|
shouldUpdateRequestCount = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (newContact.isAccepted) {
|
||||||
|
shouldRegisterNewContact = true;
|
||||||
|
}
|
||||||
|
if (newContact.isContactRequest) {
|
||||||
|
shouldUpdateRequestCount = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_updateContactLocal(newContact);
|
||||||
|
if (shouldRegisterNewContact) {
|
||||||
|
final isInvisible = userStatus.onlineStatus == OnlineStatus.invisible;
|
||||||
|
_hubManager.send("ListenOnContact", arguments: [newContact.id]);
|
||||||
|
_hubManager.send("RequestStatus", arguments: [newContact.id, isInvisible]);
|
||||||
|
if (!isInvisible) {
|
||||||
|
_hubManager.send(
|
||||||
|
"BroadcastStatus",
|
||||||
|
arguments: [
|
||||||
|
userStatus.toMap(shallow: true),
|
||||||
|
{
|
||||||
|
"group": BroadcastGroup.specificContacts.index,
|
||||||
|
"targetIds": newContact.id,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (shouldUpdateRequestCount) {
|
||||||
|
_notificationClient.showContactRequestNotification(newContact);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _onMessageSent(List args) {
|
void _onMessageSent(List args) {
|
||||||
final msg = args[0];
|
final msg = args[0];
|
||||||
final message = Message.fromMap(msg, withState: MessageState.sent);
|
final message = Message.fromMap(msg, withState: MessageState.sent);
|
||||||
|
@ -332,10 +415,12 @@ class MessagingClient extends ChangeNotifier {
|
||||||
var status = UserStatus.fromMap(statusUpdate);
|
var status = UserStatus.fromMap(statusUpdate);
|
||||||
final sessionMap = createSessionMap(status.hashSalt);
|
final sessionMap = createSessionMap(status.hashSalt);
|
||||||
status = status.copyWith(
|
status = status.copyWith(
|
||||||
decodedSessions: status.sessions.map((e) => sessionMap[e.sessionHash] ?? Session.none().copyWith(accessLevel: e.accessLevel)).toList());
|
decodedSessions: status.sessions
|
||||||
final friend = getAsFriend(statusUpdate["userId"])?.copyWith(userStatus: status);
|
.map((e) => sessionMap[e.sessionHash] ?? Session.none().copyWith(accessLevel: e.accessLevel))
|
||||||
|
.toList());
|
||||||
|
final friend = getAsContact(statusUpdate["userId"])?.copyWith(userStatus: status);
|
||||||
if (friend != null) {
|
if (friend != null) {
|
||||||
_updateContact(friend);
|
_updateContactLocal(friend);
|
||||||
}
|
}
|
||||||
for (var session in status.sessions) {
|
for (var session in status.sessions) {
|
||||||
if (session.broadcastKey != null && _knownSessionKeys.add(session.broadcastKey ?? "")) {
|
if (session.broadcastKey != null && _knownSessionKeys.add(session.broadcastKey ?? "")) {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart' as
|
||||||
import 'package:recon/auxiliary.dart';
|
import 'package:recon/auxiliary.dart';
|
||||||
import 'package:recon/models/message.dart';
|
import 'package:recon/models/message.dart';
|
||||||
import 'package:recon/models/session.dart';
|
import 'package:recon/models/session.dart';
|
||||||
|
import 'package:recon/models/users/contact.dart';
|
||||||
|
|
||||||
class NotificationChannel {
|
class NotificationChannel {
|
||||||
final String id;
|
final String id;
|
||||||
|
@ -29,13 +30,13 @@ class NotificationClient {
|
||||||
linux: fln.LinuxInitializationSettings(defaultActionName: "Open ReCon"),
|
linux: fln.LinuxInitializationSettings(defaultActionName: "Open ReCon"),
|
||||||
));
|
));
|
||||||
|
|
||||||
Future<void> showUnreadMessagesNotification(Iterable<Message> messages) async {
|
Future<void> showUnreadMessagesNotification(Iterable<Message> messages, Contact? Function(String userId) contactGetter) async {
|
||||||
if (messages.isEmpty) return;
|
if (messages.isEmpty) return;
|
||||||
|
|
||||||
final bySender = groupBy(messages, (p0) => p0.senderId);
|
final bySender = groupBy(messages, (p0) => p0.senderId);
|
||||||
|
|
||||||
for (final entry in bySender.entries) {
|
for (final entry in bySender.entries) {
|
||||||
final uname = entry.key.stripUid();
|
final contact = contactGetter(entry.key);
|
||||||
|
final uname = contact?.contactUsername ?? "Unknown";
|
||||||
await _notifier.show(
|
await _notifier.show(
|
||||||
uname.hashCode,
|
uname.hashCode,
|
||||||
null,
|
null,
|
||||||
|
@ -93,4 +94,13 @@ class NotificationClient {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> showContactRequestNotification(Contact contact) async {
|
||||||
|
await _notifier.show(
|
||||||
|
contact.contactUsername.hashCode,
|
||||||
|
"New Contact request",
|
||||||
|
"User ${contact.contactUsername} wants to be your friend.",
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,12 +105,13 @@ class HubManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleInvocation(body) async {
|
void _handleInvocation(body) async {
|
||||||
|
_logger.info(body);
|
||||||
final target = EventTarget.parse(body["target"]);
|
final target = EventTarget.parse(body["target"]);
|
||||||
final args = body["arguments"] ?? [];
|
final args = body["arguments"] ?? [];
|
||||||
final handler = _handlers[target];
|
final handler = _handlers[target];
|
||||||
if (handler == null) {
|
if (handler == null) {
|
||||||
_logger.warning("Unhandled event received");
|
_logger.warning("Unhandled event received");
|
||||||
if (kDebugMode) _logger.warning("Invocation target: ${target.name}, args:\n$args");
|
if (kDebugMode) _logger.warning("Invocation target: ${body["target"]}, args:\n$args");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
handler(args);
|
handler(args);
|
||||||
|
@ -132,6 +133,9 @@ class HubManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_handlers.clear();
|
||||||
|
_responseHandlers.clear();
|
||||||
_wsChannel?.close();
|
_wsChannel?.close();
|
||||||
|
_wsChannel = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
29
lib/lifecycle_event_handler.dart
Normal file
29
lib/lifecycle_event_handler.dart
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class LifecycleEventHandler extends WidgetsBindingObserver {
|
||||||
|
final AsyncCallback? resumeCallBack;
|
||||||
|
final AsyncCallback? suspendingCallBack;
|
||||||
|
|
||||||
|
LifecycleEventHandler({
|
||||||
|
this.resumeCallBack,
|
||||||
|
this.suspendingCallBack,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeAppLifecycleState(AppLifecycleState state) async {
|
||||||
|
super.didChangeAppLifecycleState(state);
|
||||||
|
switch (state) {
|
||||||
|
case AppLifecycleState.resumed:
|
||||||
|
await resumeCallBack?.call();
|
||||||
|
break;
|
||||||
|
case AppLifecycleState.inactive:
|
||||||
|
case AppLifecycleState.paused:
|
||||||
|
case AppLifecycleState.detached:
|
||||||
|
await suspendingCallBack?.call();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
lib/models/broadcast_group.dart
Normal file
8
lib/models/broadcast_group.dart
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
enum BroadcastGroup
|
||||||
|
{
|
||||||
|
public,
|
||||||
|
allContacts,
|
||||||
|
specificContacts,
|
||||||
|
broadcastKey,
|
||||||
|
connectionIds,
|
||||||
|
}
|
|
@ -16,7 +16,9 @@ enum EventTarget {
|
||||||
messagesRead,
|
messagesRead,
|
||||||
receiveSessionUpdate,
|
receiveSessionUpdate,
|
||||||
removeSession,
|
removeSession,
|
||||||
receiveStatusUpdate;
|
receiveStatusUpdate,
|
||||||
|
sendStatusToUser,
|
||||||
|
contactAddedOrUpdated;
|
||||||
|
|
||||||
factory EventTarget.parse(String? text) {
|
factory EventTarget.parse(String? text) {
|
||||||
if (text == null) return EventTarget.unknown;
|
if (text == null) return EventTarget.unknown;
|
||||||
|
|
106
lib/models/users/contact.dart
Normal file
106
lib/models/users/contact.dart
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
import 'package:recon/auxiliary.dart';
|
||||||
|
import 'package:recon/models/users/user_profile.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 Contact implements Comparable {
|
||||||
|
static const _emptyId = "-1";
|
||||||
|
static const _resoniteBotId = "U-Resonite";
|
||||||
|
final String id;
|
||||||
|
final String contactUsername;
|
||||||
|
final String ownerId;
|
||||||
|
final bool isAccepted;
|
||||||
|
final bool isMigrated;
|
||||||
|
final bool isCounterpartMigrated;
|
||||||
|
final UserStatus userStatus;
|
||||||
|
final UserProfile userProfile;
|
||||||
|
final ContactStatus contactStatus;
|
||||||
|
final DateTime latestMessageTime;
|
||||||
|
|
||||||
|
const Contact({required this.id, required this.contactUsername, required this.ownerId, required this.isAccepted,
|
||||||
|
required this.isMigrated, required this.isCounterpartMigrated, required this.userStatus, required this.userProfile,
|
||||||
|
required this.contactStatus, required this.latestMessageTime,
|
||||||
|
});
|
||||||
|
|
||||||
|
bool get isHeadless => userStatus.outputDevice == "Headless";
|
||||||
|
|
||||||
|
factory Contact.fromMap(Map map) {
|
||||||
|
var userStatus = map["userStatus"] == null ? UserStatus.empty() : UserStatus.fromMap(map["userStatus"]);
|
||||||
|
return Contact(
|
||||||
|
id: map["id"],
|
||||||
|
contactUsername: map["contactUsername"],
|
||||||
|
ownerId: map["ownerId"] ?? map["id"],
|
||||||
|
isAccepted: map["isAccepted"] ?? false,
|
||||||
|
isMigrated: map["isMigrated"] ?? false,
|
||||||
|
isCounterpartMigrated: map["isCounterpartMigrated"] ?? false,
|
||||||
|
// Resonite 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: ContactStatus.fromString(map["contactStatus"]),
|
||||||
|
latestMessageTime: map["latestMessageTime"] == null
|
||||||
|
? DateTime.fromMillisecondsSinceEpoch(0) : DateTime.parse(map["latestMessageTime"]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Contact? fromMapOrNull(Map? map) {
|
||||||
|
if (map == null) return null;
|
||||||
|
return Contact.fromMap(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory Contact.empty() {
|
||||||
|
return Contact(
|
||||||
|
id: _emptyId,
|
||||||
|
contactUsername: "",
|
||||||
|
ownerId: "",
|
||||||
|
isAccepted: false,
|
||||||
|
isMigrated: false,
|
||||||
|
isCounterpartMigrated: false,
|
||||||
|
userStatus: UserStatus.empty(),
|
||||||
|
userProfile: UserProfile.empty(),
|
||||||
|
contactStatus: ContactStatus.none,
|
||||||
|
latestMessageTime: DateTimeX.epoch
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get isEmpty => id == _emptyId;
|
||||||
|
bool get isPartiallyMigrated => isMigrated && !isCounterpartMigrated;
|
||||||
|
bool get isContactRequest => !isPartiallyMigrated && isAccepted && contactStatus == ContactStatus.requested;
|
||||||
|
|
||||||
|
Contact copyWith({
|
||||||
|
String? id, String? contactUsername, String? ownerId, UserStatus? userStatus, bool? isAccepted, bool? isMigrated,
|
||||||
|
bool? isCounterpartMigrated, UserProfile? userProfile, ContactStatus? contactStatus, DateTime? latestMessageTime}) {
|
||||||
|
return Contact(
|
||||||
|
id: id ?? this.id,
|
||||||
|
contactUsername: contactUsername ?? this.contactUsername,
|
||||||
|
ownerId: ownerId ?? this.ownerId,
|
||||||
|
isAccepted: isAccepted ?? this.isAccepted,
|
||||||
|
isMigrated: isMigrated ?? this.isMigrated,
|
||||||
|
isCounterpartMigrated: isCounterpartMigrated ?? this.isCounterpartMigrated,
|
||||||
|
userStatus: userStatus ?? this.userStatus,
|
||||||
|
userProfile: userProfile ?? this.userProfile,
|
||||||
|
contactStatus: contactStatus ?? this.contactStatus,
|
||||||
|
latestMessageTime: latestMessageTime ?? this.latestMessageTime,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map toMap({bool shallow=false}) {
|
||||||
|
return {
|
||||||
|
"id": id,
|
||||||
|
"contactUsername": contactUsername,
|
||||||
|
"ownerId": ownerId,
|
||||||
|
"isAccepted": isAccepted,
|
||||||
|
"isMigrated": isMigrated,
|
||||||
|
"isCounterpartMigrated": isCounterpartMigrated,
|
||||||
|
"userStatus": userStatus.toMap(shallow: shallow),
|
||||||
|
"profile": userProfile.toMap(),
|
||||||
|
"contactStatus": contactStatus.name,
|
||||||
|
"latestMessageTime": latestMessageTime.toIso8601String(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int compareTo(covariant Contact other) {
|
||||||
|
return contactUsername.compareTo(other.contactUsername);
|
||||||
|
}
|
||||||
|
}
|
14
lib/models/users/contact_status.dart
Normal file
14
lib/models/users/contact_status.dart
Normal file
|
@ -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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,88 +0,0 @@
|
||||||
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/online_status.dart';
|
|
||||||
import 'package:recon/models/users/user_status.dart';
|
|
||||||
|
|
||||||
class Friend implements Comparable {
|
|
||||||
static const _emptyId = "-1";
|
|
||||||
static const _resoniteBotId = "U-Resonite";
|
|
||||||
final String id;
|
|
||||||
final String username;
|
|
||||||
final String ownerId;
|
|
||||||
final UserStatus userStatus;
|
|
||||||
final UserProfile userProfile;
|
|
||||||
final FriendStatus contactStatus;
|
|
||||||
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,
|
|
||||||
});
|
|
||||||
|
|
||||||
bool get isHeadless => userStatus.outputDevice == "Headless";
|
|
||||||
|
|
||||||
factory Friend.fromMap(Map map) {
|
|
||||||
var userStatus = map["userStatus"] == null ? UserStatus.empty() : UserStatus.fromMap(map["userStatus"]);
|
|
||||||
return Friend(
|
|
||||||
id: map["id"],
|
|
||||||
username: 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"]),
|
|
||||||
latestMessageTime: map["latestMessageTime"] == null
|
|
||||||
? DateTime.fromMillisecondsSinceEpoch(0) : DateTime.parse(map["latestMessageTime"]),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Friend? fromMapOrNull(Map? map) {
|
|
||||||
if (map == null) return null;
|
|
||||||
return Friend.fromMap(map);
|
|
||||||
}
|
|
||||||
|
|
||||||
factory Friend.empty() {
|
|
||||||
return Friend(
|
|
||||||
id: _emptyId,
|
|
||||||
username: "",
|
|
||||||
ownerId: "",
|
|
||||||
userStatus: UserStatus.empty(),
|
|
||||||
userProfile: UserProfile.empty(),
|
|
||||||
contactStatus: FriendStatus.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(
|
|
||||||
id: id ?? this.id,
|
|
||||||
username: username ?? this.username,
|
|
||||||
ownerId: ownerId ?? this.ownerId,
|
|
||||||
userStatus: userStatus ?? this.userStatus,
|
|
||||||
userProfile: userProfile ?? this.userProfile,
|
|
||||||
contactStatus: contactStatus ?? this.contactStatus,
|
|
||||||
latestMessageTime: latestMessageTime ?? this.latestMessageTime,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map toMap({bool shallow=false}) {
|
|
||||||
return {
|
|
||||||
"id": id,
|
|
||||||
"contactUsername": username,
|
|
||||||
"ownerId": ownerId,
|
|
||||||
"userStatus": userStatus.toMap(shallow: shallow),
|
|
||||||
"profile": userProfile.toMap(),
|
|
||||||
"contactStatus": contactStatus.name,
|
|
||||||
"latestMessageTime": latestMessageTime.toIso8601String(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
int compareTo(covariant Friend other) {
|
|
||||||
return username.compareTo(other.username);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:recon/models/users/contact_status.dart';
|
||||||
import 'package:recon/models/users/user_profile.dart';
|
import 'package:recon/models/users/user_profile.dart';
|
||||||
|
|
||||||
class User {
|
class User {
|
||||||
|
@ -31,4 +32,13 @@ class User {
|
||||||
"profile": userProfile?.toMap(),
|
"profile": userProfile?.toMap(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map asContactRequest({required String ownerId, required ContactStatus contactStatus}) {
|
||||||
|
return {
|
||||||
|
"ownerId": ownerId,
|
||||||
|
"id": id,
|
||||||
|
"contactUsername": username,
|
||||||
|
"contactStatus": ContactStatus.accepted.name,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -4,7 +4,7 @@ import 'package:provider/provider.dart';
|
||||||
import 'package:recon/auxiliary.dart';
|
import 'package:recon/auxiliary.dart';
|
||||||
import 'package:recon/clients/messaging_client.dart';
|
import 'package:recon/clients/messaging_client.dart';
|
||||||
import 'package:recon/models/message.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/models/users/online_status.dart';
|
||||||
import 'package:recon/widgets/formatted_text.dart';
|
import 'package:recon/widgets/formatted_text.dart';
|
||||||
import 'package:recon/widgets/friends/friend_online_status_indicator.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 {
|
class FriendListTile extends StatelessWidget {
|
||||||
const FriendListTile({required this.friend, required this.unreads, this.onTap, super.key});
|
const FriendListTile({required this.friend, required this.unreads, this.onTap, super.key});
|
||||||
|
|
||||||
final Friend friend;
|
final Contact friend;
|
||||||
final int unreads;
|
final int unreads;
|
||||||
final Function? onTap;
|
final Function? onTap;
|
||||||
|
|
||||||
|
@ -30,15 +30,28 @@ class FriendListTile extends StatelessWidget {
|
||||||
leading: GenericAvatar(
|
leading: GenericAvatar(
|
||||||
imageUri: imageUri,
|
imageUri: imageUri,
|
||||||
),
|
),
|
||||||
trailing: unreads != 0
|
trailing: friend.isContactRequest
|
||||||
? Text(
|
? IconButton(
|
||||||
"+$unreads",
|
splashRadius: 24,
|
||||||
style: theme.textTheme.bodyMedium?.copyWith(color: theme.colorScheme.primary),
|
iconSize: 20,
|
||||||
|
icon: const Icon(Icons.person_add),
|
||||||
|
style: IconButton.styleFrom(
|
||||||
|
foregroundColor: Theme.of(context).colorScheme.onBackground,
|
||||||
|
side: BorderSide(color: Theme.of(context).colorScheme.primary, width: 2),
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
final mClient = Provider.of<MessagingClient>(context, listen: false);
|
||||||
|
},
|
||||||
)
|
)
|
||||||
: null,
|
: unreads != 0
|
||||||
|
? Text(
|
||||||
|
"+$unreads",
|
||||||
|
style: theme.textTheme.bodyMedium?.copyWith(color: theme.colorScheme.primary),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
title: Row(
|
title: Row(
|
||||||
children: [
|
children: [
|
||||||
Text(friend.username),
|
Text(friend.contactUsername),
|
||||||
if (friend.isHeadless)
|
if (friend.isHeadless)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: 8),
|
padding: const EdgeInsets.only(left: 8),
|
||||||
|
@ -92,7 +105,7 @@ class FriendListTile extends StatelessWidget {
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
onTap?.call();
|
onTap?.call();
|
||||||
mClient.loadUserMessageCache(friend.id);
|
mClient.loadUserMessageCache(friend.id);
|
||||||
final unreads = mClient.getUnreadsForFriend(friend);
|
final unreads = mClient.getUnreadsForContact(friend);
|
||||||
if (unreads.isNotEmpty) {
|
if (unreads.isNotEmpty) {
|
||||||
final readBatch = MarkReadBatch(
|
final readBatch = MarkReadBatch(
|
||||||
senderId: friend.id,
|
senderId: friend.id,
|
||||||
|
|
|
@ -45,16 +45,16 @@ class _FriendsListState extends State<FriendsList> with AutomaticKeepAliveClient
|
||||||
var friends = List.from(mClient.cachedFriends); // Explicit copy.
|
var friends = List.from(mClient.cachedFriends); // Explicit copy.
|
||||||
if (_searchFilter.isNotEmpty) {
|
if (_searchFilter.isNotEmpty) {
|
||||||
friends = friends
|
friends = friends
|
||||||
.where((element) => element.username.toLowerCase().contains(_searchFilter.toLowerCase()))
|
.where((element) => element.contactUsername.toLowerCase().contains(_searchFilter.toLowerCase()))
|
||||||
.toList();
|
.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(
|
return ListView.builder(
|
||||||
physics: const BouncingScrollPhysics(decelerationRate: ScrollDecelerationRate.fast),
|
physics: const BouncingScrollPhysics(decelerationRate: ScrollDecelerationRate.fast),
|
||||||
itemCount: friends.length,
|
itemCount: friends.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final friend = friends[index];
|
final friend = friends[index];
|
||||||
final unreads = mClient.getUnreadsForFriend(friend);
|
final unreads = mClient.getUnreadsForContact(friend);
|
||||||
return FriendListTile(
|
return FriendListTile(
|
||||||
friend: friend,
|
friend: friend,
|
||||||
unreads: unreads.length,
|
unreads: unreads.length,
|
||||||
|
|
|
@ -1,13 +1,18 @@
|
||||||
import 'package:recon/apis/contact_api.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:recon/auxiliary.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/models/users/user.dart';
|
||||||
import 'package:recon/widgets/generic_avatar.dart';
|
import 'package:recon/widgets/generic_avatar.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
class UserListTile extends StatefulWidget {
|
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 User user;
|
||||||
final bool isFriend;
|
final bool isFriend;
|
||||||
|
@ -24,24 +29,20 @@ class _UserListTileState extends State<UserListTile> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final colorScheme = Theme
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
.of(context)
|
final style = _localAdded
|
||||||
.colorScheme;
|
? IconButton.styleFrom(
|
||||||
final style = _localAdded ? IconButton.styleFrom(
|
foregroundColor: colorScheme.onBackground,
|
||||||
foregroundColor: colorScheme.onBackground,
|
side: BorderSide(color: colorScheme.error, width: 2),
|
||||||
side: BorderSide(
|
)
|
||||||
color: colorScheme.error,
|
: IconButton.styleFrom(
|
||||||
width: 2
|
foregroundColor: colorScheme.onBackground,
|
||||||
),
|
side: BorderSide(color: colorScheme.primary, width: 2),
|
||||||
) : IconButton.styleFrom(
|
);
|
||||||
foregroundColor: colorScheme.onBackground,
|
|
||||||
side: BorderSide(
|
|
||||||
color: colorScheme.primary,
|
|
||||||
width: 2
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return ListTile(
|
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),
|
title: Text(widget.user.username),
|
||||||
subtitle: Text(_regDateFormat.format(widget.user.registrationDate)),
|
subtitle: Text(_regDateFormat.format(widget.user.registrationDate)),
|
||||||
trailing: IconButton(
|
trailing: IconButton(
|
||||||
|
@ -49,48 +50,45 @@ class _UserListTileState extends State<UserListTile> {
|
||||||
iconSize: 20,
|
iconSize: 20,
|
||||||
icon: _localAdded ? const Icon(Icons.person_remove) : const Icon(Icons.person_add),
|
icon: _localAdded ? const Icon(Icons.person_remove) : const Icon(Icons.person_add),
|
||||||
style: style,
|
style: style,
|
||||||
onPressed: _loading ? null : () async {
|
onPressed: _loading
|
||||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("Sorry, this feature is not yet available")));
|
? null
|
||||||
return;
|
: () async {
|
||||||
setState(() {
|
final mClient = Provider.of<MessagingClient>(context, listen: false);
|
||||||
_loading = true;
|
setState(() {
|
||||||
});
|
_loading = true;
|
||||||
try {
|
});
|
||||||
if (_localAdded) {
|
try {
|
||||||
await ContactApi.removeUserAsFriend(ClientHolder
|
if (_localAdded) {
|
||||||
.of(context)
|
mClient.removeUserAsFriend(widget.user);
|
||||||
.apiClient, user: widget.user);
|
} else {
|
||||||
} else {
|
mClient.addUserAsFriend(widget.user);
|
||||||
await ContactApi.addUserAsFriend(ClientHolder
|
}
|
||||||
.of(context)
|
setState(() {
|
||||||
.apiClient, user: widget.user);
|
_loading = false;
|
||||||
}
|
_localAdded = !_localAdded;
|
||||||
setState(() {
|
});
|
||||||
_loading = false;
|
widget.onChanged?.call();
|
||||||
_localAdded = !_localAdded;
|
} catch (e, s) {
|
||||||
});
|
FlutterError.reportError(FlutterErrorDetails(exception: e, stack: s));
|
||||||
widget.onChanged?.call();
|
if (context.mounted) {
|
||||||
} catch (e, s) {
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
FlutterError.reportError(FlutterErrorDetails(exception: e, stack: s));
|
SnackBar(
|
||||||
if (context.mounted) {
|
duration: const Duration(seconds: 5),
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
content: Text(
|
||||||
SnackBar(
|
"Something went wrong: $e",
|
||||||
duration: const Duration(seconds: 5),
|
softWrap: true,
|
||||||
content: Text(
|
maxLines: null,
|
||||||
"Something went wrong: $e",
|
),
|
||||||
softWrap: true,
|
),
|
||||||
maxLines: null,
|
);
|
||||||
),
|
}
|
||||||
),
|
setState(() {
|
||||||
);
|
_loading = false;
|
||||||
}
|
});
|
||||||
setState(() {
|
return;
|
||||||
_loading = false;
|
}
|
||||||
});
|
},
|
||||||
return;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,24 +29,19 @@ class _UserSearchState extends State<UserSearch> {
|
||||||
late Future<List<User>?>? _usersFuture = _emptySearch;
|
late Future<List<User>?>? _usersFuture = _emptySearch;
|
||||||
|
|
||||||
Future<List<User>> get _emptySearch =>
|
Future<List<User>> get _emptySearch =>
|
||||||
Future(() =>
|
Future(() => throw const SearchError(message: "Start typing to search for users", icon: Icons.search));
|
||||||
throw const SearchError(
|
|
||||||
message: "Start typing to search for users", icon: Icons.search)
|
|
||||||
);
|
|
||||||
|
|
||||||
void _querySearch(BuildContext context, String needle) {
|
void _querySearch(BuildContext context, String needle) {
|
||||||
if (needle.isEmpty) {
|
if (needle.isEmpty) {
|
||||||
_usersFuture = _emptySearch;
|
_usersFuture = _emptySearch;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_usersFuture = UserApi.searchUsers(ClientHolder
|
_usersFuture = UserApi.searchUsers(ClientHolder.of(context).apiClient, needle: needle).then((value) {
|
||||||
.of(context)
|
|
||||||
.apiClient, needle: needle).then((value) {
|
|
||||||
final res = value.toList();
|
final res = value.toList();
|
||||||
if (res.isEmpty) throw SearchError(message: "No user found with username '$needle'", icon: Icons.search_off);
|
if (res.isEmpty) {
|
||||||
res.sort(
|
throw SearchError(message: "No user found with username '$needle'", icon: Icons.search_off);
|
||||||
(a, b) => a.username.length.compareTo(b.username.length)
|
}
|
||||||
);
|
res.sort((a, b) => a.username.length.compareTo(b.username.length));
|
||||||
return res;
|
return res;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -72,9 +67,13 @@ class _UserSearchState extends State<UserSearch> {
|
||||||
itemCount: users.length,
|
itemCount: users.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final user = users[index];
|
final user = users[index];
|
||||||
return UserListTile(user: user, onChanged: () {
|
return UserListTile(
|
||||||
mClient.refreshFriendsList();
|
user: user,
|
||||||
}, isFriend: mClient.getAsFriend(user.id) != null,);
|
onChanged: () {
|
||||||
|
mClient.refreshFriendsList();
|
||||||
|
},
|
||||||
|
isFriend: mClient.getAsContact(user.id) != null,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else if (snapshot.hasError) {
|
} else if (snapshot.hasError) {
|
||||||
|
@ -85,9 +84,13 @@ class _UserSearchState extends State<UserSearch> {
|
||||||
iconOverride: err.icon,
|
iconOverride: err.icon,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
FlutterError.reportError(
|
FlutterError.reportError(FlutterErrorDetails(
|
||||||
FlutterErrorDetails(exception: snapshot.error!, stack: snapshot.stackTrace));
|
exception: snapshot.error!,
|
||||||
return DefaultErrorWidget(title: "${snapshot.error}",);
|
stack: snapshot.stackTrace,
|
||||||
|
));
|
||||||
|
return DefaultErrorWidget(
|
||||||
|
title: "${snapshot.error}",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return const Column(
|
return const Column(
|
||||||
|
@ -106,10 +109,7 @@ class _UserSearchState extends State<UserSearch> {
|
||||||
isDense: true,
|
isDense: true,
|
||||||
hintText: "Search for users...",
|
hintText: "Search for users...",
|
||||||
contentPadding: const EdgeInsets.all(16),
|
contentPadding: const EdgeInsets.all(16),
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(borderRadius: BorderRadius.circular(24))),
|
||||||
borderRadius: BorderRadius.circular(24)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
autocorrect: false,
|
autocorrect: false,
|
||||||
controller: _searchInputController,
|
controller: _searchInputController,
|
||||||
onChanged: (String value) {
|
onChanged: (String value) {
|
||||||
|
@ -136,4 +136,4 @@ class _UserSearchState extends State<UserSearch> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import 'package:recon/client_holder.dart';
|
||||||
import 'package:recon/clients/api_client.dart';
|
import 'package:recon/clients/api_client.dart';
|
||||||
import 'package:recon/clients/messaging_client.dart';
|
import 'package:recon/clients/messaging_client.dart';
|
||||||
import 'package:recon/models/message.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:recon/widgets/messages/message_attachment_list.dart';
|
||||||
import 'package:record/record.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});
|
const MessageInputBar({this.disabled = false, required this.recipient, this.onMessageSent, super.key});
|
||||||
|
|
||||||
final bool disabled;
|
final bool disabled;
|
||||||
final Friend recipient;
|
final Contact recipient;
|
||||||
final Function()? onMessageSent;
|
final Function()? onMessageSent;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -403,7 +403,7 @@ class _MessageInputBarState extends State<MessageInputBar> {
|
||||||
style: Theme.of(context).textTheme.bodyLarge,
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
isDense: true,
|
isDense: true,
|
||||||
hintText: _isRecording ? "" : "Message ${widget.recipient.username}...",
|
hintText: _isRecording ? "" : "Message ${widget.recipient.contactUsername}...",
|
||||||
hintMaxLines: 1,
|
hintMaxLines: 1,
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
fillColor: Colors.black26,
|
fillColor: Colors.black26,
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:recon/clients/audio_cache_client.dart';
|
import 'package:recon/clients/audio_cache_client.dart';
|
||||||
import 'package:recon/clients/messaging_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/default_error_widget.dart';
|
||||||
import 'package:recon/widgets/friends/friend_online_status_indicator.dart';
|
import 'package:recon/widgets/friends/friend_online_status_indicator.dart';
|
||||||
import 'package:recon/widgets/messages/message_input_bar.dart';
|
import 'package:recon/widgets/messages/message_input_bar.dart';
|
||||||
|
@ -54,7 +54,7 @@ class _MessagesListState extends State<MessagesList> with SingleTickerProviderSt
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final appBarColor = Theme.of(context).colorScheme.surface;
|
final appBarColor = Theme.of(context).colorScheme.surface;
|
||||||
return Consumer<MessagingClient>(builder: (context, mClient, _) {
|
return Consumer<MessagingClient>(builder: (context, mClient, _) {
|
||||||
final friend = mClient.selectedFriend ?? Friend.empty();
|
final friend = mClient.selectedFriend ?? Contact.empty();
|
||||||
final cache = mClient.getUserMessageCache(friend.id);
|
final cache = mClient.getUserMessageCache(friend.id);
|
||||||
final sessions = friend.userStatus.decodedSessions.where((element) => element.isVisible).toList();
|
final sessions = friend.userStatus.decodedSessions.where((element) => element.isVisible).toList();
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
@ -66,7 +66,7 @@ class _MessagesListState extends State<MessagesList> with SingleTickerProviderSt
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: 8,
|
width: 8,
|
||||||
),
|
),
|
||||||
Text(friend.username),
|
Text(friend.contactUsername),
|
||||||
if (friend.isHeadless)
|
if (friend.isHeadless)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: 12),
|
padding: const EdgeInsets.only(left: 12),
|
||||||
|
|
284
pubspec.lock
284
pubspec.lock
|
@ -21,18 +21,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: audio_session
|
name: audio_session
|
||||||
sha256: "8a2bc5e30520e18f3fb0e366793d78057fb64cd5287862c76af0c8771f2a52ad"
|
sha256: "6fdf255ed3af86535c96452c33ecff1245990bb25a605bfb1958661ccc3d467f"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.16"
|
version: "0.1.18"
|
||||||
background_downloader:
|
background_downloader:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: background_downloader
|
name: background_downloader
|
||||||
sha256: f74abc807173daac213cd810769532c62755279936532311d994418079d16013
|
sha256: "0873a939f5a301872cb7614e12f759fbccef2e3ed0d0d37b540dde7611275b12"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.12.2"
|
version: "7.12.3"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -45,66 +45,66 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: cached_network_image
|
name: cached_network_image
|
||||||
sha256: f98972704692ba679db144261172a8e20feb145636c617af0eb4022132a6797f
|
sha256: "28ea9690a8207179c319965c13cd8df184d5ee721ae2ce60f398ced1219cea1f"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.3.0"
|
version: "3.3.1"
|
||||||
cached_network_image_platform_interface:
|
cached_network_image_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: cached_network_image_platform_interface
|
name: cached_network_image_platform_interface
|
||||||
sha256: "56aa42a7a01e3c9db8456d9f3f999931f1e05535b5a424271e9a38cabf066613"
|
sha256: "9e90e78ae72caa874a323d78fa6301b3fb8fa7ea76a8f96dc5b5bf79f283bf2f"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
version: "4.0.0"
|
||||||
cached_network_image_web:
|
cached_network_image_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: cached_network_image_web
|
name: cached_network_image_web
|
||||||
sha256: "759b9a9f8f6ccbb66c185df805fac107f05730b1dab9c64626d1008cca532257"
|
sha256: "42a835caa27c220d1294311ac409a43361088625a4f23c820b006dd9bffb3316"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.1"
|
||||||
camera:
|
camera:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: camera
|
name: camera
|
||||||
sha256: "1f9010f0689774380fbcd7d6b7820a5157e8e97685fa66d619e1d1f58b3fdf93"
|
sha256: "9499cbc2e51d8eb0beadc158b288380037618ce4e30c9acbc4fae1ac3ecb5797"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.10.5+5"
|
version: "0.10.5+9"
|
||||||
camera_android:
|
camera_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: camera_android
|
name: camera_android
|
||||||
sha256: "58463140f1b39591b8e2155861b436abad4ceb48160058be8374164ff0309ef3"
|
sha256: "351429510121d179b9aac5a2e8cb525c3cd6c39f4d709c5f72dfb21726e52371"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.10.8+13"
|
version: "0.10.8+16"
|
||||||
camera_avfoundation:
|
camera_avfoundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: camera_avfoundation
|
name: camera_avfoundation
|
||||||
sha256: "9495e633cda700717bbe299b0979e6c4a08cee45f298945973dc9cf3e4c1cba5"
|
sha256: "7d0763dfcbf060f56aa254a68c103210280bee9e97bbe4fdef23e257a4f70ab9"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.9.13+6"
|
version: "0.9.14"
|
||||||
camera_platform_interface:
|
camera_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: camera_platform_interface
|
name: camera_platform_interface
|
||||||
sha256: "86fd4fc597c6e455265ddb5884feb352d0171ad14b9cdf3aba30da59b25738c4"
|
sha256: e971ebca970f7cfee396f76ef02070b5e441b4aa04942da9c108d725f57bbd32
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.6.0"
|
version: "2.7.2"
|
||||||
camera_web:
|
camera_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: camera_web
|
name: camera_web
|
||||||
sha256: d4c2c571c7af04f8b10702ca16bb9ed2a26e64534171e8f75c9349b2c004d8f1
|
sha256: f18ccfb33b2a7c49a52ad5aa3f07330b7422faaecbdfd9b9fe8e51182f6ad67d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.2+3"
|
version: "0.3.2+4"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -125,10 +125,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: collection
|
name: collection
|
||||||
sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
|
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.17.2"
|
version: "1.18.0"
|
||||||
color:
|
color:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -141,10 +141,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: cross_file
|
name: cross_file
|
||||||
sha256: "445db18de832dba8d851e287aff8ccf169bed30d2e94243cb54c7d2f1ed2142c"
|
sha256: fedaadfa3a6996f75211d835aaeb8fede285dae94262485698afd832371b9a5e
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.3+6"
|
version: "0.3.3+8"
|
||||||
crypto:
|
crypto:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -165,18 +165,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: dbus
|
name: dbus
|
||||||
sha256: "6f07cba3f7b3448d42d015bfd3d53fe12e5b36da2423f23838efc1d5fb31a263"
|
sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.8"
|
version: "0.7.10"
|
||||||
dynamic_color:
|
dynamic_color:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: dynamic_color
|
name: dynamic_color
|
||||||
sha256: "8b8bd1d798bd393e11eddeaa8ae95b12ff028bf7d5998fc5d003488cd5f4ce2f"
|
sha256: a866f1f8947bfdaf674d7928e769eac7230388a2e7a2542824fad4bb5b87be3b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.6.8"
|
version: "1.6.9"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -245,10 +245,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: file_selector_platform_interface
|
name: file_selector_platform_interface
|
||||||
sha256: "0aa47a725c346825a2bd396343ce63ac00bda6eff2fbc43eabe99737dede8262"
|
sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.6.1"
|
version: "2.6.2"
|
||||||
file_selector_windows:
|
file_selector_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -257,6 +257,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.9.3+1"
|
version: "0.9.3+1"
|
||||||
|
fixnum:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fixnum
|
||||||
|
sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -290,10 +298,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_local_notifications
|
name: flutter_local_notifications
|
||||||
sha256: "6d11ea777496061e583623aaf31923f93a9409ef8fcaeeefdd6cd78bf4fe5bb3"
|
sha256: c18f1de98fe0bb9dd5ba91e1330d4febc8b6a7de6aae3ffe475ef423723e72f3
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "16.1.0"
|
version: "16.3.2"
|
||||||
flutter_local_notifications_linux:
|
flutter_local_notifications_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -412,10 +420,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: http
|
name: http
|
||||||
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
|
sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.2.0"
|
||||||
http_parser:
|
http_parser:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -428,34 +436,34 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: image_picker
|
name: image_picker
|
||||||
sha256: "7d7f2768df2a8b0a3cefa5ef4f84636121987d403130e70b17ef7e2cf650ba84"
|
sha256: "26222b01a0c9a2c8fe02fc90b8208bd3325da5ed1f4a2acabf75939031ac0bdd"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.4"
|
version: "1.0.7"
|
||||||
image_picker_android:
|
image_picker_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_android
|
name: image_picker_android
|
||||||
sha256: d6a6e78821086b0b737009b09363018309bbc6de3fd88cc5c26bc2bb44a4957f
|
sha256: "39f2bfe497e495450c81abcd44b62f56c2a36a37a175da7d137b4454977b51b1"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.8+2"
|
version: "0.8.9+3"
|
||||||
image_picker_for_web:
|
image_picker_for_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_for_web
|
name: image_picker_for_web
|
||||||
sha256: "869fe8a64771b7afbc99fc433a5f7be2fea4d1cb3d7c11a48b6b579eb9c797f0"
|
sha256: e2423c53a68b579a7c37a1eda967b8ae536c3d98518e5db95ca1fe5719a730a3
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "3.0.2"
|
||||||
image_picker_ios:
|
image_picker_ios:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_ios
|
name: image_picker_ios
|
||||||
sha256: "76ec722aeea419d03aa915c2c96bf5b47214b053899088c9abb4086ceecf97a7"
|
sha256: fadafce49e8569257a0cad56d24438a6fa1f0cbd7ee0af9b631f7492818a4ca3
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.8+4"
|
version: "0.8.9+1"
|
||||||
image_picker_linux:
|
image_picker_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -476,10 +484,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_platform_interface
|
name: image_picker_platform_interface
|
||||||
sha256: ed9b00e63977c93b0d2d2b343685bed9c324534ba5abafbb3dfbd6a780b1b514
|
sha256: fa4e815e6fcada50e35718727d83ba1c92f1edf95c0b4436554cec301b56233b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.9.1"
|
version: "2.9.3"
|
||||||
image_picker_windows:
|
image_picker_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -508,26 +516,26 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: just_audio
|
name: just_audio
|
||||||
sha256: "5ed0cd723e17dfd8cd4b0253726221e67f6546841ea4553635cf895061fc335b"
|
sha256: b607cd1a43bac03d85c3aaee00448ff4a589ef2a77104e3d409889ff079bf823
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.9.35"
|
version: "0.9.36"
|
||||||
just_audio_platform_interface:
|
just_audio_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: just_audio_platform_interface
|
name: just_audio_platform_interface
|
||||||
sha256: d8409da198bbc59426cd45d4c92fca522a2ec269b576ce29459d6d6fcaeb44df
|
sha256: c3dee0014248c97c91fe6299edb73dc4d6c6930a2f4f713579cd692d9e47f4a1
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.2.1"
|
version: "4.2.2"
|
||||||
just_audio_web:
|
just_audio_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: just_audio_web
|
name: just_audio_web
|
||||||
sha256: ff62f733f437b25a0ff590f0e295fa5441dcb465f1edbdb33b3dea264705bc13
|
sha256: "134356b0fe3d898293102b33b5fd618831ffdc72bb7a1b726140abdf22772b70"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.8"
|
version: "0.4.9"
|
||||||
lints:
|
lints:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -564,18 +572,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
|
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.1"
|
version: "1.10.0"
|
||||||
mime:
|
mime:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: mime
|
name: mime
|
||||||
sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e
|
sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.4"
|
version: "1.0.5"
|
||||||
nested:
|
nested:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -620,26 +628,26 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: path_provider
|
name: path_provider
|
||||||
sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa
|
sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.2"
|
||||||
path_provider_android:
|
path_provider_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_android
|
name: path_provider_android
|
||||||
sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72
|
sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.1"
|
version: "2.2.2"
|
||||||
path_provider_foundation:
|
path_provider_foundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_foundation
|
name: path_provider_foundation
|
||||||
sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d"
|
sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.1"
|
version: "2.3.2"
|
||||||
path_provider_linux:
|
path_provider_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -652,10 +660,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_platform_interface
|
name: path_provider_platform_interface
|
||||||
sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c"
|
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.2"
|
||||||
path_provider_windows:
|
path_provider_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -668,50 +676,58 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: permission_handler
|
name: permission_handler
|
||||||
sha256: "284a66179cabdf942f838543e10413246f06424d960c92ba95c84439154fcac8"
|
sha256: "45ff3fbcb99040fde55c528d5e3e6ca29171298a85436274d49c6201002087d6"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "11.0.1"
|
version: "11.2.0"
|
||||||
permission_handler_android:
|
permission_handler_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: permission_handler_android
|
name: permission_handler_android
|
||||||
sha256: f9fddd3b46109bd69ff3f9efa5006d2d309b7aec0f3c1c5637a60a2d5659e76e
|
sha256: "758284a0976772f9c744d6384fc5dc4834aa61e3f7aa40492927f244767374eb"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "11.1.0"
|
version: "12.0.3"
|
||||||
permission_handler_apple:
|
permission_handler_apple:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: permission_handler_apple
|
name: permission_handler_apple
|
||||||
sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5"
|
sha256: c6bf440f80acd2a873d3d91a699e4cc770f86e7e6b576dda98759e8b92b39830
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.1.4"
|
version: "9.3.0"
|
||||||
|
permission_handler_html:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: permission_handler_html
|
||||||
|
sha256: "54bf176b90f6eddd4ece307e2c06cf977fb3973719c35a93b85cc7093eb6070d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.1"
|
||||||
permission_handler_platform_interface:
|
permission_handler_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: permission_handler_platform_interface
|
name: permission_handler_platform_interface
|
||||||
sha256: "6760eb5ef34589224771010805bea6054ad28453906936f843a8cc4d3a55c4a4"
|
sha256: "5c43148f2bfb6d14c5a8162c0a712afe891f2d847f35fcff29c406b37da43c3c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.12.0"
|
version: "4.1.0"
|
||||||
permission_handler_windows:
|
permission_handler_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: permission_handler_windows
|
name: permission_handler_windows
|
||||||
sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098
|
sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.3"
|
version: "0.2.1"
|
||||||
petitparser:
|
petitparser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: petitparser
|
name: petitparser
|
||||||
sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750
|
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.4.0"
|
version: "6.0.2"
|
||||||
photo_view:
|
photo_view:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -724,58 +740,50 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: platform
|
name: platform
|
||||||
sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59"
|
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.3"
|
version: "3.1.4"
|
||||||
plugin_platform_interface:
|
plugin_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: plugin_platform_interface
|
name: plugin_platform_interface
|
||||||
sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d
|
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.6"
|
version: "2.1.8"
|
||||||
provider:
|
provider:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: provider
|
name: provider
|
||||||
sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f
|
sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.5"
|
version: "6.1.1"
|
||||||
quiver:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: quiver
|
|
||||||
sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "3.2.1"
|
|
||||||
record:
|
record:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: record
|
name: record
|
||||||
sha256: be9b710f42edf94f939dda1a1688e82a68dcd391be0a836c01e639a249f133d3
|
sha256: "5c8e12c692a4800b33f5f8b6c821ea083b12bfdbd031b36ba9322c40a4eeecc9"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.1"
|
version: "5.0.4"
|
||||||
record_android:
|
record_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: record_android
|
name: record_android
|
||||||
sha256: "5a96286f051cf46dffd1ae7cd5f1baa82cf6a983d26389c2f8d03d03dddc711b"
|
sha256: "805ecaa232a671aff2ee9ec4730ef6addb97c548d2db6b1fbd5197f1d4f47a5a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.3"
|
version: "1.0.4"
|
||||||
record_darwin:
|
record_darwin:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: record_darwin
|
name: record_darwin
|
||||||
sha256: "78dba641ae271e555035ee68b637f7605ba9f8c60ccfd5c03b835e0b77ea201f"
|
sha256: ee8cb1bb1712d7ce38140ecabe70e5c286c02f05296d66043bee865ace7eb1b9
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
version: "1.0.1"
|
||||||
record_linux:
|
record_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -796,18 +804,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: record_web
|
name: record_web
|
||||||
sha256: be8c62759b385a04dbc4ae7f5d3f78e6f0c532e72935d288aee87432bbbbb8f6
|
sha256: "24847cdbcf999f7a5762170792f622ac844858766becd0f2370ec8ae22f7526e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.3"
|
version: "1.0.5"
|
||||||
record_windows:
|
record_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: record_windows
|
name: record_windows
|
||||||
sha256: "326bfbe6f5232dd773ad6b848cd94f78148f02557abff1dd4627d056b688dbdb"
|
sha256: "39998b3ea7d8d28b04159d82220e6e5e32a7c357c6fb2794f5736beea272f6c3"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
version: "1.0.2"
|
||||||
rxdart:
|
rxdart:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -857,34 +865,34 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: sqflite
|
name: sqflite
|
||||||
sha256: "591f1602816e9c31377d5f008c2d9ef7b8aca8941c3f89cc5fd9d84da0c38a9a"
|
sha256: c2c32eb0c74021d987336522acc3b6bf0082fbd0c540c36a9cf4ddb8ba891ddc
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.0"
|
version: "2.3.1"
|
||||||
sqflite_common:
|
sqflite_common:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: sqflite_common
|
name: sqflite_common
|
||||||
sha256: "1b92f368f44b0dee2425bb861cfa17b6f6cf3961f762ff6f941d20b33355660a"
|
sha256: "28d8c66baee4968519fb8bd6cdbedad982d6e53359091f0b74544a9f32ec72d5"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.0"
|
version: "2.5.3"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: stack_trace
|
name: stack_trace
|
||||||
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
|
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.11.0"
|
version: "1.11.1"
|
||||||
stream_channel:
|
stream_channel:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: stream_channel
|
name: stream_channel
|
||||||
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
|
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.2"
|
||||||
stream_transform:
|
stream_transform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -905,10 +913,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: synchronized
|
name: synchronized
|
||||||
sha256: "5fcbd27688af6082f5abd611af56ee575342c30e87541d0245f7ff99faa02c60"
|
sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0"
|
version: "3.1.0+1"
|
||||||
term_glyph:
|
term_glyph:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -921,10 +929,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8"
|
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.0"
|
version: "0.6.1"
|
||||||
timezone:
|
timezone:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -945,34 +953,34 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: url_launcher
|
name: url_launcher
|
||||||
sha256: b1c9e98774adf8820c96fbc7ae3601231d324a7d5ebd8babe27b6dfac91357ba
|
sha256: c512655380d241a337521703af62d2c122bf7b77a46ff7dd750092aa9433499c
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.2.1"
|
version: "6.2.4"
|
||||||
url_launcher_android:
|
url_launcher_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_android
|
name: url_launcher_android
|
||||||
sha256: "31222ffb0063171b526d3e569079cf1f8b294075ba323443fdc690842bfd4def"
|
sha256: "507dc655b1d9cb5ebc756032eb785f114e415f91557b73bf60b7e201dfedeb2f"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.2.0"
|
version: "6.2.2"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_ios
|
name: url_launcher_ios
|
||||||
sha256: "4ac97281cf60e2e8c5cc703b2b28528f9b50c8f7cebc71df6bdf0845f647268a"
|
sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.2.0"
|
version: "6.2.4"
|
||||||
url_launcher_linux:
|
url_launcher_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_linux
|
name: url_launcher_linux
|
||||||
sha256: "9f2d390e096fdbe1e6e6256f97851e51afc2d9c423d3432f1d6a02a8a9a8b9fd"
|
sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0"
|
version: "3.1.1"
|
||||||
url_launcher_macos:
|
url_launcher_macos:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -985,34 +993,34 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_platform_interface
|
name: url_launcher_platform_interface
|
||||||
sha256: "980e8d9af422f477be6948bdfb68df8433be71f5743a188968b0c1b887807e50"
|
sha256: a932c3a8082e118f80a475ce692fde89dc20fddb24c57360b96bc56f7035de1f
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.3.1"
|
||||||
url_launcher_web:
|
url_launcher_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_web
|
name: url_launcher_web
|
||||||
sha256: "7fd2f55fe86cea2897b963e864dc01a7eb0719ecc65fcef4c1cc3d686d718bb2"
|
sha256: fff0932192afeedf63cdd50ecbb1bc825d31aed259f02bb8dba0f3b729a5e88b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.2.3"
|
||||||
url_launcher_windows:
|
url_launcher_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_windows
|
name: url_launcher_windows
|
||||||
sha256: "7754a1ad30ee896b265f8d14078b0513a4dba28d358eabb9d5f339886f4a1adc"
|
sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0"
|
version: "3.1.1"
|
||||||
uuid:
|
uuid:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: uuid
|
name: uuid
|
||||||
sha256: b715b8d3858b6fa9f68f87d20d98830283628014750c2b09b6f516c1da4af2a7
|
sha256: cd210a09f7c18cbe5a02511718e0334de6559871052c90a90c0cca46a4aa81c8
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.1.0"
|
version: "4.3.3"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1025,10 +1033,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: web
|
name: web
|
||||||
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
|
sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.4-beta"
|
version: "0.3.0"
|
||||||
web_socket_channel:
|
web_socket_channel:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -1041,34 +1049,34 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: win32
|
name: win32
|
||||||
sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3"
|
sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.9"
|
version: "5.2.0"
|
||||||
workmanager:
|
workmanager:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: workmanager
|
name: workmanager
|
||||||
sha256: e0be7e35d644643f164ee45d2ce14414f0e0fdde19456aa66065f35a0b1d2ea1
|
sha256: ed13530cccd28c5c9959ad42d657cd0666274ca74c56dea0ca183ddd527d3a00
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.1"
|
version: "0.5.2"
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: xdg_directories
|
name: xdg_directories
|
||||||
sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2"
|
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.3"
|
version: "1.0.4"
|
||||||
xml:
|
xml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: xml
|
name: xml
|
||||||
sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84"
|
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.3.0"
|
version: "6.5.0"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.1.0 <4.0.0"
|
dart: ">=3.2.3 <4.0.0"
|
||||||
flutter: ">=3.13.0"
|
flutter: ">=3.16.6"
|
||||||
|
|
|
@ -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
|
# 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
|
# 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.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 0.11.0-beta+1
|
version: 0.11.3-beta+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.0.1"
|
sdk: ">=3.0.1"
|
||||||
|
|
16
windows/installer-script.nsi
Normal file
16
windows/installer-script.nsi
Normal file
|
@ -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
|
|
@ -26,8 +26,8 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
||||||
|
|
||||||
FlutterWindow window(project);
|
FlutterWindow window(project);
|
||||||
Win32Window::Point origin(10, 10);
|
Win32Window::Point origin(10, 10);
|
||||||
Win32Window::Size size(1280, 720);
|
Win32Window::Size size(480, 900);
|
||||||
if (!window.Create(L"recon", origin, size)) {
|
if (!window.Create(L"ReCon", origin, size)) {
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
window.SetQuitOnClose(true);
|
window.SetQuitOnClose(true);
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 264 KiB |
Loading…
Reference in a new issue