Merge pull request 'gherkin update' (#348) from fastercwtch into gherkin
Reviewed-on: #348
This commit is contained in:
commit
6fdc375d18
|
@ -5,6 +5,7 @@ import 'package:cwtch/models/contact.dart';
|
||||||
import 'package:cwtch/models/message.dart';
|
import 'package:cwtch/models/message.dart';
|
||||||
import 'package:cwtch/models/profilelist.dart';
|
import 'package:cwtch/models/profilelist.dart';
|
||||||
import 'package:cwtch/models/profileservers.dart';
|
import 'package:cwtch/models/profileservers.dart';
|
||||||
|
import 'package:cwtch/models/remoteserver.dart';
|
||||||
import 'package:cwtch/models/servers.dart';
|
import 'package:cwtch/models/servers.dart';
|
||||||
import 'package:cwtch/notification_manager.dart';
|
import 'package:cwtch/notification_manager.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
@ -197,7 +198,8 @@ class CwtchNotifier {
|
||||||
var senderHandle = data['RemotePeer'];
|
var senderHandle = data['RemotePeer'];
|
||||||
var senderImage = data['Picture'];
|
var senderImage = data['Picture'];
|
||||||
var timestampSent = DateTime.tryParse(data['TimestampSent'])!;
|
var timestampSent = DateTime.tryParse(data['TimestampSent'])!;
|
||||||
var currentTotal = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.totalMessages;
|
var contact = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier);
|
||||||
|
var currentTotal = contact!.totalMessages;
|
||||||
var isAuto = data['Auto'] == "true";
|
var isAuto = data['Auto'] == "true";
|
||||||
String? contenthash = data['ContentHash'];
|
String? contenthash = data['ContentHash'];
|
||||||
var selectedConversation = appState.selectedProfile == data["ProfileOnion"] && appState.selectedConversation == identifier;
|
var selectedConversation = appState.selectedProfile == data["ProfileOnion"] && appState.selectedConversation == identifier;
|
||||||
|
@ -218,6 +220,8 @@ class CwtchNotifier {
|
||||||
|
|
||||||
notificationManager.notify("New Message From Group!");
|
notificationManager.notify("New Message From Group!");
|
||||||
}
|
}
|
||||||
|
RemoteServerInfoState? server = profileCN.getProfile(data["ProfileOnion"])?.serverList.getServer(contact.server);
|
||||||
|
server?.updateSyncProgressFor(timestampSent);
|
||||||
} else {
|
} else {
|
||||||
// This is dealt with by IndexedAcknowledgment
|
// This is dealt with by IndexedAcknowledgment
|
||||||
EnvironmentConfig.debugLog("new message from group from yourself - this should not happen");
|
EnvironmentConfig.debugLog("new message from group from yourself - this should not happen");
|
||||||
|
|
|
@ -51,13 +51,14 @@ class Flwtch extends StatefulWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FlwtchState extends State<Flwtch> with WindowListener, WidgetsBindingObserver {
|
class FlwtchState extends State<Flwtch> with WindowListener {
|
||||||
final TextStyle biggerFont = const TextStyle(fontSize: 18);
|
final TextStyle biggerFont = const TextStyle(fontSize: 18);
|
||||||
late Cwtch cwtch;
|
late Cwtch cwtch;
|
||||||
late ProfileListState profs;
|
late ProfileListState profs;
|
||||||
final MethodChannel notificationClickChannel = MethodChannel('im.cwtch.flwtch/notificationClickHandler');
|
final MethodChannel notificationClickChannel = MethodChannel('im.cwtch.flwtch/notificationClickHandler');
|
||||||
final MethodChannel shutdownMethodChannel = MethodChannel('im.cwtch.flwtch/shutdownClickHandler');
|
final MethodChannel shutdownMethodChannel = MethodChannel('im.cwtch.flwtch/shutdownClickHandler');
|
||||||
final MethodChannel shutdownLinuxMethodChannel = MethodChannel('im.cwtch.linux.shutdown');
|
final MethodChannel shutdownLinuxMethodChannel = MethodChannel('im.cwtch.linux.shutdown');
|
||||||
|
|
||||||
final GlobalKey<NavigatorState> navKey = GlobalKey<NavigatorState>();
|
final GlobalKey<NavigatorState> navKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
Future<dynamic> shutdownDirect(MethodCall call) {
|
Future<dynamic> shutdownDirect(MethodCall call) {
|
||||||
|
@ -186,7 +187,6 @@ class FlwtchState extends State<Flwtch> with WindowListener, WidgetsBindingObser
|
||||||
// coder beware: args["RemotePeer"] is actually a handle, and could be eg a groupID
|
// coder beware: args["RemotePeer"] is actually a handle, and could be eg a groupID
|
||||||
Future<void> _externalNotificationClicked(MethodCall call) async {
|
Future<void> _externalNotificationClicked(MethodCall call) async {
|
||||||
var args = jsonDecode(call.arguments);
|
var args = jsonDecode(call.arguments);
|
||||||
|
|
||||||
var profile = profs.getProfile(args["ProfileOnion"])!;
|
var profile = profs.getProfile(args["ProfileOnion"])!;
|
||||||
var convo = profile.contactList.getContact(args["Handle"])!;
|
var convo = profile.contactList.getContact(args["Handle"])!;
|
||||||
Provider.of<AppState>(navKey.currentContext!, listen: false).initialScrollIndex = convo.unreadMessages;
|
Provider.of<AppState>(navKey.currentContext!, listen: false).initialScrollIndex = convo.unreadMessages;
|
||||||
|
@ -231,7 +231,6 @@ class FlwtchState extends State<Flwtch> with WindowListener, WidgetsBindingObser
|
||||||
globalAppState.focus = false;
|
globalAppState.focus = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
cwtch.Shutdown();
|
cwtch.Shutdown();
|
||||||
|
|
|
@ -211,6 +211,7 @@ class ContactInfoState extends ChangeNotifier {
|
||||||
newMarker++;
|
newMarker++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._lastMessageTime = timestamp;
|
||||||
this.messageCache.addNew(profileOnion, identifier, messageID, timestamp, senderHandle, senderImage, isAuto, data, contenthash);
|
this.messageCache.addNew(profileOnion, identifier, messageID, timestamp, senderHandle, senderImage, isAuto, data, contenthash);
|
||||||
this.totalMessages += 1;
|
this.totalMessages += 1;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:cwtch/models/remoteserver.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
import 'contact.dart';
|
import 'contact.dart';
|
||||||
|
@ -72,7 +73,7 @@ class ProfileInfoState extends ChangeNotifier {
|
||||||
List<dynamic> servers = jsonDecode(serversJson);
|
List<dynamic> servers = jsonDecode(serversJson);
|
||||||
this._servers.replace(servers.map((server) {
|
this._servers.replace(servers.map((server) {
|
||||||
// TODO Keys...
|
// TODO Keys...
|
||||||
return RemoteServerInfoState(onion: server["onion"], identifier: server["identifier"], description: server["description"], status: server["status"]);
|
return RemoteServerInfoState(server["onion"], server["identifier"], server["description"], server["status"]);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this._contacts.contacts.forEach((contact) {
|
this._contacts.contacts.forEach((contact) {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:cwtch/models/remoteserver.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'contact.dart';
|
import 'contact.dart';
|
||||||
|
@ -33,12 +34,17 @@ class ProfileServerListState extends ChangeNotifier {
|
||||||
// return -1 = a first in list
|
// return -1 = a first in list
|
||||||
// return 1 = b first in list
|
// return 1 = b first in list
|
||||||
|
|
||||||
// online v offline
|
// online v syncing v offline
|
||||||
if (a.status == "Synced" && b.status != "Synced") {
|
if (a.status == "Synced" && b.status != "Synced") {
|
||||||
return -1;
|
return -1;
|
||||||
} else if (a.status != "Synced" && b.status == "Synced") {
|
} else if (a.status != "Synced" && b.status == "Synced") {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
if (a.status == "Authenticated" && b.status != "Authenticated") {
|
||||||
|
return -1;
|
||||||
|
} else if (a.status != "Authenticated" && b.status == "Authenticated") {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
// num of groups
|
// num of groups
|
||||||
if (a.groups.length > b.groups.length) {
|
if (a.groups.length > b.groups.length) {
|
||||||
|
@ -65,30 +71,3 @@ class ProfileServerListState extends ChangeNotifier {
|
||||||
List<RemoteServerInfoState> get servers => _servers.sublist(0); //todo: copy?? dont want caller able to bypass changenotifier
|
List<RemoteServerInfoState> get servers => _servers.sublist(0); //todo: copy?? dont want caller able to bypass changenotifier
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class RemoteServerInfoState extends ChangeNotifier {
|
|
||||||
final String onion;
|
|
||||||
final int identifier;
|
|
||||||
String status;
|
|
||||||
String description;
|
|
||||||
List<ContactInfoState> _groups = [];
|
|
||||||
|
|
||||||
RemoteServerInfoState({required this.onion, required this.identifier, required this.description, required this.status});
|
|
||||||
|
|
||||||
void updateDescription(String newDescription) {
|
|
||||||
this.description = newDescription;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
void clearGroups() {
|
|
||||||
_groups = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
void addGroup(ContactInfoState group) {
|
|
||||||
_groups.add(group);
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<ContactInfoState> get groups => _groups.sublist(0); //todo: copy?? dont want caller able to bypass changenotifier
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
|
||||||
|
import 'contact.dart';
|
||||||
|
|
||||||
|
class RemoteServerInfoState extends ChangeNotifier {
|
||||||
|
final String onion;
|
||||||
|
final int identifier;
|
||||||
|
String _status;
|
||||||
|
String description;
|
||||||
|
List<ContactInfoState> _groups = [];
|
||||||
|
|
||||||
|
double syncProgress = 0;
|
||||||
|
DateTime lastPreSyncMessagTime = new DateTime(2020);
|
||||||
|
|
||||||
|
RemoteServerInfoState(this.onion, this.identifier, this.description, this._status);
|
||||||
|
|
||||||
|
void updateDescription(String newDescription) {
|
||||||
|
this.description = newDescription;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearGroups() {
|
||||||
|
_groups = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
void addGroup(ContactInfoState group) {
|
||||||
|
_groups.add(group);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
String get status => _status;
|
||||||
|
set status(String newStatus) {
|
||||||
|
_status = newStatus;
|
||||||
|
if (status == "Authenticated") {
|
||||||
|
// syncing, set lastPreSyncMessageTime
|
||||||
|
_groups.forEach((g) {
|
||||||
|
if(g.lastMessageTime.isAfter(lastPreSyncMessagTime)) {
|
||||||
|
lastPreSyncMessagTime = g.lastMessageTime;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateSyncProgressFor point takes a message's time, and updates the server sync progress,
|
||||||
|
// based on that point in time between the precalculated lastPreSyncMessagTime and Now
|
||||||
|
void updateSyncProgressFor(DateTime point) {
|
||||||
|
var range = lastPreSyncMessagTime.difference(DateTime.now());
|
||||||
|
var pointFromStart = lastPreSyncMessagTime.difference(point);
|
||||||
|
syncProgress = pointFromStart.inSeconds / range.inSeconds;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ContactInfoState> get groups => _groups.sublist(0); //todo: copy?? dont want caller able to bypass changenotifier
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ import 'dart:convert';
|
||||||
|
|
||||||
import 'package:cwtch/cwtch_icons_icons.dart';
|
import 'package:cwtch/cwtch_icons_icons.dart';
|
||||||
import 'package:cwtch/models/profile.dart';
|
import 'package:cwtch/models/profile.dart';
|
||||||
|
import 'package:cwtch/models/remoteserver.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:cwtch/errorHandler.dart';
|
import 'package:cwtch/errorHandler.dart';
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'package:cwtch/models/appstate.dart';
|
||||||
import 'package:cwtch/models/contact.dart';
|
import 'package:cwtch/models/contact.dart';
|
||||||
import 'package:cwtch/models/contactlist.dart';
|
import 'package:cwtch/models/contactlist.dart';
|
||||||
import 'package:cwtch/models/profile.dart';
|
import 'package:cwtch/models/profile.dart';
|
||||||
|
import 'package:cwtch/models/profilelist.dart';
|
||||||
import 'package:cwtch/views/profileserversview.dart';
|
import 'package:cwtch/views/profileserversview.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:cwtch/widgets/contactrow.dart';
|
import 'package:cwtch/widgets/contactrow.dart';
|
||||||
|
@ -154,8 +155,15 @@ class _ContactsViewState extends State<ContactsView> {
|
||||||
|
|
||||||
Widget _buildContactList() {
|
Widget _buildContactList() {
|
||||||
final tiles = Provider.of<ContactListState>(context).filteredList().map((ContactInfoState contact) {
|
final tiles = Provider.of<ContactListState>(context).filteredList().map((ContactInfoState contact) {
|
||||||
return ChangeNotifierProvider<ContactInfoState>.value(key: ValueKey(contact.profileOnion + "" + contact.onion), value: contact, builder: (_, __) => RepaintBoundary(child: ContactRow()));
|
return MultiProvider(
|
||||||
|
providers: [
|
||||||
|
ChangeNotifierProvider.value(value: contact),
|
||||||
|
ChangeNotifierProvider.value(value: Provider.of<ProfileInfoState>(context).serverList),
|
||||||
|
],
|
||||||
|
builder: (context, child) => RepaintBoundary(child: ContactRow()),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
final divided = ListTile.divideTiles(
|
final divided = ListTile.divideTiles(
|
||||||
context: context,
|
context: context,
|
||||||
tiles: tiles,
|
tiles: tiles,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:cwtch/models/profile.dart';
|
import 'package:cwtch/models/profile.dart';
|
||||||
import 'package:cwtch/models/profileservers.dart';
|
import 'package:cwtch/models/profileservers.dart';
|
||||||
|
import 'package:cwtch/models/remoteserver.dart';
|
||||||
import 'package:cwtch/models/servers.dart';
|
import 'package:cwtch/models/servers.dart';
|
||||||
import 'package:cwtch/widgets/remoteserverrow.dart';
|
import 'package:cwtch/widgets/remoteserverrow.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'package:cwtch/cwtch_icons_icons.dart';
|
||||||
import 'package:cwtch/models/contact.dart';
|
import 'package:cwtch/models/contact.dart';
|
||||||
import 'package:cwtch/models/profile.dart';
|
import 'package:cwtch/models/profile.dart';
|
||||||
import 'package:cwtch/models/profileservers.dart';
|
import 'package:cwtch/models/profileservers.dart';
|
||||||
|
import 'package:cwtch/models/remoteserver.dart';
|
||||||
import 'package:cwtch/models/servers.dart';
|
import 'package:cwtch/models/servers.dart';
|
||||||
import 'package:cwtch/widgets/buttontextfield.dart';
|
import 'package:cwtch/widgets/buttontextfield.dart';
|
||||||
import 'package:cwtch/widgets/contactrow.dart';
|
import 'package:cwtch/widgets/contactrow.dart';
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'dart:io';
|
||||||
import 'package:cwtch/models/appstate.dart';
|
import 'package:cwtch/models/appstate.dart';
|
||||||
import 'package:cwtch/models/contact.dart';
|
import 'package:cwtch/models/contact.dart';
|
||||||
import 'package:cwtch/models/profile.dart';
|
import 'package:cwtch/models/profile.dart';
|
||||||
|
import 'package:cwtch/models/profileservers.dart';
|
||||||
import 'package:cwtch/views/contactsview.dart';
|
import 'package:cwtch/views/contactsview.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/painting.dart';
|
import 'package:flutter/painting.dart';
|
||||||
|
@ -66,6 +67,7 @@ class _ContactRowState extends State<ContactRow> {
|
||||||
visible: contact.isGroup && contact.status == "Authenticated",
|
visible: contact.isGroup && contact.status == "Authenticated",
|
||||||
child: LinearProgressIndicator(
|
child: LinearProgressIndicator(
|
||||||
color: Provider.of<Settings>(context).theme.defaultButtonActiveColor,
|
color: Provider.of<Settings>(context).theme.defaultButtonActiveColor,
|
||||||
|
value: Provider.of<ProfileInfoState>(context).serverList.getServer(contact.server)?.syncProgress,
|
||||||
)),
|
)),
|
||||||
Visibility(
|
Visibility(
|
||||||
visible: !Provider.of<Settings>(context).streamerMode,
|
visible: !Provider.of<Settings>(context).streamerMode,
|
||||||
|
|
|
@ -91,7 +91,7 @@ class _ProfileRowState extends State<ProfileRow> {
|
||||||
return MultiProvider(
|
return MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
ChangeNotifierProvider<ProfileInfoState>.value(value: profile),
|
ChangeNotifierProvider<ProfileInfoState>.value(value: profile),
|
||||||
ChangeNotifierProvider<ContactListState>.value(value: profile.contactList),
|
ChangeNotifierProvider<ContactListState>.value(value: profile.contactList)
|
||||||
],
|
],
|
||||||
builder: (innercontext, widget) {
|
builder: (innercontext, widget) {
|
||||||
var appState = Provider.of<AppState>(context);
|
var appState = Provider.of<AppState>(context);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:cwtch/main.dart';
|
import 'package:cwtch/main.dart';
|
||||||
import 'package:cwtch/models/profile.dart';
|
import 'package:cwtch/models/profile.dart';
|
||||||
import 'package:cwtch/models/profileservers.dart';
|
import 'package:cwtch/models/profileservers.dart';
|
||||||
|
import 'package:cwtch/models/remoteserver.dart';
|
||||||
import 'package:cwtch/models/servers.dart';
|
import 'package:cwtch/models/servers.dart';
|
||||||
import 'package:cwtch/views/addeditservers.dart';
|
import 'package:cwtch/views/addeditservers.dart';
|
||||||
import 'package:cwtch/views/remoteserverview.dart';
|
import 'package:cwtch/views/remoteserverview.dart';
|
||||||
|
@ -55,7 +56,13 @@ class _RemoteServerRowState extends State<RemoteServerRow> {
|
||||||
softWrap: true,
|
softWrap: true,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: TextStyle(color: running ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor : Provider.of<Settings>(context).theme.portraitOfflineBorderColor),
|
style: TextStyle(color: running ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor : Provider.of<Settings>(context).theme.portraitOfflineBorderColor),
|
||||||
)))
|
))),
|
||||||
|
Visibility(
|
||||||
|
visible: server.status == "Authenticated",
|
||||||
|
child: LinearProgressIndicator(
|
||||||
|
color: Provider.of<Settings>(context).theme.defaultButtonActiveColor,
|
||||||
|
value: server.syncProgress,
|
||||||
|
)),
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
]),
|
]),
|
||||||
|
|
|
@ -132,14 +132,15 @@ static void my_application_activate(GApplication* application) {
|
||||||
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
|
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
|
||||||
|
|
||||||
|
|
||||||
|
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
|
||||||
|
|
||||||
// Create a specific channel for shutting down cwtch when the close button is triggered
|
// Create a specific channel for shutting down cwtch when the close button is triggered
|
||||||
// We have registered the "destroy" handle above for this reason
|
// We have registered the "destroy" handle above for this reason
|
||||||
FlEngine *engine = fl_view_get_engine(view);
|
FlEngine *engine = fl_view_get_engine(view);
|
||||||
g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
|
g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
|
||||||
g_autoptr(FlBinaryMessenger) messenger = fl_engine_get_binary_messenger(engine);
|
g_autoptr(FlBinaryMessenger) messenger = fl_engine_get_binary_messenger(engine);
|
||||||
channel =
|
channel = fl_method_channel_new(messenger, "im.cwtch.linux.shutdown", FL_METHOD_CODEC(codec));
|
||||||
fl_method_channel_new(messenger,
|
|
||||||
"im.cwtch.linux.shutdown", FL_METHOD_CODEC(codec));
|
|
||||||
|
|
||||||
|
|
||||||
gtk_widget_grab_focus(GTK_WIDGET(view));
|
gtk_widget_grab_focus(GTK_WIDGET(view));
|
||||||
|
|
|
@ -41,7 +41,7 @@ dependencies:
|
||||||
scrollable_positioned_list: ^0.2.0-nullsafety.0
|
scrollable_positioned_list: ^0.2.0-nullsafety.0
|
||||||
file_picker: ^4.3.2
|
file_picker: ^4.3.2
|
||||||
file_picker_desktop: ^1.1.0
|
file_picker_desktop: ^1.1.0
|
||||||
url_launcher: ^6.0.12
|
url_launcher: ^6.0.18
|
||||||
desktoasts: ^0.0.2
|
desktoasts: ^0.0.2
|
||||||
window_manager: ^0.1.4
|
window_manager: ^0.1.4
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue