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.png deploy/linux
- 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
- cd deploy
- mv linux cwtch
@ -230,6 +231,12 @@ steps:
- mkdir deploy
- move build\\windows\\runner\\Release $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 "Compress-Archive -Path $Env:builddir -DestinationPath $Env:zip"
- 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
Only Open Privacy staff members can update translations automatically:
Only Open Privacy staff members can update translations.
```
flutter pub run flutter_lokalise download -v --api-token "<X>" --project-id "<Y>"
```
In Lokalise, hit Download and make sure:
This will download a bundle of translations from Lokalise and convert it to resource files in `lib/l10n/intl_*.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`).
* Format is set to "Flutter (.arb)
* 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
@ -57,6 +67,5 @@ Text(AppLocalizations.of(context)!.stringIdentifer),
### 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.

View File

@ -69,6 +69,15 @@ android {
signingConfig signingConfigs.release
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
}
flutter {
@ -82,4 +91,30 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2"
implementation "com.airbnb.android:lottie:3.5.0"
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-->
<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>

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
import SplashView
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import androidx.annotation.NonNull
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.os.Looper
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.GlobalScope
@ -25,6 +35,8 @@ import kotlin.concurrent.thread
import org.json.JSONObject
import java.io.File
import java.time.Duration
import java.util.concurrent.TimeUnit
class MainActivity: FlutterActivity() {
@ -40,13 +52,31 @@ class MainActivity: FlutterActivity() {
// Channel to send eventbus events on
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) {
super.configureFlutterEngine(flutterEngine)
// 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_CWTCH).setMethodCallHandler { call, result -> handleCwtch(call, result) }
methodChan = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL_NOTIF_CLICK)
}
private fun handleAppInfo(@NonNull call: MethodCall, @NonNull result: Result) {
@ -61,143 +91,68 @@ class MainActivity: FlutterActivity() {
val ainfo = this.applicationContext.packageManager.getApplicationInfo(
"im.cwtch.flwtch", // Must be app name
PackageManager.GET_SHARED_LIBRARY_FILES)
return ainfo.nativeLibraryDir
}
// receives messages from the ForegroundService (which provides, ironically enough, the backend)
private fun handleCwtch(@NonNull call: MethodCall, @NonNull result: Result) {
when (call.method) {
"Start" -> {
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)
var method = call.method
val argmap: Map<String, String> = call.arguments as Map<String, String>
// seperate coroutine to poll event bus and send to dart
val eventbus_chan = MethodChannel(flutterEngine?.dartExecutor?.binaryMessenger, CWTCH_EVENTBUS)
Log.i("MainActivity.kt", "got event chan: " + eventbus_chan + " launching corouting...")
GlobalScope.launch(Dispatchers.IO) {
while(true) {
val evt = AppbusEvent(Cwtch.getAppBusEvent())
Log.i("MainActivity.kt", "got appbusEvent: " + evt)
launch(Dispatchers.Main) {
//todo: this elides evt.EventID which may be needed at some point?
eventbus_chan.invokeMethod(evt.EventType, evt.Data)
}
// the frontend calls Start every time it fires up, but we don't want to *actually* call Cwtch.Start()
// in case the ForegroundService is still running. in both cases, however, we *do* want to re-register
// the eventbus listener.
if (call.method == "Start") {
val workerTag = "cwtchEventBusWorker"
val uniqueTag = argmap["torPath"] ?: "nullEventBus"
// note: because the ForegroundService is specified as UniquePeriodicWork, it can't actually get
// accidentally duplicated. however, we still need to manually check if it's running or not, so
// 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();
// Log.i("MainActivity.kt", args);
// register our eventbus listener. note that we observe any/all work according to its tag, which
// 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)
val profile = (call.argument("profile") as? String) ?: "";
val handle = (call.argument("contact") as? String) ?: "";
val indexI = call.argument<Int>("index") ?: 0;
Log.i("MainActivity.kt", "indexI = " + indexI)
result.success(Cwtch.getMessage(profile, handle, indexI.toLong()))
if (alreadyRunning) {
Log.i("MainActivity.kt", "diverting Start -> Reconnect")
method = "ReconnectCwtchForeground"
} else {
Log.i("MainActivity.kt", "Start() launching foregroundservice")
// 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
@ -219,4 +174,16 @@ class MainActivity: FlutterActivity() {
val EventID = this.optString("EventID")
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')
}
task clean(type: Delete) {
delete rootProject.buildDir
}
//removed due to gradle namespace conflicts that are beyond erinn's mere mortal understanding
//task clean(type: Delete) {
// delete rootProject.buildDir
//}

Binary file not shown.

View File

@ -1,6 +1,8 @@
abstract class Cwtch {
// ignore: non_constant_identifier_names
Future<void> Start();
Future<dynamic> Start();
// ignore: non_constant_identifier_names
Future<void> ReconnectCwtchForeground();
// ignore: non_constant_identifier_names
void SelectProfile(String onion);
@ -38,6 +40,8 @@ abstract class Cwtch {
// ignore: non_constant_identifier_names
Future<dynamic> GetMessage(String profile, String handle, int index);
// 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);
// ignore: non_constant_identifier_names
void SendMessage(String profile, String handle, String message);

View File

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

View File

@ -15,8 +15,8 @@ import '../config.dart';
/// Cwtch API ///
/////////////////////
typedef start_cwtch_function = Void 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 start_cwtch_function = Int8 Function(Pointer<Utf8> str, Int32 length, Pointer<Utf8> str2, Int32 length2);
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 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 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 NextEventFn = void Function();
@ -76,7 +79,7 @@ class CwtchFfi implements Cwtch {
}
// ignore: non_constant_identifier_names
Future<void> Start() async {
Future<dynamic> Start() async {
String home = "";
String bundledTor = "";
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
@override
void dispose() {
@ -384,4 +395,14 @@ class CwtchFfi implements Cwtch {
final u2 = groupHandle.toNativeUtf8();
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:io';
import 'package:cwtch/config.dart';
import 'package:flutter/services.dart';
@ -43,7 +42,7 @@ class CwtchGomobile implements Cwtch {
}
// ignore: non_constant_identifier_names
Future<void> Start() async {
Future<dynamic> Start() async {
print("gomobile.dart: Start()...");
var cwtchDir = path.join((await androidHomeDirectory).path, ".cwtch");
if (EnvironmentConfig.BUILD_VER == dev_version) {
@ -51,7 +50,13 @@ class CwtchGomobile implements Cwtch {
}
String torPath = path.join(await androidLibraryDir, "libtor.so");
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
@ -187,4 +192,10 @@ class CwtchGomobile implements Cwtch {
void LeaveGroup(String profileOnion, String 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
/// Copyright (C) 2021 by original authors @ fluttericon.com, fontello.com
/// Flutter icons CwtchIcons
/// 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.
///
/// 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_peer = IconData(0xe84f, 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 copy_address = IconData(0xe889, 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);
}

View File

@ -1,166 +1,186 @@
{
"@@locale": "de",
"acceptGroupBtn": "Annehmen",
"acceptGroupInviteLabel": "Möchtest Du die Einladung annehmen",
"acknowledgedLabel": "bestätigt",
"addListItem": "Liste hinzufügen",
"addListItemBtn": "Element hinzufügen",
"addNewItem": "Ein neues Element zur Liste hinzufügen",
"addNewProfileBtn": "Neues Profil hinzufügen",
"addPeer": "Peer hinzufügen",
"addPeerTab": "Einen Peer hinzufügen",
"addProfileTitle": "Neues Profil hinzufügen",
"addressLabel": "Adresse",
"blockBtn": "Peer blockieren",
"blocked": "Blockiert",
"blockUnknownLabel": "Unbekannte Peers blockieren",
"builddate": "Aufgebaut auf: %2",
"bulletinsBtn": "Meldungen",
"chatBtn": "Chat",
"chatHistoryDefault": "",
"contactAlreadyExists": "",
"conversationSettings": "",
"copiedClipboardNotification": "in die Zwischenablage kopiert",
"copiedToClipboardNotification": "in die Zwischenablage kopiert",
"copyBtn": "Kopieren",
"couldNotSendMsgError": "Nachricht konnte nicht gesendet werden",
"createGroup": "Gruppe erstellen",
"createGroupBtn": "Anlegen",
"createGroupTab": "Eine Gruppe erstellen",
"createGroupTitle": "Gruppe Anlegen",
"createProfileBtn": "Profil speichern",
"currentPasswordLabel": "derzeitiges Passwort",
"cwtchSettingsTitle": "Cwtch Einstellungen",
"cycleCatsAndroid": "",
"cycleCatsDesktop": "",
"cycleColoursAndroid": "",
"cycleColoursDesktop": "",
"cycleMorphsAndroid": "",
"cycleMorphsDesktop": "",
"dateDaysAgo": "",
"dateHoursAgo": "",
"dateLastMonth": "",
"dateLastYear": "",
"dateMinutesAgo": "",
"dateMonthsAgo": "",
"dateNever": "",
"dateRightNow": "",
"dateWeeksAgo": "",
"dateYearsAgo": "",
"dateYesterday": "",
"defaultGroupName": "Tolle Gruppe",
"defaultProfileName": "Alice",
"defaultScalingText": "defaultmäßige Textgröße (Skalierungsfaktor:",
"deleteBtn": "Löschen",
"deleteConfirmLabel": "Geben Sie LÖSCHEN zur Bestätigung ein",
"deleteConfirmText": "LÖSCHEN",
"deleteProfileBtn": "Profil löschen",
"deleteProfileConfirmBtn": "Profil wirklich löschen",
"descriptionBlockUnknownConnections": "",
"descriptionExperiments": "",
"descriptionExperimentsGroups": "",
"displayNameLabel": "Angezeigter Name",
"dmTooltip": "Klicken, um DM zu senden",
"dontSavePeerHistory": "Peer-Verlauf löschen",
"editProfile": "Profil bearbeiten",
"editProfileTitle": "Profil bearbeiten",
"enableGroups": "",
"enterCurrentPasswordForDelete": "",
"enterProfilePassword": "Geben Sie ein Passwort ein, um Ihre Profile anzuzeigen",
"error0ProfilesLoadedForPassword": "0 Profile mit diesem Passwort geladen",
"experimentsEnabled": "Experimente aktiviert",
"groupAddr": "Adresse",
"groupName": "Gruppenname",
"groupNameLabel": "Gruppenname",
"invalidImportString": "",
"invitation": "Einladung",
"invitationLabel": "Einladung",
"inviteBtn": "Einladen",
"inviteToGroup": "",
"inviteToGroupLabel": "In die Gruppe einladen",
"joinGroup": "Gruppe beitreten",
"joinGroupTab": "Einer Gruppe beitreten",
"largeTextLabel": "Groß",
"leaveGroup": "",
"listsBtn": "Listen",
"loadingTor": "Tor wird geladen...",
"localeDe": "Deutsche",
"localeEn": "",
"localeEs": "",
"localeFr": "",
"localeIt": "",
"localePt": "",
"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.",
"networkStatusAttemptingTor": "Versuche, eine Verbindung mit dem Tor-Netzwerk herzustellen",
"networkStatusConnecting": "Verbinde zu Netzwerk und Peers ...",
"networkStatusDisconnected": "Vom Internet getrennt, überprüfen Sie Ihre Verbindung",
"networkStatusOnline": "Online",
"newBulletinLabel": "Neue Meldung",
"newConnectionPaneTitle": "Neue Verbindung",
"newGroupBtn": "Neue Gruppe anlegen",
"newPassword": "",
"newProfile": "Neues Profil",
"noPasswordWarning": "Wenn für dieses Konto kein Passwort verwendet wird, bedeutet dies, dass alle lokal gespeicherten Daten nicht verschlüsselt werden.",
"password": "Passwort",
"password1Label": "Passwort",
"password2Label": "Passwort erneut eingeben",
"passwordChangeError": "Fehler beim Ändern des Passworts: Das Passwort wurde abgelehnt",
"passwordErrorEmpty": "Passwort kann nicht leer sein",
"passwordErrorMatch": "Passwörter stimmen nicht überein",
"pasteAddressToAddContact": "Adresse hier hinzufügen, um einen Kontakt aufzunehmen",
"peerAddress": "Adresse",
"peerBlockedMessage": "Peer ist blockiert",
"peerName": "Namen",
"peerNotOnline": "",
"peerOfflineMessage": "Peer ist offline, Nachrichten können derzeit nicht zugestellt werden",
"pendingLabel": "Bestätigung ausstehend",
"postNewBulletinLabel": "Neue Meldung veröffentlichen",
"profileName": "Anzeigename",
"profileOnionLabel": "Senden Sie diese Adresse an Peers, mit denen Sie sich verbinden möchten",
"puzzleGameBtn": "Puzzlespiel",
"radioNoPassword": "Unverschlüsselt (kein Passwort)",
"radioUsePassword": "Passwort",
"reallyLeaveThisGroupPrompt": "",
"rejectGroupBtn": "Ablehnen",
"saveBtn": "Speichern",
"savePeerHistory": "Peer-Verlauf speichern",
"savePeerHistoryDescription": "Legt fest, ob ein mit dem Peer verknüpfter Verlauf gelöscht werden soll oder nicht.",
"saveProfileBtn": "Profil speichern",
"search": "Suche...",
"searchList": "",
"server": "Server",
"serverConnectivityConnected": "Server verbunden",
"serverConnectivityDisconnected": "Server getrennt",
"serverInfo": "Server-Informationen",
"serverLabel": "Server",
"serverNotSynced": "",
"serverSynced": "",
"settingInterfaceZoom": "Zoomstufe",
"settingLanguage": "Sprache",
"settingTheme": "Thema",
"smallTextLabel": "Klein",
"successfullAddedContact": "",
"themeDark": "Dunkel",
"themeLight": "Licht",
"titleManageContacts": "",
"titleManageProfiles": "",
"titleManageServers": "",
"titlePlaceholder": "Titel...",
"todoPlaceholder": "noch zu erledigen",
"tooltipAddContact": "",
"tooltipOpenSettings": "",
"tooltipUnlockProfiles": "",
"unblockBtn": "Peer entblockieren",
"unlock": "Entsperren",
"update": "",
"version": "Version %1",
"versionBuilddate": "Version: %1 Aufgebaut auf: %2",
"versionTor": "Version %1 mit tor %2",
"viewGroupMembershipTooltip": "Gruppenmitgliedschaft anzeigen",
"viewServerInfo": "",
"yesLeave": "",
"yourDisplayName": "Ihr Anzeigename",
"yourProfiles": "Ihre Profile",
"yourServers": "Ihre Server",
"zoomLabel": "Benutzeroberflächen-Zoom (betriftt hauptsächlich Text- und Knopgrößen)"
"@@locale": "de",
"@@last_modified": "2021-06-15T02:08:49+02:00",
"createGroupTitle": "Gruppe Anlegen",
"serverLabel": "Server",
"groupNameLabel": "Gruppenname",
"defaultGroupName": "Tolle Gruppe",
"createGroupBtn": "Anlegen",
"profileOnionLabel": "Senden Sie diese Adresse an Peers, mit denen Sie sich verbinden möchten",
"copyBtn": "Kopieren",
"copiedToClipboardNotification": "in die Zwischenablage kopiert",
"addPeerTab": "Einen Peer hinzufügen",
"createGroupTab": "Eine Gruppe erstellen",
"joinGroupTab": "Einer Gruppe beitreten",
"peerAddress": "Adresse",
"peerName": "Namen",
"groupName": "Gruppenname",
"server": "Server",
"invitation": "Einladung",
"groupAddr": "Adresse",
"addPeer": "Peer hinzufügen",
"createGroup": "Gruppe erstellen",
"joinGroup": "Gruppe beitreten",
"newBulletinLabel": "Neue Meldung",
"postNewBulletinLabel": "Neue Meldung veröffentlichen",
"titlePlaceholder": "Titel...",
"pasteAddressToAddContact": "Adresse hier hinzufügen, um einen Kontakt aufzunehmen",
"blocked": "Blockiert",
"cycleCatsAndroid": "Click to cycle category.\nLong-press to reset.",
"cycleCatsDesktop": "Click to cycle category.\nRight-click to reset.",
"cycleMorphsAndroid": "Click to cycle morphs.\nLong-press to reset.",
"cycleMorphsDesktop": "Click to cycle morphs.\nRight-click to reset.",
"cycleColoursAndroid": "Click to cycle colours.\nLong-press to reset.",
"cycleColoursDesktop": "Click to cycle colours.\nRight-click to reset.",
"search": "Suche...",
"invitationLabel": "Einladung",
"serverInfo": "Server-Informationen",
"serverConnectivityConnected": "Server verbunden",
"serverConnectivityDisconnected": "Server getrennt",
"serverSynced": "Synced",
"serverNotSynced": "Out of Sync",
"viewServerInfo": "Server Info",
"saveBtn": "speichern",
"inviteToGroupLabel": "In die Gruppe einladen",
"inviteBtn": "Einladen",
"deleteBtn": "löschen",
"update": "Update",
"searchList": "Search List",
"peerNotOnline": "Peer is Offline. Applications cannot be used right now.",
"addListItemBtn": "Element hinzufügen",
"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.",
"dmTooltip": "Klicken, um DM zu senden",
"couldNotSendMsgError": "Nachricht konnte nicht gesendet werden",
"acknowledgedLabel": "bestätigt",
"pendingLabel": "Bestätigung ausstehend",
"peerBlockedMessage": "Peer ist blockiert",
"peerOfflineMessage": "Peer ist offline, Nachrichten können derzeit nicht zugestellt werden",
"copiedClipboardNotification": "in die Zwischenablage kopiert",
"newGroupBtn": "Neue Gruppe anlegen",
"acceptGroupInviteLabel": "Möchtest Du die Einladung annehmen",
"acceptGroupBtn": "Annehmen",
"rejectGroupBtn": "Ablehnen",
"chatBtn": "Chat",
"listsBtn": "Listen",
"bulletinsBtn": "Meldungen",
"puzzleGameBtn": "Puzzlespiel",
"addressLabel": "Adresse",
"displayNameLabel": "Angezeigter Name",
"blockBtn": "Peer blockieren",
"savePeerHistory": "Peer-Verlauf speichern",
"savePeerHistoryDescription": "Legt fest, ob ein mit dem Peer verknüpfter Verlauf gelöscht werden soll oder nicht.",
"dontSavePeerHistory": "Peer-Verlauf löschen",
"unblockBtn": "Peer entblockieren",
"addProfileTitle": "Neues Profil hinzufügen",
"editProfileTitle": "Profil bearbeiten",
"profileName": "Anzeigename",
"defaultProfileName": "Alice",
"newProfile": "Neues Profil",
"editProfile": "Profil bearbeiten",
"radioUsePassword": "Passwort",
"radioNoPassword": "Unverschlüsselt (kein Passwort)",
"noPasswordWarning": "Wenn für dieses Konto kein Passwort verwendet wird, bedeutet dies, dass alle lokal gespeicherten Daten nicht verschlüsselt werden.",
"yourDisplayName": "Ihr Anzeigename",
"currentPasswordLabel": "derzeitiges Passwort",
"password1Label": "Passwort",
"password2Label": "Passwort erneut eingeben",
"passwordErrorEmpty": "Passwort kann nicht leer sein",
"createProfileBtn": "Profil speichern",
"saveProfileBtn": "Profil speichern",
"passwordErrorMatch": "Passwörter stimmen nicht überein",
"passwordChangeError": "Fehler beim Ändern des Passworts: Das Passwort wurde abgelehnt",
"deleteProfileBtn": "Profil löschen",
"deleteConfirmLabel": "Geben Sie LÖSCHEN zur Bestätigung ein",
"deleteProfileConfirmBtn": "Profil wirklich löschen",
"deleteConfirmText": "LÖSCHEN",
"addNewProfileBtn": "Neues Profil hinzufügen",
"enterProfilePassword": "Geben Sie ein Passwort ein, um Ihre Profile anzuzeigen",
"password": "Passwort",
"error0ProfilesLoadedForPassword": "0 Profile mit diesem Passwort geladen",
"yourProfiles": "Ihre Profile",
"yourServers": "Ihre Server",
"unlock": "Entsperren",
"cwtchSettingsTitle": "Cwtch Einstellungen",
"versionBuilddate": "Version: %1 Aufgebaut auf: %2",
"zoomLabel": "Benutzeroberflächen-Zoom (betriftt hauptsächlich Text- und Knopgrößen)",
"blockUnknownLabel": "Unbekannte Peers blockieren",
"settingLanguage": "Sprache",
"localeEn": "English",
"localeFr": "Frances",
"localePt": "Portuguesa",
"localeDe": "Deutsche",
"settingInterfaceZoom": "Zoomstufe",
"largeTextLabel": "Groß",
"settingTheme": "Thema",
"themeLight": "Licht",
"themeDark": "Dunkel",
"experimentsEnabled": "Experimente aktiviert",
"versionTor": "Version %1 mit tor %2",
"version": "Version %1",
"builddate": "Aufgebaut auf: %2",
"defaultScalingText": "defaultmäßige Textgröße (Skalierungsfaktor:",
"smallTextLabel": "Klein",
"loadingTor": "Tor wird geladen...",
"viewGroupMembershipTooltip": "Gruppenmitgliedschaft anzeigen",
"networkStatusDisconnected": "Vom Internet getrennt, überprüfen Sie Ihre Verbindung",
"networkStatusAttemptingTor": "Versuche, eine Verbindung mit dem Tor-Netzwerk herzustellen",
"networkStatusConnecting": "Verbinde zu Netzwerk und Peers ...",
"networkStatusOnline": "Online",
"newConnectionPaneTitle": "Neue Verbindung",
"addListItem": "Liste hinzufügen",
"addNewItem": "Ein neues Element zur Liste hinzufügen",
"todoPlaceholder": "noch zu erledigen",
"localeEs": "Espanol",
"localeIt": "Italiana",
"enableGroups": "Enable Group Chat",
"enterCurrentPasswordForDelete": "Please enter current password to delete this profile.",
"conversationSettings": "Conversation Settings",
"invalidImportString": "Invalid import string",
"contactAlreadyExists": "Contact Already Exists",
"tooltipOpenSettings": "Open the settings pane",
"tooltipAddContact": "Add a new contact or conversation",
"titleManageContacts": "Conversations",
"tooltipUnlockProfiles": "Unlock encrypted profiles by entering their password.",
"titleManageProfiles": "Manage Cwtch Profiles",
"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.",
"descriptionExperimentsGroups": "The group experiment allows Cwtch to connect with untrusted server infrastructure to facilitate communication with more than one contact.",
"descriptionBlockUnknownConnections": "If turned on, this option will automatically close connections from Cwtch users that have not been added to your contact list.",
"successfullAddedContact": "Successfully added ",
"dateRightNow": "Right Now",
"dateMinutesAgo": "Minutes Ago",
"dateHoursAgo": "Hours Ago",
"dateDaysAgo": "Days Ago",
"dateWeeksAgo": "Weeks Ago",
"dateLastMonth": "Last Month",
"dateYesterday": "Yesterday",
"dateLastYear": "Last Year",
"dateYearsAgo": "X Years Ago (displayed next to a contact row to indicate time of last action)",
"dateNever": "Never",
"dateMonthsAgo": "Months Ago",
"titleManageServers": "Manage Servers",
"inviteToGroup": "You have been invited to join a group:",
"leaveGroup": "Leave This Conversation",
"reallyLeaveThisGroupPrompt": "Are you sure you want to leave this conversation? All messages and attributes will be deleted.",
"yesLeave": "Yes, Leave This Conversation",
"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",
"acceptGroupBtn": "Accept",
"acceptGroupInviteLabel": "Do you want to accept the invitation to",
"acknowledgedLabel": "Acknowledged",
"addListItem": "Add a New List Item",
"addListItemBtn": "Add Item",
"addNewItem": "Add a new item to the list",
"addNewProfileBtn": "Add new profile",
"addPeer": "Add Peer",
"addPeerTab": "Add a peer",
"addProfileTitle": "Add new profile",
"addressLabel": "Address",
"blockBtn": "Block Peer",
"blocked": "Blocked",
"blockUnknownLabel": "Block Unknown Peers",
"builddate": "Built on: %2",
"bulletinsBtn": "Bulletins",
"chatBtn": "Chat",
"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.",
"contactAlreadyExists": "Contact Already Exists",
"conversationSettings": "Conversation Settings",
"copiedClipboardNotification": "Copied to clipboard",
"copiedToClipboardNotification": "Copied to Clipboard",
"copyBtn": "Copy",
"couldNotSendMsgError": "Could not send this message",
"createGroup": "Create group",
"createGroupBtn": "Create",
"createGroupTab": "Create a group",
"createGroupTitle": "Create Group",
"createProfileBtn": "Create Profile",
"currentPasswordLabel": "Current Password",
"cwtchSettingsTitle": "Cwtch Settings",
"cycleCatsAndroid": "Click to cycle category.\\nLong-press to reset.",
"cycleCatsDesktop": "Click to cycle category.\\nRight-click to reset.",
"cycleColoursAndroid": "Click to cycle colours.\\nLong-press to reset.",
"cycleColoursDesktop": "Click to cycle colours.\\nRight-click to reset.",
"cycleMorphsAndroid": "Click to cycle morphs.\\nLong-press to reset.",
"cycleMorphsDesktop": "Click to cycle morphs.\\nRight-click to reset.",
"dateDaysAgo": "Days Ago",
"dateHoursAgo": "Hours Ago",
"dateLastMonth": "Last Month",
"dateLastYear": "Last Year",
"dateMinutesAgo": "Minutes Ago",
"dateMonthsAgo": "Months Ago",
"dateNever": "Never",
"dateRightNow": "Right Now",
"dateWeeksAgo": "Weeks Ago",
"dateYearsAgo": "X Years Ago (displayed next to a contact row to indicate time of last action)",
"dateYesterday": "Yesterday",
"defaultGroupName": "Awesome Group",
"defaultProfileName": "Alice",
"defaultScalingText": "Default size text (scale factor:",
"deleteBtn": "Delete",
"deleteConfirmLabel": "Type DELETE to confirm",
"deleteConfirmText": "DELETE",
"deleteProfileBtn": "Delete Profile",
"deleteProfileConfirmBtn": "Really Delete Profile",
"descriptionBlockUnknownConnections": "If turned on, this option will automatically close connections from Cwtch users that have not been added to your contact list.",
"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.",
"descriptionExperimentsGroups": "The group experiment allows Cwtch to connect with untrusted server infrastructure to facilitate communication with more than one contact.",
"displayNameLabel": "Display Name",
"dmTooltip": "Click to DM",
"dontSavePeerHistory": "Delete Peer History",
"editProfile": "Edit Profille",
"editProfileTitle": "Edit Profile",
"enableGroups": "Enable Group Chat",
"enterCurrentPasswordForDelete": "Please enter current password to delete this profile.",
"enterProfilePassword": "Enter a password to view your profiles",
"error0ProfilesLoadedForPassword": "0 profiles loaded with that password",
"experimentsEnabled": "Enable Experiments",
"groupAddr": "Address",
"groupName": "Group name",
"groupNameLabel": "Group Name",
"invalidImportString": "Invalid import string",
"invitation": "Invitation",
"invitationLabel": "Invitation",
"inviteBtn": "Invite",
"inviteToGroup": "You have been invited to join a group:",
"inviteToGroupLabel": "Invite to group",
"joinGroup": "Join group",
"joinGroupTab": "Join a group",
"largeTextLabel": "Large",
"leaveGroup": "Leave This Conversation",
"listsBtn": "Lists",
"loadingTor": "Loading tor...",
"localeDe": "Deutsche",
"localeEn": "English",
"localeEs": "Espanol",
"localeFr": "Frances",
"localeIt": "Italiana",
"localePt": "Portuguesa",
"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.",
"networkStatusAttemptingTor": "Attempting to connect to Tor network",
"networkStatusConnecting": "Connecting to network and peers...",
"networkStatusDisconnected": "Disconnected from the internet, check your connection",
"networkStatusOnline": "Online",
"newBulletinLabel": "New Bulletin",
"newConnectionPaneTitle": "New Connection",
"newGroupBtn": "Create new group",
"newPassword": "New Password",
"newProfile": "New Profile",
"noPasswordWarning": "Not using a password on this account means that all data stored locally will not be encrypted",
"password": "Password",
"password1Label": "Password",
"password2Label": "Reenter password",
"passwordChangeError": "Error changing password: Supplied password rejected",
"passwordErrorEmpty": "Password cannot be empty",
"passwordErrorMatch": "Passwords do not match",
"pasteAddressToAddContact": "Paste a cwtch address, invitation or key bundle here to add a new conversation",
"peerAddress": "Address",
"peerBlockedMessage": "Peer is blocked",
"peerName": "Name",
"peerNotOnline": "Peer is Offline. Applications cannot be used right now.",
"peerOfflineMessage": "Peer is offline, messages can't be delivered right now",
"pendingLabel": "Pending",
"postNewBulletinLabel": "Post new bulletin",
"profileName": "Display name",
"profileOnionLabel": "Send this address to peers you want to connect with",
"puzzleGameBtn": "Puzzle Game",
"radioNoPassword": "Unencrypted (No password)",
"radioUsePassword": "Password",
"reallyLeaveThisGroupPrompt": "Are you sure you want to leave this conversation? All messages and attributes will be deleted.",
"rejectGroupBtn": "Reject",
"saveBtn": "Save",
"savePeerHistory": "Save Peer History",
"savePeerHistoryDescription": "Determines whether or not to delete any history associated with the peer.",
"saveProfileBtn": "Save Profile",
"search": "Search...",
"searchList": "Search List",
"server": "Server",
"serverConnectivityConnected": "Server Connected",
"serverConnectivityDisconnected": "Server Disconnected",
"serverInfo": "Server Information",
"serverLabel": "Server",
"serverNotSynced": "Out of Sync",
"serverSynced": "Synced",
"settingInterfaceZoom": "Zoom level",
"settingLanguage": "Language",
"settingTheme": "Theme",
"smallTextLabel": "Small",
"successfullAddedContact": "Successfully added ",
"themeDark": "Dark",
"themeLight": "Light",
"titleManageContacts": "Conversations",
"titleManageProfiles": "Manage Cwtch Profiles",
"titleManageServers": "Manage Servers",
"titlePlaceholder": "title...",
"todoPlaceholder": "Todo...",
"tooltipAddContact": "Add a new contact or conversation",
"tooltipOpenSettings": "Open the settings pane",
"tooltipUnlockProfiles": "Unlock encrypted profiles by entering their password.",
"unblockBtn": "Unblock Peer",
"unlock": "Unlock",
"update": "Update",
"version": "Version %1",
"versionBuilddate": "Version: %1 Built on: %2",
"versionTor": "Version %1 with tor %2",
"viewGroupMembershipTooltip": "View Group Membership",
"viewServerInfo": "Server Info",
"yesLeave": "Yes, Leave This Conversation",
"yourDisplayName": "Your Display Name",
"yourProfiles": "Your Profiles",
"yourServers": "Your Servers",
"zoomLabel": "Interface zoom (mostly affects text and button sizes)"
"@@locale": "en",
"@@last_modified": "2021-06-15T02:08:49+02:00",
"createGroupTitle": "Create Group",
"serverLabel": "Server",
"groupNameLabel": "Group Name",
"defaultGroupName": "Awesome Group",
"createGroupBtn": "Create",
"profileOnionLabel": "Send this address to peers you want to connect with",
"copyBtn": "Copy",
"copiedToClipboardNotification": "Copied to Clipboard",
"addPeerTab": "Add a peer",
"createGroupTab": "Create a group",
"joinGroupTab": "Join a group",
"peerAddress": "Address",
"peerName": "Name",
"groupName": "Group name",
"server": "Server",
"invitation": "Invitation",
"groupAddr": "Address",
"addPeer": "Add Peer",
"createGroup": "Create group",
"joinGroup": "Join group",
"newBulletinLabel": "New Bulletin",
"postNewBulletinLabel": "Post new bulletin",
"titlePlaceholder": "title...",
"pasteAddressToAddContact": "Paste a cwtch address, invitation or key bundle here to add a new conversation",
"blocked": "Blocked",
"cycleCatsAndroid": "Click to cycle category.\nLong-press to reset.",
"cycleCatsDesktop": "Click to cycle category.\nRight-click to reset.",
"cycleMorphsAndroid": "Click to cycle morphs.\nLong-press to reset.",
"cycleMorphsDesktop": "Click to cycle morphs.\nRight-click to reset.",
"cycleColoursAndroid": "Click to cycle colours.\nLong-press to reset.",
"cycleColoursDesktop": "Click to cycle colours.\nRight-click to reset.",
"search": "Search...",
"invitationLabel": "Invitation",
"serverInfo": "Server Information",
"serverConnectivityConnected": "Server Connected",
"serverConnectivityDisconnected": "Server Disconnected",
"serverSynced": "Synced",
"serverNotSynced": "Out of Sync",
"viewServerInfo": "Server Info",
"saveBtn": "Save",
"inviteToGroupLabel": "Invite to group",
"inviteBtn": "Invite",
"deleteBtn": "Delete",
"update": "Update",
"searchList": "Search List",
"peerNotOnline": "Peer is Offline. Applications cannot be used right now.",
"addListItemBtn": "Add Item",
"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.",
"dmTooltip": "Click to DM",
"couldNotSendMsgError": "Could not send this message",
"acknowledgedLabel": "Acknowledged",
"pendingLabel": "Pending",
"peerBlockedMessage": "Peer is blocked",
"peerOfflineMessage": "Peer is offline, messages can't be delivered right now",
"copiedClipboardNotification": "Copied to clipboard",
"newGroupBtn": "Create new group",
"acceptGroupInviteLabel": "Do you want to accept the invitation to",
"acceptGroupBtn": "Accept",
"rejectGroupBtn": "Reject",
"chatBtn": "Chat",
"listsBtn": "Lists",
"bulletinsBtn": "Bulletins",
"puzzleGameBtn": "Puzzle Game",
"addressLabel": "Address",
"displayNameLabel": "Display Name",
"blockBtn": "Block Peer",
"savePeerHistory": "Save Peer History",
"savePeerHistoryDescription": "Determines whether or not to delete any history associated with the peer.",
"dontSavePeerHistory": "Delete Peer History",
"unblockBtn": "Unblock Peer",
"addProfileTitle": "Add new profile",
"editProfileTitle": "Edit Profile",
"profileName": "Display name",
"defaultProfileName": "Alice",
"newProfile": "New Profile",
"editProfile": "Edit Profille",
"radioUsePassword": "Password",
"radioNoPassword": "Unencrypted (No password)",
"noPasswordWarning": "Not using a password on this account means that all data stored locally will not be encrypted",
"yourDisplayName": "Your Display Name",
"currentPasswordLabel": "Current Password",
"password1Label": "Password",
"password2Label": "Reenter password",
"passwordErrorEmpty": "Password cannot be empty",
"createProfileBtn": "Create Profile",
"saveProfileBtn": "Save Profile",
"passwordErrorMatch": "Passwords do not match",
"passwordChangeError": "Error changing password: Supplied password rejected",
"deleteProfileBtn": "Delete Profile",
"deleteConfirmLabel": "Type DELETE to confirm",
"deleteProfileConfirmBtn": "Really Delete Profile",
"deleteConfirmText": "DELETE",
"addNewProfileBtn": "Add new profile",
"enterProfilePassword": "Enter a password to view your profiles",
"password": "Password",
"error0ProfilesLoadedForPassword": "0 profiles loaded with that password",
"yourProfiles": "Your Profiles",
"yourServers": "Your Servers",
"unlock": "Unlock",
"cwtchSettingsTitle": "Cwtch Settings",
"versionBuilddate": "Version: %1 Built on: %2",
"zoomLabel": "Interface zoom (mostly affects text and button sizes)",
"blockUnknownLabel": "Block Unknown Peers",
"settingLanguage": "Language",
"localeEn": "English",
"localeFr": "Frances",
"localePt": "Portuguesa",
"localeDe": "Deutsche",
"settingInterfaceZoom": "Zoom level",
"largeTextLabel": "Large",
"settingTheme": "Theme",
"themeLight": "Light",
"themeDark": "Dark",
"experimentsEnabled": "Enable Experiments",
"versionTor": "Version %1 with tor %2",
"version": "Version %1",
"builddate": "Built on: %2",
"defaultScalingText": "Default size text (scale factor:",
"smallTextLabel": "Small",
"loadingTor": "Loading tor...",
"viewGroupMembershipTooltip": "View Group Membership",
"networkStatusDisconnected": "Disconnected from the internet, check your connection",
"networkStatusAttemptingTor": "Attempting to connect to Tor network",
"networkStatusConnecting": "Connecting to network and peers...",
"networkStatusOnline": "Online",
"newConnectionPaneTitle": "New Connection",
"addListItem": "Add a New List Item",
"addNewItem": "Add a new item to the list",
"todoPlaceholder": "Todo...",
"localeEs": "Espanol",
"localeIt": "Italiana",
"enableGroups": "Enable Group Chat",
"enterCurrentPasswordForDelete": "Please enter current password to delete this profile.",
"conversationSettings": "Conversation Settings",
"invalidImportString": "Invalid import string",
"contactAlreadyExists": "Contact Already Exists",
"tooltipOpenSettings": "Open the settings pane",
"tooltipAddContact": "Add a new contact or conversation",
"titleManageContacts": "Conversations",
"tooltipUnlockProfiles": "Unlock encrypted profiles by entering their password.",
"titleManageProfiles": "Manage Cwtch Profiles",
"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.",
"descriptionExperimentsGroups": "The group experiment allows Cwtch to connect with untrusted server infrastructure to facilitate communication with more than one contact.",
"descriptionBlockUnknownConnections": "If turned on, this option will automatically close connections from Cwtch users that have not been added to your contact list.",
"successfullAddedContact": "Successfully added ",
"dateRightNow": "Right Now",
"dateMinutesAgo": "Minutes Ago",
"dateHoursAgo": "Hours Ago",
"dateDaysAgo": "Days Ago",
"dateWeeksAgo": "Weeks Ago",
"dateLastMonth": "Last Month",
"dateYesterday": "Yesterday",
"dateLastYear": "Last Year",
"dateYearsAgo": "X Years Ago (displayed next to a contact row to indicate time of last action)",
"dateNever": "Never",
"dateMonthsAgo": "Months Ago",
"titleManageServers": "Manage Servers",
"inviteToGroup": "You have been invited to join a group:",
"leaveGroup": "Leave This Conversation",
"reallyLeaveThisGroupPrompt": "Are you sure you want to leave this conversation? All messages and attributes will be deleted.",
"yesLeave": "Yes, Leave This Conversation",
"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",
"acceptGroupBtn": "Aceptar",
"acceptGroupInviteLabel": "¿Quieres aceptar la invitación a ",
"acknowledgedLabel": "Reconocido",
"addListItem": "Añadir un nuevo elemento a la lista",
"addListItemBtn": "Agregar artículo",
"addNewItem": "Añadir un nuevo elemento a la lista",
"addNewProfileBtn": "Agregar nuevo perfil",
"addPeer": "Agregar Contacto",
"addPeerTab": "Agregar Contacto",
"addProfileTitle": "Agregar nuevo perfil",
"addressLabel": "Dirección",
"blockBtn": "Bloquear contacto",
"blocked": "Bloqueado",
"blockUnknownLabel": "Bloquear conexiones desconocidas",
"builddate": "Basado en: %2",
"bulletinsBtn": "Boletines",
"chatBtn": "Chat",
"chatHistoryDefault": "",
"contactAlreadyExists": "",
"conversationSettings": "",
"copiedClipboardNotification": "Copiado al portapapeles",
"copiedToClipboardNotification": "Copiado al portapapeles",
"copyBtn": "Copiar",
"couldNotSendMsgError": "No se pudo enviar este mensaje",
"createGroup": "Crear perfil",
"createGroupBtn": "Crear",
"createGroupTab": "Crear un grupo",
"createGroupTitle": "Crear un grupo",
"createProfileBtn": "Crear perfil",
"currentPasswordLabel": "Contraseña actual",
"cwtchSettingsTitle": "Configuración de Cwtch",
"cycleCatsAndroid": "Click para cambiar categoría. Mantenga pulsado para reiniciar.",
"cycleCatsDesktop": "Click para cambiar categoría. Click derecho para reiniciar.",
"cycleColoursAndroid": "Click para cambiar colores. Mantenga pulsado para reiniciar.",
"cycleColoursDesktop": "Click para cambiar colores. Click derecho para reiniciar.",
"cycleMorphsAndroid": "Click para cambiar transformaciones. Mantenga pulsado para reiniciar.",
"cycleMorphsDesktop": "Click para cambiar transformaciones. Click derecho para reiniciar.",
"dateDaysAgo": "",
"dateHoursAgo": "",
"dateLastMonth": "",
"dateLastYear": "",
"dateMinutesAgo": "",
"dateMonthsAgo": "",
"dateNever": "",
"dateRightNow": "",
"dateWeeksAgo": "",
"dateYearsAgo": "",
"dateYesterday": "",
"defaultGroupName": "El Grupo Asombroso",
"defaultProfileName": "Alicia",
"defaultScalingText": "Tamaño predeterminado de texto (factor de escala:",
"deleteBtn": "Eliminar",
"deleteConfirmLabel": "Escribe ELIMINAR para confirmar",
"deleteConfirmText": "ELIMINAR",
"deleteProfileBtn": "Eliminar Perfil",
"deleteProfileConfirmBtn": "Confirmar eliminar perfil",
"descriptionBlockUnknownConnections": "",
"descriptionExperiments": "",
"descriptionExperimentsGroups": "",
"displayNameLabel": "Nombre de Usuario",
"dmTooltip": "Haz clic para enviar mensaje directo",
"dontSavePeerHistory": "Eliminar historial de contacto",
"editProfile": "Editar perfil",
"editProfileTitle": "Editar perfil",
"enableGroups": "",
"enterCurrentPasswordForDelete": "",
"enterProfilePassword": "Ingresa tu contraseña para ver tus perfiles",
"error0ProfilesLoadedForPassword": "0 perfiles cargados con esa contraseña",
"experimentsEnabled": "Experimentos habilitados",
"groupAddr": "Dirección",
"groupName": "Nombre del grupo",
"groupNameLabel": "Nombre del grupo",
"invalidImportString": "",
"invitation": "Invitación",
"invitationLabel": "Invitación",
"inviteBtn": "Invitar",
"inviteToGroup": "",
"inviteToGroupLabel": "Invitar al grupo",
"joinGroup": "Únete al grupo",
"joinGroupTab": "Únete a un grupo",
"largeTextLabel": "Grande",
"leaveGroup": "",
"listsBtn": "Listas",
"loadingTor": "Cargando tor...",
"localeDe": "Alemán",
"localeEn": "Inglés",
"localeEs": "Español",
"localeFr": "Francés",
"localeIt": "Italiano",
"localePt": "Portugués",
"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",
"networkStatusAttemptingTor": "Intentando conectarse a la red Tor",
"networkStatusConnecting": "Conectando a la red y a los contactos...",
"networkStatusDisconnected": "Sin conexión, comprueba tu conexión",
"networkStatusOnline": "En línea",
"newBulletinLabel": "Nuevo Boletín",
"newConnectionPaneTitle": "Nueva conexión",
"newGroupBtn": "Crear un nuevo grupo de chat",
"newPassword": "",
"newProfile": "Nuevo perfil",
"noPasswordWarning": "No usar una contraseña para esta cuenta significa que los datos almacenados localmente no serán encriptados",
"password": "Contraseña",
"password1Label": "Contraseña",
"password2Label": "Vuelve a ingresar tu contraseña",
"passwordChangeError": "Hubo un error cambiando tu contraseña: la contraseña ingresada fue rechazada",
"passwordErrorEmpty": "El campo de contraseña no puede estar vacío",
"passwordErrorMatch": "Las contraseñas no coinciden",
"pasteAddressToAddContact": "...pegar una dirección aquí para añadir contacto...",
"peerAddress": "Dirección",
"peerBlockedMessage": "Contacto bloqueado",
"peerName": "Nombre",
"peerNotOnline": "Este contacto no está en línea, la aplicación no puede ser usada en este momento",
"peerOfflineMessage": "Este contacto no está en línea, los mensajes no pueden ser entregados en este momento",
"pendingLabel": "Pendiente",
"postNewBulletinLabel": "Publicar nuevo boletín",
"profileName": "Nombre de Usuario",
"profileOnionLabel": "Envía esta dirección a los contactos con los que quieras conectarte",
"puzzleGameBtn": "Juego de rompecabezas",
"radioNoPassword": "Sin cifrado (sin contraseña)",
"radioUsePassword": "Contraseña",
"reallyLeaveThisGroupPrompt": "",
"rejectGroupBtn": "Rechazar",
"saveBtn": "Guardar",
"savePeerHistory": "Guardar el historial con contacto",
"savePeerHistoryDescription": "Determina si eliminar o no el historial asociado con el contacto.",
"saveProfileBtn": "Guardar perfil",
"search": "Búsqueda...",
"searchList": "Buscar en la lista",
"server": "Servidor",
"serverConnectivityConnected": "Servidor conectado",
"serverConnectivityDisconnected": "Servidor desconectado",
"serverInfo": "Información del servidor",
"serverLabel": "Servidor",
"serverNotSynced": "Fuera de sincronización con el servidor",
"serverSynced": "Sincronizado",
"settingInterfaceZoom": "Nivel de zoom",
"settingLanguage": "Idioma",
"settingTheme": "Tema",
"smallTextLabel": "Pequeño",
"successfullAddedContact": "",
"themeDark": "Oscuro",
"themeLight": "Claro",
"titleManageContacts": "",
"titleManageProfiles": "",
"titleManageServers": "",
"titlePlaceholder": "título...",
"todoPlaceholder": "Por hacer...",
"tooltipAddContact": "",
"tooltipOpenSettings": "",
"tooltipUnlockProfiles": "",
"unblockBtn": "Desbloquear contacto",
"unlock": "Desbloquear",
"update": "Actualizar",
"version": "Versión %1",
"versionBuilddate": "Versión: %1 Basado en %2",
"versionTor": "Versión %1 con tor %2",
"viewGroupMembershipTooltip": "Ver membresía del grupo",
"viewServerInfo": "Información del servidor",
"yesLeave": "",
"yourDisplayName": "Tu nombre de usuario",
"yourProfiles": "Tus perfiles",
"yourServers": "Tus servidores",
"zoomLabel": "Zoom de la interfaz (afecta principalmente el tamaño del texto y de los botones)"
"@@locale": "es",
"@@last_modified": "2021-06-15T02:08:49+02:00",
"createGroupTitle": "Crear un grupo",
"serverLabel": "Servidor",
"groupNameLabel": "Nombre del grupo",
"defaultGroupName": "El Grupo Asombroso",
"createGroupBtn": "Crear",
"profileOnionLabel": "Envía esta dirección a los contactos con los que quieras conectarte",
"copyBtn": "Copiar",
"copiedToClipboardNotification": "Copiado al portapapeles",
"addPeerTab": "Agregar Contacto",
"createGroupTab": "Crear un grupo",
"joinGroupTab": "Únete a un grupo",
"peerAddress": "Dirección",
"peerName": "Nombre",
"groupName": "Nombre del grupo",
"server": "Servidor",
"invitation": "Invitación",
"groupAddr": "Dirección",
"addPeer": "Agregar Contacto",
"createGroup": "Crear perfil",
"joinGroup": "Únete al grupo",
"newBulletinLabel": "Nuevo Boletín",
"postNewBulletinLabel": "Publicar nuevo boletín",
"titlePlaceholder": "título...",
"pasteAddressToAddContact": "...pegar una dirección aquí para añadir contacto...",
"blocked": "Bloqueado",
"cycleCatsAndroid": "Click para cambiar categoría. Mantenga pulsado para reiniciar.",
"cycleCatsDesktop": "Click para cambiar categoría. Click derecho para reiniciar.",
"cycleMorphsAndroid": "Click para cambiar transformaciones. Mantenga pulsado para reiniciar.",
"cycleMorphsDesktop": "Click para cambiar transformaciones. Click derecho para reiniciar.",
"cycleColoursAndroid": "Click para cambiar colores. Mantenga pulsado para reiniciar.",
"cycleColoursDesktop": "Click para cambiar colores. Click derecho para reiniciar.",
"search": "Búsqueda...",
"invitationLabel": "Invitación",
"serverInfo": "Información del servidor",
"serverConnectivityConnected": "Servidor conectado",
"serverConnectivityDisconnected": "Servidor desconectado",
"serverSynced": "Sincronizado",
"serverNotSynced": "Fuera de sincronización con el servidor",
"viewServerInfo": "Información del servidor",
"saveBtn": "Guardar",
"inviteToGroupLabel": "Invitar al grupo",
"inviteBtn": "Invitar",
"deleteBtn": "Eliminar",
"update": "Actualizar",
"searchList": "Buscar en la lista",
"peerNotOnline": "Este contacto no está en línea, la aplicación no puede ser usada en este momento",
"addListItemBtn": "Agregar artículo",
"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",
"dmTooltip": "Haz clic para enviar mensaje directo",
"couldNotSendMsgError": "No se pudo enviar este mensaje",
"acknowledgedLabel": "Reconocido",
"pendingLabel": "Pendiente",
"peerBlockedMessage": "Contacto bloqueado",
"peerOfflineMessage": "Este contacto no está en línea, los mensajes no pueden ser entregados en este momento",
"copiedClipboardNotification": "Copiado al portapapeles",
"newGroupBtn": "Crear un nuevo grupo de chat",
"acceptGroupInviteLabel": "¿Quieres aceptar la invitación a ",
"acceptGroupBtn": "Aceptar",
"rejectGroupBtn": "Rechazar",
"chatBtn": "Chat",
"listsBtn": "Listas",
"bulletinsBtn": "Boletines",
"puzzleGameBtn": "Juego de rompecabezas",
"addressLabel": "Dirección",
"displayNameLabel": "Nombre de Usuario",
"blockBtn": "Bloquear contacto",
"savePeerHistory": "Guardar el historial con contacto",
"savePeerHistoryDescription": "Determina si eliminar o no el historial asociado con el contacto.",
"dontSavePeerHistory": "Eliminar historial de contacto",
"unblockBtn": "Desbloquear contacto",
"addProfileTitle": "Agregar nuevo perfil",
"editProfileTitle": "Editar perfil",
"profileName": "Nombre de Usuario",
"defaultProfileName": "Alicia",
"newProfile": "Nuevo perfil",
"editProfile": "Editar perfil",
"radioUsePassword": "Contraseña",
"radioNoPassword": "Sin cifrado (sin contraseña)",
"noPasswordWarning": "No usar una contraseña para esta cuenta significa que los datos almacenados localmente no serán encriptados",
"yourDisplayName": "Tu nombre de usuario",
"currentPasswordLabel": "Contraseña actual",
"password1Label": "Contraseña",
"password2Label": "Vuelve a ingresar tu contraseña",
"passwordErrorEmpty": "El campo de contraseña no puede estar vacío",
"createProfileBtn": "Crear perfil",
"saveProfileBtn": "Guardar perfil",
"passwordErrorMatch": "Las contraseñas no coinciden",
"passwordChangeError": "Hubo un error cambiando tu contraseña: la contraseña ingresada fue rechazada",
"deleteProfileBtn": "Eliminar Perfil",
"deleteConfirmLabel": "Escribe ELIMINAR para confirmar",
"deleteProfileConfirmBtn": "Confirmar eliminar perfil",
"deleteConfirmText": "ELIMINAR",
"addNewProfileBtn": "Agregar nuevo perfil",
"enterProfilePassword": "Ingresa tu contraseña para ver tus perfiles",
"password": "Contraseña",
"error0ProfilesLoadedForPassword": "0 perfiles cargados con esa contraseña",
"yourProfiles": "Tus perfiles",
"yourServers": "Tus servidores",
"unlock": "Desbloquear",
"cwtchSettingsTitle": "Configuración de Cwtch",
"versionBuilddate": "Versión: %1 Basado en %2",
"zoomLabel": "Zoom de la interfaz (afecta principalmente el tamaño del texto y de los botones)",
"blockUnknownLabel": "Bloquear conexiones desconocidas",
"settingLanguage": "Idioma",
"localeEn": "Inglés",
"localeFr": "Francés",
"localePt": "Portugués",
"localeDe": "Alemán",
"settingInterfaceZoom": "Nivel de zoom",
"largeTextLabel": "Grande",
"settingTheme": "Tema",
"themeLight": "Claro",
"themeDark": "Oscuro",
"experimentsEnabled": "Experimentos habilitados",
"versionTor": "Versión %1 con tor %2",
"version": "Versión %1",
"builddate": "Basado en: %2",
"defaultScalingText": "Tamaño predeterminado de texto (factor de escala:",
"smallTextLabel": "Pequeño",
"loadingTor": "Cargando tor...",
"viewGroupMembershipTooltip": "Ver membresía del grupo",
"networkStatusDisconnected": "Sin conexión, comprueba tu conexión",
"networkStatusAttemptingTor": "Intentando conectarse a la red Tor",
"networkStatusConnecting": "Conectando a la red y a los contactos...",
"networkStatusOnline": "En línea",
"newConnectionPaneTitle": "Nueva conexión",
"addListItem": "Añadir un nuevo elemento a la lista",
"addNewItem": "Añadir un nuevo elemento a la lista",
"todoPlaceholder": "Por hacer...",
"localeEs": "Español",
"localeIt": "Italiano",
"enableGroups": "Enable Group Chat",
"enterCurrentPasswordForDelete": "Please enter current password to delete this profile.",
"conversationSettings": "Conversation Settings",
"invalidImportString": "Invalid import string",
"contactAlreadyExists": "Contact Already Exists",
"tooltipOpenSettings": "Open the settings pane",
"tooltipAddContact": "Add a new contact or conversation",
"titleManageContacts": "Conversations",
"tooltipUnlockProfiles": "Unlock encrypted profiles by entering their password.",
"titleManageProfiles": "Manage Cwtch Profiles",
"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.",
"descriptionExperimentsGroups": "The group experiment allows Cwtch to connect with untrusted server infrastructure to facilitate communication with more than one contact.",
"descriptionBlockUnknownConnections": "If turned on, this option will automatically close connections from Cwtch users that have not been added to your contact list.",
"successfullAddedContact": "Successfully added ",
"dateRightNow": "Right Now",
"dateMinutesAgo": "Minutes Ago",
"dateHoursAgo": "Hours Ago",
"dateDaysAgo": "Days Ago",
"dateWeeksAgo": "Weeks Ago",
"dateLastMonth": "Last Month",
"dateYesterday": "Yesterday",
"dateLastYear": "Last Year",
"dateYearsAgo": "X Years Ago (displayed next to a contact row to indicate time of last action)",
"dateNever": "Never",
"dateMonthsAgo": "Months Ago",
"titleManageServers": "Manage Servers",
"inviteToGroup": "You have been invited to join a group:",
"leaveGroup": "Leave This Conversation",
"reallyLeaveThisGroupPrompt": "Are you sure you want to leave this conversation? All messages and attributes will be deleted.",
"yesLeave": "Yes, Leave This Conversation",
"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",
"acceptGroupBtn": "Accepter",
"acceptGroupInviteLabel": "Voulez-vous accepter l'invitation au groupe",
"acknowledgedLabel": "Confirmé",
"addListItem": "Ajouter un nouvel élément",
"addListItemBtn": "",
"addNewItem": "Ajouter un nouvel élément à la liste",
"addNewProfileBtn": "",
"addPeer": "",
"addPeerTab": "",
"addProfileTitle": "",
"addressLabel": "Adresse",
"blockBtn": "",
"blocked": "",
"blockUnknownLabel": "",
"builddate": "",
"bulletinsBtn": "Bulletins",
"chatBtn": "Discuter",
"chatHistoryDefault": "",
"contactAlreadyExists": "",
"conversationSettings": "",
"copiedClipboardNotification": "Copié dans le presse-papier",
"copiedToClipboardNotification": "Copié dans le presse-papier",
"copyBtn": "Copier",
"couldNotSendMsgError": "Impossible d'envoyer ce message",
"createGroup": "",
"createGroupBtn": "Créer",
"createGroupTab": "",
"createGroupTitle": "Créer un groupe",
"createProfileBtn": "",
"currentPasswordLabel": "",
"cwtchSettingsTitle": "Préférences Cwtch",
"cycleCatsAndroid": "",
"cycleCatsDesktop": "",
"cycleColoursAndroid": "",
"cycleColoursDesktop": "",
"cycleMorphsAndroid": "",
"cycleMorphsDesktop": "",
"dateDaysAgo": "",
"dateHoursAgo": "",
"dateLastMonth": "",
"dateLastYear": "",
"dateMinutesAgo": "",
"dateMonthsAgo": "",
"dateNever": "",
"dateRightNow": "",
"dateWeeksAgo": "",
"dateYearsAgo": "",
"dateYesterday": "",
"defaultGroupName": "Un super groupe",
"defaultProfileName": "",
"defaultScalingText": "Taille par défaut du texte (échelle:",
"deleteBtn": "Effacer",
"deleteConfirmLabel": "",
"deleteConfirmText": "",
"deleteProfileBtn": "",
"deleteProfileConfirmBtn": "",
"descriptionBlockUnknownConnections": "",
"descriptionExperiments": "",
"descriptionExperimentsGroups": "",
"displayNameLabel": "Pseudo",
"dmTooltip": "Envoyer un message privé",
"dontSavePeerHistory": "",
"editProfile": "",
"editProfileTitle": "",
"enableGroups": "",
"enterCurrentPasswordForDelete": "",
"enterProfilePassword": "",
"error0ProfilesLoadedForPassword": "",
"experimentsEnabled": "",
"groupAddr": "",
"groupName": "",
"groupNameLabel": "Nom du groupe",
"invalidImportString": "",
"invitation": "",
"invitationLabel": "Invitation",
"inviteBtn": "Invitation",
"inviteToGroup": "",
"inviteToGroupLabel": "Inviter quelqu'un",
"joinGroup": "",
"joinGroupTab": "",
"largeTextLabel": "Large",
"leaveGroup": "",
"listsBtn": "Listes",
"loadingTor": "",
"localeDe": "",
"localeEn": "",
"localeEs": "",
"localeFr": "",
"localeIt": "",
"localePt": "",
"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.",
"networkStatusAttemptingTor": "",
"networkStatusConnecting": "",
"networkStatusDisconnected": "",
"networkStatusOnline": "",
"newBulletinLabel": "Nouveau bulletin",
"newConnectionPaneTitle": "",
"newGroupBtn": "Créer un nouveau groupe",
"newPassword": "",
"newProfile": "",
"noPasswordWarning": "",
"password": "",
"password1Label": "",
"password2Label": "",
"passwordChangeError": "",
"passwordErrorEmpty": "",
"passwordErrorMatch": "",
"pasteAddressToAddContact": "... coller une adresse ici pour ajouter un contact...",
"peerAddress": "",
"peerBlockedMessage": "",
"peerName": "",
"peerNotOnline": "",
"peerOfflineMessage": "",
"pendingLabel": "En attente",
"postNewBulletinLabel": "Envoyer un nouveau bulletin",
"profileName": "",
"profileOnionLabel": "",
"puzzleGameBtn": "Puzzle",
"radioNoPassword": "",
"radioUsePassword": "",
"reallyLeaveThisGroupPrompt": "",
"rejectGroupBtn": "Refuser",
"saveBtn": "Sauvegarder",
"savePeerHistory": "",
"savePeerHistoryDescription": "",
"saveProfileBtn": "",
"search": "",
"searchList": "",
"server": "",
"serverConnectivityConnected": "",
"serverConnectivityDisconnected": "",
"serverInfo": "",
"serverLabel": "Serveur",
"serverNotSynced": "",
"serverSynced": "",
"settingInterfaceZoom": "",
"settingLanguage": "",
"settingTheme": "",
"smallTextLabel": "Petit",
"successfullAddedContact": "",
"themeDark": "",
"themeLight": "",
"titleManageContacts": "",
"titleManageProfiles": "",
"titleManageServers": "",
"titlePlaceholder": "titre...",
"todoPlaceholder": "A faire...",
"tooltipAddContact": "",
"tooltipOpenSettings": "",
"tooltipUnlockProfiles": "",
"unblockBtn": "",
"unlock": "",
"update": "",
"version": "",
"versionBuilddate": "",
"versionTor": "",
"viewGroupMembershipTooltip": "",
"viewServerInfo": "",
"yesLeave": "",
"yourDisplayName": "",
"yourProfiles": "",
"yourServers": "",
"zoomLabel": "Interface zoom (essentiellement la taille du texte et des composants de l'interface)"
"@@locale": "fr",
"@@last_modified": "2021-06-15T02:08:49+02:00",
"createGroupTitle": "Créer un groupe",
"serverLabel": "Serveur",
"groupNameLabel": "Nom du groupe",
"defaultGroupName": "Un super groupe",
"createGroupBtn": "Créer",
"profileOnionLabel": "Send this address to peers you want to connect with",
"copyBtn": "Copier",
"copiedToClipboardNotification": "Copié dans le presse-papier",
"addPeerTab": "Add a peer",
"createGroupTab": "Create a group",
"joinGroupTab": "Join a group",
"peerAddress": "Address",
"peerName": "Name",
"groupName": "Group name",
"server": "Server",
"invitation": "Invitation",
"groupAddr": "Address",
"addPeer": "Add Peer",
"createGroup": "Create group",
"joinGroup": "Join group",
"newBulletinLabel": "Nouveau bulletin",
"postNewBulletinLabel": "Envoyer un nouveau bulletin",
"titlePlaceholder": "titre...",
"pasteAddressToAddContact": "... coller une adresse ici pour ajouter un contact...",
"blocked": "Blocked",
"cycleCatsAndroid": "Click to cycle category.\nLong-press to reset.",
"cycleCatsDesktop": "Click to cycle category.\nRight-click to reset.",
"cycleMorphsAndroid": "Click to cycle morphs.\nLong-press to reset.",
"cycleMorphsDesktop": "Click to cycle morphs.\nRight-click to reset.",
"cycleColoursAndroid": "Click to cycle colours.\nLong-press to reset.",
"cycleColoursDesktop": "Click to cycle colours.\nRight-click to reset.",
"search": "Search...",
"invitationLabel": "Invitation",
"serverInfo": "Server Information",
"serverConnectivityConnected": "Server Connected",
"serverConnectivityDisconnected": "Server Disconnected",
"serverSynced": "Synced",
"serverNotSynced": "Out of Sync",
"viewServerInfo": "Server Info",
"saveBtn": "Sauvegarder",
"inviteToGroupLabel": "Inviter quelqu'un",
"inviteBtn": "Invitation",
"deleteBtn": "Effacer",
"update": "Update",
"searchList": "Search List",
"peerNotOnline": "Peer is Offline. Applications cannot be used right now.",
"addListItemBtn": "Add Item",
"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.",
"dmTooltip": "Envoyer un message privé",
"couldNotSendMsgError": "Impossible d'envoyer ce message",
"acknowledgedLabel": "Confirmé",
"pendingLabel": "En attente",
"peerBlockedMessage": "Peer is blocked",
"peerOfflineMessage": "Peer is offline, messages can't be delivered right now",
"copiedClipboardNotification": "Copié dans le presse-papier",
"newGroupBtn": "Créer un nouveau groupe",
"acceptGroupInviteLabel": "Voulez-vous accepter l'invitation au groupe",
"acceptGroupBtn": "Accepter",
"rejectGroupBtn": "Refuser",
"chatBtn": "Discuter",
"listsBtn": "Listes",
"bulletinsBtn": "Bulletins",
"puzzleGameBtn": "Puzzle",
"addressLabel": "Adresse",
"displayNameLabel": "Pseudo",
"blockBtn": "Block Peer",
"savePeerHistory": "Save Peer History",
"savePeerHistoryDescription": "Determines whether or not to delete any history associated with the peer.",
"dontSavePeerHistory": "Delete Peer History",
"unblockBtn": "Unblock Peer",
"addProfileTitle": "Add new profile",
"editProfileTitle": "Edit Profile",
"profileName": "Display name",
"defaultProfileName": "Alice",
"newProfile": "New Profile",
"editProfile": "Edit Profille",
"radioUsePassword": "Password",
"radioNoPassword": "Unencrypted (No password)",
"noPasswordWarning": "Not using a password on this account means that all data stored locally will not be encrypted",
"yourDisplayName": "Your Display Name",
"currentPasswordLabel": "Current Password",
"password1Label": "Password",
"password2Label": "Reenter password",
"passwordErrorEmpty": "Password cannot be empty",
"createProfileBtn": "Create Profile",
"saveProfileBtn": "Save Profile",
"passwordErrorMatch": "Passwords do not match",
"passwordChangeError": "Error changing password: Supplied password rejected",
"deleteProfileBtn": "Delete Profile",
"deleteConfirmLabel": "Type DELETE to confirm",
"deleteProfileConfirmBtn": "Really Delete Profile",
"deleteConfirmText": "DELETE",
"addNewProfileBtn": "Add new profile",
"enterProfilePassword": "Enter a password to view your profiles",
"password": "Password",
"error0ProfilesLoadedForPassword": "0 profiles loaded with that password",
"yourProfiles": "Your Profiles",
"yourServers": "Your Servers",
"unlock": "Unlock",
"cwtchSettingsTitle": "Préférences Cwtch",
"versionBuilddate": "Version: %1 Built on: %2",
"zoomLabel": "Interface zoom (essentiellement la taille du texte et des composants de l'interface)",
"blockUnknownLabel": "Block Unknown Peers",
"settingLanguage": "Language",
"localeEn": "English",
"localeFr": "Frances",
"localePt": "Portuguesa",
"localeDe": "Deutsche",
"settingInterfaceZoom": "Zoom level",
"largeTextLabel": "Large",
"settingTheme": "Theme",
"themeLight": "Light",
"themeDark": "Dark",
"experimentsEnabled": "Enable Experiments",
"versionTor": "Version %1 with tor %2",
"version": "Version %1",
"builddate": "Built on: %2",
"defaultScalingText": "Taille par défaut du texte (échelle:",
"smallTextLabel": "Petit",
"loadingTor": "Loading tor...",
"viewGroupMembershipTooltip": "View Group Membership",
"networkStatusDisconnected": "Disconnected from the internet, check your connection",
"networkStatusAttemptingTor": "Attempting to connect to Tor network",
"networkStatusConnecting": "Connecting to network and peers...",
"networkStatusOnline": "Online",
"newConnectionPaneTitle": "New Connection",
"addListItem": "Ajouter un nouvel élément",
"addNewItem": "Ajouter un nouvel élément à la liste",
"todoPlaceholder": "A faire...",
"localeEs": "Espanol",
"localeIt": "Italiana",
"enableGroups": "Enable Group Chat",
"enterCurrentPasswordForDelete": "Please enter current password to delete this profile.",
"conversationSettings": "Conversation Settings",
"invalidImportString": "Invalid import string",
"contactAlreadyExists": "Contact Already Exists",
"tooltipOpenSettings": "Open the settings pane",
"tooltipAddContact": "Add a new contact or conversation",
"titleManageContacts": "Conversations",
"tooltipUnlockProfiles": "Unlock encrypted profiles by entering their password.",
"titleManageProfiles": "Manage Cwtch Profiles",
"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.",
"descriptionExperimentsGroups": "The group experiment allows Cwtch to connect with untrusted server infrastructure to facilitate communication with more than one contact.",
"descriptionBlockUnknownConnections": "If turned on, this option will automatically close connections from Cwtch users that have not been added to your contact list.",
"successfullAddedContact": "Successfully added ",
"dateRightNow": "Right Now",
"dateMinutesAgo": "Minutes Ago",
"dateHoursAgo": "Hours Ago",
"dateDaysAgo": "Days Ago",
"dateWeeksAgo": "Weeks Ago",
"dateLastMonth": "Last Month",
"dateYesterday": "Yesterday",
"dateLastYear": "Last Year",
"dateYearsAgo": "X Years Ago (displayed next to a contact row to indicate time of last action)",
"dateNever": "Never",
"dateMonthsAgo": "Months Ago",
"titleManageServers": "Manage Servers",
"inviteToGroup": "You have been invited to join a group:",
"leaveGroup": "Leave This Conversation",
"reallyLeaveThisGroupPrompt": "Are you sure you want to leave this conversation? All messages and attributes will be deleted.",
"yesLeave": "Yes, Leave This Conversation",
"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",
"acceptGroupBtn": "Accetta",
"acceptGroupInviteLabel": "Vuoi accettare l'invito a",
"acknowledgedLabel": "Riconosciuto",
"addListItem": "Aggiungi un nuovo elemento alla lista",
"addListItemBtn": "Aggiungi elemento",
"addNewItem": "Aggiungi un nuovo elemento alla lista",
"addNewProfileBtn": "Aggiungi nuovo profilo",
"addPeer": "Aggiungi peer",
"addPeerTab": "Aggiungi un peer",
"addProfileTitle": "Aggiungi nuovo profilo",
"addressLabel": "Indirizzo",
"blockBtn": "Blocca il peer",
"blocked": "Bloccato",
"blockUnknownLabel": "Blocca peer sconosciuti",
"builddate": "Costruito il: %2",
"bulletinsBtn": "Bollettini",
"chatBtn": "Chat",
"chatHistoryDefault": "",
"contactAlreadyExists": "",
"conversationSettings": "",
"copiedClipboardNotification": "Copiato negli Appunti",
"copiedToClipboardNotification": "Copiato negli Appunti",
"copyBtn": "Copia",
"couldNotSendMsgError": "Impossibile inviare questo messaggio",
"createGroup": "Crea un gruppo",
"createGroupBtn": "Crea",
"createGroupTab": "Crea un gruppo",
"createGroupTitle": "Crea un gruppo",
"createProfileBtn": "Crea un profilo",
"currentPasswordLabel": "Password corrente",
"cwtchSettingsTitle": "Impostazioni di Cwtch",
"cycleCatsAndroid": "Fare clic per scorrere le categorie.\\nPressione lunga per resettare.",
"cycleCatsDesktop": "Fare clic per scorrere le categorie.\\nCliccare con il tasto destro per resettare.",
"cycleColoursAndroid": "Fare clic per scorrere i colori.\\nPressione lunga per resettare.",
"cycleColoursDesktop": "Fare clic per scorrere i colori.\\nCliccare con il tasto destro per resettare.",
"cycleMorphsAndroid": "Fare clic per scorrere i morph.\\nPressione lunga per resettare.",
"cycleMorphsDesktop": "Fare clic per scorrere i morph.\\nCliccare con il tasto destro per resettare.",
"dateDaysAgo": "",
"dateHoursAgo": "",
"dateLastMonth": "",
"dateLastYear": "",
"dateMinutesAgo": "",
"dateMonthsAgo": "",
"dateNever": "",
"dateRightNow": "",
"dateWeeksAgo": "",
"dateYearsAgo": "",
"dateYesterday": "",
"defaultGroupName": "Gruppo fantastico",
"defaultProfileName": "Alice",
"defaultScalingText": "Testo di dimensioni predefinite (fattore di scala:",
"deleteBtn": "Elimina",
"deleteConfirmLabel": "Digita ELIMINA per confermare",
"deleteConfirmText": "ELIMINA",
"deleteProfileBtn": "Elimina profilo",
"deleteProfileConfirmBtn": "Elimina realmente il profilo",
"descriptionBlockUnknownConnections": "",
"descriptionExperiments": "",
"descriptionExperimentsGroups": "",
"displayNameLabel": "Nome visualizzato",
"dmTooltip": "Clicca per inviare un Messagio Diretto",
"dontSavePeerHistory": "Elimina cronologia dei peer",
"editProfile": "Modifica profilo",
"editProfileTitle": "Modifica profilo",
"enableGroups": "",
"enterCurrentPasswordForDelete": "",
"enterProfilePassword": "Inserisci una password per visualizzare i tuoi profili",
"error0ProfilesLoadedForPassword": "0 profili caricati con quella password",
"experimentsEnabled": "Esperimenti abilitati",
"groupAddr": "Indirizzo",
"groupName": "Nome del gruppo",
"groupNameLabel": "Nome del gruppo",
"invalidImportString": "",
"invitation": "Invito",
"invitationLabel": "Invito",
"inviteBtn": "Invitare",
"inviteToGroup": "",
"inviteToGroupLabel": "Invitare nel gruppo",
"joinGroup": "Unisciti al gruppo",
"joinGroupTab": "Unisciti a un gruppo",
"largeTextLabel": "Grande",
"leaveGroup": "",
"listsBtn": "Liste",
"loadingTor": "Caricamento di tor...",
"localeDe": "Tedesco",
"localeEn": "Inglese",
"localeEs": "Spagnolo",
"localeFr": "Francese",
"localeIt": "Italiano",
"localePt": "Portoghese",
"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.",
"networkStatusAttemptingTor": "Tentativo di connessione alla rete Tor",
"networkStatusConnecting": "Connessione alla rete e ai peer ...",
"networkStatusDisconnected": "Disconnesso da Internet, controlla la tua connessione",
"networkStatusOnline": "Online",
"newBulletinLabel": "Nuovo bollettino",
"newConnectionPaneTitle": "Nuova connessione",
"newGroupBtn": "Crea un nuovo gruppo",
"newPassword": "",
"newProfile": "Nuovo profilo",
"noPasswordWarning": "Non utilizzare una password su questo account significa che tutti i dati archiviati localmente non verranno criptati",
"password": "Password",
"password1Label": "Password",
"password2Label": "Reinserire la password",
"passwordChangeError": "Errore durante la modifica della password: password fornita rifiutata",
"passwordErrorEmpty": "La password non può essere vuota",
"passwordErrorMatch": "Le password non corrispondono",
"pasteAddressToAddContact": "... incolla qui un indirizzo per aggiungere un contatto...",
"peerAddress": "Indirizzo",
"peerBlockedMessage": "Il peer è bloccato",
"peerName": "Nome",
"peerNotOnline": "Il peer è offline. Le applicazioni non possono essere utilizzate in questo momento.",
"peerOfflineMessage": "Il peer è offline, i messaggi non possono essere recapitati in questo momento",
"pendingLabel": "In corso",
"postNewBulletinLabel": "Pubblica un nuovo bollettino",
"profileName": "Nome visualizzato",
"profileOnionLabel": "Inviare questo indirizzo ai peer con cui si desidera connettersi",
"puzzleGameBtn": "Gioco di puzzle",
"radioNoPassword": "Non criptato (senza password)",
"radioUsePassword": "Password",
"reallyLeaveThisGroupPrompt": "",
"rejectGroupBtn": "Rifiuta",
"saveBtn": "Salva",
"savePeerHistory": "Salva cronologia peer",
"savePeerHistoryDescription": "Determina se eliminare o meno ogni cronologia eventualmente associata al peer.",
"saveProfileBtn": "Salva il profilo",
"search": "Ricerca...",
"searchList": "Cerca nella lista",
"server": "Server",
"serverConnectivityConnected": "Server connesso",
"serverConnectivityDisconnected": "Server disconnesso",
"serverInfo": "Informazioni sul server",
"serverLabel": "Server",
"serverNotSynced": "Non sincronizzato",
"serverSynced": "Sincronizzato",
"settingInterfaceZoom": "Livello di zoom",
"settingLanguage": "Lingua",
"settingTheme": "Tema",
"smallTextLabel": "Piccolo",
"successfullAddedContact": "",
"themeDark": "Scuro",
"themeLight": "Chiaro",
"titleManageContacts": "",
"titleManageProfiles": "",
"titleManageServers": "",
"titlePlaceholder": "titolo...",
"todoPlaceholder": "Da fare...",
"tooltipAddContact": "",
"tooltipOpenSettings": "",
"tooltipUnlockProfiles": "",
"unblockBtn": "Sblocca il peer",
"unlock": "Sblocca",
"update": "Aggiornamento",
"version": "Versione %1",
"versionBuilddate": "Versione: %1 Costruito il: %2",
"versionTor": "Versione %1 con tor %2",
"viewGroupMembershipTooltip": "Visualizza i membri del gruppo",
"viewServerInfo": "Informazioni sul server",
"yesLeave": "",
"yourDisplayName": "Il tuo nome visualizzato",
"yourProfiles": "I tuoi profili",
"yourServers": "I tuoi server",
"zoomLabel": "Zoom dell'interfaccia (influisce principalmente sulle dimensioni del testo e dei pulsanti)"
"@@locale": "it",
"@@last_modified": "2021-06-15T02:08:49+02:00",
"createGroupTitle": "Crea un gruppo",
"serverLabel": "Server",
"groupNameLabel": "Nome del gruppo",
"defaultGroupName": "Gruppo fantastico",
"createGroupBtn": "Crea",
"profileOnionLabel": "Inviare questo indirizzo ai peer con cui si desidera connettersi",
"copyBtn": "Copia",
"copiedToClipboardNotification": "Copiato negli appunti",
"addPeerTab": "Aggiungi un peer",
"createGroupTab": "Crea un gruppo",
"joinGroupTab": "Unisciti a un gruppo",
"peerAddress": "Indirizzo",
"peerName": "Nome",
"groupName": "Nome del gruppo",
"server": "Server",
"invitation": "Invito",
"groupAddr": "Indirizzo",
"addPeer": "Aggiungi peer",
"createGroup": "Crea un gruppo",
"joinGroup": "Unisciti al gruppo",
"newBulletinLabel": "Nuovo bollettino",
"postNewBulletinLabel": "Pubblica un nuovo bollettino",
"titlePlaceholder": "titolo...",
"pasteAddressToAddContact": "... incolla qui un indirizzo per aggiungere un contatto...",
"blocked": "Bloccato",
"cycleCatsAndroid": "Fare clic per scorrere le categorie.\nPressione lunga per resettare.",
"cycleCatsDesktop": "Fare clic per scorrere le categorie.\nCliccare con il tasto destro per resettare.",
"cycleMorphsAndroid": "Fare clic per scorrere i morph.\nPressione lunga per resettare.",
"cycleMorphsDesktop": "Fare clic per scorrere i morph.\nCliccare con il tasto destro per resettare.",
"cycleColoursAndroid": "Fare clic per scorrere i colori.\nPressione lunga per resettare.",
"cycleColoursDesktop": "Fare clic per scorrere i colori.\nCliccare con il tasto destro per resettare.",
"search": "Ricerca...",
"invitationLabel": "Invito",
"serverInfo": "Informazioni sul server",
"serverConnectivityConnected": "Server connesso",
"serverConnectivityDisconnected": "Server disconnesso",
"serverSynced": "Sincronizzato",
"serverNotSynced": "Non sincronizzato",
"viewServerInfo": "Informazioni sul server",
"saveBtn": "Salva",
"inviteToGroupLabel": "Invitare nel gruppo",
"inviteBtn": "Invitare",
"deleteBtn": "Elimina",
"update": "Aggiornamento",
"searchList": "Cerca nella lista",
"peerNotOnline": "Il peer è offline. Le applicazioni non possono essere utilizzate in questo momento.",
"addListItemBtn": "Aggiungi elemento",
"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.",
"dmTooltip": "Clicca per inviare un Messagio Diretto",
"couldNotSendMsgError": "Impossibile inviare questo messaggio",
"acknowledgedLabel": "Riconosciuto",
"pendingLabel": "In corso",
"peerBlockedMessage": "Il peer è bloccato",
"peerOfflineMessage": "Il peer è offline, i messaggi non possono essere recapitati in questo momento",
"copiedClipboardNotification": "Copiato negli Appunti",
"newGroupBtn": "Crea un nuovo gruppo",
"acceptGroupInviteLabel": "Vuoi accettare l'invito a",
"acceptGroupBtn": "Accetta",
"rejectGroupBtn": "Rifiuta",
"chatBtn": "Chat",
"listsBtn": "Liste",
"bulletinsBtn": "Bollettini",
"puzzleGameBtn": "Gioco di puzzle",
"addressLabel": "Indirizzo",
"displayNameLabel": "Nome visualizzato",
"blockBtn": "Blocca il peer",
"savePeerHistory": "Salva cronologia peer",
"savePeerHistoryDescription": "Determina se eliminare o meno ogni cronologia eventualmente associata al peer.",
"dontSavePeerHistory": "Elimina cronologia dei peer",
"unblockBtn": "Sblocca il peer",
"addProfileTitle": "Aggiungi nuovo profilo",
"editProfileTitle": "Modifica profilo",
"profileName": "Nome visualizzato",
"defaultProfileName": "Alice",
"newProfile": "Nuovo profilo",
"editProfile": "Modifica profilo",
"radioUsePassword": "Password",
"radioNoPassword": "Non criptato (senza password)",
"noPasswordWarning": "Non utilizzare una password su questo account significa che tutti i dati archiviati localmente non verranno criptati",
"yourDisplayName": "Il tuo nome visualizzato",
"currentPasswordLabel": "Password corrente",
"password1Label": "Password",
"password2Label": "Reinserire la password",
"passwordErrorEmpty": "La password non può essere vuota",
"createProfileBtn": "Crea un profilo",
"saveProfileBtn": "Salva il profilo",
"passwordErrorMatch": "Le password non corrispondono",
"passwordChangeError": "Errore durante la modifica della password: password fornita rifiutata",
"deleteProfileBtn": "Elimina profilo",
"deleteConfirmLabel": "Digita ELIMINA per confermare",
"deleteProfileConfirmBtn": "Elimina realmente il profilo",
"deleteConfirmText": "ELIMINA",
"addNewProfileBtn": "Aggiungi nuovo profilo",
"enterProfilePassword": "Inserisci una password per visualizzare i tuoi profili",
"password": "Password",
"error0ProfilesLoadedForPassword": "0 profili caricati con quella password",
"yourProfiles": "I tuoi profili",
"yourServers": "I tuoi server",
"unlock": "Sblocca",
"cwtchSettingsTitle": "Impostazioni di Cwtch",
"versionBuilddate": "Versione: %1 Costruito il: %2",
"zoomLabel": "Zoom dell'interfaccia (influisce principalmente sulle dimensioni del testo e dei pulsanti)",
"blockUnknownLabel": "Blocca peer sconosciuti",
"settingLanguage": "Lingua",
"localeEn": "Inglese",
"localeFr": "Francese",
"localePt": "Portoghese",
"localeDe": "Tedesco",
"settingInterfaceZoom": "Livello di zoom",
"largeTextLabel": "Grande",
"settingTheme": "Tema",
"themeLight": "Chiaro",
"themeDark": "Scuro",
"experimentsEnabled": "Esperimenti abilitati",
"versionTor": "Versione %1 con tor %2",
"version": "Versione %1",
"builddate": "Costruito il: %2",
"defaultScalingText": "Testo di dimensioni predefinite (fattore di scala:",
"smallTextLabel": "Piccolo",
"loadingTor": "Caricamento di tor...",
"viewGroupMembershipTooltip": "Visualizza i membri del gruppo",
"networkStatusDisconnected": "Disconnesso da Internet, controlla la tua connessione",
"networkStatusAttemptingTor": "Tentativo di connessione alla rete Tor",
"networkStatusConnecting": "Connessione alla rete e ai peer ...",
"networkStatusOnline": "Online",
"newConnectionPaneTitle": "Nuova connessione",
"addListItem": "Aggiungi un nuovo elemento alla lista",
"addNewItem": "Aggiungi un nuovo elemento alla lista",
"todoPlaceholder": "Da fare...",
"localeEs": "Spagnolo",
"localeIt": "Italiano",
"enableGroups": "Enable Group Chat",
"enterCurrentPasswordForDelete": "Please enter current password to delete this profile.",
"conversationSettings": "Conversation Settings",
"invalidImportString": "Invalid import string",
"contactAlreadyExists": "Contact Already Exists",
"tooltipOpenSettings": "Open the settings pane",
"tooltipAddContact": "Add a new contact or conversation",
"titleManageContacts": "Conversations",
"tooltipUnlockProfiles": "Unlock encrypted profiles by entering their password.",
"titleManageProfiles": "Manage Cwtch Profiles",
"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.",
"descriptionExperimentsGroups": "The group experiment allows Cwtch to connect with untrusted server infrastructure to facilitate communication with more than one contact.",
"descriptionBlockUnknownConnections": "If turned on, this option will automatically close connections from Cwtch users that have not been added to your contact list.",
"successfullAddedContact": "Successfully added ",
"dateRightNow": "Right Now",
"dateMinutesAgo": "Minutes Ago",
"dateHoursAgo": "Hours Ago",
"dateDaysAgo": "Days Ago",
"dateWeeksAgo": "Weeks Ago",
"dateLastMonth": "Last Month",
"dateYesterday": "Yesterday",
"dateLastYear": "Last Year",
"dateYearsAgo": "X Years Ago (displayed next to a contact row to indicate time of last action)",
"dateNever": "Never",
"dateMonthsAgo": "Months Ago",
"titleManageServers": "Manage Servers",
"inviteToGroup": "You have been invited to join a group:",
"leaveGroup": "Leave This Conversation",
"reallyLeaveThisGroupPrompt": "Are you sure you want to leave this conversation? All messages and attributes will be deleted.",
"yesLeave": "Yes, Leave This Conversation",
"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",
"acceptGroupBtn": "Aceitar",
"acceptGroupInviteLabel": "Você quer aceitar o convite para",
"acknowledgedLabel": "Confirmada",
"addListItem": "Adicionar Item à Lista",
"addListItemBtn": "",
"addNewItem": "Adicionar novo item à lista",
"addNewProfileBtn": "",
"addPeer": "",
"addPeerTab": "",
"addProfileTitle": "",
"addressLabel": "Endereço",
"blockBtn": "",
"blocked": "",
"blockUnknownLabel": "",
"builddate": "",
"bulletinsBtn": "Boletins",
"chatBtn": "Chat",
"chatHistoryDefault": "",
"contactAlreadyExists": "",
"conversationSettings": "",
"copiedClipboardNotification": "Copiado",
"copiedToClipboardNotification": "Copiado",
"copyBtn": "Copiar",
"couldNotSendMsgError": "Não deu para enviar esta mensagem",
"createGroup": "",
"createGroupBtn": "Criar",
"createGroupTab": "",
"createGroupTitle": "Criar Grupo",
"createProfileBtn": "",
"currentPasswordLabel": "",
"cwtchSettingsTitle": "Configurações do Cwtch",
"cycleCatsAndroid": "",
"cycleCatsDesktop": "",
"cycleColoursAndroid": "",
"cycleColoursDesktop": "",
"cycleMorphsAndroid": "",
"cycleMorphsDesktop": "",
"dateDaysAgo": "",
"dateHoursAgo": "",
"dateLastMonth": "",
"dateLastYear": "",
"dateMinutesAgo": "",
"dateMonthsAgo": "",
"dateNever": "",
"dateRightNow": "",
"dateWeeksAgo": "",
"dateYearsAgo": "",
"dateYesterday": "",
"defaultGroupName": "Grupo incrível",
"defaultProfileName": "",
"defaultScalingText": "Texto tamanho padrão (fator de escala: ",
"deleteBtn": "Deletar",
"deleteConfirmLabel": "",
"deleteConfirmText": "",
"deleteProfileBtn": "",
"deleteProfileConfirmBtn": "",
"descriptionBlockUnknownConnections": "",
"descriptionExperiments": "",
"descriptionExperimentsGroups": "",
"displayNameLabel": "Nome de Exibição",
"dmTooltip": "Clique para DM",
"dontSavePeerHistory": "",
"editProfile": "",
"editProfileTitle": "",
"enableGroups": "",
"enterCurrentPasswordForDelete": "",
"enterProfilePassword": "",
"error0ProfilesLoadedForPassword": "",
"experimentsEnabled": "",
"groupAddr": "",
"groupName": "",
"groupNameLabel": "Nome do Grupo",
"invalidImportString": "",
"invitation": "",
"invitationLabel": "Convite",
"inviteBtn": "Convidar",
"inviteToGroup": "",
"inviteToGroupLabel": "Convidar ao grupo",
"joinGroup": "",
"joinGroupTab": "",
"largeTextLabel": "Grande",
"leaveGroup": "",
"listsBtn": "Listas",
"loadingTor": "",
"localeDe": "",
"localeEn": "",
"localeEs": "",
"localeFr": "",
"localeIt": "",
"localePt": "",
"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.",
"networkStatusAttemptingTor": "",
"networkStatusConnecting": "",
"networkStatusDisconnected": "",
"networkStatusOnline": "",
"newBulletinLabel": "Novo Boletim",
"newConnectionPaneTitle": "",
"newGroupBtn": "Criar novo grupo",
"newPassword": "",
"newProfile": "",
"noPasswordWarning": "",
"password": "",
"password1Label": "",
"password2Label": "",
"passwordChangeError": "",
"passwordErrorEmpty": "",
"passwordErrorMatch": "",
"pasteAddressToAddContact": "… cole um endereço aqui para adicionar um contato…",
"peerAddress": "",
"peerBlockedMessage": "",
"peerName": "",
"peerNotOnline": "",
"peerOfflineMessage": "",
"pendingLabel": "Pendente",
"postNewBulletinLabel": "Postar novo boletim",
"profileName": "",
"profileOnionLabel": "",
"puzzleGameBtn": "Jogo de Adivinhação",
"radioNoPassword": "",
"radioUsePassword": "",
"reallyLeaveThisGroupPrompt": "",
"rejectGroupBtn": "Recusar",
"saveBtn": "Salvar",
"savePeerHistory": "",
"savePeerHistoryDescription": "",
"saveProfileBtn": "",
"search": "",
"searchList": "",
"server": "",
"serverConnectivityConnected": "",
"serverConnectivityDisconnected": "",
"serverInfo": "",
"serverLabel": "Servidor",
"serverNotSynced": "",
"serverSynced": "",
"settingInterfaceZoom": "",
"settingLanguage": "",
"settingTheme": "",
"smallTextLabel": "Pequeno",
"successfullAddedContact": "",
"themeDark": "",
"themeLight": "",
"titleManageContacts": "",
"titleManageProfiles": "",
"titleManageServers": "",
"titlePlaceholder": "título…",
"todoPlaceholder": "Afazer…",
"tooltipAddContact": "",
"tooltipOpenSettings": "",
"tooltipUnlockProfiles": "",
"unblockBtn": "",
"unlock": "",
"update": "",
"version": "",
"versionBuilddate": "",
"versionTor": "",
"viewGroupMembershipTooltip": "",
"viewServerInfo": "",
"yesLeave": "",
"yourDisplayName": "",
"yourProfiles": "",
"yourServers": "",
"zoomLabel": "Zoom da interface (afeta principalmente tamanho de texto e botões)"
"@@locale": "pt",
"@@last_modified": "2021-06-15T02:08:49+02:00",
"createGroupTitle": "Criar Grupo",
"serverLabel": "Servidor",
"groupNameLabel": "Nome do Grupo",
"defaultGroupName": "Grupo incrível",
"createGroupBtn": "Criar",
"profileOnionLabel": "Send this address to peers you want to connect with",
"copyBtn": "Copiar",
"copiedToClipboardNotification": "Copiado",
"addPeerTab": "Add a peer",
"createGroupTab": "Create a group",
"joinGroupTab": "Join a group",
"peerAddress": "Address",
"peerName": "Name",
"groupName": "Group name",
"server": "Server",
"invitation": "Invitation",
"groupAddr": "Address",
"addPeer": "Add Peer",
"createGroup": "Create group",
"joinGroup": "Join group",
"newBulletinLabel": "Novo Boletim",
"postNewBulletinLabel": "Postar novo boletim",
"titlePlaceholder": "título…",
"pasteAddressToAddContact": "… cole um endereço aqui para adicionar um contato…",
"blocked": "Blocked",
"cycleCatsAndroid": "Click to cycle category.\nLong-press to reset.",
"cycleCatsDesktop": "Click to cycle category.\nRight-click to reset.",
"cycleMorphsAndroid": "Click to cycle morphs.\nLong-press to reset.",
"cycleMorphsDesktop": "Click to cycle morphs.\nRight-click to reset.",
"cycleColoursAndroid": "Click to cycle colours.\nLong-press to reset.",
"cycleColoursDesktop": "Click to cycle colours.\nRight-click to reset.",
"search": "Search...",
"invitationLabel": "Convite",
"serverInfo": "Server Information",
"serverConnectivityConnected": "Server Connected",
"serverConnectivityDisconnected": "Server Disconnected",
"serverSynced": "Synced",
"serverNotSynced": "Out of Sync",
"viewServerInfo": "Server Info",
"saveBtn": "Salvar",
"inviteToGroupLabel": "Convidar ao grupo",
"inviteBtn": "Convidar",
"deleteBtn": "Deletar",
"update": "Update",
"searchList": "Search List",
"peerNotOnline": "Peer is Offline. Applications cannot be used right now.",
"addListItemBtn": "Add Item",
"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.",
"dmTooltip": "Clique para DM",
"couldNotSendMsgError": "Não deu para enviar esta mensagem",
"acknowledgedLabel": "Confirmada",
"pendingLabel": "Pendente",
"peerBlockedMessage": "Peer is blocked",
"peerOfflineMessage": "Peer is offline, messages can't be delivered right now",
"copiedClipboardNotification": "Copiado",
"newGroupBtn": "Criar novo grupo",
"acceptGroupInviteLabel": "Você quer aceitar o convite para",
"acceptGroupBtn": "Aceitar",
"rejectGroupBtn": "Recusar",
"chatBtn": "Chat",
"listsBtn": "Listas",
"bulletinsBtn": "Boletins",
"puzzleGameBtn": "Jogo de Adivinhação",
"addressLabel": "Endereço",
"displayNameLabel": "Nome de Exibição",
"blockBtn": "Block Peer",
"savePeerHistory": "Save Peer History",
"savePeerHistoryDescription": "Determines whether or not to delete any history associated with the peer.",
"dontSavePeerHistory": "Delete Peer History",
"unblockBtn": "Unblock Peer",
"addProfileTitle": "Add new profile",
"editProfileTitle": "Edit Profile",
"profileName": "Display name",
"defaultProfileName": "Alice",
"newProfile": "New Profile",
"editProfile": "Edit Profille",
"radioUsePassword": "Password",
"radioNoPassword": "Unencrypted (No password)",
"noPasswordWarning": "Not using a password on this account means that all data stored locally will not be encrypted",
"yourDisplayName": "Your Display Name",
"currentPasswordLabel": "Current Password",
"password1Label": "Password",
"password2Label": "Reenter password",
"passwordErrorEmpty": "Password cannot be empty",
"createProfileBtn": "Create Profile",
"saveProfileBtn": "Save Profile",
"passwordErrorMatch": "Passwords do not match",
"passwordChangeError": "Error changing password: Supplied password rejected",
"deleteProfileBtn": "Delete Profile",
"deleteConfirmLabel": "Type DELETE to confirm",
"deleteProfileConfirmBtn": "Really Delete Profile",
"deleteConfirmText": "DELETE",
"addNewProfileBtn": "Add new profile",
"enterProfilePassword": "Enter a password to view your profiles",
"password": "Password",
"error0ProfilesLoadedForPassword": "0 profiles loaded with that password",
"yourProfiles": "Your Profiles",
"yourServers": "Your Servers",
"unlock": "Unlock",
"cwtchSettingsTitle": "Configurações do Cwtch",
"versionBuilddate": "Version: %1 Built on: %2",
"zoomLabel": "Zoom da interface (afeta principalmente tamanho de texto e botões)",
"blockUnknownLabel": "Block Unknown Peers",
"settingLanguage": "Language",
"localeEn": "English",
"localeFr": "Frances",
"localePt": "Portuguesa",
"localeDe": "Deutsche",
"settingInterfaceZoom": "Zoom level",
"largeTextLabel": "Grande",
"settingTheme": "Theme",
"themeLight": "Light",
"themeDark": "Dark",
"experimentsEnabled": "Enable Experiments",
"versionTor": "Version %1 with tor %2",
"version": "Version %1",
"builddate": "Built on: %2",
"defaultScalingText": "Texto tamanho padrão (fator de escala: ",
"smallTextLabel": "Pequeno",
"loadingTor": "Loading tor...",
"viewGroupMembershipTooltip": "View Group Membership",
"networkStatusDisconnected": "Disconnected from the internet, check your connection",
"networkStatusAttemptingTor": "Attempting to connect to Tor network",
"networkStatusConnecting": "Connecting to network and peers...",
"networkStatusOnline": "Online",
"newConnectionPaneTitle": "New Connection",
"addListItem": "Adicionar Item à Lista",
"addNewItem": "Adicionar novo item à lista",
"todoPlaceholder": "Afazer…",
"localeEs": "Espanol",
"localeIt": "Italiana",
"enableGroups": "Enable Group Chat",
"enterCurrentPasswordForDelete": "Please enter current password to delete this profile.",
"conversationSettings": "Conversation Settings",
"invalidImportString": "Invalid import string",
"contactAlreadyExists": "Contact Already Exists",
"tooltipOpenSettings": "Open the settings pane",
"tooltipAddContact": "Add a new contact or conversation",
"titleManageContacts": "Conversations",
"tooltipUnlockProfiles": "Unlock encrypted profiles by entering their password.",
"titleManageProfiles": "Manage Cwtch Profiles",
"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.",
"descriptionExperimentsGroups": "The group experiment allows Cwtch to connect with untrusted server infrastructure to facilitate communication with more than one contact.",
"descriptionBlockUnknownConnections": "If turned on, this option will automatically close connections from Cwtch users that have not been added to your contact list.",
"successfullAddedContact": "Successfully added ",
"dateRightNow": "Right Now",
"dateMinutesAgo": "Minutes Ago",
"dateHoursAgo": "Hours Ago",
"dateDaysAgo": "Days Ago",
"dateWeeksAgo": "Weeks Ago",
"dateLastMonth": "Last Month",
"dateYesterday": "Yesterday",
"dateLastYear": "Last Year",
"dateYearsAgo": "X Years Ago (displayed next to a contact row to indicate time of last action)",
"dateNever": "Never",
"dateMonthsAgo": "Months Ago",
"titleManageServers": "Manage Servers",
"inviteToGroup": "You have been invited to join a group:",
"leaveGroup": "Leave This Conversation",
"reallyLeaveThisGroupPrompt": "Are you sure you want to leave this conversation? All messages and attributes will be deleted.",
"yesLeave": "Yes, Leave This Conversation",
"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/views/messageview.dart';
import 'package:cwtch/widgets/rightshiftfixer.dart';
import 'package:flutter/foundation.dart';
import 'package:cwtch/cwtch/ffi.dart';
@ -8,6 +11,7 @@ import 'package:cwtch/errorHandler.dart';
import 'package:cwtch/settings.dart';
import 'package:cwtch/torstatus.dart';
import 'package:cwtch/views/triplecolview.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'cwtch/cwtch.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, 1, 2];
late ProfileListState profs;
final MethodChannel notificationClickChannel = MethodChannel('im.cwtch.flwtch/notificationClickHandler');
final GlobalKey<NavigatorState> navKey = GlobalKey<NavigatorState>();
@override
initState() {
@ -51,6 +57,7 @@ class FlwtchState extends State<Flwtch> {
cwtchInit = false;
profs = ProfileListState();
notificationClickChannel.setMethodCallHandler(_externalNotificationClicked);
if (Platform.isAndroid) {
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, NullNotificationsManager());
@ -63,10 +70,9 @@ class FlwtchState extends State<Flwtch> {
cwtch = CwtchFfi(cwtchNotifier);
}
cwtch.Start().then((val) {
setState(() {
cwtchInit = true;
});
cwtch.Start();
setState(() {
cwtchInit = true;
});
}
@ -92,13 +98,12 @@ class FlwtchState extends State<Flwtch> {
return Consumer<Settings>(
builder: (context, settings, child) => MaterialApp(
key: Key('app'),
navigatorKey: navKey,
locale: settings.locale,
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
title: 'Cwtch',
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(),
),
);
@ -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
void 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...
void replaceServers(String serversJson) {
if (serversJson != null && serversJson != "" && serversJson != "null") {
if (serversJson != "" && serversJson != "null") {
print("got servers $serversJson");
List<dynamic> servers = jsonDecode(serversJson);
this._servers.replace(servers.map((server) {
@ -382,6 +382,7 @@ class MessageState extends ChangeNotifier {
late String _inviteNick;
late DateTime _timestamp;
late String _senderOnion;
late int _flags;
String? _senderImage;
late String _signature = "";
late bool _ackd = false;
@ -402,6 +403,12 @@ class MessageState extends ChangeNotifier {
get message => this._message;
get overlay => this._overlay;
get timestamp => this._timestamp;
int get flags => this._flags;
set flags(int newVal) {
this._flags = newVal;
notifyListeners();
}
bool get ackd => this._ackd;
bool get error => this._error;
bool get malformed => this._malformed;
@ -450,6 +457,7 @@ class MessageState extends ChangeNotifier {
this._timestamp = DateTime.tryParse(messageWrapper['Timestamp'])!;
this._senderOnion = messageWrapper['PeerID'];
this._senderImage = messageWrapper['ContactImage'];
this._flags = int.parse(messageWrapper['Flags'].toString(), radix: 2);
// If this is a group, store the signature
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()))),
dialogTheme: DialogTheme(
backgroundColor: opaque.current().backgroundPaneColor(),

View File

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

View File

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

View File

@ -177,7 +177,9 @@ class _MessageViewState extends State<MessageView> {
),
ChangeNotifierProvider.value(
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(() {
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:cwtch/widgets/profilerow.dart';
import 'package:provider/provider.dart';
import '../config.dart';
import '../main.dart';
import '../model.dart';
import '../opaque.dart';
import '../torstatus.dart';
import 'addeditprofileview.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())))
]),
actions: [
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),
],
actions: getActions(),
),
floatingActionButton: FloatingActionButton(
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() {
final setLoggingLevel = {
"EventType": "SetLoggingLevel",

View File

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

View File

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

View File

@ -1,6 +1,7 @@
import 'dart:convert';
import 'package:cwtch/cwtch_icons_icons.dart';
import 'package:cwtch/widgets/malformedbubble.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../main.dart';
@ -21,15 +22,17 @@ class InvitationBubble extends StatefulWidget {
class InvitationBubbleState extends State<InvitationBubble> {
bool rejected = false;
bool isAccepted = false;
FocusNode _focus = FocusNode();
@override
Widget build(BuildContext context) {
var fromMe = Provider.of<MessageState>(context).senderOnion == Provider.of<ProfileInfoState>(context).onion;
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 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();
if (Provider.of<MessageState>(context).timestamp != null) {
@ -53,25 +56,32 @@ class InvitationBubbleState extends State<InvitationBubble> {
child: SelectableText(senderDisplayStr + '\u202F',
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
? senderInviteChrome("You sent an invitation for", isGroup ? "a group" : Provider.of<MessageState>(context).message, myKey)
: inviteChrome(isGroup ? AppLocalizations.of(context)!.inviteToGroup : "This is a contact suggestion for:", Provider.of<MessageState>(context).inviteNick,
Provider.of<MessageState>(context).inviteTarget, myKey);
? senderInviteChrome(AppLocalizations.of(context)!.sendAnInvitation,
isGroup ? Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageState>(context).inviteTarget)!.nickname : Provider.of<MessageState>(context).message, myKey)
: (inviteChrome(isGroup ? AppLocalizations.of(context)!.inviteToGroup : AppLocalizations.of(context)!.contactSuggestion, Provider.of<MessageState>(context).inviteNick,
Provider.of<MessageState>(context).inviteTarget, myKey));
Widget wdgDecorations;
if (fromMe) {
wdgDecorations = MessageBubbleDecoration(ackd: Provider.of<MessageState>(context).ackd, errored: Provider.of<MessageState>(context).error, fromMe: fromMe, prettyDate: prettyDate);
} else if (isAccepted) {
wdgDecorations = Text("Accepted!");
wdgDecorations = Text(AppLocalizations.of(context)!.accepted + '\u202F');
} else if (this.rejected) {
wdgDecorations = Text("Rejected.");
wdgDecorations = Text(AppLocalizations.of(context)!.rejected + '\u202F');
} else {
wdgDecorations = Center(
widthFactor: 1,
child: Row(children: [
Padding(padding: EdgeInsets.all(5), child: TextButton(child: Text("Reject"), onPressed: _btnReject)),
Padding(padding: EdgeInsets.all(5), child: TextButton(child: Text("Accept"), onPressed: _btnAccept)),
child: Wrap(children: [
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(AppLocalizations.of(context)!.acceptGroupBtn + '\u202F'), onPressed: _btnAccept)),
]));
}
@ -95,87 +105,83 @@ class InvitationBubbleState extends State<InvitationBubble> {
widthFactor: 1.0,
child: Padding(
padding: EdgeInsets.all(9.0),
child: Row(mainAxisSize: MainAxisSize.min, children: [
Center(widthFactor: 1, child: Padding(padding: EdgeInsets.all(4), child: Icon(CwtchIcons.send_invite, size: 32))),
child: Wrap(runAlignment: WrapAlignment.spaceEvenly, alignment: WrapAlignment.spaceEvenly, runSpacing: 1.0, crossAxisAlignment: WrapCrossAlignment.center, children: [
Center(widthFactor: 1, child: Padding(padding: EdgeInsets.all(10.0), child: Icon(CwtchIcons.send_invite, size: 32))),
Center(
widthFactor: 1.0,
child: Column(
crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start,
mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: fromMe ? [wdgMessage, wdgDecorations] : [wdgSender, wdgMessage, wdgDecorations])),
widthFactor: 1.0,
child: Column(
crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start,
mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: fromMe ? [wdgMessage, wdgDecorations] : [wdgSender, wdgMessage, wdgDecorations]),
)
])))));
});
}
void _btnReject() {
//todo: how should we track inline invite rejections?
setState(() => this.rejected = true);
setState(() {
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() {
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
if (Provider.of<MessageState>(context, listen: false).overlay == 100) {
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 {
setState(() {
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
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
Widget senderInviteChrome(String chrome, String targetName, String myKey) {
return Center(
widthFactor: 1,
child: Row(children: [
SelectableText(
chrome,
focusNode: _focus,
style: TextStyle(
color: Provider.of<Settings>(context).theme.messageFromMeTextColor(),
),
textAlign: TextAlign.left,
textWidthBasis: TextWidthBasis.longestLine,
),
SelectableText(
targetName + '\u202F',
key: Key(myKey),
focusNode: _focus,
style: TextStyle(
color: Provider.of<Settings>(context).theme.messageFromMeTextColor(),
),
textAlign: TextAlign.left,
textWidthBasis: TextWidthBasis.longestLine,
)
]));
return Wrap(children: [
SelectableText(
chrome + '\u202F',
style: TextStyle(
color: Provider.of<Settings>(context).theme.messageFromMeTextColor(),
),
textAlign: TextAlign.left,
maxLines: 2,
textWidthBasis: TextWidthBasis.longestLine,
),
SelectableText(
targetName + '\u202F',
key: Key(myKey),
style: TextStyle(
color: Provider.of<Settings>(context).theme.messageFromMeTextColor(),
),
textAlign: TextAlign.left,
maxLines: 2,
textWidthBasis: TextWidthBasis.longestLine,
)
]);
}
// Construct an invite chrome
Widget inviteChrome(String chrome, String targetName, String targetId, String myKey) {
return Center(
widthFactor: 1,
child: Row(children: [
SelectableText(
chrome,
focusNode: _focus,
style: TextStyle(
color: Provider.of<Settings>(context).theme.messageFromOtherTextColor(),
),
textAlign: TextAlign.left,
textWidthBasis: TextWidthBasis.longestLine,
),
SelectableText(
targetName,
key: Key(myKey),
focusNode: _focus,
style: TextStyle(color: Provider.of<Settings>(context).theme.messageFromOtherTextColor()),
textAlign: TextAlign.left,
textWidthBasis: TextWidthBasis.longestLine,
)
]));
return Wrap(children: [
SelectableText(
chrome + '\u202F',
style: TextStyle(
color: Provider.of<Settings>(context).theme.messageFromOtherTextColor(),
),
textAlign: TextAlign.left,
textWidthBasis: TextWidthBasis.longestLine,
maxLines: 2,
),
SelectableText(
targetName + '\u202F',
key: Key(myKey),
style: TextStyle(color: Provider.of<Settings>(context).theme.messageFromOtherTextColor()),
textAlign: TextAlign.left,
maxLines: 2,
textWidthBasis: TextWidthBasis.longestLine,
)
]);
}
}

View File

@ -39,7 +39,6 @@ class _MessageListState extends State<MessageList> {
)),
Expanded(
child: Scrollbar(
isAlwaysShown: true,
controller: ctrlr1,
child: Container(
// Only show broken heart is the contact is offline...
@ -54,7 +53,7 @@ class _MessageListState extends State<MessageList> {
child: ListView.builder(
controller: ctrlr1,
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) {
var trueIndex = Provider.of<ContactInfoState>(outerContext).totalMessages - index - 1;
return ChangeNotifierProvider(

View File

@ -94,7 +94,10 @@ class _MessageRowState extends State<MessageRow> {
final setPeerAttributeJson = jsonEncode(setPeerAttribute);
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);
}
}

View File

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

View File

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

View File

@ -21,7 +21,7 @@ packages:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.6.1"
version: "2.7.0"
boolean_selector:
dependency: transitive
description:
@ -191,7 +191,7 @@ packages:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
version: "1.4.0"
nested:
dependency: transitive
description:
@ -392,7 +392,7 @@ packages:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.0"
version: "0.4.0"
typed_data:
dependency: transitive
description:
@ -450,7 +450,7 @@ packages:
name: xml
url: "https://pub.dartlang.org"
source: hosted
version: "5.1.1"
version: "5.1.2"
sdks:
dart: ">=2.13.0 <3.0.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.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.0.0+9
version: 1.0.0+11
environment:
sdk: ">=2.12.0 <3.0.0"

View File

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

View File

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