Merge branch 'trunk' into assetsDir
continuous-integration/drone/pr Build is passing Details

This commit is contained in:
Sarah Jamie Lewis 2021-06-14 17:41:26 -07:00
commit 07e8c49d7b
38 changed files with 1801 additions and 1248 deletions

View File

@ -59,7 +59,8 @@ steps:
- cp linux/cwtch.desktop deploy/linux - cp linux/cwtch.desktop deploy/linux
- cp linux/cwtch.png deploy/linux - cp linux/cwtch.png deploy/linux
- cp linux/libCwtch.so deploy/linux/lib/ - cp linux/libCwtch.so deploy/linux/lib/
- cp /sdks/flutter/bin/cache/artifacts/engine/linux-x64/icudtl.dat deploy/linux # should not be needed, should be in data/flutter_assets and work from there
#- cp /sdks/flutter/bin/cache/artifacts/engine/linux-x64/icudtl.dat deploy/linux
- cp tor deploy/linux - cp tor deploy/linux
- cd deploy - cd deploy
- mv linux cwtch - mv linux cwtch
@ -230,6 +231,12 @@ steps:
- mkdir deploy - mkdir deploy
- move build\\windows\\runner\\Release $Env:builddir - move build\\windows\\runner\\Release $Env:builddir
- copy windows\libCwtch.dll $Env:builddir - copy windows\libCwtch.dll $Env:builddir
# flutter hasn't worked out it's packaging of required dll's so we have to resort to this manual nonsense
# https://github.com/google/flutter-desktop-embedding/issues/587
# https://github.com/flutter/flutter/issues/53167
- copy C:\BuildTools\VC\Redist\MSVC\14.29.30036\x64\Microsoft.VC142.CRT\vcruntime140.dll $Env:builddir
- copy C:\BuildTools\VC\Redist\MSVC\14.29.30036\x64\Microsoft.VC142.CRT\vcruntime140_1.dll $Env:builddir
- copy C:\BuildTools\VC\Redist\MSVC\14.29.30036\x64\Microsoft.VC142.CRT\msvcp140.dll $Env:builddir
- powershell -command "Expand-Archive -Path tor.zip -DestinationPath $Env:builddir\Tor" - powershell -command "Expand-Archive -Path tor.zip -DestinationPath $Env:builddir\Tor"
- powershell -command "Compress-Archive -Path $Env:builddir -DestinationPath $Env:zip" - powershell -command "Compress-Archive -Path $Env:builddir -DestinationPath $Env:zip"
- powershell -command "(Get-FileHash *.zip -Algorithm sha512).Hash" > $Env:sha - powershell -command "(Get-FileHash *.zip -Algorithm sha512).Hash" > $Env:sha

View File

@ -1 +1 @@
v0.0.2-49-g6a0e839-2021-06-02-19-40 v0.0.2-63-g033de73-2021-06-11-21-41

View File

@ -32,14 +32,24 @@ After adding a new key and providing/obtaining translations for it, follow the n
### Updating translations ### Updating translations
Only Open Privacy staff members can update translations automatically: Only Open Privacy staff members can update translations.
``` In Lokalise, hit Download and make sure:
flutter pub run flutter_lokalise download -v --api-token "<X>" --project-id "<Y>"
```
This will download a bundle of translations from Lokalise and convert it to resource files in `lib/l10n/intl_*.arb`. * Format is set to "Flutter (.arb)
The next time Flwtch is built, Flutter will notice the changes and update `app_localizations.dart` accordingly (thanks to `generate:true` in `pubspec.yaml`). * Output filename is set to `l10n/intl_%LANG_ISO%.%FORMAT%`
* Empty translations is set to "Replace with base language"
Build, download and unzip the output, overwriting `lib/l10n`. The next time Flwtch is built, Flutter will notice the changes and update `app_localizations.dart` accordingly (thanks to `generate:true` in `pubspec.yaml`).
### Adding a language
If a new language has been added to the Lokalise project, two additional manual steps need to be done:
* Create a new key called `localeXX` for the name of the language
* Add it to the settings pane by updating `getLanguageFull()` in `lib/views/globalsettingsview.dart`
Then rebuild as normal.
### Using a string ### Using a string
@ -57,6 +67,5 @@ Text(AppLocalizations.of(context)!.stringIdentifer),
### Configuration ### Configuration
API tokens are only available to Open Privacy staff at this time, who will perform the translation updates for you as part of merging your PRs.
With `generate: true` in `pubspec.yaml`, the Flutter build process checks `l10n.yaml` for input/output filenames. With `generate: true` in `pubspec.yaml`, the Flutter build process checks `l10n.yaml` for input/output filenames.

View File

@ -69,6 +69,15 @@ android {
signingConfig signingConfigs.release signingConfig signingConfigs.release
} }
} }
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
} }
flutter { flutter {
@ -82,4 +91,30 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2"
implementation "com.airbnb.android:lottie:3.5.0" implementation "com.airbnb.android:lottie:3.5.0"
implementation "com.android.support.constraint:constraint-layout:2.0.4" implementation "com.android.support.constraint:constraint-layout:2.0.4"
// WorkManager
// (Java only)
//implementation("androidx.work:work-runtime:$work_version")
// Kotlin + coroutines
implementation("androidx.work:work-runtime-ktx:2.5.0")
// optional - RxJava2 support
//implementation("androidx.work:work-rxjava2:$work_version")
// optional - GCMNetworkManager support
//implementation("androidx.work:work-gcm:$work_version")
// optional - Test helpers
//androidTestImplementation("androidx.work:work-testing:$work_version")
// optional - Multiprocess support
implementation "androidx.work:work-multiprocess:2.5.0"
// end of workmanager deps
// needed to prevent a ListenableFuture dependency conflict/bug
// see https://github.com/google/ExoPlayer/issues/7905#issuecomment-692637059
implementation 'com.google.guava:guava:any'
} }

View File

@ -43,4 +43,9 @@
<!--Needed to access Tor socket--> <!--Needed to access Tor socket-->
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<!--Needed to run in background (lol)-->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!--Meeded to check if activity is foregrounded or if messages from the service should be queued-->
<uses-permission android:name="android.permission.GET_TASKS" />
</manifest> </manifest>

View File

@ -0,0 +1,287 @@
package im.cwtch.flwtch
import android.app.*
import android.content.Context
import android.content.Context.ACTIVITY_SERVICE
import android.content.Intent
import android.graphics.BitmapFactory
import android.graphics.Color
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.work.*
import cwtch.Cwtch
import io.flutter.FlutterInjector
import org.json.JSONObject
class FlwtchWorker(context: Context, parameters: WorkerParameters) :
CoroutineWorker(context, parameters) {
private val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as
NotificationManager
private var notificationID: MutableMap<String, Int> = mutableMapOf()
private var notificationIDnext: Int = 1;
override suspend fun doWork(): Result {
val method = inputData.getString(KEY_METHOD)
?: return Result.failure()
val args = inputData.getString(KEY_ARGS)
?: return Result.failure()
// Mark the Worker as important
val progress = "Trying to do a Flwtch"//todo:translate
setForeground(createForegroundInfo(progress))
return handleCwtch(method, args)
}
private fun getNotificationID(profile: String, contact: String): Int {
val k = "$profile $contact"
if (!notificationID.containsKey(k)) {
notificationID[k] = notificationIDnext++
}
return notificationID[k] ?: -1
}
private suspend fun handleCwtch(method: String, args: String): Result {
val a = JSONObject(args);
when (method) {
"Start" -> {
Log.i("FlwtchWorker.kt", "handleAppInfo Start")
val appDir = (a.get("appDir") as? String) ?: ""
val torPath = (a.get("torPath") as? String) ?: "tor"
Log.i("FlwtchWorker.kt", "appDir: '$appDir' torPath: '$torPath'")
if (Cwtch.startCwtch(appDir, torPath) != 0.toByte()) return Result.failure()
// infinite coroutine :)
while(true) {
val evt = MainActivity.AppbusEvent(Cwtch.getAppBusEvent())
if (evt.EventType == "NewMessageFromPeer") {
val data = JSONObject(evt.Data)
val channelId =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createMessageNotificationChannel(data.getString("RemotePeer"), data.getString("RemotePeer"))
} else {
// If earlier version channel ID is not used
// https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context)
""
}
val loader = FlutterInjector.instance().flutterLoader()
val key = loader.getLookupKeyForAsset("assets/"+data.getString("Picture"))//"assets/profiles/001-centaur.png")
val fh = applicationContext.assets.open(key)
val clickIntent = Intent(applicationContext, MainActivity::class.java).also { intent ->
intent.setAction(Intent.ACTION_RUN)
intent.putExtra("EventType", "NotificationClicked")
intent.putExtra("ProfileOnion", data.getString("ProfileOnion"))
intent.putExtra("RemotePeer", data.getString("RemotePeer"))
}
val newNotification = NotificationCompat.Builder(applicationContext, channelId)
.setContentTitle(data.getString("Nick"))
.setContentText("New message")
.setLargeIcon(BitmapFactory.decodeStream(fh))
.setSmallIcon(R.mipmap.knott)
.setContentIntent(PendingIntent.getActivity(applicationContext, 1, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT))
.setAutoCancel(true)
.build()
notificationManager.notify(getNotificationID(data.getString("ProfileOnion"), data.getString("RemotePeer")), newNotification)
}
Intent().also { intent ->
intent.setAction("im.cwtch.flwtch.broadcast.SERVICE_EVENT_BUS")
intent.putExtra("EventType", evt.EventType)
intent.putExtra("Data", evt.Data)
intent.putExtra("EventID", evt.EventID)
LocalBroadcastManager.getInstance(applicationContext).sendBroadcast(intent)
}
}
}
"ReconnectCwtchForeground" -> {
Cwtch.reconnectCwtchForeground()
}
"SelectProfile" -> {
val onion = (a.get("profile") as? String) ?: "";
Cwtch.selectProfile(onion)
}
"CreateProfile" -> {
val nick = (a.get("nick") as? String) ?: "";
val pass = (a.get("pass") as? String) ?: "";
Cwtch.createProfile(nick, pass)
}
"LoadProfiles" -> {
val pass = (a.get("pass") as? String) ?: "";
Cwtch.loadProfiles(pass)
}
"GetProfiles" -> Result.success(Data.Builder().putString("result", Cwtch.getProfiles()).build())
// "ACNEvents" -> result.success(Cwtch.acnEvents())
"ContactEvents" -> Result.success(Data.Builder().putString("result", Cwtch.contactEvents()).build())
"NumMessages" -> {
val profile = (a.get("profile") as? String) ?: "";
val handle = (a.get("contact") as? String) ?: "";
return Result.success(Data.Builder().putLong("result", Cwtch.numMessages(profile, handle)).build())
}
"GetMessage" -> {
val profile = (a.get("profile") as? String) ?: "";
val handle = (a.get("contact") as? String) ?: "";
val indexI = a.getInt("index") ?: 0;
Log.i("FlwtchWorker.kt", "indexI = " + indexI)
return Result.success(Data.Builder().putString("result", Cwtch.getMessage(profile, handle, indexI.toLong())).build())
}
"GetMessages" -> {
val profile = (a.get("profile") as? String) ?: "";
val handle = (a.get("contact") as? String) ?: "";
val start = (a.get("start") as? Long) ?: 0;
val end = (a.get("end") as? Long) ?: 0;
return Result.success(Data.Builder().putString("result", Cwtch.getMessages(profile, handle, start, end)).build())
}
"UpdateMessageFlags" -> {
val profile = (a.get("profile") as? String) ?: "";
val handle = (a.get("contact") as? String) ?: "";
val midx = (a.get("midx") as? Long) ?: 0;
val flags = (a.get("flags") as? Long) ?: 0;
Cwtch.updateMessageFlags(profile, handle, midx, flags);
}
"AcceptContact" -> {
val profile = (a.get("ProfileOnion") as? String) ?: "";
val handle = (a.get("handle") as? String) ?: "";
Cwtch.acceptContact(profile, handle);
}
"BlockContact" -> {
val profile = (a.get("ProfileOnion") as? String) ?: "";
val handle = (a.get("handle") as? String) ?: "";
Cwtch.blockContact(profile, handle);
}
"DebugResetContact" -> {
val profile = (a.get("ProfileOnion") as? String) ?: "";
val handle = (a.get("handle") as? String) ?: "";
Cwtch.debugResetContact(profile, handle);
}
"SendMessage" -> {
val profile = (a.get("ProfileOnion") as? String) ?: "";
val handle = (a.get("handle") as? String) ?: "";
val message = (a.get("message") as? String) ?: "";
Cwtch.sendMessage(profile, handle, message);
}
"SendInvitation" -> {
val profile = (a.get("ProfileOnion") as? String) ?: "";
val handle = (a.get("handle") as? String) ?: "";
val target = (a.get("target") as? String) ?: "";
Cwtch.sendInvitation(profile, handle, target);
}
"SendProfileEvent" -> {
val onion = (a.get("onion") as? String) ?: "";
val jsonEvent = (a.get("jsonEvent") as? String) ?: "";
Cwtch.sendProfileEvent(onion, jsonEvent);
}
"SendAppEvent" -> {
val jsonEvent = (a.get("jsonEvent") as? String) ?: "";
Cwtch.sendAppEvent(jsonEvent);
}
"ResetTor" -> {
Cwtch.resetTor();
}
"ImportBundle" -> {
val profile = (a.get("ProfileOnion") as? String) ?: "";
val bundle = (a.get("bundle") as? String) ?: "";
Cwtch.importBundle(profile, bundle);
}
"SetGroupAttribute" -> {
val profile = (a.get("ProfileOnion") as? String) ?: "";
val groupHandle = (a.get("groupHandle") as? String) ?: "";
val key = (a.get("key") as? String) ?: "";
val value = (a.get("value") as? String) ?: "";
Cwtch.setGroupAttribute(profile, groupHandle, key, value);
}
"CreateGroup" -> {
val profile = (a.get("ProfileOnion") as? String) ?: "";
val server = (a.get("server") as? String) ?: "";
val groupName = (a.get("groupname") as? String) ?: "";
Cwtch.createGroup(profile, server, groupName);
}
"LeaveGroup" -> {
val profile = (a.get("ProfileOnion") as? String) ?: "";
val groupHandle = (a.get("groupHandle") as? String) ?: "";
Log.i("FlwtchWorker.kt", "LeaveGroup: need to recompile aar and uncomment this line")//todo
//Cwtch.leaveGroup(profile, groupHandle);
}
"RejectInvite" -> {
val profile = (a.get("ProfileOnion") as? String) ?: "";
val groupHandle = (a.get("groupHandle") as? String) ?: "";
Cwtch.rejectInvite(profile, groupHandle);
}
else -> return Result.failure()
}
return Result.success()
}
// Creates an instance of ForegroundInfo which can be used to update the
// ongoing notification.
private fun createForegroundInfo(progress: String): ForegroundInfo {
val id = "flwtch"
val title = "Flwtch"
val cancel = "Nevermind"//todo: translate
// This PendingIntent can be used to cancel the worker
val intent = WorkManager.getInstance(applicationContext)
.createCancelPendingIntent(getId())
val channelId =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel(id, id)
} else {
// If earlier version channel ID is not used
// https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context)
""
}
val notification = NotificationCompat.Builder(applicationContext, id)
.setContentTitle(title)
.setTicker(title)
.setContentText(progress)
.setSmallIcon(R.mipmap.knott)
.setOngoing(true)
// Add the cancel action to the notification which can
// be used to cancel the worker
.addAction(android.R.drawable.ic_delete, cancel, intent)
.build()
return ForegroundInfo(101, notification)
}
@RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel(channelId: String, channelName: String): String{
val chan = NotificationChannel(channelId,
channelName, NotificationManager.IMPORTANCE_NONE)
chan.lightColor = Color.MAGENTA
chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
notificationManager.createNotificationChannel(chan)
return channelId
}
@RequiresApi(Build.VERSION_CODES.O)
private fun createMessageNotificationChannel(channelId: String, channelName: String): String{
val chan = NotificationChannel(channelId,
channelName, NotificationManager.IMPORTANCE_HIGH)
chan.lightColor = Color.MAGENTA
chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
notificationManager.createNotificationChannel(chan)
return channelId
}
companion object {
const val KEY_METHOD = "KEY_METHOD"
const val KEY_ARGS = "KEY_ARGS"
}
class AppbusEvent(json: String) : JSONObject(json) {
val EventType = this.optString("EventType")
val EventID = this.optString("EventID")
val Data = this.optString("Data")
}
}

View File

@ -1,11 +1,21 @@
package im.cwtch.flwtch package im.cwtch.flwtch
import SplashView import SplashView
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import androidx.annotation.NonNull import androidx.annotation.NonNull
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Looper import android.os.Looper
import android.util.Log import android.util.Log
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.lifecycle.Observer
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.work.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
@ -25,6 +35,8 @@ import kotlin.concurrent.thread
import org.json.JSONObject import org.json.JSONObject
import java.io.File import java.io.File
import java.time.Duration
import java.util.concurrent.TimeUnit
class MainActivity: FlutterActivity() { class MainActivity: FlutterActivity() {
@ -40,13 +52,31 @@ class MainActivity: FlutterActivity() {
// Channel to send eventbus events on // Channel to send eventbus events on
private val CWTCH_EVENTBUS = "test.flutter.dev/eventBus" private val CWTCH_EVENTBUS = "test.flutter.dev/eventBus"
// Channel to trigger contactview when an external notification is clicked
private val CHANNEL_NOTIF_CLICK = "im.cwtch.flwtch/notificationClickHandler"
private var methodChan: MethodChannel? = null
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
if (methodChan == null || intent.extras == null) return
if (!intent.extras!!.containsKey("ProfileOnion") || !intent.extras!!.containsKey("RemotePeer")) {
Log.i("onNewIntent", "got intent with no onions")
return
}
val profile = intent.extras!!.getString("ProfileOnion")
val handle = intent.extras!!.getString("RemotePeer")
val mappo = mapOf("ProfileOnion" to profile, "RemotePeer" to handle)
val j = JSONObject(mappo)
methodChan!!.invokeMethod("NotificationClicked", j.toString())
}
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine) super.configureFlutterEngine(flutterEngine)
// Note: this methods are invoked on the main thread. // Note: this methods are invoked on the main thread.
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL_APP_INFO).setMethodCallHandler { call, result -> handleAppInfo(call, result) } MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL_APP_INFO).setMethodCallHandler { call, result -> handleAppInfo(call, result) }
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL_CWTCH).setMethodCallHandler { call, result -> handleCwtch(call, result) } MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL_CWTCH).setMethodCallHandler { call, result -> handleCwtch(call, result) }
methodChan = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL_NOTIF_CLICK)
} }
private fun handleAppInfo(@NonNull call: MethodCall, @NonNull result: Result) { private fun handleAppInfo(@NonNull call: MethodCall, @NonNull result: Result) {
@ -61,143 +91,68 @@ class MainActivity: FlutterActivity() {
val ainfo = this.applicationContext.packageManager.getApplicationInfo( val ainfo = this.applicationContext.packageManager.getApplicationInfo(
"im.cwtch.flwtch", // Must be app name "im.cwtch.flwtch", // Must be app name
PackageManager.GET_SHARED_LIBRARY_FILES) PackageManager.GET_SHARED_LIBRARY_FILES)
return ainfo.nativeLibraryDir return ainfo.nativeLibraryDir
} }
// receives messages from the ForegroundService (which provides, ironically enough, the backend)
private fun handleCwtch(@NonNull call: MethodCall, @NonNull result: Result) { private fun handleCwtch(@NonNull call: MethodCall, @NonNull result: Result) {
when (call.method) { var method = call.method
"Start" -> { val argmap: Map<String, String> = call.arguments as Map<String, String>
Log.i("MainActivity.kt", "handleAppInfo Start")
val appDir = (call.argument("appDir") as? String) ?: "";
val torPath = (call.argument("torPath") as? String) ?: "tor";
Log.i("MainActivity.kt", " appDir: '" + appDir + "' torPath: '" + torPath + "'")
Cwtch.startCwtch(appDir, torPath)
// seperate coroutine to poll event bus and send to dart // the frontend calls Start every time it fires up, but we don't want to *actually* call Cwtch.Start()
val eventbus_chan = MethodChannel(flutterEngine?.dartExecutor?.binaryMessenger, CWTCH_EVENTBUS) // in case the ForegroundService is still running. in both cases, however, we *do* want to re-register
Log.i("MainActivity.kt", "got event chan: " + eventbus_chan + " launching corouting...") // the eventbus listener.
GlobalScope.launch(Dispatchers.IO) { if (call.method == "Start") {
while(true) { val workerTag = "cwtchEventBusWorker"
val evt = AppbusEvent(Cwtch.getAppBusEvent()) val uniqueTag = argmap["torPath"] ?: "nullEventBus"
Log.i("MainActivity.kt", "got appbusEvent: " + evt)
launch(Dispatchers.Main) { // note: because the ForegroundService is specified as UniquePeriodicWork, it can't actually get
//todo: this elides evt.EventID which may be needed at some point? // accidentally duplicated. however, we still need to manually check if it's running or not, so
eventbus_chan.invokeMethod(evt.EventType, evt.Data) // that we can divert this method call to ReconnectCwtchForeground instead if so.
} val works = WorkManager.getInstance(this).getWorkInfosByTag(workerTag).get()
var alreadyRunning = false
for (workInfo in works) {
if (workInfo.tags.contains(uniqueTag)) {
if (workInfo.state == WorkInfo.State.RUNNING || workInfo.state == WorkInfo.State.ENQUEUED) {
alreadyRunning = true
} }
} else {
WorkManager.getInstance(this).cancelWorkById(workInfo.id)
} }
} }
"SelectProfile" -> {
val onion = (call.argument("profile") as? String) ?: "";
Cwtch.selectProfile(onion)
}
"CreateProfile" -> {
val nick = (call.argument("nick") as? String) ?: "";
val pass = (call.argument("pass") as? String) ?: "";
Cwtch.createProfile(nick, pass)
}
"LoadProfiles" -> {
val pass = (call.argument("pass") as? String) ?: "";
Cwtch.loadProfiles(pass)
}
"GetProfiles" -> result.success(Cwtch.getProfiles())
// "ACNEvents" -> result.success(Cwtch.acnEvents())
"ContactEvents" -> result.success(Cwtch.contactEvents())
"NumMessages" -> {
val profile = (call.argument("profile") as? String) ?: "";
val handle = (call.argument("contact") as? String) ?: "";
result.success(Cwtch.numMessages(profile, handle))
}
"GetMessage" -> {
//Log.i("MainActivivity.kt", (call.argument("index")));
// var args : HashMap<String, dynamic> = call.arguments(); // register our eventbus listener. note that we observe any/all work according to its tag, which
// Log.i("MainActivity.kt", args); // results in an implicit "reconnection" to old service threads even after frontend restarts
val mc = MethodChannel(flutterEngine?.dartExecutor?.binaryMessenger, CWTCH_EVENTBUS)
val filter = IntentFilter("im.cwtch.flwtch.broadcast.SERVICE_EVENT_BUS")
LocalBroadcastManager.getInstance(applicationContext).registerReceiver(MyBroadcastReceiver(mc), filter)
if (alreadyRunning) {
val profile = (call.argument("profile") as? String) ?: ""; Log.i("MainActivity.kt", "diverting Start -> Reconnect")
val handle = (call.argument("contact") as? String) ?: ""; method = "ReconnectCwtchForeground"
val indexI = call.argument<Int>("index") ?: 0; } else {
Log.i("MainActivity.kt", "indexI = " + indexI) Log.i("MainActivity.kt", "Start() launching foregroundservice")
result.success(Cwtch.getMessage(profile, handle, indexI.toLong())) // this is where the eventbus ForegroundService gets launched. WorkManager should keep it alive after this
val data: Data = Data.Builder().putString(FlwtchWorker.KEY_METHOD, call.method).putString(FlwtchWorker.KEY_ARGS, JSONObject(argmap).toString()).build()
// 15 minutes is the shortest interval you can request
val workRequest = PeriodicWorkRequestBuilder<FlwtchWorker>(15, TimeUnit.MINUTES).setInputData(data).addTag(workerTag).addTag(uniqueTag).build()
WorkManager.getInstance(this).enqueueUniquePeriodicWork("req_$uniqueTag", ExistingPeriodicWorkPolicy.KEEP, workRequest)
return
} }
"GetMessages" -> {
val profile = (call.argument("profile") as? String) ?: "";
val handle = (call.argument("contact") as? String) ?: "";
val start = (call.argument("start") as? Long) ?: 0;
val end = (call.argument("end") as? Long) ?: 0;
result.success(Cwtch.getMessages(profile, handle, start, end))
}
"AcceptContact" -> {
val profile = (call.argument("ProfileOnion") as? String) ?: "";
val handle = (call.argument("handle") as? String) ?: "";
Cwtch.acceptContact(profile, handle);
}
"BlockContact" -> {
val profile = (call.argument("ProfileOnion") as? String) ?: "";
val handle = (call.argument("handle") as? String) ?: "";
Cwtch.blockContact(profile, handle);
}
"DebugResetContact" -> {
val profile = (call.argument("ProfileOnion") as? String) ?: "";
val handle = (call.argument("handle") as? String) ?: "";
Cwtch.debugResetContact(profile, handle);
}
"SendMessage" -> {
val profile = (call.argument("ProfileOnion") as? String) ?: "";
val handle = (call.argument("handle") as? String) ?: "";
val message = (call.argument("message") as? String) ?: "";
Cwtch.sendMessage(profile, handle, message);
}
"SendInvitation" -> {
val profile = (call.argument("ProfileOnion") as? String) ?: "";
val handle = (call.argument("handle") as? String) ?: "";
val target = (call.argument("target") as? String) ?: "";
Cwtch.sendInvitation(profile, handle, target);
}
"SendProfileEvent" -> {
val onion = (call.argument("onion") as? String) ?: "";
val jsonEvent = (call.argument("jsonEvent") as? String) ?: "";
Cwtch.sendProfileEvent(onion, jsonEvent);
}
"SendAppEvent" -> {
val jsonEvent = (call.argument("jsonEvent") as? String) ?: "";
Cwtch.sendAppEvent(jsonEvent);
}
"ResetTor" -> {
Cwtch.resetTor();
}
"ImportBundle" -> {
val profile = (call.argument("ProfileOnion") as? String) ?: "";
val bundle = (call.argument("bundle") as? String) ?: "";
Cwtch.importBundle(profile, bundle);
}
"SetGroupAttribute" -> {
val profile = (call.argument("ProfileOnion") as? String) ?: "";
val groupHandle = (call.argument("groupHandle") as? String) ?: "";
val key = (call.argument("key") as? String) ?: "";
val value = (call.argument("value") as? String) ?: "";
Cwtch.setGroupAttribute(profile, groupHandle, key, value);
}
"CreateGroup" -> {
val profile = (call.argument("ProfileOnion") as? String) ?: "";
val server = (call.argument("server") as? String) ?: "";
val groupName = (call.argument("groupname") as? String) ?: "";
Cwtch.createGroup(profile, server, groupName);
}
"LeaveGroup" -> {
val profile = (call.argument("ProfileOnion") as? String) ?: "";
val groupHandle = (call.argument("groupHandle") as? String) ?: "";
Cwtch.leaveGroup(profile, groupHandle);
}
"RejectInvite" -> {
val profile = (call.argument("ProfileOnion") as? String) ?: "";
val groupHandle = (call.argument("groupHandle") as? String) ?: "";
Cwtch.rejectInvite(profile, groupHandle);
}
else -> result.notImplemented()
} }
// ...otherwise fallthru to a normal ffi method call (and return the result using the result callback)
val data: Data = Data.Builder().putString(FlwtchWorker.KEY_METHOD, method).putString(FlwtchWorker.KEY_ARGS, JSONObject(argmap).toString()).build()
val workRequest = OneTimeWorkRequestBuilder<FlwtchWorker>().setInputData(data).build()
WorkManager.getInstance(this).enqueue(workRequest)
WorkManager.getInstance(applicationContext).getWorkInfoByIdLiveData(workRequest.id).observe(
this, Observer { workInfo ->
if (workInfo.state == WorkInfo.State.SUCCEEDED) {
val res = workInfo.outputData.keyValueMap.toString()
result.success(workInfo.outputData.getString("result"))
}
}
)
} }
// source: https://web.archive.org/web/20210203022531/https://stackoverflow.com/questions/41928803/how-to-parse-json-in-kotlin/50468095 // source: https://web.archive.org/web/20210203022531/https://stackoverflow.com/questions/41928803/how-to-parse-json-in-kotlin/50468095
@ -219,4 +174,16 @@ class MainActivity: FlutterActivity() {
val EventID = this.optString("EventID") val EventID = this.optString("EventID")
val Data = this.optString("Data") val Data = this.optString("Data")
} }
class MyBroadcastReceiver(mc: MethodChannel) : BroadcastReceiver() {
val eventBus: MethodChannel = mc
override fun onReceive(context: Context, intent: Intent) {
val evtType = intent.getStringExtra("EventType") ?: ""
val evtData = intent.getStringExtra("Data") ?: ""
//val evtID = intent.getStringExtra("EventID") ?: ""//todo?
eventBus.invokeMethod(evtType, evtData)
}
}
} }

View File

@ -27,6 +27,7 @@ subprojects {
project.evaluationDependsOn(':app') project.evaluationDependsOn(':app')
} }
task clean(type: Delete) { //removed due to gradle namespace conflicts that are beyond erinn's mere mortal understanding
delete rootProject.buildDir //task clean(type: Delete) {
} // delete rootProject.buildDir
//}

Binary file not shown.

View File

@ -1,6 +1,8 @@
abstract class Cwtch { abstract class Cwtch {
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
Future<void> Start(); Future<dynamic> Start();
// ignore: non_constant_identifier_names
Future<void> ReconnectCwtchForeground();
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
void SelectProfile(String onion); void SelectProfile(String onion);
@ -38,6 +40,8 @@ abstract class Cwtch {
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
Future<dynamic> GetMessage(String profile, String handle, int index); Future<dynamic> GetMessage(String profile, String handle, int index);
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
void UpdateMessageFlags(String profile, String handle, int index, int flags);
// ignore: non_constant_identifier_names
Future<dynamic> GetMessages(String profile, String handle, int start, int end); Future<dynamic> GetMessages(String profile, String handle, int start, int end);
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
void SendMessage(String profile, String handle, String message); void SendMessage(String profile, String handle, String message);

View File

@ -178,23 +178,24 @@ class CwtchNotifier {
profileCN.getProfile(data["ProfileOnion"])?.replaceServers(data["ServerList"]); profileCN.getProfile(data["ProfileOnion"])?.replaceServers(data["ServerList"]);
break; break;
case "NewGroup": case "NewGroup":
print("new group invite: $data"); print("new group: $data");
String invite = data["GroupInvite"].toString(); String invite = data["GroupInvite"].toString();
if (invite.startsWith("torv3")) { if (invite.startsWith("torv3")) {
String inviteJson = new String.fromCharCodes(base64Decode(invite.substring(5))); String inviteJson = new String.fromCharCodes(base64Decode(invite.substring(5)));
dynamic groupInvite = jsonDecode(inviteJson); dynamic groupInvite = jsonDecode(inviteJson);
print("new group invite: $groupInvite"); print("group invite: $groupInvite");
// Retrieve Server Status from Cache... // Retrieve Server Status from Cache...
String status = ""; String status = "";
ServerInfoState? serverInfoState = profileCN.getProfile(data["ProfileOnion"])?.serverList.getServer(groupInvite["ServerHost"]); ServerInfoState? serverInfoState = profileCN.getProfile(data["ProfileOnion"])!.serverList.getServer(groupInvite["ServerHost"]);
if (serverInfoState != null) { if (serverInfoState != null) {
print("Got server status: " + serverInfoState.status);
status = serverInfoState.status; status = serverInfoState.status;
} }
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(groupInvite["GroupID"]) == null) { if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(groupInvite["GroupID"]) == null) {
profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], groupInvite["GroupID"], profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], groupInvite["GroupID"],
isInvitation: true, isInvitation: false,
imagePath: data["PicturePath"], imagePath: data["PicturePath"],
nickname: groupInvite["GroupName"], nickname: groupInvite["GroupName"],
server: groupInvite["ServerHost"], server: groupInvite["ServerHost"],

View File

@ -15,8 +15,8 @@ import '../config.dart';
/// Cwtch API /// /// Cwtch API ///
///////////////////// /////////////////////
typedef start_cwtch_function = Void Function(Pointer<Utf8> str, Int32 length, Pointer<Utf8> str2, Int32 length2); typedef start_cwtch_function = Int8 Function(Pointer<Utf8> str, Int32 length, Pointer<Utf8> str2, Int32 length2);
typedef StartCwtchFn = void Function(Pointer<Utf8> dir, int len, Pointer<Utf8> tor, int torLen); typedef StartCwtchFn = int Function(Pointer<Utf8> dir, int len, Pointer<Utf8> tor, int torLen);
typedef void_from_string_string_function = Void Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32); typedef void_from_string_string_function = Void Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32);
typedef VoidFromStringStringFn = void Function(Pointer<Utf8>, int, Pointer<Utf8>, int); typedef VoidFromStringStringFn = void Function(Pointer<Utf8>, int, Pointer<Utf8>, int);
@ -27,6 +27,9 @@ typedef VoidFromStringStringStringFn = void Function(Pointer<Utf8>, int, Pointer
typedef void_from_string_string_string_string_function = Void Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Pointer<Utf8>, Int32); typedef void_from_string_string_string_string_function = Void Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Pointer<Utf8>, Int32);
typedef VoidFromStringStringStringStringFn = void Function(Pointer<Utf8>, int, Pointer<Utf8>, int, Pointer<Utf8>, int, Pointer<Utf8>, int); typedef VoidFromStringStringStringStringFn = void Function(Pointer<Utf8>, int, Pointer<Utf8>, int, Pointer<Utf8>, int, Pointer<Utf8>, int);
typedef void_from_string_string_int_int_function = Void Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Int64, Int64);
typedef VoidFromStringStringIntIntFn = void Function(Pointer<Utf8>, int, Pointer<Utf8>, int, int, int);
typedef access_cwtch_eventbus_function = Void Function(); typedef access_cwtch_eventbus_function = Void Function();
typedef NextEventFn = void Function(); typedef NextEventFn = void Function();
@ -76,7 +79,7 @@ class CwtchFfi implements Cwtch {
} }
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
Future<void> Start() async { Future<dynamic> Start() async {
String home = ""; String home = "";
String bundledTor = ""; String bundledTor = "";
Map<String, String> envVars = Platform.environment; Map<String, String> envVars = Platform.environment;
@ -109,6 +112,14 @@ class CwtchFfi implements Cwtch {
}); });
} }
// ignore: non_constant_identifier_names
Future<void> ReconnectCwtchForeground() async {
var reconnectCwtch = library.lookup<NativeFunction<Void Function()>>("c_ReconnectCwtchForeground");
// ignore: non_constant_identifier_names
final ReconnectCwtchForeground = reconnectCwtch.asFunction<void Function()>();
ReconnectCwtchForeground();
}
// Called on object being disposed to (presumably on app close) to close the isolate that's listening to libcwtch-go events // Called on object being disposed to (presumably on app close) to close the isolate that's listening to libcwtch-go events
@override @override
void dispose() { void dispose() {
@ -384,4 +395,14 @@ class CwtchFfi implements Cwtch {
final u2 = groupHandle.toNativeUtf8(); final u2 = groupHandle.toNativeUtf8();
RejectInvite(u1, u1.length, u2, u2.length); RejectInvite(u1, u1.length, u2, u2.length);
} }
@override
void UpdateMessageFlags(String profile, String handle, int index, int flags) {
var updateMessageFlagsC = library.lookup<NativeFunction<void_from_string_string_int_int_function>>("c_UpdateMessageFlags");
// ignore: non_constant_identifier_names
final updateMessageFlags = updateMessageFlagsC.asFunction<VoidFromStringStringIntIntFn>();
final utf8profile = profile.toNativeUtf8();
final utf8handle = handle.toNativeUtf8();
updateMessageFlags(utf8profile, utf8profile.length, utf8handle, utf8handle.length, index, flags);
}
} }

View File

@ -1,5 +1,4 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io';
import 'package:cwtch/config.dart'; import 'package:cwtch/config.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -43,7 +42,7 @@ class CwtchGomobile implements Cwtch {
} }
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
Future<void> Start() async { Future<dynamic> Start() async {
print("gomobile.dart: Start()..."); print("gomobile.dart: Start()...");
var cwtchDir = path.join((await androidHomeDirectory).path, ".cwtch"); var cwtchDir = path.join((await androidHomeDirectory).path, ".cwtch");
if (EnvironmentConfig.BUILD_VER == dev_version) { if (EnvironmentConfig.BUILD_VER == dev_version) {
@ -51,7 +50,13 @@ class CwtchGomobile implements Cwtch {
} }
String torPath = path.join(await androidLibraryDir, "libtor.so"); String torPath = path.join(await androidLibraryDir, "libtor.so");
print("gomobile.dart: Start invokeMethod Start($cwtchDir, $torPath)..."); print("gomobile.dart: Start invokeMethod Start($cwtchDir, $torPath)...");
cwtchPlatform.invokeMethod("Start", {"appDir": cwtchDir, "torPath": torPath}); return cwtchPlatform.invokeMethod("Start", {"appDir": cwtchDir, "torPath": torPath});
}
@override
// ignore: non_constant_identifier_names
Future<void> ReconnectCwtchForeground() async {
cwtchPlatform.invokeMethod("ReconnectCwtchForeground", {});
} }
// Handle libcwtch-go events (received via kotlin) and dispatch to the cwtchNotifier // Handle libcwtch-go events (received via kotlin) and dispatch to the cwtchNotifier
@ -187,4 +192,10 @@ class CwtchGomobile implements Cwtch {
void LeaveGroup(String profileOnion, String groupHandle) { void LeaveGroup(String profileOnion, String groupHandle) {
cwtchPlatform.invokeMethod("LeaveGroup", {"ProfileOnion": profileOnion, "handle": groupHandle}); cwtchPlatform.invokeMethod("LeaveGroup", {"ProfileOnion": profileOnion, "handle": groupHandle});
} }
@override
void UpdateMessageFlags(String profile, String handle, int index, int flags) {
print("gomobile.dart UpdateMessageFlags " + index.toString());
cwtchPlatform.invokeMethod("UpdateMessageFlags", {"profile": profile, "contact": handle, "index": index, "flags": flags});
}
} }

View File

@ -1,5 +1,5 @@
/// Flutter icons MyFlutterApp /// Flutter icons CwtchIcons
/// Copyright (C) 2021 by original authors @ fluttericon.com, fontello.com /// Copyright (C) 2021 by Open Privacy Research Society via fluttericon.com, fontello.com
/// This font was generated by FlutterIcon.com, which is derived from Fontello. /// This font was generated by FlutterIcon.com, which is derived from Fontello.
/// ///
/// To use this font, place it in your fonts/ directory and include the /// To use this font, place it in your fonts/ directory and include the
@ -102,8 +102,9 @@ class CwtchIcons {
static const IconData add_group = IconData(0xe84e, fontFamily: _kFontFam, fontPackage: _kFontPkg); static const IconData add_group = IconData(0xe84e, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData add_peer = IconData(0xe84f, fontFamily: _kFontFam, fontPackage: _kFontPkg); static const IconData add_peer = IconData(0xe84f, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData add_24px = IconData(0xe850, fontFamily: _kFontFam, fontPackage: _kFontPkg); static const IconData add_24px = IconData(0xe850, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData address_copy_2 = IconData(0xe852, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData address = IconData(0xe856, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData send_invite = IconData(0xe888, fontFamily: _kFontFam, fontPackage: _kFontPkg); static const IconData send_invite = IconData(0xe888, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData copy_address = IconData(0xe889, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData leave_group = IconData(0xe88a, fontFamily: _kFontFam, fontPackage: _kFontPkg); static const IconData leave_group = IconData(0xe88a, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData leave_chat = IconData(0xe88b, fontFamily: _kFontFam, fontPackage: _kFontPkg); static const IconData leave_chat = IconData(0xe88b, fontFamily: _kFontFam, fontPackage: _kFontPkg);
} }

View File

@ -1,166 +1,186 @@
{ {
"@@locale": "de", "@@locale": "de",
"acceptGroupBtn": "Annehmen", "@@last_modified": "2021-06-15T02:08:49+02:00",
"acceptGroupInviteLabel": "Möchtest Du die Einladung annehmen", "createGroupTitle": "Gruppe Anlegen",
"acknowledgedLabel": "bestätigt", "serverLabel": "Server",
"addListItem": "Liste hinzufügen", "groupNameLabel": "Gruppenname",
"addListItemBtn": "Element hinzufügen", "defaultGroupName": "Tolle Gruppe",
"addNewItem": "Ein neues Element zur Liste hinzufügen", "createGroupBtn": "Anlegen",
"addNewProfileBtn": "Neues Profil hinzufügen", "profileOnionLabel": "Senden Sie diese Adresse an Peers, mit denen Sie sich verbinden möchten",
"addPeer": "Peer hinzufügen", "copyBtn": "Kopieren",
"addPeerTab": "Einen Peer hinzufügen", "copiedToClipboardNotification": "in die Zwischenablage kopiert",
"addProfileTitle": "Neues Profil hinzufügen", "addPeerTab": "Einen Peer hinzufügen",
"addressLabel": "Adresse", "createGroupTab": "Eine Gruppe erstellen",
"blockBtn": "Peer blockieren", "joinGroupTab": "Einer Gruppe beitreten",
"blocked": "Blockiert", "peerAddress": "Adresse",
"blockUnknownLabel": "Unbekannte Peers blockieren", "peerName": "Namen",
"builddate": "Aufgebaut auf: %2", "groupName": "Gruppenname",
"bulletinsBtn": "Meldungen", "server": "Server",
"chatBtn": "Chat", "invitation": "Einladung",
"chatHistoryDefault": "", "groupAddr": "Adresse",
"contactAlreadyExists": "", "addPeer": "Peer hinzufügen",
"conversationSettings": "", "createGroup": "Gruppe erstellen",
"copiedClipboardNotification": "in die Zwischenablage kopiert", "joinGroup": "Gruppe beitreten",
"copiedToClipboardNotification": "in die Zwischenablage kopiert", "newBulletinLabel": "Neue Meldung",
"copyBtn": "Kopieren", "postNewBulletinLabel": "Neue Meldung veröffentlichen",
"couldNotSendMsgError": "Nachricht konnte nicht gesendet werden", "titlePlaceholder": "Titel...",
"createGroup": "Gruppe erstellen", "pasteAddressToAddContact": "Adresse hier hinzufügen, um einen Kontakt aufzunehmen",
"createGroupBtn": "Anlegen", "blocked": "Blockiert",
"createGroupTab": "Eine Gruppe erstellen", "cycleCatsAndroid": "Click to cycle category.\nLong-press to reset.",
"createGroupTitle": "Gruppe Anlegen", "cycleCatsDesktop": "Click to cycle category.\nRight-click to reset.",
"createProfileBtn": "Profil speichern", "cycleMorphsAndroid": "Click to cycle morphs.\nLong-press to reset.",
"currentPasswordLabel": "derzeitiges Passwort", "cycleMorphsDesktop": "Click to cycle morphs.\nRight-click to reset.",
"cwtchSettingsTitle": "Cwtch Einstellungen", "cycleColoursAndroid": "Click to cycle colours.\nLong-press to reset.",
"cycleCatsAndroid": "", "cycleColoursDesktop": "Click to cycle colours.\nRight-click to reset.",
"cycleCatsDesktop": "", "search": "Suche...",
"cycleColoursAndroid": "", "invitationLabel": "Einladung",
"cycleColoursDesktop": "", "serverInfo": "Server-Informationen",
"cycleMorphsAndroid": "", "serverConnectivityConnected": "Server verbunden",
"cycleMorphsDesktop": "", "serverConnectivityDisconnected": "Server getrennt",
"dateDaysAgo": "", "serverSynced": "Synced",
"dateHoursAgo": "", "serverNotSynced": "Out of Sync",
"dateLastMonth": "", "viewServerInfo": "Server Info",
"dateLastYear": "", "saveBtn": "speichern",
"dateMinutesAgo": "", "inviteToGroupLabel": "In die Gruppe einladen",
"dateMonthsAgo": "", "inviteBtn": "Einladen",
"dateNever": "", "deleteBtn": "löschen",
"dateRightNow": "", "update": "Update",
"dateWeeksAgo": "", "searchList": "Search List",
"dateYearsAgo": "", "peerNotOnline": "Peer is Offline. Applications cannot be used right now.",
"dateYesterday": "", "addListItemBtn": "Element hinzufügen",
"defaultGroupName": "Tolle Gruppe", "membershipDescription": "Unten steht eine Liste der Benutzer, die Nachrichten an die Gruppe gesendet haben. Möglicherweise enthält diese Benutzerzliste nicht alle, die Zugang zur Gruppe haben.",
"defaultProfileName": "Alice", "dmTooltip": "Klicken, um DM zu senden",
"defaultScalingText": "defaultmäßige Textgröße (Skalierungsfaktor:", "couldNotSendMsgError": "Nachricht konnte nicht gesendet werden",
"deleteBtn": "Löschen", "acknowledgedLabel": "bestätigt",
"deleteConfirmLabel": "Geben Sie LÖSCHEN zur Bestätigung ein", "pendingLabel": "Bestätigung ausstehend",
"deleteConfirmText": "LÖSCHEN", "peerBlockedMessage": "Peer ist blockiert",
"deleteProfileBtn": "Profil löschen", "peerOfflineMessage": "Peer ist offline, Nachrichten können derzeit nicht zugestellt werden",
"deleteProfileConfirmBtn": "Profil wirklich löschen", "copiedClipboardNotification": "in die Zwischenablage kopiert",
"descriptionBlockUnknownConnections": "", "newGroupBtn": "Neue Gruppe anlegen",
"descriptionExperiments": "", "acceptGroupInviteLabel": "Möchtest Du die Einladung annehmen",
"descriptionExperimentsGroups": "", "acceptGroupBtn": "Annehmen",
"displayNameLabel": "Angezeigter Name", "rejectGroupBtn": "Ablehnen",
"dmTooltip": "Klicken, um DM zu senden", "chatBtn": "Chat",
"dontSavePeerHistory": "Peer-Verlauf löschen", "listsBtn": "Listen",
"editProfile": "Profil bearbeiten", "bulletinsBtn": "Meldungen",
"editProfileTitle": "Profil bearbeiten", "puzzleGameBtn": "Puzzlespiel",
"enableGroups": "", "addressLabel": "Adresse",
"enterCurrentPasswordForDelete": "", "displayNameLabel": "Angezeigter Name",
"enterProfilePassword": "Geben Sie ein Passwort ein, um Ihre Profile anzuzeigen", "blockBtn": "Peer blockieren",
"error0ProfilesLoadedForPassword": "0 Profile mit diesem Passwort geladen", "savePeerHistory": "Peer-Verlauf speichern",
"experimentsEnabled": "Experimente aktiviert", "savePeerHistoryDescription": "Legt fest, ob ein mit dem Peer verknüpfter Verlauf gelöscht werden soll oder nicht.",
"groupAddr": "Adresse", "dontSavePeerHistory": "Peer-Verlauf löschen",
"groupName": "Gruppenname", "unblockBtn": "Peer entblockieren",
"groupNameLabel": "Gruppenname", "addProfileTitle": "Neues Profil hinzufügen",
"invalidImportString": "", "editProfileTitle": "Profil bearbeiten",
"invitation": "Einladung", "profileName": "Anzeigename",
"invitationLabel": "Einladung", "defaultProfileName": "Alice",
"inviteBtn": "Einladen", "newProfile": "Neues Profil",
"inviteToGroup": "", "editProfile": "Profil bearbeiten",
"inviteToGroupLabel": "In die Gruppe einladen", "radioUsePassword": "Passwort",
"joinGroup": "Gruppe beitreten", "radioNoPassword": "Unverschlüsselt (kein Passwort)",
"joinGroupTab": "Einer Gruppe beitreten", "noPasswordWarning": "Wenn für dieses Konto kein Passwort verwendet wird, bedeutet dies, dass alle lokal gespeicherten Daten nicht verschlüsselt werden.",
"largeTextLabel": "Groß", "yourDisplayName": "Ihr Anzeigename",
"leaveGroup": "", "currentPasswordLabel": "derzeitiges Passwort",
"listsBtn": "Listen", "password1Label": "Passwort",
"loadingTor": "Tor wird geladen...", "password2Label": "Passwort erneut eingeben",
"localeDe": "Deutsche", "passwordErrorEmpty": "Passwort kann nicht leer sein",
"localeEn": "", "createProfileBtn": "Profil speichern",
"localeEs": "", "saveProfileBtn": "Profil speichern",
"localeFr": "", "passwordErrorMatch": "Passwörter stimmen nicht überein",
"localeIt": "", "passwordChangeError": "Fehler beim Ändern des Passworts: Das Passwort wurde abgelehnt",
"localePt": "", "deleteProfileBtn": "Profil löschen",
"membershipDescription": "Unten steht eine Liste der Benutzer, die Nachrichten an die Gruppe gesendet haben. Möglicherweise enthält diese Benutzerzliste nicht alle, die Zugang zur Gruppe haben.", "deleteConfirmLabel": "Geben Sie LÖSCHEN zur Bestätigung ein",
"networkStatusAttemptingTor": "Versuche, eine Verbindung mit dem Tor-Netzwerk herzustellen", "deleteProfileConfirmBtn": "Profil wirklich löschen",
"networkStatusConnecting": "Verbinde zu Netzwerk und Peers ...", "deleteConfirmText": "LÖSCHEN",
"networkStatusDisconnected": "Vom Internet getrennt, überprüfen Sie Ihre Verbindung", "addNewProfileBtn": "Neues Profil hinzufügen",
"networkStatusOnline": "Online", "enterProfilePassword": "Geben Sie ein Passwort ein, um Ihre Profile anzuzeigen",
"newBulletinLabel": "Neue Meldung", "password": "Passwort",
"newConnectionPaneTitle": "Neue Verbindung", "error0ProfilesLoadedForPassword": "0 Profile mit diesem Passwort geladen",
"newGroupBtn": "Neue Gruppe anlegen", "yourProfiles": "Ihre Profile",
"newPassword": "", "yourServers": "Ihre Server",
"newProfile": "Neues Profil", "unlock": "Entsperren",
"noPasswordWarning": "Wenn für dieses Konto kein Passwort verwendet wird, bedeutet dies, dass alle lokal gespeicherten Daten nicht verschlüsselt werden.", "cwtchSettingsTitle": "Cwtch Einstellungen",
"password": "Passwort", "versionBuilddate": "Version: %1 Aufgebaut auf: %2",
"password1Label": "Passwort", "zoomLabel": "Benutzeroberflächen-Zoom (betriftt hauptsächlich Text- und Knopgrößen)",
"password2Label": "Passwort erneut eingeben", "blockUnknownLabel": "Unbekannte Peers blockieren",
"passwordChangeError": "Fehler beim Ändern des Passworts: Das Passwort wurde abgelehnt", "settingLanguage": "Sprache",
"passwordErrorEmpty": "Passwort kann nicht leer sein", "localeEn": "English",
"passwordErrorMatch": "Passwörter stimmen nicht überein", "localeFr": "Frances",
"pasteAddressToAddContact": "Adresse hier hinzufügen, um einen Kontakt aufzunehmen", "localePt": "Portuguesa",
"peerAddress": "Adresse", "localeDe": "Deutsche",
"peerBlockedMessage": "Peer ist blockiert", "settingInterfaceZoom": "Zoomstufe",
"peerName": "Namen", "largeTextLabel": "Groß",
"peerNotOnline": "", "settingTheme": "Thema",
"peerOfflineMessage": "Peer ist offline, Nachrichten können derzeit nicht zugestellt werden", "themeLight": "Licht",
"pendingLabel": "Bestätigung ausstehend", "themeDark": "Dunkel",
"postNewBulletinLabel": "Neue Meldung veröffentlichen", "experimentsEnabled": "Experimente aktiviert",
"profileName": "Anzeigename", "versionTor": "Version %1 mit tor %2",
"profileOnionLabel": "Senden Sie diese Adresse an Peers, mit denen Sie sich verbinden möchten", "version": "Version %1",
"puzzleGameBtn": "Puzzlespiel", "builddate": "Aufgebaut auf: %2",
"radioNoPassword": "Unverschlüsselt (kein Passwort)", "defaultScalingText": "defaultmäßige Textgröße (Skalierungsfaktor:",
"radioUsePassword": "Passwort", "smallTextLabel": "Klein",
"reallyLeaveThisGroupPrompt": "", "loadingTor": "Tor wird geladen...",
"rejectGroupBtn": "Ablehnen", "viewGroupMembershipTooltip": "Gruppenmitgliedschaft anzeigen",
"saveBtn": "Speichern", "networkStatusDisconnected": "Vom Internet getrennt, überprüfen Sie Ihre Verbindung",
"savePeerHistory": "Peer-Verlauf speichern", "networkStatusAttemptingTor": "Versuche, eine Verbindung mit dem Tor-Netzwerk herzustellen",
"savePeerHistoryDescription": "Legt fest, ob ein mit dem Peer verknüpfter Verlauf gelöscht werden soll oder nicht.", "networkStatusConnecting": "Verbinde zu Netzwerk und Peers ...",
"saveProfileBtn": "Profil speichern", "networkStatusOnline": "Online",
"search": "Suche...", "newConnectionPaneTitle": "Neue Verbindung",
"searchList": "", "addListItem": "Liste hinzufügen",
"server": "Server", "addNewItem": "Ein neues Element zur Liste hinzufügen",
"serverConnectivityConnected": "Server verbunden", "todoPlaceholder": "noch zu erledigen",
"serverConnectivityDisconnected": "Server getrennt", "localeEs": "Espanol",
"serverInfo": "Server-Informationen", "localeIt": "Italiana",
"serverLabel": "Server", "enableGroups": "Enable Group Chat",
"serverNotSynced": "", "enterCurrentPasswordForDelete": "Please enter current password to delete this profile.",
"serverSynced": "", "conversationSettings": "Conversation Settings",
"settingInterfaceZoom": "Zoomstufe", "invalidImportString": "Invalid import string",
"settingLanguage": "Sprache", "contactAlreadyExists": "Contact Already Exists",
"settingTheme": "Thema", "tooltipOpenSettings": "Open the settings pane",
"smallTextLabel": "Klein", "tooltipAddContact": "Add a new contact or conversation",
"successfullAddedContact": "", "titleManageContacts": "Conversations",
"themeDark": "Dunkel", "tooltipUnlockProfiles": "Unlock encrypted profiles by entering their password.",
"themeLight": "Licht", "titleManageProfiles": "Manage Cwtch Profiles",
"titleManageContacts": "", "descriptionExperiments": "Cwtch experiments are optional, opt-in features that add additional functionality to Cwtch that may have different privacy considerations than traditional 1:1 metadata resistant chat e.g. group chat, bot integration etc.",
"titleManageProfiles": "", "descriptionExperimentsGroups": "The group experiment allows Cwtch to connect with untrusted server infrastructure to facilitate communication with more than one contact.",
"titleManageServers": "", "descriptionBlockUnknownConnections": "If turned on, this option will automatically close connections from Cwtch users that have not been added to your contact list.",
"titlePlaceholder": "Titel...", "successfullAddedContact": "Successfully added ",
"todoPlaceholder": "noch zu erledigen", "dateRightNow": "Right Now",
"tooltipAddContact": "", "dateMinutesAgo": "Minutes Ago",
"tooltipOpenSettings": "", "dateHoursAgo": "Hours Ago",
"tooltipUnlockProfiles": "", "dateDaysAgo": "Days Ago",
"unblockBtn": "Peer entblockieren", "dateWeeksAgo": "Weeks Ago",
"unlock": "Entsperren", "dateLastMonth": "Last Month",
"update": "", "dateYesterday": "Yesterday",
"version": "Version %1", "dateLastYear": "Last Year",
"versionBuilddate": "Version: %1 Aufgebaut auf: %2", "dateYearsAgo": "X Years Ago (displayed next to a contact row to indicate time of last action)",
"versionTor": "Version %1 mit tor %2", "dateNever": "Never",
"viewGroupMembershipTooltip": "Gruppenmitgliedschaft anzeigen", "dateMonthsAgo": "Months Ago",
"viewServerInfo": "", "titleManageServers": "Manage Servers",
"yesLeave": "", "inviteToGroup": "You have been invited to join a group:",
"yourDisplayName": "Ihr Anzeigename", "leaveGroup": "Leave This Conversation",
"yourProfiles": "Ihre Profile", "reallyLeaveThisGroupPrompt": "Are you sure you want to leave this conversation? All messages and attributes will be deleted.",
"yourServers": "Ihre Server", "yesLeave": "Yes, Leave This Conversation",
"zoomLabel": "Benutzeroberflächen-Zoom (betriftt hauptsächlich Text- und Knopgrößen)" "newPassword": "New Password",
"chatHistoryDefault": "This conversation will be deleted when Cwtch is closed! Message history can be enabled per-conversation via the Settings menu in the upper right.",
"accepted": "Accepted!",
"rejected": "Rejected!",
"contactSuggestion": "This is a contact suggestion for: ",
"sendAnInvitation": "You sent an invitation for: ",
"torVersion": "Tor Version",
"torStatus": "Tor Status",
"resetTor": "Reset",
"cancel": "Cancel",
"sendMessage": "Send Message",
"sendInvite": "Send a contact or group invite",
"deleteProfileSuccess": "Successfully deleted profile",
"addServerFirst": "You need to add a server before you can create a group",
"nickChangeSuccess": "Profile nickname changed successfully",
"createProfileToBegin": "Please create or unlock a profile to begin",
"addContactFirst": "Add or pick a contact to begin chatting.",
"torNetworkStatus": "Tor network status",
"debugLog": "Turn on console debug logging",
"profileDeleteSuccess": "Successfully deleted profile",
"malformedMessage": "Malformed message"
} }

View File

@ -1,166 +1,186 @@
{ {
"@@locale": "en", "@@locale": "en",
"acceptGroupBtn": "Accept", "@@last_modified": "2021-06-15T02:08:49+02:00",
"acceptGroupInviteLabel": "Do you want to accept the invitation to", "createGroupTitle": "Create Group",
"acknowledgedLabel": "Acknowledged", "serverLabel": "Server",
"addListItem": "Add a New List Item", "groupNameLabel": "Group Name",
"addListItemBtn": "Add Item", "defaultGroupName": "Awesome Group",
"addNewItem": "Add a new item to the list", "createGroupBtn": "Create",
"addNewProfileBtn": "Add new profile", "profileOnionLabel": "Send this address to peers you want to connect with",
"addPeer": "Add Peer", "copyBtn": "Copy",
"addPeerTab": "Add a peer", "copiedToClipboardNotification": "Copied to Clipboard",
"addProfileTitle": "Add new profile", "addPeerTab": "Add a peer",
"addressLabel": "Address", "createGroupTab": "Create a group",
"blockBtn": "Block Peer", "joinGroupTab": "Join a group",
"blocked": "Blocked", "peerAddress": "Address",
"blockUnknownLabel": "Block Unknown Peers", "peerName": "Name",
"builddate": "Built on: %2", "groupName": "Group name",
"bulletinsBtn": "Bulletins", "server": "Server",
"chatBtn": "Chat", "invitation": "Invitation",
"chatHistoryDefault": "This conversation will be deleted when Cwtch is closed! Message history can be enabled per-conversation via the Settings menu in the upper right.", "groupAddr": "Address",
"contactAlreadyExists": "Contact Already Exists", "addPeer": "Add Peer",
"conversationSettings": "Conversation Settings", "createGroup": "Create group",
"copiedClipboardNotification": "Copied to clipboard", "joinGroup": "Join group",
"copiedToClipboardNotification": "Copied to Clipboard", "newBulletinLabel": "New Bulletin",
"copyBtn": "Copy", "postNewBulletinLabel": "Post new bulletin",
"couldNotSendMsgError": "Could not send this message", "titlePlaceholder": "title...",
"createGroup": "Create group", "pasteAddressToAddContact": "Paste a cwtch address, invitation or key bundle here to add a new conversation",
"createGroupBtn": "Create", "blocked": "Blocked",
"createGroupTab": "Create a group", "cycleCatsAndroid": "Click to cycle category.\nLong-press to reset.",
"createGroupTitle": "Create Group", "cycleCatsDesktop": "Click to cycle category.\nRight-click to reset.",
"createProfileBtn": "Create Profile", "cycleMorphsAndroid": "Click to cycle morphs.\nLong-press to reset.",
"currentPasswordLabel": "Current Password", "cycleMorphsDesktop": "Click to cycle morphs.\nRight-click to reset.",
"cwtchSettingsTitle": "Cwtch Settings", "cycleColoursAndroid": "Click to cycle colours.\nLong-press to reset.",
"cycleCatsAndroid": "Click to cycle category.\\nLong-press to reset.", "cycleColoursDesktop": "Click to cycle colours.\nRight-click to reset.",
"cycleCatsDesktop": "Click to cycle category.\\nRight-click to reset.", "search": "Search...",
"cycleColoursAndroid": "Click to cycle colours.\\nLong-press to reset.", "invitationLabel": "Invitation",
"cycleColoursDesktop": "Click to cycle colours.\\nRight-click to reset.", "serverInfo": "Server Information",
"cycleMorphsAndroid": "Click to cycle morphs.\\nLong-press to reset.", "serverConnectivityConnected": "Server Connected",
"cycleMorphsDesktop": "Click to cycle morphs.\\nRight-click to reset.", "serverConnectivityDisconnected": "Server Disconnected",
"dateDaysAgo": "Days Ago", "serverSynced": "Synced",
"dateHoursAgo": "Hours Ago", "serverNotSynced": "Out of Sync",
"dateLastMonth": "Last Month", "viewServerInfo": "Server Info",
"dateLastYear": "Last Year", "saveBtn": "Save",
"dateMinutesAgo": "Minutes Ago", "inviteToGroupLabel": "Invite to group",
"dateMonthsAgo": "Months Ago", "inviteBtn": "Invite",
"dateNever": "Never", "deleteBtn": "Delete",
"dateRightNow": "Right Now", "update": "Update",
"dateWeeksAgo": "Weeks Ago", "searchList": "Search List",
"dateYearsAgo": "X Years Ago (displayed next to a contact row to indicate time of last action)", "peerNotOnline": "Peer is Offline. Applications cannot be used right now.",
"dateYesterday": "Yesterday", "addListItemBtn": "Add Item",
"defaultGroupName": "Awesome Group", "membershipDescription": "Below is a list of users who have sent messages to the group. This list may not reflect all users who have access to the group.",
"defaultProfileName": "Alice", "dmTooltip": "Click to DM",
"defaultScalingText": "Default size text (scale factor:", "couldNotSendMsgError": "Could not send this message",
"deleteBtn": "Delete", "acknowledgedLabel": "Acknowledged",
"deleteConfirmLabel": "Type DELETE to confirm", "pendingLabel": "Pending",
"deleteConfirmText": "DELETE", "peerBlockedMessage": "Peer is blocked",
"deleteProfileBtn": "Delete Profile", "peerOfflineMessage": "Peer is offline, messages can't be delivered right now",
"deleteProfileConfirmBtn": "Really Delete Profile", "copiedClipboardNotification": "Copied to clipboard",
"descriptionBlockUnknownConnections": "If turned on, this option will automatically close connections from Cwtch users that have not been added to your contact list.", "newGroupBtn": "Create new group",
"descriptionExperiments": "Cwtch experiments are optional, opt-in features that add additional functionality to Cwtch that may have different privacy considerations than traditional 1:1 metadata resistant chat e.g. group chat, bot integration etc.", "acceptGroupInviteLabel": "Do you want to accept the invitation to",
"descriptionExperimentsGroups": "The group experiment allows Cwtch to connect with untrusted server infrastructure to facilitate communication with more than one contact.", "acceptGroupBtn": "Accept",
"displayNameLabel": "Display Name", "rejectGroupBtn": "Reject",
"dmTooltip": "Click to DM", "chatBtn": "Chat",
"dontSavePeerHistory": "Delete Peer History", "listsBtn": "Lists",
"editProfile": "Edit Profille", "bulletinsBtn": "Bulletins",
"editProfileTitle": "Edit Profile", "puzzleGameBtn": "Puzzle Game",
"enableGroups": "Enable Group Chat", "addressLabel": "Address",
"enterCurrentPasswordForDelete": "Please enter current password to delete this profile.", "displayNameLabel": "Display Name",
"enterProfilePassword": "Enter a password to view your profiles", "blockBtn": "Block Peer",
"error0ProfilesLoadedForPassword": "0 profiles loaded with that password", "savePeerHistory": "Save Peer History",
"experimentsEnabled": "Enable Experiments", "savePeerHistoryDescription": "Determines whether or not to delete any history associated with the peer.",
"groupAddr": "Address", "dontSavePeerHistory": "Delete Peer History",
"groupName": "Group name", "unblockBtn": "Unblock Peer",
"groupNameLabel": "Group Name", "addProfileTitle": "Add new profile",
"invalidImportString": "Invalid import string", "editProfileTitle": "Edit Profile",
"invitation": "Invitation", "profileName": "Display name",
"invitationLabel": "Invitation", "defaultProfileName": "Alice",
"inviteBtn": "Invite", "newProfile": "New Profile",
"inviteToGroup": "You have been invited to join a group:", "editProfile": "Edit Profille",
"inviteToGroupLabel": "Invite to group", "radioUsePassword": "Password",
"joinGroup": "Join group", "radioNoPassword": "Unencrypted (No password)",
"joinGroupTab": "Join a group", "noPasswordWarning": "Not using a password on this account means that all data stored locally will not be encrypted",
"largeTextLabel": "Large", "yourDisplayName": "Your Display Name",
"leaveGroup": "Leave This Conversation", "currentPasswordLabel": "Current Password",
"listsBtn": "Lists", "password1Label": "Password",
"loadingTor": "Loading tor...", "password2Label": "Reenter password",
"localeDe": "Deutsche", "passwordErrorEmpty": "Password cannot be empty",
"localeEn": "English", "createProfileBtn": "Create Profile",
"localeEs": "Espanol", "saveProfileBtn": "Save Profile",
"localeFr": "Frances", "passwordErrorMatch": "Passwords do not match",
"localeIt": "Italiana", "passwordChangeError": "Error changing password: Supplied password rejected",
"localePt": "Portuguesa", "deleteProfileBtn": "Delete Profile",
"membershipDescription": "Below is a list of users who have sent messages to the group. This list may not reflect all users who have access to the group.", "deleteConfirmLabel": "Type DELETE to confirm",
"networkStatusAttemptingTor": "Attempting to connect to Tor network", "deleteProfileConfirmBtn": "Really Delete Profile",
"networkStatusConnecting": "Connecting to network and peers...", "deleteConfirmText": "DELETE",
"networkStatusDisconnected": "Disconnected from the internet, check your connection", "addNewProfileBtn": "Add new profile",
"networkStatusOnline": "Online", "enterProfilePassword": "Enter a password to view your profiles",
"newBulletinLabel": "New Bulletin", "password": "Password",
"newConnectionPaneTitle": "New Connection", "error0ProfilesLoadedForPassword": "0 profiles loaded with that password",
"newGroupBtn": "Create new group", "yourProfiles": "Your Profiles",
"newPassword": "New Password", "yourServers": "Your Servers",
"newProfile": "New Profile", "unlock": "Unlock",
"noPasswordWarning": "Not using a password on this account means that all data stored locally will not be encrypted", "cwtchSettingsTitle": "Cwtch Settings",
"password": "Password", "versionBuilddate": "Version: %1 Built on: %2",
"password1Label": "Password", "zoomLabel": "Interface zoom (mostly affects text and button sizes)",
"password2Label": "Reenter password", "blockUnknownLabel": "Block Unknown Peers",
"passwordChangeError": "Error changing password: Supplied password rejected", "settingLanguage": "Language",
"passwordErrorEmpty": "Password cannot be empty", "localeEn": "English",
"passwordErrorMatch": "Passwords do not match", "localeFr": "Frances",
"pasteAddressToAddContact": "Paste a cwtch address, invitation or key bundle here to add a new conversation", "localePt": "Portuguesa",
"peerAddress": "Address", "localeDe": "Deutsche",
"peerBlockedMessage": "Peer is blocked", "settingInterfaceZoom": "Zoom level",
"peerName": "Name", "largeTextLabel": "Large",
"peerNotOnline": "Peer is Offline. Applications cannot be used right now.", "settingTheme": "Theme",
"peerOfflineMessage": "Peer is offline, messages can't be delivered right now", "themeLight": "Light",
"pendingLabel": "Pending", "themeDark": "Dark",
"postNewBulletinLabel": "Post new bulletin", "experimentsEnabled": "Enable Experiments",
"profileName": "Display name", "versionTor": "Version %1 with tor %2",
"profileOnionLabel": "Send this address to peers you want to connect with", "version": "Version %1",
"puzzleGameBtn": "Puzzle Game", "builddate": "Built on: %2",
"radioNoPassword": "Unencrypted (No password)", "defaultScalingText": "Default size text (scale factor:",
"radioUsePassword": "Password", "smallTextLabel": "Small",
"reallyLeaveThisGroupPrompt": "Are you sure you want to leave this conversation? All messages and attributes will be deleted.", "loadingTor": "Loading tor...",
"rejectGroupBtn": "Reject", "viewGroupMembershipTooltip": "View Group Membership",
"saveBtn": "Save", "networkStatusDisconnected": "Disconnected from the internet, check your connection",
"savePeerHistory": "Save Peer History", "networkStatusAttemptingTor": "Attempting to connect to Tor network",
"savePeerHistoryDescription": "Determines whether or not to delete any history associated with the peer.", "networkStatusConnecting": "Connecting to network and peers...",
"saveProfileBtn": "Save Profile", "networkStatusOnline": "Online",
"search": "Search...", "newConnectionPaneTitle": "New Connection",
"searchList": "Search List", "addListItem": "Add a New List Item",
"server": "Server", "addNewItem": "Add a new item to the list",
"serverConnectivityConnected": "Server Connected", "todoPlaceholder": "Todo...",
"serverConnectivityDisconnected": "Server Disconnected", "localeEs": "Espanol",
"serverInfo": "Server Information", "localeIt": "Italiana",
"serverLabel": "Server", "enableGroups": "Enable Group Chat",
"serverNotSynced": "Out of Sync", "enterCurrentPasswordForDelete": "Please enter current password to delete this profile.",
"serverSynced": "Synced", "conversationSettings": "Conversation Settings",
"settingInterfaceZoom": "Zoom level", "invalidImportString": "Invalid import string",
"settingLanguage": "Language", "contactAlreadyExists": "Contact Already Exists",
"settingTheme": "Theme", "tooltipOpenSettings": "Open the settings pane",
"smallTextLabel": "Small", "tooltipAddContact": "Add a new contact or conversation",
"successfullAddedContact": "Successfully added ", "titleManageContacts": "Conversations",
"themeDark": "Dark", "tooltipUnlockProfiles": "Unlock encrypted profiles by entering their password.",
"themeLight": "Light", "titleManageProfiles": "Manage Cwtch Profiles",
"titleManageContacts": "Conversations", "descriptionExperiments": "Cwtch experiments are optional, opt-in features that add additional functionality to Cwtch that may have different privacy considerations than traditional 1:1 metadata resistant chat e.g. group chat, bot integration etc.",
"titleManageProfiles": "Manage Cwtch Profiles", "descriptionExperimentsGroups": "The group experiment allows Cwtch to connect with untrusted server infrastructure to facilitate communication with more than one contact.",
"titleManageServers": "Manage Servers", "descriptionBlockUnknownConnections": "If turned on, this option will automatically close connections from Cwtch users that have not been added to your contact list.",
"titlePlaceholder": "title...", "successfullAddedContact": "Successfully added ",
"todoPlaceholder": "Todo...", "dateRightNow": "Right Now",
"tooltipAddContact": "Add a new contact or conversation", "dateMinutesAgo": "Minutes Ago",
"tooltipOpenSettings": "Open the settings pane", "dateHoursAgo": "Hours Ago",
"tooltipUnlockProfiles": "Unlock encrypted profiles by entering their password.", "dateDaysAgo": "Days Ago",
"unblockBtn": "Unblock Peer", "dateWeeksAgo": "Weeks Ago",
"unlock": "Unlock", "dateLastMonth": "Last Month",
"update": "Update", "dateYesterday": "Yesterday",
"version": "Version %1", "dateLastYear": "Last Year",
"versionBuilddate": "Version: %1 Built on: %2", "dateYearsAgo": "X Years Ago (displayed next to a contact row to indicate time of last action)",
"versionTor": "Version %1 with tor %2", "dateNever": "Never",
"viewGroupMembershipTooltip": "View Group Membership", "dateMonthsAgo": "Months Ago",
"viewServerInfo": "Server Info", "titleManageServers": "Manage Servers",
"yesLeave": "Yes, Leave This Conversation", "inviteToGroup": "You have been invited to join a group:",
"yourDisplayName": "Your Display Name", "leaveGroup": "Leave This Conversation",
"yourProfiles": "Your Profiles", "reallyLeaveThisGroupPrompt": "Are you sure you want to leave this conversation? All messages and attributes will be deleted.",
"yourServers": "Your Servers", "yesLeave": "Yes, Leave This Conversation",
"zoomLabel": "Interface zoom (mostly affects text and button sizes)" "newPassword": "New Password",
"chatHistoryDefault": "This conversation will be deleted when Cwtch is closed! Message history can be enabled per-conversation via the Settings menu in the upper right.",
"accepted": "Accepted!",
"rejected": "Rejected!",
"contactSuggestion": "This is a contact suggestion for: ",
"sendAnInvitation": "You sent an invitation for: ",
"torVersion": "Tor Version",
"torStatus": "Tor Status",
"resetTor": "Reset",
"cancel": "Cancel",
"sendMessage": "Send Message",
"sendInvite": "Send a contact or group invite",
"deleteProfileSuccess": "Successfully deleted profile",
"addServerFirst": "You need to add a server before you can create a group",
"nickChangeSuccess": "Profile nickname changed successfully",
"createProfileToBegin": "Please create or unlock a profile to begin",
"addContactFirst": "Add or pick a contact to begin chatting.",
"torNetworkStatus": "Tor network status",
"debugLog": "Turn on console debug logging",
"profileDeleteSuccess": "Successfully deleted profile",
"malformedMessage": "Malformed message"
} }

View File

@ -1,166 +1,186 @@
{ {
"@@locale": "es", "@@locale": "es",
"acceptGroupBtn": "Aceptar", "@@last_modified": "2021-06-15T02:08:49+02:00",
"acceptGroupInviteLabel": "¿Quieres aceptar la invitación a ", "createGroupTitle": "Crear un grupo",
"acknowledgedLabel": "Reconocido", "serverLabel": "Servidor",
"addListItem": "Añadir un nuevo elemento a la lista", "groupNameLabel": "Nombre del grupo",
"addListItemBtn": "Agregar artículo", "defaultGroupName": "El Grupo Asombroso",
"addNewItem": "Añadir un nuevo elemento a la lista", "createGroupBtn": "Crear",
"addNewProfileBtn": "Agregar nuevo perfil", "profileOnionLabel": "Envía esta dirección a los contactos con los que quieras conectarte",
"addPeer": "Agregar Contacto", "copyBtn": "Copiar",
"addPeerTab": "Agregar Contacto", "copiedToClipboardNotification": "Copiado al portapapeles",
"addProfileTitle": "Agregar nuevo perfil", "addPeerTab": "Agregar Contacto",
"addressLabel": "Dirección", "createGroupTab": "Crear un grupo",
"blockBtn": "Bloquear contacto", "joinGroupTab": "Únete a un grupo",
"blocked": "Bloqueado", "peerAddress": "Dirección",
"blockUnknownLabel": "Bloquear conexiones desconocidas", "peerName": "Nombre",
"builddate": "Basado en: %2", "groupName": "Nombre del grupo",
"bulletinsBtn": "Boletines", "server": "Servidor",
"chatBtn": "Chat", "invitation": "Invitación",
"chatHistoryDefault": "", "groupAddr": "Dirección",
"contactAlreadyExists": "", "addPeer": "Agregar Contacto",
"conversationSettings": "", "createGroup": "Crear perfil",
"copiedClipboardNotification": "Copiado al portapapeles", "joinGroup": "Únete al grupo",
"copiedToClipboardNotification": "Copiado al portapapeles", "newBulletinLabel": "Nuevo Boletín",
"copyBtn": "Copiar", "postNewBulletinLabel": "Publicar nuevo boletín",
"couldNotSendMsgError": "No se pudo enviar este mensaje", "titlePlaceholder": "título...",
"createGroup": "Crear perfil", "pasteAddressToAddContact": "...pegar una dirección aquí para añadir contacto...",
"createGroupBtn": "Crear", "blocked": "Bloqueado",
"createGroupTab": "Crear un grupo", "cycleCatsAndroid": "Click para cambiar categoría. Mantenga pulsado para reiniciar.",
"createGroupTitle": "Crear un grupo", "cycleCatsDesktop": "Click para cambiar categoría. Click derecho para reiniciar.",
"createProfileBtn": "Crear perfil", "cycleMorphsAndroid": "Click para cambiar transformaciones. Mantenga pulsado para reiniciar.",
"currentPasswordLabel": "Contraseña actual", "cycleMorphsDesktop": "Click para cambiar transformaciones. Click derecho para reiniciar.",
"cwtchSettingsTitle": "Configuración de Cwtch", "cycleColoursAndroid": "Click para cambiar colores. Mantenga pulsado para reiniciar.",
"cycleCatsAndroid": "Click para cambiar categoría. Mantenga pulsado para reiniciar.", "cycleColoursDesktop": "Click para cambiar colores. Click derecho para reiniciar.",
"cycleCatsDesktop": "Click para cambiar categoría. Click derecho para reiniciar.", "search": "Búsqueda...",
"cycleColoursAndroid": "Click para cambiar colores. Mantenga pulsado para reiniciar.", "invitationLabel": "Invitación",
"cycleColoursDesktop": "Click para cambiar colores. Click derecho para reiniciar.", "serverInfo": "Información del servidor",
"cycleMorphsAndroid": "Click para cambiar transformaciones. Mantenga pulsado para reiniciar.", "serverConnectivityConnected": "Servidor conectado",
"cycleMorphsDesktop": "Click para cambiar transformaciones. Click derecho para reiniciar.", "serverConnectivityDisconnected": "Servidor desconectado",
"dateDaysAgo": "", "serverSynced": "Sincronizado",
"dateHoursAgo": "", "serverNotSynced": "Fuera de sincronización con el servidor",
"dateLastMonth": "", "viewServerInfo": "Información del servidor",
"dateLastYear": "", "saveBtn": "Guardar",
"dateMinutesAgo": "", "inviteToGroupLabel": "Invitar al grupo",
"dateMonthsAgo": "", "inviteBtn": "Invitar",
"dateNever": "", "deleteBtn": "Eliminar",
"dateRightNow": "", "update": "Actualizar",
"dateWeeksAgo": "", "searchList": "Buscar en la lista",
"dateYearsAgo": "", "peerNotOnline": "Este contacto no está en línea, la aplicación no puede ser usada en este momento",
"dateYesterday": "", "addListItemBtn": "Agregar artículo",
"defaultGroupName": "El Grupo Asombroso", "membershipDescription": "La lista a continuación solo muestra los miembros que han enviado mensajes al grupo, no incluye a todos los usuarios dentro del grupo",
"defaultProfileName": "Alicia", "dmTooltip": "Haz clic para enviar mensaje directo",
"defaultScalingText": "Tamaño predeterminado de texto (factor de escala:", "couldNotSendMsgError": "No se pudo enviar este mensaje",
"deleteBtn": "Eliminar", "acknowledgedLabel": "Reconocido",
"deleteConfirmLabel": "Escribe ELIMINAR para confirmar", "pendingLabel": "Pendiente",
"deleteConfirmText": "ELIMINAR", "peerBlockedMessage": "Contacto bloqueado",
"deleteProfileBtn": "Eliminar Perfil", "peerOfflineMessage": "Este contacto no está en línea, los mensajes no pueden ser entregados en este momento",
"deleteProfileConfirmBtn": "Confirmar eliminar perfil", "copiedClipboardNotification": "Copiado al portapapeles",
"descriptionBlockUnknownConnections": "", "newGroupBtn": "Crear un nuevo grupo de chat",
"descriptionExperiments": "", "acceptGroupInviteLabel": "¿Quieres aceptar la invitación a ",
"descriptionExperimentsGroups": "", "acceptGroupBtn": "Aceptar",
"displayNameLabel": "Nombre de Usuario", "rejectGroupBtn": "Rechazar",
"dmTooltip": "Haz clic para enviar mensaje directo", "chatBtn": "Chat",
"dontSavePeerHistory": "Eliminar historial de contacto", "listsBtn": "Listas",
"editProfile": "Editar perfil", "bulletinsBtn": "Boletines",
"editProfileTitle": "Editar perfil", "puzzleGameBtn": "Juego de rompecabezas",
"enableGroups": "", "addressLabel": "Dirección",
"enterCurrentPasswordForDelete": "", "displayNameLabel": "Nombre de Usuario",
"enterProfilePassword": "Ingresa tu contraseña para ver tus perfiles", "blockBtn": "Bloquear contacto",
"error0ProfilesLoadedForPassword": "0 perfiles cargados con esa contraseña", "savePeerHistory": "Guardar el historial con contacto",
"experimentsEnabled": "Experimentos habilitados", "savePeerHistoryDescription": "Determina si eliminar o no el historial asociado con el contacto.",
"groupAddr": "Dirección", "dontSavePeerHistory": "Eliminar historial de contacto",
"groupName": "Nombre del grupo", "unblockBtn": "Desbloquear contacto",
"groupNameLabel": "Nombre del grupo", "addProfileTitle": "Agregar nuevo perfil",
"invalidImportString": "", "editProfileTitle": "Editar perfil",
"invitation": "Invitación", "profileName": "Nombre de Usuario",
"invitationLabel": "Invitación", "defaultProfileName": "Alicia",
"inviteBtn": "Invitar", "newProfile": "Nuevo perfil",
"inviteToGroup": "", "editProfile": "Editar perfil",
"inviteToGroupLabel": "Invitar al grupo", "radioUsePassword": "Contraseña",
"joinGroup": "Únete al grupo", "radioNoPassword": "Sin cifrado (sin contraseña)",
"joinGroupTab": "Únete a un grupo", "noPasswordWarning": "No usar una contraseña para esta cuenta significa que los datos almacenados localmente no serán encriptados",
"largeTextLabel": "Grande", "yourDisplayName": "Tu nombre de usuario",
"leaveGroup": "", "currentPasswordLabel": "Contraseña actual",
"listsBtn": "Listas", "password1Label": "Contraseña",
"loadingTor": "Cargando tor...", "password2Label": "Vuelve a ingresar tu contraseña",
"localeDe": "Alemán", "passwordErrorEmpty": "El campo de contraseña no puede estar vacío",
"localeEn": "Inglés", "createProfileBtn": "Crear perfil",
"localeEs": "Español", "saveProfileBtn": "Guardar perfil",
"localeFr": "Francés", "passwordErrorMatch": "Las contraseñas no coinciden",
"localeIt": "Italiano", "passwordChangeError": "Hubo un error cambiando tu contraseña: la contraseña ingresada fue rechazada",
"localePt": "Portugués", "deleteProfileBtn": "Eliminar Perfil",
"membershipDescription": "La lista a continuación solo muestra los miembros que han enviado mensajes al grupo, no incluye a todos los usuarios dentro del grupo", "deleteConfirmLabel": "Escribe ELIMINAR para confirmar",
"networkStatusAttemptingTor": "Intentando conectarse a la red Tor", "deleteProfileConfirmBtn": "Confirmar eliminar perfil",
"networkStatusConnecting": "Conectando a la red y a los contactos...", "deleteConfirmText": "ELIMINAR",
"networkStatusDisconnected": "Sin conexión, comprueba tu conexión", "addNewProfileBtn": "Agregar nuevo perfil",
"networkStatusOnline": "En línea", "enterProfilePassword": "Ingresa tu contraseña para ver tus perfiles",
"newBulletinLabel": "Nuevo Boletín", "password": "Contraseña",
"newConnectionPaneTitle": "Nueva conexión", "error0ProfilesLoadedForPassword": "0 perfiles cargados con esa contraseña",
"newGroupBtn": "Crear un nuevo grupo de chat", "yourProfiles": "Tus perfiles",
"newPassword": "", "yourServers": "Tus servidores",
"newProfile": "Nuevo perfil", "unlock": "Desbloquear",
"noPasswordWarning": "No usar una contraseña para esta cuenta significa que los datos almacenados localmente no serán encriptados", "cwtchSettingsTitle": "Configuración de Cwtch",
"password": "Contraseña", "versionBuilddate": "Versión: %1 Basado en %2",
"password1Label": "Contraseña", "zoomLabel": "Zoom de la interfaz (afecta principalmente el tamaño del texto y de los botones)",
"password2Label": "Vuelve a ingresar tu contraseña", "blockUnknownLabel": "Bloquear conexiones desconocidas",
"passwordChangeError": "Hubo un error cambiando tu contraseña: la contraseña ingresada fue rechazada", "settingLanguage": "Idioma",
"passwordErrorEmpty": "El campo de contraseña no puede estar vacío", "localeEn": "Inglés",
"passwordErrorMatch": "Las contraseñas no coinciden", "localeFr": "Francés",
"pasteAddressToAddContact": "...pegar una dirección aquí para añadir contacto...", "localePt": "Portugués",
"peerAddress": "Dirección", "localeDe": "Alemán",
"peerBlockedMessage": "Contacto bloqueado", "settingInterfaceZoom": "Nivel de zoom",
"peerName": "Nombre", "largeTextLabel": "Grande",
"peerNotOnline": "Este contacto no está en línea, la aplicación no puede ser usada en este momento", "settingTheme": "Tema",
"peerOfflineMessage": "Este contacto no está en línea, los mensajes no pueden ser entregados en este momento", "themeLight": "Claro",
"pendingLabel": "Pendiente", "themeDark": "Oscuro",
"postNewBulletinLabel": "Publicar nuevo boletín", "experimentsEnabled": "Experimentos habilitados",
"profileName": "Nombre de Usuario", "versionTor": "Versión %1 con tor %2",
"profileOnionLabel": "Envía esta dirección a los contactos con los que quieras conectarte", "version": "Versión %1",
"puzzleGameBtn": "Juego de rompecabezas", "builddate": "Basado en: %2",
"radioNoPassword": "Sin cifrado (sin contraseña)", "defaultScalingText": "Tamaño predeterminado de texto (factor de escala:",
"radioUsePassword": "Contraseña", "smallTextLabel": "Pequeño",
"reallyLeaveThisGroupPrompt": "", "loadingTor": "Cargando tor...",
"rejectGroupBtn": "Rechazar", "viewGroupMembershipTooltip": "Ver membresía del grupo",
"saveBtn": "Guardar", "networkStatusDisconnected": "Sin conexión, comprueba tu conexión",
"savePeerHistory": "Guardar el historial con contacto", "networkStatusAttemptingTor": "Intentando conectarse a la red Tor",
"savePeerHistoryDescription": "Determina si eliminar o no el historial asociado con el contacto.", "networkStatusConnecting": "Conectando a la red y a los contactos...",
"saveProfileBtn": "Guardar perfil", "networkStatusOnline": "En línea",
"search": "Búsqueda...", "newConnectionPaneTitle": "Nueva conexión",
"searchList": "Buscar en la lista", "addListItem": "Añadir un nuevo elemento a la lista",
"server": "Servidor", "addNewItem": "Añadir un nuevo elemento a la lista",
"serverConnectivityConnected": "Servidor conectado", "todoPlaceholder": "Por hacer...",
"serverConnectivityDisconnected": "Servidor desconectado", "localeEs": "Español",
"serverInfo": "Información del servidor", "localeIt": "Italiano",
"serverLabel": "Servidor", "enableGroups": "Enable Group Chat",
"serverNotSynced": "Fuera de sincronización con el servidor", "enterCurrentPasswordForDelete": "Please enter current password to delete this profile.",
"serverSynced": "Sincronizado", "conversationSettings": "Conversation Settings",
"settingInterfaceZoom": "Nivel de zoom", "invalidImportString": "Invalid import string",
"settingLanguage": "Idioma", "contactAlreadyExists": "Contact Already Exists",
"settingTheme": "Tema", "tooltipOpenSettings": "Open the settings pane",
"smallTextLabel": "Pequeño", "tooltipAddContact": "Add a new contact or conversation",
"successfullAddedContact": "", "titleManageContacts": "Conversations",
"themeDark": "Oscuro", "tooltipUnlockProfiles": "Unlock encrypted profiles by entering their password.",
"themeLight": "Claro", "titleManageProfiles": "Manage Cwtch Profiles",
"titleManageContacts": "", "descriptionExperiments": "Cwtch experiments are optional, opt-in features that add additional functionality to Cwtch that may have different privacy considerations than traditional 1:1 metadata resistant chat e.g. group chat, bot integration etc.",
"titleManageProfiles": "", "descriptionExperimentsGroups": "The group experiment allows Cwtch to connect with untrusted server infrastructure to facilitate communication with more than one contact.",
"titleManageServers": "", "descriptionBlockUnknownConnections": "If turned on, this option will automatically close connections from Cwtch users that have not been added to your contact list.",
"titlePlaceholder": "título...", "successfullAddedContact": "Successfully added ",
"todoPlaceholder": "Por hacer...", "dateRightNow": "Right Now",
"tooltipAddContact": "", "dateMinutesAgo": "Minutes Ago",
"tooltipOpenSettings": "", "dateHoursAgo": "Hours Ago",
"tooltipUnlockProfiles": "", "dateDaysAgo": "Days Ago",
"unblockBtn": "Desbloquear contacto", "dateWeeksAgo": "Weeks Ago",
"unlock": "Desbloquear", "dateLastMonth": "Last Month",
"update": "Actualizar", "dateYesterday": "Yesterday",
"version": "Versión %1", "dateLastYear": "Last Year",
"versionBuilddate": "Versión: %1 Basado en %2", "dateYearsAgo": "X Years Ago (displayed next to a contact row to indicate time of last action)",
"versionTor": "Versión %1 con tor %2", "dateNever": "Never",
"viewGroupMembershipTooltip": "Ver membresía del grupo", "dateMonthsAgo": "Months Ago",
"viewServerInfo": "Información del servidor", "titleManageServers": "Manage Servers",
"yesLeave": "", "inviteToGroup": "You have been invited to join a group:",
"yourDisplayName": "Tu nombre de usuario", "leaveGroup": "Leave This Conversation",
"yourProfiles": "Tus perfiles", "reallyLeaveThisGroupPrompt": "Are you sure you want to leave this conversation? All messages and attributes will be deleted.",
"yourServers": "Tus servidores", "yesLeave": "Yes, Leave This Conversation",
"zoomLabel": "Zoom de la interfaz (afecta principalmente el tamaño del texto y de los botones)" "newPassword": "New Password",
"chatHistoryDefault": "This conversation will be deleted when Cwtch is closed! Message history can be enabled per-conversation via the Settings menu in the upper right.",
"accepted": "Accepted!",
"rejected": "Rejected!",
"contactSuggestion": "This is a contact suggestion for: ",
"sendAnInvitation": "You sent an invitation for: ",
"torVersion": "Tor Version",
"torStatus": "Tor Status",
"resetTor": "Reset",
"cancel": "Cancel",
"sendMessage": "Send Message",
"sendInvite": "Send a contact or group invite",
"deleteProfileSuccess": "Successfully deleted profile",
"addServerFirst": "You need to add a server before you can create a group",
"nickChangeSuccess": "Profile nickname changed successfully",
"createProfileToBegin": "Please create or unlock a profile to begin",
"addContactFirst": "Add or pick a contact to begin chatting.",
"torNetworkStatus": "Tor network status",
"debugLog": "Turn on console debug logging",
"profileDeleteSuccess": "Successfully deleted profile",
"malformedMessage": "Malformed message"
} }

View File

@ -1,166 +1,186 @@
{ {
"@@locale": "fr", "@@locale": "fr",
"acceptGroupBtn": "Accepter", "@@last_modified": "2021-06-15T02:08:49+02:00",
"acceptGroupInviteLabel": "Voulez-vous accepter l'invitation au groupe", "createGroupTitle": "Créer un groupe",
"acknowledgedLabel": "Confirmé", "serverLabel": "Serveur",
"addListItem": "Ajouter un nouvel élément", "groupNameLabel": "Nom du groupe",
"addListItemBtn": "", "defaultGroupName": "Un super groupe",
"addNewItem": "Ajouter un nouvel élément à la liste", "createGroupBtn": "Créer",
"addNewProfileBtn": "", "profileOnionLabel": "Send this address to peers you want to connect with",
"addPeer": "", "copyBtn": "Copier",
"addPeerTab": "", "copiedToClipboardNotification": "Copié dans le presse-papier",
"addProfileTitle": "", "addPeerTab": "Add a peer",
"addressLabel": "Adresse", "createGroupTab": "Create a group",
"blockBtn": "", "joinGroupTab": "Join a group",
"blocked": "", "peerAddress": "Address",
"blockUnknownLabel": "", "peerName": "Name",
"builddate": "", "groupName": "Group name",
"bulletinsBtn": "Bulletins", "server": "Server",
"chatBtn": "Discuter", "invitation": "Invitation",
"chatHistoryDefault": "", "groupAddr": "Address",
"contactAlreadyExists": "", "addPeer": "Add Peer",
"conversationSettings": "", "createGroup": "Create group",
"copiedClipboardNotification": "Copié dans le presse-papier", "joinGroup": "Join group",
"copiedToClipboardNotification": "Copié dans le presse-papier", "newBulletinLabel": "Nouveau bulletin",
"copyBtn": "Copier", "postNewBulletinLabel": "Envoyer un nouveau bulletin",
"couldNotSendMsgError": "Impossible d'envoyer ce message", "titlePlaceholder": "titre...",
"createGroup": "", "pasteAddressToAddContact": "... coller une adresse ici pour ajouter un contact...",
"createGroupBtn": "Créer", "blocked": "Blocked",
"createGroupTab": "", "cycleCatsAndroid": "Click to cycle category.\nLong-press to reset.",
"createGroupTitle": "Créer un groupe", "cycleCatsDesktop": "Click to cycle category.\nRight-click to reset.",
"createProfileBtn": "", "cycleMorphsAndroid": "Click to cycle morphs.\nLong-press to reset.",
"currentPasswordLabel": "", "cycleMorphsDesktop": "Click to cycle morphs.\nRight-click to reset.",
"cwtchSettingsTitle": "Préférences Cwtch", "cycleColoursAndroid": "Click to cycle colours.\nLong-press to reset.",
"cycleCatsAndroid": "", "cycleColoursDesktop": "Click to cycle colours.\nRight-click to reset.",
"cycleCatsDesktop": "", "search": "Search...",
"cycleColoursAndroid": "", "invitationLabel": "Invitation",
"cycleColoursDesktop": "", "serverInfo": "Server Information",
"cycleMorphsAndroid": "", "serverConnectivityConnected": "Server Connected",
"cycleMorphsDesktop": "", "serverConnectivityDisconnected": "Server Disconnected",
"dateDaysAgo": "", "serverSynced": "Synced",
"dateHoursAgo": "", "serverNotSynced": "Out of Sync",
"dateLastMonth": "", "viewServerInfo": "Server Info",
"dateLastYear": "", "saveBtn": "Sauvegarder",
"dateMinutesAgo": "", "inviteToGroupLabel": "Inviter quelqu'un",
"dateMonthsAgo": "", "inviteBtn": "Invitation",
"dateNever": "", "deleteBtn": "Effacer",
"dateRightNow": "", "update": "Update",
"dateWeeksAgo": "", "searchList": "Search List",
"dateYearsAgo": "", "peerNotOnline": "Peer is Offline. Applications cannot be used right now.",
"dateYesterday": "", "addListItemBtn": "Add Item",
"defaultGroupName": "Un super groupe", "membershipDescription": "Liste des utilisateurs ayant envoyés un ou plusieurs messages au groupe. Cette liste peut ne pas être representatives de l'ensemble des membres du groupe.",
"defaultProfileName": "", "dmTooltip": "Envoyer un message privé",
"defaultScalingText": "Taille par défaut du texte (échelle:", "couldNotSendMsgError": "Impossible d'envoyer ce message",
"deleteBtn": "Effacer", "acknowledgedLabel": "Confirmé",
"deleteConfirmLabel": "", "pendingLabel": "En attente",
"deleteConfirmText": "", "peerBlockedMessage": "Peer is blocked",
"deleteProfileBtn": "", "peerOfflineMessage": "Peer is offline, messages can't be delivered right now",
"deleteProfileConfirmBtn": "", "copiedClipboardNotification": "Copié dans le presse-papier",
"descriptionBlockUnknownConnections": "", "newGroupBtn": "Créer un nouveau groupe",
"descriptionExperiments": "", "acceptGroupInviteLabel": "Voulez-vous accepter l'invitation au groupe",
"descriptionExperimentsGroups": "", "acceptGroupBtn": "Accepter",
"displayNameLabel": "Pseudo", "rejectGroupBtn": "Refuser",
"dmTooltip": "Envoyer un message privé", "chatBtn": "Discuter",
"dontSavePeerHistory": "", "listsBtn": "Listes",
"editProfile": "", "bulletinsBtn": "Bulletins",
"editProfileTitle": "", "puzzleGameBtn": "Puzzle",
"enableGroups": "", "addressLabel": "Adresse",
"enterCurrentPasswordForDelete": "", "displayNameLabel": "Pseudo",
"enterProfilePassword": "", "blockBtn": "Block Peer",
"error0ProfilesLoadedForPassword": "", "savePeerHistory": "Save Peer History",
"experimentsEnabled": "", "savePeerHistoryDescription": "Determines whether or not to delete any history associated with the peer.",
"groupAddr": "", "dontSavePeerHistory": "Delete Peer History",
"groupName": "", "unblockBtn": "Unblock Peer",
"groupNameLabel": "Nom du groupe", "addProfileTitle": "Add new profile",
"invalidImportString": "", "editProfileTitle": "Edit Profile",
"invitation": "", "profileName": "Display name",
"invitationLabel": "Invitation", "defaultProfileName": "Alice",
"inviteBtn": "Invitation", "newProfile": "New Profile",
"inviteToGroup": "", "editProfile": "Edit Profille",
"inviteToGroupLabel": "Inviter quelqu'un", "radioUsePassword": "Password",
"joinGroup": "", "radioNoPassword": "Unencrypted (No password)",
"joinGroupTab": "", "noPasswordWarning": "Not using a password on this account means that all data stored locally will not be encrypted",
"largeTextLabel": "Large", "yourDisplayName": "Your Display Name",
"leaveGroup": "", "currentPasswordLabel": "Current Password",
"listsBtn": "Listes", "password1Label": "Password",
"loadingTor": "", "password2Label": "Reenter password",
"localeDe": "", "passwordErrorEmpty": "Password cannot be empty",
"localeEn": "", "createProfileBtn": "Create Profile",
"localeEs": "", "saveProfileBtn": "Save Profile",
"localeFr": "", "passwordErrorMatch": "Passwords do not match",
"localeIt": "", "passwordChangeError": "Error changing password: Supplied password rejected",
"localePt": "", "deleteProfileBtn": "Delete Profile",
"membershipDescription": "Liste des utilisateurs ayant envoyés un ou plusieurs messages au groupe. Cette liste peut ne pas être representatives de l'ensemble des membres du groupe.", "deleteConfirmLabel": "Type DELETE to confirm",
"networkStatusAttemptingTor": "", "deleteProfileConfirmBtn": "Really Delete Profile",
"networkStatusConnecting": "", "deleteConfirmText": "DELETE",
"networkStatusDisconnected": "", "addNewProfileBtn": "Add new profile",
"networkStatusOnline": "", "enterProfilePassword": "Enter a password to view your profiles",
"newBulletinLabel": "Nouveau bulletin", "password": "Password",
"newConnectionPaneTitle": "", "error0ProfilesLoadedForPassword": "0 profiles loaded with that password",
"newGroupBtn": "Créer un nouveau groupe", "yourProfiles": "Your Profiles",
"newPassword": "", "yourServers": "Your Servers",
"newProfile": "", "unlock": "Unlock",
"noPasswordWarning": "", "cwtchSettingsTitle": "Préférences Cwtch",
"password": "", "versionBuilddate": "Version: %1 Built on: %2",
"password1Label": "", "zoomLabel": "Interface zoom (essentiellement la taille du texte et des composants de l'interface)",
"password2Label": "", "blockUnknownLabel": "Block Unknown Peers",
"passwordChangeError": "", "settingLanguage": "Language",
"passwordErrorEmpty": "", "localeEn": "English",
"passwordErrorMatch": "", "localeFr": "Frances",
"pasteAddressToAddContact": "... coller une adresse ici pour ajouter un contact...", "localePt": "Portuguesa",
"peerAddress": "", "localeDe": "Deutsche",
"peerBlockedMessage": "", "settingInterfaceZoom": "Zoom level",
"peerName": "", "largeTextLabel": "Large",
"peerNotOnline": "", "settingTheme": "Theme",
"peerOfflineMessage": "", "themeLight": "Light",
"pendingLabel": "En attente", "themeDark": "Dark",
"postNewBulletinLabel": "Envoyer un nouveau bulletin", "experimentsEnabled": "Enable Experiments",
"profileName": "", "versionTor": "Version %1 with tor %2",
"profileOnionLabel": "", "version": "Version %1",
"puzzleGameBtn": "Puzzle", "builddate": "Built on: %2",
"radioNoPassword": "", "defaultScalingText": "Taille par défaut du texte (échelle:",
"radioUsePassword": "", "smallTextLabel": "Petit",
"reallyLeaveThisGroupPrompt": "", "loadingTor": "Loading tor...",
"rejectGroupBtn": "Refuser", "viewGroupMembershipTooltip": "View Group Membership",
"saveBtn": "Sauvegarder", "networkStatusDisconnected": "Disconnected from the internet, check your connection",
"savePeerHistory": "", "networkStatusAttemptingTor": "Attempting to connect to Tor network",
"savePeerHistoryDescription": "", "networkStatusConnecting": "Connecting to network and peers...",
"saveProfileBtn": "", "networkStatusOnline": "Online",
"search": "", "newConnectionPaneTitle": "New Connection",
"searchList": "", "addListItem": "Ajouter un nouvel élément",
"server": "", "addNewItem": "Ajouter un nouvel élément à la liste",
"serverConnectivityConnected": "", "todoPlaceholder": "A faire...",
"serverConnectivityDisconnected": "", "localeEs": "Espanol",
"serverInfo": "", "localeIt": "Italiana",
"serverLabel": "Serveur", "enableGroups": "Enable Group Chat",
"serverNotSynced": "", "enterCurrentPasswordForDelete": "Please enter current password to delete this profile.",
"serverSynced": "", "conversationSettings": "Conversation Settings",
"settingInterfaceZoom": "", "invalidImportString": "Invalid import string",
"settingLanguage": "", "contactAlreadyExists": "Contact Already Exists",
"settingTheme": "", "tooltipOpenSettings": "Open the settings pane",
"smallTextLabel": "Petit", "tooltipAddContact": "Add a new contact or conversation",
"successfullAddedContact": "", "titleManageContacts": "Conversations",
"themeDark": "", "tooltipUnlockProfiles": "Unlock encrypted profiles by entering their password.",
"themeLight": "", "titleManageProfiles": "Manage Cwtch Profiles",
"titleManageContacts": "", "descriptionExperiments": "Cwtch experiments are optional, opt-in features that add additional functionality to Cwtch that may have different privacy considerations than traditional 1:1 metadata resistant chat e.g. group chat, bot integration etc.",
"titleManageProfiles": "", "descriptionExperimentsGroups": "The group experiment allows Cwtch to connect with untrusted server infrastructure to facilitate communication with more than one contact.",
"titleManageServers": "", "descriptionBlockUnknownConnections": "If turned on, this option will automatically close connections from Cwtch users that have not been added to your contact list.",
"titlePlaceholder": "titre...", "successfullAddedContact": "Successfully added ",
"todoPlaceholder": "A faire...", "dateRightNow": "Right Now",
"tooltipAddContact": "", "dateMinutesAgo": "Minutes Ago",
"tooltipOpenSettings": "", "dateHoursAgo": "Hours Ago",
"tooltipUnlockProfiles": "", "dateDaysAgo": "Days Ago",
"unblockBtn": "", "dateWeeksAgo": "Weeks Ago",
"unlock": "", "dateLastMonth": "Last Month",
"update": "", "dateYesterday": "Yesterday",
"version": "", "dateLastYear": "Last Year",
"versionBuilddate": "", "dateYearsAgo": "X Years Ago (displayed next to a contact row to indicate time of last action)",
"versionTor": "", "dateNever": "Never",
"viewGroupMembershipTooltip": "", "dateMonthsAgo": "Months Ago",
"viewServerInfo": "", "titleManageServers": "Manage Servers",
"yesLeave": "", "inviteToGroup": "You have been invited to join a group:",
"yourDisplayName": "", "leaveGroup": "Leave This Conversation",
"yourProfiles": "", "reallyLeaveThisGroupPrompt": "Are you sure you want to leave this conversation? All messages and attributes will be deleted.",
"yourServers": "", "yesLeave": "Yes, Leave This Conversation",
"zoomLabel": "Interface zoom (essentiellement la taille du texte et des composants de l'interface)" "newPassword": "New Password",
"chatHistoryDefault": "This conversation will be deleted when Cwtch is closed! Message history can be enabled per-conversation via the Settings menu in the upper right.",
"accepted": "Accepted!",
"rejected": "Rejected!",
"contactSuggestion": "This is a contact suggestion for: ",
"sendAnInvitation": "You sent an invitation for: ",
"torVersion": "Tor Version",
"torStatus": "Tor Status",
"resetTor": "Reset",
"cancel": "Cancel",
"sendMessage": "Send Message",
"sendInvite": "Send a contact or group invite",
"deleteProfileSuccess": "Successfully deleted profile",
"addServerFirst": "You need to add a server before you can create a group",
"nickChangeSuccess": "Profile nickname changed successfully",
"createProfileToBegin": "Please create or unlock a profile to begin",
"addContactFirst": "Add or pick a contact to begin chatting.",
"torNetworkStatus": "Tor network status",
"debugLog": "Turn on console debug logging",
"profileDeleteSuccess": "Successfully deleted profile",
"malformedMessage": "Malformed message"
} }

View File

@ -1,166 +1,186 @@
{ {
"@@locale": "it", "@@locale": "it",
"acceptGroupBtn": "Accetta", "@@last_modified": "2021-06-15T02:08:49+02:00",
"acceptGroupInviteLabel": "Vuoi accettare l'invito a", "createGroupTitle": "Crea un gruppo",
"acknowledgedLabel": "Riconosciuto", "serverLabel": "Server",
"addListItem": "Aggiungi un nuovo elemento alla lista", "groupNameLabel": "Nome del gruppo",
"addListItemBtn": "Aggiungi elemento", "defaultGroupName": "Gruppo fantastico",
"addNewItem": "Aggiungi un nuovo elemento alla lista", "createGroupBtn": "Crea",
"addNewProfileBtn": "Aggiungi nuovo profilo", "profileOnionLabel": "Inviare questo indirizzo ai peer con cui si desidera connettersi",
"addPeer": "Aggiungi peer", "copyBtn": "Copia",
"addPeerTab": "Aggiungi un peer", "copiedToClipboardNotification": "Copiato negli appunti",
"addProfileTitle": "Aggiungi nuovo profilo", "addPeerTab": "Aggiungi un peer",
"addressLabel": "Indirizzo", "createGroupTab": "Crea un gruppo",
"blockBtn": "Blocca il peer", "joinGroupTab": "Unisciti a un gruppo",
"blocked": "Bloccato", "peerAddress": "Indirizzo",
"blockUnknownLabel": "Blocca peer sconosciuti", "peerName": "Nome",
"builddate": "Costruito il: %2", "groupName": "Nome del gruppo",
"bulletinsBtn": "Bollettini", "server": "Server",
"chatBtn": "Chat", "invitation": "Invito",
"chatHistoryDefault": "", "groupAddr": "Indirizzo",
"contactAlreadyExists": "", "addPeer": "Aggiungi peer",
"conversationSettings": "", "createGroup": "Crea un gruppo",
"copiedClipboardNotification": "Copiato negli Appunti", "joinGroup": "Unisciti al gruppo",
"copiedToClipboardNotification": "Copiato negli Appunti", "newBulletinLabel": "Nuovo bollettino",
"copyBtn": "Copia", "postNewBulletinLabel": "Pubblica un nuovo bollettino",
"couldNotSendMsgError": "Impossibile inviare questo messaggio", "titlePlaceholder": "titolo...",
"createGroup": "Crea un gruppo", "pasteAddressToAddContact": "... incolla qui un indirizzo per aggiungere un contatto...",
"createGroupBtn": "Crea", "blocked": "Bloccato",
"createGroupTab": "Crea un gruppo", "cycleCatsAndroid": "Fare clic per scorrere le categorie.\nPressione lunga per resettare.",
"createGroupTitle": "Crea un gruppo", "cycleCatsDesktop": "Fare clic per scorrere le categorie.\nCliccare con il tasto destro per resettare.",
"createProfileBtn": "Crea un profilo", "cycleMorphsAndroid": "Fare clic per scorrere i morph.\nPressione lunga per resettare.",
"currentPasswordLabel": "Password corrente", "cycleMorphsDesktop": "Fare clic per scorrere i morph.\nCliccare con il tasto destro per resettare.",
"cwtchSettingsTitle": "Impostazioni di Cwtch", "cycleColoursAndroid": "Fare clic per scorrere i colori.\nPressione lunga per resettare.",
"cycleCatsAndroid": "Fare clic per scorrere le categorie.\\nPressione lunga per resettare.", "cycleColoursDesktop": "Fare clic per scorrere i colori.\nCliccare con il tasto destro per resettare.",
"cycleCatsDesktop": "Fare clic per scorrere le categorie.\\nCliccare con il tasto destro per resettare.", "search": "Ricerca...",
"cycleColoursAndroid": "Fare clic per scorrere i colori.\\nPressione lunga per resettare.", "invitationLabel": "Invito",
"cycleColoursDesktop": "Fare clic per scorrere i colori.\\nCliccare con il tasto destro per resettare.", "serverInfo": "Informazioni sul server",
"cycleMorphsAndroid": "Fare clic per scorrere i morph.\\nPressione lunga per resettare.", "serverConnectivityConnected": "Server connesso",
"cycleMorphsDesktop": "Fare clic per scorrere i morph.\\nCliccare con il tasto destro per resettare.", "serverConnectivityDisconnected": "Server disconnesso",
"dateDaysAgo": "", "serverSynced": "Sincronizzato",
"dateHoursAgo": "", "serverNotSynced": "Non sincronizzato",
"dateLastMonth": "", "viewServerInfo": "Informazioni sul server",
"dateLastYear": "", "saveBtn": "Salva",
"dateMinutesAgo": "", "inviteToGroupLabel": "Invitare nel gruppo",
"dateMonthsAgo": "", "inviteBtn": "Invitare",
"dateNever": "", "deleteBtn": "Elimina",
"dateRightNow": "", "update": "Aggiornamento",
"dateWeeksAgo": "", "searchList": "Cerca nella lista",
"dateYearsAgo": "", "peerNotOnline": "Il peer è offline. Le applicazioni non possono essere utilizzate in questo momento.",
"dateYesterday": "", "addListItemBtn": "Aggiungi elemento",
"defaultGroupName": "Gruppo fantastico", "membershipDescription": "Di seguito è riportato un elenco di utenti che hanno inviato messaggi al gruppo. Questo elenco potrebbe non corrispondere a tutti gli utenti che hanno accesso al gruppo.",
"defaultProfileName": "Alice", "dmTooltip": "Clicca per inviare un Messagio Diretto",
"defaultScalingText": "Testo di dimensioni predefinite (fattore di scala:", "couldNotSendMsgError": "Impossibile inviare questo messaggio",
"deleteBtn": "Elimina", "acknowledgedLabel": "Riconosciuto",
"deleteConfirmLabel": "Digita ELIMINA per confermare", "pendingLabel": "In corso",
"deleteConfirmText": "ELIMINA", "peerBlockedMessage": "Il peer è bloccato",
"deleteProfileBtn": "Elimina profilo", "peerOfflineMessage": "Il peer è offline, i messaggi non possono essere recapitati in questo momento",
"deleteProfileConfirmBtn": "Elimina realmente il profilo", "copiedClipboardNotification": "Copiato negli Appunti",
"descriptionBlockUnknownConnections": "", "newGroupBtn": "Crea un nuovo gruppo",
"descriptionExperiments": "", "acceptGroupInviteLabel": "Vuoi accettare l'invito a",
"descriptionExperimentsGroups": "", "acceptGroupBtn": "Accetta",
"displayNameLabel": "Nome visualizzato", "rejectGroupBtn": "Rifiuta",
"dmTooltip": "Clicca per inviare un Messagio Diretto", "chatBtn": "Chat",
"dontSavePeerHistory": "Elimina cronologia dei peer", "listsBtn": "Liste",
"editProfile": "Modifica profilo", "bulletinsBtn": "Bollettini",
"editProfileTitle": "Modifica profilo", "puzzleGameBtn": "Gioco di puzzle",
"enableGroups": "", "addressLabel": "Indirizzo",
"enterCurrentPasswordForDelete": "", "displayNameLabel": "Nome visualizzato",
"enterProfilePassword": "Inserisci una password per visualizzare i tuoi profili", "blockBtn": "Blocca il peer",
"error0ProfilesLoadedForPassword": "0 profili caricati con quella password", "savePeerHistory": "Salva cronologia peer",
"experimentsEnabled": "Esperimenti abilitati", "savePeerHistoryDescription": "Determina se eliminare o meno ogni cronologia eventualmente associata al peer.",
"groupAddr": "Indirizzo", "dontSavePeerHistory": "Elimina cronologia dei peer",
"groupName": "Nome del gruppo", "unblockBtn": "Sblocca il peer",
"groupNameLabel": "Nome del gruppo", "addProfileTitle": "Aggiungi nuovo profilo",
"invalidImportString": "", "editProfileTitle": "Modifica profilo",
"invitation": "Invito", "profileName": "Nome visualizzato",
"invitationLabel": "Invito", "defaultProfileName": "Alice",
"inviteBtn": "Invitare", "newProfile": "Nuovo profilo",
"inviteToGroup": "", "editProfile": "Modifica profilo",
"inviteToGroupLabel": "Invitare nel gruppo", "radioUsePassword": "Password",
"joinGroup": "Unisciti al gruppo", "radioNoPassword": "Non criptato (senza password)",
"joinGroupTab": "Unisciti a un gruppo", "noPasswordWarning": "Non utilizzare una password su questo account significa che tutti i dati archiviati localmente non verranno criptati",
"largeTextLabel": "Grande", "yourDisplayName": "Il tuo nome visualizzato",
"leaveGroup": "", "currentPasswordLabel": "Password corrente",
"listsBtn": "Liste", "password1Label": "Password",
"loadingTor": "Caricamento di tor...", "password2Label": "Reinserire la password",
"localeDe": "Tedesco", "passwordErrorEmpty": "La password non può essere vuota",
"localeEn": "Inglese", "createProfileBtn": "Crea un profilo",
"localeEs": "Spagnolo", "saveProfileBtn": "Salva il profilo",
"localeFr": "Francese", "passwordErrorMatch": "Le password non corrispondono",
"localeIt": "Italiano", "passwordChangeError": "Errore durante la modifica della password: password fornita rifiutata",
"localePt": "Portoghese", "deleteProfileBtn": "Elimina profilo",
"membershipDescription": "Di seguito è riportato un elenco di utenti che hanno inviato messaggi al gruppo. Questo elenco potrebbe non corrispondere a tutti gli utenti che hanno accesso al gruppo.", "deleteConfirmLabel": "Digita ELIMINA per confermare",
"networkStatusAttemptingTor": "Tentativo di connessione alla rete Tor", "deleteProfileConfirmBtn": "Elimina realmente il profilo",
"networkStatusConnecting": "Connessione alla rete e ai peer ...", "deleteConfirmText": "ELIMINA",
"networkStatusDisconnected": "Disconnesso da Internet, controlla la tua connessione", "addNewProfileBtn": "Aggiungi nuovo profilo",
"networkStatusOnline": "Online", "enterProfilePassword": "Inserisci una password per visualizzare i tuoi profili",
"newBulletinLabel": "Nuovo bollettino", "password": "Password",
"newConnectionPaneTitle": "Nuova connessione", "error0ProfilesLoadedForPassword": "0 profili caricati con quella password",
"newGroupBtn": "Crea un nuovo gruppo", "yourProfiles": "I tuoi profili",
"newPassword": "", "yourServers": "I tuoi server",
"newProfile": "Nuovo profilo", "unlock": "Sblocca",
"noPasswordWarning": "Non utilizzare una password su questo account significa che tutti i dati archiviati localmente non verranno criptati", "cwtchSettingsTitle": "Impostazioni di Cwtch",
"password": "Password", "versionBuilddate": "Versione: %1 Costruito il: %2",
"password1Label": "Password", "zoomLabel": "Zoom dell'interfaccia (influisce principalmente sulle dimensioni del testo e dei pulsanti)",
"password2Label": "Reinserire la password", "blockUnknownLabel": "Blocca peer sconosciuti",
"passwordChangeError": "Errore durante la modifica della password: password fornita rifiutata", "settingLanguage": "Lingua",
"passwordErrorEmpty": "La password non può essere vuota", "localeEn": "Inglese",
"passwordErrorMatch": "Le password non corrispondono", "localeFr": "Francese",
"pasteAddressToAddContact": "... incolla qui un indirizzo per aggiungere un contatto...", "localePt": "Portoghese",
"peerAddress": "Indirizzo", "localeDe": "Tedesco",
"peerBlockedMessage": "Il peer è bloccato", "settingInterfaceZoom": "Livello di zoom",
"peerName": "Nome", "largeTextLabel": "Grande",
"peerNotOnline": "Il peer è offline. Le applicazioni non possono essere utilizzate in questo momento.", "settingTheme": "Tema",
"peerOfflineMessage": "Il peer è offline, i messaggi non possono essere recapitati in questo momento", "themeLight": "Chiaro",
"pendingLabel": "In corso", "themeDark": "Scuro",
"postNewBulletinLabel": "Pubblica un nuovo bollettino", "experimentsEnabled": "Esperimenti abilitati",
"profileName": "Nome visualizzato", "versionTor": "Versione %1 con tor %2",
"profileOnionLabel": "Inviare questo indirizzo ai peer con cui si desidera connettersi", "version": "Versione %1",
"puzzleGameBtn": "Gioco di puzzle", "builddate": "Costruito il: %2",
"radioNoPassword": "Non criptato (senza password)", "defaultScalingText": "Testo di dimensioni predefinite (fattore di scala:",
"radioUsePassword": "Password", "smallTextLabel": "Piccolo",
"reallyLeaveThisGroupPrompt": "", "loadingTor": "Caricamento di tor...",
"rejectGroupBtn": "Rifiuta", "viewGroupMembershipTooltip": "Visualizza i membri del gruppo",
"saveBtn": "Salva", "networkStatusDisconnected": "Disconnesso da Internet, controlla la tua connessione",
"savePeerHistory": "Salva cronologia peer", "networkStatusAttemptingTor": "Tentativo di connessione alla rete Tor",
"savePeerHistoryDescription": "Determina se eliminare o meno ogni cronologia eventualmente associata al peer.", "networkStatusConnecting": "Connessione alla rete e ai peer ...",
"saveProfileBtn": "Salva il profilo", "networkStatusOnline": "Online",
"search": "Ricerca...", "newConnectionPaneTitle": "Nuova connessione",
"searchList": "Cerca nella lista", "addListItem": "Aggiungi un nuovo elemento alla lista",
"server": "Server", "addNewItem": "Aggiungi un nuovo elemento alla lista",
"serverConnectivityConnected": "Server connesso", "todoPlaceholder": "Da fare...",
"serverConnectivityDisconnected": "Server disconnesso", "localeEs": "Spagnolo",
"serverInfo": "Informazioni sul server", "localeIt": "Italiano",
"serverLabel": "Server", "enableGroups": "Enable Group Chat",
"serverNotSynced": "Non sincronizzato", "enterCurrentPasswordForDelete": "Please enter current password to delete this profile.",
"serverSynced": "Sincronizzato", "conversationSettings": "Conversation Settings",
"settingInterfaceZoom": "Livello di zoom", "invalidImportString": "Invalid import string",
"settingLanguage": "Lingua", "contactAlreadyExists": "Contact Already Exists",
"settingTheme": "Tema", "tooltipOpenSettings": "Open the settings pane",
"smallTextLabel": "Piccolo", "tooltipAddContact": "Add a new contact or conversation",
"successfullAddedContact": "", "titleManageContacts": "Conversations",
"themeDark": "Scuro", "tooltipUnlockProfiles": "Unlock encrypted profiles by entering their password.",
"themeLight": "Chiaro", "titleManageProfiles": "Manage Cwtch Profiles",
"titleManageContacts": "", "descriptionExperiments": "Cwtch experiments are optional, opt-in features that add additional functionality to Cwtch that may have different privacy considerations than traditional 1:1 metadata resistant chat e.g. group chat, bot integration etc.",
"titleManageProfiles": "", "descriptionExperimentsGroups": "The group experiment allows Cwtch to connect with untrusted server infrastructure to facilitate communication with more than one contact.",
"titleManageServers": "", "descriptionBlockUnknownConnections": "If turned on, this option will automatically close connections from Cwtch users that have not been added to your contact list.",
"titlePlaceholder": "titolo...", "successfullAddedContact": "Successfully added ",
"todoPlaceholder": "Da fare...", "dateRightNow": "Right Now",
"tooltipAddContact": "", "dateMinutesAgo": "Minutes Ago",
"tooltipOpenSettings": "", "dateHoursAgo": "Hours Ago",
"tooltipUnlockProfiles": "", "dateDaysAgo": "Days Ago",
"unblockBtn": "Sblocca il peer", "dateWeeksAgo": "Weeks Ago",
"unlock": "Sblocca", "dateLastMonth": "Last Month",
"update": "Aggiornamento", "dateYesterday": "Yesterday",
"version": "Versione %1", "dateLastYear": "Last Year",
"versionBuilddate": "Versione: %1 Costruito il: %2", "dateYearsAgo": "X Years Ago (displayed next to a contact row to indicate time of last action)",
"versionTor": "Versione %1 con tor %2", "dateNever": "Never",
"viewGroupMembershipTooltip": "Visualizza i membri del gruppo", "dateMonthsAgo": "Months Ago",
"viewServerInfo": "Informazioni sul server", "titleManageServers": "Manage Servers",
"yesLeave": "", "inviteToGroup": "You have been invited to join a group:",
"yourDisplayName": "Il tuo nome visualizzato", "leaveGroup": "Leave This Conversation",
"yourProfiles": "I tuoi profili", "reallyLeaveThisGroupPrompt": "Are you sure you want to leave this conversation? All messages and attributes will be deleted.",
"yourServers": "I tuoi server", "yesLeave": "Yes, Leave This Conversation",
"zoomLabel": "Zoom dell'interfaccia (influisce principalmente sulle dimensioni del testo e dei pulsanti)" "newPassword": "New Password",
"chatHistoryDefault": "This conversation will be deleted when Cwtch is closed! Message history can be enabled per-conversation via the Settings menu in the upper right.",
"accepted": "Accepted!",
"rejected": "Rejected!",
"contactSuggestion": "This is a contact suggestion for: ",
"sendAnInvitation": "You sent an invitation for: ",
"torVersion": "Tor Version",
"torStatus": "Tor Status",
"resetTor": "Reset",
"cancel": "Cancel",
"sendMessage": "Send Message",
"sendInvite": "Send a contact or group invite",
"deleteProfileSuccess": "Successfully deleted profile",
"addServerFirst": "You need to add a server before you can create a group",
"nickChangeSuccess": "Profile nickname changed successfully",
"createProfileToBegin": "Please create or unlock a profile to begin",
"addContactFirst": "Add or pick a contact to begin chatting.",
"torNetworkStatus": "Tor network status",
"debugLog": "Turn on console debug logging",
"profileDeleteSuccess": "Successfully deleted profile",
"malformedMessage": "Malformed message"
} }

View File

@ -1,166 +1,186 @@
{ {
"@@locale": "pt", "@@locale": "pt",
"acceptGroupBtn": "Aceitar", "@@last_modified": "2021-06-15T02:08:49+02:00",
"acceptGroupInviteLabel": "Você quer aceitar o convite para", "createGroupTitle": "Criar Grupo",
"acknowledgedLabel": "Confirmada", "serverLabel": "Servidor",
"addListItem": "Adicionar Item à Lista", "groupNameLabel": "Nome do Grupo",
"addListItemBtn": "", "defaultGroupName": "Grupo incrível",
"addNewItem": "Adicionar novo item à lista", "createGroupBtn": "Criar",
"addNewProfileBtn": "", "profileOnionLabel": "Send this address to peers you want to connect with",
"addPeer": "", "copyBtn": "Copiar",
"addPeerTab": "", "copiedToClipboardNotification": "Copiado",
"addProfileTitle": "", "addPeerTab": "Add a peer",
"addressLabel": "Endereço", "createGroupTab": "Create a group",
"blockBtn": "", "joinGroupTab": "Join a group",
"blocked": "", "peerAddress": "Address",
"blockUnknownLabel": "", "peerName": "Name",
"builddate": "", "groupName": "Group name",
"bulletinsBtn": "Boletins", "server": "Server",
"chatBtn": "Chat", "invitation": "Invitation",
"chatHistoryDefault": "", "groupAddr": "Address",
"contactAlreadyExists": "", "addPeer": "Add Peer",
"conversationSettings": "", "createGroup": "Create group",
"copiedClipboardNotification": "Copiado", "joinGroup": "Join group",
"copiedToClipboardNotification": "Copiado", "newBulletinLabel": "Novo Boletim",
"copyBtn": "Copiar", "postNewBulletinLabel": "Postar novo boletim",
"couldNotSendMsgError": "Não deu para enviar esta mensagem", "titlePlaceholder": "título…",
"createGroup": "", "pasteAddressToAddContact": "… cole um endereço aqui para adicionar um contato…",
"createGroupBtn": "Criar", "blocked": "Blocked",
"createGroupTab": "", "cycleCatsAndroid": "Click to cycle category.\nLong-press to reset.",
"createGroupTitle": "Criar Grupo", "cycleCatsDesktop": "Click to cycle category.\nRight-click to reset.",
"createProfileBtn": "", "cycleMorphsAndroid": "Click to cycle morphs.\nLong-press to reset.",
"currentPasswordLabel": "", "cycleMorphsDesktop": "Click to cycle morphs.\nRight-click to reset.",
"cwtchSettingsTitle": "Configurações do Cwtch", "cycleColoursAndroid": "Click to cycle colours.\nLong-press to reset.",
"cycleCatsAndroid": "", "cycleColoursDesktop": "Click to cycle colours.\nRight-click to reset.",
"cycleCatsDesktop": "", "search": "Search...",
"cycleColoursAndroid": "", "invitationLabel": "Convite",
"cycleColoursDesktop": "", "serverInfo": "Server Information",
"cycleMorphsAndroid": "", "serverConnectivityConnected": "Server Connected",
"cycleMorphsDesktop": "", "serverConnectivityDisconnected": "Server Disconnected",
"dateDaysAgo": "", "serverSynced": "Synced",
"dateHoursAgo": "", "serverNotSynced": "Out of Sync",
"dateLastMonth": "", "viewServerInfo": "Server Info",
"dateLastYear": "", "saveBtn": "Salvar",
"dateMinutesAgo": "", "inviteToGroupLabel": "Convidar ao grupo",
"dateMonthsAgo": "", "inviteBtn": "Convidar",
"dateNever": "", "deleteBtn": "Deletar",
"dateRightNow": "", "update": "Update",
"dateWeeksAgo": "", "searchList": "Search List",
"dateYearsAgo": "", "peerNotOnline": "Peer is Offline. Applications cannot be used right now.",
"dateYesterday": "", "addListItemBtn": "Add Item",
"defaultGroupName": "Grupo incrível", "membershipDescription": "A lista abaixo é de usuários que enviaram mensagens ao grupo. Essa lista pode não refletir todos os usuários que têm acesso ao grupo.",
"defaultProfileName": "", "dmTooltip": "Clique para DM",
"defaultScalingText": "Texto tamanho padrão (fator de escala: ", "couldNotSendMsgError": "Não deu para enviar esta mensagem",
"deleteBtn": "Deletar", "acknowledgedLabel": "Confirmada",
"deleteConfirmLabel": "", "pendingLabel": "Pendente",
"deleteConfirmText": "", "peerBlockedMessage": "Peer is blocked",
"deleteProfileBtn": "", "peerOfflineMessage": "Peer is offline, messages can't be delivered right now",
"deleteProfileConfirmBtn": "", "copiedClipboardNotification": "Copiado",
"descriptionBlockUnknownConnections": "", "newGroupBtn": "Criar novo grupo",
"descriptionExperiments": "", "acceptGroupInviteLabel": "Você quer aceitar o convite para",
"descriptionExperimentsGroups": "", "acceptGroupBtn": "Aceitar",
"displayNameLabel": "Nome de Exibição", "rejectGroupBtn": "Recusar",
"dmTooltip": "Clique para DM", "chatBtn": "Chat",
"dontSavePeerHistory": "", "listsBtn": "Listas",
"editProfile": "", "bulletinsBtn": "Boletins",
"editProfileTitle": "", "puzzleGameBtn": "Jogo de Adivinhação",
"enableGroups": "", "addressLabel": "Endereço",
"enterCurrentPasswordForDelete": "", "displayNameLabel": "Nome de Exibição",
"enterProfilePassword": "", "blockBtn": "Block Peer",
"error0ProfilesLoadedForPassword": "", "savePeerHistory": "Save Peer History",
"experimentsEnabled": "", "savePeerHistoryDescription": "Determines whether or not to delete any history associated with the peer.",
"groupAddr": "", "dontSavePeerHistory": "Delete Peer History",
"groupName": "", "unblockBtn": "Unblock Peer",
"groupNameLabel": "Nome do Grupo", "addProfileTitle": "Add new profile",
"invalidImportString": "", "editProfileTitle": "Edit Profile",
"invitation": "", "profileName": "Display name",
"invitationLabel": "Convite", "defaultProfileName": "Alice",
"inviteBtn": "Convidar", "newProfile": "New Profile",
"inviteToGroup": "", "editProfile": "Edit Profille",
"inviteToGroupLabel": "Convidar ao grupo", "radioUsePassword": "Password",
"joinGroup": "", "radioNoPassword": "Unencrypted (No password)",
"joinGroupTab": "", "noPasswordWarning": "Not using a password on this account means that all data stored locally will not be encrypted",
"largeTextLabel": "Grande", "yourDisplayName": "Your Display Name",
"leaveGroup": "", "currentPasswordLabel": "Current Password",
"listsBtn": "Listas", "password1Label": "Password",
"loadingTor": "", "password2Label": "Reenter password",
"localeDe": "", "passwordErrorEmpty": "Password cannot be empty",
"localeEn": "", "createProfileBtn": "Create Profile",
"localeEs": "", "saveProfileBtn": "Save Profile",
"localeFr": "", "passwordErrorMatch": "Passwords do not match",
"localeIt": "", "passwordChangeError": "Error changing password: Supplied password rejected",
"localePt": "", "deleteProfileBtn": "Delete Profile",
"membershipDescription": "A lista abaixo é de usuários que enviaram mensagens ao grupo. Essa lista pode não refletir todos os usuários que têm acesso ao grupo.", "deleteConfirmLabel": "Type DELETE to confirm",
"networkStatusAttemptingTor": "", "deleteProfileConfirmBtn": "Really Delete Profile",
"networkStatusConnecting": "", "deleteConfirmText": "DELETE",
"networkStatusDisconnected": "", "addNewProfileBtn": "Add new profile",
"networkStatusOnline": "", "enterProfilePassword": "Enter a password to view your profiles",
"newBulletinLabel": "Novo Boletim", "password": "Password",
"newConnectionPaneTitle": "", "error0ProfilesLoadedForPassword": "0 profiles loaded with that password",
"newGroupBtn": "Criar novo grupo", "yourProfiles": "Your Profiles",
"newPassword": "", "yourServers": "Your Servers",
"newProfile": "", "unlock": "Unlock",
"noPasswordWarning": "", "cwtchSettingsTitle": "Configurações do Cwtch",
"password": "", "versionBuilddate": "Version: %1 Built on: %2",
"password1Label": "", "zoomLabel": "Zoom da interface (afeta principalmente tamanho de texto e botões)",
"password2Label": "", "blockUnknownLabel": "Block Unknown Peers",
"passwordChangeError": "", "settingLanguage": "Language",
"passwordErrorEmpty": "", "localeEn": "English",
"passwordErrorMatch": "", "localeFr": "Frances",
"pasteAddressToAddContact": "… cole um endereço aqui para adicionar um contato…", "localePt": "Portuguesa",
"peerAddress": "", "localeDe": "Deutsche",
"peerBlockedMessage": "", "settingInterfaceZoom": "Zoom level",
"peerName": "", "largeTextLabel": "Grande",
"peerNotOnline": "", "settingTheme": "Theme",
"peerOfflineMessage": "", "themeLight": "Light",
"pendingLabel": "Pendente", "themeDark": "Dark",
"postNewBulletinLabel": "Postar novo boletim", "experimentsEnabled": "Enable Experiments",
"profileName": "", "versionTor": "Version %1 with tor %2",
"profileOnionLabel": "", "version": "Version %1",
"puzzleGameBtn": "Jogo de Adivinhação", "builddate": "Built on: %2",
"radioNoPassword": "", "defaultScalingText": "Texto tamanho padrão (fator de escala: ",
"radioUsePassword": "", "smallTextLabel": "Pequeno",
"reallyLeaveThisGroupPrompt": "", "loadingTor": "Loading tor...",
"rejectGroupBtn": "Recusar", "viewGroupMembershipTooltip": "View Group Membership",
"saveBtn": "Salvar", "networkStatusDisconnected": "Disconnected from the internet, check your connection",
"savePeerHistory": "", "networkStatusAttemptingTor": "Attempting to connect to Tor network",
"savePeerHistoryDescription": "", "networkStatusConnecting": "Connecting to network and peers...",
"saveProfileBtn": "", "networkStatusOnline": "Online",
"search": "", "newConnectionPaneTitle": "New Connection",
"searchList": "", "addListItem": "Adicionar Item à Lista",
"server": "", "addNewItem": "Adicionar novo item à lista",
"serverConnectivityConnected": "", "todoPlaceholder": "Afazer…",
"serverConnectivityDisconnected": "", "localeEs": "Espanol",
"serverInfo": "", "localeIt": "Italiana",
"serverLabel": "Servidor", "enableGroups": "Enable Group Chat",
"serverNotSynced": "", "enterCurrentPasswordForDelete": "Please enter current password to delete this profile.",
"serverSynced": "", "conversationSettings": "Conversation Settings",
"settingInterfaceZoom": "", "invalidImportString": "Invalid import string",
"settingLanguage": "", "contactAlreadyExists": "Contact Already Exists",
"settingTheme": "", "tooltipOpenSettings": "Open the settings pane",
"smallTextLabel": "Pequeno", "tooltipAddContact": "Add a new contact or conversation",
"successfullAddedContact": "", "titleManageContacts": "Conversations",
"themeDark": "", "tooltipUnlockProfiles": "Unlock encrypted profiles by entering their password.",
"themeLight": "", "titleManageProfiles": "Manage Cwtch Profiles",
"titleManageContacts": "", "descriptionExperiments": "Cwtch experiments are optional, opt-in features that add additional functionality to Cwtch that may have different privacy considerations than traditional 1:1 metadata resistant chat e.g. group chat, bot integration etc.",
"titleManageProfiles": "", "descriptionExperimentsGroups": "The group experiment allows Cwtch to connect with untrusted server infrastructure to facilitate communication with more than one contact.",
"titleManageServers": "", "descriptionBlockUnknownConnections": "If turned on, this option will automatically close connections from Cwtch users that have not been added to your contact list.",
"titlePlaceholder": "título…", "successfullAddedContact": "Successfully added ",
"todoPlaceholder": "Afazer…", "dateRightNow": "Right Now",
"tooltipAddContact": "", "dateMinutesAgo": "Minutes Ago",
"tooltipOpenSettings": "", "dateHoursAgo": "Hours Ago",
"tooltipUnlockProfiles": "", "dateDaysAgo": "Days Ago",
"unblockBtn": "", "dateWeeksAgo": "Weeks Ago",
"unlock": "", "dateLastMonth": "Last Month",
"update": "", "dateYesterday": "Yesterday",
"version": "", "dateLastYear": "Last Year",
"versionBuilddate": "", "dateYearsAgo": "X Years Ago (displayed next to a contact row to indicate time of last action)",
"versionTor": "", "dateNever": "Never",
"viewGroupMembershipTooltip": "", "dateMonthsAgo": "Months Ago",
"viewServerInfo": "", "titleManageServers": "Manage Servers",
"yesLeave": "", "inviteToGroup": "You have been invited to join a group:",
"yourDisplayName": "", "leaveGroup": "Leave This Conversation",
"yourProfiles": "", "reallyLeaveThisGroupPrompt": "Are you sure you want to leave this conversation? All messages and attributes will be deleted.",
"yourServers": "", "yesLeave": "Yes, Leave This Conversation",
"zoomLabel": "Zoom da interface (afeta principalmente tamanho de texto e botões)" "newPassword": "New Password",
"chatHistoryDefault": "This conversation will be deleted when Cwtch is closed! Message history can be enabled per-conversation via the Settings menu in the upper right.",
"accepted": "Accepted!",
"rejected": "Rejected!",
"contactSuggestion": "This is a contact suggestion for: ",
"sendAnInvitation": "You sent an invitation for: ",
"torVersion": "Tor Version",
"torStatus": "Tor Status",
"resetTor": "Reset",
"cancel": "Cancel",
"sendMessage": "Send Message",
"sendInvite": "Send a contact or group invite",
"deleteProfileSuccess": "Successfully deleted profile",
"addServerFirst": "You need to add a server before you can create a group",
"nickChangeSuccess": "Profile nickname changed successfully",
"createProfileToBegin": "Please create or unlock a profile to begin",
"addContactFirst": "Add or pick a contact to begin chatting.",
"torNetworkStatus": "Tor network status",
"debugLog": "Turn on console debug logging",
"profileDeleteSuccess": "Successfully deleted profile",
"malformedMessage": "Malformed message"
} }

View File

@ -1,4 +1,7 @@
import 'dart:convert';
import 'package:cwtch/notification_manager.dart'; import 'package:cwtch/notification_manager.dart';
import 'package:cwtch/views/messageview.dart';
import 'package:cwtch/widgets/rightshiftfixer.dart'; import 'package:cwtch/widgets/rightshiftfixer.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:cwtch/cwtch/ffi.dart'; import 'package:cwtch/cwtch/ffi.dart';
@ -8,6 +11,7 @@ import 'package:cwtch/errorHandler.dart';
import 'package:cwtch/settings.dart'; import 'package:cwtch/settings.dart';
import 'package:cwtch/torstatus.dart'; import 'package:cwtch/torstatus.dart';
import 'package:cwtch/views/triplecolview.dart'; import 'package:cwtch/views/triplecolview.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'cwtch/cwtch.dart'; import 'cwtch/cwtch.dart';
import 'cwtch/cwtchNotifier.dart'; import 'cwtch/cwtchNotifier.dart';
@ -44,6 +48,8 @@ class FlwtchState extends State<Flwtch> {
var columns = [1]; // default or 'single column' mode var columns = [1]; // default or 'single column' mode
//var columns = [1, 1, 2]; //var columns = [1, 1, 2];
late ProfileListState profs; late ProfileListState profs;
final MethodChannel notificationClickChannel = MethodChannel('im.cwtch.flwtch/notificationClickHandler');
final GlobalKey<NavigatorState> navKey = GlobalKey<NavigatorState>();
@override @override
initState() { initState() {
@ -51,6 +57,7 @@ class FlwtchState extends State<Flwtch> {
cwtchInit = false; cwtchInit = false;
profs = ProfileListState(); profs = ProfileListState();
notificationClickChannel.setMethodCallHandler(_externalNotificationClicked);
if (Platform.isAndroid) { if (Platform.isAndroid) {
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, NullNotificationsManager()); var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, NullNotificationsManager());
@ -63,10 +70,9 @@ class FlwtchState extends State<Flwtch> {
cwtch = CwtchFfi(cwtchNotifier); cwtch = CwtchFfi(cwtchNotifier);
} }
cwtch.Start().then((val) { cwtch.Start();
setState(() { setState(() {
cwtchInit = true; cwtchInit = true;
});
}); });
} }
@ -92,13 +98,12 @@ class FlwtchState extends State<Flwtch> {
return Consumer<Settings>( return Consumer<Settings>(
builder: (context, settings, child) => MaterialApp( builder: (context, settings, child) => MaterialApp(
key: Key('app'), key: Key('app'),
navigatorKey: navKey,
locale: settings.locale, locale: settings.locale,
localizationsDelegates: AppLocalizations.localizationsDelegates, localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales, supportedLocales: AppLocalizations.supportedLocales,
title: 'Cwtch', title: 'Cwtch',
theme: mkThemeData(settings), 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() : ShiftRightFixer(child: ProfileMgrView())) : SplashView(), home: cwtchInit == true ? (columns.length == 3 ? TripleColumnView() : ShiftRightFixer(child: ProfileMgrView())) : SplashView(),
), ),
); );
@ -106,6 +111,26 @@ class FlwtchState extends State<Flwtch> {
); );
} }
Future<void> _externalNotificationClicked(MethodCall call) async {
var args = jsonDecode(call.arguments);
var profile = profs.getProfile(args["ProfileOnion"])!;
var contact = profile.contactList.getContact(args["RemotePeer"])!;
contact.unreadMessages = 0;
navKey.currentState?.push(
MaterialPageRoute<void>(
builder: (BuildContext builderContext) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(value: profile),
ChangeNotifierProvider.value(value: contact),
],
builder: (context, child) => MessageView(),
);
},
),
);
}
@override @override
void dispose() { void dispose() {
cwtch.dispose(); cwtch.dispose();

View File

@ -183,7 +183,7 @@ class ProfileInfoState extends ChangeNotifier {
// Parse out the server list json into our server info state struct... // Parse out the server list json into our server info state struct...
void replaceServers(String serversJson) { void replaceServers(String serversJson) {
if (serversJson != null && serversJson != "" && serversJson != "null") { if (serversJson != "" && serversJson != "null") {
print("got servers $serversJson"); print("got servers $serversJson");
List<dynamic> servers = jsonDecode(serversJson); List<dynamic> servers = jsonDecode(serversJson);
this._servers.replace(servers.map((server) { this._servers.replace(servers.map((server) {
@ -382,6 +382,7 @@ class MessageState extends ChangeNotifier {
late String _inviteNick; late String _inviteNick;
late DateTime _timestamp; late DateTime _timestamp;
late String _senderOnion; late String _senderOnion;
late int _flags;
String? _senderImage; String? _senderImage;
late String _signature = ""; late String _signature = "";
late bool _ackd = false; late bool _ackd = false;
@ -402,6 +403,12 @@ class MessageState extends ChangeNotifier {
get message => this._message; get message => this._message;
get overlay => this._overlay; get overlay => this._overlay;
get timestamp => this._timestamp; get timestamp => this._timestamp;
int get flags => this._flags;
set flags(int newVal) {
this._flags = newVal;
notifyListeners();
}
bool get ackd => this._ackd; bool get ackd => this._ackd;
bool get error => this._error; bool get error => this._error;
bool get malformed => this._malformed; bool get malformed => this._malformed;
@ -450,6 +457,7 @@ class MessageState extends ChangeNotifier {
this._timestamp = DateTime.tryParse(messageWrapper['Timestamp'])!; this._timestamp = DateTime.tryParse(messageWrapper['Timestamp'])!;
this._senderOnion = messageWrapper['PeerID']; this._senderOnion = messageWrapper['PeerID'];
this._senderImage = messageWrapper['ContactImage']; this._senderImage = messageWrapper['ContactImage'];
this._flags = int.parse(messageWrapper['Flags'].toString(), radix: 2);
// If this is a group, store the signature // If this is a group, store the signature
if (contactHandle.length == 32) { if (contactHandle.length == 32) {

View File

@ -1383,6 +1383,8 @@ ThemeData mkThemeData(Settings opaque) {
)), )),
), ),
), ),
scrollbarTheme: ScrollbarThemeData(
isAlwaysShown: false, thumbColor: MaterialStateProperty.all(opaque.current().scrollbarActiveColor()), trackColor: MaterialStateProperty.all(opaque.current().scrollbarDefaultColor())),
tabBarTheme: TabBarTheme(indicator: UnderlineTabIndicator(borderSide: BorderSide(color: opaque.current().defaultButtonActiveColor()))), tabBarTheme: TabBarTheme(indicator: UnderlineTabIndicator(borderSide: BorderSide(color: opaque.current().defaultButtonActiveColor()))),
dialogTheme: DialogTheme( dialogTheme: DialogTheme(
backgroundColor: opaque.current().backgroundPaneColor(), backgroundColor: opaque.current().backgroundPaneColor(),

View File

@ -119,7 +119,11 @@ class _AddContactViewState extends State<AddContactView> {
CwtchButtonTextField( CwtchButtonTextField(
controller: ctrlrOnion, controller: ctrlrOnion,
onPressed: _copyOnion, onPressed: _copyOnion,
icon: Icon(CwtchIcons.copy_address), readonly: true,
icon: Icon(
CwtchIcons.address_copy_2,
size: 32,
),
tooltip: AppLocalizations.of(context)!.copyBtn, tooltip: AppLocalizations.of(context)!.copyBtn,
), ),
SizedBox( SizedBox(

View File

@ -11,6 +11,7 @@ import 'package:cwtch/widgets/textfield.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../cwtch_icons_icons.dart';
import '../main.dart'; import '../main.dart';
import '../opaque.dart'; import '../opaque.dart';
import '../settings.dart'; import '../settings.dart';
@ -122,7 +123,11 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
CwtchButtonTextField( CwtchButtonTextField(
controller: ctrlrOnion, controller: ctrlrOnion,
onPressed: _copyOnion, onPressed: _copyOnion,
icon: Icon(Icons.copy), readonly: true,
icon: Icon(
CwtchIcons.address_copy_2,
size: 32,
),
tooltip: AppLocalizations.of(context)!.copyBtn, tooltip: AppLocalizations.of(context)!.copyBtn,
) )
])), ])),

View File

@ -177,7 +177,9 @@ class _MessageViewState extends State<MessageView> {
), ),
ChangeNotifierProvider.value( ChangeNotifierProvider.value(
value: Provider.of<ProfileInfoState>(ctx, listen: false), value: Provider.of<ProfileInfoState>(ctx, listen: false),
child: DropdownContacts(onChanged: (newVal) { child: DropdownContacts(filter: (contact) {
return contact.onion != Provider.of<ContactInfoState>(context).onion;
}, onChanged: (newVal) {
setState(() { setState(() {
this.selectedContact = newVal; this.selectedContact = newVal;
}); });

View File

@ -9,9 +9,9 @@ import 'package:cwtch/widgets/tor_icon.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:cwtch/widgets/profilerow.dart'; import 'package:cwtch/widgets/profilerow.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../config.dart';
import '../main.dart'; import '../main.dart';
import '../model.dart'; import '../model.dart';
import '../opaque.dart';
import '../torstatus.dart'; import '../torstatus.dart';
import 'addeditprofileview.dart'; import 'addeditprofileview.dart';
import 'globalsettingsview.dart'; import 'globalsettingsview.dart';
@ -58,22 +58,7 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
), ),
Expanded(child: Text(AppLocalizations.of(context)!.titleManageProfiles, style: TextStyle(color: settings.current().mainTextColor()))) Expanded(child: Text(AppLocalizations.of(context)!.titleManageProfiles, style: TextStyle(color: settings.current().mainTextColor())))
]), ]),
actions: [ actions: getActions(),
IconButton(
icon: TorIcon(),
onPressed: _pushTorStatus,
tooltip: Provider.of<TorStatus>(context).progress == 100
? AppLocalizations.of(context)!.networkStatusOnline
: (Provider.of<TorStatus>(context).progress == 0 ? AppLocalizations.of(context)!.networkStatusDisconnected : AppLocalizations.of(context)!.networkStatusAttemptingTor),
),
IconButton(icon: Icon(Icons.bug_report_outlined), onPressed: _setLoggingLevelDebug),
IconButton(
icon: Icon(CwtchIcons.lock_open_24px),
tooltip: AppLocalizations.of(context)!.tooltipUnlockProfiles,
onPressed: _modalUnlockProfiles,
),
IconButton(icon: Icon(Icons.settings), tooltip: AppLocalizations.of(context)!.tooltipOpenSettings, onPressed: _pushGlobalSettings),
],
), ),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
onPressed: _pushAddEditProfile, onPressed: _pushAddEditProfile,
@ -88,6 +73,36 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
); );
} }
List<Widget> getActions() {
List<Widget> actions = new List<Widget>.empty(growable: true);
// Tor Status
actions.add(IconButton(
icon: TorIcon(),
onPressed: _pushTorStatus,
tooltip: Provider.of<TorStatus>(context).progress == 100
? AppLocalizations.of(context)!.networkStatusOnline
: (Provider.of<TorStatus>(context).progress == 0 ? AppLocalizations.of(context)!.networkStatusDisconnected : AppLocalizations.of(context)!.networkStatusAttemptingTor),
));
// Only show debug button on development builds
if (EnvironmentConfig.BUILD_VER == dev_version) {
actions.add(IconButton(icon: Icon(Icons.bug_report_outlined), tooltip: "Turn on Debug Logging", onPressed: _setLoggingLevelDebug));
}
// Unlock Profiles
actions.add(IconButton(
icon: Icon(CwtchIcons.lock_open_24px),
tooltip: AppLocalizations.of(context)!.tooltipUnlockProfiles,
onPressed: _modalUnlockProfiles,
));
// Global Settings
actions.add(IconButton(icon: Icon(Icons.settings), tooltip: AppLocalizations.of(context)!.tooltipOpenSettings, onPressed: _pushGlobalSettings));
return actions;
}
void _setLoggingLevelDebug() { void _setLoggingLevelDebug() {
final setLoggingLevel = { final setLoggingLevel = {
"EventType": "SetLoggingLevel", "EventType": "SetLoggingLevel",

View File

@ -3,6 +3,10 @@ import 'package:provider/provider.dart';
import '../model.dart'; import '../model.dart';
bool noFilter(ContactInfoState peer) {
return true;
}
// Dropdown menu populated from Provider.of<ProfileInfoState>'s contact list // Dropdown menu populated from Provider.of<ProfileInfoState>'s contact list
// Includes both peers and groups; begins empty/nothing selected // Includes both peers and groups; begins empty/nothing selected
// Displays nicknames to UI but uses handles as values // Displays nicknames to UI but uses handles as values
@ -10,8 +14,10 @@ import '../model.dart';
class DropdownContacts extends StatefulWidget { class DropdownContacts extends StatefulWidget {
DropdownContacts({ DropdownContacts({
required this.onChanged, required this.onChanged,
this.filter = noFilter,
}); });
final Function(dynamic) onChanged; final Function(dynamic) onChanged;
final bool Function(ContactInfoState) filter;
@override @override
_DropdownContactsState createState() => _DropdownContactsState(); _DropdownContactsState createState() => _DropdownContactsState();
@ -24,7 +30,7 @@ class _DropdownContactsState extends State<DropdownContacts> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return DropdownButton( return DropdownButton(
value: this.selected, value: this.selected,
items: Provider.of<ProfileInfoState>(context, listen: false).contactList.contacts.map<DropdownMenuItem<String>>((ContactInfoState contact) { items: Provider.of<ProfileInfoState>(context, listen: false).contactList.contacts.where(widget.filter).map<DropdownMenuItem<String>>((ContactInfoState contact) {
return DropdownMenuItem<String>(value: contact.onion, child: Text(contact.nickname)); return DropdownMenuItem<String>(value: contact.onion, child: Text(contact.nickname));
}).toList(), }).toList(),
onChanged: (String? newVal) { onChanged: (String? newVal) {

View File

@ -28,6 +28,7 @@ class _CwtchButtonTextFieldState extends State<CwtchButtonTextField> {
suffixIcon: IconButton( suffixIcon: IconButton(
onPressed: widget.onPressed, onPressed: widget.onPressed,
icon: widget.icon, icon: widget.icon,
padding: EdgeInsets.fromLTRB(0.0, 4.0, 2.0, 2.0),
tooltip: widget.tooltip, tooltip: widget.tooltip,
enableFeedback: true, enableFeedback: true,
color: theme.current().mainTextColor(), color: theme.current().mainTextColor(),

View File

@ -1,6 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'package:cwtch/cwtch_icons_icons.dart'; import 'package:cwtch/cwtch_icons_icons.dart';
import 'package:cwtch/widgets/malformedbubble.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../main.dart'; import '../main.dart';
@ -21,15 +22,17 @@ class InvitationBubble extends StatefulWidget {
class InvitationBubbleState extends State<InvitationBubble> { class InvitationBubbleState extends State<InvitationBubble> {
bool rejected = false; bool rejected = false;
bool isAccepted = false;
FocusNode _focus = FocusNode(); FocusNode _focus = FocusNode();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var fromMe = Provider.of<MessageState>(context).senderOnion == Provider.of<ProfileInfoState>(context).onion; var fromMe = Provider.of<MessageState>(context).senderOnion == Provider.of<ProfileInfoState>(context).onion;
var isGroup = Provider.of<MessageState>(context).overlay == 101; var isGroup = Provider.of<MessageState>(context).overlay == 101;
var isAccepted = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageState>(context).inviteTarget) != null; isAccepted = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageState>(context).inviteTarget) != null;
var prettyDate = ""; var prettyDate = "";
var borderRadiousEh = 15.0; var borderRadiousEh = 15.0;
rejected = Provider.of<MessageState>(context).flags & 0x01 == 0x01;
var myKey = Provider.of<MessageState>(context).profileOnion + "::" + Provider.of<MessageState>(context).contactHandle + "::" + Provider.of<MessageState>(context).messageIndex.toString(); var myKey = Provider.of<MessageState>(context).profileOnion + "::" + Provider.of<MessageState>(context).contactHandle + "::" + Provider.of<MessageState>(context).messageIndex.toString();
if (Provider.of<MessageState>(context).timestamp != null) { if (Provider.of<MessageState>(context).timestamp != null) {
@ -53,25 +56,32 @@ class InvitationBubbleState extends State<InvitationBubble> {
child: SelectableText(senderDisplayStr + '\u202F', child: SelectableText(senderDisplayStr + '\u202F',
style: TextStyle(fontSize: 9.0, color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor()))); style: TextStyle(fontSize: 9.0, color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor())));
// todo: translations // If we receive an invite for ourselves, treat it as a bug. The UI no longer allows this so it could have only come from
// some kind of malfeasance.
var selfInvite = Provider.of<MessageState>(context).inviteNick == Provider.of<ProfileInfoState>(context).onion;
if (selfInvite) {
return MalformedBubble();
}
var wdgMessage = fromMe var wdgMessage = fromMe
? senderInviteChrome("You sent an invitation for", isGroup ? "a group" : Provider.of<MessageState>(context).message, myKey) ? senderInviteChrome(AppLocalizations.of(context)!.sendAnInvitation,
: inviteChrome(isGroup ? AppLocalizations.of(context)!.inviteToGroup : "This is a contact suggestion for:", Provider.of<MessageState>(context).inviteNick, isGroup ? Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageState>(context).inviteTarget)!.nickname : Provider.of<MessageState>(context).message, myKey)
Provider.of<MessageState>(context).inviteTarget, myKey); : (inviteChrome(isGroup ? AppLocalizations.of(context)!.inviteToGroup : AppLocalizations.of(context)!.contactSuggestion, Provider.of<MessageState>(context).inviteNick,
Provider.of<MessageState>(context).inviteTarget, myKey));
Widget wdgDecorations; Widget wdgDecorations;
if (fromMe) { if (fromMe) {
wdgDecorations = MessageBubbleDecoration(ackd: Provider.of<MessageState>(context).ackd, errored: Provider.of<MessageState>(context).error, fromMe: fromMe, prettyDate: prettyDate); wdgDecorations = MessageBubbleDecoration(ackd: Provider.of<MessageState>(context).ackd, errored: Provider.of<MessageState>(context).error, fromMe: fromMe, prettyDate: prettyDate);
} else if (isAccepted) { } else if (isAccepted) {
wdgDecorations = Text("Accepted!"); wdgDecorations = Text(AppLocalizations.of(context)!.accepted + '\u202F');
} else if (this.rejected) { } else if (this.rejected) {
wdgDecorations = Text("Rejected."); wdgDecorations = Text(AppLocalizations.of(context)!.rejected + '\u202F');
} else { } else {
wdgDecorations = Center( wdgDecorations = Center(
widthFactor: 1, widthFactor: 1,
child: Row(children: [ child: Wrap(children: [
Padding(padding: EdgeInsets.all(5), child: TextButton(child: Text("Reject"), onPressed: _btnReject)), Padding(padding: EdgeInsets.all(5), child: TextButton(child: Text(AppLocalizations.of(context)!.rejectGroupBtn + '\u202F'), onPressed: _btnReject)),
Padding(padding: EdgeInsets.all(5), child: TextButton(child: Text("Accept"), onPressed: _btnAccept)), Padding(padding: EdgeInsets.all(5), child: TextButton(child: Text(AppLocalizations.of(context)!.acceptGroupBtn + '\u202F'), onPressed: _btnAccept)),
])); ]));
} }
@ -95,87 +105,83 @@ class InvitationBubbleState extends State<InvitationBubble> {
widthFactor: 1.0, widthFactor: 1.0,
child: Padding( child: Padding(
padding: EdgeInsets.all(9.0), padding: EdgeInsets.all(9.0),
child: Row(mainAxisSize: MainAxisSize.min, children: [ child: Wrap(runAlignment: WrapAlignment.spaceEvenly, alignment: WrapAlignment.spaceEvenly, runSpacing: 1.0, crossAxisAlignment: WrapCrossAlignment.center, children: [
Center(widthFactor: 1, child: Padding(padding: EdgeInsets.all(4), child: Icon(CwtchIcons.send_invite, size: 32))), Center(widthFactor: 1, child: Padding(padding: EdgeInsets.all(10.0), child: Icon(CwtchIcons.send_invite, size: 32))),
Center( Center(
widthFactor: 1.0, widthFactor: 1.0,
child: Column( child: Column(
crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start, crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start,
mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start, mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: fromMe ? [wdgMessage, wdgDecorations] : [wdgSender, wdgMessage, wdgDecorations])), children: fromMe ? [wdgMessage, wdgDecorations] : [wdgSender, wdgMessage, wdgDecorations]),
)
]))))); ])))));
}); });
} }
void _btnReject() { void _btnReject() {
//todo: how should we track inline invite rejections? setState(() {
setState(() => this.rejected = true); var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
var contact = Provider.of<ContactInfoState>(context, listen: false).onion;
var idx = Provider.of<MessageState>(context, listen: false).messageIndex;
Provider.of<FlwtchState>(context, listen: false).cwtch.UpdateMessageFlags(profileOnion, contact, idx, Provider.of<MessageState>(context, listen: false).flags | 0x01);
Provider.of<MessageState>(context).flags |= 0x01;
});
} }
void _btnAccept() { void _btnAccept() {
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion; setState(() {
if (Provider.of<MessageState>(context, listen: false).overlay == 100) { var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
final setPeerAttribute = {
"EventType": "AddContact",
"Data": {"ImportString": Provider.of<MessageState>(context, listen: false).message},
};
final setPeerAttributeJson = jsonEncode(setPeerAttribute);
Provider.of<FlwtchState>(context, listen: false).cwtch.SendProfileEvent(profileOnion, setPeerAttributeJson);
} else {
Provider.of<FlwtchState>(context, listen: false).cwtch.ImportBundle(profileOnion, Provider.of<MessageState>(context, listen: false).message); Provider.of<FlwtchState>(context, listen: false).cwtch.ImportBundle(profileOnion, Provider.of<MessageState>(context, listen: false).message);
} isAccepted = true;
});
} }
// Construct an invite chrome for the sender // Construct an invite chrome for the sender
Widget senderInviteChrome(String chrome, String targetName, String myKey) { Widget senderInviteChrome(String chrome, String targetName, String myKey) {
return Center( return Wrap(children: [
widthFactor: 1, SelectableText(
child: Row(children: [ chrome + '\u202F',
SelectableText( style: TextStyle(
chrome, color: Provider.of<Settings>(context).theme.messageFromMeTextColor(),
focusNode: _focus, ),
style: TextStyle( textAlign: TextAlign.left,
color: Provider.of<Settings>(context).theme.messageFromMeTextColor(), maxLines: 2,
), textWidthBasis: TextWidthBasis.longestLine,
textAlign: TextAlign.left, ),
textWidthBasis: TextWidthBasis.longestLine, SelectableText(
), targetName + '\u202F',
SelectableText( key: Key(myKey),
targetName + '\u202F', style: TextStyle(
key: Key(myKey), color: Provider.of<Settings>(context).theme.messageFromMeTextColor(),
focusNode: _focus, ),
style: TextStyle( textAlign: TextAlign.left,
color: Provider.of<Settings>(context).theme.messageFromMeTextColor(), maxLines: 2,
), textWidthBasis: TextWidthBasis.longestLine,
textAlign: TextAlign.left, )
textWidthBasis: TextWidthBasis.longestLine, ]);
)
]));
} }
// Construct an invite chrome // Construct an invite chrome
Widget inviteChrome(String chrome, String targetName, String targetId, String myKey) { Widget inviteChrome(String chrome, String targetName, String targetId, String myKey) {
return Center( return Wrap(children: [
widthFactor: 1, SelectableText(
child: Row(children: [ chrome + '\u202F',
SelectableText( style: TextStyle(
chrome, color: Provider.of<Settings>(context).theme.messageFromOtherTextColor(),
focusNode: _focus, ),
style: TextStyle( textAlign: TextAlign.left,
color: Provider.of<Settings>(context).theme.messageFromOtherTextColor(), textWidthBasis: TextWidthBasis.longestLine,
), maxLines: 2,
textAlign: TextAlign.left, ),
textWidthBasis: TextWidthBasis.longestLine, SelectableText(
), targetName + '\u202F',
SelectableText( key: Key(myKey),
targetName, style: TextStyle(color: Provider.of<Settings>(context).theme.messageFromOtherTextColor()),
key: Key(myKey), textAlign: TextAlign.left,
focusNode: _focus, maxLines: 2,
style: TextStyle(color: Provider.of<Settings>(context).theme.messageFromOtherTextColor()), textWidthBasis: TextWidthBasis.longestLine,
textAlign: TextAlign.left, )
textWidthBasis: TextWidthBasis.longestLine, ]);
)
]));
} }
} }

View File

@ -39,7 +39,6 @@ class _MessageListState extends State<MessageList> {
)), )),
Expanded( Expanded(
child: Scrollbar( child: Scrollbar(
isAlwaysShown: true,
controller: ctrlr1, controller: ctrlr1,
child: Container( child: Container(
// Only show broken heart is the contact is offline... // Only show broken heart is the contact is offline...
@ -54,7 +53,7 @@ class _MessageListState extends State<MessageList> {
child: ListView.builder( child: ListView.builder(
controller: ctrlr1, controller: ctrlr1,
itemCount: Provider.of<ContactInfoState>(outerContext).totalMessages, itemCount: Provider.of<ContactInfoState>(outerContext).totalMessages,
reverse: true, reverse: true, // NOTE: There seems to be a bug in flutter that corrects the mouse wheel scroll, but not the drag direction...
itemBuilder: (itemBuilderContext, index) { itemBuilder: (itemBuilderContext, index) {
var trueIndex = Provider.of<ContactInfoState>(outerContext).totalMessages - index - 1; var trueIndex = Provider.of<ContactInfoState>(outerContext).totalMessages - index - 1;
return ChangeNotifierProvider( return ChangeNotifierProvider(

View File

@ -94,7 +94,10 @@ class _MessageRowState extends State<MessageRow> {
final setPeerAttributeJson = jsonEncode(setPeerAttribute); final setPeerAttributeJson = jsonEncode(setPeerAttribute);
Provider.of<FlwtchState>(context, listen: false).cwtch.SendProfileEvent(profileOnion, setPeerAttributeJson); Provider.of<FlwtchState>(context, listen: false).cwtch.SendProfileEvent(profileOnion, setPeerAttributeJson);
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.successfullAddedContact)); final snackBar = SnackBar(
content: Text(AppLocalizations.of(context)!.successfullAddedContact),
duration: Duration(seconds: 2),
);
ScaffoldMessenger.of(context).showSnackBar(snackBar); ScaffoldMessenger.of(context).showSnackBar(snackBar);
} }
} }

View File

@ -2,6 +2,8 @@
// Generated file. Do not edit. // Generated file. Do not edit.
// //
// clang-format off
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <window_size/window_size_plugin.h> #include <window_size/window_size_plugin.h>

View File

@ -2,6 +2,8 @@
// Generated file. Do not edit. // Generated file. Do not edit.
// //
// clang-format off
#ifndef GENERATED_PLUGIN_REGISTRANT_ #ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_

View File

@ -21,7 +21,7 @@ packages:
name: async name: async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.6.1" version: "2.7.0"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -191,7 +191,7 @@ packages:
name: meta name: meta
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.0" version: "1.4.0"
nested: nested:
dependency: transitive dependency: transitive
description: description:
@ -392,7 +392,7 @@ packages:
name: test_api name: test_api
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.3.0" version: "0.4.0"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@ -450,7 +450,7 @@ packages:
name: xml name: xml
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "5.1.1" version: "5.1.2"
sdks: sdks:
dart: ">=2.13.0 <3.0.0" dart: ">=2.13.0 <3.0.0"
flutter: ">=1.20.0" flutter: ">=1.20.0"

View File

@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at # Read more about iOS versioning at
# 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
version: 1.0.0+9 version: 1.0.0+11
environment: environment:
sdk: ">=2.12.0 <3.0.0" sdk: ">=2.12.0 <3.0.0"

View File

@ -2,6 +2,8 @@
// Generated file. Do not edit. // Generated file. Do not edit.
// //
// clang-format off
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <window_size/window_size_plugin.h> #include <window_size/window_size_plugin.h>

View File

@ -2,6 +2,8 @@
// Generated file. Do not edit. // Generated file. Do not edit.
// //
// clang-format off
#ifndef GENERATED_PLUGIN_REGISTRANT_ #ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_