Merge pull request 'Autobindings, Remove Server code from Android, Debug mode Fixes' (#639) from autobindings into trunk
continuous-integration/drone/push Build is pending Details

Reviewed-on: #639
This commit is contained in:
Sarah Jamie Lewis 2023-03-16 23:35:59 +00:00
commit fe55c9dd18
42 changed files with 845 additions and 177 deletions

5
.gitignore vendored
View File

@ -8,6 +8,11 @@
.buildlog/
.history
.svn/
package.json
package-lock.json
libCwtch*
cwtch.aar
node_modules
# IntelliJ related
*.iml

View File

@ -1 +0,0 @@
2023-02-08-16-57-v1.10.5

View File

@ -1 +1 @@
2023-02-08-21-57-v1.10.5
2023-03-14-11-33-v0.0.3

View File

@ -323,7 +323,7 @@ class MainActivity: FlutterActivity() {
val profile: String = call.argument("ProfileOnion") ?: ""
val conversation: Int = call.argument("conversation") ?: 0
val target: Int = call.argument("target") ?: 0
result.success(Cwtch.sendInvitation(profile, conversation.toLong(), target.toLong()))
result.success(Cwtch.sendInviteMessage(profile, conversation.toLong(), target.toLong()))
return
}
@ -345,14 +345,14 @@ class MainActivity: FlutterActivity() {
"RestartSharing" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val filepath: String = call.argument("filekey") ?: ""
result.success(Cwtch.restartSharing(profile, filepath))
result.success(Cwtch.restartFileShare(profile, filepath))
return
}
"StopSharing" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val filepath: String = call.argument("filekey") ?: ""
result.success(Cwtch.stopSharing(profile, filepath))
result.success(Cwtch.stopFileShare(profile, filepath))
return
}
@ -381,25 +381,18 @@ class MainActivity: FlutterActivity() {
val passNew2: String = call.argument("NewPassAgain") ?: ""
Cwtch.changePassword(profile, pass, passNew, passNew2)
}
"GetMessage" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val conversation: Int = call.argument("conversation") ?: 0
val indexI: Int = call.argument("index") ?: 0
result.success(Cwtch.getMessage(profile, conversation.toLong(), indexI.toLong()))
return
}
"GetMessageByID" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val conversation: Int = call.argument("conversation") ?: 0
val id: Int = call.argument("id") ?: 0
result.success(Cwtch.getMessageByID(profile, conversation.toLong(), id.toLong()))
result.success(Cwtch.getMessageById(profile, conversation.toLong(), id.toLong()))
return
}
"GetMessageByContentHash" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val conversation: Int = call.argument("conversation") ?: 0
val contentHash: String = call.argument("contentHash") ?: ""
result.success(Cwtch.getMessagesByContentHash(profile, conversation.toLong(), contentHash))
result.success(Cwtch.getMessageByContentHash(profile, conversation.toLong(), contentHash))
return
}
"SetMessageAttribute" -> {
@ -409,7 +402,7 @@ class MainActivity: FlutterActivity() {
val midx: Int = call.argument("Message") ?: 0
val key: String = call.argument("key") ?: ""
val value: String = call.argument("value") ?: ""
Cwtch.setMessageAttribute(profile, conversation.toLong(), channel.toLong(), midx.toLong(), key, value)
Cwtch.updateMessageAttribute(profile, conversation.toLong(), channel.toLong(), midx.toLong(), key, value)
}
"AcceptConversation" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
@ -419,12 +412,12 @@ class MainActivity: FlutterActivity() {
"BlockContact" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val conversation: Int = call.argument("conversation") ?: 0
Cwtch.blockContact(profile, conversation.toLong())
Cwtch.blockConversation(profile, conversation.toLong())
}
"UnblockContact" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val conversation: Int = call.argument("conversation") ?: 0
Cwtch.unblockContact(profile, conversation.toLong())
Cwtch.unblockConversation(profile, conversation.toLong())
}
"DownloadFile" -> {
@ -436,7 +429,7 @@ class MainActivity: FlutterActivity() {
val filekey: String = call.argument("filekey") ?: ""
// FIXME: Prevent spurious calls by Intent
if (profile != "") {
Cwtch.downloadFile(profile, conversation.toLong(), filepath, manifestpath, filekey)
Cwtch.downloadFileDefaultLimit(profile, conversation.toLong(), filepath, manifestpath, filekey)
}
}
"CheckDownloadStatus" -> {
@ -450,14 +443,9 @@ class MainActivity: FlutterActivity() {
val fileKey: String = call.argument("fileKey") ?: ""
Cwtch.verifyOrResumeDownload(profile, conversation.toLong(), fileKey)
}
"SendProfileEvent" -> {
val onion: String= call.argument("onion") ?: ""
val jsonEvent: String = call.argument("jsonEvent") ?: ""
Cwtch.sendProfileEvent(onion, jsonEvent)
}
"SendAppEvent" -> {
val jsonEvent: String = call.argument("jsonEvent") ?: ""
Cwtch.sendAppEvent(jsonEvent)
"UpdateSettings" -> {
val json: String = call.argument("json") ?: ""
Cwtch.updateSettings(json)
}
"ResetTor" -> {
Cwtch.resetTor()
@ -471,7 +459,7 @@ class MainActivity: FlutterActivity() {
val profile: String = call.argument("ProfileOnion") ?: ""
val server: String = call.argument("server") ?: ""
val groupName: String = call.argument("groupName") ?: ""
Cwtch.createGroup(profile, server, groupName)
Cwtch.startGroup(profile, server, groupName)
}
"DeleteProfile" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
@ -486,7 +474,7 @@ class MainActivity: FlutterActivity() {
"DeleteConversation" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val conversation: Int = call.argument("conversation") ?: 0
Cwtch.deleteContact(profile, conversation.toLong())
Cwtch.deleteConversation(profile, conversation.toLong())
}
"SetProfileAttribute" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
@ -501,44 +489,6 @@ class MainActivity: FlutterActivity() {
val v: String = call.argument("Val") ?: ""
Cwtch.setConversationAttribute(profile, conversation.toLong(), key, v)
}
"LoadServers" -> {
val password: String = call.argument("Password") ?: ""
Cwtch.loadServers(password)
}
"CreateServer" -> {
val password: String = call.argument("Password") ?: ""
val desc: String = call.argument("Description") ?: ""
val autostart: Boolean = call.argument("Autostart") ?: false
Cwtch.createServer(password, desc, autostart)
}
"DeleteServer" -> {
val serverOnion: String = call.argument("ServerOnion") ?: ""
val password: String = call.argument("Password") ?: ""
Cwtch.deleteServer(serverOnion, password)
}
"LaunchServers" -> {
Cwtch.launchServers()
}
"LaunchServer" -> {
val serverOnion: String = call.argument("ServerOnion") ?: ""
Cwtch.launchServer(serverOnion)
}
"StopServer" -> {
val serverOnion: String = call.argument("ServerOnion") ?: ""
Cwtch.stopServer(serverOnion)
}
"StopServers" -> {
Cwtch.stopServers()
}
"DestroyServers" -> {
Cwtch.destroyServers()
}
"SetServerAttribute" -> {
val serverOnion: String = call.argument("ServerOnion") ?: ""
val key: String = call.argument("Key") ?: ""
val v: String = call.argument("Val") ?: ""
Cwtch.setServerAttribute(serverOnion, key, v)
}
"ImportProfile" -> {
val file: String = call.argument("file") ?: ""
val pass: String = call.argument("pass") ?: ""

View File

@ -1,8 +1,8 @@
#!/bin/sh
VERSION=`cat LIBCWTCH-GO-MACOS.version`
VERSION=`cat LIBCWTCH-GO.version`
echo $VERSION
curl --fail https://build.openprivacy.ca/files/libCwtch-go-macos-$VERSION/libCwtch.x64.dylib --output libCwtch.x64.dylib
curl --fail https://build.openprivacy.ca/files/libCwtch-go-macos-$VERSION/libCwtch.arm64.dylib --output libCwtch.arm64.dylib
curl --fail https://build.openprivacy.ca/files/libCwtch-autobindings-$VERSION/macos/libCwtch.x64.dylib --output libCwtch.x64.dylib
curl --fail https://build.openprivacy.ca/files/libCwtch-autobindings-$VERSION/macos/libCwtch.arm64.dylib --output libCwtch.arm64.dylib

View File

@ -2,7 +2,7 @@ $Env:VERSION = type LIBCWTCH-GO.version
echo $Env:VERSION
# This should automatically fail on error...
Invoke-WebRequest -Uri https://build.openprivacy.ca/files/libCwtch-go-$Env:VERSION/libCwtch.dll -OutFile windows/libCwtch.dll
Invoke-WebRequest -Uri https://build.openprivacy.ca/files/libCwtch-autobindings-$Env:VERSION/windows/libCwtch.dll -OutFile windows/libCwtch.dll
#Invoke-WebRequest -Uri https://build.openprivacy.ca/files/libCwtch-go-$Env:VERSION/cwtch.aar -OutFile android/cwtch/cwtch.aar
#Invoke-WebRequest -Uri https://build.openprivacy.ca/files/libCwtch-go-$Env:VERSION/libCwtch.so -Outfile linux/libCwtch.so

View File

@ -3,5 +3,5 @@
VERSION=`cat LIBCWTCH-GO.version`
echo $VERSION
curl --fail https://build.openprivacy.ca/files/libCwtch-go-$VERSION/cwtch.aar --output android/cwtch/cwtch.aar
curl --fail https://build.openprivacy.ca/files/libCwtch-go-$VERSION/libCwtch.so --output linux/libCwtch.so
curl --fail https://build.openprivacy.ca/files/libCwtch-autobindings-$VERSION/android/cwtch.aar --output android/cwtch/cwtch.aar
curl --fail https://build.openprivacy.ca/files/libCwtch-autobindings-$VERSION/linux/libCwtch.so --output linux/libCwtch.so

View File

@ -32,12 +32,7 @@ abstract class Cwtch {
// ignore: non_constant_identifier_names
void ResetTor();
// todo: remove these
// ignore: non_constant_identifier_names
void SendProfileEvent(String onion, String jsonEvent);
// ignore: non_constant_identifier_names
void SendAppEvent(String jsonEvent);
void UpdateSettings(String json);
// ignore: non_constant_identifier_names
void AcceptContact(String profileOnion, int contactHandle);
@ -136,4 +131,6 @@ abstract class Cwtch {
void dispose();
Future<dynamic> GetDebugInfo();
bool IsServersCompiled();
}

View File

@ -70,7 +70,7 @@ class CwtchNotifier {
if (data["Online"] == null) {
break;
}
EnvironmentConfig.debugLog("NewPeer $data");
// EnvironmentConfig.debugLog("NewPeer $data");
// if tag != v1-defaultPassword then it is either encrypted OR it is an unencrypted account created during pre-beta...
profileCN.add(data["Identity"], data["name"], data["picture"], data["defaultPicture"], data["ContactsJson"], data["ServerList"], data["Online"] == "true", data["autostart"] == "true",
data["tag"] != "v1-defaultPassword");

View File

@ -29,6 +29,7 @@ typedef FreeFn = void Function(Pointer<Utf8>);
typedef void_from_string_string_function = Void Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32);
typedef VoidFromStringStringFn = void Function(Pointer<Utf8>, int, Pointer<Utf8>, int);
typedef VoidFromStringFn = void Function(Pointer<Utf8>, int);
typedef void_from_string_string_string_function = Void Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Pointer<Utf8>, Int32);
typedef VoidFromStringStringStringFn = void Function(Pointer<Utf8>, int, Pointer<Utf8>, int, Pointer<Utf8>, int);
@ -361,30 +362,6 @@ class CwtchFfi implements Cwtch {
return jsonMessage;
}
@override
// ignore: non_constant_identifier_names
void SendProfileEvent(String onion, String json) {
var sendAppBusEvent = library.lookup<NativeFunction<string_string_to_void_function>>("c_SendProfileEvent");
// ignore: non_constant_identifier_names
final SendAppBusEvent = sendAppBusEvent.asFunction<StringStringFn>();
final utf8onion = onion.toNativeUtf8();
final utf8json = json.toNativeUtf8();
SendAppBusEvent(utf8onion, utf8onion.length, utf8json, utf8json.length);
malloc.free(utf8onion);
malloc.free(utf8json);
}
@override
// ignore: non_constant_identifier_names
void SendAppEvent(String json) {
var sendAppBusEvent = library.lookup<NativeFunction<string_to_void_function>>("c_SendAppEvent");
// ignore: non_constant_identifier_names
final SendAppBusEvent = sendAppBusEvent.asFunction<StringFn>();
final utf8json = json.toNativeUtf8();
SendAppBusEvent(utf8json, utf8json.length);
malloc.free(utf8json);
}
@override
// ignore: non_constant_identifier_names
void AcceptContact(String profileOnion, int contactHandle) {
@ -437,7 +414,7 @@ class CwtchFfi implements Cwtch {
@override
// ignore: non_constant_identifier_names
Future<dynamic> SendInvitation(String profileOnion, int contactHandle, int target) async {
var sendInvitation = library.lookup<NativeFunction<get_json_blob_from_str_int_int_function>>("c_SendInvitation");
var sendInvitation = library.lookup<NativeFunction<get_json_blob_from_str_int_int_function>>("c_SendInviteMessage");
// ignore: non_constant_identifier_names
final SendInvitation = sendInvitation.asFunction<GetJsonBlobFromStrIntIntFn>();
final u1 = profileOnion.toNativeUtf8();
@ -467,7 +444,7 @@ class CwtchFfi implements Cwtch {
@override
// ignore: non_constant_identifier_names
void DownloadFile(String profileOnion, int contactHandle, String filepath, String manifestpath, String filekey) {
var dlFile = library.lookup<NativeFunction<void_from_string_int_string_string_string_function>>("c_DownloadFile");
var dlFile = library.lookup<NativeFunction<void_from_string_int_string_string_string_function>>("c_DownloadFileDefaultLimit");
// ignore: non_constant_identifier_names
final DownloadFile = dlFile.asFunction<VoidFromStringIntStringStringStringFn>();
final u1 = profileOnion.toNativeUtf8();
@ -546,12 +523,12 @@ class CwtchFfi implements Cwtch {
@override
// ignore: non_constant_identifier_names
void CreateGroup(String profileOnion, String server, String groupName) {
var createGroup = library.lookup<NativeFunction<void_from_string_string_string_function>>("c_CreateGroup");
var createGroup = library.lookup<NativeFunction<void_from_string_string_string_function>>("c_StartGroup");
// ignore: non_constant_identifier_names
final CreateGroup = createGroup.asFunction<VoidFromStringStringStringFn>();
final u1 = profileOnion.toNativeUtf8();
final u2 = server.toNativeUtf8();
final u3 = groupName.toNativeUtf8();
final u3 = server.toNativeUtf8();
final u2 = groupName.toNativeUtf8();
CreateGroup(u1, u1.length, u2, u2.length, u3, u3.length);
malloc.free(u1);
@ -627,7 +604,7 @@ class CwtchFfi implements Cwtch {
@override
// ignore: non_constant_identifier_names
void SetMessageAttribute(String profile, int conversation, int channel, int message, String key, String val) {
var setMessageAttribute = library.lookup<NativeFunction<void_from_string_int_int_int_string_string_function>>("c_SetMessageAttribute");
var setMessageAttribute = library.lookup<NativeFunction<void_from_string_int_int_int_string_string_function>>("c_UpdateMessageAttribute");
// ignore: non_constant_identifier_names
final SetMessageAttribute = setMessageAttribute.asFunction<VoidFromStringIntIntIntStringStringFn>();
final u1 = profile.toNativeUtf8();
@ -762,7 +739,7 @@ class CwtchFfi implements Cwtch {
@override
// ignore: non_constant_identifier_names
Future GetMessageByContentHash(String profile, int handle, String contentHash) async {
var getMessagesByContentHashC = library.lookup<NativeFunction<get_json_blob_from_str_int_string_function>>("c_GetMessagesByContentHash");
var getMessagesByContentHashC = library.lookup<NativeFunction<get_json_blob_from_str_int_string_function>>("c_GetMessageByContentHash");
// ignore: non_constant_identifier_names
final GetMessagesByContentHash = getMessagesByContentHashC.asFunction<GetJsonBlobFromStrIntStringFn>();
final utf8profile = profile.toNativeUtf8();
@ -911,4 +888,19 @@ class CwtchFfi implements Cwtch {
malloc.free(utf8profile);
malloc.free(ut8filekey);
}
@override
void UpdateSettings(String json) {
var updateSettings = library.lookup<NativeFunction<string_to_void_function>>("c_UpdateSettings");
// ignore: non_constant_identifier_names
final UpdateSettingsFn = updateSettings.asFunction<VoidFromStringFn>();
final u1 = json.toNativeUtf8();
UpdateSettingsFn(u1, u1.length);
malloc.free(u1);
}
@override
bool IsServersCompiled() {
return library.providesSymbol("c_LoadServers");
}
}

View File

@ -358,4 +358,15 @@ class CwtchGomobile implements Cwtch {
void StopSharing(String profile, String filekey) {
cwtchPlatform.invokeMethod("StopSharing", {"ProfileOnion": profile, "filekey": filekey});
}
@override
void UpdateSettings(String json) {
cwtchPlatform.invokeMethod("UpdateSettings", {"json": json});
}
@override
bool IsServersCompiled() {
// never for android builds...
return false;
}
}

View File

@ -1,6 +1,7 @@
{
"@@locale": "cy",
"@@last_modified": "2023-02-07T01:58:40+01:00",
"@@last_modified": "2023-03-14T14:10:01+01:00",
"localeKo": "Korean \/ 한국어",
"localeSk": "Slovak \/ Slovák",
"profileEnabledDescription": "Start or stop the profile",
"profileAutostartDescription": "Controls if the profile will be automatically launched on startup",

View File

@ -1,6 +1,7 @@
{
"@@locale": "da",
"@@last_modified": "2023-02-07T01:58:40+01:00",
"@@last_modified": "2023-03-14T14:10:01+01:00",
"localeKo": "Korean \/ 한국어",
"localeSk": "Slovak \/ Slovák",
"profileEnabledDescription": "Start or stop the profile",
"profileAutostartDescription": "Controls if the profile will be automatically launched on startup",

View File

@ -1,6 +1,7 @@
{
"@@locale": "de",
"@@last_modified": "2023-02-07T01:58:40+01:00",
"@@last_modified": "2023-03-14T14:10:01+01:00",
"localeKo": "Korean \/ 한국어",
"localeSk": "Slovak \/ Slovák",
"profileAutostartLabel": "Autostart",
"profileEnabled": "Aktivieren",

View File

@ -1,6 +1,7 @@
{
"@@locale": "el",
"@@last_modified": "2023-02-07T01:58:40+01:00",
"@@last_modified": "2023-03-14T14:10:01+01:00",
"localeKo": "Korean \/ 한국어",
"localeSk": "Slovak \/ Slovák",
"profileEnabledDescription": "Start or stop the profile",
"profileAutostartDescription": "Controls if the profile will be automatically launched on startup",

View File

@ -1,6 +1,7 @@
{
"@@locale": "en",
"@@last_modified": "2023-02-07T01:58:40+01:00",
"@@last_modified": "2023-03-14T14:10:01+01:00",
"localeKo": "Korean \/ 한국어",
"localeSk": "Slovak \/ Slovák",
"profileEnabledDescription": "Start or stop the profile",
"profileAutostartDescription": "Controls if the profile will be automatically launched on startup",

View File

@ -1,6 +1,7 @@
{
"@@locale": "es",
"@@last_modified": "2023-02-07T01:58:40+01:00",
"@@last_modified": "2023-03-14T14:10:01+01:00",
"localeKo": "Korean \/ 한국어",
"localeSk": "Slovak \/ Slovák",
"profileEnabledDescription": "Start or stop the profile",
"profileAutostartDescription": "Controls if the profile will be automatically launched on startup",

View File

@ -1,6 +1,7 @@
{
"@@locale": "fr",
"@@last_modified": "2023-02-07T01:58:40+01:00",
"@@last_modified": "2023-03-14T14:10:01+01:00",
"localeKo": "Korean \/ 한국어",
"localeSk": "Slovak \/ Slovák",
"profileEnabledDescription": "Start or stop the profile",
"profileAutostartDescription": "Controls if the profile will be automatically launched on startup",

View File

@ -1,6 +1,7 @@
{
"@@locale": "it",
"@@last_modified": "2023-02-07T01:58:40+01:00",
"@@last_modified": "2023-03-14T14:10:01+01:00",
"localeKo": "Korean \/ 한국어",
"localeSk": "Slovak \/ Slovák",
"profileEnabledDescription": "Start or stop the profile",
"profileAutostartDescription": "Controls if the profile will be automatically launched on startup",

369
lib/l10n/intl_ko.arb Normal file
View File

@ -0,0 +1,369 @@
{
"@@locale": "ko",
"@@last_modified": "2023-03-14T14:10:01+01:00",
"deleteServerSuccess": "서버를 성공적으로 삭제했습니다.",
"localeRU": "러시아어 \/ Русский",
"newMessagesLabel": "새로운 메시지",
"themeNameCwtch": "Cwtch (크치)",
"torSettingsErrorSettingPort": "포트 번호는 1에서 65535 사이여야 합니다.",
"labelTorNetwork": "토르 네트워크",
"notificationPolicyOptIn": "옵트인",
"conversationNotificationPolicyOptIn": "옵트인",
"settingGroupBehaviour": "행동",
"settingsGroupExperiments": "실험",
"importProfileTooltip": "암호화된 Cwtch 백업을 사용하여 다른 Cwtch 인스턴스에서 생성된 프로필을 가져옵니다.",
"conversationNotificationPolicyNever": "절대",
"settingAndroidPowerExemptionDescription": "선택 사항: 최적화된 전원 관리에서 Cwtch를 제외하도록 Android에 요청합니다. 이것은 더 많은 배터리 사용 비용으로 더 나은 안정성을 가져올 것입니다.",
"tooltipPreviewFormatting": "메시지 형식 미리 보기",
"formattingExperiment": "메시지 형식",
"settingsAndroidPowerReenablePopup": "Cwtch 내에서 배터리 최적화를 다시 활성화할 수 없습니다. Android \/ 설정 \/ 앱 \/ Cwtch \/ 배터리로 이동하여 사용량을 '최적화'로 설정하십시오.",
"tooltipBackToMessageEditing": "메시지 편집으로 돌아가기",
"tooltipSubscript": "아래 첨자",
"tooltipSuperscript": "위 첨자",
"localeKo": "Korean \/ 한국어",
"notificationContentContactInfo": "대화 정보",
"newMessageNotificationConversationInfo": "%1의 새로운 메시지",
"localePl": "폴란드어 \/ Polski",
"addContact": "Add contact",
"addContactConfirm": "Add contact %1",
"downloadFileButton": "다운로드",
"labelFilesize": "크기",
"messageFileSent": "파일을 보냈습니다.",
"tooltipSendFile": "파일 보내기",
"addServerTitle": "서버 추가",
"serverAddress": "서버 주소",
"serverAutostartLabel": "자동 시작",
"serversManagerTitleLong": "호스팅하는 서버",
"serversManagerTitleShort": "서버",
"addServerTooltip": "새 서버 추가",
"settingServers": "서버 호스팅",
"copyAddress": "주소 복사",
"importLocalServerButton": "%1 가져오기",
"manageKnownServersButton": "서버 관리",
"manageKnownServersLong": "알려진 서버 관리",
"serverMetricsLabel": "서버 지표",
"manageKnownServersShort": "서버",
"serverConnectionsLabel": "연결",
"themeNamePumpkin": "호박",
"themeNameNeon1": "네온1",
"themeNameNeon2": "네온2",
"themeColorLabel": "색상 테마",
"btnSendFile": "파일 보내기",
"msgFileTooBig": "파일 크기는 10GB를 초과할 수 없습니다.",
"localeRo": "루마니아어 \/ Română",
"localeLb": "룩셈부르크어 \/ Lëtzebuergesch",
"localeNo": "노르웨이어 \/ Norsk",
"localeEl": "그리스어 \/ Ελληνικά",
"localeDa": "덴마크어 \/ Dansk",
"localeCy": "웨일스어 \/ Cymraeg",
"newMessageNotificationSimple": "새로운 메시지",
"exportProfile": "프로필 내보내기",
"importProfile": "프로필 가져오기",
"failedToImportProfile": "프로필 가져오기 오류",
"successfullyImportedProfile": "프로필을 성공적으로 가져왔습니다: %profile",
"shuttingDownApp": "종료...",
"clickableLinkError": "URL을 여는 동안 오류가 발생했습니다.",
"clickableLinkOpen": "URL 열기",
"clickableLinksCopy": "URL 복사",
"okButton": "확인",
"manageSharedFiles": "공유 파일 관리",
"stopSharingFile": "파일 공유 중지",
"restartFileShare": "파일 공유 시작",
"messageNoReplies": "이 메시지에 대한 답장이 없습니다.",
"headingReplies": "답장",
"viewReplies": "이 메시지에 대한 답장 보기",
"fileDownloadUnavailable": "이 파일은 다운로드할 수 없는 것 같습니다. 보낸 사람이 이 파일에 대한 다운로드를 비활성화했을 가망이 있습니다.",
"tooltipUnpinConversation": "'대화' 상단에서 고정 해제",
"tooltipPinConversation": "'대화' 상단에 고정",
"localeTr": "터키어 \/ Türk",
"errorDownloadDirectoryDoesNotExist": "다운로드 폴더가 설정되지 않았거나 존재하지 않는 폴더로 설정되어 있으므로 파일 공유를 사용할 수 없습니다.",
"acquiringTicketsFromServer": "안티스팸 챌린지 수행",
"acquiredTicketsFromServer": "안티스팸 챌린지 완료",
"shareProfileMenuTooltop": "다음을 통해 프로필 공유...",
"shareMenuQRCode": "QR 코드 표시",
"enableExperimentQRCode": "QR 코드",
"experimentQRCodeDescription": "QR 코드 지원으로 데이터(예: 프로필 ID)를 공유할 수 있습니다.",
"localeSk": "슬로바키아어 \/ Slovák",
"localePtBr": "브라질 포르투갈어 \/ Português do Brasil",
"localeNl": "네덜란드어 \/ Dutch",
"profileAutostartDescription": "시작 시 프로필이 자동으로 시작되는지 여부를 제어합니다.",
"profileAutostartLabel": "자동 시작",
"profileEnabled": "허락",
"profileEnabledDescription": "프로필 시각 또는 중지",
"replyingTo": "%1에 회신",
"tooltipCode": "Code \/ Monospace",
"tooltipStrikethrough": "Strikethrough",
"tooltipItalicize": "Italic",
"tooltipBoldText": "Bold",
"settingAndroidPowerExemption": "Android Ignore Battery Optimizations",
"thisFeatureRequiresGroupExpermientsToBeEnabled": "This feature requires the Groups Experiment to be enabled in Settings",
"messageFormattingDescription": "Enable rich text formatting in displayed messages e.g. **bold** and *italic*",
"clickableLinksWarning": "Opening this URL will launch an application outside of Cwtch and may reveal metadata or otherwise compromise the security of Cwtch. Only open URLs from people you trust. Are you sure you want to continue?",
"exportProfileTooltip": "Backup this profile to an encrypted file. The encrypted file can be imported into another Cwtch app.",
"notificationContentSimpleEvent": "Plain Event",
"conversationNotificationPolicySettingDescription": "Control notification behaviour for this conversation",
"conversationNotificationPolicySettingLabel": "Conversation Notification Policy",
"settingsGroupAppearance": "Appearance",
"notificationContentSettingDescription": "Controls the contents of conversation notifications",
"notificationPolicySettingDescription": "Controls the default application notification behaviour",
"notificationContentSettingLabel": "Notification Content",
"notificationPolicySettingLabel": "Notification Policy",
"conversationNotificationPolicyDefault": "Default",
"notificationPolicyDefaultAll": "Default All",
"notificationPolicyMute": "Mute",
"tooltipSelectACustomProfileImage": "Select a Custom Profile Image",
"torSettingsEnabledCacheDescription": "Cache the current downloaded Tor consensus to reuse next time Cwtch is opened. This will allow Tor to start faster. When disabled, Cwtch will purge cached data on start up.",
"torSettingsEnableCache": "Cache Tor Consensus",
"descriptionACNCircuitInfo": "In depth information about the path that the anonymous communication network is using to connect to this conversation.",
"labelACNCircuitInfo": "ACN Circuit Info",
"fileSharingSettingsDownloadFolderTooltip": "Browse to select a different default folder for downloaded files.",
"fileSharingSettingsDownloadFolderDescription": "When files are downloaded automatically (e.g. image files, when image previews are enabled) a default location to download the files to is needed.",
"torSettingsUseCustomTorServiceConfigurastionDescription": "Override the default tor configuration. Warning: This could be dangerous. Only turn this on if you know what you are doing.",
"torSettingsUseCustomTorServiceConfiguration": "Use a Custom Tor Service Configuration (torrc)",
"torSettingsCustomControlPortDescription": "Use a custom port for control connections to the Tor proxy",
"torSettingsCustomControlPort": "Custom Control Port",
"torSettingsCustomSocksPortDescription": "Use a custom port for data connections to the Tor proxy",
"torSettingsCustomSocksPort": "Custom SOCKS Port",
"torSettingsEnabledAdvancedDescription": "Use an existing Tor service on your system, or change the parameters of the Cwtch Tor Service",
"torSettingsEnabledAdvanced": "Enable Advanced Tor Configuration",
"msgAddToAccept": "Add this account to your contacts in order to accept this file.",
"msgConfirmSend": "Are you sure you want to send",
"storageMigrationModalMessage": "Migrating profiles to new storage format. This could take a few minutes...",
"loadingCwtch": "Loading Cwtch...",
"themeNameMidnight": "Midnight",
"themeNameMermaid": "Mermaid",
"themeNameGhost": "Ghost",
"themeNameVampire": "Vampire",
"themeNameWitch": "Witch",
"settingDownloadFolder": "Download Folder",
"settingImagePreviewsDescription": "Images and Profile Pictures will be downloaded and previewed automatically. We recommend that you do not enable this Experiment if you use Cwtch with untrusted contacts.",
"settingImagePreviews": "Image Previews and Profile Pictures",
"experimentClickableLinksDescription": "The clickable links experiment allows you to click on URLs shared in messages",
"enableExperimentClickableLinks": "Enable Clickable Links",
"serverTotalMessagesLabel": "Total Messages",
"displayNameTooltip": "Please enter a display name",
"fieldDescriptionLabel": "Description",
"groupsOnThisServerLabel": "Groups I am in hosted on this server",
"importLocalServerSelectText": "Select Local Server",
"importLocalServerLabel": "Import a locally hosted server",
"copyServerKeys": "Copy keys",
"verfiyResumeButton": "Verify\/resume",
"fileCheckingStatus": "Checking download status",
"fileInterrupted": "Interrupted",
"fileSavedTo": "Saved to",
"encryptedServerDescription": "Encrypting a server with a password protects it from other people who may also use this device. Encrypted servers cannot be decrypted, displayed or accessed until the correct password is entered to unlock them.",
"plainServerDescription": "We recommend that you protect your Cwtch servers with a password. If you do not set a password on this server then anyone who has access to this device may be able to access information about this server, including sensitive cryptographic keys.",
"deleteServerConfirmBtn": "Really delete server",
"enterCurrentPasswordForDeleteServer": "Please enter current password to delete this server",
"settingServersDescription": "The hosting servers experiment enables hosting and managing Cwtch servers",
"enterServerPassword": "Enter password to unlock server",
"unlockProfileTip": "Please create or unlock a profile to begin!",
"unlockServerTip": "Please create or unlock a server to begin!",
"saveServerButton": "Save Server",
"serverAutostartDescription": "Controls if the application will automatically launch the server on start",
"serverEnabledDescription": "Start or stop the server",
"serverEnabled": "Server Enabled",
"serverDescriptionDescription": "Your description of the server for personal management use only, will never be shared",
"serverDescriptionLabel": "Server Description",
"editServerTitle": "Edit Server",
"titleManageProfilesShort": "Profiles",
"descriptionFileSharing": "The file sharing experiment allows you to send and receive files from Cwtch contacts and groups. Note that sharing a file with a group will result in members of that group connecting with you directly over Cwtch to download it.",
"settingFileSharing": "File Sharing",
"messageFileOffered": "Contact is offering to send you a file",
"messageEnableFileSharing": "Enable the file sharing experiment to view this message.",
"labelFilename": "Filename",
"openFolderButton": "Open Folder",
"retrievingManifestMessage": "Retrieving file information...",
"descriptionStreamerMode": "If turned on, this option makes the app more visually private for streaming or presenting with, for example, hiding profile and contact addresses",
"streamerModeLabel": "Streamer\/Presentation Mode",
"archiveConversation": "Archive this Conversation",
"blockUnknownConnectionsEnabledDescription": "Connections from unknown contacts are blocked. You can change this in Settings",
"showMessageButton": "Show Message",
"blockedMessageMessage": "This message is from a profile you have blocked.",
"placeholderEnterMessage": "Type a message...",
"plainProfileDescription": "We recommend that you protect your Cwtch profiles with a password. If you do not set a password on this profile then anyone who has access to this device may be able to access information about this profile, including contacts, messages and sensitive cryptographic keys.",
"encryptedProfileDescription": "Encrypting a profile with a password protects it from other people who may also use this device. Encrypted profiles cannot be decrypted, displayed or accessed until the correct password is entered to unlock them.",
"contactGoto": "Go to conversation with %1",
"settingUIColumnOptionSame": "Same as portrait mode setting",
"settingUIColumnDouble14Ratio": "Double (1:4)",
"settingUIColumnDouble12Ratio": "Double (1:2)",
"settingUIColumnSingle": "Single",
"settingUIColumnLandscape": "UI Columns in Landscape Mode",
"settingUIColumnPortrait": "UI Columns in Portrait Mode",
"tooltipRemoveThisQuotedMessage": "Remove quoted message.",
"tooltipReplyToThisMessage": "Reply to this message",
"tooltipRejectContactRequest": "Reject this contact request",
"tooltipAcceptContactRequest": "Accept this contact request.",
"notificationNewMessageFromGroup": "New message in a group!",
"notificationNewMessageFromPeer": "New message from a contact!",
"tooltipHidePassword": "Hide Password",
"tooltipShowPassword": "Show Password",
"groupInviteSettingsWarning": "You have been invited to join a group! Please enable the Group Chat Experiment in Settings to view this Invitation.",
"shutdownCwtchAction": "Shutdown Cwtch",
"shutdownCwtchDialog": "Are you sure you want to shutdown Cwtch? This will close all connections, and exit the application.",
"shutdownCwtchDialogTitle": "Shutdown Cwtch?",
"shutdownCwtchTooltip": "Shutdown Cwtch",
"malformedMessage": "Malformed message",
"profileDeleteSuccess": "Successfully deleted profile",
"debugLog": "Turn on console debug logging",
"torNetworkStatus": "Tor network status",
"addContactFirst": "Add or pick a contact to begin chatting.",
"createProfileToBegin": "Please create or unlock a profile to begin",
"nickChangeSuccess": "Profile nickname changed successfully",
"addServerFirst": "You need to add a server before you can create a group",
"deleteProfileSuccess": "Successfully deleted profile",
"sendInvite": "Send a contact or group invite",
"sendMessage": "Send Message",
"cancel": "Cancel",
"resetTor": "Reset",
"torStatus": "Tor Status",
"torVersion": "Tor Version",
"sendAnInvitation": "You sent an invitation for: ",
"contactSuggestion": "This is a contact suggestion for: ",
"rejected": "Rejected!",
"accepted": "Accepted!",
"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.",
"newPassword": "New Password",
"yesLeave": "Yes, Leave This Conversation",
"reallyLeaveThisGroupPrompt": "Are you sure you want to leave this conversation? All messages and attributes will be deleted.",
"leaveConversation": "Leave This Conversation",
"inviteToGroup": "You have been invited to join a group:",
"titleManageServers": "Manage Servers",
"successfullAddedContact": "Successfully added ",
"descriptionBlockUnknownConnections": "If turned on, this option will automatically close connections from Cwtch users that have not been added to your contact list.",
"descriptionExperimentsGroups": "The group experiment allows Cwtch to connect with untrusted server infrastructure to facilitate communication with more than one contact.",
"descriptionExperiments": "Cwtch experiments are optional, opt-in features that add additional functionality to Cwtch that may have different privacy considerations than traditional 1:1 metadata resistant chat e.g. group chat, bot integration etc.",
"titleManageProfiles": "Manage Cwtch Profiles",
"tooltipUnlockProfiles": "Unlock encrypted profiles by entering their password.",
"titleManageContacts": "Conversations",
"tooltipAddContact": "Add a new contact or conversation",
"tooltipOpenSettings": "Open the settings pane",
"contactAlreadyExists": "Contact Already Exists",
"invalidImportString": "Invalid import string",
"conversationSettings": "Conversation Settings",
"enterCurrentPasswordForDelete": "Please enter current password to delete this profile.",
"enableGroups": "Enable Group Chat",
"localeIt": "Italian \/ Italiano",
"localeEs": "Spanish \/ Español",
"todoPlaceholder": "Todo...",
"addNewItem": "Add a new item to the list",
"addListItem": "Add a New List Item",
"newConnectionPaneTitle": "New Connection",
"networkStatusOnline": "Online",
"networkStatusConnecting": "Connecting to network and contacts...",
"networkStatusAttemptingTor": "Attempting to connect to Tor network",
"networkStatusDisconnected": "Disconnected from the internet, check your connection",
"viewGroupMembershipTooltip": "View Group Membership",
"loadingTor": "Loading tor...",
"smallTextLabel": "Small",
"defaultScalingText": "Default size text (scale factor:",
"builddate": "Built on: %2",
"version": "Version %1",
"versionTor": "Version %1 with tor %2",
"experimentsEnabled": "Enable Experiments",
"themeDark": "Dark",
"themeLight": "Light",
"settingTheme": "Use Light Themes",
"largeTextLabel": "Large",
"settingInterfaceZoom": "Zoom level",
"localeDe": "German \/ Deutsch",
"localePt": "Portuguese \/ Portuguesa",
"localeFr": "French \/ Français",
"localeEn": "English \/ English",
"settingLanguage": "Language",
"blockUnknownLabel": "Block Unknown Contacts",
"zoomLabel": "Interface zoom (mostly affects text and button sizes)",
"versionBuilddate": "Version: %1 Built on: %2",
"cwtchSettingsTitle": "Cwtch Settings",
"unlock": "Unlock",
"yourServers": "Your Servers",
"yourProfiles": "Your Profiles",
"error0ProfilesLoadedForPassword": "0 profiles loaded with that password",
"password": "Password",
"enterProfilePassword": "Enter a password to view your profiles",
"addNewProfileBtn": "Add new profile",
"deleteConfirmText": "DELETE",
"deleteProfileConfirmBtn": "Really Delete Profile",
"deleteConfirmLabel": "Type DELETE to confirm",
"deleteProfileBtn": "Delete Profile",
"passwordChangeError": "Error changing password: Supplied password rejected",
"passwordErrorMatch": "Passwords do not match",
"saveProfileBtn": "Save Profile",
"createProfileBtn": "Create Profile",
"passwordErrorEmpty": "Password cannot be empty",
"password2Label": "Reenter password",
"password1Label": "Password",
"currentPasswordLabel": "Current Password",
"yourDisplayName": "Your Display Name",
"profileOnionLabel": "Send this address to contacts you want to connect with",
"noPasswordWarning": "Not using a password on this account means that all data stored locally will not be encrypted",
"radioNoPassword": "Unencrypted (No password)",
"radioUsePassword": "Password",
"editProfile": "Edit Profile",
"newProfile": "New Profile",
"defaultProfileName": "Alice",
"profileName": "Display name",
"editProfileTitle": "Edit Profile",
"addProfileTitle": "Add new profile",
"deleteBtn": "Delete",
"unblockBtn": "Unblock Contact",
"dontSavePeerHistory": "Delete History",
"savePeerHistoryDescription": "Determines whether to delete any history associated with the contact.",
"savePeerHistory": "Save History",
"blockBtn": "Block Contact",
"saveBtn": "Save",
"displayNameLabel": "Display Name",
"copiedToClipboardNotification": "Copied to Clipboard",
"addressLabel": "Address",
"puzzleGameBtn": "Puzzle Game",
"bulletinsBtn": "Bulletins",
"listsBtn": "Lists",
"chatBtn": "Chat",
"rejectGroupBtn": "Reject",
"acceptGroupBtn": "Accept",
"acceptGroupInviteLabel": "Do you want to accept the invitation to",
"newGroupBtn": "Create new group",
"copyBtn": "Copy",
"peerOfflineMessage": "Contact is offline, messages can't be delivered right now",
"peerBlockedMessage": "Contact is blocked",
"pendingLabel": "Pending",
"acknowledgedLabel": "Acknowledged",
"couldNotSendMsgError": "Could not send this message",
"dmTooltip": "Click to DM",
"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.",
"addListItemBtn": "Add Item",
"peerNotOnline": "Contact is offline. Applications cannot be used right now.",
"searchList": "Search List",
"update": "Update",
"inviteBtn": "Invite",
"inviteToGroupLabel": "Invite to group",
"groupNameLabel": "Group Name",
"viewServerInfo": "Server Info",
"serverNotSynced": "Syncing New Messages (This can take some time)...",
"serverSynced": "Synced",
"serverConnectivityDisconnected": "Server Disconnected",
"serverConnectivityConnected": "Server Connected",
"serverInfo": "Server Information",
"invitationLabel": "Invitation",
"serverLabel": "Server",
"search": "Search...",
"blocked": "Blocked",
"pasteAddressToAddContact": "Paste a cwtch address, invitation or key bundle here to add a new conversation",
"titlePlaceholder": "title...",
"postNewBulletinLabel": "Post new bulletin",
"newBulletinLabel": "New Bulletin",
"joinGroup": "Join group",
"createGroup": "Create group",
"addPeer": "Add Contact",
"groupAddr": "Address",
"invitation": "Invitation",
"server": "Server",
"peerName": "Name",
"peerAddress": "Address",
"joinGroupTab": "Join a group",
"createGroupTab": "Create a group",
"addPeerTab": "Add a contact",
"createGroupBtn": "Create",
"defaultGroupName": "Awesome Group",
"createGroupTitle": "Create Group"
}

View File

@ -1,6 +1,7 @@
{
"@@locale": "lb",
"@@last_modified": "2023-02-07T01:58:40+01:00",
"@@last_modified": "2023-03-14T14:10:01+01:00",
"localeKo": "Korean \/ 한국어",
"localeSk": "Slovak \/ Slovák",
"profileEnabledDescription": "Start or stop the profile",
"profileAutostartDescription": "Controls if the profile will be automatically launched on startup",

View File

@ -1,11 +1,12 @@
{
"@@locale": "nl",
"@@last_modified": "2023-02-07T01:58:40+01:00",
"localeSk": "Slovak \/ Slovák",
"profileEnabledDescription": "Start or stop the profile",
"profileAutostartDescription": "Controls if the profile will be automatically launched on startup",
"profileEnabled": "Enable",
"profileAutostartLabel": "Autostart",
"@@last_modified": "2023-03-14T14:10:01+01:00",
"localeKo": "Korean \/ 한국어",
"profileAutostartDescription": "Regelt of het profiel automatisch wordt gestart bij het opstarten",
"profileAutostartLabel": "Automatisch starten",
"profileEnabled": "Inschakelen",
"profileEnabledDescription": "Start of stop het profiel",
"localeSk": "Slowaaks \/ Slovák",
"localePtBr": "Braziliaans Portugees \/ Português do Brasil",
"acquiredTicketsFromServer": "Anti-spam uitdaging voltooid",
"acquiringTicketsFromServer": "Anti-spam uitdaging uitvoeren",

View File

@ -1,6 +1,7 @@
{
"@@locale": "no",
"@@last_modified": "2023-02-07T01:58:40+01:00",
"@@last_modified": "2023-03-14T14:10:01+01:00",
"localeKo": "Korean \/ 한국어",
"localeSk": "Slovak \/ Slovák",
"profileEnabledDescription": "Start or stop the profile",
"profileAutostartDescription": "Controls if the profile will be automatically launched on startup",

View File

@ -1,6 +1,7 @@
{
"@@locale": "pl",
"@@last_modified": "2023-02-07T01:58:40+01:00",
"@@last_modified": "2023-03-14T14:10:01+01:00",
"localeKo": "Korean \/ 한국어",
"localeSk": "Slovak \/ Slovák",
"profileEnabledDescription": "Start or stop the profile",
"profileAutostartDescription": "Controls if the profile will be automatically launched on startup",

View File

@ -1,6 +1,7 @@
{
"@@locale": "pt",
"@@last_modified": "2023-02-07T01:58:40+01:00",
"@@last_modified": "2023-03-14T14:10:01+01:00",
"localeKo": "Korean \/ 한국어",
"localeSk": "Slovak \/ Slovák",
"profileEnabledDescription": "Start or stop the profile",
"profileAutostartDescription": "Controls if the profile will be automatically launched on startup",

View File

@ -1,6 +1,7 @@
{
"@@locale": "pt_BR",
"@@last_modified": "2023-02-07T01:58:40+01:00",
"@@last_modified": "2023-03-14T14:10:01+01:00",
"localeKo": "Korean \/ 한국어",
"localeSk": "Slovak \/ Slovák",
"profileEnabledDescription": "Start or stop the profile",
"profileAutostartDescription": "Controls if the profile will be automatically launched on startup",

View File

@ -1,6 +1,7 @@
{
"@@locale": "ro",
"@@last_modified": "2023-02-07T01:58:40+01:00",
"@@last_modified": "2023-03-14T14:10:01+01:00",
"localeKo": "Korean \/ 한국어",
"localeSk": "Slovak \/ Slovák",
"profileEnabledDescription": "Start or stop the profile",
"profileAutostartDescription": "Controls if the profile will be automatically launched on startup",

View File

@ -1,6 +1,7 @@
{
"@@locale": "ru",
"@@last_modified": "2023-02-07T01:58:40+01:00",
"@@last_modified": "2023-03-14T14:10:01+01:00",
"localeKo": "Korean \/ 한국어",
"localeSk": "Slovak \/ Slovák",
"profileEnabledDescription": "Start or stop the profile",
"profileAutostartDescription": "Controls if the profile will be automatically launched on startup",

View File

@ -1,6 +1,7 @@
{
"@@locale": "sk",
"@@last_modified": "2023-02-07T01:58:40+01:00",
"@@last_modified": "2023-03-14T14:10:01+01:00",
"localeKo": "Korean \/ 한국어",
"localeSk": "Slovak \/ Slovák",
"deleteBtn": " Vymazať",
"saveBtn": "Uložiť",

View File

@ -1,6 +1,7 @@
{
"@@locale": "tr",
"@@last_modified": "2023-02-07T01:58:40+01:00",
"@@last_modified": "2023-03-14T14:10:01+01:00",
"localeKo": "Korean \/ 한국어",
"localeSk": "Slovak \/ Slovák",
"profileEnabledDescription": "Profili başlat veya durdur",
"profileAutostartLabel": "Otomatik başlatma",

View File

@ -1,5 +1,6 @@
import 'dart:ui';
import 'package:cwtch/config.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/widgets.dart';

9
lib/third_party/base32/LICENSE vendored Normal file
View File

@ -0,0 +1,9 @@
The original version of the base32 code in this library is from https://github.com/Daegalus/dart-base32
Copyright (c) 2012 Yulian Kuncheff
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

196
lib/third_party/base32/base32.dart vendored Normal file
View File

@ -0,0 +1,196 @@
import 'dart:typed_data';
import 'package:cwtch/third_party/base32/encoding.dart';
// ignore: camel_case_types
class base32 {
/// Takes in a [byteList] converts it to a Uint8List so that I can run
/// bit operations on it, then outputs a [String] representation of the
/// base32.
static String encode(Uint8List bytesList, {Encoding encoding = Encoding.standardRFC4648}) {
var base32Chars = EncodingUtils.getChars(encoding);
var i = 0;
var count = (bytesList.length ~/ 5) * 5;
var base32str = '';
while (i < count) {
var v1 = bytesList[i++];
var v2 = bytesList[i++];
var v3 = bytesList[i++];
var v4 = bytesList[i++];
var v5 = bytesList[i++];
base32str += base32Chars[v1 >> 3] +
base32Chars[(v1 << 2 | v2 >> 6) & 31] +
base32Chars[(v2 >> 1) & 31] +
base32Chars[(v2 << 4 | v3 >> 4) & 31] +
base32Chars[(v3 << 1 | v4 >> 7) & 31] +
base32Chars[(v4 >> 2) & 31] +
base32Chars[(v4 << 3 | v5 >> 5) & 31] +
base32Chars[v5 & 31];
}
var remain = bytesList.length - count;
if (remain == 1) {
var v1 = bytesList[i];
base32str += base32Chars[v1 >> 3] + base32Chars[(v1 << 2) & 31];
if (EncodingUtils.getPadded(encoding)) {
base32str += '======';
}
} else if (remain == 2) {
var v1 = bytesList[i++];
var v2 = bytesList[i];
base32str += base32Chars[v1 >> 3] + base32Chars[(v1 << 2 | v2 >> 6) & 31] + base32Chars[(v2 >> 1) & 31] + base32Chars[(v2 << 4) & 31];
if (EncodingUtils.getPadded(encoding)) {
base32str += '====';
}
} else if (remain == 3) {
var v1 = bytesList[i++];
var v2 = bytesList[i++];
var v3 = bytesList[i];
base32str += base32Chars[v1 >> 3] + base32Chars[(v1 << 2 | v2 >> 6) & 31] + base32Chars[(v2 >> 1) & 31] + base32Chars[(v2 << 4 | v3 >> 4) & 31] + base32Chars[(v3 << 1) & 31];
if (EncodingUtils.getPadded(encoding)) {
base32str += '===';
}
} else if (remain == 4) {
var v1 = bytesList[i++];
var v2 = bytesList[i++];
var v3 = bytesList[i++];
var v4 = bytesList[i];
base32str += base32Chars[v1 >> 3] +
base32Chars[(v1 << 2 | v2 >> 6) & 31] +
base32Chars[(v2 >> 1) & 31] +
base32Chars[(v2 << 4 | v3 >> 4) & 31] +
base32Chars[(v3 << 1 | v4 >> 7) & 31] +
base32Chars[(v4 >> 2) & 31] +
base32Chars[(v4 << 3) & 31];
if (EncodingUtils.getPadded(encoding)) {
base32str += '=';
}
}
return base32str;
}
static Uint8List _hexDecode(final String input) => Uint8List.fromList([
for (int i = 0; i < input.length; i += 2) int.parse(input.substring(i, i + 2), radix: 16),
]);
static String _hexEncode(final Uint8List input) => [for (int i = 0; i < input.length; i++) input[i].toRadixString(16).padLeft(2, '0')].join();
/// Takes in a [hex] string, converts the string to a byte list
/// and runs a normal encode() on it. Returning a [String] representation
/// of the base32.
static String encodeHexString(String b32hex, {Encoding encoding = Encoding.standardRFC4648}) {
return encode(_hexDecode(b32hex), encoding: encoding);
}
/// Takes in a [utf8string], converts the string to a byte list
/// and runs a normal encode() on it. Returning a [String] representation
/// of the base32.
static String encodeString(String utf8string, {Encoding encoding = Encoding.standardRFC4648}) {
return encode(Uint8List.fromList(utf8string.codeUnits), encoding: encoding);
}
/// Takes in a [base32] string and decodes it back to a [String] in hex format.
static String decodeAsHexString(String base32, {Encoding encoding = Encoding.standardRFC4648}) {
return _hexEncode(decode(base32, encoding: encoding));
}
/// Takes in a [base32] string and decodes it back to a [String].
static String decodeAsString(String base32, {Encoding encoding = Encoding.standardRFC4648}) {
return decode(base32, encoding: encoding).toList().map((charCode) => String.fromCharCode(charCode)).join();
}
/// Takes in a [base32] string and decodes it back to a [Uint8List] that can be
/// converted to a hex string using hexEncode
static Uint8List decode(String base32, {Encoding encoding = Encoding.standardRFC4648}) {
if (base32.isEmpty) {
return Uint8List(0);
}
base32 = _pad(base32, encoding: encoding);
if (!_isValid(base32, encoding: encoding)) {
throw FormatException('Invalid Base32 characters');
}
if (encoding == Encoding.crockford) {
base32 = base32.replaceAll('-', '');
} // Handle crockford dashes.
var base32Decode = EncodingUtils.getDecodeMap(encoding);
var length = base32.indexOf('=');
if (length == -1) {
length = base32.length;
}
var i = 0;
var count = length >> 3 << 3;
var bytes = <int>[];
while (i < count) {
var v1 = base32Decode[base32[i++]] ?? 0;
var v2 = base32Decode[base32[i++]] ?? 0;
var v3 = base32Decode[base32[i++]] ?? 0;
var v4 = base32Decode[base32[i++]] ?? 0;
var v5 = base32Decode[base32[i++]] ?? 0;
var v6 = base32Decode[base32[i++]] ?? 0;
var v7 = base32Decode[base32[i++]] ?? 0;
var v8 = base32Decode[base32[i++]] ?? 0;
bytes.add((v1 << 3 | v2 >> 2) & 255);
bytes.add((v2 << 6 | v3 << 1 | v4 >> 4) & 255);
bytes.add((v4 << 4 | v5 >> 1) & 255);
bytes.add((v5 << 7 | v6 << 2 | v7 >> 3) & 255);
bytes.add((v7 << 5 | v8) & 255);
}
var remain = length - count;
if (remain == 2) {
var v1 = base32Decode[base32[i++]] ?? 0;
var v2 = base32Decode[base32[i++]] ?? 0;
bytes.add((v1 << 3 | v2 >> 2) & 255);
} else if (remain == 4) {
var v1 = base32Decode[base32[i++]] ?? 0;
var v2 = base32Decode[base32[i++]] ?? 0;
var v3 = base32Decode[base32[i++]] ?? 0;
var v4 = base32Decode[base32[i++]] ?? 0;
bytes.add((v1 << 3 | v2 >> 2) & 255);
bytes.add((v2 << 6 | v3 << 1 | v4 >> 4) & 255);
} else if (remain == 5) {
var v1 = base32Decode[base32[i++]] ?? 0;
var v2 = base32Decode[base32[i++]] ?? 0;
var v3 = base32Decode[base32[i++]] ?? 0;
var v4 = base32Decode[base32[i++]] ?? 0;
var v5 = base32Decode[base32[i++]] ?? 0;
bytes.add((v1 << 3 | v2 >> 2) & 255);
bytes.add((v2 << 6 | v3 << 1 | v4 >> 4) & 255);
bytes.add((v4 << 4 | v5 >> 1) & 255);
} else if (remain == 7) {
var v1 = base32Decode[base32[i++]] ?? 0;
var v2 = base32Decode[base32[i++]] ?? 0;
var v3 = base32Decode[base32[i++]] ?? 0;
var v4 = base32Decode[base32[i++]] ?? 0;
var v5 = base32Decode[base32[i++]] ?? 0;
var v6 = base32Decode[base32[i++]] ?? 0;
var v7 = base32Decode[base32[i++]] ?? 0;
bytes.add((v1 << 3 | v2 >> 2) & 255);
bytes.add((v2 << 6 | v3 << 1 | v4 >> 4) & 255);
bytes.add((v4 << 4 | v5 >> 1) & 255);
bytes.add((v5 << 7 | v6 << 2 | v7 >> 3) & 255);
}
return Uint8List.fromList(bytes);
}
static bool _isValid(String b32str, {Encoding encoding = Encoding.standardRFC4648}) {
var regex = EncodingUtils.getRegex(encoding);
if (b32str.length % 2 != 0 || !regex.hasMatch(b32str)) {
return false;
}
return true;
}
static String _pad(String base32, {Encoding encoding = Encoding.standardRFC4648}) {
if (EncodingUtils.getPadded(encoding)) {
int neededPadding = (8 - base32.length % 8) % 8;
return base32.padRight(base32.length + neededPadding, '=');
}
return base32;
}
}

52
lib/third_party/base32/encoding.dart vendored Normal file
View File

@ -0,0 +1,52 @@
class EncodingUtils {
static String getChars(Encoding encoding) {
return _encodeMap[encoding]!;
}
static Map<String, int> getDecodeMap(Encoding encoding) {
var map = _decodeMap[encoding];
if (map != null) {
return map;
} else {
var chars = _encodeMap[encoding]!;
// ignore: omit_local_variable_types
Map<String, int> map = {};
for (var i = 0; i < 32; i++) {
map[chars[i]] = i;
}
_decodeMap[encoding] = map;
return map;
}
}
static RegExp getRegex(Encoding encoding) {
return _regexMap[encoding]!;
}
static bool getPadded(Encoding encoding) {
return _padded[encoding]!;
}
static final _regexMap = {
Encoding.standardRFC4648: RegExp(r'^[A-Z2-7=]+$'),
Encoding.nonStandardRFC4648Lower: RegExp(r'^[a-z2-7=]+$'),
Encoding.base32Hex: RegExp(r'^[0-9A-V=]+$'),
Encoding.crockford: RegExp(r'^[0123456789ABCDEFGHJKMNPQRSTVWXYZ-]+$'),
Encoding.zbase32: RegExp(r'^[ybndrfg8ejkmcpqxot1uwisza345h769]+$'),
Encoding.geohash: RegExp(r'^[0123456789bcdefghjkmnpqrstuvwxyz=]+$')
};
static final _encodeMap = {
Encoding.standardRFC4648: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567',
Encoding.nonStandardRFC4648Lower: 'abcdefghijklmnopqrstuvwxyz234567',
Encoding.base32Hex: '0123456789ABCDEFGHIJKLMNOPQRSTUV',
Encoding.crockford: '0123456789ABCDEFGHJKMNPQRSTVWXYZ',
Encoding.zbase32: 'ybndrfg8ejkmcpqxot1uwisza345h769',
Encoding.geohash: '0123456789bcdefghjkmnpqrstuvwxyz'
};
static final Map<Encoding, Map<String, int>> _decodeMap = {};
static final _padded = {Encoding.standardRFC4648: true, Encoding.nonStandardRFC4648Lower: true, Encoding.base32Hex: true, Encoding.crockford: false, Encoding.zbase32: false, Encoding.geohash: true};
}
enum Encoding { standardRFC4648, base32Hex, crockford, zbase32, geohash, nonStandardRFC4648Lower }

View File

@ -307,12 +307,13 @@ class _ContactsViewState extends State<ContactsView> {
}
void _pushServers() {
var profile = Provider.of<ProfileInfoState>(context);
var profileInfoState = Provider.of<ProfileInfoState>(context, listen: false);
Navigator.of(context).push(
PageRouteBuilder(
pageBuilder: (bcontext, a1, a2) {
return MultiProvider(
providers: [ChangeNotifierProvider(create: (context) => profile), Provider.value(value: Provider.of<FlwtchState>(context))],
providers: [ChangeNotifierProvider.value(value: profileInfoState), Provider.value(value: Provider.of<FlwtchState>(context))],
child: ProfileServersView(),
);
},

View File

@ -377,22 +377,27 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
Visibility(
visible: !Platform.isAndroid && !Platform.isIOS,
child: SwitchListTile(
title: Text(AppLocalizations.of(context)!.settingServers, style: TextStyle(color: settings.current().mainTextColor)),
subtitle: Text(AppLocalizations.of(context)!.settingServersDescription),
value: settings.isExperimentEnabled(ServerManagementExperiment),
onChanged: (bool value) {
Provider.of<ServerListState>(context, listen: false).clear();
if (value) {
settings.enableExperiment(ServerManagementExperiment);
} else {
settings.disableExperiment(ServerManagementExperiment);
}
// Save Settings...
saveSettings(context);
},
title: Text(AppLocalizations.of(context)!.settingServers),
subtitle: Provider.of<FlwtchState>(context, listen: false).cwtch.IsServersCompiled()
? Text(AppLocalizations.of(context)!.settingServersDescription)
: Text("This version of Cwtch has been compiled without support for the server hosting experiment."),
value: Provider.of<FlwtchState>(context, listen: false).cwtch.IsServersCompiled() && settings.isExperimentEnabled(ServerManagementExperiment),
onChanged: Provider.of<FlwtchState>(context, listen: false).cwtch.IsServersCompiled()
? (bool value) {
Provider.of<ServerListState>(context, listen: false).clear();
if (value) {
settings.enableExperiment(ServerManagementExperiment);
} else {
settings.disableExperiment(ServerManagementExperiment);
}
// Save Settings...
saveSettings(context);
}
: null,
activeTrackColor: settings.theme.defaultButtonColor,
inactiveTrackColor: settings.theme.defaultButtonDisabledColor,
secondary: Icon(CwtchIcons.dns_24px, color: settings.current().mainTextColor),
inactiveThumbColor: settings.theme.defaultButtonDisabledColor,
secondary: Icon(CwtchIcons.dns_24px),
)),
SwitchListTile(
title: Text(AppLocalizations.of(context)!.settingFileSharing, style: TextStyle(color: settings.current().mainTextColor)),
@ -661,6 +666,9 @@ String getLanguageFull(context, String languageCode, String? countryCode) {
if (languageCode == "sk") {
return AppLocalizations.of(context)!.localeSk;
}
if (languageCode == "ko") {
return AppLocalizations.of(context)!.localeKo;
}
return languageCode;
}
@ -694,10 +702,5 @@ String getThemeName(context, String theme) {
/// Send an UpdateGlobalSettings to the Event Bus
saveSettings(context) {
var settings = Provider.of<Settings>(context, listen: false);
final updateSettingsEvent = {
"EventType": "UpdateGlobalSettings",
"Data": {"Data": jsonEncode(settings.asJson())},
};
final updateSettingsEventJson = jsonEncode(updateSettingsEvent);
Provider.of<FlwtchState>(context, listen: false).cwtch.SendAppEvent(updateSettingsEventJson);
Provider.of<FlwtchState>(context, listen: false).cwtch.UpdateSettings(jsonEncode(settings.asJson()));
}

View File

@ -299,10 +299,6 @@ class _MessageViewState extends State<MessageView> {
static const GroupMessageLengthMax = 1600;
void _sendMessage([String? ignoredParam]) {
// Trim message
final messageWithoutNewLine = ctrlrCompose.value.text.trimRight();
ctrlrCompose.value = TextEditingValue(text: messageWithoutNewLine, selection: TextSelection.fromPosition(TextPosition(offset: messageWithoutNewLine.length)));
// Do this after we trim to preserve enter-behaviour...
bool isOffline = Provider.of<ContactInfoState>(context, listen: false).isOnline() == false;
bool performingAntiSpam = Provider.of<ContactInfoState>(context, listen: false).antispamTickets == 0;
@ -311,6 +307,10 @@ class _MessageViewState extends State<MessageView> {
return;
}
// Trim message
final messageWithoutNewLine = ctrlrCompose.value.text.trimRight();
ctrlrCompose.value = TextEditingValue(text: messageWithoutNewLine, selection: TextSelection.fromPosition(TextPosition(offset: messageWithoutNewLine.length)));
// peers and groups currently have different length constraints (servers can store less)...
var actualMessageLength = ctrlrCompose.value.text.length;
var lengthOk = (isGroup && actualMessageLength < GroupMessageLengthMax) || actualMessageLength <= P2PMessageLengthMax;
@ -368,6 +368,11 @@ class _MessageViewState extends State<MessageView> {
return;
}
// At this point we have decided to send the text to the backend, failure is still possible
// but it will show as an error-ed message, as such the draft can be purged.
Provider.of<ContactInfoState>(context, listen: false).messageDraft = null;
ctrlrCompose.clear();
var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
var identifier = Provider.of<ContactInfoState>(context, listen: false).identifier;
var profile = Provider.of<ProfileInfoState>(context, listen: false);
@ -388,11 +393,8 @@ class _MessageViewState extends State<MessageView> {
);
}
Provider.of<ContactInfoState>(context, listen: false).messageDraft = null;
ctrlrCompose.clear();
focusNode.requestFocus();
Provider.of<FlwtchState>(context, listen: false).cwtch.SetConversationAttribute(profileOnion, identifier, LastMessageSeenTimeKey, DateTime.now().toIso8601String());
focusNode.requestFocus();
}
Widget _buildPreviewBox() {

View File

@ -106,7 +106,10 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
));
// Servers
if (Provider.of<Settings>(context).isExperimentEnabled(ServerManagementExperiment) && !Platform.isAndroid && !Platform.isIOS) {
if (Provider.of<FlwtchState>(context, listen: false).cwtch.IsServersCompiled() &&
Provider.of<Settings>(context).isExperimentEnabled(ServerManagementExperiment) &&
!Platform.isAndroid &&
!Platform.isIOS) {
actions.add(
IconButton(icon: Icon(CwtchIcons.dns_black_24dp), splashRadius: Material.defaultSplashRadius / 2, tooltip: AppLocalizations.of(context)!.serversManagerTitleShort, onPressed: _pushServers));
}
@ -150,7 +153,7 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
settings: RouteSettings(name: "servers"),
pageBuilder: (bcontext, a1, a2) {
return MultiProvider(
providers: [Provider.value(value: Provider.of<FlwtchState>(context))],
providers: [ChangeNotifierProvider.value(value: globalServersList), Provider.value(value: Provider.of<FlwtchState>(context))],
child: ServersView(),
);
},
@ -226,7 +229,7 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
shape: RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))),
),
child: Text(
key:Key("addNewProfileActual"),
key: Key("addNewProfileActual"),
AppLocalizations.of(context)!.addProfileTitle,
semanticsLabel: AppLocalizations.of(context)!.addProfileTitle,
style: TextStyle(fontWeight: FontWeight.bold),

View File

@ -37,10 +37,6 @@ class _RemoteServerViewState extends State<RemoteServerView> {
@override
void initState() {
super.initState();
var serverInfoState = Provider.of<RemoteServerInfoState>(context, listen: false);
if (serverInfoState.description.isNotEmpty) {
ctrlrDesc.text = serverInfoState.description;
}
}
@override
@ -50,6 +46,11 @@ class _RemoteServerViewState extends State<RemoteServerView> {
@override
Widget build(BuildContext context) {
var serverInfoState = Provider.of<RemoteServerInfoState>(context, listen: false);
if (serverInfoState.description.isNotEmpty) {
ctrlrDesc.text = serverInfoState.description;
}
return Consumer3<ProfileInfoState, RemoteServerInfoState, Settings>(builder: (context, profile, serverInfoState, settings, child) {
return Scaffold(
appBar: AppBar(title: Text(ctrlrDesc.text.isNotEmpty ? ctrlrDesc.text : serverInfoState.onion)),

View File

@ -6,6 +6,7 @@ import 'package:cwtch/models/appstate.dart';
import 'package:cwtch/models/contact.dart';
import 'package:cwtch/models/message.dart';
import 'package:cwtch/models/profile.dart';
import 'package:cwtch/third_party/base32/base32.dart';
import 'package:cwtch/views/contactsview.dart';
import 'package:cwtch/widgets/staticmessagebubble.dart';
import 'package:flutter/material.dart';
@ -180,6 +181,8 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
String imagePath = Provider.of<MessageMetadata>(context).senderImage!;
if (sender != null) {
imagePath = Provider.of<Settings>(context).isExperimentEnabled(ImagePreviewsExperiment) ? sender.imagePath : sender.defaultImagePath;
} else {
imagePath = RandomProfileImage(Provider.of<MessageMetadata>(context).senderHandle);
}
Widget wdgPortrait = GestureDetector(
onTap: !isGroup
@ -388,6 +391,8 @@ void modalShowReplies(
var sender = profile.contactList.findContact(e.getMetadata().senderHandle);
if (sender != null) {
imagePath = showImage ? sender.imagePath : sender.defaultImagePath;
} else {
imagePath = RandomProfileImage(e.getMetadata().senderHandle);
}
if (fromMe) {
@ -453,3 +458,61 @@ void modalShowReplies(
});
});
}
// temporary until we do real picture selection
String RandomProfileImage(String onion) {
var choices = [
"001-centaur",
"002-kraken",
"003-dinosaur",
"004-tree-1",
"005-hand",
"006-echidna",
"007-robot",
"008-mushroom",
"009-harpy",
"010-phoenix",
"011-dragon-1",
"012-devil",
"013-troll",
"014-alien",
"015-minotaur",
"016-madre-monte",
"017-satyr",
"018-karakasakozou",
"019-pirate",
"020-werewolf",
"021-scarecrow",
"022-valkyrie",
"023-curupira",
"024-loch-ness-monster",
"025-tree",
"026-cerberus",
"027-gryphon",
"028-mermaid",
"029-vampire",
"030-goblin",
"031-yeti",
"032-leprechaun",
"033-medusa",
"034-chimera",
"035-elf",
"036-hydra",
"037-cyclops",
"038-pegasus",
"039-narwhal",
"040-woodcutter",
"041-zombie",
"042-dragon",
"043-frankenstein",
"044-witch",
"045-fairy",
"046-genie",
"047-pinocchio",
"048-ghost",
"049-wizard",
"050-unicorn"
];
var encoding = base32.decode(onion.toUpperCase());
return "assets/profiles/" + choices[encoding[33] % choices.length] + ".png";
}

View File

@ -81,7 +81,7 @@ class _RemoteServerRowState extends State<RemoteServerRow> {
settings: RouteSettings(name: "remoteserverview"),
pageBuilder: (bcontext, a1, a2) {
return MultiProvider(
providers: [Provider.value(value: profile), ChangeNotifierProvider(create: (context) => server), Provider.value(value: Provider.of<FlwtchState>(context))],
providers: [ChangeNotifierProvider.value(value: profile), ChangeNotifierProvider.value(value: server), Provider.value(value: Provider.of<FlwtchState>(context))],
child: RemoteServerView(),
);
},

View File

@ -91,16 +91,12 @@ class _ServerRowState extends State<ServerRow> {
}
void _pushEditServer(ServerInfoState server) {
Provider.of<ErrorHandler>(context).reset();
Provider.of<ErrorHandler>(context, listen: false).reset();
Navigator.of(context).push(MaterialPageRoute<void>(
settings: RouteSettings(name: "serveraddedit"),
builder: (BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<ServerInfoState>(
create: (_) => server,
)
],
providers: [ChangeNotifierProvider.value(value: server)],
child: AddEditServerView(),
);
},