Merge pull request #5308 from vector-im/release/1.6.11/release
Release 1.6.11
42
.github/workflows/triage-move-labelled.yml
vendored
@@ -47,23 +47,8 @@ jobs:
|
||||
PROJECT_ID: "PN_kwDOAM0swc0sUA"
|
||||
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
|
||||
spaces_issues_to_old_board:
|
||||
name: Spaces issues to old Delight project board
|
||||
runs-on: ubuntu-latest
|
||||
if: >
|
||||
contains(github.event.issue.labels.*.name, 'A-Spaces') ||
|
||||
contains(github.event.issue.labels.*.name, 'A-Space-Settings') ||
|
||||
contains(github.event.issue.labels.*.name, 'A-Subspaces')
|
||||
steps:
|
||||
- uses: konradpabjan/move-labeled-or-milestoned-issue@219d384e03fa4b6460cd24f9f37d19eb033a4338
|
||||
with:
|
||||
action-token: "${{ secrets.ELEMENT_BOT_TOKEN }}"
|
||||
project-url: "https://github.com/orgs/vector-im/projects/6"
|
||||
column-name: "📥 Inbox"
|
||||
label-name: "A-Spaces"
|
||||
|
||||
spaces_issues_to_new_board:
|
||||
name: Spaces issues to new Delight project board
|
||||
Delight_issues_to_board:
|
||||
name: Spaces issues to Delight project board
|
||||
runs-on: ubuntu-latest
|
||||
if: >
|
||||
contains(github.event.issue.labels.*.name, 'A-Spaces') ||
|
||||
@@ -132,3 +117,26 @@ jobs:
|
||||
env:
|
||||
PROJECT_ID: "PN_kwDOAM0swc0rRA"
|
||||
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
|
||||
move_message_bubble_issues:
|
||||
name: A-Message-Bubbles to Message bubble board
|
||||
runs-on: ubuntu-latest
|
||||
if: >
|
||||
contains(github.event.issue.labels.*.name, 'A-Message-Bubbles')
|
||||
steps:
|
||||
- uses: octokit/graphql-action@v2.x
|
||||
with:
|
||||
headers: '{"GraphQL-Features": "projects_next_graphql"}'
|
||||
query: |
|
||||
mutation add_to_project($projectid:String!,$contentid:String!) {
|
||||
addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) {
|
||||
projectNextItem {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
projectid: ${{ env.PROJECT_ID }}
|
||||
contentid: ${{ github.event.issue.node_id }}
|
||||
env:
|
||||
PROJECT_ID: "PN_kwDOAM0swc3m-g"
|
||||
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
|
||||
25
CHANGES.md
@@ -1,3 +1,28 @@
|
||||
## Changes in 1.6.11 (2021-12-14)
|
||||
|
||||
✨ Features
|
||||
|
||||
- Added support for creating, displaying and interacting with polls in the timeline. ([#5114](https://github.com/vector-im/element-ios/issues/5114))
|
||||
|
||||
🙌 Improvements
|
||||
|
||||
- Upgrade MatrixSDK version ([v0.20.15](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.20.15)).
|
||||
- Room member details: Display user Matrix ID and make it copyable. ([#4568](https://github.com/vector-im/element-ios/issues/4568))
|
||||
|
||||
🐛 Bugfixes
|
||||
|
||||
- Fix crash when trying to scroll the people's tab to the top. ([#5190](https://github.com/vector-im/element-ios/issues/5190))
|
||||
|
||||
🧱 Build
|
||||
|
||||
- Fix SwiftGen only generating strings for MatrixKit. ([#5280](https://github.com/vector-im/element-ios/issues/5280))
|
||||
|
||||
Others
|
||||
|
||||
- Update issue workflow automation for the Delight team ([#5285](https://github.com/vector-im/element-ios/issues/5285))
|
||||
- Update workflow to add automation for the new Message Bubbles board ([#5289](https://github.com/vector-im/element-ios/issues/5289))
|
||||
|
||||
|
||||
## Changes in 1.6.10 (2021-12-09)
|
||||
|
||||
🙌 Improvements
|
||||
|
||||
@@ -15,5 +15,5 @@
|
||||
//
|
||||
|
||||
// Version
|
||||
MARKETING_VERSION = 1.6.10
|
||||
CURRENT_PROJECT_VERSION = 1.6.10
|
||||
MARKETING_VERSION = 1.6.11
|
||||
CURRENT_PROJECT_VERSION = 1.6.11
|
||||
|
||||
@@ -285,14 +285,7 @@ final class BuildSettings: NSObject {
|
||||
static let roomScreenAllowMediaLibraryAction: Bool = true
|
||||
static let roomScreenAllowStickerAction: Bool = true
|
||||
static let roomScreenAllowFilesAction: Bool = true
|
||||
static var roomScreenAllowPollsAction: Bool {
|
||||
guard #available(iOS 14, *) else {
|
||||
return false
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
/// Allow split view detail view stacking
|
||||
static let allowSplitViewDetailsScreenStacking: Bool = true
|
||||
|
||||
@@ -349,4 +342,14 @@ final class BuildSettings: NSObject {
|
||||
|
||||
// MARK: - Secrets Recovery
|
||||
static let secretsRecoveryAllowReset = true
|
||||
|
||||
// MARK: - Polls
|
||||
|
||||
static var pollsEnabled: Bool {
|
||||
guard #available(iOS 14, *) else {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
2
Podfile
@@ -13,7 +13,7 @@ use_frameworks!
|
||||
# - `{ :specHash => {sdk spec hash}` to depend on specific pod options (:git => …, :podspec => …) for MatrixSDK repo. Used by Fastfile during CI
|
||||
#
|
||||
# Warning: our internal tooling depends on the name of this variable name, so be sure not to change it
|
||||
$matrixSDKVersion = '= 0.20.14'
|
||||
$matrixSDKVersion = '0.20.15'
|
||||
# $matrixSDKVersion = :local
|
||||
# $matrixSDKVersion = { :branch => 'develop'}
|
||||
# $matrixSDKVersion = { :specHash => { git: 'https://git.io/fork123', branch: 'fix' } }
|
||||
|
||||
23
Riot/Assets/Images.xcassets/Room/Polls/poll_checkbox_default.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "poll_checkbox_default.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "poll_checkbox_default@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "poll_checkbox_default@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Riot/Assets/Images.xcassets/Room/Polls/poll_checkbox_default.imageset/poll_checkbox_default.png
vendored
Normal file
|
After Width: | Height: | Size: 615 B |
BIN
Riot/Assets/Images.xcassets/Room/Polls/poll_checkbox_default.imageset/poll_checkbox_default@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Riot/Assets/Images.xcassets/Room/Polls/poll_checkbox_default.imageset/poll_checkbox_default@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
23
Riot/Assets/Images.xcassets/Room/Polls/poll_checkbox_selected.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "poll_checkbox_selected.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "poll_checkbox_selected@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "poll_checkbox_selected@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Riot/Assets/Images.xcassets/Room/Polls/poll_checkbox_selected.imageset/poll_checkbox_selected.png
vendored
Normal file
|
After Width: | Height: | Size: 632 B |
BIN
Riot/Assets/Images.xcassets/Room/Polls/poll_checkbox_selected.imageset/poll_checkbox_selected@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Riot/Assets/Images.xcassets/Room/Polls/poll_checkbox_selected.imageset/poll_checkbox_selected@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
23
Riot/Assets/Images.xcassets/Room/Polls/poll_winner_icon.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "poll_winner_icon.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "poll_winner_icon@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "poll_winner_icon@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Riot/Assets/Images.xcassets/Room/Polls/poll_winner_icon.imageset/poll_winner_icon.png
vendored
Normal file
|
After Width: | Height: | Size: 385 B |
BIN
Riot/Assets/Images.xcassets/Room/Polls/poll_winner_icon.imageset/poll_winner_icon@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 630 B |
BIN
Riot/Assets/Images.xcassets/Room/Polls/poll_winner_icon.imageset/poll_winner_icon@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 695 B |
@@ -357,6 +357,8 @@ Tap the + to start adding people.";
|
||||
"room_delete_unsent_messages" = "Delete unsent messages";
|
||||
"room_event_action_copy" = "Copy";
|
||||
"room_event_action_quote" = "Quote";
|
||||
"room_event_action_remove_poll" = "Remove poll";
|
||||
"room_event_action_end_poll" = "End poll";
|
||||
"room_event_action_redact" = "Remove";
|
||||
"room_event_action_more" = "More";
|
||||
"room_event_action_share" = "Share";
|
||||
@@ -567,7 +569,7 @@ Tap the + to start adding people.";
|
||||
"settings_labs_create_conference_with_jitsi" = "Create conference calls with jitsi";
|
||||
"settings_labs_message_reaction" = "React to messages with emoji";
|
||||
"settings_labs_enable_ringing_for_group_calls" = "Ring for group calls";
|
||||
"settings_labs_voice_messages" = "Voice messages";
|
||||
"settings_labs_enabled_polls" = "Polls";
|
||||
|
||||
"settings_version" = "Version %@";
|
||||
"settings_olm_version" = "Olm Version %@";
|
||||
@@ -1793,6 +1795,42 @@ Tap the + to start adding people.";
|
||||
|
||||
"poll_edit_form_create_options" = "Create options";
|
||||
|
||||
"poll_edit_form_option_number" = "Option %d";
|
||||
"poll_edit_form_option_number" = "Option %lu";
|
||||
|
||||
"poll_edit_form_add_option" = "Add option";
|
||||
|
||||
"poll_edit_form_post_failure_title" = "Failed to post poll";
|
||||
|
||||
"poll_edit_form_post_failure_subtitle" = "Please try again";
|
||||
|
||||
"poll_edit_form_post_failure_action" = "OK";
|
||||
|
||||
"poll_timeline_one_vote" = "1 vote";
|
||||
|
||||
"poll_timeline_votes_count" = "%lu votes";
|
||||
|
||||
"poll_timeline_total_no_votes" = "No votes cast";
|
||||
|
||||
"poll_timeline_total_one_vote" = "1 vote cast";
|
||||
|
||||
"poll_timeline_total_votes" = "%lu votes cast";
|
||||
|
||||
"poll_timeline_total_one_vote_not_voted" = "1 vote cast. Vote to the see the results";
|
||||
|
||||
"poll_timeline_total_votes_not_voted" = "%lu votes cast. Vote to the see the results";
|
||||
|
||||
"poll_timeline_total_final_results_one_vote" = "Final results based on 1 vote";
|
||||
|
||||
"poll_timeline_total_final_results" = "Final results based on %lu votes";
|
||||
|
||||
"poll_timeline_vote_not_registered_title" = "Vote not registered";
|
||||
|
||||
"poll_timeline_vote_not_registered_subtitle" = "Sorry, your vote was not registered, please try again";
|
||||
|
||||
"poll_timeline_vote_not_registered_action" = "OK";
|
||||
|
||||
"poll_timeline_not_closed_title" = "Failed to end poll";
|
||||
|
||||
"poll_timeline_not_closed_subtitle" = "Please try again";
|
||||
|
||||
"poll_timeline_not_closed_action" = "OK";
|
||||
|
||||
@@ -759,7 +759,7 @@
|
||||
"settings_ignored_users" = "EIRATUD KASUTAJAD";
|
||||
"settings_contacts" = "KONTAKTID SEADMES";
|
||||
"settings_advanced" = "KEERUKAMAD SEADISTUSED";
|
||||
"settings_other" = "MUUD SEADISTUSED";
|
||||
"settings_other" = "Muud seadistused";
|
||||
"settings_labs" = "KATSED";
|
||||
"settings_discovery_no_identity_server" = "Sa hetkel ei kasuta isikutuvastusserverit. Et olla leitav ja ise leida sinule teadaolevaid inimesi seadista ta alljärgnevalt.";
|
||||
"settings_discovery_terms_not_signed" = "Selleks, et sind võiks leida e-posti aadressi või telefoninumbri alusel, nõustu isikutuvastusserveri (%@) kasutustingimustega.";
|
||||
@@ -1469,3 +1469,18 @@
|
||||
"contacts_address_book_permission_denied_alert_title" = "Kontaktid pole kasutusel";
|
||||
"space_home_show_all_rooms" = "Näita kõiki jututubasid";
|
||||
"room_event_action_forward" = "Edasta";
|
||||
"share_extension_send_now" = "Saada nüüd";
|
||||
"share_extension_low_quality_video_message" = "Parema kvaliteedi jaoks saada %@ vormingus või alljärgnevaga madalama kvaliteediga.";
|
||||
"share_extension_low_quality_video_title" = "Saadame video madalama kvalitediga";
|
||||
"settings_about" = "TEAVE MEIST";
|
||||
"poll_edit_form_add_option" = "Lisa valik";
|
||||
"poll_edit_form_option_number" = "Valik %d";
|
||||
"poll_edit_form_create_options" = "Koosta valikud";
|
||||
"poll_edit_form_input_placeholder" = "Kirjuta midagi";
|
||||
"poll_edit_form_question_or_topic" = "Küsimus või teema";
|
||||
"poll_edit_form_poll_question_or_topic" = "Küsitluse küsimus või teema";
|
||||
|
||||
// Mark: - Polls
|
||||
|
||||
"poll_edit_form_create_poll" = "Koosta üks küsitlus";
|
||||
"settings_discovery_accept_terms" = "Nõustu isikutuvastusserveri tingimustega";
|
||||
|
||||
@@ -283,7 +283,7 @@
|
||||
"settings_ignored_users" = "FIGYELMEN KÍVÜL HAGYOTT FELHASZNÁLÓK";
|
||||
"settings_contacts" = "ESZKÖZ NÉVJEGYZÉK";
|
||||
"settings_advanced" = "HALADÓ";
|
||||
"settings_other" = "MÁS";
|
||||
"settings_other" = "Más";
|
||||
"settings_labs" = "LABOR";
|
||||
"settings_flair" = "Kitűző mutatása ahol lehet";
|
||||
"settings_devices" = "MUNKAMENETEK";
|
||||
@@ -1532,3 +1532,18 @@
|
||||
"contacts_address_book_permission_denied_alert_title" = "Névjegyzék letiltva";
|
||||
"space_home_show_all_rooms" = "Minden szoba megjelenítése";
|
||||
"room_event_action_forward" = "Továbbítás";
|
||||
"poll_edit_form_add_option" = "Lehetőség hozzáadása";
|
||||
"poll_edit_form_option_number" = "%d lehetőség";
|
||||
"poll_edit_form_create_options" = "Lehetőségek hozzáadása";
|
||||
"poll_edit_form_input_placeholder" = "Írjon valamit";
|
||||
"poll_edit_form_question_or_topic" = "Kérdés vagy téma";
|
||||
"poll_edit_form_poll_question_or_topic" = "Szavazás kérdés vagy téma";
|
||||
|
||||
// Mark: - Polls
|
||||
|
||||
"poll_edit_form_create_poll" = "Szavazás létrehozása";
|
||||
"share_extension_send_now" = "Küldés most";
|
||||
"share_extension_low_quality_video_message" = "Jobb minőségért küld így: %@ vagy gyengébb minőségben alább.";
|
||||
"share_extension_low_quality_video_title" = "Alacsony minőségű videó lesz elküldve";
|
||||
"settings_discovery_accept_terms" = "Azonosítási Szolgáltatás felhasználási feltételeinek elfogadása";
|
||||
"settings_about" = "NÉVJEGY";
|
||||
|
||||
@@ -48,8 +48,8 @@
|
||||
"join" = "Bergabung";
|
||||
"save" = "Simpan";
|
||||
"cancel" = "Batalkan";
|
||||
"off" = "Nonaktif";
|
||||
"on" = "Aktif";
|
||||
"off" = "Mati";
|
||||
"on" = "Nyala";
|
||||
"retry" = "Coba Lagi";
|
||||
"invite" = "Undang";
|
||||
"remove" = "Hapus";
|
||||
@@ -63,9 +63,9 @@
|
||||
// Actions
|
||||
"view" = "Tampilkan";
|
||||
"warning" = "Peringatan";
|
||||
"title_groups" = "Komunitas-komunitas";
|
||||
"title_rooms" = "Ruangan-ruangan";
|
||||
"title_people" = "Orang-orang";
|
||||
"title_groups" = "Komunitas";
|
||||
"title_rooms" = "Ruangan";
|
||||
"title_people" = "Orang";
|
||||
"title_favourites" = "Favorit";
|
||||
|
||||
// Titles
|
||||
@@ -101,8 +101,8 @@
|
||||
"callbar_only_single_active_group" = "Ketuk untuk Bergabung panggilan grup (%@)";
|
||||
"joined" = "Bergabung";
|
||||
"collapse" = "tutup";
|
||||
"store_promotional_text" = "Aplikasi perpesanan dan kolaborasi yang menjaga privasi, pada jaringan terbuka. Terdesentralisasi untuk Anda kendali. Tidak ada penambangan data, tidak ada backdoor dan tidak ada akses pihak ketiga.";
|
||||
"store_full_description" = "Element adalah aplikasi messenger dan kolaborasi tipe baru yang:\n\n1. Menempatkan Anda dalam kendali untuk mempertahankan privasi Anda\n2. Memungkinkan Anda berkomunikasi dengan siapa pun di jaringan Matrix, dan bahkan di luar dengan mengintegrasikan dengan aplikasi seperti Slack\n3. Melindungi Anda dari iklan, menambangan data, backdoor, dan taman berdinding\n4. Mengamankan Anda melalui enkripsi ujung-ke-ujung, dengan penandatanganan-silang untuk memverifikasi orang lain\n\nElement benar-benar berbeda dari aplikasi perpesanan dan kolaborasi lain karena terdesentralisasi dan sumber terbuka.\n\nElement memungkinkan Anda host sendiri - atau memilih host - sehingga Anda memiliki privasi, kepemilikan, dan kontrol data dan percakapan Anda. Ini memberi Anda akses ke jaringan terbuka; jadi Anda tidak hanya terjebak berbicara dengan pengguna Element. Itu sangat aman.\n\nElement dapat melakukan semua ini karena beroperasi pada Matrix - standar untuk komunikasi terdesentralisasi terbuka.\n\nElement menempatkan Anda dalam kendali dengan membiarkan Anda memilih siapa yang meng-host percakapan Anda. Dari aplikasi Element, Anda dapat memilih untuk meng-host dengan cara yang berbeda:\n\n1. Dapatkan akun gratis pada server publik matrix.org\n2. Host sendiri akun Anda dengan menjalankan server pada perangkat keras Anda sendiri\n3. Mendaftar untuk akun di server khusus dengan hanya berlangganan platform hosting Element Matrix Services\n\nMengapa memilih Element?\n\nMILIKI DATA ANDA: Anda memutuskan di mana harus menyimpan data dan pesan Anda. Anda memilikinya dan mengendalikannya, bukan perusahaan besar yang menambang data Anda atau memberikan akses ke pihak ketiga.\n\nPESAN DAN KOLABORASI TERBUKA: Anda dapat mengobrol dengan orang lain di jaringan Matrix, jika mereka menggunakan Element atau aplikasi Matrix lain, dan bahkan jika mereka menggunakan sistem perpesanan seperti Slack, IRC atau XMPP.\n\nSUPER-AMAN: Enkripsi ujung-ke-ujung (hanya mereka yang dalam percakapan dapat mendekripsi pesan), dan penandatanganan-silang untuk memverifikasi perangkat peserta percakapan.\n\nKOMUNIKASI LENGKAP: Pesan, panggilan suara dan video, berbagi file, berbagi layar dan banyak integrasi, bot dan widget. Buat ruangan, komunitas, tetap terhubung dan selesaikan hal-hal.\n\nDI MANA PUN ANDA BERADA: Tetap berkomunikasi di mana pun Anda berada dengan riwayat pesan yang sepenuhnya disinkronkan di semua perangkat Anda dan di web di https://app.element.io/.";
|
||||
"store_promotional_text" = "Aplikasi perpesanan dan kolaborasi yang menjaga privasi, pada jaringan terbuka. Terdesentralisasi untuk Anda kendali. Tidak ada penambangan data, tidak ada pintu belakang dan tidak ada akses pihak ketiga.";
|
||||
"store_full_description" = "Element adalah aplikasi messenger dan kolaborasi tipe baru yang:\n\n1. Menempatkan Anda dalam kendali untuk mempertahankan privasi Anda\n2. Memungkinkan Anda berkomunikasi dengan siapa pun di jaringan Matrix, dan bahkan di luar dengan mengintegrasikan dengan aplikasi seperti Slack\n3. Melindungi Anda dari iklan, menambangan data, pintu belakang, dan taman berdinding\n4. Mengamankan Anda melalui enkripsi ujung-ke-ujung, dengan penandatanganan silang untuk memverifikasi orang lain\n\nElement benar-benar berbeda dari aplikasi perpesanan dan kolaborasi lain karena Element terdesentralisasi dan sumber terbuka.\n\nElement memungkinkan Anda host sendiri - atau memilih host - sehingga Anda memiliki privasi, kepemilikan, dan kontrol data dan obrolan Anda. Ini memberi Anda akses ke jaringan terbuka, jadi Anda tidak hanya terjebak berbicara dengan pengguna Element. Itu sangat aman.\n\nElement dapat melakukan semua ini karena beroperasi pada Matrix - standar untuk komunikasi terdesentralisasi terbuka.\n\nElement menempatkan Anda dalam kendali dengan membiarkan Anda memilih siapa yang menghost percakapan Anda. Dari aplikasi Element, Anda dapat memilih untuk menghost dengan cara yang berbeda:\n\n1. Dapatkan akun gratis pada server publik matrix.org\n2. Host sendiri akun Anda dengan menjalankan server pada perangkat keras Anda sendiri\n3. Mendaftar untuk akun di server khusus dengan hanya berlangganan platform hosting Element Matrix Services\n\nMengapa memilih Element?\n\nMILIKI DATA ANDA: Anda memutuskan di mana untuk menyimpan data dan pesan Anda. Anda memilikinya dan mengendalikannya, bukan perusahaan besar yang menambang data Anda atau memberikan akses ke pihak ketiga.\n\nPESAN DAN KOLABORASI TERBUKA: Anda dapat mengobrol dengan orang lain di jaringan Matrix, jika mereka menggunakan Element atau aplikasi Matrix lain, dan bahkan jika mereka menggunakan sistem perpesanan seperti Slack, IRC atau XMPP.\n\nSANGAT AMAN: Enkripsi ujung-ke-ujung yang nyata (hanya mereka yang dalam percakapan dapat mendekripsi pesan), dan penandatanganan silang untuk memverifikasi perangkat anggota obrolan.\n\nKOMUNIKASI LENGKAP: Perpesanan, panggilan suara dan video, pembagian file, pembagian layar dan banyak integrasi, bot dan widget. Buat ruangan, komunitas, tetap terhubung dan selesaikan hal-hal.\n\nDI MANA PUN ANDA BERADA: Tetap berkomunikasi di mana pun Anda berada dengan riwayat pesan yang sepenuhnya disinkronkan di semua perangkat Anda dan di web di https://app.element.io/.";
|
||||
// String for App Store
|
||||
"store_short_description" = "Obrolan/VoIP terdesentralisasi aman";
|
||||
|
||||
@@ -166,7 +166,7 @@
|
||||
"room_message_edits_history_title" = "Editan pesan";
|
||||
"room_action_send_file" = "Kirim file";
|
||||
"room_action_send_sticker" = "Kirim stiker";
|
||||
"room_event_action_reaction_history" = "Sejarah reaksi";
|
||||
"room_event_action_reaction_history" = "Riwayat reaksi";
|
||||
"room_event_action_reaction_show_less" = "Tampilkan lebih sedikit";
|
||||
"room_event_action_reaction_show_all" = "Tampilkan semua";
|
||||
"room_event_action_view_encryption" = "Informasi enkripsi";
|
||||
@@ -472,7 +472,7 @@
|
||||
"settings_cryptography" = "KRIPTOGRAFI";
|
||||
"settings_devices" = "SESI";
|
||||
"settings_labs" = "UJI COBA";
|
||||
"settings_other" = "LAINNYA";
|
||||
"settings_other" = "Lainnya";
|
||||
"settings_advanced" = "LANJUTAN";
|
||||
"settings_integrations" = "INTEGRASI";
|
||||
"settings_discovery_settings" = "PENEMUAN";
|
||||
@@ -621,10 +621,10 @@
|
||||
"home_empty_view_information" = "Aplikasi obrolan aman semua-dalam-satu untuk tim, teman, dan organisasi. Ketuk tombol + di bawah untuk menambahkan orang dan ruangan.";
|
||||
"pin_protection_explanatory" = "Menyiapkan PIN memungkinkan Anda melindungi data seperti pesan dan kontak, jadi hanya Anda yang dapat mengaksesnya dengan memasukkan PIN di awal aplikasi.";
|
||||
"major_update_information" = "Kami senang mengumumkan bahwa kami telah mengubah nama kami! Aplikasi Anda telah diperbarui dan Anda masuk ke akun Anda.";
|
||||
"secrets_reset_warning_message" = "Anda akan memulai ulang tanpa sejarah, tanpa pesan, perangkat tepercaya, atau pengguna tepercaya.";
|
||||
"secrets_reset_warning_message" = "Anda akan memulai ulang tanpa riwayat, tanpa pesan, perangkat tepercaya, atau pengguna tepercaya.";
|
||||
"secrets_setup_recovery_key_storage_alert_message" = "✓ Cetak dan simpan di tempat yang aman\n✓ Simpan di flashdisk USB atau drive cadangan\n✓ Salin ke penyimpanan awan pribadi Anda";
|
||||
"secrets_recovery_with_key_information_default" = "Akses sejarah pesan terenkripsi Anda dan identitas penandatanganan silang Anda untuk memverifikasi sesi lain dengan memasukkan Kunci Keamanan Anda.";
|
||||
"secrets_recovery_with_passphrase_information_default" = "Akses sejarah pesan terenkripsi Anda dan identitas penandatanganan silang Anda untuk memverifikasi sesi lain dengan memasukkan Frasa Keamanan Anda.";
|
||||
"secrets_recovery_with_key_information_default" = "Akses riwayat pesan terenkripsi Anda dan identitas penandatanganan silang Anda untuk memverifikasi sesi lain dengan memasukkan Kunci Keamanan Anda.";
|
||||
"secrets_recovery_with_passphrase_information_default" = "Akses riwayat pesan terenkripsi Anda dan identitas penandatanganan silang Anda untuk memverifikasi sesi lain dengan memasukkan Frasa Keamanan Anda.";
|
||||
"user_verification_session_details_additional_information_untrusted_other_user" = "Hingga pengguna ini memercayai sesi ini, pesan yang dikirim ke dan dari sesi ini akan diberi label peringatan. Atau, Anda dapat memverifikasinya secara manual.";
|
||||
"user_verification_session_details_information_untrusted_current_user" = "Verifikasi sesi ini untuk menandainya sebagai tepercaya & memberikan akses ke pesan terenkripsi:";
|
||||
"user_verification_sessions_list_information" = "Pesan dengan pengguna ini di ruangan ini dienkripsi secara ujung-ke-ujung dan tidak dapat dibaca oleh pihak ketiga.";
|
||||
@@ -682,7 +682,7 @@
|
||||
"bug_report_prompt" = "Aplikasi telah crash terakhir kali. Apakah Anda ingin mengirimkan laporan crash?";
|
||||
"room_notifs_settings_encrypted_room_notice" = "Harap dicatat bahwa notifikasi sebutan & keyword tidak tersedia di ruang terenkripsi di ponsel.";
|
||||
"room_details_addresses_disable_main_address_prompt_msg" = "Anda tidak akan memiliki alamat utama yang ditentukan. Alamat utama default untuk ruangan ini akan dipilih secara acak";
|
||||
"room_details_history_section_prompt_msg" = "Perubahan siapa yang dapat membaca sejarah hanya akan berlaku untuk pesan berikutnya di ruang ini. Visibilitas sejarah yang ada tidak akan berubah.";
|
||||
"room_details_history_section_prompt_msg" = "Perubahan siapa yang dapat membaca riwayat hanya akan berlaku untuk pesan berikutnya di ruang ini. Visibilitas riwayat yang ada tidak akan berubah.";
|
||||
"identity_server_settings_alert_error_terms_not_accepted" = "Anda harus menerima syaratnya %@ untuk menyetelnya sebagai server identitas.";
|
||||
"identity_server_settings_alert_change" = "Lepaskan hubungan dari server identitas %1$@ dan hubungkan ke %2$@?";
|
||||
"identity_server_settings_alert_no_terms" = "Server identitas ini yang Anda pilih tidak memiliki syarat layanan. Hanya lanjutkan jika Anda mempercayai pemilik server itu.";
|
||||
@@ -735,11 +735,11 @@
|
||||
|
||||
// Recover from recovery key
|
||||
|
||||
"key_backup_recover_from_recovery_key_info" = "Gunakan Frasa Keamanan Anda untuk mengakses sejarah pesan secara aman";
|
||||
"key_backup_recover_from_recovery_key_info" = "Gunakan Frasa Keamanan Anda untuk mengakses riwayat pesan secara aman";
|
||||
|
||||
// Recover from passphrase
|
||||
|
||||
"key_backup_recover_from_passphrase_info" = "Gunakan Frasa Keamanan Anda untuk mengakses sejarah pesan secara aman";
|
||||
"key_backup_recover_from_passphrase_info" = "Gunakan Frasa Keamanan Anda untuk mengakses riwayat pesan secara aman";
|
||||
"key_backup_setup_passphrase_setup_recovery_key_info" = "Atau, amankan cadangan Anda dengan sebuah Kunci Keamanan, menyimpannya di tempat yang aman.";
|
||||
"secure_backup_setup_banner_subtitle" = "Amankan dari kehilangan akses ke pesan & data terenkripsi";
|
||||
"deactivate_account_informations_part4_emphasize" = "tidak secara default menyebabkan kami melupakan pesan yang telah Anda kirim. ";
|
||||
@@ -977,7 +977,7 @@
|
||||
"room_details_set_main_address" = "Atur sebagai Alamat Utama";
|
||||
"room_details_advanced_e2e_encryption_enabled_for_dm" = "Enkripsi diaktifkan di sini";
|
||||
"room_details_flair_section" = "Tampilkan bakat untuk komunitas";
|
||||
"room_details_history_section" = "Siapa saja yang dapat membaca sejarah?";
|
||||
"room_details_history_section" = "Siapa saja yang dapat membaca riwayat?";
|
||||
"room_details_access_section_directory_toggle_for_dm" = "Daftarkan di direktori ruangan";
|
||||
"room_details_access_section_for_dm" = "Siapa saja yang dapat mengakses?";
|
||||
"identity_server_settings_place_holder" = "Masukkan sebuah server identitas";
|
||||
@@ -1508,7 +1508,7 @@
|
||||
"room_details_fail_to_update_room_canonical_alias" = "Gagal untuk memperbarui alamat utama";
|
||||
"room_details_fail_to_update_room_communities" = "Gagal untuk memperbarui komunitas terkait";
|
||||
"room_details_fail_to_remove_room_aliases" = "Gagal untuk menghapus alamat-alamat ruangan";
|
||||
"room_details_fail_to_update_history_visibility" = "Gagal untuk memperbarui visibilitas sejarah";
|
||||
"room_details_fail_to_update_history_visibility" = "Gagal untuk memperbarui visibilitas riwayat";
|
||||
"room_details_fail_to_update_room_join_rule" = "Gagal untuk memperbarui aturan bergabung";
|
||||
"room_details_fail_to_update_room_name" = "Gagal untuk memperbarui nama ruangannya";
|
||||
"room_details_fail_to_update_avatar" = "Gagal untuk memperbarui foto ruangannya";
|
||||
@@ -1550,7 +1550,7 @@
|
||||
"settings_discovery_three_pids_management_information_part1" = "Kelola alamat email atau nomor telepon apa saja yang pengguna lain dapat menggunakan untuk menemukan Anda dan menggunakannya untuk mengundang Anda ke ruangan. Tambahkan atau hapus alamat email atau nomor telepon dari daftar ini di ";
|
||||
"room_preview_unlinked_email_warning" = "Undangan ini telah dikirim ke %@, yang tidak diasosiasikan dengan akun ini. Anda mungkin ingin masuk ke akun yang lain, atau tambahkan email ini ke akun Anda.";
|
||||
"unknown_devices_alert" = "Ruangan ini berisi sesi tidak dikenal yang belum diverifikasi.\nIni berarti tidak ada jaminan bahwa sesi tersebut adalah milik pengguna yang mereka klaim.\nKami menyarankan Anda memverifikasinya untuk setiap sesi sebelum melanjutkan, tetapi Anda dapat mengirim ulang pesan tanpa memverifikasi jika Anda mau.";
|
||||
"room_warning_about_encryption" = "Enkripsi ujung-ke-ujung masih dalam beta dan mungkin tidak dapat dihandalkan.\n\nAnda seharusnya tidak mempercayainya dulu untuk mengamankan data.\n\nPerangkat masih belum dapat mendekripsi sejarah sebelum mereka bergabung ke ruangannya.\n\nPesan terenkripsi masih belum terlihat di client yang belum mengimplementasikan enkripsi.";
|
||||
"room_warning_about_encryption" = "Enkripsi ujung-ke-ujung masih dalam beta dan mungkin tidak dapat dihandalkan.\n\nAnda seharusnya tidak mempercayainya dulu untuk mengamankan data.\n\nPerangkat masih belum dapat mendekripsi riwayat sebelum mereka bergabung ke ruangannya.\n\nPesan terenkripsi masih belum terlihat di client yang belum mengimplementasikan enkripsi.";
|
||||
"auth_add_email_and_phone_warning" = "Pendaftaran dengan email dan nomor telepon sekaligus belum didukung sampai API-nya sudah ada. Hanya nomor telepon yang akan diperhitungkan. Anda dapat menambahkan email Anda di profil Anda di pengaturan.";
|
||||
"auth_reset_password_success_message" = "Kata sandi Anda telah diatur ulang.\n\nAnda telah dikeluarkan dari semua sesi dan tidak akan menerima lagi notifikasi push. Untuk mengaktifkan ulang notifikasi, masuk ulang di setiap perangkat.";
|
||||
"spaces_add_rooms_coming_soon_title" = "Penambahan ruangan akan segera datang";
|
||||
@@ -1648,3 +1648,18 @@
|
||||
// Group Details
|
||||
"group_details_title" = "Detail Komunitas";
|
||||
"room_event_action_forward" = "Teruskan";
|
||||
"poll_edit_form_add_option" = "Tambahkan opsi";
|
||||
"poll_edit_form_option_number" = "Opsi %d";
|
||||
"poll_edit_form_create_options" = "Buat opsi";
|
||||
"poll_edit_form_input_placeholder" = "Tulis sesuatu";
|
||||
"poll_edit_form_question_or_topic" = "Pertanyaan atau topik";
|
||||
"poll_edit_form_poll_question_or_topic" = "Pertanyaan atau topik poll";
|
||||
|
||||
// Mark: - Polls
|
||||
|
||||
"poll_edit_form_create_poll" = "Buat poll";
|
||||
"share_extension_send_now" = "Kirim sekarang";
|
||||
"share_extension_low_quality_video_message" = "Kirim dalam %@ untuk kualitas yang lebih baik, atau kirim dengan kualitas rendah di bawah.";
|
||||
"share_extension_low_quality_video_title" = "Video akan dikirim dalam kualitas rendah";
|
||||
"settings_discovery_accept_terms" = "Terima Persyaratan Server Identitas";
|
||||
"settings_about" = "TENTANG";
|
||||
|
||||
@@ -297,7 +297,7 @@
|
||||
"settings_ignored_users" = "UTENTI IGNORATI";
|
||||
"settings_contacts" = "CONTATTI DEL DISPOSITIVO";
|
||||
"settings_advanced" = "AVANZATE";
|
||||
"settings_other" = "ALTRO";
|
||||
"settings_other" = "Altro";
|
||||
"settings_labs" = "LABORATORIO";
|
||||
"settings_flair" = "Mostra la predisposizione quando è consentito";
|
||||
"settings_devices" = "SESSIONI";
|
||||
@@ -1503,3 +1503,18 @@
|
||||
"contacts_address_book_permission_denied_alert_title" = "Contatti disattivati";
|
||||
"space_home_show_all_rooms" = "Mostra tutte le stanze";
|
||||
"room_event_action_forward" = "Inoltra";
|
||||
"poll_edit_form_add_option" = "Aggiungi opzione";
|
||||
"poll_edit_form_option_number" = "Opzione %d";
|
||||
"poll_edit_form_create_options" = "Crea opzioni";
|
||||
"poll_edit_form_input_placeholder" = "Scrivi qualcosa";
|
||||
"poll_edit_form_question_or_topic" = "Domanda o argomento";
|
||||
"poll_edit_form_poll_question_or_topic" = "Domanda o argomento del sondaggio";
|
||||
|
||||
// Mark: - Polls
|
||||
|
||||
"poll_edit_form_create_poll" = "Crea sondaggio";
|
||||
"share_extension_send_now" = "Invia adesso";
|
||||
"share_extension_low_quality_video_message" = "Invia in %@ per una migliore qualità, o invia in bassa qualità qua sotto.";
|
||||
"share_extension_low_quality_video_title" = "Il video verrà inviato in bassa qualità";
|
||||
"settings_discovery_accept_terms" = "Accetta termini del server d'identità";
|
||||
"settings_about" = "INFORMAZIONI";
|
||||
|
||||
@@ -873,7 +873,7 @@
|
||||
"settings_key_backup_info" = "暗号化されたメッセージはエンドツーエンドで暗号化されています。送信者と受信者だけがこのメッセージを読むための鍵を持っています。";
|
||||
"settings_labs_message_reaction" = "絵文字でメッセージに反応する";
|
||||
"settings_calls_stun_server_fallback_description" = "ホームサーバーがフォールバックコールアシストサーバーを提供していない場合は%@を許可します(IPアドレスは通話中に共有されます)。";
|
||||
"settings_security" = "セキュリティー";
|
||||
"settings_security" = "セキュリティ";
|
||||
"settings_three_pids_management_information_part3" = "";
|
||||
"settings_three_pids_management_information_part2" = "ディスカバリー";
|
||||
"store_full_description" = "Elementはまったく新しいメッセンジャーアプリです。\n\n1. あなた自身がプライバシーをコントロールすることを可能にします。\n2. Matrixネットワークにいる誰とでも通信できることはもちろん、Slackなどのアプリとの連携によって他のネットワークとも通信ができます。\n3. 広告、データ収集、バックドア、ユーザーの囲い込みから逃れることができます。\n4. エンドツーエンド暗号化とクロス署名によってあなたを保護します。\n\nElementは分散型(非中央集権型)でオープンソースであるため、他のメッセンジャーアプリと完全に異なっています。\n\nElementはあなた自身でサーバーをホストすることも、サーバーを選ぶこともできます。これによってあなたのデータと会話に関するプライバシーや所有権はあなた自身で管理できるようになります。さらに、あなたは他のElementユーザーと話せるだけでなくオープンネットワークへのアクセスも可能です。\n\nElementは、オープンな分散型通信の標準規格であるMatrixで動作するため、これらすべてを実現することができています。\n\nどのサーバーがホストするか決めることができます。さまざまな方法で選択できます。\n\n1. 開発者がホストするmatrix.orgの公開サーバーで無料のアカウントを取得します。\n2. あなた自身がサーバーを動かし、アカウントを管理します。\n3. Element Matrix Servicesのホスティングプラットフォームに登録することで、カスタムサーバー上のアカウントを取得できます。\n\nなぜElementを選ぶべきなのか?\n\nデータを所有する: 自分でデータやメッセージを保管する場所を決めることができます。あなたが所有権を持ってコントロールすることで、第三者にあなたのデータを渡したり、ビッグデータを収集する巨大テック企業に依存する必要がなくなります。\n\n開かれたネットワークと共同作業: Matrixネットワーク内の他の誰とでも、あるいはElementや他のMatrixアプリを使っているかどうかに関わらず、またSlack、IRC、XMPPのような他のメッセージングシステムを使っているかどうかに関わらず、チャットすることができます。\n\nとても安全: 本物のエンドツーエンド暗号化(会話に参加している者のみがメッセージを読める)と会話参加者の真正性を確認するためクロス署名によって。\n\n完全なるコミュニケーションの訪れ: テキスト、音声通話、ビデオ通話、ファイル共有、画面共有、連携機能、ボット、ウィジェットなどのコミュニケーションに必要な機能の全てが実装されています。ルームやコミュニティを立ち上げて連絡を取り合い、物事をスムーズに成し遂げることができます。\n\nいつでもどこにいても: すべてのデバイスとウェブでメッセージの履歴が同期されるため、どこにいても連絡を取ることができます。https://app.element.io";
|
||||
@@ -1038,3 +1038,39 @@
|
||||
|
||||
// New login
|
||||
"device_verification_self_verify_alert_title" = "ログインしましたか?";
|
||||
"room_recents_suggested_rooms_section" = "おすすめの部屋";
|
||||
"settings_show_url_previews_description" = "プレビューは暗号化されていない部屋でのみ表示されます。";
|
||||
"settings_show_url_previews" = "ウェブサイトプレビューを表示";
|
||||
"biometrics_setup_enable_button_title_x" = "%@ を有効にする";
|
||||
"biometrics_setup_title_x" = "%@ を有効にする";
|
||||
"biometrics_settings_enable_x" = "%@ を有効にする";
|
||||
"biometrics_mode_face_id" = "Face ID";
|
||||
|
||||
// MARK: - Biometrics Protection
|
||||
|
||||
"biometrics_mode_touch_id" = "Touch ID";
|
||||
"pin_protection_settings_enable_pin" = "PIN を有効にする";
|
||||
"pin_protection_settings_section_header_with_biometrics" = "PIN と %@";
|
||||
"pin_protection_settings_section_header" = "PIN";
|
||||
"settings_mentions_and_keywords_encryption_notice" = "モバイルでは、暗号化された部屋でのメンションとキーワードの通知は受信できません。";
|
||||
"settings_new_keyword" = "キーワードを追加";
|
||||
"settings_your_keywords" = "以下でキーワードを指定できます";
|
||||
"settings_mentions_and_keywords" = "メンションとキーワード";
|
||||
"settings_messages_containing_keywords" = "キーワード";
|
||||
"settings_messages_containing_at_room" = "@room";
|
||||
"settings_messages_containing_user_name" = "あなたのユーザー名";
|
||||
"settings_messages_containing_display_name" = "あなたの表示名";
|
||||
"settings_encrypted_group_messages" = "暗号化されたグループメッセージ";
|
||||
"settings_group_messages" = "グループメッセージ";
|
||||
"settings_encrypted_direct_messages" = "暗号化されたダイレクトメッセージ";
|
||||
"settings_direct_messages" = "ダイレクトメッセージ";
|
||||
"settings_notify_me_for" = "以下がメッセージ含まれる場合通知します";
|
||||
"settings_phone_contacts" = "端末の連絡先";
|
||||
"settings_notifications" = "通知";
|
||||
"settings_links" = "リンク";
|
||||
"side_menu_action_feedback" = "フィードバック";
|
||||
"side_menu_action_help" = "ヘルプ";
|
||||
"side_menu_action_settings" = "設定";
|
||||
"spaces_home_space_title" = "ホーム";
|
||||
"spaces_left_panel_title" = "スペース";
|
||||
"spaces_suggested_room" = "おすすめ";
|
||||
|
||||
@@ -122,7 +122,7 @@
|
||||
"room_creation_keep_private" = "개인으로 유지";
|
||||
"room_creation_make_private" = "개인으로 하기";
|
||||
"room_creation_wait_for_creation" = "방을 이미 만드는 중입니다. 기다려주세요.";
|
||||
"room_creation_invite_another_user" = "검색 / 사용자 ID, 이름 또는 이메일로 초대";
|
||||
"room_creation_invite_another_user" = "사용자 ID, 이름 또는 이메일";
|
||||
"room_creation_error_invite_user_by_email_without_identity_server" = "ID 서버가 설정되지 않아 이메일로 참가자를 추가할 수 없습니다.";
|
||||
// Room recents
|
||||
"room_recents_directory_section" = "방 목록";
|
||||
@@ -397,7 +397,7 @@
|
||||
"settings_key_backup_info" = "암호화된 메시지는 종단간 암호화로 보호됩니다. 오직 당신과 참가자만 키를 갖고 있어서 이 메시지를 읽을 수 있습니다.";
|
||||
"settings_key_backup_info_checking" = "확인 중…";
|
||||
"settings_key_backup_info_none" = "키가 이 세션에서 백업되지 않았습니다.";
|
||||
"settings_key_backup_info_signout_warning" = "이 기기에만 있을 수 있는 키를 잃지 않도록, 로그아웃하기 전에 이 기기를 키 백업에 연결하세요.";
|
||||
"settings_key_backup_info_signout_warning" = "키를 잃어버리지 않도록 로그아웃을 하기 전에 키를 백업하세요.";
|
||||
"settings_key_backup_info_version" = "키 백업 버전: %@";
|
||||
"settings_key_backup_info_algorithm" = "알고리즘: %@";
|
||||
"settings_key_backup_info_valid" = "이 세션은 키를 백업하고 있습니다.";
|
||||
@@ -564,7 +564,7 @@
|
||||
"e2e_need_log_in_again" = "이 세션에 종단간 암호화 키를 생성하고 공개 키를 홈서버에 제출하려면 다시 로그인해야 함니다.\n한 번만 하면 됩니다. 불편을 드려 죄송합니다.";
|
||||
// Key backup wrong version
|
||||
"e2e_key_backup_wrong_version_title" = "새 키 백업";
|
||||
"e2e_key_backup_wrong_version" = "새 보안 메시지 키 백업이 감지되었습니다.\n\n당신이 한 것이 아니라면 설정에서 새 암호를 설정하세요.";
|
||||
"e2e_key_backup_wrong_version" = "새 보안 메시지 키 백업이 감지되었습니다.\n\n스스로 진행한 것이 아니라면 설정에서 새 암호를 설정하세요.";
|
||||
"e2e_key_backup_wrong_version_button_settings" = "설정";
|
||||
"e2e_key_backup_wrong_version_button_wasme" = "접니다";
|
||||
// Bug report
|
||||
@@ -641,7 +641,7 @@
|
||||
"key_backup_setup_intro_manual_export_info" = "(고급)";
|
||||
"key_backup_setup_intro_manual_export_action" = "수동으로 키 내보내기";
|
||||
"key_backup_setup_passphrase_title" = "백업을 암호로 보호하기";
|
||||
"key_backup_setup_passphrase_info" = "암호화된 키의 사본을 서버에 보관합니다. 암호로된 백업을 보호하며 안전하게 유지해줍니다.\n\n보안을 최대화하려면, 암호는 계정 비밀번호와 달라야 합니다.";
|
||||
"key_backup_setup_passphrase_info" = "암호화된 키의 사본을 서버에 보관합니다. 암호로 된 백업을 보호하며 안전하게 유지해줍니다.\n\n보안을 최대화하려면, 암호는 계정 비밀번호와 달라야 합니다.";
|
||||
"key_backup_setup_passphrase_passphrase_title" = "입력";
|
||||
"key_backup_setup_passphrase_passphrase_placeholder" = "암호 입력";
|
||||
"key_backup_setup_passphrase_passphrase_valid" = "좋아요!";
|
||||
@@ -649,7 +649,7 @@
|
||||
"key_backup_setup_passphrase_confirm_passphrase_title" = "확인";
|
||||
"key_backup_setup_passphrase_confirm_passphrase_placeholder" = "암호 확인";
|
||||
"key_backup_setup_passphrase_confirm_passphrase_valid" = "좋아요!";
|
||||
"key_backup_setup_passphrase_confirm_passphrase_invalid" = "암호가 맞지 않습니다";
|
||||
"key_backup_setup_passphrase_confirm_passphrase_invalid" = "암호가 틀렸습니다";
|
||||
"key_backup_setup_passphrase_set_passphrase_action" = "암호 설정";
|
||||
"key_backup_setup_passphrase_setup_recovery_key_info" = "또는, 안전한 곳에 저장해 둘 복구 키로 백업을 보호합니다.";
|
||||
"key_backup_setup_passphrase_setup_recovery_key_action" = "(고급) 복구 키로 설정";
|
||||
@@ -896,7 +896,7 @@
|
||||
"widget_menu_open_outside" = "브라우저에서 열기";
|
||||
"widget_menu_revoke_permission" = "액세스 취소";
|
||||
"widget_menu_remove" = "모두를 위해 제거";
|
||||
"widget_integration_manager_disabled" = "설정에서 통합 관리자를 켜야 합니다";
|
||||
"widget_integration_manager_disabled" = "설정에서 통합 관리자를 활성화 해야 합니다";
|
||||
"people_empty_view_information" = "누구든지 안전하게 대화하세요. 사람을 추가하려면 + 버튼을 누르세요.";
|
||||
"people_empty_view_title" = "사람들";
|
||||
"room_recents_unknown_room_error_message" = "이 방을 찾을 수 없습니다. 방이 존재하는지 확인하세요";
|
||||
@@ -931,3 +931,140 @@
|
||||
"joined" = "참여했습니다";
|
||||
"skip" = "넘기기";
|
||||
"store_promotional_text" = "개방된 네트워크에서 프라이버시를 보장받을 수 있는 대화와 협업 앱. 분산화된 네트워크를 통해 정보를 제어할 수 있습니다. 데이터마이닝도 백도어도 다른 곳에서의 접근도 존재하지 않습니다.";
|
||||
"security_settings_blacklist_unverified_devices_description" = "모든 사용자 세션을 확인하여 신뢰할 수 있는 것으로 표시하고 메시지를 보냅니다.";
|
||||
"security_settings_blacklist_unverified_devices" = "신뢰할 수 없는 세션에 메시지를 보내지 않음";
|
||||
"security_settings_advanced" = "고급";
|
||||
"security_settings_export_keys_manually" = "수동으로 키 내보내기";
|
||||
"security_settings_cryptography" = "암호화";
|
||||
"security_settings_crosssigning_complete_security" = "완벽한 보안";
|
||||
"security_settings_crosssigning_reset" = "초기화";
|
||||
"security_settings_crosssigning_bootstrap" = "설정";
|
||||
"security_settings_crosssigning_info_ok" = "교차 서명을 사용할 준비가 되었습니다.";
|
||||
"security_settings_crosssigning_info_trusted" = "교차 검증이 활성화 되었습니다. 교차 서명을 기반으로 다른 사용자와 다른 세션을 신뢰할 수 있지만 이 세션에는 교차 서명 개인 키가 없기 때문에 이 세션에서 교차 서명할 수 없습니다. 이 세션의 보안을 완전히 하십시오.";
|
||||
"security_settings_crosssigning_info_exists" = "계정에는 교차 서명 ID가 있지만, 아직 이 세션에서 신뢰하지 않습니다. 이 세션의 보안을 완전히 하십시오.";
|
||||
"security_settings_crosssigning_info_not_bootstrapped" = "교차 서명이 아직 설정되지 않았습니다.";
|
||||
"security_settings_crosssigning" = "교차 서명";
|
||||
"security_settings_backup" = "메세지 백업";
|
||||
"security_settings_secure_backup_delete" = "백업 삭제";
|
||||
"security_settings_secure_backup_restore" = "백업에서 복구";
|
||||
"security_settings_secure_backup_reset" = "초기화";
|
||||
"security_settings_secure_backup_setup" = "설정";
|
||||
"security_settings_secure_backup_info_valid" = "이 세션은 키를 백업하고 있습니다.";
|
||||
"security_settings_secure_backup_info_checking" = "확인 중…";
|
||||
"security_settings_secure_backup_description" = "세션에 액세스할 수 없는 경우에 대비하여 계정 데이터로 암호화 키를 백업하십시오. 귀하의 키는 고유한 보안 키로 보호됩니다.";
|
||||
"security_settings_secure_backup" = "보안 백업";
|
||||
"security_settings_crypto_sessions_description_2" = "로그인을 인식 하지 못하는 경우 비밀번호를 변경하고 보안 백업을 재설정하세요.";
|
||||
"security_settings_crypto_sessions_loading" = "세션 로딩 중…";
|
||||
"security_settings_crypto_sessions" = "내 세션";
|
||||
|
||||
// Security settings
|
||||
"security_settings_title" = "보안";
|
||||
"settings_show_NSFW_public_rooms" = "NSFW 공개방 표시";
|
||||
"settings_discovery_accept_terms" = "ID 서버 약관 동의";
|
||||
"settings_labs_voice_messages" = "음성 메시지";
|
||||
"settings_labs_enable_ringing_for_group_calls" = "그룹 통화 벨소리";
|
||||
"settings_contacts_enable_sync_description" = "이렇게 하면 ID 서버를 사용하여 연락처와 연결하고 사용자를 찾는 데 도움이 됩니다.";
|
||||
"settings_contacts_enable_sync" = "연락처에서 찾기";
|
||||
"settings_show_url_previews_description" = "미리보기는 암호화 되지 않은 방에만 표시 됩니다.";
|
||||
"settings_show_url_previews" = "웹사이트 미리보기 표시";
|
||||
"settings_ui_theme_picker_message_match_system_theme" = "\"자동\"은 기기의 시스템 테마와 일치 시킵니다";
|
||||
"settings_ui_theme_picker_message_invert_colours" = "\"자동\" 은 기기의 \"색상 반전\" 설정을 사용합니다";
|
||||
"room_multiple_typing_notification" = "%@ 및 다른 사용자가 입력 중 입니다";
|
||||
"external_link_confirmation_message" = "링크 %@이(가) 다른 사이트: %@로 연결됩니다\n\n정말로 계속 하시겠습니까?";
|
||||
"room_member_power_level_custom_in" = "사용자 정의 (%@) in %@";
|
||||
"room_participants_action_security_status_complete_security" = "완벽한 보안";
|
||||
"room_participants_filter_room_members_for_dm" = "구성원 필터";
|
||||
"key_verification_tile_request_incoming_approval_accept" = "수락";
|
||||
"key_verification_tile_request_status_accepted" = "수락됨";
|
||||
"key_verification_tile_request_status_cancelled" = "%@ 취소됨";
|
||||
"key_verification_tile_request_status_cancelled_by_me" = "취소됨";
|
||||
"key_verification_tile_request_status_expired" = "만료됨";
|
||||
"key_verification_tile_request_status_waiting" = "기다려주세요…";
|
||||
"key_verification_tile_request_status_data_loading" = "데이터 로딩…";
|
||||
|
||||
// Tiles
|
||||
|
||||
"key_verification_tile_request_incoming_title" = "검증 요청";
|
||||
|
||||
// MARK: - Key Verification
|
||||
|
||||
"key_verification_bootstrap_not_setup_title" = "오류";
|
||||
|
||||
// Incoming key verification request
|
||||
|
||||
"key_verification_incoming_request_incoming_alert_message" = "%@가 검증을 요청함";
|
||||
"key_verification_tile_conclusion_warning_title" = "신뢰할 수 없는 로그인";
|
||||
"key_verification_tile_conclusion_done_title" = "검증됨";
|
||||
"key_verification_tile_request_incoming_approval_decline" = "끊기";
|
||||
"settings_mentions_and_keywords_encryption_notice" = "암호화된 방에서는 멘션과 키워드에 대한 알림을 받을 수 없습니다.";
|
||||
"settings_new_keyword" = "키워드 추가";
|
||||
"settings_your_keywords" = "키워드 설정";
|
||||
"settings_room_upgrades" = "방 설정 변경";
|
||||
"settings_messages_by_a_bot" = "봇으로부터 받은 메시지";
|
||||
"settings_call_invitations" = "통화 초대";
|
||||
"settings_room_invitations" = "방 초대";
|
||||
"settings_messages_containing_keywords" = "키워드";
|
||||
"settings_messages_containing_at_room" = "@방";
|
||||
"settings_messages_containing_user_name" = "아이디";
|
||||
"settings_messages_containing_display_name" = "프로필 이름";
|
||||
"settings_direct_messages" = "다이렉트 메시지";
|
||||
"settings_notify_me_for" = "해당 메시지를 받으면 알림 받기";
|
||||
"settings_encrypted_group_messages" = "암호화된 그룹 메시지";
|
||||
"settings_group_messages" = "그룹 메시지";
|
||||
"settings_encrypted_direct_messages" = "암호화된 다이렉트 메시지";
|
||||
"settings_mentions_and_keywords" = "멘션과 키워드";
|
||||
"settings_default" = "기본 알림";
|
||||
"settings_notifications_disabled_alert_title" = "알림 비활성화";
|
||||
"settings_security" = "보안";
|
||||
"settings_confirm_media_size" = "전송 시 크기 확인";
|
||||
"settings_confirm_media_size_description" = "이 기능을 켜면 보낼 이미지나 동영상의 크기를 확인하는 메시지가 표시됩니다.";
|
||||
"settings_about" = "정보";
|
||||
"settings_phone_contacts" = "연락처";
|
||||
"settings_notifications" = "알림";
|
||||
"settings_links" = "링크";
|
||||
"settings_sending_media" = "사진 및 영상 전송";
|
||||
"external_link_confirmation_title" = "링크 확인";
|
||||
"room_no_privileges_to_create_group_call" = "통화를 시작하려면 관리자나 중재자의 권한이 필요합니다.";
|
||||
"room_join_group_call" = "입장";
|
||||
"room_open_dialpad" = "키패드";
|
||||
"room_place_voice_call" = "음성 통화";
|
||||
"room_accessibility_video_call" = "영상 통화";
|
||||
"room_event_action_delete_confirmation_message" = "전송되지 않은 메시지를 삭제하시겠습니까?";
|
||||
"room_event_action_delete_confirmation_title" = "전송되지 않은 메시지 삭제";
|
||||
"room_event_action_forward" = "전달";
|
||||
"room_unsent_messages_cancel_message" = "이 방에서 전송되지 않은 모든 메시지를 삭제하시겠습니까?";
|
||||
"room_unsent_messages_cancel_title" = "전송되지 않은 메시지 삭제";
|
||||
"room_message_replying_to" = "%@에게 답장";
|
||||
"room_message_editing" = "편집";
|
||||
|
||||
// Chat
|
||||
"room_slide_to_end_group_call" = "모든 구성원의 통화를 종료하려면 슬라이드";
|
||||
"room_member_power_level_short_custom" = "맞춤 설정";
|
||||
"room_member_power_level_short_moderator" = "중재자";
|
||||
"room_member_power_level_short_admin" = "관리자";
|
||||
"room_member_power_level_moderator_in" = "%@ 중재자";
|
||||
"room_member_power_level_admin_in" = "%@ 관리자";
|
||||
"room_participants_security_information_room_encrypted_for_dm" = "이 메시지는 종단간 암호화가 적용되어 있습니다.\n\n메시지는 암호화되어 보호되며, 메시지를 복호화할 수 있는 고유 키는 사용자와 수신자만 가지고 있습니다.";
|
||||
"room_participants_security_information_room_encrypted" = "이 방의 메시지는 종단간 암호화가 적용됩니다.\n\n메시지는 암호화되어 보호되며, 메시지를 복호화할 수 있는 고유 키는 사용자와 수신자만 가지고 있습니다.";
|
||||
"room_participants_security_information_room_not_encrypted_for_dm" = "이 메시지는 종단간 암호화가 적용되지 않습니다.";
|
||||
"room_participants_security_information_room_not_encrypted" = "이 방에서 보내는 메시지는 종단간 암호화가 적용되지 않습니다.";
|
||||
"room_participants_security_loading" = "로딩 중…";
|
||||
"room_participants_action_security_status_loading" = "로딩 중…";
|
||||
"room_participants_action_security_status_warning" = "주의";
|
||||
"room_participants_action_security_status_verify" = "검증";
|
||||
"room_participants_action_security_status_verified" = "검증됨";
|
||||
"room_participants_action_section_security" = "보안";
|
||||
"room_participants_leave_prompt_msg_for_dm" = "정말 떠나시겠습니까?";
|
||||
"room_participants_leave_prompt_title_for_dm" = "떠나기";
|
||||
"find_your_contacts_identity_service_error" = "ID 서버에 연결할 수 없습니다.";
|
||||
"find_your_contacts_button_title" = "연락처에서 찾기";
|
||||
"find_your_contacts_message" = "%@에서 연락처를 표시하여 잘 아는 사람과 빠르게 대화할 수 있습니다.";
|
||||
"find_your_contacts_title" = "연락처에서 시작하기";
|
||||
"poll_edit_form_add_option" = "옵션 추가";
|
||||
"settings_notifications_disabled_alert_message" = "알림을 허용하려면, 설정으로 이동하십시오.";
|
||||
"settings_device_notifications" = "장치 알림";
|
||||
"find_your_contacts_footer" = "설정에서 언제든지 비활성화 할 수 있습니다.";
|
||||
"side_menu_action_invite_friends" = "친구 초대";
|
||||
"contacts_address_book_permission_denied_alert_message" = "연락처를 사용하려면, 설정으로 이동하십시오.";
|
||||
"contacts_address_book_permission_denied_alert_title" = "연락처 사용 안 함";
|
||||
"rooms_empty_view_title" = "방";
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
"auth_repeat_password_placeholder" = "Wachtwoord herhalen";
|
||||
"auth_repeat_new_password_placeholder" = "Bevestig uw nieuwe wachtwoord";
|
||||
"auth_invalid_login_param" = "Onjuiste gebruikersnaam en/of wachtwoord";
|
||||
"auth_invalid_user_name" = "Gebruikersnamen mogen alleen letters, cijfers, punten, koppeltekens en underscores bevatten";
|
||||
"auth_invalid_user_name" = "Inlognamen mogen alleen letters, cijfers, punten, koppeltekens en onderstrepingstekens bevatten";
|
||||
"auth_invalid_password" = "Het wachtwoord is te kort (min 6)";
|
||||
"auth_invalid_email" = "Dit ziet er niet uit als een geldig e-mailadres";
|
||||
"auth_invalid_phone" = "Dit ziet er niet uit als een geldig telefoonnummer";
|
||||
@@ -263,7 +263,7 @@
|
||||
"settings_ignored_users" = "GENEGEERDE GEBRUIKERS";
|
||||
"settings_contacts" = "APPARAAT CONTACTEN";
|
||||
"settings_advanced" = "GEAVANCEERD";
|
||||
"settings_other" = "OVERIGE";
|
||||
"settings_other" = "OVERIG";
|
||||
"settings_labs" = "EXPERIMENTEEL";
|
||||
"settings_devices" = "APPARATEN";
|
||||
"settings_cryptography" = "CRYPTOGRAFIE";
|
||||
@@ -566,7 +566,7 @@
|
||||
"store_short_description" = "Veilig en gedecentraliseerd chatten en bellen";
|
||||
"store_full_description" = "Element is een nieuw type messenger en samenwerkings app die:\n\n1. U de controle geeft om uw privacy te behouden\n2. U laat communiceren met iedereen in het Matrix-netwerk, en zelfs daarbuiten door integratie met apps zoals Slack\n3. Beschermt u tegen reclame, datamining, achterdeurtjes en ommuurde netwerken\n4. Beveiligt u door eind-tot-eind versleuteling, met kruislings ondertekenen om anderen te verifiëren\n\nElement is compleet anders dan andere messengers en samenwerkings-apps, omdat het gedecentraliseerd en open source is.\n\nMet Element kunt u zelf hosten - of een host kiezen - zodat u privacy, eigendom en controle heeft over uw gegevens en gesprekken. Het geeft u toegang tot een open netwerk; u zit dus niet vast aan het praten met alleen andere Element-gebruikers. En het is zeer veilig.\n\nElement is hiertoe in staat omdat het werkt op basis van Matrix - de standaard voor open, gedecentraliseerde communicatie. \n\nElement geeft u de controle door u te laten kiezen wie uw gesprekken host. Vanuit de Element app kunt u kiezen om op verschillende manieren te hosten:\n\n1. Neem een gratis account op de publieke server matrix.org\n2. Host het zelf, uw account door draait op uw eigen server\n3. Laat ons het hosten, meld u aan voor een account op een aangepaste server bij het Element Matrix Services hosting platform\n\nWaarom kiest u voor Element?\n\nEIGENAAR VAN UW GEGEVENS: U bepaalt waar uw gegevens en berichten worden bewaard. U bent de eigenaar en heeft de controle, niet een of andere MEGACORP die uw gegevens mijnt of toegang geeft aan derden.\n\nOPEN MESSAGING EN SAMENWERKING: U kunt met iedereen in het Matrix-netwerk chatten, of ze nu Element of een andere Matrix-app gebruiken, en zelfs als ze een ander messaging-systeem gebruiken zoals Slack, IRC of XMPP.\n\nSUPER-VEILIG: Echte eind-tot-eind versleuteling (alleen degenen in de conversatie kunnen berichten ontsleutelen), en kruislings ondertekenen om de apparaten van gespreksdeelnemers te verifiëren.\n\nCOMPLETE COMMUNICATIE: Berichten, spraak- en videogesprekken, bestandsdeling, schermdeling en een heleboel integraties, bots en widgets. Bouw kamers, Spaces, blijf in contact en krijg het gedaan.\n\nOVERAL WAAR U BENT: Blijf in contact waar u ook bent met volledig gesynchroniseerde berichtgeschiedenis op al uw apparaten en op het web op https://element.io/app.";
|
||||
"auth_login_single_sign_on" = "Aanmelden met enkele aanmelding";
|
||||
"auth_accept_policies" = "Gelieve het beleid van deze server te lezen en aanvaarden:";
|
||||
"auth_accept_policies" = "Lees het beleid van deze homeserver en kies om te aanvaarden:";
|
||||
"auth_autodiscover_invalid_response" = "Ongeldig server-ontdekkings-antwoord";
|
||||
"room_recents_server_notice_section" = "SYSTEEMMELDINGEN";
|
||||
"room_message_unable_open_link_error_message" = "Kan de koppeling niet openen.";
|
||||
@@ -1632,3 +1632,18 @@
|
||||
"find_your_contacts_title" = "Begin met een lijst van uw contacten";
|
||||
"contacts_address_book_permission_denied_alert_message" = "Om contacten in te schakelen, ga naar uw apparaatinstellingen.";
|
||||
"contacts_address_book_permission_denied_alert_title" = "Contacten uitgeschakeld";
|
||||
"poll_edit_form_add_option" = "Optie toevoegen";
|
||||
"poll_edit_form_option_number" = "Optie %d";
|
||||
"poll_edit_form_create_options" = "Opties maken";
|
||||
"poll_edit_form_input_placeholder" = "Schrijf iets";
|
||||
"poll_edit_form_question_or_topic" = "Vraag of onderwerp";
|
||||
"poll_edit_form_poll_question_or_topic" = "Poll vraag of onderwerp";
|
||||
|
||||
// Mark: - Polls
|
||||
|
||||
"poll_edit_form_create_poll" = "Poll maken";
|
||||
"share_extension_send_now" = "Versturen";
|
||||
"share_extension_low_quality_video_message" = "Stuur in %@ voor een betere kwaliteit of stuur in lage kwaliteit hieronder.";
|
||||
"share_extension_low_quality_video_title" = "Video zal in lage kwaliteit worden verstuurd";
|
||||
"settings_discovery_accept_terms" = "Identiteitsserver-voorwaarden aanvaarden";
|
||||
"settings_about" = "OVER";
|
||||
|
||||
@@ -290,7 +290,7 @@
|
||||
"settings_ignored_users" = "USUÁRIAS(OS) IGNORADAS(OS)";
|
||||
"settings_contacts" = "CONTATOS DE DISPOSITIVO";
|
||||
"settings_advanced" = "AVANÇADAS";
|
||||
"settings_other" = "OUTRAS";
|
||||
"settings_other" = "Outras";
|
||||
"settings_labs" = "LABS";
|
||||
"settings_flair" = "Mostrar flair onde permitido";
|
||||
"settings_devices" = "SESSÕES";
|
||||
@@ -1500,3 +1500,18 @@
|
||||
"contacts_address_book_permission_denied_alert_title" = "Contatos desabilitados";
|
||||
"space_home_show_all_rooms" = "Mostrar todas as salas";
|
||||
"room_event_action_forward" = "Encaminhar";
|
||||
"poll_edit_form_add_option" = "Adicionar opção";
|
||||
"poll_edit_form_option_number" = "Opção %d";
|
||||
"poll_edit_form_create_options" = "Criar opções";
|
||||
"poll_edit_form_input_placeholder" = "Escreva algo";
|
||||
"poll_edit_form_question_or_topic" = "Pergunta ou tópico";
|
||||
"poll_edit_form_poll_question_or_topic" = "Sondar pergunta ou tópico";
|
||||
|
||||
// Mark: - Polls
|
||||
|
||||
"poll_edit_form_create_poll" = "Criar sondagem";
|
||||
"share_extension_send_now" = "Enviar agora";
|
||||
"share_extension_low_quality_video_message" = "Enviar em %@ para melhor qualidade, ou enviar em baixa qualidade abaixo.";
|
||||
"share_extension_low_quality_video_title" = "Vídeo vai ser enviado em baixa qualidade";
|
||||
"settings_discovery_accept_terms" = "Aceitar Termos de Servidor de Identidade";
|
||||
"settings_about" = "SOBRE";
|
||||
|
||||
9
Riot/Assets/sk.lproj/InfoPlist.strings
Normal file
@@ -0,0 +1,9 @@
|
||||
|
||||
|
||||
"NSFaceIDUsageDescription" = "Face ID sa používa na prístup k aplikácii.";
|
||||
"NSCalendarsUsageDescription" = "Zobrazte svoje naplánované stretnutia v aplikácii.";
|
||||
"NSContactsUsageDescription" = "Element zobrazí vaše kontakty, aby ste ich mohli pozvať do chatu.";
|
||||
"NSMicrophoneUsageDescription" = "Element potrebuje prístup k mikrofónu, aby mohol uskutočňovať a prijímať hovory, nahrávať videá a hlasové správy.";
|
||||
"NSPhotoLibraryUsageDescription" = "Knižnica fotografií sa používa na odosielanie fotografií a videí.";
|
||||
// Permissions usage explanations
|
||||
"NSCameraUsageDescription" = "Fotoaparát sa používa na snímanie fotografií a videí, uskutočňovanie videohovorov.";
|
||||
77
Riot/Assets/sk.lproj/Localizable.strings
Normal file
@@ -0,0 +1,77 @@
|
||||
|
||||
|
||||
|
||||
/* Multiple unread messages from three people */
|
||||
"MSGS_FROM_THREE_USERS" = "%@ nových správ od %@, %@ a %@";
|
||||
|
||||
/* Multiple unread messages from two people */
|
||||
"MSGS_FROM_TWO_USERS" = "%@ nových správ od %@ a %@";
|
||||
|
||||
/* Multiple unread messages from a specific person, not referencing a room */
|
||||
"MSGS_FROM_USER" = "%@ nových správ v %@";
|
||||
|
||||
/** Coalesced messages **/
|
||||
|
||||
/* Multiple unread messages in a room */
|
||||
"UNREAD_IN_ROOM" = "%@ nové správy v %@";
|
||||
|
||||
/* New message with hidden content due to PIN enabled */
|
||||
"MESSAGE_PROTECTED" = "Nová správa";
|
||||
|
||||
/* New message indicator on a room */
|
||||
"MESSAGE_IN_X" = "Správa v %@";
|
||||
|
||||
/* New message indicator from a DM */
|
||||
"MESSAGE_FROM_X" = "Správa od %@";
|
||||
|
||||
/** Notification messages **/
|
||||
|
||||
/* New message indicator on unknown room */
|
||||
"MESSAGE" = "Správa";
|
||||
|
||||
/* A single unread message */
|
||||
"SINGLE_UNREAD" = "Dostali ste správu";
|
||||
|
||||
/* A single unread message in a room */
|
||||
"SINGLE_UNREAD_IN_ROOM" = "Dostali ste správu v %@";
|
||||
|
||||
/* New video message from a specific person, not referencing a room. */
|
||||
"VIDEO_FROM_USER" = "%@ odoslal/a video";
|
||||
|
||||
/* New image message from a specific person in a named room. */
|
||||
"IMAGE_FROM_USER_IN_ROOM" = "%@ zverejnil/a obrázok %@ v %@";
|
||||
|
||||
/** Media Messages **/
|
||||
|
||||
/* New image message from a specific person, not referencing a room. */
|
||||
"PICTURE_FROM_USER" = "%@ odoslal/a obrázok";
|
||||
|
||||
/* New action message from a specific person in a named room. */
|
||||
"ACTION_FROM_USER_IN_ROOM" = "%@: * %@ %@";
|
||||
|
||||
/* New action message from a specific person, not referencing a room. */
|
||||
"ACTION_FROM_USER" = "* %@ %@";
|
||||
|
||||
/* New message from a specific person in a named room. Content included. */
|
||||
"MSG_FROM_USER_IN_ROOM_WITH_CONTENT" = "%@ v %@: %@";
|
||||
|
||||
/** Single, unencrypted messages (where we can include the content */
|
||||
|
||||
/* New message from a specific person, not referencing a room. Content included. */
|
||||
"MSG_FROM_USER_WITH_CONTENT" = "%@: %@";
|
||||
|
||||
/** Single, end-to-end encrypted messages (ie. we don't know what they say) */
|
||||
|
||||
/* New message from a specific person, not referencing a room */
|
||||
"MSG_FROM_USER" = "%@ odoslal/a správu";
|
||||
|
||||
/* New message reply from a specific person, not referencing a room. */
|
||||
"REPLY_FROM_USER_TITLE" = "%@ odpovedal/a";
|
||||
|
||||
/** Titles **/
|
||||
|
||||
/* Message title for a specific person in a named room */
|
||||
"MSG_FROM_USER_IN_ROOM_TITLE" = "%@ v %@";
|
||||
/** General **/
|
||||
|
||||
"NOTIFICATION" = "Oznámenia";
|
||||
167
Riot/Assets/sk.lproj/Vector.strings
Normal file
@@ -0,0 +1,167 @@
|
||||
|
||||
|
||||
"done" = "Hotovo";
|
||||
"open" = "Otvoriť";
|
||||
"less" = "Menej";
|
||||
"more" = "Viac";
|
||||
"skip" = "Preskočiť";
|
||||
"close" = "Zavrieť";
|
||||
"collapse" = "zbaliť";
|
||||
"rename" = "Premenovať";
|
||||
"later" = "Neskôr";
|
||||
"active_call_details" = "Prebiehajúci hovor (%s)";
|
||||
"active_call" = "Aktívny hovor";
|
||||
"video" = "Video";
|
||||
"voice" = "Hlas";
|
||||
"camera" = "Kamera";
|
||||
"preview" = "Zobraziť náhľad";
|
||||
"accept" = "Prijať";
|
||||
"decline" = "Odmietnuť";
|
||||
"join" = "Vstúpiť";
|
||||
"save" = "Uložiť";
|
||||
"cancel" = "Zrušiť";
|
||||
"off" = "Vypnuté";
|
||||
"on" = "Povolené";
|
||||
"retry" = "Skúsiť znovu";
|
||||
"invite" = "Pozvať";
|
||||
"remove" = "Odstrániť";
|
||||
"leave" = "Opustiť";
|
||||
"start" = "Začať";
|
||||
"store_promotional_text" = "Aplikácia na chatovanie a spoluprácu s ochranou súkromia v otvorenej sieti. Decentralizovaná, aby ste ju mali pod kontrolou. Žiadne dolovanie údajov, žiadne zadné vrátka ani prístup tretích strán.";
|
||||
"store_full_description" = "Element je nový typ aplikácie na posielanie správ a spoluprácu, ktorá:\n\n1. vám dáva kontrolu nad zachovaním vášho súkromia\n2. Umožňuje vám komunikovať s kýmkoľvek v sieti Matrix, a dokonca aj mimo nej vďaka integrácii s aplikáciami, ako je Slack\n3. Chráni vás pred reklamou, získavaním údajov, zadnými vrátkami a uzavretým ekosystémom.\n4. Zabezpečuje vás prostredníctvom end-to-end šifrovania s krížovým podpisovaním na overenie ostatných\n\nElement sa úplne líši od ostatných aplikácií na zasielanie správ a spoluprácu, pretože je decentralizovaný a má otvorený zdrojový kód.\n\nElement vám umožňuje, aby ste sa sami hosťovali - alebo si vybrali hosting - takže máte súkromie, vlastníctvo a kontrolu nad svojimi údajmi a konverzáciami. Poskytuje vám prístup k otvorenej sieti; takže nie ste odkázaní len na rozhovory s inými používateľmi aplikácie Element. A je veľmi bezpečný.\n\nElement toto všetko dokáže, pretože funguje na Matrixe - štandarde pre otvorenú, decentralizovanú komunikáciu. \n\nElement vám dáva kontrolu tým, že vám umožňuje vybrať si, kto bude hostiť vaše konverzácie. V aplikácii Element si môžete vybrať, či chcete hosťovať rôznymi spôsobmi:\n\n1. Získajte bezplatný účet na verejnom serveri matrix.org\n2. Vlastný hosting účtu spustením servera na vlastnom hardvéri.\n3. Zaregistrujte si účet na vlastnom serveri jednoduchým predplatením hostingovej platformy Element Matrix Services.\n\nPrečo si vybrať práve Element?\n\nVLASTNÍTE SVOJE DÁTA: Vy rozhodujete o tom, kde budú vaše dáta a správy uložené. Vlastníte ich a máte nad nimi kontrolu, nie nejaká MEGAKORPORÁCIA, ktorá vaše údaje doluje alebo poskytuje prístup tretím stranám.\n\nOTVORENÁ KONVERZÁCIA A SPOLUPRÁCA: Môžete chatovať s kýmkoľvek iným v sieti Matrix, či už používa aplikáciu Element alebo inú aplikáciu Matrix, a dokonca aj vtedy, ak používa iný systém správ typu Slack, IRC alebo XMPP.\n\nSUPER-BEZPEČNÉ: Skutočné end-to-end šifrovanie (správy môžu dešifrovať len účastníci konverzácie) a krížové podpisovanie na overenie zariadení účastníkov konverzácie.\n\nKOMPLETNÁ KOMUNIKÁCIA: Správy, hlasové a videohovory, zdieľanie súborov, zdieľanie obrazovky a celý rad integrácií, botov a widgetov. Vytvárajte miestnosti, komunity, zostaňte v kontakte a vybavujte veci.\n\nKDEKOĽVEK STE: Zostaňte v kontakte, nech ste kdekoľvek, s plne synchronizovanou históriou správ vo všetkých zariadeniach a na webe na https://element.io/app.";
|
||||
"create" = "Vytvoriť";
|
||||
"continue" = "Pokračovať";
|
||||
"back" = "Späť";
|
||||
"next" = "Ďalej";
|
||||
|
||||
// Actions
|
||||
"view" = "Zobraziť";
|
||||
"warning" = "Upozornenie";
|
||||
"title_groups" = "Komunity";
|
||||
"title_rooms" = "Miestnosti";
|
||||
"title_people" = "Ľudia";
|
||||
"title_favourites" = "Obľúbené";
|
||||
|
||||
// Titles
|
||||
"title_home" = "Domov";
|
||||
"room_creation_make_private" = "Zmeniť na súkromnú";
|
||||
"room_creation_keep_private" = "Ponechať ako súkromnú";
|
||||
"room_creation_make_public_prompt_title" = "Zverejniť túto konverzáciu?";
|
||||
"room_creation_make_public" = "Zverejniť";
|
||||
"room_creation_appearance_name" = "Názov";
|
||||
|
||||
// Errors
|
||||
"error_user_already_logged_in" = "Zdá sa, že sa pokúšate pripojiť k inému domovskému serveru. Chcete sa odhlásiť?";
|
||||
"social_login_button_title_sign_up" = "Prihlásiť sa s %@";
|
||||
"social_login_list_title_sign_up" = "alebo";
|
||||
"social_login_list_title_sign_in" = "alebo";
|
||||
"auth_softlogout_clear_data_sign_out" = "Odhlásiť sa";
|
||||
"auth_softlogout_clear_data_sign_out_title" = "Ste si istí?";
|
||||
"room_creation_invite_another_user" = "Používateľské ID, meno alebo emailová adresa";
|
||||
"auth_reset_password_error_not_found" = "Zdá sa, že vaša emailová adresa nie je priradená k žiadnemu Matrix ID na tomto domovskom serveri.";
|
||||
"auth_email_validation_message" = "Prosím, skontrolujte si email, aby ste mohli pokračovať v registrácii";
|
||||
"rooms_empty_view_title" = "Miestnosti";
|
||||
|
||||
// Rooms tab
|
||||
"room_directory_no_public_room" = "Nie sú dostupné žiadne verejné miestnosti";
|
||||
"room_recents_join_room_prompt" = "Zadajte ID miestnosti alebo alias";
|
||||
"room_recents_join_room_title" = "Vstúpiť do miestnosti";
|
||||
"room_recents_join_room" = "Vstúpiť do miestnosti";
|
||||
"room_recents_create_empty_room" = "Vytvoriť miestnosť";
|
||||
"room_recents_start_chat_with" = "Začať konverzáciu";
|
||||
"room_recents_suggested_rooms_section" = "NAVRHOVANÉ MIESTNOSTI";
|
||||
"room_recents_invites_section" = "POZVANIA";
|
||||
"room_recents_server_notice_section" = "UPOZORNENIA SYSTÉMU";
|
||||
"room_recents_low_priority_section" = "NÍZKA PRIORITA";
|
||||
"room_recents_no_conversation" = "Žiadne miestnosti";
|
||||
"room_recents_conversations_section" = "MIESTNOSTI";
|
||||
"room_recents_people_section" = "ĽUDIA";
|
||||
"room_recents_favourites_section" = "OBĽÚBENÉ";
|
||||
"room_creation_public_room" = "Táto konverzácia je verejná";
|
||||
"room_creation_private_room" = "Táto konverzácia je súkromná";
|
||||
"room_creation_privacy" = "Súkromie";
|
||||
"room_creation_appearance" = "Vzhľad";
|
||||
"room_creation_account" = "Účet";
|
||||
|
||||
// Chat creation
|
||||
"room_creation_title" = "Nová konverzácia";
|
||||
"room_participants_now" = "teraz";
|
||||
"room_participants_idle" = "Nečinný";
|
||||
"room_participants_unknown" = "Neznámy";
|
||||
"room_participants_offline" = "Nedostupný";
|
||||
"room_participants_online" = "Prítomný";
|
||||
"room_participants_invited_section" = "POZVANÍ";
|
||||
"room_participants_invite_prompt_title" = "Potvrdenie";
|
||||
"room_participants_remove_prompt_title" = "Potvrdenie";
|
||||
"room_participants_leave_prompt_title_for_dm" = "Opustiť";
|
||||
|
||||
// Chat participants
|
||||
"room_participants_title" = "Účastníci";
|
||||
"search_default_placeholder" = "Hľadať";
|
||||
"search_files" = "Súbory";
|
||||
"search_people" = "Ľudia";
|
||||
"auth_softlogout_sign_in" = "Prihlásiť sa";
|
||||
"people_empty_view_title" = "Ľudia";
|
||||
"people_no_conversation" = "Žiadne konverzácie";
|
||||
"people_conversation_section" = "KONVERZÁCIE";
|
||||
|
||||
// People tab
|
||||
"people_invites_section" = "POZVANIA";
|
||||
"auth_reset_password_error_unauthorized" = "Nepodarilo sa overiť emailovú adresu: Uistite sa, že ste správne klikli na odkaz v emailovej správe";
|
||||
"auth_reset_password_next_step_button" = "Overil som svoju emailovú adresu";
|
||||
"auth_reset_password_email_validation_message" = "Na adresu %s bola odoslaná správa. Po kliknutí na odkaz, ktorý obsahuje, kliknite nižšie.";
|
||||
"auth_reset_password_missing_password" = "Musíte zadať nové heslo.";
|
||||
"auth_reset_password_missing_email" = "Musíte zadať emailovú adresu prepojenú s vašim účtom.";
|
||||
"auth_reset_password_message" = "Ak chcete obnoviť vaše heslo, zadajte emailovú adresu prepojenú s vašim účtom:";
|
||||
"auth_recaptcha_message" = "Tento domovský server by sa rád uistil, že nie ste robot";
|
||||
"auth_msisdn_validation_error" = "Nie je možné overiť telefónne číslo.";
|
||||
"auth_msisdn_validation_message" = "Odoslali sme vám SMS správu, ktorá obsahuje overovací kód. Prosím, zadajte ho nižšie.";
|
||||
"auth_msisdn_validation_title" = "Čaká sa na overenie";
|
||||
"auth_use_server_options" = "Použiť vlastné možnosti servera (pre pokročilých)";
|
||||
"auth_email_not_found" = "Nepodarilo sa odoslať e-mail: Táto e-mailová adresa nebola nájdená";
|
||||
"auth_forgot_password_error_no_configured_identity_server" = "Nie je nastavený žiadny server identity: pridajte ho na obnovenie hesla.";
|
||||
"auth_forgot_password" = "Zabudli ste heslo?";
|
||||
"auth_username_in_use" = "Používateľské meno sa už používa";
|
||||
"auth_password_dont_match" = "Heslá sa nezhodujú";
|
||||
"auth_phone_in_use" = "Toto telefónne číslo sa už používa";
|
||||
"auth_email_in_use" = "Táto emailová adresa sa už používa";
|
||||
"auth_missing_email_or_phone" = "Chýba emailová adresa alebo telefónne číslo";
|
||||
"auth_missing_phone" = "Chýba telefónne číslo";
|
||||
"auth_missing_email" = "Chýba emailová adresa";
|
||||
"auth_add_phone_message_2" = "Nastavte si telefónne číslo, aby bolo neskôr možné voliteľné vyhľadanie ľuďmi, ktorí vás poznajú.";
|
||||
"auth_add_email_message_2" = "Nastavte e-mail na obnovenie účtu a neskôr na voliteľné vyhľadanie ľuďmi, ktorí vás poznajú.";
|
||||
"auth_missing_password" = "Chýba heslo";
|
||||
"auth_invalid_phone" = "Toto nevyzerá ako platné telefónne číslo";
|
||||
"auth_invalid_email" = "Toto nevyzerá ako platná e-mailová adresa";
|
||||
"auth_invalid_password" = "Heslo je veľmi krátke (minimálne 6 znakov)";
|
||||
"auth_invalid_user_name" = "Používateľské meno môže obsahovať len písmená, číslice, bodky, pomlčky a podčiarkovníky";
|
||||
"auth_invalid_login_param" = "Nesprávne používateľské meno a/alebo heslo";
|
||||
"auth_identity_server_placeholder" = "URL (napr. https://vector.im)";
|
||||
"auth_home_server_placeholder" = "URL (napr. https://matrix.org)";
|
||||
"auth_repeat_new_password_placeholder" = "Potvrďte svoje nové heslo";
|
||||
"auth_repeat_password_placeholder" = "Zopakovať heslo";
|
||||
"auth_phone_placeholder" = "Telefónne číslo";
|
||||
"auth_optional_phone_placeholder" = "Telefónne číslo (nepovinné)";
|
||||
"auth_email_placeholder" = "Emailová adresa";
|
||||
"auth_optional_email_placeholder" = "Emailová adresa (nepovinné)";
|
||||
"auth_user_name_placeholder" = "Meno používateľa";
|
||||
"auth_new_password_placeholder" = "Nové heslo";
|
||||
"auth_password_placeholder" = "Heslo";
|
||||
"auth_user_id_placeholder" = "Emailová adresa alebo používateľské meno";
|
||||
"auth_return_to_login" = "Vrátiť sa na prihlasovaciu obrazovku";
|
||||
"auth_send_reset_email" = "Poslať obnovovací email";
|
||||
"auth_login_single_sign_on" = "Prihlásiť sa";
|
||||
"auth_skip" = "Preskočiť";
|
||||
"auth_submit" = "Odoslať";
|
||||
"auth_register" = "Zaregistrovať";
|
||||
|
||||
// Authentication
|
||||
"auth_login" = "Prihlásiť sa";
|
||||
"callbar_only_single_paused" = "Pozastavený hovor";
|
||||
"callbar_active_and_multiple_paused" = "1 aktívny hovor (%@) - %@ pozastavených hovorov";
|
||||
"callbar_active_and_single_paused" = "1 aktívny hovor (%@) - 1 pozastavený hovor";
|
||||
|
||||
// Call Bar
|
||||
"callbar_only_single_active" = "Ťuknutím sa vrátite k hovoru (%@)";
|
||||
"switch" = "Prepnúť";
|
||||
"sending" = "Odosielanie";
|
||||
@@ -353,10 +353,10 @@
|
||||
|
||||
// Others
|
||||
"or" = "або";
|
||||
"event_formatter_widget_removed_by_you" = "Ви вилучили розширення: %@";
|
||||
"event_formatter_widget_removed_by_you" = "Ви вилучили віджет: %@";
|
||||
|
||||
// Events formatter with you
|
||||
"event_formatter_widget_added_by_you" = "Ви додали розширення: %@";
|
||||
"event_formatter_widget_added_by_you" = "Ви додали віджет: %@";
|
||||
"event_formatter_group_call_incoming" = "%@ у %@";
|
||||
"event_formatter_group_call_leave" = "Вийти";
|
||||
"room_join_group_call" = "Приєднатися";
|
||||
@@ -429,15 +429,15 @@
|
||||
"room_multiple_typing_notification" = "%@ та інші";
|
||||
"room_place_voice_call" = "Голосовий виклик";
|
||||
"room_widget_permission_room_id_permission" = "ID кімнати";
|
||||
"room_widget_permission_widget_id_permission" = "ID розширення";
|
||||
"room_widget_permission_widget_id_permission" = "ID віджету";
|
||||
"room_widget_permission_theme_permission" = "Ваша тема";
|
||||
"room_widget_permission_user_id_permission" = "Ваш ID користувача";
|
||||
"room_widget_permission_avatar_url_permission" = "URL-адреса вашого аватара";
|
||||
"room_widget_permission_display_name_permission" = "Ваше показуване ім'я";
|
||||
"room_widget_permission_creator_info_title" = "Це розширення додано:";
|
||||
"room_widget_permission_creator_info_title" = "Цей віджет додано:";
|
||||
|
||||
// Room widget permissions
|
||||
"room_widget_permission_title" = "Завантажити розширення";
|
||||
"room_widget_permission_title" = "Завантажити віджет";
|
||||
"widget_picker_manage_integrations" = "Керувати інтеграціями…";
|
||||
"room_accessibility_video_call" = "Відеовиклик";
|
||||
"room_accessibility_call" = "Виклик";
|
||||
@@ -949,7 +949,7 @@
|
||||
"room_event_action_reply" = "Відповідь";
|
||||
"room_event_action_delete_confirmation_title" = "Видалити не надіслане повідомлення";
|
||||
"room_event_action_report_prompt_ignore_user" = "Бажаєте сховати усі повідомлення від цього користувача?";
|
||||
"room_event_action_kick_prompt_reason" = "Причина викидання користувача";
|
||||
"room_event_action_kick_prompt_reason" = "Причина вилучення користувача";
|
||||
"room_participants_action_mention" = "Згадати";
|
||||
"room_participants_action_unignore" = "Показати усі повідомлення від цього користувача";
|
||||
"room_participants_action_ignore" = "Сховати усі повідомлення від цього користувача";
|
||||
@@ -963,3 +963,62 @@
|
||||
"find_your_contacts_button_title" = "Пошук контактів";
|
||||
"contacts_address_book_permission_denied_alert_message" = "Увімкніть контакти з налаштувань пристрою.";
|
||||
"contacts_address_book_permission_denied_alert_title" = "Контакти вимкнено";
|
||||
"network_offline_prompt" = "Відсутнє зʼєднання з інтернетом.";
|
||||
"room_no_privileges_to_create_group_call" = "Щоб розпочати виклик ви повинні бути адміністратором або модератором.";
|
||||
"room_event_action_delete_confirmation_message" = "Ви впевнені, що хочете видалити це не надіслане повідомлення?";
|
||||
"room_event_action_report_prompt_reason" = "Причина скарги на цей вміст";
|
||||
"room_event_action_report" = "Поскаржитися на вміст";
|
||||
"room_event_action_view_decrypted_source" = "Переглянути розшифроване джерело";
|
||||
"room_event_action_view_source" = "Переглянути джерело";
|
||||
"room_event_action_permalink" = "Постійне посилання";
|
||||
"room_event_action_forward" = "Переслати";
|
||||
"room_resend_unsent_messages" = "Повторно надіслати не надіслані повідомлення";
|
||||
"room_prompt_cancel" = "скасувати всі";
|
||||
"room_prompt_resend" = "Надіслати всі";
|
||||
"room_unsent_messages_cancel_message" = "Ви впевнені, що хочете видалити не надіслані повідомлення у цій кімнаті?";
|
||||
"room_delete_unsent_messages" = "Видалити не надіслані повідомлення";
|
||||
"room_unsent_messages_cancel_title" = "Видалити не надіслані повідомлення";
|
||||
"encrypted_room_message_reply_to_placeholder" = "Надіслати зашифровану відповідь…";
|
||||
"encrypted_room_message_placeholder" = "Надіслати зашифроване повідомлення…";
|
||||
"room_do_not_have_permission_to_post" = "Вам не дозволено дописувати у цій кімнаті";
|
||||
"room_accessiblity_scroll_to_bottom" = "Униз";
|
||||
"room_jump_to_first_unread" = "До непрочитаних";
|
||||
|
||||
// Chat
|
||||
"room_slide_to_end_group_call" = "Проведіть пальцем, щоб завершити виклик для всіх";
|
||||
"room_participants_security_information_room_not_encrypted_for_dm" = "Повідомлення тут не захищено наскрізним шифруванням.";
|
||||
"room_participants_security_information_room_not_encrypted" = "Повідомлення у цій кімнаті не захищено наскрізним шифруванням.";
|
||||
"room_participants_action_set_default_power_level" = "Скинути до звичайного користувача";
|
||||
"share_extension_low_quality_video_title" = "Відео буде надіслано у низькій якості";
|
||||
"deactivate_account_informations_part3" = "\n\nДеактивація вашого облікового запису ";
|
||||
"service_terms_modal_footer" = "Потім це можна вимкнути у налаштуваннях.";
|
||||
"service_terms_modal_table_header_identity_server" = "УМОВИ СЕРВЕРА ІДЕНТИФІКАЦІЇ";
|
||||
"service_terms_modal_table_header_integration_manager" = "УМОВИ МЕНЕДЖЕРА ІНТЕГРАЦІЙ";
|
||||
|
||||
// Alert explaining what an identity server / integration manager is.
|
||||
"service_terms_modal_information_title_identity_server" = "Сервер ідентифікації";
|
||||
"service_terms_modal_information_title_integration_manager" = "Менеджер інтеграцій";
|
||||
|
||||
// Room key request dialog
|
||||
"e2e_room_key_request_title" = "Запит ключів шифрування";
|
||||
"share_extension_send_now" = "Надіслати зараз";
|
||||
"service_terms_modal_accept_button" = "Прийняти";
|
||||
"room_details_flair_invalid_id_prompt_msg" = "%@ — неправильний ідентифікатор спільноти";
|
||||
"room_details_flair_invalid_id_prompt_title" = "Неправильний формат";
|
||||
"room_details_new_flair_placeholder" = "Додати новий ID спільноти (напр., +foo%@)";
|
||||
"room_details_access_section_invited_only" = "Лише запрошені люди";
|
||||
"room_details_access_section_directory_toggle_for_dm" = "Показувати у каталозі кімнат";
|
||||
"room_details_access_section_directory_toggle" = "Додати кімнату в каталог кімнат";
|
||||
"room_details_addresses_disable_main_address_prompt_title" = "Попередження про основну адресу";
|
||||
"room_details_addresses_invalid_address_prompt_msg" = "%@ — неправильний формат псевдоніма";
|
||||
"room_details_addresses_invalid_address_prompt_title" = "Неправильний формат псевдоніма";
|
||||
"room_details_no_local_addresses_for_dm" = "Ще немає локальних адрес";
|
||||
"room_details_no_local_addresses" = "У цієї кімнати немає локальних адрес";
|
||||
"room_details_addresses_section" = "Адреси";
|
||||
"room_details_history_section_prompt_msg" = "Змінює, хто може читати історію. Застосовуватиметься лише до майбутніх повідомлень у цій кімнаті. Видимість наявної історії не зміниться.";
|
||||
"room_details_history_section_prompt_title" = "Попередження приватності";
|
||||
"room_details_history_section_members_only_since_joined" = "Лише учасники (від часу приєднання)";
|
||||
"room_details_history_section_members_only_since_invited" = "Лише учасники (від часу запрошення)";
|
||||
"room_details_history_section_members_only" = "Лише учасники (від часу вибору цієї опції)";
|
||||
"room_details_history_section_anyone" = "Будь-хто";
|
||||
"room_details_history_section" = "Хто може переглядати історію?";
|
||||
|
||||
@@ -1547,3 +1547,18 @@
|
||||
"contacts_address_book_permission_denied_alert_title" = "联系人被禁用";
|
||||
"space_home_show_all_rooms" = "显示所有聊天室";
|
||||
"room_event_action_forward" = "转发";
|
||||
"poll_edit_form_add_option" = "添加选项";
|
||||
"poll_edit_form_option_number" = "选项 %d";
|
||||
"poll_edit_form_create_options" = "创建选项";
|
||||
"poll_edit_form_input_placeholder" = "写些东西";
|
||||
"poll_edit_form_question_or_topic" = "问题或话题";
|
||||
"poll_edit_form_poll_question_or_topic" = "投票问题或话题";
|
||||
|
||||
// Mark: - Polls
|
||||
|
||||
"poll_edit_form_create_poll" = "创建投票";
|
||||
"share_extension_send_now" = "立即发送";
|
||||
"share_extension_low_quality_video_message" = "以 %@ 发送画质更好,或者用下方的低画质发送。";
|
||||
"share_extension_low_quality_video_title" = "将以低画质发送视频";
|
||||
"settings_discovery_accept_terms" = "接受身份服务器条款";
|
||||
"settings_about" = "关于";
|
||||
|
||||
@@ -143,10 +143,13 @@ internal enum Asset {
|
||||
internal static let videoCall = ImageAsset(name: "video_call")
|
||||
internal static let voiceCallHangonIcon = ImageAsset(name: "voice_call_hangon_icon")
|
||||
internal static let voiceCallHangupIcon = ImageAsset(name: "voice_call_hangup_icon")
|
||||
internal static let pollCheckboxDefault = ImageAsset(name: "poll_checkbox_default")
|
||||
internal static let pollCheckboxSelected = ImageAsset(name: "poll_checkbox_selected")
|
||||
internal static let pollDeleteIcon = ImageAsset(name: "poll_delete_icon")
|
||||
internal static let pollDeleteOptionIcon = ImageAsset(name: "poll_delete_option_icon")
|
||||
internal static let pollEditIcon = ImageAsset(name: "poll_edit_icon")
|
||||
internal static let pollEndIcon = ImageAsset(name: "poll_end_icon")
|
||||
internal static let pollWinnerIcon = ImageAsset(name: "poll_winner_icon")
|
||||
internal static let urlPreviewClose = ImageAsset(name: "url_preview_close")
|
||||
internal static let urlPreviewCloseDark = ImageAsset(name: "url_preview_close_dark")
|
||||
internal static let voiceMessageCancelGradient = ImageAsset(name: "voice_message_cancel_gradient")
|
||||
|
||||
@@ -2379,7 +2379,7 @@ public class VectorL10n: NSObject {
|
||||
public static var pollEditFormInputPlaceholder: String {
|
||||
return VectorL10n.tr("Vector", "poll_edit_form_input_placeholder")
|
||||
}
|
||||
/// Option %d
|
||||
/// Option %lu
|
||||
public static func pollEditFormOptionNumber(_ p1: Int) -> String {
|
||||
return VectorL10n.tr("Vector", "poll_edit_form_option_number", p1)
|
||||
}
|
||||
@@ -2387,10 +2387,82 @@ public class VectorL10n: NSObject {
|
||||
public static var pollEditFormPollQuestionOrTopic: String {
|
||||
return VectorL10n.tr("Vector", "poll_edit_form_poll_question_or_topic")
|
||||
}
|
||||
/// OK
|
||||
public static var pollEditFormPostFailureAction: String {
|
||||
return VectorL10n.tr("Vector", "poll_edit_form_post_failure_action")
|
||||
}
|
||||
/// Please try again
|
||||
public static var pollEditFormPostFailureSubtitle: String {
|
||||
return VectorL10n.tr("Vector", "poll_edit_form_post_failure_subtitle")
|
||||
}
|
||||
/// Failed to post poll
|
||||
public static var pollEditFormPostFailureTitle: String {
|
||||
return VectorL10n.tr("Vector", "poll_edit_form_post_failure_title")
|
||||
}
|
||||
/// Question or topic
|
||||
public static var pollEditFormQuestionOrTopic: String {
|
||||
return VectorL10n.tr("Vector", "poll_edit_form_question_or_topic")
|
||||
}
|
||||
/// OK
|
||||
public static var pollTimelineNotClosedAction: String {
|
||||
return VectorL10n.tr("Vector", "poll_timeline_not_closed_action")
|
||||
}
|
||||
/// Please try again
|
||||
public static var pollTimelineNotClosedSubtitle: String {
|
||||
return VectorL10n.tr("Vector", "poll_timeline_not_closed_subtitle")
|
||||
}
|
||||
/// Failed to end poll
|
||||
public static var pollTimelineNotClosedTitle: String {
|
||||
return VectorL10n.tr("Vector", "poll_timeline_not_closed_title")
|
||||
}
|
||||
/// 1 vote
|
||||
public static var pollTimelineOneVote: String {
|
||||
return VectorL10n.tr("Vector", "poll_timeline_one_vote")
|
||||
}
|
||||
/// Final results based on %lu votes
|
||||
public static func pollTimelineTotalFinalResults(_ p1: Int) -> String {
|
||||
return VectorL10n.tr("Vector", "poll_timeline_total_final_results", p1)
|
||||
}
|
||||
/// Final results based on 1 vote
|
||||
public static var pollTimelineTotalFinalResultsOneVote: String {
|
||||
return VectorL10n.tr("Vector", "poll_timeline_total_final_results_one_vote")
|
||||
}
|
||||
/// No votes cast
|
||||
public static var pollTimelineTotalNoVotes: String {
|
||||
return VectorL10n.tr("Vector", "poll_timeline_total_no_votes")
|
||||
}
|
||||
/// 1 vote cast
|
||||
public static var pollTimelineTotalOneVote: String {
|
||||
return VectorL10n.tr("Vector", "poll_timeline_total_one_vote")
|
||||
}
|
||||
/// 1 vote cast. Vote to the see the results
|
||||
public static var pollTimelineTotalOneVoteNotVoted: String {
|
||||
return VectorL10n.tr("Vector", "poll_timeline_total_one_vote_not_voted")
|
||||
}
|
||||
/// %lu votes cast
|
||||
public static func pollTimelineTotalVotes(_ p1: Int) -> String {
|
||||
return VectorL10n.tr("Vector", "poll_timeline_total_votes", p1)
|
||||
}
|
||||
/// %lu votes cast. Vote to the see the results
|
||||
public static func pollTimelineTotalVotesNotVoted(_ p1: Int) -> String {
|
||||
return VectorL10n.tr("Vector", "poll_timeline_total_votes_not_voted", p1)
|
||||
}
|
||||
/// OK
|
||||
public static var pollTimelineVoteNotRegisteredAction: String {
|
||||
return VectorL10n.tr("Vector", "poll_timeline_vote_not_registered_action")
|
||||
}
|
||||
/// Sorry, your vote was not registered, please try again
|
||||
public static var pollTimelineVoteNotRegisteredSubtitle: String {
|
||||
return VectorL10n.tr("Vector", "poll_timeline_vote_not_registered_subtitle")
|
||||
}
|
||||
/// Vote not registered
|
||||
public static var pollTimelineVoteNotRegisteredTitle: String {
|
||||
return VectorL10n.tr("Vector", "poll_timeline_vote_not_registered_title")
|
||||
}
|
||||
/// %lu votes
|
||||
public static func pollTimelineVotesCount(_ p1: Int) -> String {
|
||||
return VectorL10n.tr("Vector", "poll_timeline_votes_count", p1)
|
||||
}
|
||||
/// Preview
|
||||
public static var preview: String {
|
||||
return VectorL10n.tr("Vector", "preview")
|
||||
@@ -2915,6 +2987,10 @@ public class VectorL10n: NSObject {
|
||||
public static var roomEventActionEdit: String {
|
||||
return VectorL10n.tr("Vector", "room_event_action_edit")
|
||||
}
|
||||
/// End poll
|
||||
public static var roomEventActionEndPoll: String {
|
||||
return VectorL10n.tr("Vector", "room_event_action_end_poll")
|
||||
}
|
||||
/// Forward
|
||||
public static var roomEventActionForward: String {
|
||||
return VectorL10n.tr("Vector", "room_event_action_forward")
|
||||
@@ -2951,6 +3027,10 @@ public class VectorL10n: NSObject {
|
||||
public static var roomEventActionRedact: String {
|
||||
return VectorL10n.tr("Vector", "room_event_action_redact")
|
||||
}
|
||||
/// Remove poll
|
||||
public static var roomEventActionRemovePoll: String {
|
||||
return VectorL10n.tr("Vector", "room_event_action_remove_poll")
|
||||
}
|
||||
/// Reply
|
||||
public static var roomEventActionReply: String {
|
||||
return VectorL10n.tr("Vector", "room_event_action_reply")
|
||||
@@ -4535,14 +4615,14 @@ public class VectorL10n: NSObject {
|
||||
public static var settingsLabsEnableRingingForGroupCalls: String {
|
||||
return VectorL10n.tr("Vector", "settings_labs_enable_ringing_for_group_calls")
|
||||
}
|
||||
/// Polls
|
||||
public static var settingsLabsEnabledPolls: String {
|
||||
return VectorL10n.tr("Vector", "settings_labs_enabled_polls")
|
||||
}
|
||||
/// React to messages with emoji
|
||||
public static var settingsLabsMessageReaction: String {
|
||||
return VectorL10n.tr("Vector", "settings_labs_message_reaction")
|
||||
}
|
||||
/// Voice messages
|
||||
public static var settingsLabsVoiceMessages: String {
|
||||
return VectorL10n.tr("Vector", "settings_labs_voice_messages")
|
||||
}
|
||||
/// LINKS
|
||||
public static var settingsLinks: String {
|
||||
return VectorL10n.tr("Vector", "settings_links")
|
||||
|
||||
@@ -165,6 +165,9 @@ final class RiotSettings: NSObject {
|
||||
|
||||
@UserDefault(key: "roomScreenAllowFilesAction", defaultValue: BuildSettings.roomScreenAllowFilesAction, storage: defaults)
|
||||
var roomScreenAllowFilesAction
|
||||
|
||||
@UserDefault(key: "roomScreenAllowPollsAction", defaultValue: false, storage: defaults)
|
||||
var roomScreenAllowPollsAction
|
||||
|
||||
@UserDefault(key: "roomScreenShowsURLPreviews", defaultValue: true, storage: defaults)
|
||||
var roomScreenShowsURLPreviews
|
||||
|
||||
@@ -2050,6 +2050,11 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro
|
||||
|
||||
- (void)scrollToTheTopTheNextRoomWithMissedNotificationsInSection:(NSInteger)section
|
||||
{
|
||||
if (section < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UITableViewCell *firstVisibleCell;
|
||||
NSIndexPath *firstVisibleCellIndexPath;
|
||||
|
||||
|
||||
71
Riot/Modules/Common/Views/CopyableLabel.swift
Normal file
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
/// Enables to copy text content of the label
|
||||
/// https://stackoverflow.com/a/62978837
|
||||
class CopyableLabel: UILabel {
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
self.sharedInit()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
self.sharedInit()
|
||||
}
|
||||
|
||||
func sharedInit() {
|
||||
self.isUserInteractionEnabled = true
|
||||
self.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(self.showMenu)))
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
@objc func showMenu(sender: AnyObject?) {
|
||||
self.becomeFirstResponder()
|
||||
|
||||
let menu = UIMenuController.shared
|
||||
|
||||
if !menu.isMenuVisible {
|
||||
if #available(iOS 13.0, *) {
|
||||
menu.showMenu(from: self, rect: self.bounds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Overrides
|
||||
|
||||
override func copy(_ sender: Any?) {
|
||||
let board = UIPasteboard.general
|
||||
|
||||
board.string = self.text
|
||||
|
||||
// Note that the UIMenuController will be dismissed by itself after copying the text
|
||||
}
|
||||
|
||||
override var canBecomeFirstResponder: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
|
||||
return action == #selector(UIResponderStandardEditActions.copy)
|
||||
}
|
||||
}
|
||||
@@ -476,3 +476,5 @@
|
||||
"auth_invalid_user_name" = "Vigane kasutajanimi";
|
||||
"rename" = "Muuda nime";
|
||||
"room_displayname_all_other_members_left" = "%@ (lahkus(id))";
|
||||
"attachment_unsupported_preview_message" = "See failitüüp ei ole toetatud.";
|
||||
"attachment_unsupported_preview_title" = "Eelvaate kuvamine ei õnnestu";
|
||||
|
||||
@@ -477,3 +477,5 @@
|
||||
"auth_invalid_user_name" = "Érvénytelen felhasználónév";
|
||||
"rename" = "Átnevez";
|
||||
"room_displayname_all_other_members_left" = "%@ (Bal)";
|
||||
"attachment_unsupported_preview_message" = "Ez a fájl típus nem támogatott.";
|
||||
"attachment_unsupported_preview_title" = "Az előnézetet nem lehet megjeleníteni";
|
||||
|
||||
@@ -419,7 +419,7 @@
|
||||
|
||||
// Attachment
|
||||
"attachment_size_prompt" = "Apakah Anda ingin mengirimnya sebagai:";
|
||||
"room_member_power_level_prompt" = "Anda tidak akan lagi membatalkan perubahan ini ketika Anda mempromosikan penggunanya untuk memiliki level kekuatan yang sama dengan Anda sendiri.\nApakah Anda yakin?";
|
||||
"room_member_power_level_prompt" = "Anda tidak akan lagi membatalkan perubahan ini ketika Anda mempromosikan penggunanya untuk memiliki tingkat daya yang sama dengan Anda sendiri.\nApakah Anda yakin?";
|
||||
|
||||
// Room members
|
||||
"room_member_ignore_prompt" = "Apakah Anda yakin untuk menyembunyikan semua pesan dari pengguna ini?";
|
||||
@@ -555,3 +555,5 @@
|
||||
"notice_room_aliases" = "Alias ruangannya adalah: %@";
|
||||
"notice_room_power_level_event_requirement" = "Tingkat daya minimum yang terkait dengan peristiwa adalah:";
|
||||
"notice_room_power_level_acting_requirement" = "Tingkat daya minimum yang harus dimiliki pengguna sebelum bertindak adalah:";
|
||||
"attachment_unsupported_preview_title" = "Tidak dapat ditampilkan";
|
||||
"attachment_unsupported_preview_message" = "Tipe file ini tidak didukung.";
|
||||
|
||||
@@ -476,3 +476,5 @@
|
||||
"room_displayname_all_other_members_left" = "%@ (Uscito)";
|
||||
"auth_username_in_use" = "Nome utente in uso";
|
||||
"rename" = "Rinomina";
|
||||
"attachment_unsupported_preview_message" = "Questo tipo di file non è supportato.";
|
||||
"attachment_unsupported_preview_title" = "Anteprima non disponibile";
|
||||
|
||||
@@ -532,3 +532,11 @@
|
||||
"attachment_size_prompt_message" = "U kunt dit uitzetten in uw instellingen.";
|
||||
"attachment_size_prompt_title" = "Bevestig de afmeting om te versturen";
|
||||
"room_displayname_all_other_participants_left" = "%@ (vertrok)";
|
||||
"auth_reset_password_error_not_found" = "Niet gevonden";
|
||||
"auth_reset_password_error_unauthorized" = "Niet toegestaan";
|
||||
"auth_invalid_user_name" = "Ongeldige inlognaam";
|
||||
"attachment_unsupported_preview_message" = "Dit bestandstype wordt niet ondersteund.";
|
||||
"attachment_unsupported_preview_title" = "Kan geen voorbeeld geven";
|
||||
"room_displayname_all_other_members_left" = "%@ (Vertrok)";
|
||||
"auth_username_in_use" = "Inlognaam al in gebruik";
|
||||
"rename" = "Hernoemen";
|
||||
|
||||
@@ -477,3 +477,5 @@
|
||||
"auth_invalid_user_name" = "Nome de usuária(o) inválido";
|
||||
"rename" = "Renomear";
|
||||
"room_displayname_all_other_members_left" = "%@ (Saiu)";
|
||||
"attachment_unsupported_preview_message" = "Este tipo de arquivo não é suportado.";
|
||||
"attachment_unsupported_preview_title" = "Incapaz de previsualizar";
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -519,3 +519,16 @@
|
||||
"rename" = "Перейменувати";
|
||||
"room_creation_alias_title" = "Псевдоніми кімнати:";
|
||||
"account_email_validation_error" = "Не вдалося перевірити адресу електронної пошти. Перевірте свою електронну пошту та натисніть на посилання в ній. Після цього натисніть продовжити";
|
||||
"auth_reset_password_error_not_found" = "Не знайдено";
|
||||
"auth_reset_password_error_unauthorized" = "Не авторизовано";
|
||||
"auth_invalid_user_name" = "Хибне імʼя користувача";
|
||||
"ssl_could_not_verify" = "Не вдалося перевірити справжність сервера ідентифікації.";
|
||||
"call_consulting_with_user" = "Консультація з %@";
|
||||
"local_contacts_access_discovery_warning_title" = "Виявлення користувачів";
|
||||
"attachment_large_with_resolution" = "Великий %@ (~%@)";
|
||||
"attachment_medium_with_resolution" = "Середній %@ (~%@)";
|
||||
"attachment_small_with_resolution" = "Малий %@ (~%@)";
|
||||
"attachment_size_prompt_message" = "Ви можете вимкнути це у налаштуваннях.";
|
||||
"attachment_size_prompt_title" = "Підтвердити розмір, щоб надіслати";
|
||||
"room_no_power_to_create_conference_call" = "Вам потрібен дозвіл, щоб надсилати запрошення, щоб розпочати конференцію в цій кімнаті";
|
||||
"room_event_encryption_verify_message" = "Щоб переконатися, що цьому сеансу можна довіряти, зв’яжіться з його власником іншим способом (наприклад, особисто чи телефоном) і запитайте його, чи збігається ключ, який вони бачать у налаштуваннях користувача для цього сеансу, з ключем нижче:\n\nНазва сеансу: %@\nID сеансу: %@\nКлюч сеансу: %@\n\nЯкщо він збігається, натисніть кнопку підтвердження внизу. Якщо ні, значить хтось інший перехоплює цей сеанс, і ви, ймовірно, хочете натиснути кнопку чорного списку.\n\nУ майбутньому цей процес перевірки буде ускладнено.";
|
||||
|
||||
@@ -478,3 +478,5 @@
|
||||
"room_displayname_all_other_members_left" = "%@ (离开)";
|
||||
"auth_username_in_use" = "用户名被占用";
|
||||
"rename" = "重命名";
|
||||
"attachment_unsupported_preview_message" = "文件类型不受支持。";
|
||||
"attachment_unsupported_preview_title" = "无法预览";
|
||||
|
||||
@@ -145,7 +145,9 @@ static NSString *const kMXAppGroupID = @"group.org.matrix";
|
||||
kMXEventTypeStringCallNegotiate,
|
||||
kMXEventTypeStringSticker,
|
||||
kMXEventTypeStringKeyVerificationCancel,
|
||||
kMXEventTypeStringKeyVerificationDone
|
||||
kMXEventTypeStringKeyVerificationDone,
|
||||
kMXEventTypeStringPollStart,
|
||||
kMXEventTypeStringPollStartMSC3381
|
||||
].mutableCopy;
|
||||
|
||||
|
||||
@@ -175,7 +177,9 @@ static NSString *const kMXAppGroupID = @"group.org.matrix";
|
||||
kMXEventTypeStringCallNegotiate,
|
||||
kMXEventTypeStringSticker,
|
||||
kMXEventTypeStringKeyVerificationCancel,
|
||||
kMXEventTypeStringKeyVerificationDone
|
||||
kMXEventTypeStringKeyVerificationDone,
|
||||
kMXEventTypeStringPollStart,
|
||||
kMXEventTypeStringPollStartMSC3381
|
||||
].mutableCopy;
|
||||
|
||||
lastMessageEventTypesAllowList = @[
|
||||
@@ -186,7 +190,9 @@ static NSString *const kMXAppGroupID = @"group.org.matrix";
|
||||
kMXEventTypeStringCallInvite,
|
||||
kMXEventTypeStringCallAnswer,
|
||||
kMXEventTypeStringCallHangup,
|
||||
kMXEventTypeStringSticker
|
||||
kMXEventTypeStringSticker,
|
||||
kMXEventTypeStringPollStart,
|
||||
kMXEventTypeStringPollStartMSC3381
|
||||
].mutableCopy;
|
||||
|
||||
_messageDetailsAllowSharing = YES;
|
||||
|
||||
@@ -2697,11 +2697,27 @@ typedef NS_ENUM (NSUInteger, MXKRoomDataSourceError) {
|
||||
- (BOOL)canPerformActionOnEvent:(MXEvent*)event
|
||||
{
|
||||
BOOL isSent = event.sentState == MXEventSentStateSent;
|
||||
BOOL isRoomMessage = event.eventType == MXEventTypeRoomMessage;
|
||||
|
||||
if (!isSent) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (event.eventType == MXEventTypePollStart) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
BOOL isRoomMessage = (event.eventType == MXEventTypeRoomMessage);
|
||||
|
||||
if (!isRoomMessage) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSString *messageType = event.content[@"msgtype"];
|
||||
if (messageType == nil || [messageType isEqualToString:@"m.bad.encrypted"]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
return isSent && isRoomMessage && messageType && ![messageType isEqualToString:@"m.bad.encrypted"];
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)setState:(MXKDataSourceState)newState
|
||||
|
||||
@@ -1596,7 +1596,11 @@ static NSString *const kHTMLATagRegexPattern = @"<a href=\"(.*?)\">([^<]*)</a>";
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MXEventTypePollStart:
|
||||
{
|
||||
displayText = [MXEventContentPollStart modelFromJSON:event.content].question;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
*error = MXKEventFormatterErrorUnknownEventType;
|
||||
break;
|
||||
|
||||
@@ -140,7 +140,7 @@
|
||||
// Check whether the recents data source is correctly configured.
|
||||
if (recentsDataSource.recentsDataSourceMode == RecentsDataSourceModePeople)
|
||||
{
|
||||
[self scrollToTheTopTheNextRoomWithMissedNotificationsInSection:recentsDataSource.conversationSection];
|
||||
[self scrollToTheTopTheNextRoomWithMissedNotificationsInSection:recentsDataSource.peopleSection];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,8 @@ typedef NS_ENUM(NSInteger, RoomBubbleCellDataTag)
|
||||
RoomBubbleCellDataTagKeyVerificationConclusion,
|
||||
RoomBubbleCellDataTagCall,
|
||||
RoomBubbleCellDataTagGroupCall,
|
||||
RoomBubbleCellDataTagRoomCreationIntro
|
||||
RoomBubbleCellDataTagRoomCreationIntro,
|
||||
RoomBubbleCellDataTagPoll
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -149,6 +149,15 @@ NSString *const URLPreviewDidUpdateNotification = @"URLPreviewDidUpdateNotificat
|
||||
|
||||
// Show timestamps always on right
|
||||
self.displayTimestampForSelectedComponentOnLeftWhenPossible = NO;
|
||||
break;
|
||||
}
|
||||
case MXEventTypePollStart:
|
||||
{
|
||||
self.tag = RoomBubbleCellDataTagPoll;
|
||||
self.collapsable = NO;
|
||||
self.collapsed = NO;
|
||||
|
||||
break;
|
||||
}
|
||||
case MXEventTypeCustom:
|
||||
{
|
||||
@@ -259,6 +268,11 @@ NSString *const URLPreviewDidUpdateNotification = @"URLPreviewDidUpdateNotificat
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (self.tag == RoomBubbleCellDataTagPoll)
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
return [super hasNoDisplay];
|
||||
}
|
||||
|
||||
@@ -417,7 +431,9 @@ NSString *const URLPreviewDidUpdateNotification = @"URLPreviewDidUpdateNotificat
|
||||
{
|
||||
__block NSInteger firstVisibleComponentIndex = NSNotFound;
|
||||
|
||||
if (self.attachment && self.bubbleComponents.count)
|
||||
BOOL isPoll = (self.events.firstObject.eventType == MXEventTypePollStart);
|
||||
|
||||
if ((isPoll || self.attachment) && self.bubbleComponents.count)
|
||||
{
|
||||
firstVisibleComponentIndex = 0;
|
||||
}
|
||||
@@ -826,10 +842,15 @@ NSString *const URLPreviewDidUpdateNotification = @"URLPreviewDidUpdateNotificat
|
||||
case RoomBubbleCellDataTagRoomCreationIntro:
|
||||
shouldAddEvent = NO;
|
||||
break;
|
||||
case RoomBubbleCellDataTagPoll:
|
||||
shouldAddEvent = NO;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// If the current bubbleData supports adding events then check
|
||||
// if the incoming event can be added in
|
||||
if (shouldAddEvent)
|
||||
{
|
||||
switch (event.eventType)
|
||||
@@ -873,6 +894,9 @@ NSString *const URLPreviewDidUpdateNotification = @"URLPreviewDidUpdateNotificat
|
||||
case MXEventTypeCallReject:
|
||||
shouldAddEvent = NO;
|
||||
break;
|
||||
case MXEventTypePollStart:
|
||||
shouldAddEvent = NO;
|
||||
break;
|
||||
case MXEventTypeCustom:
|
||||
{
|
||||
if ([event.type isEqualToString:kWidgetMatrixEventTypeString]
|
||||
|
||||
@@ -89,7 +89,9 @@
|
||||
@property (weak, nonatomic) IBOutlet UIImageView *roomMemberAvatarBadgeImageView;
|
||||
|
||||
@property (weak, nonatomic) IBOutlet UILabel *roomMemberNameLabel;
|
||||
@property (weak, nonatomic) IBOutlet UIView *roomMemberNameLabelMask;
|
||||
@property (weak, nonatomic) IBOutlet UIView *roomMemberNameContainerView;
|
||||
|
||||
@property (weak, nonatomic) IBOutlet UILabel *roomMemberUserIdLabel;
|
||||
|
||||
@property (weak, nonatomic) IBOutlet UILabel *roomMemberStatusLabel;
|
||||
|
||||
@@ -148,17 +150,10 @@
|
||||
memberTitleView.delegate = self;
|
||||
|
||||
// Define directly the navigation titleView with the custom title view instance. Do not use anymore a container.
|
||||
self.navigationItem.titleView = memberTitleView;
|
||||
|
||||
// Add tap gesture on member's name
|
||||
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)];
|
||||
[tap setNumberOfTouchesRequired:1];
|
||||
[tap setNumberOfTapsRequired:1];
|
||||
[tap setDelegate:self];
|
||||
[self.roomMemberNameLabelMask addGestureRecognizer:tap];
|
||||
self.roomMemberNameLabelMask.userInteractionEnabled = YES;
|
||||
self.navigationItem.titleView = memberTitleView;
|
||||
|
||||
// Add tap to show the room member avatar in fullscreen
|
||||
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)];
|
||||
tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)];
|
||||
[tap setNumberOfTouchesRequired:1];
|
||||
[tap setNumberOfTapsRequired:1];
|
||||
@@ -211,6 +206,7 @@
|
||||
|
||||
self.memberHeaderView.backgroundColor = ThemeService.shared.theme.baseColor;
|
||||
self.roomMemberNameLabel.textColor = ThemeService.shared.theme.textPrimaryColor;
|
||||
self.roomMemberUserIdLabel.textColor = ThemeService.shared.theme.textPrimaryColor;
|
||||
|
||||
self.roomMemberStatusLabel.textColor = ThemeService.shared.theme.tintColor;
|
||||
self.roomMemberPowerLevelLabel.textColor = ThemeService.shared.theme.textPrimaryColor;
|
||||
@@ -348,8 +344,12 @@
|
||||
- (void)updateMemberInfo
|
||||
{
|
||||
if (self.mxRoomMember)
|
||||
{
|
||||
self.roomMemberNameLabel.text = self.mxRoomMember.displayname ? self.mxRoomMember.displayname : self.mxRoomMember.userId;
|
||||
{
|
||||
self.roomMemberNameContainerView.hidden = !self.mxRoomMember.displayname;
|
||||
|
||||
self.roomMemberNameLabel.text = self.mxRoomMember.displayname;
|
||||
|
||||
self.roomMemberUserIdLabel.text = self.mxRoomMember.userId;
|
||||
|
||||
// Update member power level
|
||||
MXWeakify(self);
|
||||
@@ -1257,20 +1257,7 @@
|
||||
{
|
||||
UIView *view = tapGestureRecognizer.view;
|
||||
|
||||
if (view == self.roomMemberNameLabelMask && self.mxRoomMember.displayname)
|
||||
{
|
||||
if ([self.roomMemberNameLabel.text isEqualToString:self.mxRoomMember.displayname])
|
||||
{
|
||||
// Display room member matrix id
|
||||
self.roomMemberNameLabel.text = self.mxRoomMember.userId;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Restore display name
|
||||
self.roomMemberNameLabel.text = self.mxRoomMember.displayname;
|
||||
}
|
||||
}
|
||||
else if (view == memberTitleView.memberAvatarMask || view == self.roomMemberAvatarMask)
|
||||
if (view == memberTitleView.memberAvatarMask || view == self.roomMemberAvatarMask)
|
||||
{
|
||||
MXWeakify(self);
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -18,11 +17,12 @@
|
||||
<outlet property="roomMemberAvatarHeaderBackground" destination="ouj-VM-zdT" id="YeD-zt-8y5"/>
|
||||
<outlet property="roomMemberAvatarHeaderBackgroundHeightConstraint" destination="dBL-G6-Yec" id="QXZ-ZP-0Rn"/>
|
||||
<outlet property="roomMemberAvatarMask" destination="MAS-3M-3cg" id="nLI-7d-5Hu"/>
|
||||
<outlet property="roomMemberNameLabel" destination="92g-hC-6jB" id="gcP-wz-8FT"/>
|
||||
<outlet property="roomMemberNameLabelMask" destination="wEo-Mk-SgZ" id="uEv-LU-eEI"/>
|
||||
<outlet property="roomMemberNameContainerView" destination="QHN-zr-wtC" id="T3p-iz-nIk"/>
|
||||
<outlet property="roomMemberNameLabel" destination="i1p-3D-PhB" id="NRi-dr-RPt"/>
|
||||
<outlet property="roomMemberPowerLevelContainerView" destination="Flu-1i-oqi" id="mdj-h2-evz"/>
|
||||
<outlet property="roomMemberPowerLevelLabel" destination="lbh-ZN-XUO" id="DXV-T0-yKs"/>
|
||||
<outlet property="roomMemberStatusLabel" destination="5le-5e-Vml" id="ODo-tG-ewy"/>
|
||||
<outlet property="roomMemberStatusLabel" destination="aBz-2v-ij6" id="eLJ-zY-ZEp"/>
|
||||
<outlet property="roomMemberUserIdLabel" destination="9qc-7W-xzu" id="EZA-rX-xxA"/>
|
||||
<outlet property="tableView" destination="R6u-PR-DcU" id="Cm1-1y-meQ"/>
|
||||
<outlet property="view" destination="gX8-mM-6Ig" id="R3w-s7-1CY"/>
|
||||
</connections>
|
||||
@@ -33,7 +33,7 @@
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="YXr-As-Mqh">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="241"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="343"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ouj-VM-zdT">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="117"/>
|
||||
@@ -44,19 +44,19 @@
|
||||
</constraints>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="MAS-3M-3cg">
|
||||
<rect key="frame" x="137.5" y="0.0" width="100" height="125"/>
|
||||
<rect key="frame" x="135.5" y="0.0" width="104" height="104"/>
|
||||
<subviews>
|
||||
<view clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="GQ1-rP-ckr" customClass="MXKImageView">
|
||||
<rect key="frame" x="8" y="31" width="84" height="84"/>
|
||||
<rect key="frame" x="10" y="10" width="84" height="84"/>
|
||||
<color key="backgroundColor" red="0.6886889638" green="1" blue="0.74383144840000004" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="MemberAvatar"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="84" id="HfP-Pj-zLa"/>
|
||||
<constraint firstAttribute="width" secondItem="GQ1-rP-ckr" secondAttribute="height" multiplier="1:1" id="a1T-Y0-Iic"/>
|
||||
<constraint firstAttribute="width" constant="84" id="kqX-QV-pvk"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="jHh-A3-In3">
|
||||
<rect key="frame" x="68" y="91" width="24" height="24"/>
|
||||
<rect key="frame" x="70" y="70" width="24" height="24"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" secondItem="jHh-A3-In3" secondAttribute="height" multiplier="1:1" id="fvP-Hk-apc"/>
|
||||
<constraint firstAttribute="width" constant="24" id="xga-bi-rLY"/>
|
||||
@@ -68,46 +68,79 @@
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="GQ1-rP-ckr" secondAttribute="bottom" constant="10" id="3pC-So-WvO"/>
|
||||
<constraint firstItem="jHh-A3-In3" firstAttribute="bottom" secondItem="GQ1-rP-ckr" secondAttribute="bottom" id="6Gg-lp-pJw"/>
|
||||
<constraint firstAttribute="trailing" secondItem="GQ1-rP-ckr" secondAttribute="trailing" constant="10" id="89D-wS-nJA"/>
|
||||
<constraint firstItem="GQ1-rP-ckr" firstAttribute="top" secondItem="MAS-3M-3cg" secondAttribute="top" constant="10" id="Mcw-V3-4fA"/>
|
||||
<constraint firstItem="jHh-A3-In3" firstAttribute="trailing" secondItem="GQ1-rP-ckr" secondAttribute="trailing" id="TbA-vY-3Ef"/>
|
||||
<constraint firstItem="GQ1-rP-ckr" firstAttribute="centerX" secondItem="MAS-3M-3cg" secondAttribute="centerX" id="ZGI-nR-gGx"/>
|
||||
<constraint firstAttribute="width" constant="100" id="fwv-qE-IV1"/>
|
||||
<constraint firstItem="GQ1-rP-ckr" firstAttribute="leading" secondItem="MAS-3M-3cg" secondAttribute="leading" constant="10" id="eSa-mP-j83"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="92g-hC-6jB">
|
||||
<rect key="frame" x="165" y="125" width="45" height="21"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="RoomMemberDetailsVCNameLabel"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="21" id="WyU-2u-Lek"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="18"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="wEo-Mk-SgZ">
|
||||
<rect key="frame" x="0.0" y="119" width="375" height="33"/>
|
||||
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="RoomMemberDetailsVCNameLabelMask"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="33" id="qhK-10-7AW"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="5le-5e-Vml">
|
||||
<rect key="frame" x="168" y="153" width="39" height="21"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="RoomMemberDetailsVCStatusLabel"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="21" id="7lY-ku-cPk"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="15"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="hSH-1t-jnC">
|
||||
<rect key="frame" x="0.0" y="181" width="375" height="50"/>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="7" translatesAutoresizingMaskIntoConstraints="NO" id="hSH-1t-jnC">
|
||||
<rect key="frame" x="0.0" y="112" width="375" height="221"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Flu-1i-oqi">
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="QHN-zr-wtC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="50"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lbh-ZN-XUO">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="252" text="Display name" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="i1p-3D-PhB" userLabel="Display name" customClass="CopyableLabel" customModule="Riot" customModuleProvider="target">
|
||||
<rect key="frame" x="20" y="0.0" width="335" height="50"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="RoomMemberDetailsVCStatusLabel"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="15"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="i1p-3D-PhB" secondAttribute="trailing" constant="20" id="XfU-OS-0s4"/>
|
||||
<constraint firstItem="i1p-3D-PhB" firstAttribute="leading" secondItem="QHN-zr-wtC" secondAttribute="leading" constant="20" id="c8p-6h-5f3"/>
|
||||
<constraint firstAttribute="bottom" secondItem="i1p-3D-PhB" secondAttribute="bottom" id="opt-ku-nZK"/>
|
||||
<constraint firstItem="i1p-3D-PhB" firstAttribute="top" secondItem="QHN-zr-wtC" secondAttribute="top" id="prC-iD-bnr"/>
|
||||
<constraint firstAttribute="height" priority="250" id="spl-iu-Mj1"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="dtL-Kk-RjD">
|
||||
<rect key="frame" x="0.0" y="57" width="375" height="50"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Matrix ID" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="9qc-7W-xzu" customClass="CopyableLabel" customModule="Riot" customModuleProvider="target">
|
||||
<rect key="frame" x="20" y="0.0" width="335" height="50"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="RoomMemberDetailsVCStatusLabel"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="15"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="9qc-7W-xzu" secondAttribute="bottom" id="3zr-VV-1Xj"/>
|
||||
<constraint firstItem="9qc-7W-xzu" firstAttribute="leading" secondItem="dtL-Kk-RjD" secondAttribute="leading" constant="20" id="9ai-xl-rJw"/>
|
||||
<constraint firstItem="9qc-7W-xzu" firstAttribute="top" secondItem="dtL-Kk-RjD" secondAttribute="top" id="dtv-La-iU0"/>
|
||||
<constraint firstAttribute="height" priority="250" id="fY4-Tr-GPF"/>
|
||||
<constraint firstAttribute="trailing" secondItem="9qc-7W-xzu" secondAttribute="trailing" constant="20" id="yse-Ty-mfU"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="gbf-sy-gsc">
|
||||
<rect key="frame" x="0.0" y="114" width="375" height="50"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Status" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="aBz-2v-ij6">
|
||||
<rect key="frame" x="20" y="0.0" width="335" height="50"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="RoomMemberDetailsVCStatusLabel"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="15"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="aBz-2v-ij6" firstAttribute="leading" secondItem="gbf-sy-gsc" secondAttribute="leading" constant="20" id="Hnt-NR-O6s"/>
|
||||
<constraint firstItem="aBz-2v-ij6" firstAttribute="top" secondItem="gbf-sy-gsc" secondAttribute="top" id="XJG-ub-Ybx"/>
|
||||
<constraint firstAttribute="height" priority="250" id="aUZ-qS-Fx6"/>
|
||||
<constraint firstAttribute="bottom" secondItem="aBz-2v-ij6" secondAttribute="bottom" id="dE7-Lg-OIb"/>
|
||||
<constraint firstAttribute="trailing" secondItem="aBz-2v-ij6" secondAttribute="trailing" constant="20" id="kne-1B-Dlg"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Flu-1i-oqi">
|
||||
<rect key="frame" x="0.0" y="171" width="375" height="50"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Power level" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lbh-ZN-XUO">
|
||||
<rect key="frame" x="20" y="0.0" width="335" height="50"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="RoomMemberDetailsVCStatusLabel"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="15"/>
|
||||
@@ -125,34 +158,18 @@
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="Flu-1i-oqi" firstAttribute="width" secondItem="hSH-1t-jnC" secondAttribute="width" id="oyq-sY-NBr"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="RoomMemberDetailsVCHeaderView"/>
|
||||
<constraints>
|
||||
<constraint firstItem="wEo-Mk-SgZ" firstAttribute="centerY" secondItem="92g-hC-6jB" secondAttribute="centerY" id="3Zt-MD-sZK"/>
|
||||
<constraint firstItem="5le-5e-Vml" firstAttribute="top" secondItem="92g-hC-6jB" secondAttribute="bottom" constant="7" id="5zX-1T-n38"/>
|
||||
<constraint firstItem="92g-hC-6jB" firstAttribute="centerX" secondItem="YXr-As-Mqh" secondAttribute="centerX" id="7Is-d0-FZp"/>
|
||||
<constraint firstItem="hSH-1t-jnC" firstAttribute="top" secondItem="5le-5e-Vml" secondAttribute="bottom" constant="7" id="EB4-IF-sT6"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="92g-hC-6jB" secondAttribute="trailing" constant="32" id="Eyx-UF-fYc"/>
|
||||
<constraint firstItem="hSH-1t-jnC" firstAttribute="top" secondItem="MAS-3M-3cg" secondAttribute="bottom" constant="8" id="BWn-Ie-nKk"/>
|
||||
<constraint firstAttribute="trailing" secondItem="ouj-VM-zdT" secondAttribute="trailing" id="FRy-TL-gS2"/>
|
||||
<constraint firstItem="wEo-Mk-SgZ" firstAttribute="centerX" secondItem="92g-hC-6jB" secondAttribute="centerX" id="K1f-RX-kpp"/>
|
||||
<constraint firstItem="wEo-Mk-SgZ" firstAttribute="width" secondItem="YXr-As-Mqh" secondAttribute="width" id="P5e-q6-OIS"/>
|
||||
<constraint firstItem="92g-hC-6jB" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="YXr-As-Mqh" secondAttribute="leading" constant="32" id="QZB-ue-Sih"/>
|
||||
<constraint firstItem="hSH-1t-jnC" firstAttribute="leading" secondItem="YXr-As-Mqh" secondAttribute="leading" id="Z0t-iC-uTc"/>
|
||||
<constraint firstAttribute="bottom" secondItem="hSH-1t-jnC" secondAttribute="bottom" constant="10" id="Z9m-Up-f4r"/>
|
||||
<constraint firstItem="5le-5e-Vml" firstAttribute="centerX" secondItem="YXr-As-Mqh" secondAttribute="centerX" id="bmA-Fq-uxO"/>
|
||||
<constraint firstItem="5le-5e-Vml" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="YXr-As-Mqh" secondAttribute="leading" constant="42" id="ioz-jk-jrE"/>
|
||||
<constraint firstItem="MAS-3M-3cg" firstAttribute="top" secondItem="YXr-As-Mqh" secondAttribute="top" id="jJp-cP-Vgp"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="5le-5e-Vml" secondAttribute="trailing" constant="42" id="mad-qx-tHe"/>
|
||||
<constraint firstAttribute="trailing" secondItem="hSH-1t-jnC" secondAttribute="trailing" id="nJW-bQ-zVq"/>
|
||||
<constraint firstItem="92g-hC-6jB" firstAttribute="top" secondItem="ouj-VM-zdT" secondAttribute="bottom" constant="8" id="rKl-Gw-ajI"/>
|
||||
<constraint firstItem="ouj-VM-zdT" firstAttribute="leading" secondItem="YXr-As-Mqh" secondAttribute="leading" id="rWQ-Ru-7Ej"/>
|
||||
<constraint firstItem="MAS-3M-3cg" firstAttribute="bottom" secondItem="92g-hC-6jB" secondAttribute="top" id="rgU-C1-YMW"/>
|
||||
<constraint firstItem="ouj-VM-zdT" firstAttribute="top" secondItem="YXr-As-Mqh" secondAttribute="top" id="srY-tD-AhJ"/>
|
||||
<constraint firstItem="hSH-1t-jnC" firstAttribute="width" secondItem="YXr-As-Mqh" secondAttribute="width" id="srS-ma-V2U"/>
|
||||
<constraint firstItem="MAS-3M-3cg" firstAttribute="centerX" secondItem="YXr-As-Mqh" secondAttribute="centerX" id="vNM-7Z-K2b"/>
|
||||
</constraints>
|
||||
</view>
|
||||
@@ -171,7 +188,7 @@
|
||||
</constraints>
|
||||
</imageView>
|
||||
<tableView clipsSubviews="YES" contentMode="scaleToFill" style="grouped" separatorStyle="default" rowHeight="46" sectionHeaderHeight="28" sectionFooterHeight="8" translatesAutoresizingMaskIntoConstraints="NO" id="R6u-PR-DcU">
|
||||
<rect key="frame" x="0.0" y="241" width="375" height="426"/>
|
||||
<rect key="frame" x="0.0" y="343" width="375" height="324"/>
|
||||
<color key="backgroundColor" red="0.93725490199999995" green="0.93725490199999995" blue="0.95686274510000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="string" keyPath="accessibilityIdentifier" value="RoomMemberDetailsVCTableView"/>
|
||||
@@ -182,6 +199,7 @@
|
||||
</connections>
|
||||
</tableView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="wdI-9L-Ozp"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="RoomMemberDetailsVC"/>
|
||||
<constraints>
|
||||
@@ -192,11 +210,13 @@
|
||||
<constraint firstItem="RBF-EK-dhz" firstAttribute="trailing" secondItem="7Dc-jk-9sT" secondAttribute="trailing" id="RTr-1M-aeU"/>
|
||||
<constraint firstItem="R6u-PR-DcU" firstAttribute="top" secondItem="YXr-As-Mqh" secondAttribute="bottom" id="VW4-0P-ALX"/>
|
||||
<constraint firstAttribute="bottom" secondItem="R6u-PR-DcU" secondAttribute="bottom" id="X1a-xq-1Aa"/>
|
||||
<constraint firstItem="MAS-3M-3cg" firstAttribute="top" secondItem="wdI-9L-Ozp" secondAttribute="top" id="a9L-QI-eJb"/>
|
||||
<constraint firstAttribute="trailing" secondItem="R6u-PR-DcU" secondAttribute="trailing" id="aMA-vf-GrY"/>
|
||||
<constraint firstItem="RBF-EK-dhz" firstAttribute="leading" secondItem="7Dc-jk-9sT" secondAttribute="leading" id="dHb-9z-eww"/>
|
||||
<constraint firstItem="YXr-As-Mqh" firstAttribute="top" secondItem="gX8-mM-6Ig" secondAttribute="top" id="l7z-od-LJm"/>
|
||||
<constraint firstItem="7Dc-jk-9sT" firstAttribute="leading" secondItem="gX8-mM-6Ig" secondAttribute="leading" id="rNt-rC-Pxv"/>
|
||||
<constraint firstItem="R6u-PR-DcU" firstAttribute="leading" secondItem="gX8-mM-6Ig" secondAttribute="leading" id="rbT-O1-m3d"/>
|
||||
<constraint firstItem="ouj-VM-zdT" firstAttribute="top" secondItem="wdI-9L-Ozp" secondAttribute="top" id="v9S-Vu-m6M"/>
|
||||
<constraint firstAttribute="bottom" secondItem="7Dc-jk-9sT" secondAttribute="bottom" id="yKK-K2-ebi"/>
|
||||
</constraints>
|
||||
<point key="canvasLocation" x="53.600000000000001" y="26.53673163418291"/>
|
||||
|
||||
@@ -77,6 +77,10 @@ final class RoomCoordinator: NSObject, RoomCoordinatorProtocol {
|
||||
self.roomViewController = RoomViewController.instantiate()
|
||||
self.activityIndicatorPresenter = ActivityIndicatorPresenter()
|
||||
|
||||
if #available(iOS 14, *) {
|
||||
PollTimelineProvider.shared.session = parameters.session
|
||||
}
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
||||
@@ -257,10 +261,25 @@ extension RoomCoordinator: RoomViewControllerDelegate {
|
||||
return
|
||||
}
|
||||
|
||||
let parameters = PollEditFormCoordinatorParameters(navigationRouter: self.navigationRouter)
|
||||
|
||||
let parameters = PollEditFormCoordinatorParameters(navigationRouter: self.navigationRouter, room: roomViewController.roomDataSource.room)
|
||||
pollEditFormCoordinator = PollEditFormCoordinator(parameters: parameters)
|
||||
|
||||
pollEditFormCoordinator?.start()
|
||||
}
|
||||
|
||||
func roomViewController(_ roomViewController: RoomViewController, canEndPollWithEventIdentifier eventIdentifier: String) -> Bool {
|
||||
guard #available(iOS 14.0, *) else {
|
||||
return false
|
||||
}
|
||||
|
||||
return PollTimelineProvider.shared.pollTimelineCoordinatorForEventIdentifier(eventIdentifier)?.canEndPoll() ?? false
|
||||
}
|
||||
|
||||
func roomViewController(_ roomViewController: RoomViewController, endPollWithEventIdentifier eventIdentifier: String) {
|
||||
guard #available(iOS 14.0, *) else {
|
||||
return
|
||||
}
|
||||
|
||||
PollTimelineProvider.shared.pollTimelineCoordinatorForEventIdentifier(eventIdentifier)?.endPoll()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,6 +183,12 @@ handleUniversalLinkWithParameters:(UniversalLinkParameters*)parameters;
|
||||
*/
|
||||
- (void)roomViewControllerDidRequestPollCreationFormPresentation:(RoomViewController *)roomViewController;
|
||||
|
||||
- (BOOL)roomViewController:(RoomViewController *)roomViewController
|
||||
canEndPollWithEventIdentifier:(NSString *)eventIdentifier;
|
||||
|
||||
- (void)roomViewController:(RoomViewController *)roomViewController
|
||||
endPollWithEventIdentifier:(NSString *)eventIdentifier;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -415,6 +415,10 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
|
||||
[self.bubblesTableView registerClass:VoiceMessageWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:VoiceMessageWithoutSenderInfoBubbleCell.defaultReuseIdentifier];
|
||||
[self.bubblesTableView registerClass:VoiceMessageWithPaginationTitleBubbleCell.class forCellReuseIdentifier:VoiceMessageWithPaginationTitleBubbleCell.defaultReuseIdentifier];
|
||||
|
||||
[self.bubblesTableView registerClass:PollBubbleCell.class forCellReuseIdentifier:PollBubbleCell.defaultReuseIdentifier];
|
||||
[self.bubblesTableView registerClass:PollWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:PollWithoutSenderInfoBubbleCell.defaultReuseIdentifier];
|
||||
[self.bubblesTableView registerClass:PollWithPaginationTitleBubbleCell.class forCellReuseIdentifier:PollWithPaginationTitleBubbleCell.defaultReuseIdentifier];
|
||||
|
||||
[self vc_removeBackTitle];
|
||||
|
||||
// Display leftBarButtonItems or leftBarButtonItem to the right of the Back button
|
||||
@@ -2013,7 +2017,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
|
||||
[self roomInputToolbarViewDidTapFileUpload];
|
||||
}]];
|
||||
}
|
||||
if (BuildSettings.roomScreenAllowPollsAction)
|
||||
if (RiotSettings.shared.roomScreenAllowPollsAction)
|
||||
{
|
||||
[actionItems addObject:[[RoomActionItem alloc] initWithImage:[UIImage imageNamed:@"action_poll"] andAction:^{
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
@@ -2615,193 +2619,210 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
|
||||
BOOL showEncryptionBadge = NO;
|
||||
|
||||
// Sanity check
|
||||
if ([cellData conformsToProtocol:@protocol(MXKRoomBubbleCellDataStoring)])
|
||||
if (![cellData conformsToProtocol:@protocol(MXKRoomBubbleCellDataStoring)])
|
||||
{
|
||||
id<MXKRoomBubbleCellDataStoring> bubbleData = (id<MXKRoomBubbleCellDataStoring>)cellData;
|
||||
return nil;
|
||||
}
|
||||
|
||||
MXKRoomBubbleCellData *roomBubbleCellData;
|
||||
|
||||
if ([bubbleData isKindOfClass:MXKRoomBubbleCellData.class])
|
||||
id<MXKRoomBubbleCellDataStoring> bubbleData = (id<MXKRoomBubbleCellDataStoring>)cellData;
|
||||
|
||||
MXKRoomBubbleCellData *roomBubbleCellData;
|
||||
|
||||
if ([bubbleData isKindOfClass:MXKRoomBubbleCellData.class])
|
||||
{
|
||||
roomBubbleCellData = (MXKRoomBubbleCellData*)bubbleData;
|
||||
showEncryptionBadge = roomBubbleCellData.containsBubbleComponentWithEncryptionBadge;
|
||||
}
|
||||
|
||||
// Select the suitable table view cell class, by considering first the empty bubble cell.
|
||||
if (bubbleData.hasNoDisplay)
|
||||
{
|
||||
cellViewClass = RoomEmptyBubbleCell.class;
|
||||
}
|
||||
else if (bubbleData.tag == RoomBubbleCellDataTagRoomCreationIntro)
|
||||
{
|
||||
cellViewClass = RoomCreationIntroCell.class;
|
||||
}
|
||||
else if (bubbleData.tag == RoomBubbleCellDataTagRoomCreateWithPredecessor)
|
||||
{
|
||||
cellViewClass = RoomPredecessorBubbleCell.class;
|
||||
}
|
||||
else if (bubbleData.tag == RoomBubbleCellDataTagKeyVerificationRequestIncomingApproval)
|
||||
{
|
||||
cellViewClass = bubbleData.isPaginationFirstBubble ? KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell.class : KeyVerificationIncomingRequestApprovalBubbleCell.class;
|
||||
}
|
||||
else if (bubbleData.tag == RoomBubbleCellDataTagKeyVerificationRequest)
|
||||
{
|
||||
cellViewClass = bubbleData.isPaginationFirstBubble ? KeyVerificationRequestStatusWithPaginationTitleBubbleCell.class : KeyVerificationRequestStatusBubbleCell.class;
|
||||
}
|
||||
else if (bubbleData.tag == RoomBubbleCellDataTagKeyVerificationConclusion)
|
||||
{
|
||||
cellViewClass = bubbleData.isPaginationFirstBubble ? KeyVerificationConclusionWithPaginationTitleBubbleCell.class : KeyVerificationConclusionBubbleCell.class;
|
||||
}
|
||||
else if (bubbleData.tag == RoomBubbleCellDataTagMembership)
|
||||
{
|
||||
if (bubbleData.collapsed)
|
||||
{
|
||||
roomBubbleCellData = (MXKRoomBubbleCellData*)bubbleData;
|
||||
showEncryptionBadge = roomBubbleCellData.containsBubbleComponentWithEncryptionBadge;
|
||||
}
|
||||
|
||||
// Select the suitable table view cell class, by considering first the empty bubble cell.
|
||||
if (bubbleData.hasNoDisplay)
|
||||
{
|
||||
cellViewClass = RoomEmptyBubbleCell.class;
|
||||
}
|
||||
else if (bubbleData.tag == RoomBubbleCellDataTagRoomCreationIntro)
|
||||
{
|
||||
cellViewClass = RoomCreationIntroCell.class;
|
||||
}
|
||||
else if (bubbleData.tag == RoomBubbleCellDataTagRoomCreateWithPredecessor)
|
||||
{
|
||||
cellViewClass = RoomPredecessorBubbleCell.class;
|
||||
}
|
||||
else if (bubbleData.tag == RoomBubbleCellDataTagKeyVerificationRequestIncomingApproval)
|
||||
{
|
||||
cellViewClass = bubbleData.isPaginationFirstBubble ? KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell.class : KeyVerificationIncomingRequestApprovalBubbleCell.class;
|
||||
}
|
||||
else if (bubbleData.tag == RoomBubbleCellDataTagKeyVerificationRequest)
|
||||
{
|
||||
cellViewClass = bubbleData.isPaginationFirstBubble ? KeyVerificationRequestStatusWithPaginationTitleBubbleCell.class : KeyVerificationRequestStatusBubbleCell.class;
|
||||
}
|
||||
else if (bubbleData.tag == RoomBubbleCellDataTagKeyVerificationConclusion)
|
||||
{
|
||||
cellViewClass = bubbleData.isPaginationFirstBubble ? KeyVerificationConclusionWithPaginationTitleBubbleCell.class : KeyVerificationConclusionBubbleCell.class;
|
||||
}
|
||||
else if (bubbleData.tag == RoomBubbleCellDataTagMembership)
|
||||
{
|
||||
if (bubbleData.collapsed)
|
||||
if (bubbleData.nextCollapsableCellData)
|
||||
{
|
||||
if (bubbleData.nextCollapsableCellData)
|
||||
{
|
||||
cellViewClass = bubbleData.isPaginationFirstBubble ? RoomMembershipCollapsedWithPaginationTitleBubbleCell.class : RoomMembershipCollapsedBubbleCell.class;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use a normal membership cell for a single membership event
|
||||
cellViewClass = bubbleData.isPaginationFirstBubble ? RoomMembershipWithPaginationTitleBubbleCell.class : RoomMembershipBubbleCell.class;
|
||||
}
|
||||
}
|
||||
else if (bubbleData.collapsedAttributedTextMessage)
|
||||
{
|
||||
// The cell (and its series) is not collapsed but this cell is the first
|
||||
// of the series. So, use the cell with the "collapse" button.
|
||||
cellViewClass = bubbleData.isPaginationFirstBubble ? RoomMembershipExpandedWithPaginationTitleBubbleCell.class : RoomMembershipExpandedBubbleCell.class;
|
||||
cellViewClass = bubbleData.isPaginationFirstBubble ? RoomMembershipCollapsedWithPaginationTitleBubbleCell.class : RoomMembershipCollapsedBubbleCell.class;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use a normal membership cell for a single membership event
|
||||
cellViewClass = bubbleData.isPaginationFirstBubble ? RoomMembershipWithPaginationTitleBubbleCell.class : RoomMembershipBubbleCell.class;
|
||||
}
|
||||
}
|
||||
else if (bubbleData.tag == RoomBubbleCellDataTagRoomCreateConfiguration)
|
||||
else if (bubbleData.collapsedAttributedTextMessage)
|
||||
{
|
||||
cellViewClass = bubbleData.isPaginationFirstBubble ? RoomCreationWithPaginationCollapsedBubbleCell.class : RoomCreationCollapsedBubbleCell.class;
|
||||
// The cell (and its series) is not collapsed but this cell is the first
|
||||
// of the series. So, use the cell with the "collapse" button.
|
||||
cellViewClass = bubbleData.isPaginationFirstBubble ? RoomMembershipExpandedWithPaginationTitleBubbleCell.class : RoomMembershipExpandedBubbleCell.class;
|
||||
}
|
||||
else if (bubbleData.tag == RoomBubbleCellDataTagCall)
|
||||
else
|
||||
{
|
||||
cellViewClass = RoomDirectCallStatusBubbleCell.class;
|
||||
cellViewClass = bubbleData.isPaginationFirstBubble ? RoomMembershipWithPaginationTitleBubbleCell.class : RoomMembershipBubbleCell.class;
|
||||
}
|
||||
else if (bubbleData.tag == RoomBubbleCellDataTagGroupCall)
|
||||
}
|
||||
else if (bubbleData.tag == RoomBubbleCellDataTagRoomCreateConfiguration)
|
||||
{
|
||||
cellViewClass = bubbleData.isPaginationFirstBubble ? RoomCreationWithPaginationCollapsedBubbleCell.class : RoomCreationCollapsedBubbleCell.class;
|
||||
}
|
||||
else if (bubbleData.tag == RoomBubbleCellDataTagCall)
|
||||
{
|
||||
cellViewClass = RoomDirectCallStatusBubbleCell.class;
|
||||
}
|
||||
else if (bubbleData.tag == RoomBubbleCellDataTagGroupCall)
|
||||
{
|
||||
cellViewClass = RoomGroupCallStatusBubbleCell.class;
|
||||
}
|
||||
else if (bubbleData.attachment.type == MXKAttachmentTypeVoiceMessage || bubbleData.attachment.type == MXKAttachmentTypeAudio)
|
||||
{
|
||||
if (bubbleData.isPaginationFirstBubble)
|
||||
{
|
||||
cellViewClass = RoomGroupCallStatusBubbleCell.class;
|
||||
cellViewClass = VoiceMessageWithPaginationTitleBubbleCell.class;
|
||||
}
|
||||
else if (bubbleData.attachment.type == MXKAttachmentTypeVoiceMessage || bubbleData.attachment.type == MXKAttachmentTypeAudio)
|
||||
else if (bubbleData.shouldHideSenderInformation)
|
||||
{
|
||||
if (bubbleData.isPaginationFirstBubble)
|
||||
cellViewClass = VoiceMessageWithoutSenderInfoBubbleCell.class;
|
||||
}
|
||||
else
|
||||
{
|
||||
cellViewClass = VoiceMessageBubbleCell.class;
|
||||
}
|
||||
}
|
||||
else if (bubbleData.tag == RoomBubbleCellDataTagPoll)
|
||||
{
|
||||
if (bubbleData.isPaginationFirstBubble)
|
||||
{
|
||||
cellViewClass = PollWithPaginationTitleBubbleCell.class;
|
||||
}
|
||||
else if (bubbleData.shouldHideSenderInformation)
|
||||
{
|
||||
cellViewClass = PollWithoutSenderInfoBubbleCell.class;
|
||||
}
|
||||
else
|
||||
{
|
||||
cellViewClass = PollBubbleCell.class;
|
||||
}
|
||||
}
|
||||
else if (bubbleData.isIncoming)
|
||||
{
|
||||
if (bubbleData.isAttachmentWithThumbnail)
|
||||
{
|
||||
// Check whether the provided celldata corresponds to a selected sticker
|
||||
if (customizedRoomDataSource.selectedEventId && (bubbleData.attachment.type == MXKAttachmentTypeSticker) && [bubbleData.attachment.eventId isEqualToString:customizedRoomDataSource.selectedEventId])
|
||||
{
|
||||
cellViewClass = VoiceMessageWithPaginationTitleBubbleCell.class;
|
||||
cellViewClass = RoomSelectedStickerBubbleCell.class;
|
||||
}
|
||||
else if (bubbleData.isPaginationFirstBubble)
|
||||
{
|
||||
cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.class : RoomIncomingAttachmentWithPaginationTitleBubbleCell.class;
|
||||
}
|
||||
else if (bubbleData.shouldHideSenderInformation)
|
||||
{
|
||||
cellViewClass = VoiceMessageWithoutSenderInfoBubbleCell.class;
|
||||
cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.class : RoomIncomingAttachmentWithoutSenderInfoBubbleCell.class;
|
||||
}
|
||||
else
|
||||
{
|
||||
cellViewClass = VoiceMessageBubbleCell.class;
|
||||
}
|
||||
}
|
||||
else if (bubbleData.isIncoming)
|
||||
{
|
||||
if (bubbleData.isAttachmentWithThumbnail)
|
||||
{
|
||||
// Check whether the provided celldata corresponds to a selected sticker
|
||||
if (customizedRoomDataSource.selectedEventId && (bubbleData.attachment.type == MXKAttachmentTypeSticker) && [bubbleData.attachment.eventId isEqualToString:customizedRoomDataSource.selectedEventId])
|
||||
{
|
||||
cellViewClass = RoomSelectedStickerBubbleCell.class;
|
||||
}
|
||||
else if (bubbleData.isPaginationFirstBubble)
|
||||
{
|
||||
cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.class : RoomIncomingAttachmentWithPaginationTitleBubbleCell.class;
|
||||
}
|
||||
else if (bubbleData.shouldHideSenderInformation)
|
||||
{
|
||||
cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.class : RoomIncomingAttachmentWithoutSenderInfoBubbleCell.class;
|
||||
}
|
||||
else
|
||||
{
|
||||
cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedAttachmentBubbleCell.class : RoomIncomingAttachmentBubbleCell.class;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (bubbleData.isPaginationFirstBubble)
|
||||
{
|
||||
if (bubbleData.shouldHideSenderName)
|
||||
{
|
||||
cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class : RoomIncomingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class;
|
||||
}
|
||||
else
|
||||
{
|
||||
cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.class : RoomIncomingTextMsgWithPaginationTitleBubbleCell.class;
|
||||
}
|
||||
}
|
||||
else if (bubbleData.shouldHideSenderInformation)
|
||||
{
|
||||
cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.class : RoomIncomingTextMsgWithoutSenderInfoBubbleCell.class;
|
||||
}
|
||||
else if (bubbleData.shouldHideSenderName)
|
||||
{
|
||||
cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell.class : RoomIncomingTextMsgWithoutSenderNameBubbleCell.class;
|
||||
}
|
||||
else
|
||||
{
|
||||
cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedTextMsgBubbleCell.class : RoomIncomingTextMsgBubbleCell.class;
|
||||
}
|
||||
cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedAttachmentBubbleCell.class : RoomIncomingAttachmentBubbleCell.class;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Handle here outgoing bubbles
|
||||
if (bubbleData.isAttachmentWithThumbnail)
|
||||
if (bubbleData.isPaginationFirstBubble)
|
||||
{
|
||||
// Check whether the provided celldata corresponds to a selected sticker
|
||||
if (customizedRoomDataSource.selectedEventId && (bubbleData.attachment.type == MXKAttachmentTypeSticker) && [bubbleData.attachment.eventId isEqualToString:customizedRoomDataSource.selectedEventId])
|
||||
if (bubbleData.shouldHideSenderName)
|
||||
{
|
||||
cellViewClass = RoomSelectedStickerBubbleCell.class;
|
||||
}
|
||||
else if (bubbleData.isPaginationFirstBubble)
|
||||
{
|
||||
cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell.class :RoomOutgoingAttachmentWithPaginationTitleBubbleCell.class;
|
||||
}
|
||||
else if (bubbleData.shouldHideSenderInformation)
|
||||
{
|
||||
cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.class : RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.class;
|
||||
cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class : RoomIncomingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class;
|
||||
}
|
||||
else
|
||||
{
|
||||
cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedAttachmentBubbleCell.class : RoomOutgoingAttachmentBubbleCell.class;
|
||||
cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.class : RoomIncomingTextMsgWithPaginationTitleBubbleCell.class;
|
||||
}
|
||||
}
|
||||
else if (bubbleData.shouldHideSenderInformation)
|
||||
{
|
||||
cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.class : RoomIncomingTextMsgWithoutSenderInfoBubbleCell.class;
|
||||
}
|
||||
else if (bubbleData.shouldHideSenderName)
|
||||
{
|
||||
cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell.class : RoomIncomingTextMsgWithoutSenderNameBubbleCell.class;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (bubbleData.isPaginationFirstBubble)
|
||||
cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedTextMsgBubbleCell.class : RoomIncomingTextMsgBubbleCell.class;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Handle here outgoing bubbles
|
||||
if (bubbleData.isAttachmentWithThumbnail)
|
||||
{
|
||||
// Check whether the provided celldata corresponds to a selected sticker
|
||||
if (customizedRoomDataSource.selectedEventId && (bubbleData.attachment.type == MXKAttachmentTypeSticker) && [bubbleData.attachment.eventId isEqualToString:customizedRoomDataSource.selectedEventId])
|
||||
{
|
||||
cellViewClass = RoomSelectedStickerBubbleCell.class;
|
||||
}
|
||||
else if (bubbleData.isPaginationFirstBubble)
|
||||
{
|
||||
cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell.class :RoomOutgoingAttachmentWithPaginationTitleBubbleCell.class;
|
||||
}
|
||||
else if (bubbleData.shouldHideSenderInformation)
|
||||
{
|
||||
cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.class : RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.class;
|
||||
}
|
||||
else
|
||||
{
|
||||
cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedAttachmentBubbleCell.class : RoomOutgoingAttachmentBubbleCell.class;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (bubbleData.isPaginationFirstBubble)
|
||||
{
|
||||
if (bubbleData.shouldHideSenderName)
|
||||
{
|
||||
if (bubbleData.shouldHideSenderName)
|
||||
{
|
||||
cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class : RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class;
|
||||
}
|
||||
else
|
||||
{
|
||||
cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.class : RoomOutgoingTextMsgWithPaginationTitleBubbleCell.class;
|
||||
}
|
||||
}
|
||||
else if (bubbleData.shouldHideSenderInformation)
|
||||
{
|
||||
cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.class :RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.class;
|
||||
}
|
||||
else if (bubbleData.shouldHideSenderName)
|
||||
{
|
||||
cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.class : RoomOutgoingTextMsgWithoutSenderNameBubbleCell.class;
|
||||
cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class : RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class;
|
||||
}
|
||||
else
|
||||
{
|
||||
cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedTextMsgBubbleCell.class : RoomOutgoingTextMsgBubbleCell.class;
|
||||
cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.class : RoomOutgoingTextMsgWithPaginationTitleBubbleCell.class;
|
||||
}
|
||||
}
|
||||
else if (bubbleData.shouldHideSenderInformation)
|
||||
{
|
||||
cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.class :RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.class;
|
||||
}
|
||||
else if (bubbleData.shouldHideSenderName)
|
||||
{
|
||||
cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.class : RoomOutgoingTextMsgWithoutSenderNameBubbleCell.class;
|
||||
}
|
||||
else
|
||||
{
|
||||
cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedTextMsgBubbleCell.class : RoomOutgoingTextMsgBubbleCell.class;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3222,7 +3243,8 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
|
||||
}]];
|
||||
}
|
||||
|
||||
if (selectedEvent.sentState == MXEventSentStateSent) {
|
||||
if (selectedEvent.sentState == MXEventSentStateSent && selectedEvent.eventType != MXEventTypePollStart)
|
||||
{
|
||||
[actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionForward]
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction * action) {
|
||||
@@ -3231,7 +3253,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
|
||||
}]];
|
||||
}
|
||||
|
||||
if (!isJitsiCallEvent)
|
||||
if (!isJitsiCallEvent && selectedEvent.eventType != MXEventTypePollStart)
|
||||
{
|
||||
[actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionQuote]
|
||||
style:UIAlertActionStyleDefault
|
||||
@@ -3248,7 +3270,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
|
||||
}]];
|
||||
}
|
||||
|
||||
if (!isJitsiCallEvent && BuildSettings.messageDetailsAllowShare)
|
||||
if (!isJitsiCallEvent && BuildSettings.messageDetailsAllowShare && selectedEvent.eventType != MXEventTypePollStart)
|
||||
{
|
||||
[actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionShare]
|
||||
style:UIAlertActionStyleDefault
|
||||
@@ -3432,7 +3454,17 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
|
||||
// because it breaks everything
|
||||
if (selectedEvent.eventType != MXEventTypeRoomEncryption)
|
||||
{
|
||||
[actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionRedact]
|
||||
NSString *title;
|
||||
if (selectedEvent.eventType == MXEventTypePollStart)
|
||||
{
|
||||
title = [VectorL10n roomEventActionRemovePoll];
|
||||
}
|
||||
else
|
||||
{
|
||||
title = [VectorL10n roomEventActionRedact];
|
||||
}
|
||||
|
||||
[actionsMenu addAction:[UIAlertAction actionWithTitle:title
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction * action) {
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
@@ -3456,6 +3488,28 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
|
||||
}]];
|
||||
}
|
||||
|
||||
if (selectedEvent.eventType == MXEventTypePollStart && [selectedEvent.sender isEqualToString:self.mainSession.myUser.userId]) {
|
||||
if ([self.delegate roomViewController:self canEndPollWithEventIdentifier:selectedEvent.eventId]) {
|
||||
[actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionEndPoll]
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction * action) {
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
|
||||
[self.delegate roomViewController:self endPollWithEventIdentifier:selectedEvent.eventId];
|
||||
|
||||
[self hideContextualMenuAnimated:YES];
|
||||
}]];
|
||||
}
|
||||
}
|
||||
|
||||
[actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel]
|
||||
style:UIAlertActionStyleCancel
|
||||
handler:^(UIAlertAction * action) {
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
|
||||
[self hideContextualMenuAnimated:YES];
|
||||
}]];
|
||||
|
||||
if (BuildSettings.messageDetailsAllowPermalink)
|
||||
{
|
||||
[actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionPermalink]
|
||||
@@ -3618,9 +3672,9 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
|
||||
|
||||
if (!isJitsiCallEvent && self.roomDataSource.room.summary.isEncrypted)
|
||||
{
|
||||
[currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionViewEncryption]
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction * action) {
|
||||
[actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionViewEncryption]
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction * action) {
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
|
||||
[self cancelEventSelection];
|
||||
@@ -3629,15 +3683,8 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
|
||||
[self showEncryptionInformation:selectedEvent];
|
||||
}]];
|
||||
}
|
||||
}
|
||||
|
||||
[actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel]
|
||||
style:UIAlertActionStyleCancel
|
||||
handler:^(UIAlertAction * action) {
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
|
||||
[self hideContextualMenuAnimated:YES];
|
||||
}]];
|
||||
}
|
||||
|
||||
// Do not display empty action sheet
|
||||
if (actionsMenu.actions.count > 1)
|
||||
@@ -6037,7 +6084,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
|
||||
|
||||
MXWeakify(self);
|
||||
|
||||
BOOL isCopyActionEnabled = !attachment || attachment.type != MXKAttachmentTypeSticker;
|
||||
BOOL isCopyActionEnabled = (event.eventType != MXEventTypePollStart && (!attachment || attachment.type != MXKAttachmentTypeSticker));
|
||||
|
||||
if (attachment && !BuildSettings.messageDetailsAllowCopyMedia)
|
||||
{
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16097.2" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -17,7 +17,7 @@
|
||||
<rect key="frame" x="0.0" y="0.0" width="595" height="97"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</view>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="5GX-gn-bK1">
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalSpacing" translatesAutoresizingMaskIntoConstraints="NO" id="5GX-gn-bK1">
|
||||
<rect key="frame" x="0.0" y="0.0" width="595" height="97"/>
|
||||
<subviews>
|
||||
<view hidden="YES" contentMode="scaleToFill" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="u1e-Q2-PhY">
|
||||
@@ -74,11 +74,11 @@
|
||||
</constraints>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ohU-Sc-mgb">
|
||||
<rect key="frame" x="46" y="4" width="484" height="30"/>
|
||||
<rect key="frame" x="46" y="4" width="534" height="30"/>
|
||||
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</view>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="User name:" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="8" translatesAutoresizingMaskIntoConstraints="NO" id="meG-P8-61b">
|
||||
<rect key="frame" x="56" y="10" width="474" height="18"/>
|
||||
<rect key="frame" x="56" y="10" width="524" height="18"/>
|
||||
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="UserNameLabel"/>
|
||||
<constraints>
|
||||
@@ -105,10 +105,10 @@
|
||||
<rect key="frame" x="0.0" y="0.0" width="595" height="97"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="4d4-XQ-ido">
|
||||
<rect key="frame" x="41" y="0.0" width="0.0" height="97"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="0.0" height="97"/>
|
||||
<subviews>
|
||||
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="uHE-o7-sCe">
|
||||
<rect key="frame" x="0.0" y="0.0" width="16" height="97"/>
|
||||
<rect key="frame" x="0.0" y="16" width="16" height="97"/>
|
||||
<subviews>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="encryption_warning" translatesAutoresizingMaskIntoConstraints="NO" id="Ujc-3c-e5B">
|
||||
<rect key="frame" x="0.0" y="3" width="16" height="16"/>
|
||||
@@ -125,37 +125,35 @@
|
||||
<constraint firstItem="Ujc-3c-e5B" firstAttribute="centerX" secondItem="uHE-o7-sCe" secondAttribute="centerX" id="z4k-EX-K17"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<view hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="7Y6-Py-paB">
|
||||
<rect key="frame" x="0.0" y="16" width="50" height="97"/>
|
||||
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="BubbleInfoContainer"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="50" id="LtA-zk-OCc"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="uHE-o7-sCe" firstAttribute="height" secondItem="4d4-XQ-ido" secondAttribute="height" id="DhB-EC-rCE"/>
|
||||
<constraint firstItem="7Y6-Py-paB" firstAttribute="top" secondItem="4d4-XQ-ido" secondAttribute="top" constant="16" id="Wx9-0o-vzm"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
<view clipsSubviews="YES" contentMode="scaleAspectFit" translatesAutoresizingMaskIntoConstraints="NO" id="oeI-eO-mFK">
|
||||
<rect key="frame" x="56" y="3" width="474" height="91"/>
|
||||
<rect key="frame" x="56" y="3" width="524" height="91"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</view>
|
||||
<view hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="7Y6-Py-paB">
|
||||
<rect key="frame" x="530" y="3" width="50" height="91"/>
|
||||
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="BubbleInfoContainer"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="50" id="LtA-zk-OCc"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="oeI-eO-mFK" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="vcq-cR-uBc" secondAttribute="leading" constant="56" id="0Fr-0L-9tU"/>
|
||||
<constraint firstItem="7Y6-Py-paB" firstAttribute="top" secondItem="vcq-cR-uBc" secondAttribute="top" constant="3" id="16j-F9-tL8"/>
|
||||
<constraint firstAttribute="bottom" secondItem="oeI-eO-mFK" secondAttribute="bottom" constant="3" id="8M5-uW-82s"/>
|
||||
<constraint firstItem="oeI-eO-mFK" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="4d4-XQ-ido" secondAttribute="trailing" constant="6" id="9By-U1-wTY"/>
|
||||
<constraint firstItem="7Y6-Py-paB" firstAttribute="leading" secondItem="oeI-eO-mFK" secondAttribute="trailing" id="9V6-A8-9i0"/>
|
||||
<constraint firstAttribute="trailing" secondItem="oeI-eO-mFK" secondAttribute="trailing" constant="15" id="Pbe-4d-q6Y"/>
|
||||
<constraint firstAttribute="bottom" secondItem="4d4-XQ-ido" secondAttribute="bottom" id="Tkw-p1-CYF"/>
|
||||
<constraint firstItem="4d4-XQ-ido" firstAttribute="leading" secondItem="vcq-cR-uBc" secondAttribute="leading" constant="41" id="cbh-iX-gKz"/>
|
||||
<constraint firstItem="4d4-XQ-ido" firstAttribute="leading" secondItem="vcq-cR-uBc" secondAttribute="leading" id="cbh-iX-gKz"/>
|
||||
<constraint firstItem="4d4-XQ-ido" firstAttribute="top" secondItem="vcq-cR-uBc" secondAttribute="top" id="hOM-gq-1au"/>
|
||||
<constraint firstAttribute="height" priority="250" id="lRu-Kd-3JZ"/>
|
||||
<constraint firstAttribute="bottom" secondItem="7Y6-Py-paB" secondAttribute="bottom" constant="3" id="lee-yN-381"/>
|
||||
<constraint firstAttribute="trailing" secondItem="7Y6-Py-paB" secondAttribute="trailing" constant="15" id="rG0-0L-I4O"/>
|
||||
<constraint firstItem="oeI-eO-mFK" firstAttribute="top" secondItem="vcq-cR-uBc" secondAttribute="top" constant="3" id="uZZ-I6-Xtq"/>
|
||||
<constraint firstItem="oeI-eO-mFK" firstAttribute="leading" secondItem="vcq-cR-uBc" secondAttribute="leading" priority="750" id="vsh-pW-S46"/>
|
||||
</constraints>
|
||||
@@ -227,7 +225,7 @@
|
||||
<connections>
|
||||
<outlet property="avatarImageView" destination="yXz-Za-4yR" id="f56-93-gxa"/>
|
||||
<outlet property="bubbleInfoContainer" destination="7Y6-Py-paB" id="uLv-MM-HIL"/>
|
||||
<outlet property="bubbleInfoContainerTopConstraint" destination="16j-F9-tL8" id="zxd-pd-SSx"/>
|
||||
<outlet property="bubbleInfoContainerTopConstraint" destination="Wx9-0o-vzm" id="nLG-nC-lwV"/>
|
||||
<outlet property="bubbleOverlayContainer" destination="XQw-Mj-NZY" id="6d1-EN-LPY"/>
|
||||
<outlet property="encryptionImageView" destination="Ujc-3c-e5B" id="7zc-Y7-1jT"/>
|
||||
<outlet property="encryptionStatusContainerView" destination="uHE-o7-sCe" id="Dl7-QS-WKl"/>
|
||||
@@ -247,6 +245,6 @@
|
||||
</view>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="encryption_warning" width="16" height="16"/>
|
||||
<image name="encryption_warning" width="12" height="12"/>
|
||||
</resources>
|
||||
</document>
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class PollBubbleCell: SizableBaseBubbleCell, BubbleCellReactionsDisplayable {
|
||||
|
||||
private var pollView: UIView?
|
||||
private var event: MXEvent?
|
||||
|
||||
override func render(_ cellData: MXKCellData!) {
|
||||
super.render(cellData)
|
||||
|
||||
guard #available(iOS 14.0, *),
|
||||
let contentView = bubbleCellContentView?.innerContentView,
|
||||
let bubbleData = cellData as? RoomBubbleCellData,
|
||||
let event = bubbleData.events.last,
|
||||
event.eventType == __MXEventType.pollStart,
|
||||
let view = PollTimelineProvider.shared.buildPollTimelineViewForEvent(event) else {
|
||||
return
|
||||
}
|
||||
|
||||
self.event = event
|
||||
|
||||
pollView?.removeFromSuperview()
|
||||
contentView.vc_addSubViewMatchingParent(view)
|
||||
pollView = view
|
||||
}
|
||||
|
||||
override func setupViews() {
|
||||
super.setupViews()
|
||||
|
||||
bubbleCellContentView?.backgroundColor = .clear
|
||||
bubbleCellContentView?.showSenderInfo = true
|
||||
bubbleCellContentView?.showPaginationTitle = false
|
||||
}
|
||||
|
||||
// The normal flow for tapping on cell content views doesn't work for bubbles without attributed strings
|
||||
func onContentViewTap(_ sender: UITapGestureRecognizer) {
|
||||
guard let event = self.event else {
|
||||
return
|
||||
}
|
||||
|
||||
delegate.cell(self, didRecognizeAction: kMXKRoomBubbleCellTapOnContentView, userInfo: [kMXKRoomBubbleCellEventKey: event])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class PollWithPaginationTitleBubbleCell: PollBubbleCell {
|
||||
|
||||
override func setupViews() {
|
||||
super.setupViews()
|
||||
|
||||
bubbleCellContentView?.showPaginationTitle = true
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class PollWithoutSenderInfoBubbleCell: PollBubbleCell {
|
||||
|
||||
override func setupViews() {
|
||||
super.setupViews()
|
||||
|
||||
bubbleCellContentView?.showSenderInfo = false
|
||||
}
|
||||
|
||||
}
|
||||
@@ -153,9 +153,10 @@ enum
|
||||
ABOUT_THIRD_PARTY_INDEX,
|
||||
};
|
||||
|
||||
enum
|
||||
typedef NS_ENUM(NSUInteger, LABS_ENABLE)
|
||||
{
|
||||
LABS_ENABLE_RINGING_FOR_GROUP_CALLS_INDEX = 0
|
||||
LABS_ENABLE_RINGING_FOR_GROUP_CALLS_INDEX,
|
||||
LABS_ENABLE_POLLS
|
||||
};
|
||||
|
||||
enum
|
||||
@@ -551,6 +552,8 @@ TableViewSectionsDelegate>
|
||||
{
|
||||
Section *sectionLabs = [Section sectionWithTag:SECTION_TAG_LABS];
|
||||
[sectionLabs addRowWithTag:LABS_ENABLE_RINGING_FOR_GROUP_CALLS_INDEX];
|
||||
[sectionLabs addRowWithTag:LABS_ENABLE_POLLS];
|
||||
|
||||
sectionLabs.headerTitle = [VectorL10n settingsLabs];
|
||||
if (sectionLabs.hasAnyRows)
|
||||
{
|
||||
@@ -2401,6 +2404,19 @@ TableViewSectionsDelegate>
|
||||
|
||||
cell = labelAndSwitchCell;
|
||||
}
|
||||
|
||||
if (row == LABS_ENABLE_POLLS && BuildSettings.pollsEnabled)
|
||||
{
|
||||
MXKTableViewCellWithLabelAndSwitch *labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath];
|
||||
|
||||
labelAndSwitchCell.mxkLabel.text = [VectorL10n settingsLabsEnabledPolls];
|
||||
labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.roomScreenAllowPollsAction;
|
||||
labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor;
|
||||
|
||||
[labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleEnablePolls:) forControlEvents:UIControlEventValueChanged];
|
||||
|
||||
cell = labelAndSwitchCell;
|
||||
}
|
||||
}
|
||||
else if (section == SECTION_TAG_FLAIR)
|
||||
{
|
||||
@@ -3135,6 +3151,11 @@ TableViewSectionsDelegate>
|
||||
RiotSettings.shared.enableRingingForGroupCalls = sender.isOn;
|
||||
}
|
||||
|
||||
- (void)toggleEnablePolls:(UISwitch *)sender
|
||||
{
|
||||
RiotSettings.shared.roomScreenAllowPollsAction = sender.isOn;
|
||||
}
|
||||
|
||||
- (void)togglePinRoomsWithMissedNotif:(UISwitch *)sender
|
||||
{
|
||||
RiotSettings.shared.pinRoomsWithMissedNotificationsOnHome = sender.isOn;
|
||||
|
||||
@@ -500,6 +500,9 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
additionalUserInfo = [Constants.userInfoKeyPresentNotificationOnForeground: true]
|
||||
}
|
||||
}
|
||||
case .pollStart:
|
||||
notificationTitle = self.messageTitle(for: eventSenderName, in: roomDisplayName)
|
||||
notificationBody = MXEventContentPollStart(fromJSON: event.content)?.question
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
@@ -24,7 +24,8 @@ enum MockAppScreens {
|
||||
MockTemplateRoomListScreenState.self,
|
||||
MockTemplateRoomChatScreenState.self,
|
||||
MockUserSuggestionScreenState.self,
|
||||
MockPollEditFormScreenState.self
|
||||
MockPollEditFormScreenState.self,
|
||||
MockPollTimelineScreenState.self
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import SwiftUI
|
||||
|
||||
struct PollEditFormCoordinatorParameters {
|
||||
let navigationRouter: NavigationRouterType?
|
||||
let room: MXRoom
|
||||
}
|
||||
|
||||
final class PollEditFormCoordinator: Coordinator {
|
||||
@@ -60,12 +61,10 @@ final class PollEditFormCoordinator: Coordinator {
|
||||
// MARK: - Public
|
||||
func start() {
|
||||
guard #available(iOS 14.0, *) else {
|
||||
MXLog.debug("[PollEditFormCoordinator] start: Invalid iOS version, returning.")
|
||||
MXLog.error("[PollEditFormCoordinator] start: Invalid iOS version, returning.")
|
||||
return
|
||||
}
|
||||
|
||||
MXLog.debug("[PollEditFormCoordinator] did start.")
|
||||
|
||||
parameters.navigationRouter?.present(pollEditFormHostingController, animated: true)
|
||||
|
||||
pollEditFormViewModel.completion = { [weak self] result in
|
||||
@@ -73,8 +72,30 @@ final class PollEditFormCoordinator: Coordinator {
|
||||
switch result {
|
||||
case .cancel:
|
||||
self.parameters.navigationRouter?.dismissModule(animated: true, completion: nil)
|
||||
case .create(_, _):
|
||||
break
|
||||
case .create(let question, let answerOptions):
|
||||
var options = [MXEventContentPollStartAnswerOption]()
|
||||
for answerOption in answerOptions {
|
||||
options.append(MXEventContentPollStartAnswerOption(uuid: UUID().uuidString, text: answerOption))
|
||||
}
|
||||
|
||||
let pollStartContent = MXEventContentPollStart(question: question,
|
||||
kind: kMXMessageContentKeyExtensiblePollKindDisclosed,
|
||||
maxSelections: 1,
|
||||
answerOptions: options)
|
||||
|
||||
self.pollEditFormViewModel.dispatch(action: .startLoading)
|
||||
|
||||
self.parameters.room.sendPollStart(withContent: pollStartContent, localEcho: nil) { [weak self] result in
|
||||
guard let self = self else { return }
|
||||
|
||||
self.parameters.navigationRouter?.dismissModule(animated: true, completion: nil)
|
||||
self.pollEditFormViewModel.dispatch(action: .stopLoading(nil))
|
||||
} failure: { [weak self] error in
|
||||
guard let self = self else { return }
|
||||
|
||||
MXLog.error("Failed creating poll with error: \(String(describing: error))")
|
||||
self.pollEditFormViewModel.dispatch(action: .stopLoading(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ import SwiftUI
|
||||
|
||||
enum PollEditFormStateAction {
|
||||
case viewAction(PollEditFormViewAction)
|
||||
case startLoading
|
||||
case stopLoading(Error?)
|
||||
}
|
||||
|
||||
enum PollEditFormViewAction {
|
||||
@@ -58,7 +60,7 @@ struct PollEditFormAnswerOption: Identifiable, Equatable {
|
||||
}
|
||||
|
||||
struct PollEditFormViewState: BindableState {
|
||||
let maxAnswerOptionsCount: Int
|
||||
var maxAnswerOptionsCount: Int
|
||||
var bindings: PollEditFormViewStateBindings
|
||||
|
||||
var confirmationButtonEnabled: Bool {
|
||||
@@ -69,9 +71,13 @@ struct PollEditFormViewState: BindableState {
|
||||
var addAnswerOptionButtonEnabled: Bool {
|
||||
bindings.answerOptions.count < maxAnswerOptionsCount
|
||||
}
|
||||
|
||||
var showLoadingIndicator: Bool = false
|
||||
}
|
||||
|
||||
struct PollEditFormViewStateBindings {
|
||||
var question: PollEditFormQuestion
|
||||
var answerOptions: [PollEditFormAnswerOption]
|
||||
|
||||
var showsFailureAlert: Bool = false
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ class PollEditFormViewModel: PollEditFormViewModelType {
|
||||
|
||||
private struct Constants {
|
||||
static let maxAnswerOptionsCount = 20
|
||||
static let maxQuestionLength = 200
|
||||
static let maxAnswerOptionLength = 200
|
||||
static let maxQuestionLength = 340
|
||||
static let maxAnswerOptionLength = 340
|
||||
}
|
||||
|
||||
// MARK: - Properties
|
||||
@@ -86,6 +86,16 @@ class PollEditFormViewModel: PollEditFormViewModelType {
|
||||
default:
|
||||
break
|
||||
}
|
||||
case .startLoading:
|
||||
state.showLoadingIndicator = true
|
||||
break
|
||||
case .stopLoading(let error):
|
||||
state.showLoadingIndicator = false
|
||||
|
||||
if error != nil {
|
||||
state.bindings.showsFailureAlert = true
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,14 +59,18 @@ struct PollEditForm: View {
|
||||
ForEach(0..<viewModel.answerOptions.count, id: \.self) { index in
|
||||
SafeBindingCollectionEnumerator($viewModel.answerOptions, index: index) { binding in
|
||||
AnswerOptionGroup(text: binding.text, index: index) {
|
||||
viewModel.send(viewAction: .deleteAnswerOption(viewModel.answerOptions[index]))
|
||||
withAnimation(.easeInOut(duration: 0.2)) {
|
||||
viewModel.send(viewAction: .deleteAnswerOption(viewModel.answerOptions[index]))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button(VectorL10n.pollEditFormAddOption) {
|
||||
viewModel.send(viewAction: .addAnswerOption)
|
||||
withAnimation(.easeInOut(duration: 0.2)) {
|
||||
viewModel.send(viewAction: .addAnswerOption)
|
||||
}
|
||||
}
|
||||
.disabled(!viewModel.viewState.addAnswerOptionButtonEnabled)
|
||||
|
||||
@@ -78,8 +82,13 @@ struct PollEditForm: View {
|
||||
.buttonStyle(PrimaryActionButtonStyle(enabled: viewModel.viewState.confirmationButtonEnabled))
|
||||
.disabled(!viewModel.viewState.confirmationButtonEnabled)
|
||||
}
|
||||
.animation(.easeInOut(duration: 0.2))
|
||||
.padding()
|
||||
.activityIndicator(show: viewModel.viewState.showLoadingIndicator)
|
||||
.alert(isPresented: $viewModel.showsFailureAlert) {
|
||||
Alert(title: Text(VectorL10n.pollEditFormPostFailureTitle),
|
||||
message: Text(VectorL10n.pollEditFormPostFailureSubtitle),
|
||||
dismissButton: .default(Text(VectorL10n.pollEditFormPostFailureAction)))
|
||||
}
|
||||
.frame(minHeight: proxy.size.height) // Make the VStack fill the ScrollView's parent
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
@@ -98,6 +107,7 @@ struct PollEditForm: View {
|
||||
}
|
||||
}
|
||||
.accentColor(theme.colors.accent)
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
// File created from SimpleUserProfileExample
|
||||
// $ createScreen.sh Room/PollTimeline PollTimeline
|
||||
/*
|
||||
Copyright 2021 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import SwiftUI
|
||||
import MatrixSDK
|
||||
import Combine
|
||||
|
||||
struct PollTimelineCoordinatorParameters {
|
||||
let session: MXSession
|
||||
let room: MXRoom
|
||||
let pollStartEvent: MXEvent
|
||||
}
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
final class PollTimelineCoordinator: Coordinator, PollAggregatorDelegate {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let parameters: PollTimelineCoordinatorParameters
|
||||
private let selectedAnswerIdentifiersSubject = PassthroughSubject<[String], Never>()
|
||||
|
||||
private var pollAggregator: PollAggregator
|
||||
private var pollTimelineViewModel: PollTimelineViewModel!
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
// MARK: Public
|
||||
|
||||
// Must be used only internally
|
||||
var childCoordinators: [Coordinator] = []
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
init(parameters: PollTimelineCoordinatorParameters) throws {
|
||||
self.parameters = parameters
|
||||
|
||||
try pollAggregator = PollAggregator(session: parameters.session, room: parameters.room, pollStartEvent: parameters.pollStartEvent)
|
||||
pollAggregator.delegate = self
|
||||
|
||||
pollTimelineViewModel = PollTimelineViewModel(timelinePoll: buildTimelinePollFrom(pollAggregator.poll))
|
||||
pollTimelineViewModel.callback = { [weak self] result in
|
||||
guard let self = self else { return }
|
||||
|
||||
switch result {
|
||||
case .selectedAnswerOptionsWithIdentifiers(let identifiers):
|
||||
self.selectedAnswerIdentifiersSubject.send(identifiers)
|
||||
}
|
||||
}
|
||||
|
||||
selectedAnswerIdentifiersSubject
|
||||
.debounce(for: 1.0, scheduler: RunLoop.main)
|
||||
.removeDuplicates()
|
||||
.sink { [weak self] identifiers in
|
||||
guard let self = self else { return }
|
||||
|
||||
self.parameters.room.sendPollResponse(for: parameters.pollStartEvent,
|
||||
withAnswerIdentifiers: identifiers,
|
||||
localEcho: nil, success: nil) { [weak self] error in
|
||||
guard let self = self else { return }
|
||||
|
||||
MXLog.error("[PollTimelineCoordinator]] Failed submitting response with error \(String(describing: error))")
|
||||
|
||||
self.pollTimelineViewModel.dispatch(action: .showAnsweringFailure)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
func start() {
|
||||
|
||||
}
|
||||
|
||||
func toPresentable() -> UIViewController {
|
||||
return VectorHostingController(rootView: PollTimelineView(viewModel: pollTimelineViewModel.context))
|
||||
}
|
||||
|
||||
func canEndPoll() -> Bool {
|
||||
return pollAggregator.poll.isClosed == false
|
||||
}
|
||||
|
||||
func endPoll() {
|
||||
parameters.room.sendPollEnd(for: parameters.pollStartEvent, localEcho: nil, success: nil) { [weak self] error in
|
||||
self?.pollTimelineViewModel.dispatch(action: .showClosingFailure)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - PollAggregatorDelegate
|
||||
|
||||
func pollAggregatorDidUpdateData(_ aggregator: PollAggregator) {
|
||||
pollTimelineViewModel.dispatch(action: .updateWithPoll(buildTimelinePollFrom(aggregator.poll)))
|
||||
}
|
||||
|
||||
func pollAggregatorDidStartLoading(_ aggregator: PollAggregator) {
|
||||
|
||||
}
|
||||
|
||||
func pollAggregatorDidEndLoading(_ aggregator: PollAggregator) {
|
||||
|
||||
}
|
||||
|
||||
func pollAggregator(_ aggregator: PollAggregator, didFailWithError: Error) {
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
// PollProtocol is intentionally not available in the SwiftUI target as we don't want
|
||||
// to add the SDK as a dependency to it. We need to translate from one to the other on this level.
|
||||
func buildTimelinePollFrom(_ poll: PollProtocol) -> TimelinePoll {
|
||||
let answerOptions = poll.answerOptions.map { pollAnswerOption in
|
||||
TimelineAnswerOption(id: pollAnswerOption.id,
|
||||
text: pollAnswerOption.text,
|
||||
count: pollAnswerOption.count,
|
||||
winner: pollAnswerOption.isWinner,
|
||||
selected: pollAnswerOption.isCurrentUserSelection)
|
||||
}
|
||||
|
||||
return TimelinePoll(question: poll.text,
|
||||
answerOptions: answerOptions,
|
||||
closed: poll.isClosed,
|
||||
totalAnswerCount: poll.totalAnswerCount,
|
||||
type: (poll.kind == .disclosed ? .disclosed : .undisclosed),
|
||||
maxAllowedSelections: poll.maxAllowedSelections)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@available(iOS 14, *)
|
||||
class PollTimelineProvider {
|
||||
static let shared = PollTimelineProvider()
|
||||
|
||||
var session: MXSession?
|
||||
var coordinatorsForEventIdentifiers = [String: PollTimelineCoordinator]()
|
||||
|
||||
private init() {
|
||||
|
||||
}
|
||||
|
||||
/// Create or retrieve the poll timeline coordinator for this event and return
|
||||
/// a view to be displayed in the timeline
|
||||
func buildPollTimelineViewForEvent(_ event: MXEvent) -> UIView? {
|
||||
guard let session = session, let room = session.room(withRoomId: event.roomId) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if let coordinator = coordinatorsForEventIdentifiers[event.eventId] {
|
||||
return coordinator.toPresentable().view
|
||||
}
|
||||
|
||||
let parameters = PollTimelineCoordinatorParameters(session: session, room: room, pollStartEvent: event)
|
||||
guard let coordinator = try? PollTimelineCoordinator(parameters: parameters) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
coordinatorsForEventIdentifiers[event.eventId] = coordinator
|
||||
|
||||
return coordinator.toPresentable().view
|
||||
}
|
||||
|
||||
/// Retrieve the poll timeline coordinator for the given event or nil if it hasn't been created yet
|
||||
func pollTimelineCoordinatorForEventIdentifier(_ eventIdentifier: String) -> PollTimelineCoordinator? {
|
||||
return coordinatorsForEventIdentifiers[eventIdentifier]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
// File created from SimpleUserProfileExample
|
||||
// $ createScreen.sh Room/PollTimeline PollTimeline
|
||||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
typealias PollTimelineViewModelCallback = ((PollTimelineViewModelResult) -> Void)
|
||||
|
||||
enum PollTimelineStateAction {
|
||||
case viewAction(PollTimelineViewAction, PollTimelineViewModelCallback?)
|
||||
case updateWithPoll(TimelinePoll)
|
||||
case showAnsweringFailure
|
||||
case showClosingFailure
|
||||
}
|
||||
|
||||
enum PollTimelineViewAction {
|
||||
case selectAnswerOptionWithIdentifier(String)
|
||||
}
|
||||
|
||||
enum PollTimelineViewModelResult {
|
||||
case selectedAnswerOptionsWithIdentifiers([String])
|
||||
}
|
||||
|
||||
enum TimelinePollType {
|
||||
case disclosed
|
||||
case undisclosed
|
||||
}
|
||||
|
||||
class TimelineAnswerOption: Identifiable {
|
||||
var id: String
|
||||
var text: String
|
||||
var count: UInt
|
||||
var winner: Bool
|
||||
var selected: Bool
|
||||
|
||||
init(id: String, text: String, count: UInt, winner: Bool, selected: Bool) {
|
||||
self.id = id
|
||||
self.text = text
|
||||
self.count = count
|
||||
self.winner = winner
|
||||
self.selected = selected
|
||||
}
|
||||
}
|
||||
|
||||
class TimelinePoll {
|
||||
var question: String
|
||||
var answerOptions: [TimelineAnswerOption]
|
||||
var closed: Bool
|
||||
var totalAnswerCount: UInt
|
||||
var type: TimelinePollType
|
||||
var maxAllowedSelections: UInt
|
||||
|
||||
init(question: String, answerOptions: [TimelineAnswerOption], closed: Bool, totalAnswerCount: UInt, type: TimelinePollType, maxAllowedSelections: UInt) {
|
||||
self.question = question
|
||||
self.answerOptions = answerOptions
|
||||
self.closed = closed
|
||||
self.totalAnswerCount = totalAnswerCount
|
||||
self.type = type
|
||||
self.maxAllowedSelections = maxAllowedSelections
|
||||
}
|
||||
|
||||
var hasCurrentUserVoted: Bool {
|
||||
answerOptions.filter { $0.selected == true}.count > 0
|
||||
}
|
||||
}
|
||||
|
||||
struct PollTimelineViewState: BindableState {
|
||||
var poll: TimelinePoll
|
||||
var bindings: PollTimelineViewStateBindings
|
||||
}
|
||||
|
||||
struct PollTimelineViewStateBindings {
|
||||
var showsAnsweringFailureAlert: Bool = false
|
||||
var showsClosingFailureAlert: Bool = false
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
// File created from SimpleUserProfileExample
|
||||
// $ createScreen.sh Room/UserSuggestion UserSuggestion
|
||||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
enum MockPollTimelineScreenState: MockScreenState, CaseIterable {
|
||||
case open
|
||||
case closed
|
||||
|
||||
var screenType: Any.Type {
|
||||
MockPollTimelineScreenState.self
|
||||
}
|
||||
|
||||
var screenView: ([Any], AnyView) {
|
||||
let answerOptions = [TimelineAnswerOption(id: "1", text: "First", count: 10, winner: false, selected: false),
|
||||
TimelineAnswerOption(id: "2", text: "Second", count: 5, winner: false, selected: true),
|
||||
TimelineAnswerOption(id: "3", text: "Third", count: 15, winner: true, selected: false)]
|
||||
|
||||
let poll = TimelinePoll(question: "Question",
|
||||
answerOptions: answerOptions,
|
||||
closed: (self == .closed ? true : false),
|
||||
totalAnswerCount: 20,
|
||||
type: .disclosed,
|
||||
maxAllowedSelections: 1)
|
||||
|
||||
let viewModel = PollTimelineViewModel(timelinePoll: poll)
|
||||
|
||||
return ([viewModel], AnyView(PollTimelineView(viewModel: viewModel.context)))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
// File created from SimpleUserProfileExample
|
||||
// $ createScreen.sh Room/PollTimeline PollTimeline
|
||||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Combine
|
||||
|
||||
@available(iOS 14, *)
|
||||
typealias PollTimelineViewModelType = StateStoreViewModel<PollTimelineViewState,
|
||||
PollTimelineStateAction,
|
||||
PollTimelineViewAction>
|
||||
@available(iOS 14, *)
|
||||
class PollTimelineViewModel: PollTimelineViewModelType {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
// MARK: Public
|
||||
|
||||
var callback: PollTimelineViewModelCallback?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(timelinePoll: TimelinePoll) {
|
||||
super.init(initialViewState: PollTimelineViewState(poll: timelinePoll, bindings: PollTimelineViewStateBindings()))
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
override func process(viewAction: PollTimelineViewAction) {
|
||||
switch viewAction {
|
||||
case .selectAnswerOptionWithIdentifier(_):
|
||||
dispatch(action: .viewAction(viewAction, callback))
|
||||
}
|
||||
}
|
||||
|
||||
override class func reducer(state: inout PollTimelineViewState, action: PollTimelineStateAction) {
|
||||
switch action {
|
||||
case .viewAction(let viewAction, let callback):
|
||||
switch viewAction {
|
||||
|
||||
// Update local state. An update will be pushed from the coordinator once sent.
|
||||
case .selectAnswerOptionWithIdentifier(let identifier):
|
||||
guard !state.poll.closed else {
|
||||
return
|
||||
}
|
||||
|
||||
if (state.poll.maxAllowedSelections == 1) {
|
||||
updateSingleSelectPollLocalState(&state, selectedAnswerIdentifier: identifier, callback: callback)
|
||||
} else {
|
||||
updateMultiSelectPollLocalState(&state, selectedAnswerIdentifier: identifier, callback: callback)
|
||||
}
|
||||
}
|
||||
case .updateWithPoll(let poll):
|
||||
state.poll = poll
|
||||
case .showAnsweringFailure:
|
||||
state.bindings.showsAnsweringFailureAlert = true
|
||||
case .showClosingFailure:
|
||||
state.bindings.showsClosingFailureAlert = true
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
static func updateSingleSelectPollLocalState(_ state: inout PollTimelineViewState, selectedAnswerIdentifier: String, callback: PollTimelineViewModelCallback?) {
|
||||
for answerOption in state.poll.answerOptions {
|
||||
if answerOption.selected {
|
||||
answerOption.selected = false
|
||||
|
||||
if(answerOption.count > 0) {
|
||||
answerOption.count = answerOption.count - 1
|
||||
state.poll.totalAnswerCount -= 1
|
||||
}
|
||||
}
|
||||
|
||||
if answerOption.id == selectedAnswerIdentifier {
|
||||
answerOption.selected = true
|
||||
answerOption.count += 1
|
||||
state.poll.totalAnswerCount += 1
|
||||
}
|
||||
}
|
||||
|
||||
informCoordinatorOfSelectionUpdate(state: state, callback: callback)
|
||||
}
|
||||
|
||||
static func updateMultiSelectPollLocalState(_ state: inout PollTimelineViewState, selectedAnswerIdentifier: String, callback: PollTimelineViewModelCallback?) {
|
||||
let selectedAnswerOptions = state.poll.answerOptions.filter { $0.selected == true }
|
||||
|
||||
let isDeselecting = selectedAnswerOptions.filter { $0.id == selectedAnswerIdentifier }.count > 0
|
||||
|
||||
if !isDeselecting && selectedAnswerOptions.count >= state.poll.maxAllowedSelections {
|
||||
return
|
||||
}
|
||||
|
||||
for answerOption in state.poll.answerOptions where answerOption.id == selectedAnswerIdentifier {
|
||||
if answerOption.selected {
|
||||
answerOption.selected = false
|
||||
answerOption.count -= 1
|
||||
state.poll.totalAnswerCount -= 1
|
||||
} else {
|
||||
answerOption.selected = true
|
||||
answerOption.count += 1
|
||||
state.poll.totalAnswerCount += 1
|
||||
}
|
||||
}
|
||||
|
||||
informCoordinatorOfSelectionUpdate(state: state, callback: callback)
|
||||
}
|
||||
|
||||
static func informCoordinatorOfSelectionUpdate(state: PollTimelineViewState, callback: PollTimelineViewModelCallback?) {
|
||||
let selectedIdentifiers = state.poll.answerOptions.compactMap { answerOption in
|
||||
answerOption.selected ? answerOption.id : nil
|
||||
}
|
||||
|
||||
callback?(.selectedAnswerOptionsWithIdentifiers(selectedIdentifiers))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
// File created from SimpleUserProfileExample
|
||||
// $ createScreen.sh Room/PollTimeline PollTimeline
|
||||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import RiotSwiftUI
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
class PollTimelineUITests: XCTestCase {
|
||||
|
||||
private var app: XCUIApplication!
|
||||
|
||||
override func setUp() {
|
||||
continueAfterFailure = false
|
||||
|
||||
app = XCUIApplication()
|
||||
app.launch()
|
||||
}
|
||||
|
||||
func testOpenPoll() {
|
||||
app.buttons[MockPollTimelineScreenState.screenStateKeys.first!].tap()
|
||||
|
||||
XCTAssert(app.staticTexts["Question"].exists)
|
||||
XCTAssert(app.staticTexts["20 votes cast"].exists)
|
||||
|
||||
XCTAssert(app.buttons["First, 10 votes"].exists)
|
||||
XCTAssertEqual(app.buttons["First, 10 votes"].value as! String, "50%")
|
||||
|
||||
XCTAssert(app.buttons["Second, 5 votes"].exists)
|
||||
XCTAssertEqual(app.buttons["Second, 5 votes"].value as! String, "25%")
|
||||
|
||||
XCTAssert(app.buttons["Third, 15 votes"].exists)
|
||||
XCTAssertEqual(app.buttons["Third, 15 votes"].value as! String, "75%")
|
||||
|
||||
app.buttons["First, 10 votes"].tap()
|
||||
|
||||
XCTAssert(app.buttons["First, 11 votes"].exists)
|
||||
XCTAssertEqual(app.buttons["First, 11 votes"].value as! String, "55%")
|
||||
|
||||
XCTAssert(app.buttons["Second, 4 votes"].exists)
|
||||
XCTAssertEqual(app.buttons["Second, 4 votes"].value as! String, "20%")
|
||||
|
||||
XCTAssert(app.buttons["Third, 15 votes"].exists)
|
||||
XCTAssertEqual(app.buttons["Third, 15 votes"].value as! String, "75%")
|
||||
|
||||
app.buttons["Third, 15 votes"].tap()
|
||||
|
||||
XCTAssert(app.buttons["First, 10 votes"].exists)
|
||||
XCTAssertEqual(app.buttons["First, 10 votes"].value as! String, "50%")
|
||||
|
||||
XCTAssert(app.buttons["Second, 4 votes"].exists)
|
||||
XCTAssertEqual(app.buttons["Second, 4 votes"].value as! String, "20%")
|
||||
|
||||
XCTAssert(app.buttons["Third, 16 votes"].exists)
|
||||
XCTAssertEqual(app.buttons["Third, 16 votes"].value as! String, "80%")
|
||||
}
|
||||
|
||||
func testClosedPoll() {
|
||||
app.buttons[MockPollTimelineScreenState.screenStateKeys.last!].tap()
|
||||
|
||||
XCTAssert(app.staticTexts["Question"].exists)
|
||||
XCTAssert(app.staticTexts["Final results based on 20 votes"].exists)
|
||||
|
||||
XCTAssert(app.buttons["First, 10 votes"].exists)
|
||||
XCTAssertEqual(app.buttons["First, 10 votes"].value as! String, "50%")
|
||||
|
||||
XCTAssert(app.buttons["Second, 5 votes"].exists)
|
||||
XCTAssertEqual(app.buttons["Second, 5 votes"].value as! String, "25%")
|
||||
|
||||
XCTAssert(app.buttons["Third, 15 votes"].exists)
|
||||
XCTAssertEqual(app.buttons["Third, 15 votes"].value as! String, "75%")
|
||||
|
||||
app.buttons["First, 10 votes"].tap()
|
||||
|
||||
XCTAssert(app.buttons["First, 10 votes"].exists)
|
||||
XCTAssertEqual(app.buttons["First, 10 votes"].value as! String, "50%")
|
||||
|
||||
XCTAssert(app.buttons["Second, 5 votes"].exists)
|
||||
XCTAssertEqual(app.buttons["Second, 5 votes"].value as! String, "25%")
|
||||
|
||||
XCTAssert(app.buttons["Third, 15 votes"].exists)
|
||||
XCTAssertEqual(app.buttons["Third, 15 votes"].value as! String, "75%")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
// File created from SimpleUserProfileExample
|
||||
// $ createScreen.sh Room/PollTimeline PollTimeline
|
||||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import Combine
|
||||
|
||||
@testable import RiotSwiftUI
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
class PollTimelineViewModelTests: XCTestCase {
|
||||
var viewModel: PollTimelineViewModel!
|
||||
var context: PollTimelineViewModelType.Context!
|
||||
var cancellables = Set<AnyCancellable>()
|
||||
|
||||
override func setUpWithError() throws {
|
||||
let answerOptions = [TimelineAnswerOption(id: "1", text: "1", count: 1, winner: false, selected: false),
|
||||
TimelineAnswerOption(id: "2", text: "2", count: 1, winner: false, selected: false),
|
||||
TimelineAnswerOption(id: "3", text: "3", count: 1, winner: false, selected: false)]
|
||||
|
||||
let timelinePoll = TimelinePoll(question: "Question",
|
||||
answerOptions: answerOptions,
|
||||
closed: false,
|
||||
totalAnswerCount: 3,
|
||||
type: .disclosed,
|
||||
maxAllowedSelections: 1)
|
||||
|
||||
viewModel = PollTimelineViewModel(timelinePoll: timelinePoll)
|
||||
context = viewModel.context
|
||||
}
|
||||
|
||||
func testInitialState() {
|
||||
XCTAssertEqual(context.viewState.poll.answerOptions.count, 3)
|
||||
XCTAssertFalse(context.viewState.poll.closed)
|
||||
XCTAssertEqual(context.viewState.poll.type, .disclosed)
|
||||
}
|
||||
|
||||
func testSingleSelectionOnMax1Allowed() {
|
||||
context.send(viewAction: .selectAnswerOptionWithIdentifier("1"))
|
||||
|
||||
XCTAssertTrue(context.viewState.poll.answerOptions[0].selected)
|
||||
XCTAssertFalse(context.viewState.poll.answerOptions[1].selected)
|
||||
XCTAssertFalse(context.viewState.poll.answerOptions[2].selected)
|
||||
}
|
||||
|
||||
func testSingleReselectionOnMax1Allowed() {
|
||||
context.send(viewAction: .selectAnswerOptionWithIdentifier("1"))
|
||||
context.send(viewAction: .selectAnswerOptionWithIdentifier("1"))
|
||||
|
||||
XCTAssertTrue(context.viewState.poll.answerOptions[0].selected)
|
||||
XCTAssertFalse(context.viewState.poll.answerOptions[1].selected)
|
||||
XCTAssertFalse(context.viewState.poll.answerOptions[2].selected)
|
||||
}
|
||||
|
||||
func testMultipleSelectionOnMax1Allowed() {
|
||||
context.send(viewAction: .selectAnswerOptionWithIdentifier("1"))
|
||||
context.send(viewAction: .selectAnswerOptionWithIdentifier("3"))
|
||||
|
||||
XCTAssertFalse(context.viewState.poll.answerOptions[0].selected)
|
||||
XCTAssertFalse(context.viewState.poll.answerOptions[1].selected)
|
||||
XCTAssertTrue(context.viewState.poll.answerOptions[2].selected)
|
||||
}
|
||||
|
||||
func testMultipleReselectionOnMax1Allowed() {
|
||||
context.send(viewAction: .selectAnswerOptionWithIdentifier("1"))
|
||||
context.send(viewAction: .selectAnswerOptionWithIdentifier("3"))
|
||||
context.send(viewAction: .selectAnswerOptionWithIdentifier("3"))
|
||||
|
||||
XCTAssertFalse(context.viewState.poll.answerOptions[0].selected)
|
||||
XCTAssertFalse(context.viewState.poll.answerOptions[1].selected)
|
||||
XCTAssertTrue(context.viewState.poll.answerOptions[2].selected)
|
||||
}
|
||||
|
||||
func testClosedSelection() {
|
||||
context.viewState.poll.closed = true
|
||||
|
||||
context.send(viewAction: .selectAnswerOptionWithIdentifier("1"))
|
||||
context.send(viewAction: .selectAnswerOptionWithIdentifier("3"))
|
||||
|
||||
XCTAssertFalse(context.viewState.poll.answerOptions[0].selected)
|
||||
XCTAssertFalse(context.viewState.poll.answerOptions[1].selected)
|
||||
XCTAssertFalse(context.viewState.poll.answerOptions[2].selected)
|
||||
}
|
||||
|
||||
func testSingleSelectionOnMax2Allowed() {
|
||||
context.viewState.poll.maxAllowedSelections = 2
|
||||
|
||||
context.send(viewAction: .selectAnswerOptionWithIdentifier("1"))
|
||||
|
||||
XCTAssertTrue(context.viewState.poll.answerOptions[0].selected)
|
||||
XCTAssertFalse(context.viewState.poll.answerOptions[1].selected)
|
||||
XCTAssertFalse(context.viewState.poll.answerOptions[2].selected)
|
||||
}
|
||||
|
||||
func testSingleReselectionOnMax2Allowed() {
|
||||
context.viewState.poll.maxAllowedSelections = 2
|
||||
|
||||
context.send(viewAction: .selectAnswerOptionWithIdentifier("1"))
|
||||
context.send(viewAction: .selectAnswerOptionWithIdentifier("1"))
|
||||
|
||||
XCTAssertFalse(context.viewState.poll.answerOptions[0].selected)
|
||||
XCTAssertFalse(context.viewState.poll.answerOptions[1].selected)
|
||||
XCTAssertFalse(context.viewState.poll.answerOptions[2].selected)
|
||||
}
|
||||
|
||||
func testMultipleSelectionOnMax2Allowed() {
|
||||
context.viewState.poll.maxAllowedSelections = 2
|
||||
|
||||
context.send(viewAction: .selectAnswerOptionWithIdentifier("1"))
|
||||
context.send(viewAction: .selectAnswerOptionWithIdentifier("3"))
|
||||
context.send(viewAction: .selectAnswerOptionWithIdentifier("2"))
|
||||
|
||||
XCTAssertTrue(context.viewState.poll.answerOptions[0].selected)
|
||||
XCTAssertFalse(context.viewState.poll.answerOptions[1].selected)
|
||||
XCTAssertTrue(context.viewState.poll.answerOptions[2].selected)
|
||||
|
||||
context.send(viewAction: .selectAnswerOptionWithIdentifier("1"))
|
||||
|
||||
XCTAssertFalse(context.viewState.poll.answerOptions[0].selected)
|
||||
XCTAssertFalse(context.viewState.poll.answerOptions[1].selected)
|
||||
XCTAssertTrue(context.viewState.poll.answerOptions[2].selected)
|
||||
|
||||
context.send(viewAction: .selectAnswerOptionWithIdentifier("2"))
|
||||
|
||||
XCTAssertFalse(context.viewState.poll.answerOptions[0].selected)
|
||||
XCTAssertTrue(context.viewState.poll.answerOptions[1].selected)
|
||||
XCTAssertTrue(context.viewState.poll.answerOptions[2].selected)
|
||||
|
||||
context.send(viewAction: .selectAnswerOptionWithIdentifier("3"))
|
||||
|
||||
XCTAssertFalse(context.viewState.poll.answerOptions[0].selected)
|
||||
XCTAssertTrue(context.viewState.poll.answerOptions[1].selected)
|
||||
XCTAssertFalse(context.viewState.poll.answerOptions[2].selected)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
struct PollTimelineAnswerOptionButton: View {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@Environment(\.theme) private var theme: ThemeSwiftUI
|
||||
|
||||
let answerOption: TimelineAnswerOption
|
||||
let pollClosed: Bool
|
||||
let showResults: Bool
|
||||
let totalAnswerCount: UInt
|
||||
let action: () -> Void
|
||||
|
||||
// MARK: Public
|
||||
|
||||
var body: some View {
|
||||
Button(action: action) {
|
||||
let rect = RoundedRectangle(cornerRadius: 4.0)
|
||||
answerOptionLabel
|
||||
.padding(.horizontal, 8.0)
|
||||
.padding(.top, 12.0)
|
||||
.padding(.bottom, 4.0)
|
||||
.clipShape(rect)
|
||||
.overlay(rect.stroke(borderAccentColor, lineWidth: 1.0))
|
||||
.accentColor(progressViewAccentColor)
|
||||
}
|
||||
}
|
||||
|
||||
var answerOptionLabel: some View {
|
||||
VStack(alignment: .leading, spacing: 12.0) {
|
||||
HStack(alignment: .top, spacing: 8.0) {
|
||||
|
||||
if !pollClosed {
|
||||
Image(uiImage: answerOption.selected ? Asset.Images.pollCheckboxSelected.image : Asset.Images.pollCheckboxDefault.image)
|
||||
}
|
||||
|
||||
Text(answerOption.text)
|
||||
.font(theme.fonts.body)
|
||||
.foregroundColor(theme.colors.primaryContent)
|
||||
|
||||
if pollClosed && answerOption.winner {
|
||||
Spacer()
|
||||
Image(uiImage: Asset.Images.pollWinnerIcon.image)
|
||||
}
|
||||
}
|
||||
|
||||
HStack {
|
||||
ProgressView(value: Double(showResults ? answerOption.count : 0),
|
||||
total: Double(totalAnswerCount))
|
||||
.progressViewStyle(LinearProgressViewStyle())
|
||||
.scaleEffect(x: 1.0, y: 1.2, anchor: .center)
|
||||
.padding(.vertical, 8.0)
|
||||
|
||||
if (showResults) {
|
||||
Text(answerOption.count == 1 ? VectorL10n.pollTimelineOneVote : VectorL10n.pollTimelineVotesCount(Int(answerOption.count)))
|
||||
.font(theme.fonts.footnote)
|
||||
.foregroundColor(pollClosed && answerOption.winner ? theme.colors.accent : theme.colors.secondaryContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var borderAccentColor: Color {
|
||||
guard !pollClosed else {
|
||||
return (answerOption.winner ? theme.colors.accent : theme.colors.quinaryContent)
|
||||
}
|
||||
|
||||
return answerOption.selected ? theme.colors.accent : theme.colors.quinaryContent
|
||||
}
|
||||
|
||||
var progressViewAccentColor: Color {
|
||||
guard !pollClosed else {
|
||||
return (answerOption.winner ? theme.colors.accent : theme.colors.quarterlyContent)
|
||||
}
|
||||
|
||||
return answerOption.selected ? theme.colors.accent : theme.colors.quarterlyContent
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
struct PollTimelineAnswerOptionButton_Previews: PreviewProvider {
|
||||
static let stateRenderer = MockPollTimelineScreenState.stateRenderer
|
||||
static var previews: some View {
|
||||
|
||||
Group {
|
||||
VStack {
|
||||
PollTimelineAnswerOptionButton(answerOption: TimelineAnswerOption(id: "", text: "Test", count: 5, winner: false, selected: false),
|
||||
pollClosed: false, showResults: true, totalAnswerCount: 100, action: {})
|
||||
|
||||
PollTimelineAnswerOptionButton(answerOption: TimelineAnswerOption(id: "", text: "Test", count: 5, winner: false, selected: false),
|
||||
pollClosed: false, showResults: false, totalAnswerCount: 100, action: {})
|
||||
|
||||
PollTimelineAnswerOptionButton(answerOption: TimelineAnswerOption(id: "", text: "Test", count: 8, winner: false, selected: true),
|
||||
pollClosed: false, showResults: true, totalAnswerCount: 100, action: {})
|
||||
|
||||
PollTimelineAnswerOptionButton(answerOption: TimelineAnswerOption(id: "", text: "Test", count: 8, winner: false, selected: true),
|
||||
pollClosed: false, showResults: false, totalAnswerCount: 100, action: {})
|
||||
|
||||
PollTimelineAnswerOptionButton(answerOption: TimelineAnswerOption(id: "",
|
||||
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
|
||||
count: 200, winner: false, selected: false),
|
||||
pollClosed: false, showResults: true, totalAnswerCount: 1000, action: {})
|
||||
|
||||
PollTimelineAnswerOptionButton(answerOption: TimelineAnswerOption(id: "",
|
||||
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
|
||||
count: 200, winner: false, selected: false),
|
||||
pollClosed: false, showResults: false, totalAnswerCount: 1000, action: {})
|
||||
}
|
||||
|
||||
VStack {
|
||||
PollTimelineAnswerOptionButton(answerOption: TimelineAnswerOption(id: "", text: "Test", count: 5, winner: false, selected: false),
|
||||
pollClosed: true, showResults: true, totalAnswerCount: 100, action: {})
|
||||
|
||||
PollTimelineAnswerOptionButton(answerOption: TimelineAnswerOption(id: "", text: "Test", count: 5, winner: true, selected: false),
|
||||
pollClosed: true, showResults: true, totalAnswerCount: 100, action: {})
|
||||
|
||||
PollTimelineAnswerOptionButton(answerOption: TimelineAnswerOption(id: "", text: "Test", count: 8, winner: false, selected: true),
|
||||
pollClosed: true, showResults: true, totalAnswerCount: 100, action: {})
|
||||
|
||||
PollTimelineAnswerOptionButton(answerOption: TimelineAnswerOption(id: "", text: "Test", count: 8, winner: true, selected: true),
|
||||
pollClosed: true, showResults: true, totalAnswerCount: 100, action: {})
|
||||
|
||||
PollTimelineAnswerOptionButton(answerOption: TimelineAnswerOption(id: "",
|
||||
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
|
||||
count: 200, winner: false, selected: false),
|
||||
pollClosed: true, showResults: true, totalAnswerCount: 1000, action: {})
|
||||
|
||||
PollTimelineAnswerOptionButton(answerOption: TimelineAnswerOption(id: "",
|
||||
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
|
||||
count: 200, winner: true, selected: false),
|
||||
pollClosed: true, showResults: true, totalAnswerCount: 1000, action: {})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
// File created from SimpleUserProfileExample
|
||||
// $ createScreen.sh Room/PollEditForm PollEditForm
|
||||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
struct PollTimelineView: View {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@Environment(\.theme) private var theme: ThemeSwiftUI
|
||||
|
||||
// MARK: Public
|
||||
|
||||
@ObservedObject var viewModel: PollTimelineViewModel.Context
|
||||
|
||||
var body: some View {
|
||||
let poll = viewModel.viewState.poll
|
||||
|
||||
VStack(alignment: .leading, spacing: 16.0) {
|
||||
Text(poll.question)
|
||||
.font(theme.fonts.bodySB)
|
||||
|
||||
VStack(spacing: 24.0) {
|
||||
ForEach(poll.answerOptions) { answerOption in
|
||||
PollTimelineAnswerOptionButton(answerOption: answerOption,
|
||||
pollClosed: poll.closed,
|
||||
showResults: shouldDiscloseResults,
|
||||
totalAnswerCount: poll.totalAnswerCount) {
|
||||
viewModel.send(viewAction: .selectAnswerOptionWithIdentifier(answerOption.id))
|
||||
}
|
||||
}
|
||||
.alert(isPresented: $viewModel.showsClosingFailureAlert) {
|
||||
Alert(title: Text(VectorL10n.pollTimelineNotClosedTitle),
|
||||
message: Text(VectorL10n.pollTimelineNotClosedSubtitle),
|
||||
dismissButton: .default(Text(VectorL10n.pollTimelineNotClosedAction)))
|
||||
}
|
||||
}
|
||||
.disabled(poll.closed)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
|
||||
Text(totalVotesString)
|
||||
.font(theme.fonts.footnote)
|
||||
.foregroundColor(theme.colors.tertiaryContent)
|
||||
.alert(isPresented: $viewModel.showsAnsweringFailureAlert) {
|
||||
Alert(title: Text(VectorL10n.pollTimelineVoteNotRegisteredTitle),
|
||||
message: Text(VectorL10n.pollTimelineVoteNotRegisteredSubtitle),
|
||||
dismissButton: .default(Text(VectorL10n.pollTimelineVoteNotRegisteredAction)))
|
||||
}
|
||||
}
|
||||
.padding([.horizontal, .top], 2.0)
|
||||
.padding([.bottom])
|
||||
}
|
||||
|
||||
private var totalVotesString: String {
|
||||
let poll = viewModel.viewState.poll
|
||||
|
||||
if poll.closed {
|
||||
if poll.totalAnswerCount == 1 {
|
||||
return VectorL10n.pollTimelineTotalFinalResultsOneVote
|
||||
} else {
|
||||
return VectorL10n.pollTimelineTotalFinalResults(Int(poll.totalAnswerCount))
|
||||
}
|
||||
}
|
||||
|
||||
switch poll.totalAnswerCount {
|
||||
case 0:
|
||||
return VectorL10n.pollTimelineTotalNoVotes
|
||||
case 1:
|
||||
return (poll.hasCurrentUserVoted ?
|
||||
VectorL10n.pollTimelineTotalOneVote :
|
||||
VectorL10n.pollTimelineTotalOneVoteNotVoted)
|
||||
default:
|
||||
return (poll.hasCurrentUserVoted ?
|
||||
VectorL10n.pollTimelineTotalVotes(Int(poll.totalAnswerCount)) :
|
||||
VectorL10n.pollTimelineTotalVotesNotVoted(Int(poll.totalAnswerCount)))
|
||||
}
|
||||
}
|
||||
|
||||
private var shouldDiscloseResults: Bool {
|
||||
let poll = viewModel.viewState.poll
|
||||
|
||||
if poll.closed {
|
||||
return poll.totalAnswerCount > 0
|
||||
} else {
|
||||
return poll.type == .disclosed && poll.totalAnswerCount > 0 && poll.hasCurrentUserVoted
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
struct PollTimelineView_Previews: PreviewProvider {
|
||||
static let stateRenderer = MockPollTimelineScreenState.stateRenderer
|
||||
static var previews: some View {
|
||||
stateRenderer.screenGroup()
|
||||
}
|
||||
}
|
||||
@@ -9,21 +9,20 @@ ib:
|
||||
templateName: scenes-swift4
|
||||
output: Storyboards.swift
|
||||
strings:
|
||||
inputs: Assets/en.lproj/Vector.strings
|
||||
outputs:
|
||||
templatePath: Templates/Strings/flat-swift4-vector.stencil
|
||||
output: Strings.swift
|
||||
params:
|
||||
className: VectorL10n
|
||||
publicAccess: true
|
||||
strings:
|
||||
inputs: Modules/MatrixKit/Assets/MatrixKitAssets.bundle/en.lproj/MatrixKit.strings
|
||||
outputs:
|
||||
templatePath: Templates/Strings/matrixkit-flat-swift4-vector.stencil
|
||||
output: MatrixKitStrings.swift
|
||||
params:
|
||||
className: MatrixKitL10n
|
||||
publicAccess: true
|
||||
- inputs: Assets/en.lproj/Vector.strings
|
||||
outputs:
|
||||
templatePath: Templates/Strings/flat-swift4-vector.stencil
|
||||
output: Strings.swift
|
||||
params:
|
||||
className: VectorL10n
|
||||
publicAccess: true
|
||||
- inputs: Modules/MatrixKit/Assets/MatrixKitAssets.bundle/en.lproj/MatrixKit.strings
|
||||
outputs:
|
||||
templatePath: Templates/Strings/matrixkit-flat-swift4-vector.stencil
|
||||
output: MatrixKitStrings.swift
|
||||
params:
|
||||
className: MatrixKitL10n
|
||||
publicAccess: true
|
||||
xcassets:
|
||||
inputs:
|
||||
- Assets/Images.xcassets
|
||||
|
||||