From 7fb67111feba9cf54920955a6ec5ebe335af5b22 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 7 Dec 2021 22:45:35 +0100 Subject: [PATCH 01/46] Prepare for new sprint --- Config/AppVersion.xcconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Config/AppVersion.xcconfig b/Config/AppVersion.xcconfig index d414d01ef..ef94fbbf6 100644 --- a/Config/AppVersion.xcconfig +++ b/Config/AppVersion.xcconfig @@ -15,5 +15,5 @@ // // Version -MARKETING_VERSION = 1.6.9 -CURRENT_PROJECT_VERSION = 1.6.9 +MARKETING_VERSION = 1.6.10 +CURRENT_PROJECT_VERSION = 1.6.10 From 1cfbd5cb2b6e722e0ab048c181d0dff35137a894 Mon Sep 17 00:00:00 2001 From: Jozef Gaal Date: Wed, 8 Dec 2021 02:06:21 +0000 Subject: [PATCH 02/46] Added translation using Weblate (Slovak) --- Riot/Assets/sk.lproj/Vector.strings | 1 + 1 file changed, 1 insertion(+) create mode 100644 Riot/Assets/sk.lproj/Vector.strings diff --git a/Riot/Assets/sk.lproj/Vector.strings b/Riot/Assets/sk.lproj/Vector.strings new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/Riot/Assets/sk.lproj/Vector.strings @@ -0,0 +1 @@ + From 31c53b610af1a4db4df354ee2427b911b5c25549 Mon Sep 17 00:00:00 2001 From: Jozef Gaal Date: Wed, 8 Dec 2021 02:09:37 +0000 Subject: [PATCH 03/46] Added translation using Weblate (Slovak) --- Riot/Assets/sk.lproj/Localizable.strings | 1 + 1 file changed, 1 insertion(+) create mode 100644 Riot/Assets/sk.lproj/Localizable.strings diff --git a/Riot/Assets/sk.lproj/Localizable.strings b/Riot/Assets/sk.lproj/Localizable.strings new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/Riot/Assets/sk.lproj/Localizable.strings @@ -0,0 +1 @@ + From c70c8a1d70b63935461cf695433fa09ecce6c26b Mon Sep 17 00:00:00 2001 From: Jozef Gaal Date: Wed, 8 Dec 2021 02:09:42 +0000 Subject: [PATCH 04/46] Added translation using Weblate (Slovak) --- Riot/Assets/sk.lproj/InfoPlist.strings | 1 + 1 file changed, 1 insertion(+) create mode 100644 Riot/Assets/sk.lproj/InfoPlist.strings diff --git a/Riot/Assets/sk.lproj/InfoPlist.strings b/Riot/Assets/sk.lproj/InfoPlist.strings new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/Riot/Assets/sk.lproj/InfoPlist.strings @@ -0,0 +1 @@ + From 509f5aaa3d2214f62ab0183b75631ab093d0f17f Mon Sep 17 00:00:00 2001 From: Doug Date: Wed, 8 Dec 2021 16:19:24 +0000 Subject: [PATCH 05/46] Use a single strings step with 2 actions. --- Tools/SwiftGen/swiftgen-config.yml | 29 ++++++++++++++--------------- changelog.d/5280.build | 1 + 2 files changed, 15 insertions(+), 15 deletions(-) create mode 100644 changelog.d/5280.build diff --git a/Tools/SwiftGen/swiftgen-config.yml b/Tools/SwiftGen/swiftgen-config.yml index cad5c1099..3d1cd6693 100755 --- a/Tools/SwiftGen/swiftgen-config.yml +++ b/Tools/SwiftGen/swiftgen-config.yml @@ -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 diff --git a/changelog.d/5280.build b/changelog.d/5280.build new file mode 100644 index 000000000..e5a166d66 --- /dev/null +++ b/changelog.d/5280.build @@ -0,0 +1 @@ +Fix SwiftGen only generating strings for MatrixKit. \ No newline at end of file From 3c75300a9bc21434cb5de79e68cda3d2363b8f53 Mon Sep 17 00:00:00 2001 From: Jozef Gaal Date: Wed, 8 Dec 2021 02:20:05 +0000 Subject: [PATCH 06/46] Translated using Weblate (Slovak) Currently translated at 43.7% (21 of 48 strings) Translation: Element iOS/Element iOS (Push) Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios-push/sk/ --- Riot/Assets/sk.lproj/Localizable.strings | 76 ++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/Riot/Assets/sk.lproj/Localizable.strings b/Riot/Assets/sk.lproj/Localizable.strings index 8b1378917..950c96d36 100644 --- a/Riot/Assets/sk.lproj/Localizable.strings +++ b/Riot/Assets/sk.lproj/Localizable.strings @@ -1 +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"; From 6886d98ae81d5d9f499713ab5fe0ab3104c44348 Mon Sep 17 00:00:00 2001 From: Jozef Gaal Date: Wed, 8 Dec 2021 02:12:45 +0000 Subject: [PATCH 07/46] Translated using Weblate (Slovak) Currently translated at 100.0% (6 of 6 strings) Translation: Element iOS/Element iOS (Dialogs) Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios-dialogs/sk/ --- Riot/Assets/sk.lproj/InfoPlist.strings | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Riot/Assets/sk.lproj/InfoPlist.strings b/Riot/Assets/sk.lproj/InfoPlist.strings index 8b1378917..9d8892825 100644 --- a/Riot/Assets/sk.lproj/InfoPlist.strings +++ b/Riot/Assets/sk.lproj/InfoPlist.strings @@ -1 +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."; From 17f2f7163491a0649fb8d697a003ea634c8bb42c Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Fri, 19 Nov 2021 17:40:52 +0200 Subject: [PATCH 08/46] vector-im/element-ios/issues/5114 - Polls in the timeline. --- Config/BuildSettings.swift | 19 +- .../Contents.json | 23 ++ .../poll_checkbox_default.png | Bin 0 -> 615 bytes .../poll_checkbox_default@2x.png | Bin 0 -> 1153 bytes .../poll_checkbox_default@3x.png | Bin 0 -> 1705 bytes .../Contents.json | 23 ++ .../poll_checkbox_selected.png | Bin 0 -> 632 bytes .../poll_checkbox_selected@2x.png | Bin 0 -> 1127 bytes .../poll_checkbox_selected@3x.png | Bin 0 -> 1590 bytes .../poll_winner_icon.imageset/Contents.json | 23 ++ .../poll_winner_icon.png | Bin 0 -> 385 bytes .../poll_winner_icon@2x.png | Bin 0 -> 630 bytes .../poll_winner_icon@3x.png | Bin 0 -> 695 bytes Riot/Assets/en.lproj/Vector.strings | 42 +- Riot/Generated/Images.swift | 3 + Riot/Generated/Strings.swift | 90 ++++- Riot/Managers/Settings/RiotSettings.swift | 3 + .../Modules/MatrixKit/Models/MXKAppSettings.m | 12 +- .../MXKRoomBubbleCellDataWithAppendingMode.m | 10 + .../MatrixKit/Models/Room/MXKRoomDataSource.m | 20 +- .../Utils/EventFormatter/MXKEventFormatter.m | 6 +- .../Room/CellData/RoomBubbleCellData.h | 3 +- .../Room/CellData/RoomBubbleCellData.m | 21 +- Riot/Modules/Room/RoomCoordinator.swift | 23 +- Riot/Modules/Room/RoomViewController.h | 6 + Riot/Modules/Room/RoomViewController.m | 369 ++++++++++-------- .../BaseBubbleCell/BubbleCellContentView.xib | 44 +-- .../BubbleCells/Poll/PollBubbleCell.swift | 59 +++ .../PollWithPaginationTitleBubbleCell.swift | 27 ++ .../PollWithoutSenderInfoBubbleCell.swift | 27 ++ .../Modules/Settings/SettingsViewController.m | 25 +- RiotNSE/NotificationService.swift | 3 + .../Modules/Common/Mock/MockAppScreens.swift | 3 +- .../Coordinator/PollEditFormCoordinator.swift | 31 +- .../PollEditForm/PollEditFormModels.swift | 8 +- .../PollEditForm/PollEditFormViewModel.swift | 14 +- .../Room/PollEditForm/View/PollEditForm.swift | 16 +- .../Coordinator/PollTimelineCoordinator.swift | 143 +++++++ .../Coordinator/PollTimelineProvider.swift | 55 +++ .../PollTimeline/PollTimelineModels.swift | 91 +++++ .../PollTimelineScreenState.swift | 47 +++ .../PollTimeline/PollTimelineViewModel.swift | 132 +++++++ .../Test/UI/PollTimelineUITests.swift | 98 +++++ .../Unit/PollTimelineViewModelTests.swift | 149 +++++++ .../View/PollTimelineAnswerOptionButton.swift | 155 ++++++++ .../PollTimeline/View/PollTimelineView.swift | 116 ++++++ changelog.d/5114.feature | 1 + 47 files changed, 1717 insertions(+), 223 deletions(-) create mode 100644 Riot/Assets/Images.xcassets/Room/Polls/poll_checkbox_default.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/Room/Polls/poll_checkbox_default.imageset/poll_checkbox_default.png create mode 100644 Riot/Assets/Images.xcassets/Room/Polls/poll_checkbox_default.imageset/poll_checkbox_default@2x.png create mode 100644 Riot/Assets/Images.xcassets/Room/Polls/poll_checkbox_default.imageset/poll_checkbox_default@3x.png create mode 100644 Riot/Assets/Images.xcassets/Room/Polls/poll_checkbox_selected.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/Room/Polls/poll_checkbox_selected.imageset/poll_checkbox_selected.png create mode 100644 Riot/Assets/Images.xcassets/Room/Polls/poll_checkbox_selected.imageset/poll_checkbox_selected@2x.png create mode 100644 Riot/Assets/Images.xcassets/Room/Polls/poll_checkbox_selected.imageset/poll_checkbox_selected@3x.png create mode 100644 Riot/Assets/Images.xcassets/Room/Polls/poll_winner_icon.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/Room/Polls/poll_winner_icon.imageset/poll_winner_icon.png create mode 100644 Riot/Assets/Images.xcassets/Room/Polls/poll_winner_icon.imageset/poll_winner_icon@2x.png create mode 100644 Riot/Assets/Images.xcassets/Room/Polls/poll_winner_icon.imageset/poll_winner_icon@3x.png create mode 100644 Riot/Modules/Room/Views/BubbleCells/Poll/PollBubbleCell.swift create mode 100644 Riot/Modules/Room/Views/BubbleCells/Poll/PollWithPaginationTitleBubbleCell.swift create mode 100644 Riot/Modules/Room/Views/BubbleCells/Poll/PollWithoutSenderInfoBubbleCell.swift create mode 100644 RiotSwiftUI/Modules/Room/PollTimeline/Coordinator/PollTimelineCoordinator.swift create mode 100644 RiotSwiftUI/Modules/Room/PollTimeline/Coordinator/PollTimelineProvider.swift create mode 100644 RiotSwiftUI/Modules/Room/PollTimeline/PollTimelineModels.swift create mode 100644 RiotSwiftUI/Modules/Room/PollTimeline/PollTimelineScreenState.swift create mode 100644 RiotSwiftUI/Modules/Room/PollTimeline/PollTimelineViewModel.swift create mode 100644 RiotSwiftUI/Modules/Room/PollTimeline/Test/UI/PollTimelineUITests.swift create mode 100644 RiotSwiftUI/Modules/Room/PollTimeline/Test/Unit/PollTimelineViewModelTests.swift create mode 100644 RiotSwiftUI/Modules/Room/PollTimeline/View/PollTimelineAnswerOptionButton.swift create mode 100644 RiotSwiftUI/Modules/Room/PollTimeline/View/PollTimelineView.swift create mode 100644 changelog.d/5114.feature diff --git a/Config/BuildSettings.swift b/Config/BuildSettings.swift index 0919c08fd..525870b51 100644 --- a/Config/BuildSettings.swift +++ b/Config/BuildSettings.swift @@ -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 + } } diff --git a/Riot/Assets/Images.xcassets/Room/Polls/poll_checkbox_default.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Polls/poll_checkbox_default.imageset/Contents.json new file mode 100644 index 000000000..dad70857c --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Polls/poll_checkbox_default.imageset/Contents.json @@ -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 + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Polls/poll_checkbox_default.imageset/poll_checkbox_default.png b/Riot/Assets/Images.xcassets/Room/Polls/poll_checkbox_default.imageset/poll_checkbox_default.png new file mode 100644 index 0000000000000000000000000000000000000000..84e419079478efbbd5b03ef22c5b6c750ec45e79 GIT binary patch literal 615 zcmV-t0+{`YP)p&1avqt&2U+LibR&Zl+xe`GLTtQ3+PywU@k_tix&;g`^MM6ms4<&S9I^Y}R9d9{b zK%EKcQpASQUM8d#4c-ifr*#bxy(_7b6JBTJCKQn zP0NNa2Bsa>x6ZHvx_^J7pgJyFQmx(XpuJQ;_Z|-=8;K>$aOeLB1)zI(HASAbVby>S z!ur3f?A=U>EVln}2g%R&*mhCtVjqw*Hz1Qgf} z+5>H)BoZXulr1k{!{6z&!llW9HeBr$(cioPu{07sQ~P;@+d-U$eqP{JSSiq^*JYCK<?p-hInDV`jh3@1+5=9&tATqJFAHJSOt;^8U+O9hf*1T)yaUqy)<{&lLtOv>002ovPDHLkV1lm0 B5dr`J literal 0 HcmV?d00001 diff --git a/Riot/Assets/Images.xcassets/Room/Polls/poll_checkbox_default.imageset/poll_checkbox_default@2x.png b/Riot/Assets/Images.xcassets/Room/Polls/poll_checkbox_default.imageset/poll_checkbox_default@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..7e6083bc319d633e3955789d9d89f19b8123b096 GIT binary patch literal 1153 zcmV-{1b+L8P)iLCFA}0fvWHP*_3J70|ALu!6!02rDREfddRM!(##~sJnxFbxMk* zUKs{z0X81C7NI? zV30v5#VrKt-pWE91)whizTcRcc!ez?#Fr;yvm;;(_3%$?$MY5g^oX7aD8rp8Y)g>g z1%QaudL@#ZfPZ$i!6n=8VaB^55E47y@?0%Xqg(`z&l=x4%z9HY97=&BAiTl!WZ;#{ zP4&WWG-mMouM0S{U~-*T^u zS2)8q{dJ8lX-Tav&VA0(PbLB>#y5o&d*1Rw6@9=zy{yu#Z|mh*NpvM}a^Bb`L#tY= zK!|YDTmEo^0^pxFTrPzV?Q1ab-PNV?*G_Fa5{N(ZzSbfTXpzI{-St_=N+BU2H@&6# z!?bp(1jtpod`jzZK*m3fX(!PXcl@2_+4mPu>x@r@RT#4|IO7$(#WK0laFhPF8ylTD zX>F_o)a=uAe`+tIeXuh~;~$~+m2wX4Wy*D{!_!vZN+6#7XVB8=*W2VlJXVk>EZtkr zpOhuU*4m$hK<0y`CYf z<#ot~{zw!$6sBw7W&ul7dWdaC1j*{!^SWNevIqFvorUQOuC8Z z)?|QD!5rIgr8X~=sFZdB4g;34GZFZ&U?Y%Vopu5a1D3Ik)zFmyN-C2Y5sa}}pjR;o zNO`N37`}ISa1vuvjPy&dVGs~Z*4v*Mch)c>aLS*PCDL%|<`)Q&sKg{TMz;^5sed3CU&(F zSE&aW5|oetEgO_*Swng&fhZD`(ul|X(~E(7B$W;7DhNfywX|$7{UmNVRcL#GGv~YS zpSu_k+JuB>(ArO%eT7s4aXhXIOEf^q9|Q@j3WA4huv0GHF&0vKjJ}jG0201`F>5tm z2?<@Y!LGraw>(eDv^>y?6^WnmX6p;84_y(T<#`BTEr3ZOEx`GneNV{Jk@_QCN=zvS5-$C^cn-#zGCCCHjTG#Nw3?fuu35(zZF6Btggid{gJ?F4ZG?-7)$ zJb2oZt^d3S6;O#lAC6956X>l1 zQ$~caPbr*&q6F}tAuKPCqTaU%6(1jm9JNA;cBtqHyAemb8!Y6{D2RfBd^|pJ<~JPT_w};sH6@M^t@RRp zu6mLW|b2ke%^Mu96#d*KjS`l;w=#Sd#Jj0 zGnVxxT7S3=b3lCf<&8poVHqB=wf*S39(lW%>ZaRZ5l7r=Uc?ci^#@xHcgwPU@We}? zEdK!9G!F8DOn239FlVIa*)t`kG1&^qei1zI5-7lqb#c=$j_L~Ivmh>{Dc!J*%Vrup z$Q4MGwneRM-C3)@CQ}xPq**RzKz@^t73B(~6q=(FyOtz@Fz<}BXEa*4j}1=_ncafqXvRe5Ud`>Zs^X#sWp*>$ zZv~e?3i_s(wWd>ODZCFmFb)JzTp%?qaoz-J0Ih|LmXD~cbuFtvJn0AHKnksMuW!gn zt>IO|;uge91Xn^L%<*k;Pqr2kg}m7%ae zd3%@t8ZLnn`ERdSwf91O5^R>Y^R#@aeB&;wTj>CA&tNDs`DaI<~+P}Wm)10sCq zn&nOu&pUgTk*w_mT7vWxfX%(sc;K3wdp7TAbt!bleOrj7tFAr)gFsxTKHy_&=W>y| z)kxFS*5}s>E9xwRG%2e}PZUYk{i z6>)S3-p}KLec@WMZJbEI1P{Cfk`n<(J4sliounx)o+&hq5yGCY)q9saF4{?q3$nD6 zlxc6Pi)ovnCF7TN9Ts~Qzx-7T9~N|{9k>KXgXrq8u2aVxQ$SsJ}6xek@rylo)iFCL~E zeh1P+Ijy}KSu(ej!vU#(YYel?znT}{0(a4>YHF|ULY?>Ro7&0Jks{snE~>qfDaR_5 zZYx_)bU4{alMcm&l@u0lr|NYAMmy zDa<9c3Uo>4dRq4pX>(Sq%0$t40N<&-Cfbl25$G!M2}h{EC#%xphtp#ij!YQhwZ)y} zIGAX}^^{?D`B$Rd_nl!Idb1ZSSg>Hhg8ATI6MEZVK&@sK00000NkvXXu0mjfI}#^A literal 0 HcmV?d00001 diff --git a/Riot/Assets/Images.xcassets/Room/Polls/poll_checkbox_selected.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Polls/poll_checkbox_selected.imageset/Contents.json new file mode 100644 index 000000000..e1f28a0dc --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Polls/poll_checkbox_selected.imageset/Contents.json @@ -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 + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Polls/poll_checkbox_selected.imageset/poll_checkbox_selected.png b/Riot/Assets/Images.xcassets/Room/Polls/poll_checkbox_selected.imageset/poll_checkbox_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..8771564f496a8a47805ec93d9bec4f1d0153bce1 GIT binary patch literal 632 zcmV-;0*C#HP)@pS_X=GYNBC#|!L`)gpRP1)PGKyw9wbRDLj-fCXVx}>6 z8jK+n6#NQG7M216%;mn>o$SuM+1XjB;3SiI^X|NJ-sj%)fa4BI3!0u|?&FLNA9sjo zm>E_uh%n2{OVXUK-gxo(Py+dx*EW$+X4FViumo_am5`CtS@7N!LB>=Das~N^J{7#l zVp5Qj$l3QD90Fv(*&KOZYAKNa`R1XT4UPh)yVcQWZun_L)u@OLVdhTbLEmNc=DO=7 zTsFP3sR^jrXWa7$`M0b0@NwW4hVtjaZfoD)wKV}*nvD(+k`MKq$E(HP__e;;2r_g5 zIiP-IM4Vhb5fEL`1f+eX z!9yCvrfTwThXy zwyb0-KB8CBa-)7O?ZJga@<6r?tT>lSXvmC06E?W$&&NphX0yNs$^8F4B&u{Z!S>&0000Bw3?I?N&Ttp7Ff>w0BvB}-zywq(pdxx2pwSX6Fj`5Wu@JIn zV(cV*84FD~w7^K=>75D6-Hh{&J2`e|Z)fjz7ZdN75H9a#Z~y=K+Ba_j%9Q!PA>a{} z#(0P_t%U<`C4{taPILgSXKxcHgvU7NF#>#xhoj&DB^l6-QJuk&e$L3io>*mK z;`Y%Ux-+&N9*#kg6lEYQe1tO?08bz@#Ts#Kplbg1B_I{kMfA86G4Y#YiMAUvF0KLU zrg60qnE*nc`XC;u7yUUc?lI!#BIOaNGVV?T<2?`vWG2xz*sIi1rBSfrbEuhcoI=&c zy^-L>BfO`2K}=Z(3XVY4!kr$RmvE49(wS%GY3mRWO_j%Cwy6`or?#PcWezsBe^}axQ8*?W z1?dQ2??XVK_~&Q3pzd(B_{ga^jC_hyKP^6I;@@9B98R zZZS}4L_$H#eVO?2^*1p4WzFsqz4B!wWDZ&kP{u;wGdNuyi8*f;kAH=rl(kr5<^bok z1$+d>W6#%$|2b5|Lq;13nPV0MghCzoL@DAk5YlW8Nk+h*`7aZH|MOdQ@w-^EV5-un zRAl03H{Zdn4+gkI$$*29%kcW#)y$r)evMUp-}@Kf1u_G1yT6F!J{ zeiDm^n5}`;RV;S^EX0AZklX=9t9*W3U7c zpaI+hbL31kX4ghz-rDd={H`_{HOHjsbk#!S8z^k24^CZ#?vtl&zxPtSAeLwwYA^@o z*(ksiD1dVscNH0!(_ay}y+`6vRa6$08lY601Vv;fP<+SYWB4RFm=p_E?qqSP0X+GL z#7SuskbDzg~p?*S7vlJdHl~W5`%-#ym5Zj@tu$=+hR3dZ!x1K{so7qml7Z&-d8S&*fi3;iT0awZmx25ph0j^QL(MzFoQ>6uUF7y zgIn=Pi7)v?2~t$?un^{cL`a-7KBZ|tC*$GKI24I@uUSRWNz`pbQLLg_q`mcG`>i*< tX2qxbl6cMf3Nzm1g`p^vDO09o{s03=!wWKHwbB3p002ovPDHLkV1jJp0iFN= literal 0 HcmV?d00001 diff --git a/Riot/Assets/Images.xcassets/Room/Polls/poll_checkbox_selected.imageset/poll_checkbox_selected@3x.png b/Riot/Assets/Images.xcassets/Room/Polls/poll_checkbox_selected.imageset/poll_checkbox_selected@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..cb6195cb5937799b7086b239f70d9d967236b91e GIT binary patch literal 1590 zcmV-62Fdw}P)P%Y#U|PJ=FRtPnID9bzPFQ|-~QkC ze-Z0#y!B1(_&TGkV!*w% zT6wAlF%pX)l)_lsC@1hLa410#2vmf$r5$Ql?v^L25HXPo%9}GK!i~wmQl#(|PVI$y zd7=Ul5s9Ga#q2>u6(je@w#)CWLCAzE2%D14@)yM?dlC|XEbh>q=`fpKXoA|m`QRjw z9Lgy`vQ));nBF>Zei1@|LI`x2bMrvxath=iz2bAfDBjZ~gg~GOa+(xxTaK--@rvl1 za>N*}mB)Nf4SWl7j?yR;D=P@)*e445^M{3;mztJ+e(U!za$w3SV0UTX$2u6CucEj<`ZWy}u8T@(oU+{{XoS|!%;f{3=;zHg@ z6FkDz<9KJ+uHU;w1mR73jidzSRAmLKCZ>IcRNLAAroBqSdY|?WClfiuhae7Y9W=o{ zp;>d%mDKaFUU!pvc-@hWm1vt4<9B35@+QWqcCO8)xCvRG(~uDLEo~uDB(9x~|2z4bu<++&n+?^Hwn%~IAaw2l@o^~2>b z62x(>g(Db7O`NOp-H^qt6K7BB)YRS&%F!HNFz}N$C4tlHRKe4ZBIfpqo__&a6wa9EM-+VJNwhYlHW%;eV=Mzr;X|TK_`)sf)BFE&u=k literal 0 HcmV?d00001 diff --git a/Riot/Assets/Images.xcassets/Room/Polls/poll_winner_icon.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Polls/poll_winner_icon.imageset/Contents.json new file mode 100644 index 000000000..61c735d9b --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Polls/poll_winner_icon.imageset/Contents.json @@ -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 + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Polls/poll_winner_icon.imageset/poll_winner_icon.png b/Riot/Assets/Images.xcassets/Room/Polls/poll_winner_icon.imageset/poll_winner_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..255cdff7393aaffe44fdb4cf545bc9674c8f8680 GIT binary patch literal 385 zcmV-{0e=38P)#8kHZVW4rxQkhV`FN`7?_+|)wUj6_FQm|L9~at;GJAsPM|XBu|uQU?>}Em z^dzOsuO#@`K7MgH^GW2o_FV5$PW{i|^zA>m1?%^hAu;+zPLw(ucTU2JDQaM9B~V}T zV;q$GUkL8g`9K>*vow_itx?#TpxMm_`E7;dMh}UDwKxfSNm1V@8>DD@tZ4eHm}UqQ f`P$Xf5G#dmd5c{n)wo?p00000NkvXXu0mjfnUI_m literal 0 HcmV?d00001 diff --git a/Riot/Assets/Images.xcassets/Room/Polls/poll_winner_icon.imageset/poll_winner_icon@2x.png b/Riot/Assets/Images.xcassets/Room/Polls/poll_winner_icon.imageset/poll_winner_icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a9a25f56d54749b854cd3c1778cc0f1eaf501379 GIT binary patch literal 630 zcmV-+0*U>JP)c(5gyNpV&NAG!KLml%9b0xrL-RpeGQzxD(#;F$9m)Y%}N0|PRI36)zM0M3pM@QlO5yNF1GYaI?;zB8PC zpmN+BGBn2~WG%}x~K-*~IDP=wCP2=ag-ONMK9tj*o8)M@_nL`}M` zCs>gX0;a5Dlj+vnB0QYAc#IJC0zdmrlzBGxhEc;iKO$l^?qz>VGQ7+ z^K#*PH62fh&&2s_QxL`!N|Jhi4mz&|3pxu7%wvGCj(NdontrOIpvg)%m2QpG1BOF) zoZpxUPkt79z5x)aC4R=sF9*wbB#TimmGdgMewf-C17L}tsfI!>JP@5aQr4r)*W<~` z&cJj`?pC7l^>72kC$}6nQ1@gD4v|2p(l)&L3%9KU(A3}!)yelz0hV%qy0#nUwJB84 zR1rjx&TEA$vc_p|i$Hsh>ww#gRVuZoYu!j`?aAuy5DqvC;F1nRW#+n`sWO)u68M`Ce+F?Y zjNyp$04`-Kwrs`aim%A3<^!nO^L{p($G;!AR@wUjm(NYD7q${(fIFF@-;&x|B;|Ib dp67Yh`3G`7)-fU-6N~@=002ovPDHLkV1j;}GkX94 literal 0 HcmV?d00001 diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index c8b57ab05..768862c3b 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -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"; diff --git a/Riot/Generated/Images.swift b/Riot/Generated/Images.swift index 92e82c765..3c25ee336 100644 --- a/Riot/Generated/Images.swift +++ b/Riot/Generated/Images.swift @@ -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") diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 3ddc20669..4ab7572a3 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -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") diff --git a/Riot/Managers/Settings/RiotSettings.swift b/Riot/Managers/Settings/RiotSettings.swift index dade3bafd..5b31329d4 100644 --- a/Riot/Managers/Settings/RiotSettings.swift +++ b/Riot/Managers/Settings/RiotSettings.swift @@ -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 diff --git a/Riot/Modules/MatrixKit/Models/MXKAppSettings.m b/Riot/Modules/MatrixKit/Models/MXKAppSettings.m index 22b48f831..88aa527f7 100644 --- a/Riot/Modules/MatrixKit/Models/MXKAppSettings.m +++ b/Riot/Modules/MatrixKit/Models/MXKAppSettings.m @@ -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; diff --git a/Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleCellDataWithAppendingMode.m b/Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleCellDataWithAppendingMode.m index 680086ddd..6bec70d0c 100644 --- a/Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleCellDataWithAppendingMode.m +++ b/Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleCellDataWithAppendingMode.m @@ -36,6 +36,11 @@ static NSAttributedString *messageSeparator = nil; - (BOOL)addEvent:(MXEvent*)event andRoomState:(MXRoomState*)roomState { + // Never merge polls + if (self.events.firstObject.eventType == MXEventTypePollStart) { + return NO; + } + // We group together text messages from the same user (attachments are not merged). if ([event.sender isEqualToString:self.senderId] && (self.attachment == nil) && (self.bubbleComponents.count < self.maxComponentCount)) { @@ -85,6 +90,11 @@ static NSAttributedString *messageSeparator = nil; - (BOOL)mergeWithBubbleCellData:(id)bubbleCellData { + // Never merge polls + if (self.events.firstObject.eventType == MXEventTypePollStart) { + return NO; + } + if ([self hasSameSenderAsBubbleCellData:bubbleCellData]) { MXKRoomBubbleCellData *cellData = (MXKRoomBubbleCellData*)bubbleCellData; diff --git a/Riot/Modules/MatrixKit/Models/Room/MXKRoomDataSource.m b/Riot/Modules/MatrixKit/Models/Room/MXKRoomDataSource.m index cfe7e1a31..43ab3e623 100644 --- a/Riot/Modules/MatrixKit/Models/Room/MXKRoomDataSource.m +++ b/Riot/Modules/MatrixKit/Models/Room/MXKRoomDataSource.m @@ -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 diff --git a/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m b/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m index d28641dbf..9177cc5be 100644 --- a/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m +++ b/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m @@ -1596,7 +1596,11 @@ static NSString *const kHTMLATagRegexPattern = @"([^<]*)"; } break; } - + case MXEventTypePollStart: + { + displayText = [MXEventContentPollStart modelFromJSON:event.content].question; + break; + } default: *error = MXKEventFormatterErrorUnknownEventType; break; diff --git a/Riot/Modules/Room/CellData/RoomBubbleCellData.h b/Riot/Modules/Room/CellData/RoomBubbleCellData.h index 526abea1b..ca0058be5 100644 --- a/Riot/Modules/Room/CellData/RoomBubbleCellData.h +++ b/Riot/Modules/Room/CellData/RoomBubbleCellData.h @@ -31,7 +31,8 @@ typedef NS_ENUM(NSInteger, RoomBubbleCellDataTag) RoomBubbleCellDataTagKeyVerificationConclusion, RoomBubbleCellDataTagCall, RoomBubbleCellDataTagGroupCall, - RoomBubbleCellDataTagRoomCreationIntro + RoomBubbleCellDataTagRoomCreationIntro, + RoomBubbleCellDataTagPoll }; /** diff --git a/Riot/Modules/Room/CellData/RoomBubbleCellData.m b/Riot/Modules/Room/CellData/RoomBubbleCellData.m index f73e211c3..8c73b01e9 100644 --- a/Riot/Modules/Room/CellData/RoomBubbleCellData.m +++ b/Riot/Modules/Room/CellData/RoomBubbleCellData.m @@ -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; } @@ -873,6 +889,9 @@ NSString *const URLPreviewDidUpdateNotification = @"URLPreviewDidUpdateNotificat case MXEventTypeCallReject: shouldAddEvent = NO; break; + case MXEventTypePollStart: + shouldAddEvent = NO; + break; case MXEventTypeCustom: { if ([event.type isEqualToString:kWidgetMatrixEventTypeString] diff --git a/Riot/Modules/Room/RoomCoordinator.swift b/Riot/Modules/Room/RoomCoordinator.swift index 3a6c803ff..7f860168f 100644 --- a/Riot/Modules/Room/RoomCoordinator.swift +++ b/Riot/Modules/Room/RoomCoordinator.swift @@ -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() + } } diff --git a/Riot/Modules/Room/RoomViewController.h b/Riot/Modules/Room/RoomViewController.h index 4aa3818bf..74941c31e 100644 --- a/Riot/Modules/Room/RoomViewController.h +++ b/Riot/Modules/Room/RoomViewController.h @@ -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 diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index 518a7ac2f..46c83de52 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -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 bubbleData = (id)cellData; + return nil; + } - MXKRoomBubbleCellData *roomBubbleCellData; - - if ([bubbleData isKindOfClass:MXKRoomBubbleCellData.class]) + id bubbleData = (id)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) { diff --git a/Riot/Modules/Room/Views/BubbleCells/BaseBubbleCell/BubbleCellContentView.xib b/Riot/Modules/Room/Views/BubbleCells/BaseBubbleCell/BubbleCellContentView.xib index 47244effb..56c26c56c 100644 --- a/Riot/Modules/Room/Views/BubbleCells/BaseBubbleCell/BubbleCellContentView.xib +++ b/Riot/Modules/Room/Views/BubbleCells/BaseBubbleCell/BubbleCellContentView.xib @@ -1,9 +1,9 @@ - + - + @@ -17,7 +17,7 @@ - + - + + - + - - - + - + - - @@ -227,7 +225,7 @@ - + @@ -247,6 +245,6 @@ - + diff --git a/Riot/Modules/Room/Views/BubbleCells/Poll/PollBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Poll/PollBubbleCell.swift new file mode 100644 index 000000000..4d3d42e28 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Poll/PollBubbleCell.swift @@ -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]) + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Poll/PollWithPaginationTitleBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Poll/PollWithPaginationTitleBubbleCell.swift new file mode 100644 index 000000000..de6e92041 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Poll/PollWithPaginationTitleBubbleCell.swift @@ -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 + } + +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Poll/PollWithoutSenderInfoBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Poll/PollWithoutSenderInfoBubbleCell.swift new file mode 100644 index 000000000..880ef0cd4 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Poll/PollWithoutSenderInfoBubbleCell.swift @@ -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 + } + +} diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index 91fa9afa7..1993ab9f7 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -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; diff --git a/RiotNSE/NotificationService.swift b/RiotNSE/NotificationService.swift index 9464453ca..2dfaec7f3 100644 --- a/RiotNSE/NotificationService.swift +++ b/RiotNSE/NotificationService.swift @@ -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 } diff --git a/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift b/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift index 51c3e182c..bceefcd26 100644 --- a/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift +++ b/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift @@ -24,7 +24,8 @@ enum MockAppScreens { MockTemplateRoomListScreenState.self, MockTemplateRoomChatScreenState.self, MockUserSuggestionScreenState.self, - MockPollEditFormScreenState.self + MockPollEditFormScreenState.self, + MockPollTimelineScreenState.self ] } diff --git a/RiotSwiftUI/Modules/Room/PollEditForm/Coordinator/PollEditFormCoordinator.swift b/RiotSwiftUI/Modules/Room/PollEditForm/Coordinator/PollEditFormCoordinator.swift index 1df71686e..da63d7098 100644 --- a/RiotSwiftUI/Modules/Room/PollEditForm/Coordinator/PollEditFormCoordinator.swift +++ b/RiotSwiftUI/Modules/Room/PollEditForm/Coordinator/PollEditFormCoordinator.swift @@ -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)) + } } } } diff --git a/RiotSwiftUI/Modules/Room/PollEditForm/PollEditFormModels.swift b/RiotSwiftUI/Modules/Room/PollEditForm/PollEditFormModels.swift index 9e6657506..9c7d856c8 100644 --- a/RiotSwiftUI/Modules/Room/PollEditForm/PollEditFormModels.swift +++ b/RiotSwiftUI/Modules/Room/PollEditForm/PollEditFormModels.swift @@ -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 } diff --git a/RiotSwiftUI/Modules/Room/PollEditForm/PollEditFormViewModel.swift b/RiotSwiftUI/Modules/Room/PollEditForm/PollEditFormViewModel.swift index b77274bcb..f38ac9fc9 100644 --- a/RiotSwiftUI/Modules/Room/PollEditForm/PollEditFormViewModel.swift +++ b/RiotSwiftUI/Modules/Room/PollEditForm/PollEditFormViewModel.swift @@ -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 } } } diff --git a/RiotSwiftUI/Modules/Room/PollEditForm/View/PollEditForm.swift b/RiotSwiftUI/Modules/Room/PollEditForm/View/PollEditForm.swift index 1d874688b..bdf54e7bd 100644 --- a/RiotSwiftUI/Modules/Room/PollEditForm/View/PollEditForm.swift +++ b/RiotSwiftUI/Modules/Room/PollEditForm/View/PollEditForm.swift @@ -59,14 +59,18 @@ struct PollEditForm: View { ForEach(0..() + + private var pollAggregator: PollAggregator + private var pollTimelineViewModel: PollTimelineViewModel! + private var cancellables = Set() + + // 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) + } +} diff --git a/RiotSwiftUI/Modules/Room/PollTimeline/Coordinator/PollTimelineProvider.swift b/RiotSwiftUI/Modules/Room/PollTimeline/Coordinator/PollTimelineProvider.swift new file mode 100644 index 000000000..d778cbedf --- /dev/null +++ b/RiotSwiftUI/Modules/Room/PollTimeline/Coordinator/PollTimelineProvider.swift @@ -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] + } +} diff --git a/RiotSwiftUI/Modules/Room/PollTimeline/PollTimelineModels.swift b/RiotSwiftUI/Modules/Room/PollTimeline/PollTimelineModels.swift new file mode 100644 index 000000000..d01eaf864 --- /dev/null +++ b/RiotSwiftUI/Modules/Room/PollTimeline/PollTimelineModels.swift @@ -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 +} + diff --git a/RiotSwiftUI/Modules/Room/PollTimeline/PollTimelineScreenState.swift b/RiotSwiftUI/Modules/Room/PollTimeline/PollTimelineScreenState.swift new file mode 100644 index 000000000..b7752acf5 --- /dev/null +++ b/RiotSwiftUI/Modules/Room/PollTimeline/PollTimelineScreenState.swift @@ -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))) + } +} diff --git a/RiotSwiftUI/Modules/Room/PollTimeline/PollTimelineViewModel.swift b/RiotSwiftUI/Modules/Room/PollTimeline/PollTimelineViewModel.swift new file mode 100644 index 000000000..eb802509b --- /dev/null +++ b/RiotSwiftUI/Modules/Room/PollTimeline/PollTimelineViewModel.swift @@ -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 +@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)) + } +} diff --git a/RiotSwiftUI/Modules/Room/PollTimeline/Test/UI/PollTimelineUITests.swift b/RiotSwiftUI/Modules/Room/PollTimeline/Test/UI/PollTimelineUITests.swift new file mode 100644 index 000000000..798c07f3c --- /dev/null +++ b/RiotSwiftUI/Modules/Room/PollTimeline/Test/UI/PollTimelineUITests.swift @@ -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%") + } +} diff --git a/RiotSwiftUI/Modules/Room/PollTimeline/Test/Unit/PollTimelineViewModelTests.swift b/RiotSwiftUI/Modules/Room/PollTimeline/Test/Unit/PollTimelineViewModelTests.swift new file mode 100644 index 000000000..9ad80e5b9 --- /dev/null +++ b/RiotSwiftUI/Modules/Room/PollTimeline/Test/Unit/PollTimelineViewModelTests.swift @@ -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() + + 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) + } +} diff --git a/RiotSwiftUI/Modules/Room/PollTimeline/View/PollTimelineAnswerOptionButton.swift b/RiotSwiftUI/Modules/Room/PollTimeline/View/PollTimelineAnswerOptionButton.swift new file mode 100644 index 000000000..e5daad842 --- /dev/null +++ b/RiotSwiftUI/Modules/Room/PollTimeline/View/PollTimelineAnswerOptionButton.swift @@ -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: {}) + } + } + } +} diff --git a/RiotSwiftUI/Modules/Room/PollTimeline/View/PollTimelineView.swift b/RiotSwiftUI/Modules/Room/PollTimeline/View/PollTimelineView.swift new file mode 100644 index 000000000..22a94bb68 --- /dev/null +++ b/RiotSwiftUI/Modules/Room/PollTimeline/View/PollTimelineView.swift @@ -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() + } +} diff --git a/changelog.d/5114.feature b/changelog.d/5114.feature new file mode 100644 index 000000000..489c8dbd8 --- /dev/null +++ b/changelog.d/5114.feature @@ -0,0 +1 @@ +Added support for creating, displaying and interacting with polls in the timeline. \ No newline at end of file From f5410b2060d657fe25ef0165fd492039a207b1b5 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Wed, 8 Dec 2021 13:17:49 +0200 Subject: [PATCH 09/46] vector-im/element-ios/issues/5190 - Fix crash when trying to scroll the people's tab to the top. --- Riot/Modules/Common/Recents/RecentsViewController.m | 5 +++++ Riot/Modules/People/PeopleViewController.m | 2 +- changelog.d/5190.bugfix | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 changelog.d/5190.bugfix diff --git a/Riot/Modules/Common/Recents/RecentsViewController.m b/Riot/Modules/Common/Recents/RecentsViewController.m index 6e8adb76e..b8f0d0a80 100644 --- a/Riot/Modules/Common/Recents/RecentsViewController.m +++ b/Riot/Modules/Common/Recents/RecentsViewController.m @@ -2050,6 +2050,11 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro - (void)scrollToTheTopTheNextRoomWithMissedNotificationsInSection:(NSInteger)section { + if (section < 0) + { + return; + } + UITableViewCell *firstVisibleCell; NSIndexPath *firstVisibleCellIndexPath; diff --git a/Riot/Modules/People/PeopleViewController.m b/Riot/Modules/People/PeopleViewController.m index 93157578f..1cb481a90 100644 --- a/Riot/Modules/People/PeopleViewController.m +++ b/Riot/Modules/People/PeopleViewController.m @@ -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]; } } diff --git a/changelog.d/5190.bugfix b/changelog.d/5190.bugfix new file mode 100644 index 000000000..d9dc6460d --- /dev/null +++ b/changelog.d/5190.bugfix @@ -0,0 +1 @@ +Fix crash when trying to scroll the people's tab to the top. \ No newline at end of file From 750ad2ff2a8d8774906cf51b0531dc044d517d14 Mon Sep 17 00:00:00 2001 From: Ekaterina Gerasimova Date: Thu, 9 Dec 2021 10:38:13 +0000 Subject: [PATCH 10/46] Remove issue workflow for the old Delight board --- .github/workflows/triage-move-labelled.yml | 15 --------------- changelog.d/5285.misc | 1 + 2 files changed, 1 insertion(+), 15 deletions(-) create mode 100644 changelog.d/5285.misc diff --git a/.github/workflows/triage-move-labelled.yml b/.github/workflows/triage-move-labelled.yml index e82c227de..bb55d2b71 100644 --- a/.github/workflows/triage-move-labelled.yml +++ b/.github/workflows/triage-move-labelled.yml @@ -47,21 +47,6 @@ 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 runs-on: ubuntu-latest diff --git a/changelog.d/5285.misc b/changelog.d/5285.misc new file mode 100644 index 000000000..b0515608c --- /dev/null +++ b/changelog.d/5285.misc @@ -0,0 +1 @@ +Update issue workflow automation for the Delight team From ee9043547ba7e98673f6df4050f679e0878f56a3 Mon Sep 17 00:00:00 2001 From: Ekaterina Gerasimova Date: Thu, 9 Dec 2021 10:39:56 +0000 Subject: [PATCH 11/46] Update naming of Delight board in issue workflow Only using the new Delight board now and naming is better for the Action page in GitHub when it's short and precise. --- .github/workflows/triage-move-labelled.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/triage-move-labelled.yml b/.github/workflows/triage-move-labelled.yml index bb55d2b71..567160661 100644 --- a/.github/workflows/triage-move-labelled.yml +++ b/.github/workflows/triage-move-labelled.yml @@ -47,8 +47,8 @@ jobs: PROJECT_ID: "PN_kwDOAM0swc0sUA" GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} - 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') || From 321e759a393d7be676869b09ebf6a7579e846748 Mon Sep 17 00:00:00 2001 From: Kat Gerasimova Date: Thu, 9 Dec 2021 14:43:59 +0000 Subject: [PATCH 12/46] Add automation to move Message Bubble issues to the project board (#5290) * Add automation to move Message bubbles to new board * Add changelog entry --- .github/workflows/triage-move-labelled.yml | 23 ++++++++++++++++++++++ changelog.d/5289.misc | 1 + 2 files changed, 24 insertions(+) create mode 100644 changelog.d/5289.misc diff --git a/.github/workflows/triage-move-labelled.yml b/.github/workflows/triage-move-labelled.yml index 567160661..44704833b 100644 --- a/.github/workflows/triage-move-labelled.yml +++ b/.github/workflows/triage-move-labelled.yml @@ -117,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 }} diff --git a/changelog.d/5289.misc b/changelog.d/5289.misc new file mode 100644 index 000000000..ffdd1fa4c --- /dev/null +++ b/changelog.d/5289.misc @@ -0,0 +1 @@ +Update workflow to add automation for the new Message Bubbles board From 1924c9c75eab7423368b647c6959e0b34bc744e2 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 10 Dec 2021 09:25:06 +0100 Subject: [PATCH 13/46] Add CopyableLabel enables to copy past text content. --- Riot/Modules/Common/Views/CopyableLabel.swift | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 Riot/Modules/Common/Views/CopyableLabel.swift diff --git a/Riot/Modules/Common/Views/CopyableLabel.swift b/Riot/Modules/Common/Views/CopyableLabel.swift new file mode 100644 index 000000000..ba19b2a03 --- /dev/null +++ b/Riot/Modules/Common/Views/CopyableLabel.swift @@ -0,0 +1,75 @@ +// +// 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 = text + + let menu = UIMenuController.shared + + if #available(iOS 13.0, *) { + menu.showMenu(from: self, rect: self.bounds) + } + } + + override var canBecomeFirstResponder: Bool { + return true + } + + override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { + return action == #selector(UIResponderStandardEditActions.copy) + } +} From 64547c04be74fac8475bcaab97505cad1522fde3 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 10 Dec 2021 09:29:09 +0100 Subject: [PATCH 14/46] RoomMemberDetailsViewController: Show user Matrix id and make it copyable. --- .../Detail/RoomMemberDetailsViewController.m | 36 ++--- .../RoomMemberDetailsViewController.xib | 134 ++++++++++-------- 2 files changed, 87 insertions(+), 83 deletions(-) diff --git a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m index ca9339f55..6a6b04f64 100644 --- a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m +++ b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m @@ -91,6 +91,8 @@ @property (weak, nonatomic) IBOutlet UILabel *roomMemberNameLabel; @property (weak, nonatomic) IBOutlet UIView *roomMemberNameLabelMask; +@property (weak, nonatomic) IBOutlet UILabel *roomMemberUserIdLabel; + @property (weak, nonatomic) IBOutlet UILabel *roomMemberStatusLabel; @property (weak, nonatomic) IBOutlet UIImageView *bottomImageView; @@ -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]; @@ -348,8 +343,12 @@ - (void)updateMemberInfo { if (self.mxRoomMember) - { - self.roomMemberNameLabel.text = self.mxRoomMember.displayname ? self.mxRoomMember.displayname : self.mxRoomMember.userId; + { + self.roomMemberNameLabelMask.hidden = !self.mxRoomMember.displayname; + + self.roomMemberNameLabel.text = self.mxRoomMember.displayname; + + self.roomMemberUserIdLabel.text = self.mxRoomMember.userId; // Update member power level MXWeakify(self); @@ -1257,20 +1256,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); diff --git a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.xib b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.xib index 999ddf2fc..6991e4da3 100644 --- a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.xib +++ b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.xib @@ -1,11 +1,9 @@ - - - - + + - + @@ -18,11 +16,12 @@ - - + + - + + @@ -33,7 +32,7 @@ - + @@ -68,46 +67,79 @@ + - - - - - - - - - - - - + + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - + - - - - - - - - + @@ -171,7 +189,7 @@ - + From 08886b883c7c5b3b5af312c93d744ef108df03b7 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 10 Dec 2021 09:50:06 +0100 Subject: [PATCH 15/46] RoomMemberDetailsViewController: Refactor a property name. --- .../Room/Members/Detail/RoomMemberDetailsViewController.m | 4 ++-- .../Room/Members/Detail/RoomMemberDetailsViewController.xib | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m index 6a6b04f64..ee9e4f261 100644 --- a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m +++ b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m @@ -89,7 +89,7 @@ @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; @@ -344,7 +344,7 @@ { if (self.mxRoomMember) { - self.roomMemberNameLabelMask.hidden = !self.mxRoomMember.displayname; + self.roomMemberNameContainerView.hidden = !self.mxRoomMember.displayname; self.roomMemberNameLabel.text = self.mxRoomMember.displayname; diff --git a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.xib b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.xib index 6991e4da3..d8d9e01b1 100644 --- a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.xib +++ b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.xib @@ -16,8 +16,8 @@ + - From 20110d7dcf8cd715fd641726819760117adccb23 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 10 Dec 2021 10:01:20 +0100 Subject: [PATCH 16/46] Add changes --- changelog.d/4568.change | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/4568.change diff --git a/changelog.d/4568.change b/changelog.d/4568.change new file mode 100644 index 000000000..1da5dc958 --- /dev/null +++ b/changelog.d/4568.change @@ -0,0 +1 @@ +Room member details: Display user Matrix ID and make it copyable. From 389064126b0ef486fe3da60457026f22ecfe6c9e Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 10 Dec 2021 14:02:09 +0100 Subject: [PATCH 17/46] RoomMemberDetails: Add safe area layout guides. --- .../RoomMemberDetailsViewController.xib | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.xib b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.xib index d8d9e01b1..2db05b047 100644 --- a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.xib +++ b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.xib @@ -1,9 +1,10 @@ - + + @@ -32,7 +33,7 @@ - + @@ -43,19 +44,19 @@ - + - + - + - + @@ -67,14 +68,14 @@ - + + - - + - + @@ -166,11 +167,9 @@ - - @@ -189,7 +188,7 @@ - + @@ -200,6 +199,7 @@ + @@ -210,11 +210,13 @@ + + From 1ae9b708a187dffd6be047b86bc84227676e49a8 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Thu, 9 Dec 2021 17:44:47 +0200 Subject: [PATCH 18/46] vector-im/element-ios/issues/5114 - Prevent poll events from ever getting merged into other bubble data. --- .../Room/MXKRoomBubbleCellDataWithAppendingMode.m | 10 ---------- Riot/Modules/Room/CellData/RoomBubbleCellData.m | 5 +++++ 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleCellDataWithAppendingMode.m b/Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleCellDataWithAppendingMode.m index 6bec70d0c..680086ddd 100644 --- a/Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleCellDataWithAppendingMode.m +++ b/Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleCellDataWithAppendingMode.m @@ -36,11 +36,6 @@ static NSAttributedString *messageSeparator = nil; - (BOOL)addEvent:(MXEvent*)event andRoomState:(MXRoomState*)roomState { - // Never merge polls - if (self.events.firstObject.eventType == MXEventTypePollStart) { - return NO; - } - // We group together text messages from the same user (attachments are not merged). if ([event.sender isEqualToString:self.senderId] && (self.attachment == nil) && (self.bubbleComponents.count < self.maxComponentCount)) { @@ -90,11 +85,6 @@ static NSAttributedString *messageSeparator = nil; - (BOOL)mergeWithBubbleCellData:(id)bubbleCellData { - // Never merge polls - if (self.events.firstObject.eventType == MXEventTypePollStart) { - return NO; - } - if ([self hasSameSenderAsBubbleCellData:bubbleCellData]) { MXKRoomBubbleCellData *cellData = (MXKRoomBubbleCellData*)bubbleCellData; diff --git a/Riot/Modules/Room/CellData/RoomBubbleCellData.m b/Riot/Modules/Room/CellData/RoomBubbleCellData.m index 8c73b01e9..4c9e9a4e2 100644 --- a/Riot/Modules/Room/CellData/RoomBubbleCellData.m +++ b/Riot/Modules/Room/CellData/RoomBubbleCellData.m @@ -842,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) From a43c1664a9b2b046b66e86072ebbfbb56e06dcfc Mon Sep 17 00:00:00 2001 From: jelv Date: Tue, 7 Dec 2021 10:04:41 +0000 Subject: [PATCH 19/46] Translated using Weblate (Dutch) Currently translated at 100.0% (1341 of 1341 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/nl/ --- Riot/Assets/nl.lproj/Vector.strings | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/Riot/Assets/nl.lproj/Vector.strings b/Riot/Assets/nl.lproj/Vector.strings index 3e1e99512..7f4072640 100644 --- a/Riot/Assets/nl.lproj/Vector.strings +++ b/Riot/Assets/nl.lproj/Vector.strings @@ -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"; From 3e77d33a889239e9b98ea173394131363e7f27b8 Mon Sep 17 00:00:00 2001 From: sr093906 Date: Tue, 7 Dec 2021 08:17:41 +0000 Subject: [PATCH 20/46] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1341 of 1341 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/zh_Hans/ --- Riot/Assets/zh_Hans.lproj/Vector.strings | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Riot/Assets/zh_Hans.lproj/Vector.strings b/Riot/Assets/zh_Hans.lproj/Vector.strings index 07d29a933..f54deca1f 100644 --- a/Riot/Assets/zh_Hans.lproj/Vector.strings +++ b/Riot/Assets/zh_Hans.lproj/Vector.strings @@ -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" = "关于"; From f5d2e0807da8195350166a4f92d33a30c71a1dbe Mon Sep 17 00:00:00 2001 From: Szimszon Date: Tue, 7 Dec 2021 19:57:13 +0000 Subject: [PATCH 21/46] Translated using Weblate (Hungarian) Currently translated at 100.0% (1341 of 1341 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/hu/ --- Riot/Assets/hu.lproj/Vector.strings | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Riot/Assets/hu.lproj/Vector.strings b/Riot/Assets/hu.lproj/Vector.strings index 605aa6f70..0d8050079 100644 --- a/Riot/Assets/hu.lproj/Vector.strings +++ b/Riot/Assets/hu.lproj/Vector.strings @@ -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"; From cac0cc3c6911b1e1ba5cf98d8680b68bcce9207d Mon Sep 17 00:00:00 2001 From: random Date: Tue, 7 Dec 2021 09:38:24 +0000 Subject: [PATCH 22/46] Translated using Weblate (Italian) Currently translated at 100.0% (1341 of 1341 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ --- Riot/Assets/it.lproj/Vector.strings | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Riot/Assets/it.lproj/Vector.strings b/Riot/Assets/it.lproj/Vector.strings index e94c77666..ce4d6c96d 100644 --- a/Riot/Assets/it.lproj/Vector.strings +++ b/Riot/Assets/it.lproj/Vector.strings @@ -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"; From 792200d12845bc927beccd8adbbdb496e851a408 Mon Sep 17 00:00:00 2001 From: lvre <7uu3qrbvm@relay.firefox.com> Date: Tue, 7 Dec 2021 21:57:45 +0000 Subject: [PATCH 23/46] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (1341 of 1341 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/pt_BR/ --- Riot/Assets/pt_BR.lproj/Vector.strings | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Riot/Assets/pt_BR.lproj/Vector.strings b/Riot/Assets/pt_BR.lproj/Vector.strings index 0c64d526a..ba473313e 100644 --- a/Riot/Assets/pt_BR.lproj/Vector.strings +++ b/Riot/Assets/pt_BR.lproj/Vector.strings @@ -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"; From f9e5466171358ac7c5e192921ca0ab9a878b420d Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Thu, 9 Dec 2021 14:50:34 +0000 Subject: [PATCH 24/46] Translated using Weblate (Ukrainian) Currently translated at 60.4% (811 of 1341 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/uk/ --- Riot/Assets/uk.lproj/Vector.strings | 38 ++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/Riot/Assets/uk.lproj/Vector.strings b/Riot/Assets/uk.lproj/Vector.strings index 8b88b65b4..6d07f84ca 100644 --- a/Riot/Assets/uk.lproj/Vector.strings +++ b/Riot/Assets/uk.lproj/Vector.strings @@ -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,29 @@ "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" = "Скинути до звичайного користувача"; From cec7da347ce760892b243b7b34a6b5f67a6daec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Tue, 7 Dec 2021 22:11:23 +0000 Subject: [PATCH 25/46] Translated using Weblate (Estonian) Currently translated at 100.0% (1341 of 1341 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/et/ --- Riot/Assets/et.lproj/Vector.strings | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Riot/Assets/et.lproj/Vector.strings b/Riot/Assets/et.lproj/Vector.strings index 0c73787d9..9e209a1f2 100644 --- a/Riot/Assets/et.lproj/Vector.strings +++ b/Riot/Assets/et.lproj/Vector.strings @@ -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"; From 5a45504d3a6d0625bb48903671f4e4ad76c5d6a1 Mon Sep 17 00:00:00 2001 From: Linerly Date: Thu, 9 Dec 2021 02:40:36 +0000 Subject: [PATCH 26/46] Translated using Weblate (Indonesian) Currently translated at 100.0% (1341 of 1341 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/id/ --- Riot/Assets/id.lproj/Vector.strings | 51 +++++++++++++++++++---------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/Riot/Assets/id.lproj/Vector.strings b/Riot/Assets/id.lproj/Vector.strings index 905e4a2fc..62d27b60d 100644 --- a/Riot/Assets/id.lproj/Vector.strings +++ b/Riot/Assets/id.lproj/Vector.strings @@ -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"; From fec85f0f69e5cb82a8c06a9a899bcc5fb9ad266f Mon Sep 17 00:00:00 2001 From: Jozef Gaal Date: Fri, 10 Dec 2021 01:01:45 +0000 Subject: [PATCH 27/46] Translated using Weblate (Slovak) Currently translated at 2.9% (39 of 1341 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/sk/ --- Riot/Assets/sk.lproj/Vector.strings | 45 +++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/Riot/Assets/sk.lproj/Vector.strings b/Riot/Assets/sk.lproj/Vector.strings index 8b1378917..02ad62efc 100644 --- a/Riot/Assets/sk.lproj/Vector.strings +++ b/Riot/Assets/sk.lproj/Vector.strings @@ -1 +1,46 @@ + +"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"; From 94e71174f29af605ed09d765d4f79386d7af9af4 Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Sat, 11 Dec 2021 05:42:10 +0000 Subject: [PATCH 28/46] Translated using Weblate (Ukrainian) Currently translated at 62.6% (840 of 1341 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/uk/ --- Riot/Assets/uk.lproj/Vector.strings | 33 +++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/Riot/Assets/uk.lproj/Vector.strings b/Riot/Assets/uk.lproj/Vector.strings index 6d07f84ca..abf77edfd 100644 --- a/Riot/Assets/uk.lproj/Vector.strings +++ b/Riot/Assets/uk.lproj/Vector.strings @@ -989,3 +989,36 @@ "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" = "Хто може переглядати історію?"; From d224a3380670d0e5174b41837764bf09c983bc66 Mon Sep 17 00:00:00 2001 From: manuroe Date: Tue, 14 Dec 2021 10:18:19 +0100 Subject: [PATCH 29/46] Prepare for new sprint --- Config/AppVersion.xcconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Config/AppVersion.xcconfig b/Config/AppVersion.xcconfig index ef94fbbf6..febe520e8 100644 --- a/Config/AppVersion.xcconfig +++ b/Config/AppVersion.xcconfig @@ -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 From 510d977377caa474d76056f9d750bad1e9e08229 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 14 Dec 2021 12:40:12 +0100 Subject: [PATCH 30/46] Update CopyableLabel. --- Riot/Modules/Common/Views/CopyableLabel.swift | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Riot/Modules/Common/Views/CopyableLabel.swift b/Riot/Modules/Common/Views/CopyableLabel.swift index ba19b2a03..56a735695 100644 --- a/Riot/Modules/Common/Views/CopyableLabel.swift +++ b/Riot/Modules/Common/Views/CopyableLabel.swift @@ -56,13 +56,9 @@ class CopyableLabel: UILabel { override func copy(_ sender: Any?) { let board = UIPasteboard.general - board.string = text - - let menu = UIMenuController.shared - - if #available(iOS 13.0, *) { - menu.showMenu(from: self, rect: self.bounds) - } + board.string = self.text + + // Note that the UIMenuController will be dismissed by itself after copying the text } override var canBecomeFirstResponder: Bool { From 30247716303dfc400d4a2daf241db1ed60efcbe1 Mon Sep 17 00:00:00 2001 From: Jozef Gaal Date: Wed, 8 Dec 2021 02:09:53 +0000 Subject: [PATCH 31/46] Added translation using Weblate (Slovak) --- .../Assets/MatrixKitAssets.bundle/sk.lproj/MatrixKit.strings | 1 + 1 file changed, 1 insertion(+) create mode 100644 Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/sk.lproj/MatrixKit.strings diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/sk.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/sk.lproj/MatrixKit.strings new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/sk.lproj/MatrixKit.strings @@ -0,0 +1 @@ + From 452b620c24fe55d7a55f8e2b550617b032d28a35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Tue, 7 Dec 2021 07:52:40 +0000 Subject: [PATCH 32/46] Translated using Weblate (Estonian) Currently translated at 100.0% (441 of 441 strings) Translation: Element iOS/Element iOS (MatrixKit) Translate-URL: https://translate.element.io/projects/riot-ios/element-ios-matrixkit/et/ --- .../Assets/MatrixKitAssets.bundle/et.lproj/MatrixKit.strings | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/et.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/et.lproj/MatrixKit.strings index 017cbbe88..997cb7964 100644 --- a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/et.lproj/MatrixKit.strings +++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/et.lproj/MatrixKit.strings @@ -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"; From 5e6c20cfd3d33ab8fb31615debfbc119d30c1c84 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Tue, 7 Dec 2021 07:22:13 +0000 Subject: [PATCH 33/46] Translated using Weblate (Hungarian) Currently translated at 100.0% (441 of 441 strings) Translation: Element iOS/Element iOS (MatrixKit) Translate-URL: https://translate.element.io/projects/riot-ios/element-ios-matrixkit/hu/ --- .../Assets/MatrixKitAssets.bundle/hu.lproj/MatrixKit.strings | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/hu.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/hu.lproj/MatrixKit.strings index 1bf93bad0..d99934823 100644 --- a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/hu.lproj/MatrixKit.strings +++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/hu.lproj/MatrixKit.strings @@ -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"; From 434d2384ab1efaa4db56bd69c62eb8986fd9e152 Mon Sep 17 00:00:00 2001 From: Linerly Date: Tue, 7 Dec 2021 12:20:58 +0000 Subject: [PATCH 34/46] Translated using Weblate (Indonesian) Currently translated at 100.0% (441 of 441 strings) Translation: Element iOS/Element iOS (MatrixKit) Translate-URL: https://translate.element.io/projects/riot-ios/element-ios-matrixkit/id/ --- .../Assets/MatrixKitAssets.bundle/id.lproj/MatrixKit.strings | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/id.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/id.lproj/MatrixKit.strings index 3bee04d44..6dc5772bd 100644 --- a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/id.lproj/MatrixKit.strings +++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/id.lproj/MatrixKit.strings @@ -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."; From 74527b3afd5651b0cf512e98907d4f2b509bdb12 Mon Sep 17 00:00:00 2001 From: random Date: Tue, 7 Dec 2021 09:39:34 +0000 Subject: [PATCH 35/46] Translated using Weblate (Italian) Currently translated at 100.0% (441 of 441 strings) Translation: Element iOS/Element iOS (MatrixKit) Translate-URL: https://translate.element.io/projects/riot-ios/element-ios-matrixkit/it/ --- .../Assets/MatrixKitAssets.bundle/it.lproj/MatrixKit.strings | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/it.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/it.lproj/MatrixKit.strings index 630520ca6..6049a7351 100644 --- a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/it.lproj/MatrixKit.strings +++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/it.lproj/MatrixKit.strings @@ -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"; From f130ec4c90f4b7471168802cac342827fb7d4739 Mon Sep 17 00:00:00 2001 From: jelv Date: Tue, 7 Dec 2021 10:05:35 +0000 Subject: [PATCH 36/46] Translated using Weblate (Dutch) Currently translated at 100.0% (441 of 441 strings) Translation: Element iOS/Element iOS (MatrixKit) Translate-URL: https://translate.element.io/projects/riot-ios/element-ios-matrixkit/nl/ --- .../MatrixKitAssets.bundle/nl.lproj/MatrixKit.strings | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/nl.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/nl.lproj/MatrixKit.strings index 6dfa3be48..8c6e58224 100644 --- a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/nl.lproj/MatrixKit.strings +++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/nl.lproj/MatrixKit.strings @@ -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"; From daa10483544cc39b3d59f827c34bc1a91a561488 Mon Sep 17 00:00:00 2001 From: lvre <7uu3qrbvm@relay.firefox.com> Date: Tue, 7 Dec 2021 22:00:02 +0000 Subject: [PATCH 37/46] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (441 of 441 strings) Translation: Element iOS/Element iOS (MatrixKit) Translate-URL: https://translate.element.io/projects/riot-ios/element-ios-matrixkit/pt_BR/ --- .../Assets/MatrixKitAssets.bundle/pt_BR.lproj/MatrixKit.strings | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/pt_BR.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/pt_BR.lproj/MatrixKit.strings index 19b5d5cc3..f3dba6590 100644 --- a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/pt_BR.lproj/MatrixKit.strings +++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/pt_BR.lproj/MatrixKit.strings @@ -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"; From 67401b12c033f81e9c894493292c7269b2cf02c9 Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Tue, 7 Dec 2021 21:47:21 +0000 Subject: [PATCH 38/46] Translated using Weblate (Ukrainian) Currently translated at 95.0% (419 of 441 strings) Translation: Element iOS/Element iOS (MatrixKit) Translate-URL: https://translate.element.io/projects/riot-ios/element-ios-matrixkit/uk/ --- .../uk.lproj/MatrixKit.strings | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/uk.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/uk.lproj/MatrixKit.strings index 28bb81299..f9ca88a63 100644 --- a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/uk.lproj/MatrixKit.strings +++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/uk.lproj/MatrixKit.strings @@ -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У майбутньому цей процес перевірки буде ускладнено."; From d251bce35d292e061a3c9ade94dca4607ddbe4d6 Mon Sep 17 00:00:00 2001 From: sr093906 Date: Tue, 7 Dec 2021 08:13:54 +0000 Subject: [PATCH 39/46] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (441 of 441 strings) Translation: Element iOS/Element iOS (MatrixKit) Translate-URL: https://translate.element.io/projects/riot-ios/element-ios-matrixkit/zh_Hans/ --- .../MatrixKitAssets.bundle/zh_Hans.lproj/MatrixKit.strings | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/zh_Hans.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/zh_Hans.lproj/MatrixKit.strings index b1a270dd2..87fdb0a0c 100644 --- a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/zh_Hans.lproj/MatrixKit.strings +++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/zh_Hans.lproj/MatrixKit.strings @@ -478,3 +478,5 @@ "room_displayname_all_other_members_left" = "%@ (离开)"; "auth_username_in_use" = "用户名被占用"; "rename" = "重命名"; +"attachment_unsupported_preview_message" = "文件类型不受支持。"; +"attachment_unsupported_preview_title" = "无法预览"; From 7ab474fe6a574546eb0f7426f85c9f69b1f03a38 Mon Sep 17 00:00:00 2001 From: Kaede Date: Tue, 14 Dec 2021 00:06:44 +0000 Subject: [PATCH 40/46] Translated using Weblate (Japanese) Currently translated at 66.2% (889 of 1341 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/ja/ --- Riot/Assets/ja.lproj/Vector.strings | 38 ++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/Riot/Assets/ja.lproj/Vector.strings b/Riot/Assets/ja.lproj/Vector.strings index bee78f6d0..6ee67bd59 100644 --- a/Riot/Assets/ja.lproj/Vector.strings +++ b/Riot/Assets/ja.lproj/Vector.strings @@ -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" = "おすすめ"; From 97e0165ba648b6b2a7ca9acd903f4f33e3791708 Mon Sep 17 00:00:00 2001 From: Moon Sungjoon Date: Mon, 13 Dec 2021 19:49:32 +0000 Subject: [PATCH 41/46] Translated using Weblate (Korean) Currently translated at 69.8% (937 of 1341 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/ko/ --- Riot/Assets/ko.lproj/Vector.strings | 43 +++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/Riot/Assets/ko.lproj/Vector.strings b/Riot/Assets/ko.lproj/Vector.strings index 7e4ef9aac..05ed29b11 100644 --- a/Riot/Assets/ko.lproj/Vector.strings +++ b/Riot/Assets/ko.lproj/Vector.strings @@ -931,3 +931,46 @@ "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" = "구성원 필터"; From c7f893b8f59d738762de629d40897f0b5803b5e0 Mon Sep 17 00:00:00 2001 From: DUCKCHI Date: Mon, 13 Dec 2021 10:40:57 +0000 Subject: [PATCH 42/46] Translated using Weblate (Korean) Currently translated at 69.8% (937 of 1341 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/ko/ --- Riot/Assets/ko.lproj/Vector.strings | 106 ++++++++++++++++++++++++++-- 1 file changed, 100 insertions(+), 6 deletions(-) diff --git a/Riot/Assets/ko.lproj/Vector.strings b/Riot/Assets/ko.lproj/Vector.strings index 05ed29b11..0baff0850 100644 --- a/Riot/Assets/ko.lproj/Vector.strings +++ b/Riot/Assets/ko.lproj/Vector.strings @@ -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" = "이 방을 찾을 수 없습니다. 방이 존재하는지 확인하세요"; @@ -974,3 +974,97 @@ "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" = "방"; From bb5c094f6b2b0a039a0a964f4633e02592a698b0 Mon Sep 17 00:00:00 2001 From: Jozef Gaal Date: Mon, 13 Dec 2021 17:31:48 +0000 Subject: [PATCH 43/46] Translated using Weblate (Slovak) Currently translated at 10.9% (147 of 1341 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/sk/ --- Riot/Assets/sk.lproj/Vector.strings | 121 ++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/Riot/Assets/sk.lproj/Vector.strings b/Riot/Assets/sk.lproj/Vector.strings index 02ad62efc..f485f1f64 100644 --- a/Riot/Assets/sk.lproj/Vector.strings +++ b/Riot/Assets/sk.lproj/Vector.strings @@ -44,3 +44,124 @@ // 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"; From 57e305f9e815dd6eba4dff476ecb4768b8acdf4e Mon Sep 17 00:00:00 2001 From: Doug Date: Tue, 14 Dec 2021 13:01:14 +0000 Subject: [PATCH 44/46] changelog.d: Upgrade MatrixSDK version ([v0.20.15](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.20.15)). --- Podfile | 2 +- changelog.d/x-nolink-0.change | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelog.d/x-nolink-0.change diff --git a/Podfile b/Podfile index f51a72727..92a7bdad0 100644 --- a/Podfile +++ b/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' } } diff --git a/changelog.d/x-nolink-0.change b/changelog.d/x-nolink-0.change new file mode 100644 index 000000000..50360ca73 --- /dev/null +++ b/changelog.d/x-nolink-0.change @@ -0,0 +1 @@ +Upgrade MatrixSDK version ([v0.20.15](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.20.15)). \ No newline at end of file From 37120644e4cbe564bee676bc145f90acdd9adaf2 Mon Sep 17 00:00:00 2001 From: Doug Date: Tue, 14 Dec 2021 13:01:15 +0000 Subject: [PATCH 45/46] version++ --- CHANGES.md | 25 +++++++++++++++++++++++++ changelog.d/4568.change | 1 - changelog.d/5114.feature | 1 - changelog.d/5190.bugfix | 1 - changelog.d/5280.build | 1 - changelog.d/5285.misc | 1 - changelog.d/5289.misc | 1 - changelog.d/x-nolink-0.change | 1 - 8 files changed, 25 insertions(+), 7 deletions(-) delete mode 100644 changelog.d/4568.change delete mode 100644 changelog.d/5114.feature delete mode 100644 changelog.d/5190.bugfix delete mode 100644 changelog.d/5280.build delete mode 100644 changelog.d/5285.misc delete mode 100644 changelog.d/5289.misc delete mode 100644 changelog.d/x-nolink-0.change diff --git a/CHANGES.md b/CHANGES.md index d2b81bdf1..e530b5806 100644 --- a/CHANGES.md +++ b/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 diff --git a/changelog.d/4568.change b/changelog.d/4568.change deleted file mode 100644 index 1da5dc958..000000000 --- a/changelog.d/4568.change +++ /dev/null @@ -1 +0,0 @@ -Room member details: Display user Matrix ID and make it copyable. diff --git a/changelog.d/5114.feature b/changelog.d/5114.feature deleted file mode 100644 index 489c8dbd8..000000000 --- a/changelog.d/5114.feature +++ /dev/null @@ -1 +0,0 @@ -Added support for creating, displaying and interacting with polls in the timeline. \ No newline at end of file diff --git a/changelog.d/5190.bugfix b/changelog.d/5190.bugfix deleted file mode 100644 index d9dc6460d..000000000 --- a/changelog.d/5190.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix crash when trying to scroll the people's tab to the top. \ No newline at end of file diff --git a/changelog.d/5280.build b/changelog.d/5280.build deleted file mode 100644 index e5a166d66..000000000 --- a/changelog.d/5280.build +++ /dev/null @@ -1 +0,0 @@ -Fix SwiftGen only generating strings for MatrixKit. \ No newline at end of file diff --git a/changelog.d/5285.misc b/changelog.d/5285.misc deleted file mode 100644 index b0515608c..000000000 --- a/changelog.d/5285.misc +++ /dev/null @@ -1 +0,0 @@ -Update issue workflow automation for the Delight team diff --git a/changelog.d/5289.misc b/changelog.d/5289.misc deleted file mode 100644 index ffdd1fa4c..000000000 --- a/changelog.d/5289.misc +++ /dev/null @@ -1 +0,0 @@ -Update workflow to add automation for the new Message Bubbles board diff --git a/changelog.d/x-nolink-0.change b/changelog.d/x-nolink-0.change deleted file mode 100644 index 50360ca73..000000000 --- a/changelog.d/x-nolink-0.change +++ /dev/null @@ -1 +0,0 @@ -Upgrade MatrixSDK version ([v0.20.15](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.20.15)). \ No newline at end of file From bc89fe93983987c988a2bd8f45b9f82bcb96ac87 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 14 Dec 2021 15:47:09 +0100 Subject: [PATCH 46/46] RoomMemberDetailsViewController: Fix user id theming. --- .../Room/Members/Detail/RoomMemberDetailsViewController.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m index ee9e4f261..d1888c6d7 100644 --- a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m +++ b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m @@ -206,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;