Merge pull request 'Expose antispam status in UI' (#534) from antispam into trunk
continuous-integration/drone/push Build is failing Details

Reviewed-on: #534
This commit is contained in:
Dan Ballard 2022-09-09 19:33:44 +00:00
commit e570f6941b
30 changed files with 265 additions and 128 deletions

View File

@ -121,7 +121,7 @@ abstract class Cwtch {
Future<void> Shutdown();
// non-ffi
String defaultDownloadPath();
String? defaultDownloadPath();
bool isL10nInit();

View File

@ -309,6 +309,14 @@ class CwtchNotifier {
case "UpdateServerInfo":
profileCN.getProfile(data["ProfileOnion"])?.replaceServers(data["ServerList"]);
break;
case "TokenManagerInfo":
List<dynamic> associatedGroups = jsonDecode(data["Data"]);
int count = int.parse(data["ServerTokenCount"]);
associatedGroups.forEach((identifier) {
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(int.parse(identifier.toString()))!.antispamTickets = count;
});
EnvironmentConfig.debugLog("update server token count for ${associatedGroups}, $count");
break;
case "NewGroup":
String invite = data["GroupInvite"].toString();
if (invite.startsWith("torv3")) {

View File

@ -762,9 +762,13 @@ class CwtchFfi implements Cwtch {
}
@override
String defaultDownloadPath() {
String? defaultDownloadPath() {
Map<String, String> envVars = Platform.environment;
return path.join(envVars[Platform.isWindows ? 'UserProfile' : 'HOME']!, "Downloads");
String nominalPath = path.join(envVars[Platform.isWindows ? 'UserProfile' : 'HOME']!, "Downloads");
if (Directory(nominalPath).existsSync() == false) {
return null;
}
return nominalPath;
}
@override

View File

@ -294,7 +294,7 @@ class CwtchGomobile implements Cwtch {
}
@override
String defaultDownloadPath() {
String? defaultDownloadPath() {
return this.androidHomeDirectoryStr;
}

View File

@ -618,4 +618,8 @@ class MaterialLocalizationLu extends MaterialLocalizations {
// TODO: implement timeOfDayFormat
throw UnimplementedError();
}
@override
// TODO: implement menuBarMenuLabel
String get menuBarMenuLabel => throw UnimplementedError();
}

View File

@ -1,6 +1,9 @@
{
"@@locale": "cy",
"@@last_modified": "2022-07-30T00:18:33+02:00",
"@@last_modified": "2022-09-06T20:59:50+02:00",
"acquiredTicketsFromServer": "Antispam Challenge Complete",
"acquiringTicketsFromServer": "Performing Antispam Challenge",
"errorDownloadDirectoryDoesNotExist": "Filesharing cannot be enabled because the Download Folder has not been set, or is set to a folder that does not exist.",
"localeTr": "Tyrceg \/ Türk",
"localeIt": "Eidaleg \/ Italiana",
"tooltipUnpinConversation": "Unpin conversation from the top of \"Conversations\"",

View File

@ -1,6 +1,9 @@
{
"@@locale": "da",
"@@last_modified": "2022-07-30T00:18:33+02:00",
"@@last_modified": "2022-09-06T20:59:50+02:00",
"acquiredTicketsFromServer": "Antispam Challenge Complete",
"acquiringTicketsFromServer": "Performing Antispam Challenge",
"errorDownloadDirectoryDoesNotExist": "Filesharing cannot be enabled because the Download Folder has not been set, or is set to a folder that does not exist.",
"localeTr": "Tyrkisk \/ Türk",
"localeIt": "Italiensk \/ Italiano",
"tooltipUnpinConversation": "Unpin conversation from the top of \"Conversations\"",

View File

@ -1,8 +1,11 @@
{
"@@locale": "de",
"@@last_modified": "2022-07-30T00:18:33+02:00",
"localeTr": "Türkisch \/ Türk",
"@@last_modified": "2022-09-06T20:59:50+02:00",
"acquiredTicketsFromServer": "Antispam Challenge Complete",
"acquiringTicketsFromServer": "Performing Antispam Challenge",
"localeIt": "Italienisch \/ Italiano",
"errorDownloadDirectoryDoesNotExist": "Die Dateifreigabe kann nicht aktiviert werden, da der Download-Ordner nicht festgelegt wurde oder auf einen nicht vorhandenen Ordner festgelegt ist.",
"localeTr": "Türkisch \/ Türk",
"viewReplies": "Antworten auf diese Nachricht anzeigen",
"manageSharedFiles": "Freigegebene Dateien verwalten",
"tooltipPinConversation": "Konversation oben in \"Konversationen\" anheften",

View File

@ -1,6 +1,9 @@
{
"@@locale": "el",
"@@last_modified": "2022-07-30T00:18:33+02:00",
"@@last_modified": "2022-09-06T20:59:50+02:00",
"acquiredTicketsFromServer": "Antispam Challenge Complete",
"acquiringTicketsFromServer": "Performing Antispam Challenge",
"errorDownloadDirectoryDoesNotExist": "Filesharing cannot be enabled because the Download Folder has not been set, or is set to a folder that does not exist.",
"localeTr": "Τουρκικά \/ Türk",
"localeIt": "Italian \/ Italiano",
"localeCy": "Ουαλικά \/ Cymraeg",

View File

@ -1,6 +1,9 @@
{
"@@locale": "en",
"@@last_modified": "2022-07-30T00:18:33+02:00",
"@@last_modified": "2022-09-06T20:59:50+02:00",
"acquiredTicketsFromServer": "Antispam Challenge Complete",
"acquiringTicketsFromServer": "Performing Antispam Challenge",
"errorDownloadDirectoryDoesNotExist": "Filesharing cannot be enabled because the Download Folder has not been set, or is set to a folder that does not exist.",
"localeIt": "Italian \/ Italiano",
"localeTr": "Turkish \/ Türk",
"tooltipUnpinConversation": "Unpin conversation from the top of \"Conversations\"",

View File

@ -1,6 +1,9 @@
{
"@@locale": "es",
"@@last_modified": "2022-07-30T00:18:33+02:00",
"@@last_modified": "2022-09-06T20:59:50+02:00",
"acquiredTicketsFromServer": "Antispam Challenge Complete",
"acquiringTicketsFromServer": "Performing Antispam Challenge",
"errorDownloadDirectoryDoesNotExist": "Filesharing cannot be enabled because the Download Folder has not been set, or is set to a folder that does not exist.",
"localeTr": "Turco \/ Türk",
"localeIt": "Italiano \/ Italiano",
"tooltipUnpinConversation": "Unpin conversation from the top of \"Conversations\"",

View File

@ -1,8 +1,11 @@
{
"@@locale": "fr",
"@@last_modified": "2022-07-30T00:18:33+02:00",
"@@last_modified": "2022-09-06T20:59:50+02:00",
"acquiredTicketsFromServer": "Antispam Challenge Complete",
"acquiringTicketsFromServer": "Performing Antispam Challenge",
"errorDownloadDirectoryDoesNotExist": "Le partage de fichiers ne peut pas être activé car le dossier de téléchargement n'a pas été défini ou est défini sur un dossier qui n'existe pas.",
"localeIt": "italien \/ italien",
"localeTr": "Turc \/ Türk",
"localeIt": "Italien \/ Italiano",
"tooltipPinConversation": "Épingler la conversation en haut de «Conversations»",
"tooltipUnpinConversation": "Détacher la conversation du haut de «Conversations»",
"viewReplies": "Voir les réponses à ce message",

View File

@ -1,6 +1,9 @@
{
"@@locale": "it",
"@@last_modified": "2022-07-30T00:18:33+02:00",
"@@last_modified": "2022-09-06T20:59:50+02:00",
"acquiredTicketsFromServer": "Antispam Challenge Complete",
"acquiringTicketsFromServer": "Performing Antispam Challenge",
"errorDownloadDirectoryDoesNotExist": "Filesharing cannot be enabled because the Download Folder has not been set, or is set to a folder that does not exist.",
"localeTr": "Turco \/ Türk",
"localeIt": "Italiano \/ Italiano",
"tooltipUnpinConversation": "Unpin conversation from the top of \"Conversations\"",

View File

@ -1,6 +1,9 @@
{
"@@locale": "lb",
"@@last_modified": "2022-07-30T00:18:33+02:00",
"@@last_modified": "2022-09-06T20:59:50+02:00",
"acquiredTicketsFromServer": "Antispam Challenge Complete",
"acquiringTicketsFromServer": "Performing Antispam Challenge",
"errorDownloadDirectoryDoesNotExist": "Filesharing cannot be enabled because the Download Folder has not been set, or is set to a folder that does not exist.",
"localeTr": "Tierkesch \/ Türk",
"localeIt": "Italienesch",
"localeEn": "Englesch \/ English",

View File

@ -1,6 +1,9 @@
{
"@@locale": "no",
"@@last_modified": "2022-07-30T00:18:33+02:00",
"@@last_modified": "2022-09-06T20:59:50+02:00",
"acquiredTicketsFromServer": "Antispam Challenge Complete",
"acquiringTicketsFromServer": "Performing Antispam Challenge",
"errorDownloadDirectoryDoesNotExist": "Filesharing cannot be enabled because the Download Folder has not been set, or is set to a folder that does not exist.",
"localeTr": "Tyrkisk \/ Türk",
"localeIt": "Italiensk",
"localeEn": "Engelsk \/ English",

View File

@ -1,6 +1,9 @@
{
"@@locale": "pl",
"@@last_modified": "2022-07-30T00:18:33+02:00",
"@@last_modified": "2022-09-06T20:59:50+02:00",
"acquiredTicketsFromServer": "Antispam Challenge Complete",
"acquiringTicketsFromServer": "Performing Antispam Challenge",
"errorDownloadDirectoryDoesNotExist": "Filesharing cannot be enabled because the Download Folder has not been set, or is set to a folder that does not exist.",
"localeTr": "Turecki \/ Türk",
"localeIt": "Włoski \/ Italiano",
"localeEn": "Angielski \/ English",

View File

@ -1,6 +1,9 @@
{
"@@locale": "pt",
"@@last_modified": "2022-07-30T00:18:33+02:00",
"@@last_modified": "2022-09-06T20:59:50+02:00",
"acquiredTicketsFromServer": "Antispam Challenge Complete",
"acquiringTicketsFromServer": "Performing Antispam Challenge",
"errorDownloadDirectoryDoesNotExist": "Filesharing cannot be enabled because the Download Folder has not been set, or is set to a folder that does not exist.",
"localeTr": "Turco \/ Türk",
"localeIt": "Italian \/ Italiano",
"tooltipUnpinConversation": "Unpin conversation from the top of \"Conversations\"",

View File

@ -1,6 +1,9 @@
{
"@@locale": "ro",
"@@last_modified": "2022-07-30T00:18:33+02:00",
"@@last_modified": "2022-09-06T20:59:50+02:00",
"acquiredTicketsFromServer": "Antispam Challenge Complete",
"acquiringTicketsFromServer": "Performing Antispam Challenge",
"errorDownloadDirectoryDoesNotExist": "Filesharing cannot be enabled because the Download Folder has not been set, or is set to a folder that does not exist.",
"localeTr": "Turcă \/ Türk",
"localeIt": "Italiană",
"localeEn": "Engleză \/ English",

View File

@ -1,6 +1,9 @@
{
"@@locale": "ru",
"@@last_modified": "2022-07-30T00:18:33+02:00",
"@@last_modified": "2022-09-06T20:59:50+02:00",
"acquiredTicketsFromServer": "Antispam Challenge Complete",
"acquiringTicketsFromServer": "Performing Antispam Challenge",
"errorDownloadDirectoryDoesNotExist": "Filesharing cannot be enabled because the Download Folder has not been set, or is set to a folder that does not exist.",
"localeTr": "Турецкий \/ Türk",
"localeIt": "Итальянский \/ Italiano",
"tooltipUnpinConversation": "Unpin conversation from the top of \"Conversations\"",

View File

@ -1,12 +1,59 @@
{
"@@locale": "tr",
"@@last_modified": "2022-07-30T00:18:33+02:00",
"localeTr": "Türk \/ Türk",
"localeIt": "Italian \/ Italiano",
"@@last_modified": "2022-09-06T20:59:50+02:00",
"acquiredTicketsFromServer": "Antispam Challenge Complete",
"acquiringTicketsFromServer": "Performing Antispam Challenge",
"errorDownloadDirectoryDoesNotExist": "İndirilenler Klasörü ayarlanmadığı veya mevcut olmayan bir klasöre ayarlandığı için dosya paylaşımı etkinleştirilemiyor.",
"radioNoPassword": "Şifrelenmemiş (Parola yok)",
"msgAddToAccept": "Dosyayı kabul etmek için bu hesabı kişilerinize ekleyin.",
"fileSharingSettingsDownloadFolderDescription": "Dosyalar (örn. görsel önizlemeleri etkinleştirildiğinde görsel dosyaları) otomatik olarak indirildiğinde, dosyaların indirileceği varsayılan bir konum gereklidir.",
"torSettingsEnabledCacheDescription": "İndirilmiş Tor uzlaşmasını Cwtch'un bir sonraki açılışında yeniden kullanmak için önbelleğe alın. Bu Tor'un daha hızlıılmasını sağlar. Devre dışı bırakıldığında Cwtch açılırken önbelleğe alınmış verileri siler.",
"notificationContentSimpleEvent": "Yalın Bildiri",
"exportProfileTooltip": "Bu profili şifrelenmiş bir dosyaya yedekle. Şifrelenmiş dosya başka bir Cwtch uygulamasına aktarılabilir.",
"importProfileTooltip": "Başka Cwtch oluşumunda oluşturulmuş bir profili içeri aktarmak için şifrelenmiş Cwtch yedeği kullanın.",
"clickableLinksWarning": "Bu URL'yi açmak Cwtch dışında bir uygulama başlatacak ve metadatanız teşhir olabilir veya Cwtch'un güvenliği tehlikeye girebilir. Yalnızca güvendiğiniz kişilerden gelen URL'leri açın. Devam etmek istediğinize emin misiniz?",
"settingAndroidPowerExemptionDescription": "Opsiyonel: Android'den Cwtch'u optimize edilmiş güç yönetiminden muaf tutmasını isteyin. Bu, daha fazla pil kullanımı pahasına uygulamanın daha stabil çalışmasını sağlayacaktır.",
"localeTr": "Türkçe \/ Türkçe",
"defaultGroupName": "Muhteşem Grup",
"defaultProfileName": "Alice",
"localeDe": "Almanca \/ Deutsch",
"localePl": "Lehçe \/ Polski",
"localeDa": "Danca \/ Dansk",
"acceptGroupInviteLabel": "Daveti kabul etmek istiyor musunuz",
"pendingLabel": "Beklemede",
"chatBtn": "Sohbet",
"yourDisplayName": "Kullanıcı Adınız",
"localeEn": "İngilizce \/ English",
"localeFr": "Fransızca \/ Français",
"localePt": "Portekizce \/ Portuguesa",
"networkStatusAttemptingTor": "Tor ağına bağlanılıyor",
"localeEs": "İspanyolca \/ Español",
"localeIt": "İtalyanca \/ Italiano",
"debugLog": "Konsol hata ayıklama kaydını başlat",
"localeRU": "Rusça \/ Русский",
"serverMetricsLabel": "Sunucu Bilgileri",
"themeNameCwtch": "Cwtch",
"themeNameWitch": "Cadı",
"themeNameVampire": "Vampir",
"themeNameGhost": "Hayalet",
"themeNamePumpkin": "Balkabağı",
"themeNameMermaid": "Denizkızı",
"themeNameMidnight": "Gece",
"themeNameNeon1": "Neon1",
"themeNameNeon2": "Neon2",
"descriptionACNCircuitInfo": "Anonim iletişim ağının (ACN) bu konuşmaya bağlanmak için kullandığı yol hakkında ayrıntılı bilgi.",
"tooltipSelectACustomProfileImage": "Profil Resmi Seçin",
"notificationPolicyMute": "Sessiz",
"localeRo": "Romence \/ Română",
"localeLb": "Lüksemburgca \/ Lëtzebuergesch",
"localeNo": "Norveççe \/ Norsk",
"localeEl": "Yunanca \/ Ελληνικά",
"localeCy": "Galce \/ Cymraeg",
"notificationPolicyOptIn": "Mümkünse Al",
"conversationNotificationPolicyOptIn": "Mümkünse Al",
"tooltipCode": "Kod \/ Monospace",
"createGroupTitle": "Grup Oluştur",
"serverLabel": "Sunucu",
"defaultGroupName": "Awesome Group",
"createGroupBtn": "Oluştur",
"profileOnionLabel": "Bu adresi bağlantı kurmak istediğiniz insanlara gönderin",
"addPeerTab": "Kişi ekle",
@ -46,15 +93,12 @@
"dmTooltip": "DM için tıklayın",
"couldNotSendMsgError": "Mesaj gönderilemedi",
"acknowledgedLabel": "Onaylandı",
"pendingLabel": "Bekliyor",
"peerBlockedMessage": "Kişi engelli",
"peerOfflineMessage": "Kişi çevrimdışı, mesajlar şu anda iletilemiyor",
"copyBtn": "Kopyala",
"newGroupBtn": "Yeni grup oluştur",
"acceptGroupInviteLabel": "Daveti kabul etmek istiyor musunuz",
"acceptGroupBtn": "Kabul Et",
"rejectGroupBtn": "Reddet",
"chatBtn": "Sohbet",
"listsBtn": "Listeler",
"bulletinsBtn": "Bültenler",
"puzzleGameBtn": "Yapboz",
@ -69,12 +113,9 @@
"addProfileTitle": "Yeni profil ekle",
"editProfileTitle": "Profili Düzenle",
"profileName": "Kullanıcı adı",
"defaultProfileName": "Alice",
"newProfile": "Yeni Profil",
"editProfile": "Profili Düzenle",
"radioNoPassword": "Şifrelenmemiş (parola yok)",
"noPasswordWarning": "Bu hesapta parola kullanılmaması yerel olarak depolanan verilerin şifrelenmeyeceği anlamına gelir",
"yourDisplayName": "Kullanıcı Adınız",
"currentPasswordLabel": "Mevcut Parola",
"password2Label": "Parolayı yeniden gir",
"passwordErrorEmpty": "Parola boş bırakılamaz",
@ -97,7 +138,6 @@
"zoomLabel": "Arayüz yakınlaştırma (aslen metin ve buton boyutlarını etkiler)",
"blockUnknownLabel": "Tanınmayan Kişileri Engelle",
"settingLanguage": "Dil",
"localeDe": "German \/ Deutsch",
"settingInterfaceZoom": "Yakınlaştırma seviyesi",
"largeTextLabel": "Büyük",
"settingTheme": "Açık Tema Kullan",
@ -112,7 +152,6 @@
"loadingTor": "Tor yükleniyor...",
"viewGroupMembershipTooltip": "Grup Üyeliğini Görüntüle",
"networkStatusDisconnected": "İnternet bağlantısı kesildi, bağlantınızı kontrol edin",
"networkStatusAttemptingTor": "Tor ağına bağlanıyor",
"networkStatusConnecting": "Ağa ve kişilere bağlanıyor...",
"networkStatusOnline": "Çevrimiçi",
"newConnectionPaneTitle": "Yeni Bağlantı",
@ -155,7 +194,6 @@
"createProfileToBegin": "Başlamak için bir profil oluşturun veya kilidini açın",
"addContactFirst": "Sohbete başlamak için bir kişi ekleyin veya seçin.",
"torNetworkStatus": "Tor ağ durumu",
"debugLog": "Konsol hata ayıklama kayıtlarını aç",
"profileDeleteSuccess": "Profil başarıyla silindi",
"malformedMessage": "Hatalı biçimlendirilmiş mesaj",
"shutdownCwtchTooltip": "Cwtch'u Kapat",
@ -175,7 +213,6 @@
"tooltipRejectContactRequest": "Bağlantı isteğini reddet",
"tooltipReplyToThisMessage": "Bu mesajı yanıtla",
"tooltipRemoveThisQuotedMessage": "Alıntılanan mesajı kaldır.",
"localePl": "Polish \/ Polski",
"settingUIColumnPortrait": "UI Sütunları Portre Modu",
"settingUIColumnLandscape": "UI Sütunları Yatay Modu",
"settingUIColumnSingle": "Tek",
@ -243,7 +280,6 @@
"fieldDescriptionLabel": "Açıklama",
"manageKnownServersButton": "Bilinen Sunucuları Yönet",
"displayNameTooltip": "Lütfen bir ad girin",
"serverMetricsLabel": "Sunucu Bilgileri",
"manageKnownServersLong": "Bilinen Sunucuları Yönet",
"manageKnownServersShort": "Sunucular",
"serverTotalMessagesLabel": "Toplam Mesaj",
@ -258,7 +294,6 @@
"msgFileTooBig": "Dosya boyutu 10 GB'ı geçemez",
"msgConfirmSend": "Göndermek istediğinize emin misiniz",
"btnSendFile": "Dosya Gönder",
"msgAddToAccept": "Bu dosyayı kabul etmek için hesabı kişilerinize ekleyin.",
"torSettingsUseCustomTorServiceConfigurastionDescription": "Varsayılan tor konfigürasyonunu geçersiz kıl. Uyarı: Bu tehlikeli olabilir. Yalnızca ne yaptığınızı biliyorsanız açın.",
"torSettingsEnabledAdvanced": "Gelişmiş Tor Konfigürasyonunu Etkinleştir",
"torSettingsUseCustomTorServiceConfiguration": "Özel Tor Hizmeti Konfigürasyonunu (torrc) Kullan",
@ -269,18 +304,11 @@
"torSettingsCustomControlPort": "Özel Kontrol Portu",
"torSettingsErrorSettingPort": "Port Numarası 1 ile 65535 arasında olmalıdır",
"settingImagePreviews": "Görsel Önizlemeleri ve Profil Resimleri",
"fileSharingSettingsDownloadFolderDescription": "Dosyalar otomatik olarak indirildiğinde (örneğin görsel önizlemeleri etkinleştirildiğinde görsel dosyaları), dosyaların indirileceği varsayılan bir konum gereklidir.",
"fileSharingSettingsDownloadFolderTooltip": "İndirilen dosyalara farklı bir varsayılan klasör seçmek için gözat.",
"descriptionACNCircuitInfo": "Anonim iletişim ağının (ACN) bu konuşmaya bağlanmak için kullandığı yol hakkında ayrıntılı bilgi.",
"labelACNCircuitInfo": "ACN Ağ Bilgisi",
"labelTorNetwork": "Tor Ağı",
"torSettingsEnabledCacheDescription": "Cwtch'un bir sonraki açılışında yeniden kullanmak için indirilmiş Tor uzlaşmasını önbelleğe alın. Bu, Tor'un daha hızlı başlamasını sağlar. Devre dışı bırakıldığında, Cwtch açılırken önbelleğe alınmış verileri temizler.",
"torSettingsEnableCache": "Tor Uzlaşmasını Önbelleğe Al",
"notificationPolicyMute": "Sessiz",
"tooltipSelectACustomProfileImage": "Profil Resmi Seçin",
"notificationPolicyOptIn": "Mümkünse Al",
"notificationPolicyDefaultAll": "Tümü Varsayılan",
"conversationNotificationPolicyOptIn": "Mümkünse Al",
"conversationNotificationPolicyDefault": "Varsayılan",
"conversationNotificationPolicyNever": "Asla",
"notificationPolicySettingLabel": "Bildirim İlkeleri",
@ -296,25 +324,19 @@
"settingGroupBehaviour": "Davranış",
"settingsGroupAppearance": "Görünüş",
"settingsGroupExperiments": "Deneyler",
"notificationContentSimpleEvent": "Yalın Olay",
"newMessageNotificationSimple": "Yeni Mesaj",
"localeDa": "Danish \/ Dansk",
"exportProfile": "Profili Dışa Aktar",
"exportProfileTooltip": "Bu profili şifrelenmiş bir dosyaya yedekleyin. Şifrelenmiş dosya başka bir Cwtch uygulamasına aktarılabilir.",
"importProfile": "Profili İçe Aktar",
"importProfileTooltip": "Başka bir Cwtch oluşumunda oluşturulmuş bir profili içeri aktarmak için şifrelenmiş bir Cwtch yedeği kullanın.",
"clickableLinkError": "URL açılmaya çalışılırken hata oluştu",
"failedToImportProfile": "Profil İçe Aktarılırken Hata Oluştu",
"successfullyImportedProfile": "Profil Başarıyla İçe Aktarıldı: %profile",
"shuttingDownApp": "Kapanıyor...",
"clickableLinksWarning": "Bu URL'yi açmak Cwtch dışında bir uygulama başlatacak ve metadata teşhir edebilir veya Cwtch'un güvenliğini tehlikeye atabilir. Yalnızca güvendiğiniz kişilerden gelen URL'leri açın. Devam etmek istediğinize emin misiniz?",
"clickableLinkOpen": "URL'yi aç",
"clickableLinksCopy": "URL'yi kopyala",
"formattingExperiment": "Mesaj Biçimlendirme",
"messageFormattingDescription": "Görüntülenen mesajlarda zengin metin biçimlendirmesini etkinleştirin, örneğin **kalın** ve *italik*",
"thisFeatureRequiresGroupExpermientsToBeEnabled": "Bu özellik Gruplar Özelliği'nin Ayarlar'dan etkinleştirilmesini gerektirir",
"settingAndroidPowerExemption": "Android Pil Optimizasyonlarını Yoksay",
"settingAndroidPowerExemptionDescription": "Opsiyonel: Android'den Cwtch'u optimize edilmiş güç yönetiminden muaf tutmasını isteyin. Bu daha fazla pil kullanımı pahasına uygulamanın daha stabil çalışmasını sağlayacaktır.",
"settingsAndroidPowerReenablePopup": "Cwtch içinden Pil Optimizasyonu yeniden etkinleştirilemiyor. Lütfen Android \/ Ayarlar \/ Uygulamalar \/ Cwtch \/ Pil sayfasına gidin ve 'Pil Kullanımını Yönet' bölümünün altında 'Optimize edilmiş'e basın",
"okButton": "OK",
"tooltipBoldText": "Kalın",
@ -331,24 +353,5 @@
"headingReplies": "Yanıtlar",
"messageNoReplies": "Bu mesaja yanıt gelmemiş.",
"fileDownloadUnavailable": "Bu dosya indirmeye uygun görünmüyor. Gönderen dosyanın indirilmesini engellemiş olabilir.",
"replyingTo": "%1 hesabına yanıt veriliyor",
"localeCy": "Welsh \/ Cymraeg",
"localeEl": "Greek \/ Ελληνικά",
"localeNo": "Norwegian \/ Norsk",
"localeLb": "Luxembourgish \/ Lëtzebuergesch",
"localeRo": "Romanian \/ Română",
"themeNameNeon2": "Neon2",
"themeNameNeon1": "Neon1",
"themeNameMidnight": "Midnight",
"themeNameMermaid": "Mermaid",
"themeNamePumpkin": "Pumpkin",
"themeNameGhost": "Ghost",
"themeNameVampire": "Vampire",
"themeNameWitch": "Witch",
"themeNameCwtch": "Cwtch",
"localeRU": "Russian \/ Русский",
"localeEs": "Spanish \/ Español",
"localePt": "Portuguese \/ Portuguesa",
"localeFr": "French \/ Français",
"localeEn": "English \/ English"
"replyingTo": "%1 hesabına yanıt veriliyor"
}

View File

@ -56,6 +56,7 @@ class ContactInfoState extends ChangeNotifier {
late bool _archived;
late bool _pinned;
int _antispamTickets = 0;
String? _acnCircuit;
String? _messageDraft;
@ -100,8 +101,17 @@ class ContactInfoState extends ChangeNotifier {
String? get acnCircuit => this._acnCircuit;
String? get messageDraft => this._messageDraft;
set antispamTickets(int antispamTickets) {
this._antispamTickets = antispamTickets;
notifyListeners();
}
int get antispamTickets => this._antispamTickets;
set acnCircuit(String? acnCircuit) {
this._acnCircuit = acnCircuit;
notifyListeners();

View File

@ -174,7 +174,6 @@ ThemeData mkThemeData(Settings opaque) {
? opaque.current().defaultButtonDisabledColor
: null),
enableFeedback: true,
splashFactory: InkRipple.splashFactory,
padding: MaterialStateProperty.all(EdgeInsets.all(20)),
shape: MaterialStateProperty.all(RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6.0),

View File

@ -40,7 +40,6 @@ class _AddContactViewState extends State<AddContactView> {
String lastContactValue = "";
bool failedImport = false;
@override
Widget build(BuildContext context) {
// if we haven't picked a server yet, pick the first one in the list...
@ -162,24 +161,19 @@ class _AddContactViewState extends State<AddContactView> {
onChanged: (String importBundle) async {
if (lastContactValue != importBundle) {
lastContactValue = importBundle;
var profileOnion = Provider
.of<ProfileInfoState>(bcontext, listen: false)
.onion;
Provider
.of<FlwtchState>(bcontext, listen: false)
.cwtch
.ImportBundle(profileOnion, importBundle.replaceFirst("cwtch:", "")).then((result) {
if (result == "importBundle.success") {
failedImport = false;
if (AppLocalizations.of(bcontext) != null) {
final snackBar = SnackBar(content: Text(AppLocalizations.of(bcontext)!.successfullAddedContact + importBundle));
ScaffoldMessenger.of(bcontext).showSnackBar(snackBar);
Navigator.popUntil(bcontext, (route) => route.settings.name == "conversations");
}
} else {
failedImport = true;
}
});
var profileOnion = Provider.of<ProfileInfoState>(bcontext, listen: false).onion;
Provider.of<FlwtchState>(bcontext, listen: false).cwtch.ImportBundle(profileOnion, importBundle.replaceFirst("cwtch:", "")).then((result) {
if (result == "importBundle.success") {
failedImport = false;
if (AppLocalizations.of(bcontext) != null) {
final snackBar = SnackBar(content: Text(AppLocalizations.of(bcontext)!.successfullAddedContact + importBundle));
ScaffoldMessenger.of(bcontext).showSnackBar(snackBar);
Navigator.popUntil(bcontext, (route) => route.settings.name == "conversations");
}
} else {
failedImport = true;
}
});
}
},
hintText: '',

View File

@ -31,6 +31,9 @@ class ContactsView extends StatefulWidget {
// selectConversation can be called from anywhere to set the active conversation
void selectConversation(BuildContext context, int handle) {
if (handle == Provider.of<AppState>(context, listen: false).selectedConversation) {
return;
}
// requery instead of using contactinfostate directly because sometimes listview gets confused about data that resorts
var unread = Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(handle)!.unreadMessages;
var previouslySelected = Provider.of<AppState>(context, listen: false).selectedConversation;
@ -122,8 +125,7 @@ class _ContactsViewState extends State<ContactsView> {
}),
)
]),
title: RepaintBoundary(
child: Row(children: [
title: Row(children: [
ProfileImage(
imagePath: Provider.of<Settings>(context).isExperimentEnabled(ImagePreviewsExperiment)
? Provider.of<ProfileInfoState>(context).imagePath
@ -141,7 +143,7 @@ class _ContactsViewState extends State<ContactsView> {
Expanded(
child: Text("%1 » %2".replaceAll("%1", Provider.of<ProfileInfoState>(context).nickname).replaceAll("%2", AppLocalizations.of(context)!.titleManageContacts),
overflow: TextOverflow.ellipsis, style: TextStyle(color: Provider.of<Settings>(context).current().mainTextColor))),
])),
]),
actions: getActions(context),
),
floatingActionButton: FloatingActionButton(
@ -215,7 +217,7 @@ class _ContactsViewState extends State<ContactsView> {
ChangeNotifierProvider.value(value: contact),
ChangeNotifierProvider.value(value: Provider.of<ProfileInfoState>(context).serverList),
],
builder: (context, child) => RepaintBoundary(child: ContactRow()),
builder: (context, child) => ContactRow(),
);
});
@ -240,7 +242,7 @@ class _ContactsViewState extends State<ContactsView> {
},
);
return RepaintBoundary(child: contactList);
return contactList;
}
void _pushAddContact(bool newGroup) {

View File

@ -393,7 +393,12 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
value: settings.isExperimentEnabled(FileSharingExperiment),
onChanged: (bool value) {
if (value) {
settings.enableExperiment(FileSharingExperiment);
if (checkDownloadDirectory(context, settings)) {
settings.enableExperiment(FileSharingExperiment);
} else {
settings.disableExperiment(FileSharingExperiment);
settings.disableExperiment(ImagePreviewsExperiment);
}
} else {
settings.disableExperiment(FileSharingExperiment);
settings.disableExperiment(ImagePreviewsExperiment);
@ -413,8 +418,11 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
value: settings.isExperimentEnabled(ImagePreviewsExperiment),
onChanged: (bool value) {
if (value) {
settings.enableExperiment(ImagePreviewsExperiment);
settings.downloadPath = Provider.of<FlwtchState>(context, listen: false).cwtch.defaultDownloadPath();
if (checkDownloadDirectory(context, settings)) {
settings.enableExperiment(ImagePreviewsExperiment);
} else {
settings.disableExperiment(ImagePreviewsExperiment);
}
} else {
settings.disableExperiment(ImagePreviewsExperiment);
}
@ -539,6 +547,30 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
}
}
bool checkDownloadDirectory(context, settings) {
bool showError = false;
if (settings.downloadPath != "") {
} else {
// check if the default download path exists
var path = Provider.of<FlwtchState>(context, listen: false).cwtch.defaultDownloadPath();
if (path != null) {
settings.downloadPath = path;
} else {
showError = true;
}
}
if (!showError && Directory(settings.downloadPath).existsSync()) {
return true;
} else {
final snackBar = SnackBar(
content: Text(AppLocalizations.of(context)!.errorDownloadDirectoryDoesNotExist),
);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
return false;
}
}
/// Construct a version string from Package Info
String constructVersionString(PackageInfo pinfo) {
if (pinfo == null) {

View File

@ -169,14 +169,36 @@ class _MessageViewState extends State<MessageView> {
: null,
title: Row(children: [
ProfileImage(
imagePath: Provider.of<Settings>(context).isExperimentEnabled(ImagePreviewsExperiment)
? Provider.of<ContactInfoState>(context).imagePath
: Provider.of<ContactInfoState>(context).defaultImagePath,
diameter: 42,
border: Provider.of<Settings>(context).current().portraitOnlineBorderColor,
badgeTextColor: Colors.red,
badgeColor: Colors.red,
),
imagePath: Provider.of<Settings>(context).isExperimentEnabled(ImagePreviewsExperiment)
? Provider.of<ContactInfoState>(context).imagePath
: Provider.of<ContactInfoState>(context).defaultImagePath,
diameter: 42,
border: Provider.of<Settings>(context).current().portraitOnlineBorderColor,
badgeTextColor: Colors.red,
badgeColor: Provider.of<Settings>(context).theme.portraitContactBadgeColor,
badgeIcon: Provider.of<ContactInfoState>(context).isGroup
? (Tooltip(
message: Provider.of<ContactInfoState>(context).isOnline() ? Provider.of<ContactInfoState>(context).antispamTickets == 0
? AppLocalizations.of(context)!.acquiringTicketsFromServer
: AppLocalizations.of(context)!.acquiredTicketsFromServer
: AppLocalizations.of(context)!.serverConnectivityDisconnected,
child: Provider.of<ContactInfoState>(context).isOnline() ? Provider.of<ContactInfoState>(context).antispamTickets == 0
? Icon(
Icons.schedule_send,
size: 10.0,
semanticLabel: AppLocalizations.of(context)!.acquiringTicketsFromServer,
color: Provider.of<Settings>(context).theme.portraitContactBadgeTextColor,
)
: Icon(
Icons.send,
color: Provider.of<Settings>(context).theme.portraitContactBadgeTextColor,
size: 10.0,
) : Icon(
CwtchIcons.onion_off,
color: Provider.of<Settings>(context).theme.portraitContactBadgeTextColor,
size: 10.0,
)))
: null),
SizedBox(
width: 10,
),
@ -329,6 +351,12 @@ class _MessageViewState extends State<MessageView> {
}
void _sendMessageHandler(dynamic messageJson) {
if (Provider.of<ContactInfoState>(context, listen: false).antispamTickets == 0) {
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.acquiringTicketsFromServer));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
return;
}
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);
@ -561,8 +589,14 @@ class _MessageViewState extends State<MessageView> {
suffixIcon: ElevatedButton(
key: Key("btnSend"),
style: ElevatedButton.styleFrom(padding: EdgeInsets.all(0.0), shape: new RoundedRectangleBorder(borderRadius: new BorderRadius.circular(45.0))),
child: Icon(CwtchIcons.send_24px, size: 24, color: Provider.of<Settings>(context).theme.defaultButtonTextColor),
onPressed: isOffline ? null : _sendMessage,
child: Tooltip(
message: isOffline
? AppLocalizations.of(context)!.serverNotSynced
: Provider.of<ContactInfoState>(context, listen: false).antispamTickets == 0
? AppLocalizations.of(context)!.acquiringTicketsFromServer
: AppLocalizations.of(context)!.sendMessage,
child: Icon(CwtchIcons.send_24px, size: 24, color: Provider.of<Settings>(context).theme.defaultButtonTextColor)),
onPressed: isOffline || Provider.of<ContactInfoState>(context, listen: false).antispamTickets == 0 ? null : _sendMessage,
))),
)));

View File

@ -340,7 +340,7 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
(ProfileInfoState profile) {
return ChangeNotifierProvider<ProfileInfoState>.value(
value: profile,
builder: (context, child) => RepaintBoundary(child: ProfileRow()),
builder: (context, child) => ProfileRow(),
);
},
);

View File

@ -50,7 +50,7 @@ class _ProfileServersView extends State<ProfileServersView> {
(RemoteServerInfoState server) {
return ChangeNotifierProvider<RemoteServerInfoState>.value(
value: server,
builder: (context, child) => RepaintBoundary(child: RemoteServerRow()),
builder: (context, child) => RemoteServerRow(),
);
},
);

View File

@ -37,12 +37,11 @@ class _ContactRowState extends State<ContactRow> {
));
}
return Card(
clipBehavior: Clip.antiAlias,
color: Provider.of<AppState>(context).selectedConversation == contact.identifier ? Provider.of<Settings>(context).theme.backgroundHilightElementColor : null,
borderOnForeground: true,
margin: EdgeInsets.all(0.0),
child: InkWell(
return InkWell(
enableFeedback: true,
splashFactory: InkSplash.splashFactory,
child: Ink(
color: Provider.of<AppState>(context).selectedConversation == contact.identifier ? Provider.of<Settings>(context).theme.backgroundHilightElementColor : Colors.transparent,
child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Padding(
padding: const EdgeInsets.all(6.0), //border size
@ -144,16 +143,20 @@ class _ContactRowState extends State<ContactRow> {
Provider.of<ContactListState>(context, listen: false).resort();
},
))
]),
onTap: () {
selectConversation(context, contact.identifier);
},
onHover: (onHover) {
setState(() {
isHover = onHover;
});
},
));
])),
onTap: () {
setState(() {
selectConversation(context, contact.identifier);
});
},
onHover: (hover) {
if (isHover != hover) {
setState(() {
isHover = hover;
});
}
},
);
}
void _btnApprove() {

View File

@ -17,7 +17,8 @@ class ProfileImage extends StatefulWidget {
required this.badgeTextColor,
this.maskOut = false,
this.tooltip = "",
this.badgeEdit = false});
this.badgeEdit = false,
this.badgeIcon = null});
final double diameter;
final String imagePath;
final Color border;
@ -27,6 +28,7 @@ class ProfileImage extends StatefulWidget {
final bool maskOut;
final bool badgeEdit;
final String tooltip;
final Widget? badgeIcon;
@override
_ProfileImageState createState() => _ProfileImageState();
@ -81,7 +83,7 @@ class _ProfileImageState extends State<ProfileImage> {
padding: const EdgeInsets.all(2.0), //border size
child: ClipOval(clipBehavior: Clip.antiAlias, child: widget.tooltip == "" ? image : Tooltip(message: widget.tooltip, child: image))))),
Visibility(
visible: widget.badgeEdit || widget.badgeCount > 0,
visible: widget.badgeIcon != null || widget.badgeEdit || widget.badgeCount > 0,
child: Positioned(
bottom: 0.0,
right: 0.0,
@ -93,7 +95,7 @@ class _ProfileImageState extends State<ProfileImage> {
Icons.edit,
color: widget.badgeTextColor,
)
: Text(widget.badgeCount > 99 ? "99+" : widget.badgeCount.toString(), style: TextStyle(color: widget.badgeTextColor, fontSize: 8.0)),
: (widget.badgeIcon != null ? widget.badgeIcon : Text(widget.badgeCount > 99 ? "99+" : widget.badgeCount.toString(), style: TextStyle(color: widget.badgeTextColor, fontSize: 8.0))),
),
)),
]));