android service and notifications #167
36
.drone.yml
36
.drone.yml
|
@ -53,13 +53,18 @@ steps:
|
|||
- name: deps
|
||||
path: /root/.pub-cache
|
||||
commands:
|
||||
- flutter build linux
|
||||
- flutter build linux --dart-define BUILD_VER=`cat VERSION` --dart-define BUILD_DATE=`cat BUILDDATE`
|
||||
- mkdir deploy/linux
|
||||
- cp -r build/linux/x64/release/bundle/* deploy/linux
|
||||
- cp linux/cwtch.desktop deploy/linux
|
||||
- cp linux/cwtch.png deploy/linux
|
||||
- cp linux/libCwtch.so deploy/linux/lib/
|
||||
- cp /sdks/flutter/bin/cache/artifacts/engine/linux-x64/icudtl.dat deploy/linux
|
||||
- cp tor deploy/linux
|
||||
- cd deploy
|
||||
- mv linux cwtch
|
||||
- tar -czf cwtch-`cat ../VERSION`.tar.gz cwtch
|
||||
- rm -r cwtch
|
||||
|
||||
- name: build-android
|
||||
image: cirrusci/flutter:dev
|
||||
|
@ -77,13 +82,12 @@ steps:
|
|||
- echo $upload_jks_file_b64 > upload-keystore.jks.b64
|
||||
- base64 -i --decode upload-keystore.jks.b64 > android/app/upload-keystore.jks
|
||||
- sed -i "s/%jks-password%/$upload_jks_pass/g" android/key.properties
|
||||
- flutter build appbundle
|
||||
- flutter build appbundle --dart-define BUILD_VER=`cat VERSION` --dart-define BUILD_DATE=`cat BUILDDATE`
|
||||
# cant do debug for final release, this is just a stop gap
|
||||
- flutter build apk
|
||||
- flutter build apk --dart-define BUILD_VER=`cat VERSION` --dart-define BUILD_DATE=`cat BUILDDATE`
|
||||
# or build apk --split-per-abi ?
|
||||
- mkdir deploy/android
|
||||
- cp build/app/outputs/bundle/release/app-release.aab deploy/android
|
||||
- cp build/app/outputs/apk/release/app-release.apk deploy/android
|
||||
- cp build/app/outputs/bundle/release/app-release.aab deploy/
|
||||
- cp build/app/outputs/apk/release/app-release.apk deploy/
|
||||
#- cp build/app/outputs/flutter-apk/app-debug.apk deploy/android
|
||||
|
||||
- name: widget-tests
|
||||
|
@ -179,7 +183,7 @@ clone:
|
|||
|
||||
steps:
|
||||
- name: clone
|
||||
image: openpriv/flutter-desktop:windows-sdk30-fdev2.2rc
|
||||
image: openpriv/flutter-desktop:windows-sdk30-fdev2.3rc
|
||||
environment:
|
||||
buildbot_key_b64:
|
||||
from_secret: buildbot_key_b64
|
||||
|
@ -197,7 +201,7 @@ steps:
|
|||
- git checkout $DRONE_COMMIT
|
||||
|
||||
- name: fetch
|
||||
image: openpriv/flutter-desktop:windows-sdk30-fdev2.2rc
|
||||
image: openpriv/flutter-desktop:windows-sdk30-fdev2.3rc
|
||||
commands:
|
||||
- powershell -command "Invoke-WebRequest -Uri https://www.torproject.org/dist/torbrowser/10.0.16/tor-win32-0.4.5.7.zip -OutFile tor.zip"
|
||||
- powershell -command "if ((Get-FileHash tor.zip -Algorithm sha512).Hash -ne '2b7d683f036d0fec149f1d2bdfcf5b7ef4c337005a2b685c056b00047fdb2b57d4c25b8559ad7ef5c7a030b273934be82a9f83ef6e391f5d7d13d8d6c83e8048' ) { Write-Error 'tor.zip sha512sum mismatch' }"
|
||||
|
@ -206,19 +210,23 @@ steps:
|
|||
- .\fetch-libcwtch-go.ps1
|
||||
-
|
||||
- name: build-windows
|
||||
image: openpriv/flutter-desktop:windows-beta
|
||||
image: openpriv/flutter-desktop:windows-sdk30-fdev2.3rc
|
||||
commands:
|
||||
- flutter pub get
|
||||
- flutter build windows
|
||||
# flwtch-`cat VERSION`-`cat BUILDDATE`
|
||||
- $Env:buildname = 'flwtch-win-'
|
||||
- $Env:buildname += type .\VERSION
|
||||
- $Env:version += type .\VERSION
|
||||
- $Env:buildname += $Env:version
|
||||
- $Env:buildname += '-'
|
||||
- $Env:buildname += type .\BUILDDATE
|
||||
- $Env:builddate += type .\BUILDDATE
|
||||
- $Env:buildname += $Env:builddate
|
||||
- $Env:builddir += $Env:buildname
|
||||
- $Env:zip = 'flwtch.zip'
|
||||
- $Env:zip = 'cwtch-'
|
||||
- $Env:zip += $Env:version
|
||||
- $Env:zip += '.zip'
|
||||
- $Env:sha = $Env:zip
|
||||
- $Env:sha += '.sha512'
|
||||
- flutter build windows --dart-define BUILD_VER=$Env:version --dart-define BUILD_DATE=$Env:builddate
|
||||
- mkdir deploy
|
||||
- move build\\windows\\runner\\Release $Env:builddir
|
||||
- copy windows\libCwtch.dll $Env:builddir
|
||||
|
@ -230,7 +238,7 @@ steps:
|
|||
- move $Env:sha deploy\$Env:builddir
|
||||
|
||||
- name: deploy-windows
|
||||
image: openpriv/flutter-desktop:windows-sdk30-fdev2.2rc
|
||||
image: openpriv/flutter-desktop:windows-sdk30-fdev2.3rc
|
||||
when:
|
||||
event: push
|
||||
status: [ success ]
|
||||
|
|
|
@ -1 +1 @@
|
|||
v0.0.2-43-ga98b5de-2021-05-28-21-21
|
||||
v0.0.2-45-g4f625c7-2021-05-31-23-30
|
|
@ -0,0 +1,6 @@
|
|||
const dev_version = "development";
|
||||
|
||||
class EnvironmentConfig {
|
||||
static const BUILD_VER = String.fromEnvironment('BUILD_VER', defaultValue: dev_version);
|
||||
static const BUILD_DATE = String.fromEnvironment('BUILD_DATE', defaultValue: "now");
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
import 'dart:convert';
|
||||
import 'package:cwtch/models/servers.dart';
|
||||
import 'package:cwtch/notification_manager.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
|
@ -40,18 +41,26 @@ class CwtchNotifier {
|
|||
imagePath: data["picture"],
|
||||
isBlocked: data["authorization"] == "blocked",
|
||||
isInvitation: data["authorization"] == "unknown",
|
||||
savePeerHistory: data["saveConversationHistory"],
|
||||
savePeerHistory: data["saveConversationHistory"] == null ? "DeleteHistoryConfirmed" : data["saveConversationHistory"],
|
||||
numMessages: int.parse(data["numMessages"]),
|
||||
numUnread: int.parse(data["unread"]),
|
||||
isGroup: data["isGroup"],
|
||||
isGroup: data["isGroup"] == true,
|
||||
server: data["groupServer"],
|
||||
lastMessageTime: DateTime.now(), //show at the top of the contact list even if no messages yet
|
||||
));
|
||||
break;
|
||||
case "GroupCreated":
|
||||
|
||||
// Retrieve Server Status from Cache...
|
||||
String status = "";
|
||||
ServerInfoState? serverInfoState = profileCN.getProfile(data["ProfileOnion"])?.serverList.getServer(data["GroupServer"]);
|
||||
if (serverInfoState != null) {
|
||||
status = serverInfoState.status;
|
||||
}
|
||||
|
||||
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"]) == null) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], data["GroupID"],
|
||||
isInvitation: false, imagePath: data["PicturePath"], nickname: data["GroupName"], server: data["GroupServer"], isGroup: true, lastMessageTime: DateTime.now()));
|
||||
isInvitation: false, imagePath: data["PicturePath"], nickname: data["GroupName"], status: status, server: data["GroupServer"], isGroup: true, lastMessageTime: DateTime.now()));
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["GroupID"], DateTime.now());
|
||||
}
|
||||
break;
|
||||
|
@ -175,9 +184,23 @@ class CwtchNotifier {
|
|||
String inviteJson = new String.fromCharCodes(base64Decode(invite.substring(5)));
|
||||
dynamic groupInvite = jsonDecode(inviteJson);
|
||||
print("new group invite: $groupInvite");
|
||||
|
||||
// Retrieve Server Status from Cache...
|
||||
String status = "";
|
||||
ServerInfoState? serverInfoState = profileCN.getProfile(data["ProfileOnion"])?.serverList.getServer(groupInvite["ServerHost"]);
|
||||
if (serverInfoState != null) {
|
||||
status = serverInfoState.status;
|
||||
}
|
||||
|
||||
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(groupInvite["GroupID"]) == null) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], groupInvite["GroupID"],
|
||||
isInvitation: true, imagePath: data["PicturePath"], nickname: groupInvite["GroupName"], server: groupInvite["ServerHost"], isGroup: true, lastMessageTime: DateTime.now()));
|
||||
isInvitation: true,
|
||||
imagePath: data["PicturePath"],
|
||||
nickname: groupInvite["GroupName"],
|
||||
server: groupInvite["ServerHost"],
|
||||
status: status,
|
||||
isGroup: true,
|
||||
lastMessageTime: DateTime.now()));
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(groupInvite["GroupID"], DateTime.now());
|
||||
}
|
||||
}
|
||||
|
@ -189,6 +212,8 @@ class CwtchNotifier {
|
|||
break;
|
||||
case "ServerStateChange":
|
||||
print("server state change: $data");
|
||||
// Update the Server Cache
|
||||
profileCN.getProfile(data["ProfileOnion"])?.updateServerStatusCache(data["GroupServer"], data["ConnectionState"]);
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.contacts.forEach((contact) {
|
||||
if (contact.isGroup == true && contact.server == data["GroupServer"]) {
|
||||
contact.status = data["ConnectionState"];
|
||||
|
@ -205,6 +230,16 @@ class CwtchNotifier {
|
|||
print("unhandled set group attribute event: $type $data");
|
||||
}
|
||||
break;
|
||||
case "NewRetValMessageFromPeer":
|
||||
if (data["Path"] == "name") {
|
||||
// Update locally on the UI...
|
||||
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"]) != null) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.nickname = data["Data"];
|
||||
}
|
||||
} else {
|
||||
print("unhandled peer attribute event: $type $data");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
print("unhandled event: $type $data");
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ import 'package:path/path.dart' as path;
|
|||
import 'package:ffi/ffi.dart';
|
||||
import 'package:cwtch/cwtch/cwtch.dart';
|
||||
|
||||
import '../config.dart';
|
||||
|
||||
/////////////////////
|
||||
/// Cwtch API ///
|
||||
/////////////////////
|
||||
|
@ -84,7 +86,10 @@ class CwtchFfi implements Cwtch {
|
|||
home = (envVars['UserProfile'])!;
|
||||
bundledTor = "Tor\\Tor\\tor.exe";
|
||||
}
|
||||
var cwtchDir = path.join(home, ".cwtch/dev/");
|
||||
var cwtchDir = path.join(home, ".cwtch");
|
||||
if (EnvironmentConfig.BUILD_VER == dev_version) {
|
||||
cwtchDir = path.join(cwtchDir, "dev");
|
||||
}
|
||||
print("cwtchDir $cwtchDir");
|
||||
|
||||
var startCwtchC = library.lookup<NativeFunction<start_cwtch_function>>("c_StartCwtch");
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cwtch/config.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'dart:async';
|
||||
|
@ -44,7 +45,10 @@ class CwtchGomobile implements Cwtch {
|
|||
// ignore: non_constant_identifier_names
|
||||
Future<void> Start() async {
|
||||
print("gomobile.dart: Start()...");
|
||||
var cwtchDir = path.join((await androidHomeDirectory).path, ".cwtch/dev/");
|
||||
var cwtchDir = path.join((await androidHomeDirectory).path, ".cwtch");
|
||||
if (EnvironmentConfig.BUILD_VER == dev_version) {
|
||||
cwtchDir = path.join(cwtchDir, "dev");
|
||||
}
|
||||
String torPath = path.join(await androidLibraryDir, "libtor.so");
|
||||
print("gomobile.dart: Start invokeMethod Start($cwtchDir, $torPath)...");
|
||||
cwtchPlatform.invokeMethod("Start", {"appDir": cwtchDir, "torPath": torPath});
|
||||
|
|
|
@ -29,6 +29,7 @@ class ErrorHandler extends ChangeNotifier {
|
|||
break;
|
||||
case importBundleErrorPrefix:
|
||||
handleImportBundleError(errorType);
|
||||
break;
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
|
@ -49,6 +50,7 @@ class ErrorHandler extends ChangeNotifier {
|
|||
break;
|
||||
case successErrorType:
|
||||
explicitAddContactSuccess = true;
|
||||
importBundleSuccess = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,10 +75,12 @@
|
|||
"invitation": "Einladung",
|
||||
"invitationLabel": "Einladung",
|
||||
"inviteBtn": "Einladen",
|
||||
"inviteToGroup": "",
|
||||
"inviteToGroupLabel": "In die Gruppe einladen",
|
||||
"joinGroup": "Gruppe beitreten",
|
||||
"joinGroupTab": "Einer Gruppe beitreten",
|
||||
"largeTextLabel": "Groß",
|
||||
"leaveGroup": "",
|
||||
"listsBtn": "Listen",
|
||||
"loadingTor": "Tor wird geladen...",
|
||||
"localeDe": "Deutsche",
|
||||
|
@ -95,6 +97,7 @@
|
|||
"newBulletinLabel": "Neue Meldung",
|
||||
"newConnectionPaneTitle": "Neue Verbindung",
|
||||
"newGroupBtn": "Neue Gruppe anlegen",
|
||||
"newPassword": "",
|
||||
"newProfile": "Neues Profil",
|
||||
"noPasswordWarning": "Wenn für dieses Konto kein Passwort verwendet wird, bedeutet dies, dass alle lokal gespeicherten Daten nicht verschlüsselt werden.",
|
||||
"password": "Passwort",
|
||||
|
@ -116,6 +119,7 @@
|
|||
"puzzleGameBtn": "Puzzlespiel",
|
||||
"radioNoPassword": "Unverschlüsselt (kein Passwort)",
|
||||
"radioUsePassword": "Passwort",
|
||||
"reallyLeaveThisGroupPrompt": "",
|
||||
"rejectGroupBtn": "Ablehnen",
|
||||
"saveBtn": "Speichern",
|
||||
"savePeerHistory": "Peer-Verlauf speichern",
|
||||
|
@ -153,6 +157,7 @@
|
|||
"versionTor": "Version %1 mit tor %2",
|
||||
"viewGroupMembershipTooltip": "Gruppenmitgliedschaft anzeigen",
|
||||
"viewServerInfo": "",
|
||||
"yesLeave": "",
|
||||
"yourDisplayName": "Ihr Anzeigename",
|
||||
"yourProfiles": "Ihre Profile",
|
||||
"yourServers": "Ihre Server",
|
||||
|
|
|
@ -75,10 +75,12 @@
|
|||
"invitation": "Invitation",
|
||||
"invitationLabel": "Invitation",
|
||||
"inviteBtn": "Invite",
|
||||
"inviteToGroup": "You have been invited to join a group:",
|
||||
"inviteToGroupLabel": "Invite to group",
|
||||
"joinGroup": "Join group",
|
||||
"joinGroupTab": "Join a group",
|
||||
"largeTextLabel": "Large",
|
||||
"leaveGroup": "Leave This Conversation",
|
||||
"listsBtn": "Lists",
|
||||
"loadingTor": "Loading tor...",
|
||||
"localeDe": "Deutsche",
|
||||
|
@ -95,6 +97,7 @@
|
|||
"newBulletinLabel": "New Bulletin",
|
||||
"newConnectionPaneTitle": "New Connection",
|
||||
"newGroupBtn": "Create new group",
|
||||
"newPassword": "New Password",
|
||||
"newProfile": "New Profile",
|
||||
"noPasswordWarning": "Not using a password on this account means that all data stored locally will not be encrypted",
|
||||
"password": "Password",
|
||||
|
@ -103,7 +106,7 @@
|
|||
"passwordChangeError": "Error changing password: Supplied password rejected",
|
||||
"passwordErrorEmpty": "Password cannot be empty",
|
||||
"passwordErrorMatch": "Passwords do not match",
|
||||
"pasteAddressToAddContact": "Paste a cwtch address here to add a new contact.",
|
||||
"pasteAddressToAddContact": "Paste a cwtch address, invitation or key bundle here to add a new conversation",
|
||||
"peerAddress": "Address",
|
||||
"peerBlockedMessage": "Peer is blocked",
|
||||
"peerName": "Name",
|
||||
|
@ -116,6 +119,7 @@
|
|||
"puzzleGameBtn": "Puzzle Game",
|
||||
"radioNoPassword": "Unencrypted (No password)",
|
||||
"radioUsePassword": "Password",
|
||||
"reallyLeaveThisGroupPrompt": "Are you sure you want to leave this conversation? All messages and attributes will be deleted.",
|
||||
"rejectGroupBtn": "Reject",
|
||||
"saveBtn": "Save",
|
||||
"savePeerHistory": "Save Peer History",
|
||||
|
@ -137,12 +141,12 @@
|
|||
"successfullAddedContact": "Successfully added ",
|
||||
"themeDark": "Dark",
|
||||
"themeLight": "Light",
|
||||
"titleManageContacts": "Manage Contacts",
|
||||
"titleManageContacts": "Conversations",
|
||||
"titleManageProfiles": "Manage Cwtch Profiles",
|
||||
"titleManageServers": "Manage Servers",
|
||||
"titlePlaceholder": "title...",
|
||||
"todoPlaceholder": "Todo...",
|
||||
"tooltipAddContact": "Add a new contact",
|
||||
"tooltipAddContact": "Add a new contact or conversation",
|
||||
"tooltipOpenSettings": "Open the settings pane",
|
||||
"tooltipUnlockProfiles": "Unlock encrypted profiles by entering their password.",
|
||||
"unblockBtn": "Unblock Peer",
|
||||
|
@ -153,6 +157,7 @@
|
|||
"versionTor": "Version %1 with tor %2",
|
||||
"viewGroupMembershipTooltip": "View Group Membership",
|
||||
"viewServerInfo": "Server Info",
|
||||
"yesLeave": "Yes, Leave This Conversation",
|
||||
"yourDisplayName": "Your Display Name",
|
||||
"yourProfiles": "Your Profiles",
|
||||
"yourServers": "Your Servers",
|
||||
|
|
|
@ -75,10 +75,12 @@
|
|||
"invitation": "Invitación",
|
||||
"invitationLabel": "Invitación",
|
||||
"inviteBtn": "Invitar",
|
||||
"inviteToGroup": "",
|
||||
"inviteToGroupLabel": "Invitar al grupo",
|
||||
"joinGroup": "Únete al grupo",
|
||||
"joinGroupTab": "Únete a un grupo",
|
||||
"largeTextLabel": "Grande",
|
||||
"leaveGroup": "",
|
||||
"listsBtn": "Listas",
|
||||
"loadingTor": "Cargando tor...",
|
||||
"localeDe": "Alemán",
|
||||
|
@ -95,6 +97,7 @@
|
|||
"newBulletinLabel": "Nuevo Boletín",
|
||||
"newConnectionPaneTitle": "Nueva conexión",
|
||||
"newGroupBtn": "Crear un nuevo grupo de chat",
|
||||
"newPassword": "",
|
||||
"newProfile": "Nuevo perfil",
|
||||
"noPasswordWarning": "No usar una contraseña para esta cuenta significa que los datos almacenados localmente no serán encriptados",
|
||||
"password": "Contraseña",
|
||||
|
@ -116,6 +119,7 @@
|
|||
"puzzleGameBtn": "Juego de rompecabezas",
|
||||
"radioNoPassword": "Sin cifrado (sin contraseña)",
|
||||
"radioUsePassword": "Contraseña",
|
||||
"reallyLeaveThisGroupPrompt": "",
|
||||
"rejectGroupBtn": "Rechazar",
|
||||
"saveBtn": "Guardar",
|
||||
"savePeerHistory": "Guardar el historial con contacto",
|
||||
|
@ -153,6 +157,7 @@
|
|||
"versionTor": "Versión %1 con tor %2",
|
||||
"viewGroupMembershipTooltip": "Ver membresía del grupo",
|
||||
"viewServerInfo": "Información del servidor",
|
||||
"yesLeave": "",
|
||||
"yourDisplayName": "Tu nombre de usuario",
|
||||
"yourProfiles": "Tus perfiles",
|
||||
"yourServers": "Tus servidores",
|
||||
|
|
|
@ -75,10 +75,12 @@
|
|||
"invitation": "",
|
||||
"invitationLabel": "Invitation",
|
||||
"inviteBtn": "Invitation",
|
||||
"inviteToGroup": "",
|
||||
"inviteToGroupLabel": "Inviter quelqu'un",
|
||||
"joinGroup": "",
|
||||
"joinGroupTab": "",
|
||||
"largeTextLabel": "Large",
|
||||
"leaveGroup": "",
|
||||
"listsBtn": "Listes",
|
||||
"loadingTor": "",
|
||||
"localeDe": "",
|
||||
|
@ -95,6 +97,7 @@
|
|||
"newBulletinLabel": "Nouveau bulletin",
|
||||
"newConnectionPaneTitle": "",
|
||||
"newGroupBtn": "Créer un nouveau groupe",
|
||||
"newPassword": "",
|
||||
"newProfile": "",
|
||||
"noPasswordWarning": "",
|
||||
"password": "",
|
||||
|
@ -116,6 +119,7 @@
|
|||
"puzzleGameBtn": "Puzzle",
|
||||
"radioNoPassword": "",
|
||||
"radioUsePassword": "",
|
||||
"reallyLeaveThisGroupPrompt": "",
|
||||
"rejectGroupBtn": "Refuser",
|
||||
"saveBtn": "Sauvegarder",
|
||||
"savePeerHistory": "",
|
||||
|
@ -153,6 +157,7 @@
|
|||
"versionTor": "",
|
||||
"viewGroupMembershipTooltip": "",
|
||||
"viewServerInfo": "",
|
||||
"yesLeave": "",
|
||||
"yourDisplayName": "",
|
||||
"yourProfiles": "",
|
||||
"yourServers": "",
|
||||
|
|
|
@ -75,10 +75,12 @@
|
|||
"invitation": "Invito",
|
||||
"invitationLabel": "Invito",
|
||||
"inviteBtn": "Invitare",
|
||||
"inviteToGroup": "",
|
||||
"inviteToGroupLabel": "Invitare nel gruppo",
|
||||
"joinGroup": "Unisciti al gruppo",
|
||||
"joinGroupTab": "Unisciti a un gruppo",
|
||||
"largeTextLabel": "Grande",
|
||||
"leaveGroup": "",
|
||||
"listsBtn": "Liste",
|
||||
"loadingTor": "Caricamento di tor...",
|
||||
"localeDe": "Tedesco",
|
||||
|
@ -95,6 +97,7 @@
|
|||
"newBulletinLabel": "Nuovo bollettino",
|
||||
"newConnectionPaneTitle": "Nuova connessione",
|
||||
"newGroupBtn": "Crea un nuovo gruppo",
|
||||
"newPassword": "",
|
||||
"newProfile": "Nuovo profilo",
|
||||
"noPasswordWarning": "Non utilizzare una password su questo account significa che tutti i dati archiviati localmente non verranno criptati",
|
||||
"password": "Password",
|
||||
|
@ -116,6 +119,7 @@
|
|||
"puzzleGameBtn": "Gioco di puzzle",
|
||||
"radioNoPassword": "Non criptato (senza password)",
|
||||
"radioUsePassword": "Password",
|
||||
"reallyLeaveThisGroupPrompt": "",
|
||||
"rejectGroupBtn": "Rifiuta",
|
||||
"saveBtn": "Salva",
|
||||
"savePeerHistory": "Salva cronologia peer",
|
||||
|
@ -153,6 +157,7 @@
|
|||
"versionTor": "Versione %1 con tor %2",
|
||||
"viewGroupMembershipTooltip": "Visualizza i membri del gruppo",
|
||||
"viewServerInfo": "Informazioni sul server",
|
||||
"yesLeave": "",
|
||||
"yourDisplayName": "Il tuo nome visualizzato",
|
||||
"yourProfiles": "I tuoi profili",
|
||||
"yourServers": "I tuoi server",
|
||||
|
|
|
@ -75,10 +75,12 @@
|
|||
"invitation": "",
|
||||
"invitationLabel": "Convite",
|
||||
"inviteBtn": "Convidar",
|
||||
"inviteToGroup": "",
|
||||
"inviteToGroupLabel": "Convidar ao grupo",
|
||||
"joinGroup": "",
|
||||
"joinGroupTab": "",
|
||||
"largeTextLabel": "Grande",
|
||||
"leaveGroup": "",
|
||||
"listsBtn": "Listas",
|
||||
"loadingTor": "",
|
||||
"localeDe": "",
|
||||
|
@ -95,6 +97,7 @@
|
|||
"newBulletinLabel": "Novo Boletim",
|
||||
"newConnectionPaneTitle": "",
|
||||
"newGroupBtn": "Criar novo grupo",
|
||||
"newPassword": "",
|
||||
"newProfile": "",
|
||||
"noPasswordWarning": "",
|
||||
"password": "",
|
||||
|
@ -116,6 +119,7 @@
|
|||
"puzzleGameBtn": "Jogo de Adivinhação",
|
||||
"radioNoPassword": "",
|
||||
"radioUsePassword": "",
|
||||
"reallyLeaveThisGroupPrompt": "",
|
||||
"rejectGroupBtn": "Recusar",
|
||||
"saveBtn": "Salvar",
|
||||
"savePeerHistory": "",
|
||||
|
@ -153,6 +157,7 @@
|
|||
"versionTor": "",
|
||||
"viewGroupMembershipTooltip": "",
|
||||
"viewServerInfo": "",
|
||||
"yesLeave": "",
|
||||
"yourDisplayName": "",
|
||||
"yourProfiles": "",
|
||||
"yourServers": "",
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:cwtch/notification_manager.dart';
|
||||
import 'package:cwtch/widgets/rightshiftfixer.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:cwtch/cwtch/ffi.dart';
|
||||
import 'package:cwtch/cwtch/gomobile.dart';
|
||||
|
@ -42,7 +43,6 @@ class FlwtchState extends State<Flwtch> {
|
|||
String selectedConversation = "";
|
||||
var columns = [1]; // default or 'single column' mode
|
||||
//var columns = [1, 1, 2];
|
||||
late AppModel appStatus;
|
||||
late ProfileListState profs;
|
||||
|
||||
@override
|
||||
|
@ -68,8 +68,6 @@ class FlwtchState extends State<Flwtch> {
|
|||
cwtchInit = true;
|
||||
});
|
||||
});
|
||||
|
||||
appStatus = AppModel(cwtch: cwtch);
|
||||
}
|
||||
|
||||
ChangeNotifierProvider<TorStatus> getTorStatusProvider() => ChangeNotifierProvider.value(value: globalTorStatus);
|
||||
|
@ -101,7 +99,7 @@ class FlwtchState extends State<Flwtch> {
|
|||
theme: mkThemeData(settings),
|
||||
// from dan: home: cwtchInit == true ? ProfileMgrView(cwtch) : SplashView(),
|
||||
// from erinn: home: columns.length == 3 ? TripleColumnView() : ProfileMgrView(),
|
||||
home: cwtchInit == true ? (columns.length == 3 ? TripleColumnView() : ProfileMgrView()) : SplashView(),
|
||||
home: cwtchInit == true ? (columns.length == 3 ? TripleColumnView() : ShiftRightFixer(child: ProfileMgrView())) : SplashView(),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
|
|
@ -190,9 +190,16 @@ class ProfileInfoState extends ChangeNotifier {
|
|||
// TODO Keys...
|
||||
return ServerInfoState(onion: server["onion"], status: server["status"]);
|
||||
}));
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
void updateServerStatusCache(String server, String status) {
|
||||
this._servers.updateServerCache(server, status);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// Getters and Setters for Online Status
|
||||
bool get isOnline => this._online;
|
||||
set isOnline(bool newValue) {
|
||||
|
@ -253,7 +260,7 @@ class ContactInfoState extends ChangeNotifier {
|
|||
|
||||
// todo: a nicer way to model contacts, groups and other "entities"
|
||||
late bool _isGroup;
|
||||
late String _server;
|
||||
String? _server;
|
||||
|
||||
ContactInfoState(
|
||||
this.profileOnion,
|
||||
|
@ -268,7 +275,7 @@ class ContactInfoState extends ChangeNotifier {
|
|||
numMessages = 0,
|
||||
numUnread = 0,
|
||||
lastMessageTime,
|
||||
server = "",
|
||||
server,
|
||||
}) {
|
||||
this._nickname = nickname;
|
||||
this._isGroup = isGroup;
|
||||
|
@ -470,37 +477,3 @@ class MessageState extends ChangeNotifier {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
/////////////
|
||||
/// ACN ///
|
||||
/////////////
|
||||
|
||||
class AppModel {
|
||||
final Cwtch cwtch;
|
||||
AppModel({required this.cwtch});
|
||||
|
||||
Stream<String> contactEvents() async* {
|
||||
while (true) {
|
||||
String event = await cwtch.ContactEvents();
|
||||
if (event != "") {
|
||||
print(event);
|
||||
yield event;
|
||||
} else {
|
||||
print("TEST TEST FAIL TEST FAIL 123");
|
||||
await Future.delayed(Duration(seconds: 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Stream<String> torStatus() async* {
|
||||
while (true) {
|
||||
String event = await cwtch.ACNEvents();
|
||||
if (event != "") {
|
||||
yield event;
|
||||
} else {
|
||||
print("TOR TEST TEST FAIL TEST FAIL 123");
|
||||
await Future.delayed(Duration(seconds: 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,16 @@ class ServerListState extends ChangeNotifier {
|
|||
return idx >= 0 ? _servers[idx] : null;
|
||||
}
|
||||
|
||||
void updateServerCache(String onion, String status) {
|
||||
int idx = _servers.indexWhere((element) => element.onion == onion);
|
||||
if (idx >= 0) {
|
||||
_servers[idx] = ServerInfoState(onion: onion, status: status);
|
||||
} else {
|
||||
print("Tried to update server cache without a starting state...this is probably an error");
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
List<ServerInfoState> get servers => _servers.sublist(0); //todo: copy?? dont want caller able to bypass changenotifier
|
||||
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ class _AddContactViewState extends State<AddContactView> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// if we haven't picked a server yet, pick the first one in the list...
|
||||
if (server.isEmpty) {
|
||||
if (server.isEmpty && Provider.of<ProfileInfoState>(context).serverList.servers.isNotEmpty) {
|
||||
server = Provider.of<ProfileInfoState>(context).serverList.servers.first.onion;
|
||||
}
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ class _ContactsViewState extends State<ContactsView> {
|
|||
width: 10,
|
||||
),
|
||||
Expanded(
|
||||
child: Text("%1 » %2".replaceAll("%1", Provider.of<ProfileInfoState>(context).nickname).replaceAll("%2", "Contacts"),
|
||||
child: Text("%1 » %2".replaceAll("%1", Provider.of<ProfileInfoState>(context).nickname).replaceAll("%2", AppLocalizations.of(context)!.titleManageContacts),
|
||||
overflow: TextOverflow.ellipsis, style: TextStyle(color: Provider.of<Settings>(context).current().mainTextColor()))), //todo
|
||||
])),
|
||||
actions: [
|
||||
|
|
|
@ -31,7 +31,7 @@ class _DoubleColumnViewState extends State<DoubleColumnView> {
|
|||
: //dev
|
||||
MultiProvider(providers: [
|
||||
ChangeNotifierProvider.value(value: Provider.of<ProfileInfoState>(context)),
|
||||
ChangeNotifierProvider.value(value: Provider.of<ProfileInfoState>(context).contactList.getContact(flwtch.selectedConversation)),
|
||||
ChangeNotifierProvider.value(value: Provider.of<ProfileInfoState>(context).contactList.getContact(flwtch.selectedConversation)!),
|
||||
], child: Container(child: MessageView())),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -7,6 +7,7 @@ import 'package:provider/provider.dart';
|
|||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import '../main.dart';
|
||||
import '../config.dart';
|
||||
|
||||
/// Global Settings View provides access to modify all the Globally Relevant Settings including Locale, Theme and Experiments.
|
||||
class GlobalSettingsView extends StatefulWidget {
|
||||
|
@ -156,7 +157,7 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
|
|||
height: 128,
|
||||
)),
|
||||
applicationName: "Cwtch (Flutter UI)",
|
||||
applicationVersion: AppLocalizations.of(context)!.version.replaceAll("%1", constructVersionString(Provider.of<Settings>(context).packageInfo)),
|
||||
applicationVersion: AppLocalizations.of(context)!.versionBuilddate.replaceAll("%1", EnvironmentConfig.BUILD_VER).replaceAll("%2", EnvironmentConfig.BUILD_DATE),
|
||||
applicationLegalese: '\u{a9} 2021 Open Privacy Research Society',
|
||||
),
|
||||
]))));
|
||||
|
|
|
@ -141,7 +141,7 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
|
|||
showAlertDialog(context);
|
||||
},
|
||||
icon: Icon(Icons.delete),
|
||||
label: Text(AppLocalizations.of(context)!.deleteBtn),
|
||||
label: Text(AppLocalizations.of(context)!.leaveGroup),
|
||||
))
|
||||
])
|
||||
])))));
|
||||
|
@ -166,7 +166,7 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
|
|||
);
|
||||
Widget continueButton = TextButton(
|
||||
style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))),
|
||||
child: Text(AppLocalizations.of(context)!.deleteProfileConfirmBtn),
|
||||
child: Text(AppLocalizations.of(context)!.yesLeave),
|
||||
onPressed: () {
|
||||
var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
|
||||
var handle = Provider.of<ContactInfoState>(context, listen: false).onion;
|
||||
|
@ -179,7 +179,7 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
|
|||
|
||||
// set up the AlertDialog
|
||||
AlertDialog alert = AlertDialog(
|
||||
title: Text(AppLocalizations.of(context)!.deleteProfileConfirmBtn),
|
||||
title: Text(AppLocalizations.of(context)!.reallyLeaveThisGroupPrompt),
|
||||
actions: [
|
||||
cancelButton,
|
||||
continueButton,
|
||||
|
|
|
@ -93,7 +93,8 @@ class _ContactRowState extends State<ContactRow> {
|
|||
MaterialPageRoute<void>(
|
||||
builder: (BuildContext builderContext) {
|
||||
// assert we have an actual profile...
|
||||
var profile = Provider.of<FlwtchState>(builderContext, listen: false).profs.getProfile(profileOnion)!;
|
||||
// We need to listen for updates to the profile in order to update things like invitation message bubbles.
|
||||
var profile = Provider.of<FlwtchState>(builderContext).profs.getProfile(profileOnion)!;
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider.value(value: profile),
|
||||
|
|
|
@ -5,6 +5,7 @@ import 'package:provider/provider.dart';
|
|||
import '../main.dart';
|
||||
import '../model.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import '../settings.dart';
|
||||
|
||||
|
@ -34,11 +35,17 @@ class InvitationBubbleState extends State<InvitationBubble> {
|
|||
prettyDate = DateFormat.yMd().add_jm().format(Provider.of<MessageState>(context).timestamp);
|
||||
}
|
||||
|
||||
// If the sender is not us, then we want to give them a nickname...
|
||||
var senderDisplayStr = "";
|
||||
if (Provider.of<MessageState>(context).senderOnion != null) {
|
||||
var contact = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageState>(context).senderOnion);
|
||||
senderDisplayStr = contact!.nickname;
|
||||
if (!fromMe && Provider.of<MessageState>(context).senderOnion != null) {
|
||||
ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageState>(context).senderOnion);
|
||||
if (contact != null) {
|
||||
senderDisplayStr = contact.nickname;
|
||||
} else {
|
||||
senderDisplayStr = Provider.of<MessageState>(context).senderOnion;
|
||||
}
|
||||
}
|
||||
|
||||
var wdgSender = Center(
|
||||
widthFactor: 1,
|
||||
child: SelectableText(senderDisplayStr + '\u202F',
|
||||
|
@ -50,9 +57,8 @@ class InvitationBubbleState extends State<InvitationBubble> {
|
|||
//todo: get group name?
|
||||
messageStr = "You sent an invitation for " + (isGroup ? "a group" : Provider.of<MessageState>(context).message ?? "");
|
||||
} else {
|
||||
messageStr = (isGroup ? "You have been invited to join " + (Provider.of<MessageState>(context).inviteNick ?? "") : "This is a contact suggestion for:") +
|
||||
"\n" +
|
||||
(Provider.of<MessageState>(context).inviteTarget ?? "");
|
||||
String joinGroup = AppLocalizations.of(context)!.inviteToGroup;
|
||||
messageStr = (isGroup ? joinGroup + (Provider.of<MessageState>(context).inviteNick ?? "") : "This is a contact suggestion for:") + "\n" + (Provider.of<MessageState>(context).inviteTarget ?? "");
|
||||
}
|
||||
var wdgMessage = Center(
|
||||
widthFactor: 1,
|
||||
|
@ -79,9 +85,13 @@ class InvitationBubbleState extends State<InvitationBubble> {
|
|||
textAlign: fromMe ? TextAlign.right : TextAlign.left),
|
||||
!fromMe
|
||||
? SizedBox(width: 1, height: 1)
|
||||
: Provider.of<MessageState>(context).ackd
|
||||
? Icon(Icons.check_circle_outline, color: Provider.of<Settings>(context).theme.messageFromMeTextColor(), size: 12)
|
||||
: Icon(Icons.hourglass_bottom_outlined, color: Provider.of<Settings>(context).theme.messageFromMeTextColor(), size: 12)
|
||||
: Padding(
|
||||
padding: EdgeInsets.all(1.0),
|
||||
child: Provider.of<MessageState>(context).ackd == true
|
||||
? Icon(Icons.check_circle_outline, color: Provider.of<Settings>(context).theme.messageFromMeTextColor(), size: 16)
|
||||
: (Provider.of<MessageState>(context).error == true
|
||||
? Icon(Icons.error_outline, color: Provider.of<Settings>(context).theme.messageFromMeTextColor(), size: 16)
|
||||
: Icon(Icons.hourglass_bottom_outlined, color: Provider.of<Settings>(context).theme.messageFromMeTextColor(), size: 16)))
|
||||
],
|
||||
));
|
||||
} else if (isAccepted) {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:cwtch/widgets/malformedbubble.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../model.dart';
|
||||
|
@ -63,23 +64,32 @@ class MessageBubbleState extends State<MessageBubble> {
|
|||
textAlign: fromMe ? TextAlign.right : TextAlign.left),
|
||||
!fromMe
|
||||
? SizedBox(width: 1, height: 1)
|
||||
: Provider.of<MessageState>(context).ackd == true
|
||||
? Icon(Icons.check_circle_outline, color: Provider.of<Settings>(context).theme.messageFromMeTextColor(), size: 12)
|
||||
: (Provider.of<MessageState>(context).error == true
|
||||
? Icon(Icons.error_outline, color: Provider.of<Settings>(context).theme.messageFromMeTextColor(), size: 12)
|
||||
: Icon(Icons.hourglass_bottom_outlined, color: Provider.of<Settings>(context).theme.messageFromMeTextColor(), size: 12))
|
||||
: Padding(
|
||||
padding: EdgeInsets.all(1.0),
|
||||
child: Provider.of<MessageState>(context).ackd == true
|
||||
? Icon(Icons.check_circle_outline, color: Provider.of<Settings>(context).theme.messageFromMeTextColor(), size: 16)
|
||||
: (Provider.of<MessageState>(context).error == true
|
||||
? Icon(Icons.error_outline, color: Provider.of<Settings>(context).theme.messageFromMeTextColor(), size: 16)
|
||||
: Icon(Icons.hourglass_bottom_outlined, color: Provider.of<Settings>(context).theme.messageFromMeTextColor(), size: 16)))
|
||||
],
|
||||
));
|
||||
|
||||
var error = Provider.of<MessageState>(context).error;
|
||||
|
||||
return LayoutBuilder(builder: (context, constraints) {
|
||||
//print(constraints.toString()+", "+constraints.maxWidth.toString());
|
||||
return RepaintBoundary(
|
||||
child: Container(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor() : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor(),
|
||||
color: error
|
||||
? malformedColor
|
||||
: (fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor() : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor()),
|
||||
border: Border.all(
|
||||
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor() : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor(), width: 1),
|
||||
color: error
|
||||
? malformedColor
|
||||
: (fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor() : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor()),
|
||||
width: 1),
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(borderRadiousEh),
|
||||
topRight: Radius.circular(borderRadiousEh),
|
||||
|
|
|
@ -40,6 +40,7 @@ class _MessageListState extends State<MessageList> {
|
|||
create: (x) => MessageState(
|
||||
context: itemBuilderContext,
|
||||
profileOnion: Provider.of<ProfileInfoState>(outerContext, listen: false).onion,
|
||||
// We don't want to listen for updates to the contact handle...
|
||||
contactHandle: Provider.of<ContactInfoState>(x, listen: false).onion,
|
||||
messageIndex: trueIndex,
|
||||
),
|
||||
|
|
|
@ -39,7 +39,9 @@ class _CwtchTextFieldState extends State<CwtchPasswordField> {
|
|||
decoration: InputDecoration(
|
||||
suffixIcon: IconButton(
|
||||
onPressed: () {
|
||||
obscureText = !obscureText;
|
||||
setState(() {
|
||||
obscureText = !obscureText;
|
||||
});
|
||||
},
|
||||
icon: Icon((obscureText ? Icons.remove_red_eye : Icons.remove_red_eye_outlined), semanticLabel: label),
|
||||
tooltip: label,
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
// From https://github.com/flutter/flutter/issues/75675#issuecomment-846601115
|
||||
// necessary to fix bug in flutter engine on Windows.
|
||||
// todo: hopefully we can remove this soon
|
||||
class ShiftRightFixer extends StatefulWidget {
|
||||
ShiftRightFixer({required this.child});
|
||||
final Widget child;
|
||||
@override
|
||||
State<StatefulWidget> createState() => _ShiftRightFixerState();
|
||||
}
|
||||
|
||||
class _ShiftRightFixerState extends State<ShiftRightFixer> {
|
||||
final FocusNode focus = FocusNode(skipTraversal: true, canRequestFocus: false);
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Focus(
|
||||
focusNode: focus,
|
||||
onKey: (_, RawKeyEvent event) {
|
||||
return event.physicalKey == PhysicalKeyboardKey.shiftRight ? KeyEventResult.handled : KeyEventResult.ignored;
|
||||
},
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../main.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
class TorStatusLabel extends StatefulWidget {
|
||||
@override
|
||||
_TorStatusState createState() => _TorStatusState();
|
||||
}
|
||||
|
||||
class _TorStatusState extends State<TorStatusLabel> {
|
||||
String status = "";
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Builder(
|
||||
builder: (context2) => StreamBuilder<String>(
|
||||
stream: Provider.of<FlwtchState>(context).appStatus.torStatus(),
|
||||
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
|
||||
return Text(
|
||||
snapshot.hasData ? snapshot.data! : AppLocalizations.of(context)!.loadingTor,
|
||||
style: Theme.of(context).textTheme.headline4,
|
||||
);
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
20
pubspec.lock
20
pubspec.lock
|
@ -14,7 +14,7 @@ packages:
|
|||
name: args
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
version: "2.1.1"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -77,7 +77,7 @@ packages:
|
|||
name: dbus
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.5.0"
|
||||
version: "0.5.1"
|
||||
desktop_notifications:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -142,7 +142,7 @@ packages:
|
|||
name: glob
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
version: "1.2.0"
|
||||
http:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -199,6 +199,20 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
node_interop:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: node_interop
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
node_io:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: node_io
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
package_info_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
Loading…
Reference in New Issue