mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-16 06:28:27 +02:00
Merge commit '56d9e1f6a55a93dc71149ae429eaa615a98de0d5' into feature/6076_foss_merge
* commit '56d9e1f6a55a93dc71149ae429eaa615a98de0d5': (79 commits) finish version++ version++ Translated using Weblate (Hungarian) Translated using Weblate (Italian) Translated using Weblate (Ukrainian) Translated using Weblate (Hungarian) Translated using Weblate (Slovak) Translated using Weblate (Swedish) Translated using Weblate (Indonesian) Translated using Weblate (Albanian) Translated using Weblate (Estonian) Translated using Weblate (Estonian) updated the submodule updated SDK Update the SDK. (#7819) Prepare for new sprint finish version++ version++ fix Changelog. ... # Conflicts: # Config/AppVersion.xcconfig # Podfile # Podfile.lock # Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved # Riot/Managers/Settings/RiotSettings.swift # Riot/Modules/Analytics/Analytics.swift # Riot/Modules/Analytics/DecryptionFailure.swift # Riot/Modules/Analytics/PHGPostHogConfiguration.swift # Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewAction.swift # Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewModel.swift # Riot/Modules/Room/Views/Title/Preview/PreviewRoomTitleView.m # Riot/Modules/Settings/SettingsViewController.m # Riot/Utils/EventFormatter.m # Riot/Utils/Tools.m # RiotNSE/target.yml # fastlane/Fastfile # project.yml
This commit is contained in:
6
.github/workflows/ci-build.yml
vendored
6
.github/workflows/ci-build.yml
vendored
@@ -20,7 +20,9 @@ jobs:
|
||||
# Concurrency group not needed as this workflow only runs on develop which we always want to test.
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
|
||||
# Common cache
|
||||
# Note: GH actions do not support yaml anchor yet. We need to duplicate this for every job
|
||||
@@ -49,8 +51,6 @@ jobs:
|
||||
run: |
|
||||
bundle config path vendor/bundle
|
||||
bundle install --jobs 4 --retry 3
|
||||
- name: Use right MatrixSDK versions
|
||||
run: bundle exec fastlane point_dependencies_to_related_branches
|
||||
|
||||
# Main step
|
||||
- name: Build iOS simulator
|
||||
|
||||
6
.github/workflows/ci-tests.yml
vendored
6
.github/workflows/ci-tests.yml
vendored
@@ -25,7 +25,9 @@ jobs:
|
||||
cancel-in-progress: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
|
||||
# Common cache
|
||||
# Note: GH actions do not support yaml anchor yet. We need to duplicate this for every job
|
||||
@@ -54,8 +56,6 @@ jobs:
|
||||
run: |
|
||||
bundle config path vendor/bundle
|
||||
bundle install --jobs 4 --retry 3
|
||||
- name: Use right MatrixSDK versions
|
||||
run: bundle exec fastlane point_dependencies_to_related_branches
|
||||
|
||||
# Main step
|
||||
- name: Unit tests
|
||||
|
||||
6
.github/workflows/ci-ui-tests.yml
vendored
6
.github/workflows/ci-ui-tests.yml
vendored
@@ -20,7 +20,9 @@ jobs:
|
||||
cancel-in-progress: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
|
||||
# Common cache
|
||||
# Note: GH actions do not support yaml anchor yet. We need to duplicate this for every job
|
||||
@@ -49,8 +51,6 @@ jobs:
|
||||
run: |
|
||||
bundle config path vendor/bundle
|
||||
bundle install --jobs 4 --retry 3
|
||||
- name: Use right MatrixSDK versions
|
||||
run: bundle exec fastlane point_dependencies_to_related_branches
|
||||
|
||||
# Main step
|
||||
- name: UI tests
|
||||
|
||||
6
.github/workflows/release-alpha.yml
vendored
6
.github/workflows/release-alpha.yml
vendored
@@ -25,7 +25,9 @@ jobs:
|
||||
cancel-in-progress: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
|
||||
# Common cache
|
||||
# Note: GH actions do not support yaml anchor yet. We need to duplicate this for every job
|
||||
@@ -57,8 +59,6 @@ jobs:
|
||||
run: |
|
||||
bundle config path vendor/bundle
|
||||
bundle install --jobs 4 --retry 3
|
||||
- name: Use right MatrixSDK versions
|
||||
run: bundle exec fastlane point_dependencies_to_related_branches
|
||||
|
||||
# Import alpha release private signing certificate
|
||||
- name: Import signing certificate
|
||||
|
||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "matrix-ios-sdk"]
|
||||
path = matrix-ios-sdk
|
||||
url = git@github.com:matrix-org/matrix-ios-sdk.git
|
||||
47
CHANGES.md
47
CHANGES.md
@@ -1,3 +1,50 @@
|
||||
## Changes in 1.11.16 (2024-07-23)
|
||||
|
||||
No significant changes.
|
||||
|
||||
|
||||
## Changes in 1.11.15 (2024-06-18)
|
||||
|
||||
No significant changes.
|
||||
|
||||
|
||||
## Changes in 1.11.14 (2024-06-17)
|
||||
|
||||
🙌 Improvements
|
||||
|
||||
- Room retention event implementation ([#7809](https://github.com/element-hq/element-ios/pull/7809))
|
||||
|
||||
|
||||
## Changes in 1.11.13 (2024-06-12)
|
||||
|
||||
Others
|
||||
|
||||
- Analytics | Add support for super properties and appPlatform ([#7801](https://github.com/element-hq/element-ios/issues/7801))
|
||||
|
||||
|
||||
## Changes in 1.11.12 (2024-05-30)
|
||||
|
||||
🐛 Bugfixes
|
||||
|
||||
- Fix a crash when the user taps play multiple times and the video download fails. ([#7791](https://github.com/element-hq/element-ios/issues/7791))
|
||||
|
||||
|
||||
## Changes in 1.11.11 (2024-05-29)
|
||||
|
||||
No significant changes.
|
||||
|
||||
|
||||
## Changes in 1.11.10 (2024-05-01)
|
||||
|
||||
🙌 Improvements
|
||||
|
||||
- Upgrade MatrixSDK version ([v0.27.7](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.27.7)).
|
||||
|
||||
Others
|
||||
|
||||
- Improvements to reporting of decryption failures.
|
||||
|
||||
|
||||
## Changes in 1.11.9 (2024-04-02)
|
||||
|
||||
Others
|
||||
|
||||
@@ -297,7 +297,6 @@ final class BuildSettings: NSObject {
|
||||
static let settingsScreenShowChangePassword:Bool = true
|
||||
static let settingsScreenShowEnableStunServerFallback: Bool = true
|
||||
static let settingsScreenShowNotificationDecodedContentOption: Bool = true
|
||||
static let settingsScreenShowNsfwRoomsOption: Bool = true
|
||||
static let settingsSecurityScreenShowSessions:Bool = true
|
||||
static let settingsSecurityScreenShowSetupBackup:Bool = true
|
||||
static let settingsSecurityScreenShowRestoreBackup:Bool = true
|
||||
|
||||
58
Gemfile.lock
58
Gemfile.lock
@@ -5,7 +5,7 @@ GEM
|
||||
base64
|
||||
nkf
|
||||
rexml
|
||||
activesupport (7.1.3.2)
|
||||
activesupport (7.1.3.4)
|
||||
base64
|
||||
bigdecimal
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
@@ -23,24 +23,24 @@ GEM
|
||||
artifactory (3.0.17)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.3.0)
|
||||
aws-partitions (1.899.0)
|
||||
aws-sdk-core (3.191.4)
|
||||
aws-partitions (1.941.0)
|
||||
aws-sdk-core (3.197.0)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.651.0)
|
||||
aws-sigv4 (~> 1.8)
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
aws-sdk-kms (1.78.0)
|
||||
aws-sdk-core (~> 3, >= 3.191.0)
|
||||
aws-sdk-kms (1.83.0)
|
||||
aws-sdk-core (~> 3, >= 3.197.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.146.0)
|
||||
aws-sdk-core (~> 3, >= 3.191.0)
|
||||
aws-sdk-s3 (1.152.0)
|
||||
aws-sdk-core (~> 3, >= 3.197.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.8)
|
||||
aws-sigv4 (1.8.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.4)
|
||||
base64 (0.2.0)
|
||||
bigdecimal (3.1.7)
|
||||
bigdecimal (3.1.8)
|
||||
claide (1.1.0)
|
||||
clamp (1.3.2)
|
||||
cocoapods (1.14.3)
|
||||
@@ -84,7 +84,7 @@ GEM
|
||||
colored2 (3.1.2)
|
||||
commander (4.6.0)
|
||||
highline (~> 2.0.0)
|
||||
concurrent-ruby (1.2.3)
|
||||
concurrent-ruby (1.3.3)
|
||||
connection_pool (2.4.1)
|
||||
declarative (0.0.20)
|
||||
digest-crc (0.6.5)
|
||||
@@ -125,15 +125,15 @@ GEM
|
||||
faraday-retry (1.0.3)
|
||||
faraday_middleware (1.2.0)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.3.0)
|
||||
fastlane (2.219.0)
|
||||
fastimage (2.3.1)
|
||||
fastlane (2.220.0)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
aws-sdk-s3 (~> 1.0)
|
||||
babosa (>= 1.0.3, < 2.0.0)
|
||||
bundler (>= 1.12.0, < 3.0.0)
|
||||
colored
|
||||
colored (~> 1.2)
|
||||
commander (~> 4.6)
|
||||
dotenv (>= 2.1.1, < 3.0.0)
|
||||
emoji_regex (>= 0.1, < 4.0)
|
||||
@@ -154,10 +154,10 @@ GEM
|
||||
mini_magick (>= 4.9.4, < 5.0.0)
|
||||
multipart-post (>= 2.0.0, < 3.0.0)
|
||||
naturally (~> 2.2)
|
||||
optparse (>= 0.1.1)
|
||||
optparse (>= 0.1.1, < 1.0.0)
|
||||
plist (>= 3.1.0, < 4.0.0)
|
||||
rubyzip (>= 2.0.0, < 3.0.0)
|
||||
security (= 0.1.3)
|
||||
security (= 0.1.5)
|
||||
simctl (~> 1.6.3)
|
||||
terminal-notifier (>= 2.0.0, < 3.0.0)
|
||||
terminal-table (~> 3)
|
||||
@@ -166,14 +166,14 @@ GEM
|
||||
word_wrap (~> 1.0.0)
|
||||
xcodeproj (>= 1.13.0, < 2.0.0)
|
||||
xcpretty (~> 0.3.0)
|
||||
xcpretty-travis-formatter (>= 0.0.3)
|
||||
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
|
||||
fastlane-plugin-brew (0.1.1)
|
||||
fastlane-plugin-sentry (1.20.0)
|
||||
fastlane-plugin-sentry (1.23.0)
|
||||
os (~> 1.1, >= 1.1.4)
|
||||
fastlane-plugin-versioning (0.5.2)
|
||||
fastlane-plugin-xcodegen (1.1.0)
|
||||
fastlane-plugin-brew (~> 0.1.1)
|
||||
ffi (1.16.3)
|
||||
ffi (1.17.0)
|
||||
fourflusher (2.3.1)
|
||||
fuzzy_match (2.0.4)
|
||||
gh_inspector (1.1.3)
|
||||
@@ -214,22 +214,22 @@ GEM
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (>= 0.16, < 2.a)
|
||||
highline (2.0.3)
|
||||
http-cookie (1.0.5)
|
||||
http-cookie (1.0.6)
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
i18n (1.14.4)
|
||||
i18n (1.14.5)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jmespath (1.6.2)
|
||||
json (2.7.1)
|
||||
json (2.7.2)
|
||||
jwt (2.8.1)
|
||||
base64
|
||||
mini_magick (4.12.0)
|
||||
mini_mime (1.1.5)
|
||||
mini_portile2 (2.8.5)
|
||||
minitest (5.22.3)
|
||||
mini_portile2 (2.8.7)
|
||||
minitest (5.23.1)
|
||||
molinillo (0.8.0)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.4.0)
|
||||
multipart-post (2.4.1)
|
||||
mutex_m (0.2.0)
|
||||
nanaimo (0.3.0)
|
||||
nap (1.1.0)
|
||||
@@ -239,23 +239,24 @@ GEM
|
||||
nokogiri (1.15.6)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
racc (~> 1.4)
|
||||
optparse (0.4.0)
|
||||
optparse (0.5.0)
|
||||
os (1.1.4)
|
||||
plist (3.7.1)
|
||||
public_suffix (4.0.7)
|
||||
racc (1.7.3)
|
||||
rake (13.1.0)
|
||||
racc (1.8.0)
|
||||
rake (13.2.1)
|
||||
representable (3.2.0)
|
||||
declarative (< 0.1.0)
|
||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
retriable (3.1.2)
|
||||
rexml (3.2.6)
|
||||
rexml (3.2.9)
|
||||
strscan
|
||||
rouge (2.0.7)
|
||||
ruby-macho (2.5.1)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.3.2)
|
||||
security (0.1.3)
|
||||
security (0.1.5)
|
||||
signet (0.19.0)
|
||||
addressable (~> 2.8)
|
||||
faraday (>= 0.17.5, < 3.a)
|
||||
@@ -270,6 +271,7 @@ GEM
|
||||
clamp (~> 1.3)
|
||||
nokogiri (>= 1.14.3)
|
||||
xcodeproj (~> 1.21)
|
||||
strscan (3.1.0)
|
||||
terminal-notifier (2.0.0)
|
||||
terminal-table (3.0.2)
|
||||
unicode-display_width (>= 1.1.1, < 3)
|
||||
|
||||
@@ -1,5 +1,32 @@
|
||||
{
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "bluecryptor",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/Kitura/BlueCryptor.git",
|
||||
"state" : {
|
||||
"revision" : "cec97c24b111351e70e448972a7d3fe68a756d6d",
|
||||
"version" : "2.0.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "blueecc",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/Kitura/BlueECC.git",
|
||||
"state" : {
|
||||
"revision" : "1485268a54f8135435a825a855e733f026fa6cc8",
|
||||
"version" : "1.2.201"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "bluersa",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/Kitura/BlueRSA.git",
|
||||
"state" : {
|
||||
"revision" : "440f78db26d8bb073f29590f1c7bd31004da09ae",
|
||||
"version" : "1.0.201"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "devicekit",
|
||||
"kind" : "remoteSourceControl",
|
||||
@@ -27,6 +54,24 @@
|
||||
"version" : "1.7.18"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "kituracontracts",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/Kitura/KituraContracts.git",
|
||||
"state" : {
|
||||
"revision" : "6edf7ac3dd2b3a2c61284778d430bbad7d8a6f23",
|
||||
"version" : "2.0.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "loggerapi",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/Kitura/LoggerAPI.git",
|
||||
"state" : {
|
||||
"revision" : "4e6b45e850ffa275e8e26a24c6454fd709d5b6ac",
|
||||
"version" : "2.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "lottie-ios",
|
||||
"kind" : "remoteSourceControl",
|
||||
@@ -86,8 +131,26 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-collections",
|
||||
"state" : {
|
||||
"revision" : "937e904258d22af6e447a0b72c0bc67583ef64a2",
|
||||
"version" : "1.0.4"
|
||||
"revision" : "94cf62b3ba8d4bed62680a282d4c25f9c63c2efb",
|
||||
"version" : "1.1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-jwt",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/Kitura/Swift-JWT",
|
||||
"state" : {
|
||||
"revision" : "08e02ff214c41df49bdd189ff837d68ba11c437b",
|
||||
"version" : "4.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-log",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-log.git",
|
||||
"state" : {
|
||||
"revision" : "9cb486020ebf03bfa5b5df985387a14a98744537",
|
||||
"version" : "1.6.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -592,6 +592,8 @@ Tap the + to start adding people.";
|
||||
"room_action_send_sticker" = "Send sticker";
|
||||
"room_action_send_file" = "Send file";
|
||||
"room_action_reply" = "Reply";
|
||||
"room_action_report" = "Report room";
|
||||
"room_action_report_prompt_reason" = "Reason for reporting this room";
|
||||
"room_replacement_information" = "This room has been replaced and is no longer active.";
|
||||
"room_replacement_link" = "The conversation continues here.";
|
||||
"room_predecessor_information" = "This room is a continuation of another conversation.";
|
||||
@@ -2603,6 +2605,8 @@ To enable access, tap Settings> Location and select Always";
|
||||
|
||||
|
||||
// MARK: - WYSIWYG Composer
|
||||
"wysiwyg_composer_action_minimise_action" = "Shrink composer";
|
||||
"wysiwyg_composer_action_maximise_action" = "Expand composer";
|
||||
|
||||
// Send Media Actions
|
||||
"wysiwyg_composer_start_action_media_picker" = "Photo Library";
|
||||
|
||||
@@ -2719,3 +2719,10 @@
|
||||
"settings_manage_account_description" = "Halda kasutajakontot koduserveris %@";
|
||||
"manage_session_redirect" = "Järgmiseks suuname sind sinu serveriteenuse autentijale ning seal saad sa väljalogimise lõpuni viia.";
|
||||
"manage_session_redirect_error" = "See funktsionaalsus pole hetkel saadaval. Lisateavet saad oma koduserveri haldajalt";
|
||||
"room_action_report" = "Teata jututoast";
|
||||
"room_action_report_prompt_reason" = "Jututoast teatamise põhjus";
|
||||
|
||||
|
||||
// MARK: - WYSIWYG Composer
|
||||
"wysiwyg_composer_action_minimise_action" = "Peida tekstitoimeti";
|
||||
"wysiwyg_composer_action_maximise_action" = "Ava tekstitoimeti";
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
"contacts_address_book_matrix_users_toggle" = "فقط کاربران ماتریس";
|
||||
"directory_search_results_more_than" = ">%tu نتیجه برای %@ یافت شد";
|
||||
"directory_search_results" = "%tu نتیجه برای %@ یافت شد";
|
||||
"search_in_progress" = "در حال جست وجو";
|
||||
"search_in_progress" = "جوییدن…";
|
||||
"search_no_result" = "نتیچه ای یافت نشد";
|
||||
"search_people_placeholder" = "جستجو بر اساس شناسه کاربری، نام یا آدرس ایمیل";
|
||||
"search_default_placeholder" = "جست و جو";
|
||||
@@ -246,7 +246,7 @@
|
||||
"room_accessibility_search" = "جستجو";
|
||||
"room_message_edits_history_title" = "ویرایش های پیام";
|
||||
"room_resource_usage_limit_reached_message_2" = "برخی از کاربران نمی توانند وارد سیستم شوند.";
|
||||
"room_resource_limit_exceeded_message_contact_3" = " برای ادامه استفاده از این سرویس";
|
||||
"room_resource_limit_exceeded_message_contact_3" = " برای ادامه استفاده از این خدمت.";
|
||||
"room_resource_limit_exceeded_message_contact_2_link" = "با سرپرست سرویس خود تماس بگیرید";
|
||||
"room_resource_limit_exceeded_message_contact_1" = " لطفا ";
|
||||
"room_predecessor_link" = "برای دیدن پیامهای قدیمیتر به اینجاضربه بزنید.";
|
||||
@@ -416,7 +416,7 @@
|
||||
"event_formatter_call_retry" = "تلاش مجدد";
|
||||
"event_formatter_call_answer" = "پاسخ";
|
||||
"event_formatter_call_decline" = "رد تماس";
|
||||
"event_formatter_call_back" = "تماس";
|
||||
"event_formatter_call_back" = "پاسخ تماس";
|
||||
"event_formatter_call_connection_failed" = "ارتباط ناموفق بود";
|
||||
"event_formatter_call_missed_video" = "تماس ویدیویی از دست رفته";
|
||||
"event_formatter_call_missed_voice" = "تماس صوتی از دست رفته";
|
||||
@@ -464,7 +464,7 @@
|
||||
"group_participants_invited_section" = "دعوت شده";
|
||||
"group_participants_invite_malformed_id_title" = "خطای دعوت";
|
||||
"group_participants_invite_another_user" = "جستجو / دعوت با شناسه کاربری یا نام";
|
||||
"group_participants_filter_members" = "اعضای انجمن را فیلتر کنید";
|
||||
"group_participants_filter_members" = "پالایش اعضای اجتماع";
|
||||
"group_participants_invite_prompt_msg" = "آیا مطمئنید که می خواهید %@ را به این گروه دعوت کنید؟";
|
||||
"group_participants_invite_prompt_title" = "تایید";
|
||||
"group_participants_remove_prompt_msg" = "آیا مطمئنید که می خواهید %@ را از این گروه حذف کنید؟";
|
||||
@@ -570,7 +570,7 @@
|
||||
"room_details_files" = "بارگذاری شده ها";
|
||||
"room_details_people" = "اعضا";
|
||||
"room_details_title_for_dm" = "جزییات";
|
||||
"identity_server_settings_alert_disconnect_still_sharing_3pid_button" = "به هر حال قطع کن";
|
||||
"identity_server_settings_alert_disconnect_still_sharing_3pid_button" = "قطع شدن به هر روی";
|
||||
"identity_server_settings_alert_disconnect_button" = "قطع کردن";
|
||||
"identity_server_settings_disconnect" = "قطع کردن";
|
||||
"identity_server_settings_change" = "تغییر دادن";
|
||||
@@ -652,7 +652,7 @@
|
||||
"settings_confirm_password" = "تایید رمز";
|
||||
"settings_new_password" = "رمز جدید";
|
||||
"settings_old_password" = "رمز قدیمی";
|
||||
"settings_third_party_notices" = "اعلامیه های شخص ثالث";
|
||||
"settings_third_party_notices" = "یادآوریهای سومشخص";
|
||||
"settings_privacy_policy" = "سیاست حفظ حریم خصوصی";
|
||||
"settings_term_conditions" = "شرایط و ضوابط";
|
||||
"settings_copyright" = "کپی رایت";
|
||||
@@ -684,7 +684,7 @@
|
||||
"settings_room_upgrades" = "ارتقاء اتاق";
|
||||
"settings_messages_by_a_bot" = "پیام های ربات";
|
||||
"settings_call_invitations" = "دعوت نامه های تماس";
|
||||
"settings_room_invitations" = "دعوت نامه های اتاق";
|
||||
"settings_room_invitations" = "دعوتهای اتاق";
|
||||
"settings_messages_containing_keywords" = "کلمات کلیدی";
|
||||
"settings_messages_containing_at_room" = "@ اتاق";
|
||||
"settings_messages_containing_user_name" = "نام کاربری من";
|
||||
@@ -694,13 +694,13 @@
|
||||
"settings_encrypted_direct_messages" = "پیام های مستقیم رمزگذاری شده";
|
||||
"settings_direct_messages" = "پیام مستقیم";
|
||||
"settings_notify_me_for" = "به من اطلاع بده برای";
|
||||
"settings_mentions_and_keywords" = "ذکر و کلمات کلیدی";
|
||||
"settings_mentions_and_keywords" = "نامبری و کلیدواژگان";
|
||||
"settings_default" = "اعلان های پیش فرض";
|
||||
"settings_notifications_disabled_alert_message" = "برای فعال کردن اعلانها، به تنظیمات دستگاه خود بروید.";
|
||||
"settings_notifications_disabled_alert_title" = "غیر فعال کردن اعلان ها";
|
||||
"settings_pin_rooms_with_missed_notif" = "پین کردن اتاق هایی با اعلان های از دست رفته";
|
||||
"settings_pin_rooms_with_unread" = "پین کردن اتاق هایی با پیام های خوانده نشده";
|
||||
"settings_global_settings_info" = "تنظیمات اعلان جهانی در سرویس گیرنده وب %@ شما موجود است";
|
||||
"settings_global_settings_info" = "تنظیمات آگاهی عمومی در کارخواه وب %@تان موجود است";
|
||||
"settings_show_decrypted_content" = "نمایش محتوای رمزگشایی شده";
|
||||
"settings_device_notifications" = "اعلان های دستگاه";
|
||||
"settings_enable_push_notif" = "اعلان ها در این دستگاه";
|
||||
@@ -1346,3 +1346,4 @@
|
||||
"onboarding_splash_page_2_title" = "تحت کنترل شماست.";
|
||||
"onboarding_splash_page_1_message" = "یک ارتباط امن و مستقل که سطح حریم شخصی آن دقیقا مشابه ارتباط رو در رو در منزل شماست.";
|
||||
"accessibility_selected" = "انتخاب شده";
|
||||
"room_title_members" = "%@ عضو";
|
||||
|
||||
@@ -1352,7 +1352,7 @@
|
||||
"event_formatter_call_ringing" = "Hívás…";
|
||||
"event_formatter_call_connecting" = "Kapcsolás…";
|
||||
"settings_labs_enable_ringing_for_group_calls" = "Csengetés csoportos hívásokhoz";
|
||||
"room_no_privileges_to_create_group_call" = "Adminisztrátornak vagy moderátornak kell lenned a hívás indításához.";
|
||||
"room_no_privileges_to_create_group_call" = "Adminisztrátornak vagy moderátornak kell lennie a hívás indításához.";
|
||||
"room_join_group_call" = "Csatlakozás";
|
||||
|
||||
// Chat
|
||||
@@ -2478,9 +2478,9 @@
|
||||
"user_sessions_settings" = "Munkamenetek kezelése";
|
||||
"invite_to" = "Meghívó ide: %@";
|
||||
"device_name_unknown" = "Ismeretlen kliens";
|
||||
"device_name_mobile" = "%@ Mobil";
|
||||
"device_name_desktop" = "%@ Alkalmazás";
|
||||
"device_name_web" = "%@ Web";
|
||||
"device_name_mobile" = "Mobilos %@";
|
||||
"device_name_desktop" = "Asztali %@";
|
||||
"device_name_web" = "Webes %@";
|
||||
"user_session_item_details" = "%1$@ · %2$@";
|
||||
|
||||
// First item is client name and second item is session display name
|
||||
@@ -2501,8 +2501,8 @@
|
||||
"room_first_message_placeholder" = "Küld el az első üzenetedet…";
|
||||
"authentication_qr_login_confirm_title" = "Biztonságos kapcsolat beállítva";
|
||||
"room_event_encryption_info_key_authenticity_not_guaranteed" = "A titkosított üzenetek valódiságát ezen az eszközön nem lehet garantálni.";
|
||||
"wysiwyg_composer_format_action_underline" = "Aláhúzott";
|
||||
"wysiwyg_composer_format_action_strikethrough" = "Áthúzott";
|
||||
"wysiwyg_composer_format_action_underline" = "Aláhúzott formázás alkalmazása";
|
||||
"wysiwyg_composer_format_action_strikethrough" = "Áthúzott formázás alkalmazása";
|
||||
"wysiwyg_composer_format_action_italic" = "Dőlt";
|
||||
|
||||
// Formatting Actions
|
||||
@@ -2528,19 +2528,19 @@
|
||||
"user_session_details_device_os" = "Operációs rendszer";
|
||||
"user_session_details_device_browser" = "Böngésző";
|
||||
"user_session_details_device_model" = "Modell";
|
||||
"user_session_details_device_ip_location" = "Tartózkodási helyem";
|
||||
"user_session_details_device_ip_location" = "Saját tartózkodási hely";
|
||||
"user_session_details_device_ip_address" = "IP cím";
|
||||
"user_session_details_last_activity" = "Utolsó tevékenység";
|
||||
"user_session_details_session_section_footer" = "A másoláshoz koppints és tartsd rajta az ujjad.";
|
||||
"user_session_details_session_id" = "Kapcsolat azonosító";
|
||||
"user_session_details_session_section_footer" = "A másoláshoz koppintson és tartsa lenyomva.";
|
||||
"user_session_details_session_id" = "Kapcsolatazonosító";
|
||||
"user_session_details_session_name" = "Munkamenet neve";
|
||||
"user_session_details_device_section_header" = "Eszköz";
|
||||
"user_session_details_application_section_header" = "Alkalmazás";
|
||||
"user_session_details_session_section_header" = "Munkamenet";
|
||||
"user_session_details_title" = "Munkamenet információk";
|
||||
"user_session_details_title" = "Munkamenet-információk";
|
||||
"device_type_name_unknown" = "Ismeretlen";
|
||||
"device_type_name_mobile" = "Mobil";
|
||||
"device_type_name_web" = "Web";
|
||||
"device_type_name_mobile" = "Mobilos";
|
||||
"device_type_name_web" = "Webes";
|
||||
"device_type_name_desktop" = "Asztali";
|
||||
"user_inactive_session_item_with_date" = "90+ napja inaktív (%@)";
|
||||
"user_inactive_session_item" = "90+ napja inaktív";
|
||||
@@ -2740,3 +2740,37 @@
|
||||
|
||||
"key_verification_self_verify_security_upgrade_alert_title" = "Alkalmazás frissítve";
|
||||
"settings_acceptable_use" = "Elfogadható felhasználói feltételek";
|
||||
"room_creation_user_not_found_prompt_title" = "Megerősítés";
|
||||
"room_creation_user_not_found_prompt_message" = "Nem található profil ehhez a Matrix-azonosítóhoz. Mindenképp elindítja a privát csevegést?";
|
||||
"room_creation_user_not_found_prompt_invite_action" = "Privát csevegés mindenképp";
|
||||
"room_participants_invite_unknown_participant_prompt_to_msg" = "Nem található profil ehhez a Matrix-azonosítóhoz. Mindenképp meghívja %@ felhasználót a(z) %@ szobába?";
|
||||
"room_participants_invite_anyway" = "Meghívás mindenképp";
|
||||
|
||||
// Room commands descriptions
|
||||
"room_command_change_display_name_description" = "Megváltoztatja a megjelenítendő becenevét";
|
||||
"room_command_emote_description" = "Megjeleníti a műveletet";
|
||||
"room_command_join_room_description" = "Csatlakozik a megadott című szobához";
|
||||
"room_command_part_room_description" = "Elhagyja a szobát";
|
||||
"room_command_invite_user_description" = "Meghívja az adott azonosítójú felhasználót a jelenlegi szobába";
|
||||
"room_command_kick_user_description" = "Eltávolítja az adott azonosítójú felhasználót ebből a szobából";
|
||||
"room_command_ban_user_description" = "Kitiltja az adott azonosítójú felhasználót";
|
||||
"room_command_unban_user_description" = "Feloldja az adott azonosítójú felhasználó kitiltását";
|
||||
"notice_display_name_changed_to" = "%@ erre módosította a megjelenítendő nevét: %@";
|
||||
"poll_timeline_loading" = "Betöltés...";
|
||||
"manage_session_redirect" = "Át lesz irányítva a kiszolgálója hitelesítési szolgáltatójához, hogy befejezze a kijelentkezést.";
|
||||
"manage_session_redirect_error" = "A funkcionalitás jelenleg nem érhető el. Lépjen kapcsolatba a Matrix-kiszolgáló rendszergazdájával.";
|
||||
"settings_manage_account_title" = "Fiók";
|
||||
"settings_manage_account_action" = "Fiók kezelése";
|
||||
"settings_manage_account_description" = "A fiókja kezelése itt: %@";
|
||||
"room_command_change_room_topic_description" = "Beállítja a szoba témáját";
|
||||
"room_action_report" = "Szoba jelentése";
|
||||
"room_action_report_prompt_reason" = "A szoba jelentésének oka";
|
||||
|
||||
|
||||
// MARK: - WYSIWYG Composer
|
||||
"wysiwyg_composer_action_minimise_action" = "Szerkesztő összecsukása";
|
||||
"wysiwyg_composer_action_maximise_action" = "Szerkesztő kibontása";
|
||||
"room_command_set_user_power_level_description" = "Meghatározza a felhasználó szintjét";
|
||||
"room_command_reset_user_power_level_description" = "Elveszi az adott azonosítójú felhasználó operátori jogosultságát";
|
||||
"room_command_error_unknown_command" = "Érvénytelen vagy nem kezelt parancs";
|
||||
"room_command_discard_session_description" = "Kényszeríti a titkosított szobában lévő aktuális kimenő csoportmunkamenet elvetését";
|
||||
|
||||
@@ -2974,3 +2974,10 @@
|
||||
"settings_manage_account_description" = "Kelola akun Anda di %@";
|
||||
"manage_session_redirect" = "Anda akan dialihkan ke penyedia autentikasi server Anda untuk menyelesaikan proses keluar.";
|
||||
"manage_session_redirect_error" = "Fungsi saat ini tidak tersedia. Silakan hubungi admin homeserver Anda";
|
||||
"room_action_report_prompt_reason" = "Alasan melaporkan ruangan ini";
|
||||
|
||||
|
||||
// MARK: - WYSIWYG Composer
|
||||
"wysiwyg_composer_action_minimise_action" = "Kecilkan komposer";
|
||||
"wysiwyg_composer_action_maximise_action" = "Luaskan komposer";
|
||||
"room_action_report" = "Laporkan ruangan";
|
||||
|
||||
@@ -2747,3 +2747,10 @@
|
||||
"settings_manage_account_description" = "Gestisci il tuo account su %@";
|
||||
"manage_session_redirect" = "Verrai reindirizzato al fornitore di autenticazione del tuo server per completare la disconnessione.";
|
||||
"manage_session_redirect_error" = "Funzionalità attualmente non disponibile. Contatta l'amministratore del tuo homeserver";
|
||||
|
||||
|
||||
// MARK: - WYSIWYG Composer
|
||||
"wysiwyg_composer_action_minimise_action" = "Rimpicciolisci il compositore";
|
||||
"wysiwyg_composer_action_maximise_action" = "Espandi il compositore";
|
||||
"room_action_report" = "Segnala stanza";
|
||||
"room_action_report_prompt_reason" = "Motivo della segnalazione della stanza";
|
||||
|
||||
@@ -1 +1,77 @@
|
||||
|
||||
|
||||
// String for App Store
|
||||
"store_short_description" = "უსაფრთხო დეცენტრალიზებული ჩატი/ვოიპი";
|
||||
|
||||
// Titles
|
||||
"title_home" = "მთავარი";
|
||||
"title_favourites" = "ფავორიტები";
|
||||
"title_people" = "ხალხი";
|
||||
"title_rooms" = "ოთახები";
|
||||
"title_groups" = "თემები";
|
||||
"warning" = "გაფრთხილება";
|
||||
|
||||
// Actions
|
||||
"view" = "ხედი";
|
||||
"next" = "შემდეგი";
|
||||
"back" = "უკან";
|
||||
"continue" = "გაგრძელება";
|
||||
"create" = "შექმნა";
|
||||
"start" = "აწყება";
|
||||
"leave" = "დატოვება";
|
||||
"remove" = "წაშლა";
|
||||
"retry" = "გამეორება";
|
||||
"on" = "ჩართული";
|
||||
"off" = "გამორთული";
|
||||
"enable" = "ჩართვა";
|
||||
"save" = "შენახვა";
|
||||
"join" = "შეუერთდება";
|
||||
"decline" = "უარყოფა";
|
||||
"accept" = "მიღება";
|
||||
"camera" = "კამერა";
|
||||
"voice" = "ხმა";
|
||||
"video" = "ვიდეო";
|
||||
"active_call" = "აქტიური ზარი";
|
||||
"later" = "შემდეგ";
|
||||
"rename" = "გადარქმევა";
|
||||
"collapse" = "ჩაკეცვა";
|
||||
"send_to" = "გაგზავნა %@-ს";
|
||||
"close" = "დახურვა";
|
||||
"skip" = "გამოტოვება";
|
||||
"joined" = "შეუერთდა";
|
||||
"switch" = "გადართვა";
|
||||
"more" = "მეტი";
|
||||
"less" = "ნაკლები";
|
||||
"open" = "გახსნა";
|
||||
"private" = "ირადი";
|
||||
"public" = "საჯარო";
|
||||
"stop" = "შეჩერება";
|
||||
"new_word" = "ახალი";
|
||||
"existing" = "არსებული";
|
||||
"add" = "დამატება";
|
||||
"ok" = "კარგი";
|
||||
"error" = "შეცდომა";
|
||||
"suggest" = "შესთავაზება";
|
||||
"confirm" = "დადასტურება";
|
||||
"invite_to" = "მიწვევა %@-ში";
|
||||
|
||||
// Activities
|
||||
"loading" = "ჩატვირთვა";
|
||||
"sending" = "გაგზავნა";
|
||||
"callbar_active_and_single_paused" = "1 აქტიური ზარი (%@) · 1 შეჩერებული ზარი";
|
||||
"callbar_active_and_multiple_paused" = "1 აქტიური ზარი (%@) · %@ შეჩერებული ზარები";
|
||||
"callbar_only_single_paused" = "შეჩერებული ზარი";
|
||||
"callbar_return" = "დაბრუნება";
|
||||
"store_promotional_text" = "კონფიდენციალობის დაცვით ჩატისა და თანამშრომლობის აპლიკაცია, ღია ქსელზე. დეცენტრალიზებული, რათა მართვა გადაგიცემათ. არაა დათამაინინგი, არაა უკანა კარები და არაა მესამე მხარის წვდომა.";
|
||||
"invite" = "მიწვევა";
|
||||
"cancel" = "გაუქმება";
|
||||
"preview" = "წინასწარი ხილვა";
|
||||
"active_call_details" = "აქტიური ზარი (%@)";
|
||||
"joining" = "შეერთება";
|
||||
"done" = "დასრულებული";
|
||||
"edit" = "რედაქტირება";
|
||||
"saving" = "შენახვა";
|
||||
|
||||
// Call Bar
|
||||
"callbar_only_single_active" = "შეეხეთ, რომ დაბრუნდეთ ზარში (%@)";
|
||||
"callbar_only_multiple_paused" = "%@ შეჩერებული ზარები";
|
||||
|
||||
7
Riot/Assets/nn.lproj/InfoPlist.strings
Normal file
7
Riot/Assets/nn.lproj/InfoPlist.strings
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
|
||||
"NSCalendarsUsageDescription" = "Sjå dei planlagde møta dine i appen.";
|
||||
"NSFaceIDUsageDescription" = "Face ID vert brukt til å få tilgang til appen din.";
|
||||
// Permissions usage explanations
|
||||
"NSCameraUsageDescription" = "Kameraet vert brukt til videosamtalar, og til å ta og laste opp bilde og videoar.";
|
||||
"NSMicrophoneUsageDescription" = "Appen treng tilgang til mikrofonen for samtalar, og for å spele inn video og lydmeldingar.";
|
||||
1
Riot/Assets/nn.lproj/Localizable.strings
Normal file
1
Riot/Assets/nn.lproj/Localizable.strings
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
@@ -9,3 +9,74 @@
|
||||
"warning" = "Åtvaring";
|
||||
// String for App Store
|
||||
"store_short_description" = "Sikker desentralisert chat/IP-telefoni";
|
||||
"ok" = "OK";
|
||||
"callbar_only_single_paused" = "Samtale sett på pause";
|
||||
"cancel" = "Avbryt";
|
||||
"save" = "Lagra";
|
||||
"join" = "Ver med";
|
||||
"decline" = "Avslå";
|
||||
"accept" = "Godta";
|
||||
"preview" = "Førehandsvising";
|
||||
"camera" = "Kamera";
|
||||
"voice" = "Røyst";
|
||||
"video" = "Video";
|
||||
"active_call" = "Aktiv samtale";
|
||||
"active_call_details" = "Aktiv samtale (%@)";
|
||||
"joined" = "Vart med";
|
||||
"later" = "Seinare";
|
||||
"rename" = "Endre namn";
|
||||
"collapse" = "Skjul";
|
||||
"send_to" = "Send til %@";
|
||||
"sending" = "Sender";
|
||||
"close" = "Lat att";
|
||||
"skip" = "Hopp over";
|
||||
"switch" = "Byt";
|
||||
"more" = "Meir";
|
||||
"less" = "Mindre";
|
||||
"open" = "Opne";
|
||||
"done" = "Ferdig";
|
||||
|
||||
// Call Bar
|
||||
"callbar_only_single_active" = "Trykk for å gå tilbake til samtalen (%@)";
|
||||
"callbar_only_multiple_paused" = "%@ samtalar sett på pause";
|
||||
"callbar_return" = "Gå tilbake";
|
||||
"callbar_only_single_active_group" = "Trykk for å bli med i konferansesamtalen (%@)";
|
||||
|
||||
// Accessibility
|
||||
"accessibility_checkbox_label" = "avkryssingsboks";
|
||||
"accessibility_button_label" = "knapp";
|
||||
"error" = "Feil";
|
||||
"invite_to" = "Inviter til %@";
|
||||
|
||||
// MARK: Onboarding
|
||||
"onboarding_splash_register_button_title" = "Opprett konto";
|
||||
"accessibility_selected" = "vald";
|
||||
"private" = "Privat";
|
||||
"public" = "Offentleg";
|
||||
"stop" = "Stopp";
|
||||
"new_word" = "Ny";
|
||||
"existing" = "Eksisterande";
|
||||
"add" = "Legg til";
|
||||
"suggest" = "Føreslå";
|
||||
"edit" = "Rediger";
|
||||
|
||||
// Activities
|
||||
"loading" = "Lastar";
|
||||
"saving" = "Lagrar";
|
||||
"callbar_active_and_single_paused" = "1 aktiv samtale (%@) · 1 samtale sett i pause";
|
||||
"callbar_active_and_multiple_paused" = "1 aktiv samtale (%@) · %@ samtalar sett på pause";
|
||||
"confirm" = "Stadfest";
|
||||
"onboarding_splash_login_button_title" = "Eg har allereie ein konto";
|
||||
"authentication_forgot_password_text_field_placeholder" = "E-postadresse";
|
||||
"next" = "Neste";
|
||||
"back" = "Tilbake";
|
||||
"continue" = "Fortset";
|
||||
"create" = "Lag";
|
||||
"remove" = "Fjern";
|
||||
"invite" = "Inviter";
|
||||
"retry" = "Prøv på nytt";
|
||||
"on" = "På";
|
||||
"off" = "Av";
|
||||
"authentication_login_username" = "Brukarnamn / e-postadresse / telefonnummer";
|
||||
"onboarding_display_name_placeholder" = "Visingsnamn";
|
||||
"authentication_login_with_qr" = "Logg inn med QR-kode";
|
||||
|
||||
@@ -1226,7 +1226,7 @@
|
||||
"secrets_recovery_reset_action_part_2" = "Resettar tudo";
|
||||
"secrets_setup_recovery_passphrase_summary_information" = "Lembre-se de sua Frase de Segurança. Ela pode ser usada para destrancar suas mensagens & dados encriptados.";
|
||||
"secrets_setup_recovery_passphrase_summary_title" = "Salvar sua Frase de Segurança";
|
||||
"home_empty_view_information" = "O app de chat seguro tudo-em-um para equipes, amigas(os) e organizações. Toque no botão + abaixo para adicionar pessoas e salas.";
|
||||
"home_empty_view_information" = "O app de chat seguro tudo-em-um para equipes, amigos e organizações. Toque no botão + abaixo para adicionar pessoas e salas.";
|
||||
|
||||
// MARK: - Home
|
||||
|
||||
@@ -1244,7 +1244,7 @@
|
||||
|
||||
// MARK: - Invite friends
|
||||
|
||||
"invite_friends_action" = "Convidar amigas(os) para %@";
|
||||
"invite_friends_action" = "Convidar amigos para %@";
|
||||
"pin_protection_settings_change_pin" = "Mudar PIN";
|
||||
"pin_protection_confirm_pin_to_change" = "Confirme PIN para mudar PIN";
|
||||
"bug_report_background_mode" = "Continuar em background";
|
||||
@@ -1348,7 +1348,7 @@
|
||||
"side_menu_action_feedback" = "Feedback";
|
||||
"side_menu_action_help" = "Ajuda";
|
||||
"side_menu_action_settings" = "Ajustes";
|
||||
"side_menu_action_invite_friends" = "Convidar amigas(os)";
|
||||
"side_menu_action_invite_friends" = "Convidar amigos";
|
||||
|
||||
// Mark: - Side menu
|
||||
|
||||
@@ -1632,7 +1632,7 @@
|
||||
"onboarding_use_case_not_sure_yet" = "Não tem certeza ainda? %@";
|
||||
"onboarding_use_case_community_messaging" = "Comunidades";
|
||||
"onboarding_use_case_work_messaging" = "Times";
|
||||
"onboarding_use_case_personal_messaging" = "Amigas(os) e família";
|
||||
"onboarding_use_case_personal_messaging" = "Amigos e família";
|
||||
"onboarding_use_case_message" = "Nós vamos ajudá-la(o) a ficar conectada(o)";
|
||||
"onboarding_use_case_title" = "Com quem você vai fazer chat mais?";
|
||||
|
||||
@@ -2654,3 +2654,20 @@
|
||||
"user_other_session_security_recommendation_title" = "Outras sessões";
|
||||
"room_creation_only_one_email_invite" = "Você só pode convidar um e-mail de cada vez";
|
||||
"accessibility_selected" = "selecionado";
|
||||
"room_creation_user_not_found_prompt_title" = "Confirmação";
|
||||
"room_creation_user_not_found_prompt_message" = "Não foi possível encontrar perfis para este ID do Matrix. Quer começar uma conversa mesmo assim?";
|
||||
"room_creation_user_not_found_prompt_invite_action" = "Começar conversa mesmo assim";
|
||||
"room_participants_invite_anyway" = "Convidar mesmo assim";
|
||||
"room_command_part_room_description" = "Sair da sala";
|
||||
"room_command_kick_user_description" = "Remove o usuário com o ID fornecido desta sala";
|
||||
"room_command_unban_user_description" = "Desbane o usuário com o ID fornecido";
|
||||
"room_command_set_user_power_level_description" = "Define o nível de poder de um usuário";
|
||||
"room_participants_invite_unknown_participant_prompt_to_msg" = "Não foi possível encontrar perfis para este ID do Matrix. Tem certeza que deseja convidar %@ para %@?";
|
||||
"room_command_ban_user_description" = "Bane o usuário com o ID fornecido";
|
||||
|
||||
// Room commands descriptions
|
||||
"room_command_change_display_name_description" = "Altera o seu nome de exibição";
|
||||
"room_command_emote_description" = "Exibe ação";
|
||||
"room_command_join_room_description" = "Entra na sala com o endereço fornecido";
|
||||
"room_command_invite_user_description" = "Convida o usuário com o ID fornecido para a sala atual";
|
||||
"authentication_qr_login_failure_device_not_supported" = "Vincular com este dispositivo não é suportado.";
|
||||
|
||||
@@ -2970,3 +2970,10 @@
|
||||
"settings_manage_account_description" = "Spravujte svoj účet na %@";
|
||||
"manage_session_redirect" = "Budete presmerovaní na poskytovateľa overovania vášho servera, aby ste dokončili odhlásenie.";
|
||||
"manage_session_redirect_error" = "Funkcia aktuálne nie je dostupná. Obráťte sa na správcu vášho domovského servera";
|
||||
"room_action_report_prompt_reason" = "Dôvod nahlásenia tejto miestnosti";
|
||||
"wysiwyg_composer_action_maximise_action" = "Rozšíriť editor";
|
||||
"room_action_report" = "Nahlásiť miestnosť";
|
||||
|
||||
|
||||
// MARK: - WYSIWYG Composer
|
||||
"wysiwyg_composer_action_minimise_action" = "Zmenšiť editor";
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"leave" = "Dilni";
|
||||
"remove" = "Hiqe";
|
||||
"invite" = "Ftoje";
|
||||
"retry" = "Riprovo";
|
||||
"retry" = "Riprovoni";
|
||||
"off" = "Off";
|
||||
"cancel" = "Anuloje";
|
||||
"save" = "Ruaje";
|
||||
@@ -2757,3 +2757,10 @@
|
||||
"settings_manage_account_title" = "Llogari";
|
||||
"settings_manage_account_action" = "Administroni llogari";
|
||||
"manage_session_redirect" = "Do të ridrejtoheni te shërbimi i mirëfilltësimit të shërbyesit tuaj, për të plotësuar daljen nga llogaria.";
|
||||
"room_action_report" = "Raportojeni dhomën";
|
||||
"room_action_report_prompt_reason" = "Arsye për raportimin e kësaj dhome";
|
||||
|
||||
|
||||
// MARK: - WYSIWYG Composer
|
||||
"wysiwyg_composer_action_minimise_action" = "Tkurre hartuesin";
|
||||
"wysiwyg_composer_action_maximise_action" = "Zgjeroje hartuesin";
|
||||
|
||||
@@ -2714,3 +2714,10 @@
|
||||
"settings_manage_account_description" = "Hantera ditt konto på %@";
|
||||
"manage_session_redirect" = "Du kommer att omdirigeras till din servers autentiseringsleverantör för att fortsätta utloggning.";
|
||||
"manage_session_redirect_error" = "Funktion för närvarande otillgänglig. Vänligen kontakta din hemserveradministratör";
|
||||
"room_action_report" = "Rapportera rum";
|
||||
"room_action_report_prompt_reason" = "Anledning att rapportera det här rummet";
|
||||
"wysiwyg_composer_action_maximise_action" = "Expandera redigerare";
|
||||
|
||||
|
||||
// MARK: - WYSIWYG Composer
|
||||
"wysiwyg_composer_action_minimise_action" = "Krymp redigerare";
|
||||
|
||||
@@ -2972,3 +2972,10 @@
|
||||
"settings_manage_account_description" = "Керувати обліковим записом у %@";
|
||||
"manage_session_redirect" = "Вас буде перенаправлено до постачальника автентифікації вашого сервера для завершення виходу.";
|
||||
"manage_session_redirect_error" = "Функціональність наразі недоступна. Зверніться до адміністратора вашого домашнього сервера";
|
||||
"wysiwyg_composer_action_maximise_action" = "Розгорнути редактор";
|
||||
"room_action_report" = "Поскаржитися на кімнату";
|
||||
"room_action_report_prompt_reason" = "Причина скарги на цю кімнату";
|
||||
|
||||
|
||||
// MARK: - WYSIWYG Composer
|
||||
"wysiwyg_composer_action_minimise_action" = "Зменшити редактор";
|
||||
|
||||
57
Riot/Categories/MXRoomSummary.swift
Normal file
57
Riot/Categories/MXRoomSummary.swift
Normal file
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// Copyright 2024 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
|
||||
|
||||
extension Notification.Name {
|
||||
static let roomSummaryDidRemoveExpiredDataFromStore = Notification.Name(MXRoomSummary.roomSummaryDidRemoveExpiredDataFromStore)
|
||||
}
|
||||
|
||||
extension MXRoomSummary {
|
||||
@objc static let roomSummaryDidRemoveExpiredDataFromStore = "roomSummaryDidRemoveExpiredDataFromStore"
|
||||
@objc static let roomRetentionStateEventType = "m.room.retention"
|
||||
@objc static let roomRetentionEventMaxLifetimeKey = "max_lifetime"
|
||||
@objc static let roomRetentionMaxLifetime = "roomRetentionMaxLifetime"
|
||||
|
||||
/// Get the room messages retention period in days
|
||||
private func roomRetentionPeriodInMillis() -> UInt64 {
|
||||
if let period = self.others[MXRoomSummary.roomRetentionMaxLifetime] as? UInt64 {
|
||||
return period
|
||||
} else {
|
||||
return Tools.durationInMs(fromDays: 365)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the timestamp below which the received messages must be removed from the store, and the display
|
||||
@objc func minimumTimestamp() -> UInt64 {
|
||||
let periodInMs = self.roomRetentionPeriodInMillis()
|
||||
let currentTs = (UInt64)(Date().timeIntervalSince1970 * 1000)
|
||||
return (currentTs - periodInMs)
|
||||
}
|
||||
|
||||
/// Remove the expired messages from the store.
|
||||
/// If some data are removed, this operation posts the notification: roomSummaryDidRemoveExpiredDataFromStore.
|
||||
/// This operation does not commit the potential change. We let the caller trigger the commit when this is the more suitable.
|
||||
///
|
||||
/// Provide a boolean telling whether some data have been removed.
|
||||
@objc func removeExpiredRoomContentsFromStore() -> Bool {
|
||||
let ret = self.mxSession.store.removeAllMessagesSent(before: self.minimumTimestamp(), inRoom: roomId)
|
||||
if ret {
|
||||
NotificationCenter.default.post(name: .roomSummaryDidRemoveExpiredDataFromStore, object: self)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
}
|
||||
@@ -25,4 +25,16 @@ extension MXSession {
|
||||
matrixItemId: userId,
|
||||
displayName: user?.displayname)
|
||||
}
|
||||
|
||||
/// Clean the storage of a session by removing the expired contents.
|
||||
@objc func removeExpiredMessages() {
|
||||
var hasStoreChanged = false
|
||||
for room in self.rooms {
|
||||
hasStoreChanged = hasStoreChanged || room.summary.removeExpiredRoomContentsFromStore()
|
||||
}
|
||||
|
||||
if hasStoreChanged {
|
||||
self.store.commit?()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5207,6 +5207,14 @@ public class VectorL10n: NSObject {
|
||||
public static var roomActionReply: String {
|
||||
return VectorL10n.tr("Vector", "room_action_reply")
|
||||
}
|
||||
/// Report room
|
||||
public static var roomActionReport: String {
|
||||
return VectorL10n.tr("Vector", "room_action_report")
|
||||
}
|
||||
/// Reason for reporting this room
|
||||
public static var roomActionReportPromptReason: String {
|
||||
return VectorL10n.tr("Vector", "room_action_report_prompt_reason")
|
||||
}
|
||||
/// Send file
|
||||
public static var roomActionSendFile: String {
|
||||
return VectorL10n.tr("Vector", "room_action_send_file")
|
||||
@@ -9567,6 +9575,14 @@ public class VectorL10n: NSObject {
|
||||
public static var widgetStickerPickerNoStickerpacksAlertAddNow: String {
|
||||
return VectorL10n.tr("Vector", "widget_sticker_picker_no_stickerpacks_alert_add_now")
|
||||
}
|
||||
/// Expand composer
|
||||
public static var wysiwygComposerActionMaximiseAction: String {
|
||||
return VectorL10n.tr("Vector", "wysiwyg_composer_action_maximise_action")
|
||||
}
|
||||
/// Shrink composer
|
||||
public static var wysiwygComposerActionMinimiseAction: String {
|
||||
return VectorL10n.tr("Vector", "wysiwyg_composer_action_minimise_action")
|
||||
}
|
||||
/// Apply bold format
|
||||
public static var wysiwygComposerFormatActionBold: String {
|
||||
return VectorL10n.tr("Vector", "wysiwyg_composer_format_action_bold")
|
||||
|
||||
@@ -108,10 +108,6 @@ final class RiotSettings: NSObject {
|
||||
@UserDefault(key: UserDefaultsKeys.pinRoomsWithUnreadMessagesOnHome, defaultValue: false, storage: defaults)
|
||||
var pinRoomsWithUnreadMessagesOnHome
|
||||
|
||||
/// Indicate to show Not Safe For Work public rooms.
|
||||
@UserDefault(key: "showNSFWPublicRooms", defaultValue: false, storage: defaults)
|
||||
var showNSFWPublicRooms
|
||||
|
||||
// MARK: User interface
|
||||
|
||||
@UserDefault<String?>(key: "userInterfaceTheme", defaultValue: nil, storage: defaults)
|
||||
@@ -390,9 +386,11 @@ final class RiotSettings: NSObject {
|
||||
@UserDefault(key: "settingsScreenShowNotificationDecodedContentOption", defaultValue: BWIBuildSettings.shared.settingsScreenShowNotificationDecodedContentOption, storage: defaults)
|
||||
var settingsScreenShowNotificationDecodedContentOption
|
||||
|
||||
// BWI: #6076 removed from nv, need to be checked after merge
|
||||
@UserDefault(key: "settingsScreenShowNsfwRoomsOption", defaultValue: BWIBuildSettings.shared.settingsScreenShowNsfwRoomsOption, storage: defaults)
|
||||
var settingsScreenShowNsfwRoomsOption
|
||||
|
||||
|
||||
@UserDefault(key: "settingsSecurityScreenShowSessions", defaultValue: BuildSettings.settingsSecurityScreenShowSessions, storage: defaults)
|
||||
var settingsSecurityScreenShowSessions
|
||||
|
||||
|
||||
@@ -106,6 +106,9 @@ import AnalyticsEvents
|
||||
|
||||
guard let session = session else { return }
|
||||
useAnalyticsSettings(from: session)
|
||||
client.updateSuperProperties(.init(appPlatform: .EI,
|
||||
cryptoSDK: .Rust,
|
||||
cryptoSDKVersion: session.crypto.version))
|
||||
}
|
||||
|
||||
/// Stops analytics tracking and calls `reset` to clear any IDs and event queues.
|
||||
@@ -171,6 +174,13 @@ import AnalyticsEvents
|
||||
switch result {
|
||||
case .success(let settings):
|
||||
self.identify(with: settings)
|
||||
self.client.updateSuperProperties(
|
||||
AnalyticsEvent.SuperProperties(
|
||||
appPlatform: .EI,
|
||||
cryptoSDK: .Rust,
|
||||
cryptoSDKVersion: session.crypto.version
|
||||
)
|
||||
)
|
||||
self.service = nil
|
||||
case .failure:
|
||||
MXLog.error("[Analytics] Failed to use analytics settings. Will continue to run without analytics ID.")
|
||||
@@ -275,7 +285,9 @@ extension Analytics {
|
||||
let userProperties = AnalyticsEvent.UserProperties(allChatsActiveFilter: allChatsActiveFilter?.analyticsName,
|
||||
ftueUseCaseSelection: ftueUseCase?.analyticsName,
|
||||
numFavouriteRooms: numFavouriteRooms,
|
||||
numSpaces: numSpaces)
|
||||
numSpaces: numSpaces,
|
||||
recoveryState: nil,
|
||||
verificationState: nil)
|
||||
#if POSTHOG
|
||||
client.updateUserProperties(userProperties)
|
||||
#endif
|
||||
|
||||
@@ -53,4 +53,11 @@ protocol AnalyticsClientProtocol {
|
||||
/// be a delay when updating user properties as these are cached to be included
|
||||
/// as part of the next event that gets captured.
|
||||
func updateUserProperties(_ userProperties: AnalyticsEvent.UserProperties)
|
||||
|
||||
|
||||
/// Updates the super properties.
|
||||
/// Super properties added to all captured events and screen.
|
||||
/// - Parameter superProperties: The properties event to capture.
|
||||
func updateSuperProperties(_ event: AnalyticsEvent.SuperProperties)
|
||||
|
||||
}
|
||||
|
||||
@@ -21,24 +21,34 @@ extension DecryptionFailure {
|
||||
|
||||
public func toAnalyticsEvent() -> AnalyticsEvent.Error {
|
||||
|
||||
let timeToDecryptMillis: Int = if self.timeToDecrypt != nil {
|
||||
Int(self.timeToDecrypt! * 1000)
|
||||
let timeToDecryptMillis: Int = if let ttd = self.timeToDecrypt {
|
||||
Int(ttd * 1000)
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
|
||||
let isHistoricalEvent = if let localAge = self.eventLocalAgeMillis {
|
||||
localAge < 0
|
||||
} else { false }
|
||||
|
||||
let errorName = if isHistoricalEvent && self.trustOwnIdentityAtTimeOfFailure == false {
|
||||
AnalyticsEvent.Error.Name.HistoricalMessage
|
||||
} else {
|
||||
self.reason.errorName
|
||||
}
|
||||
|
||||
return AnalyticsEvent.Error(
|
||||
context: self.context,
|
||||
cryptoModule: .Rust,
|
||||
cryptoSDK: .Rust,
|
||||
domain: .E2EE,
|
||||
|
||||
eventLocalAgeMillis: nil,
|
||||
isFederated: nil,
|
||||
isMatrixDotOrg: nil,
|
||||
name: self.reason.errorName,
|
||||
eventLocalAgeMillis: self.eventLocalAgeMillis,
|
||||
isFederated: self.isFederated,
|
||||
isMatrixDotOrg: self.isMatrixOrg,
|
||||
name: errorName,
|
||||
timeToDecryptMillis: timeToDecryptMillis,
|
||||
userTrustsOwnIdentity: nil,
|
||||
wasVisibleToUser: nil
|
||||
userTrustsOwnIdentity: self.trustOwnIdentityAtTimeOfFailure,
|
||||
wasVisibleToUser: self.wasVisibleToUser
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,13 +52,27 @@ import AnalyticsEvents
|
||||
|
||||
/// UTDs can be permanent or temporary. If temporary, this field will contain the time it took to decrypt the message in milliseconds. If permanent should be nil
|
||||
var timeToDecrypt: TimeInterval?
|
||||
|
||||
init(failedEventId: String, reason: DecryptionFailureReason, context: String, errorCode: NSInteger, deviceCount: NSInteger, unspecifiedErrorMessage: String?) {
|
||||
|
||||
/// Was the current cross-signing identity trusted at the time of decryption
|
||||
var trustOwnIdentityAtTimeOfFailure: Bool?
|
||||
|
||||
var eventLocalAgeMillis: Int?
|
||||
|
||||
/// Is the current user on matrix org
|
||||
var isMatrixOrg: Bool?
|
||||
/// Are the sender and recipient on the same homeserver
|
||||
var isFederated: Bool?
|
||||
|
||||
/// As for now the ios App only reports UTDs visible to user (error are reported from EventFormatter
|
||||
var wasVisibleToUser: Bool = true
|
||||
|
||||
init(failedEventId: String, reason: DecryptionFailureReason, context: String, errorCode: NSInteger, deviceCount: NSInteger, unspecifiedErrorMessage: String?, ts: TimeInterval) {
|
||||
self.failedEventId = failedEventId
|
||||
self.reason = reason
|
||||
self.context = context
|
||||
self.errorCode = errorCode
|
||||
self.deviceCount = deviceCount
|
||||
self.unspecifiedErrorMessage = unspecifiedErrorMessage
|
||||
self.ts = ts
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,14 +63,14 @@ class DecryptionFailureTracker: NSObject {
|
||||
selector: #selector(eventDidDecrypt(_:)),
|
||||
name: .mxEventDidDecrypt,
|
||||
object: nil)
|
||||
|
||||
}
|
||||
|
||||
@objc
|
||||
func reportUnableToDecryptError(forEvent event: MXEvent, withRoomState roomState: MXRoomState, myUser userId: String) {
|
||||
func reportUnableToDecryptError(forEvent event: MXEvent, withRoomState roomState: MXRoomState, mySession: MXSession) {
|
||||
if reportedFailures[event.eventId] != nil || trackedEvents.contains(event.eventId) {
|
||||
return
|
||||
}
|
||||
guard let userId = mySession.myUserId else { return }
|
||||
|
||||
// Filter out "expected" UTDs
|
||||
// We cannot decrypt messages sent before the user joined the room
|
||||
@@ -83,6 +83,12 @@ class DecryptionFailureTracker: NSObject {
|
||||
|
||||
guard let error = event.decryptionError as? NSError else { return }
|
||||
|
||||
let eventOrigin = event.originServerTs
|
||||
let deviceTimestamp = mySession.crypto.deviceCreationTs
|
||||
// If negative it's an historical event relative to the current session
|
||||
let eventRelativeAgeMillis = Int(eventOrigin) - Int(deviceTimestamp)
|
||||
let isSessionVerified = mySession.crypto.crossSigning.canTrustCrossSigning
|
||||
|
||||
var reason = DecryptionFailureReason.unspecified
|
||||
|
||||
if error.code == MXDecryptingErrorUnknownInboundSessionIdCode.rawValue {
|
||||
@@ -93,7 +99,24 @@ class DecryptionFailureTracker: NSObject {
|
||||
|
||||
let context = String(format: "code: %ld, description: %@", error.code, event.decryptionError.localizedDescription)
|
||||
|
||||
reportedFailures[failedEventId] = DecryptionFailure(failedEventId: failedEventId, reason: reason, context: context, ts: self.timeProvider.nowTs())
|
||||
let failure = DecryptionFailure(failedEventId: failedEventId, reason: reason, context: context, ts: self.timeProvider.nowTs())
|
||||
|
||||
failure.eventLocalAgeMillis = Int(exactly: eventRelativeAgeMillis)
|
||||
failure.trustOwnIdentityAtTimeOfFailure = isSessionVerified
|
||||
|
||||
let myDomain = userId.components(separatedBy: ":").last
|
||||
failure.isMatrixOrg = myDomain == "matrix.org"
|
||||
|
||||
if MXTools.isMatrixUserIdentifier(event.sender) {
|
||||
let senderDomain = event.sender.components(separatedBy: ":").last
|
||||
failure.isFederated = senderDomain != nil && senderDomain != myDomain
|
||||
}
|
||||
|
||||
/// XXX for future work, as for now only the event formatter reports UTDs. That means that it's only UTD ~visible to users
|
||||
failure.wasVisibleToUser = true
|
||||
|
||||
reportedFailures[failedEventId] = failure
|
||||
|
||||
|
||||
// Start the ticker if needed. There is no need to have a ticker if no failures are tracked
|
||||
if checkFailuresTimer == nil {
|
||||
|
||||
@@ -16,15 +16,20 @@
|
||||
|
||||
#if POSTHOG
|
||||
import PostHog
|
||||
|
||||
extension PHGPostHogConfiguration {
|
||||
static var standard: PHGPostHogConfiguration? {
|
||||
// BWI: #6076 need to be checked after merge
|
||||
// extension PHGPostHogConfiguration {
|
||||
// static var standard: PHGPostHogConfiguration? {
|
||||
// let analyticsConfiguration = BWIBuildSettings.shared.analyticsConfiguration
|
||||
extension PostHogConfig {
|
||||
static var standard: PostHogConfig? {
|
||||
let analyticsConfiguration = BWIBuildSettings.shared.analyticsConfiguration
|
||||
guard analyticsConfiguration.isEnabled else { return nil }
|
||||
|
||||
let postHogConfiguration = PHGPostHogConfiguration(apiKey: analyticsConfiguration.apiKey, host: analyticsConfiguration.host)
|
||||
postHogConfiguration.shouldSendDeviceID = false
|
||||
|
||||
let postHogConfiguration = PostHogConfig(apiKey: analyticsConfiguration.apiKey, host: analyticsConfiguration.host)
|
||||
// We capture screens manually
|
||||
postHogConfiguration.captureScreenViews = false
|
||||
|
||||
|
||||
return postHogConfiguration
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,31 +20,48 @@ import AnalyticsEvents
|
||||
|
||||
/// An analytics client that reports events to a PostHog server.
|
||||
class PostHogAnalyticsClient: AnalyticsClientProtocol {
|
||||
|
||||
private var posthogFactory: PostHogFactory = DefaultPostHogFactory()
|
||||
|
||||
init(posthogFactory: PostHogFactory? = nil) {
|
||||
if let factory = posthogFactory {
|
||||
self.posthogFactory = factory
|
||||
}
|
||||
}
|
||||
|
||||
/// The PHGPostHog object used to report events.
|
||||
private var postHog: PHGPostHog?
|
||||
private var postHog: PostHogProtocol?
|
||||
|
||||
/// Any user properties to be included with the next captured event.
|
||||
private(set) var pendingUserProperties: AnalyticsEvent.UserProperties?
|
||||
|
||||
/// Super Properties are properties associated with events that are set once and then sent with every capture call, be it a $screen, an autocaptured button click, or anything else.
|
||||
/// It is different from user properties that will be attached to the user and not events.
|
||||
/// Not persisted for now, should be set on start.
|
||||
private var superProperties: AnalyticsEvent.SuperProperties?
|
||||
|
||||
static let shared = PostHogAnalyticsClient()
|
||||
|
||||
var isRunning: Bool { postHog?.enabled ?? false }
|
||||
var isRunning: Bool {
|
||||
guard let postHog else { return false }
|
||||
return !postHog.isOptOut()
|
||||
}
|
||||
|
||||
func start() {
|
||||
// Only start if analytics have been configured in BuildSettings
|
||||
guard let configuration = PHGPostHogConfiguration.standard else { return }
|
||||
guard let configuration = PostHogConfig.standard else { return }
|
||||
|
||||
if postHog == nil {
|
||||
postHog = PHGPostHog(configuration: configuration)
|
||||
postHog = posthogFactory.createPostHog(config: configuration)
|
||||
}
|
||||
|
||||
postHog?.enable()
|
||||
postHog?.optIn()
|
||||
}
|
||||
|
||||
func identify(id: String) {
|
||||
if let userProperties = pendingUserProperties {
|
||||
// As user properties overwrite old ones, compactMap the dictionary to avoid resetting any missing properties
|
||||
postHog?.identify(id, properties: userProperties.properties.compactMapValues { $0 })
|
||||
postHog?.identify(id, userProperties: userProperties.properties.compactMapValues { $0 })
|
||||
pendingUserProperties = nil
|
||||
} else {
|
||||
postHog?.identify(id)
|
||||
@@ -57,10 +74,9 @@ class PostHogAnalyticsClient: AnalyticsClientProtocol {
|
||||
}
|
||||
|
||||
func stop() {
|
||||
postHog?.disable()
|
||||
postHog?.optOut()
|
||||
|
||||
// As of PostHog 1.4.4, setting the client to nil here doesn't release
|
||||
// it. Keep it around to avoid having multiple instances if the user re-enables
|
||||
self.postHog = nil
|
||||
}
|
||||
|
||||
func flush() {
|
||||
@@ -68,11 +84,13 @@ class PostHogAnalyticsClient: AnalyticsClientProtocol {
|
||||
}
|
||||
|
||||
func capture(_ event: AnalyticsEventProtocol) {
|
||||
postHog?.capture(event.eventName, properties: attachUserProperties(to: event.properties))
|
||||
postHog?.capture(event.eventName, properties: attachSuperProperties(to: event.properties), userProperties: pendingUserProperties?.properties.compactMapValues { $0 })
|
||||
// Pending user properties have been added
|
||||
self.pendingUserProperties = nil
|
||||
}
|
||||
|
||||
func screen(_ event: AnalyticsScreenProtocol) {
|
||||
postHog?.screen(event.screenName.rawValue, properties: attachUserProperties(to: event.properties))
|
||||
postHog?.screen(event.screenName.rawValue, properties: attachSuperProperties(to: event.properties))
|
||||
}
|
||||
|
||||
func updateUserProperties(_ userProperties: AnalyticsEvent.UserProperties) {
|
||||
@@ -85,25 +103,35 @@ class PostHogAnalyticsClient: AnalyticsClientProtocol {
|
||||
self.pendingUserProperties = AnalyticsEvent.UserProperties(allChatsActiveFilter: userProperties.allChatsActiveFilter ?? pendingUserProperties.allChatsActiveFilter,
|
||||
ftueUseCaseSelection: userProperties.ftueUseCaseSelection ?? pendingUserProperties.ftueUseCaseSelection,
|
||||
numFavouriteRooms: userProperties.numFavouriteRooms ?? pendingUserProperties.numFavouriteRooms,
|
||||
numSpaces: userProperties.numSpaces ?? pendingUserProperties.numSpaces)
|
||||
numSpaces: userProperties.numSpaces ?? pendingUserProperties.numSpaces,
|
||||
// Not yet supported
|
||||
recoveryState: nil, verificationState: nil)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
func updateSuperProperties(_ updatedProperties: AnalyticsEvent.SuperProperties) {
|
||||
self.superProperties = AnalyticsEvent.SuperProperties(
|
||||
appPlatform: updatedProperties.appPlatform ?? superProperties?.appPlatform,
|
||||
cryptoSDK: updatedProperties.cryptoSDK ?? superProperties?.cryptoSDK,
|
||||
cryptoSDKVersion: updatedProperties.cryptoSDKVersion ?? superProperties?.cryptoSDKVersion
|
||||
)
|
||||
}
|
||||
|
||||
/// Given a dictionary containing properties from an event, this method will return those properties
|
||||
/// with any pending user properties included under the `$set` key.
|
||||
/// - Parameter properties: A dictionary of properties from an event.
|
||||
/// - Returns: The `properties` dictionary with any user properties included.
|
||||
private func attachUserProperties(to properties: [String: Any]) -> [String: Any] {
|
||||
guard isRunning, let userProperties = pendingUserProperties else { return properties }
|
||||
/// Attach super properties to events.
|
||||
/// If the property is already set on the event, the already set value will be kept.
|
||||
private func attachSuperProperties(to properties: [String: Any]) -> [String: Any] {
|
||||
guard isRunning, let superProperties else { return properties }
|
||||
|
||||
var properties = properties
|
||||
|
||||
// As user properties overwrite old ones via $set, compactMap the dictionary to avoid resetting any missing properties
|
||||
properties["$set"] = userProperties.properties.compactMapValues { $0 }
|
||||
pendingUserProperties = nil
|
||||
superProperties.properties.forEach { (key: String, value: Any) in
|
||||
if properties[key] == nil {
|
||||
properties[key] = value
|
||||
}
|
||||
}
|
||||
return properties
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension PostHogAnalyticsClient: RemoteFeaturesClientProtocol {
|
||||
|
||||
53
Riot/Modules/Analytics/PosthogProtocol.swift
Normal file
53
Riot/Modules/Analytics/PosthogProtocol.swift
Normal file
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// Copyright 2024 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 PostHog
|
||||
|
||||
protocol PostHogProtocol {
|
||||
func optIn()
|
||||
|
||||
func optOut()
|
||||
|
||||
func reset()
|
||||
|
||||
func flush()
|
||||
|
||||
func capture(_ event: String, properties: [String: Any]?, userProperties: [String: Any]?)
|
||||
|
||||
func screen(_ screenTitle: String, properties: [String: Any]?)
|
||||
|
||||
func isFeatureEnabled(_ feature: String) -> Bool
|
||||
|
||||
func identify(_ distinctId: String)
|
||||
|
||||
func identify(_ distinctId: String, userProperties: [String: Any]?)
|
||||
|
||||
func isOptOut() -> Bool
|
||||
}
|
||||
|
||||
protocol PostHogFactory {
|
||||
func createPostHog(config: PostHogConfig) -> PostHogProtocol
|
||||
}
|
||||
|
||||
class DefaultPostHogFactory: PostHogFactory {
|
||||
func createPostHog(config: PostHogConfig) -> PostHogProtocol {
|
||||
PostHogSDK.shared.setup(config)
|
||||
return PostHogSDK.shared
|
||||
}
|
||||
}
|
||||
|
||||
extension PostHogSDK: PostHogProtocol { }
|
||||
@@ -1908,6 +1908,9 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
||||
[self registerNewRequestNotificationForSession:mxSession];
|
||||
|
||||
[self.pushNotificationService checkPushKitPushersInSession:mxSession];
|
||||
|
||||
// Clean the storage by removing expired data
|
||||
[mxSession removeExpiredMessages];
|
||||
}
|
||||
else if (mxSession.state == MXSessionStateRunning)
|
||||
{
|
||||
|
||||
@@ -512,7 +512,6 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou
|
||||
if (!_publicRoomsDirectoryDataSource)
|
||||
{
|
||||
_publicRoomsDirectoryDataSource = [[PublicRoomsDirectoryDataSource alloc] initWithMatrixSession:mxSession];
|
||||
_publicRoomsDirectoryDataSource.showNSFWRooms = RiotSettings.shared.showNSFWPublicRooms;
|
||||
_publicRoomsDirectoryDataSource.delegate = self;
|
||||
}
|
||||
|
||||
|
||||
@@ -147,8 +147,6 @@
|
||||
|
||||
// Reset searches
|
||||
[recentsDataSource searchWithPatterns:nil];
|
||||
// TODO: Notify RiotSettings.shared.showNSFWPublicRooms change for iPad as viewWillAppear may not be called
|
||||
recentsDataSource.publicRoomsDirectoryDataSource.showNSFWRooms = RiotSettings.shared.showNSFWPublicRooms;
|
||||
|
||||
[self updateSearch];
|
||||
|
||||
|
||||
@@ -45,11 +45,6 @@
|
||||
*/
|
||||
@property (nonatomic) BOOL includeAllNetworks;
|
||||
|
||||
/**
|
||||
Flag to indicate to show Not Safe For Work rooms in the public room list.
|
||||
*/
|
||||
@property (nonatomic) BOOL showNSFWRooms;
|
||||
|
||||
/**
|
||||
List public rooms from a third party protocol.
|
||||
Default is nil.
|
||||
|
||||
@@ -46,6 +46,8 @@ static NSString *const kNSFWKeyword = @"nsfw";
|
||||
NSString *nextBatch;
|
||||
}
|
||||
|
||||
@property (nonatomic, strong) NSRegularExpression *forbiddenTermsRegex;
|
||||
|
||||
@end
|
||||
|
||||
@implementation PublicRoomsDirectoryDataSource
|
||||
@@ -57,6 +59,15 @@ static NSString *const kNSFWKeyword = @"nsfw";
|
||||
{
|
||||
rooms = [NSMutableArray array];
|
||||
_paginationLimit = 20;
|
||||
|
||||
NSString *path = [[NSBundle mainBundle] pathForResource:@"forbidden_terms" ofType:@"txt"];
|
||||
NSString *fileContents = [NSString stringWithContentsOfFile:path encoding: NSUTF8StringEncoding error:nil];
|
||||
NSArray *forbiddenTerms = [fileContents componentsSeparatedByCharactersInSet: NSCharacterSet.whitespaceAndNewlineCharacterSet];
|
||||
|
||||
NSString *pattern = [NSString stringWithFormat:@"\\b(%@)\\b", [forbiddenTerms componentsJoinedByString:@"|"]];
|
||||
pattern = [pattern stringByAppendingString:@"|(\\b18\\+)"]; // Special case "18+"
|
||||
|
||||
_forbiddenTermsRegex = [[NSRegularExpression alloc] initWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:nil];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -155,16 +166,6 @@ static NSString *const kNSFWKeyword = @"nsfw";
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setShowNSFWRooms:(BOOL)showNSFWRooms
|
||||
{
|
||||
if (showNSFWRooms != _showNSFWRooms)
|
||||
{
|
||||
_showNSFWRooms = showNSFWRooms;
|
||||
|
||||
[self resetPagination];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSUInteger)roomsCount
|
||||
{
|
||||
return rooms.count;
|
||||
@@ -254,14 +255,7 @@ static NSString *const kNSFWKeyword = @"nsfw";
|
||||
|
||||
NSArray<MXPublicRoom*> *publicRooms;
|
||||
|
||||
if (self.showNSFWRooms)
|
||||
{
|
||||
publicRooms = publicRoomsResponse.chunk;
|
||||
}
|
||||
else
|
||||
{
|
||||
publicRooms = [self filterPublicRooms:publicRoomsResponse.chunk containingKeyword:kNSFWKeyword];
|
||||
}
|
||||
publicRooms = [self filterPublicRooms:publicRoomsResponse.chunk];
|
||||
|
||||
[self->rooms addObjectsFromArray:publicRooms];
|
||||
self->nextBatch = publicRoomsResponse.nextBatch;
|
||||
@@ -338,15 +332,23 @@ static NSString *const kNSFWKeyword = @"nsfw";
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray<MXPublicRoom*>*)filterPublicRooms:(NSArray<MXPublicRoom*>*)publicRooms containingKeyword:(NSString*)keyword
|
||||
- (NSArray<MXPublicRoom*>*)filterPublicRooms:(NSArray<MXPublicRoom*>*)publicRooms
|
||||
{
|
||||
NSMutableArray *filteredRooms = [NSMutableArray new];
|
||||
|
||||
for (MXPublicRoom *publicRoom in publicRooms)
|
||||
{
|
||||
if (NO == [[publicRoom.name lowercaseString] containsString:keyword]
|
||||
&& NO == [[publicRoom.topic lowercaseString] containsString:keyword])
|
||||
{
|
||||
BOOL shouldAllow = YES;
|
||||
|
||||
if (publicRoom.name != nil) {
|
||||
shouldAllow &= [self.forbiddenTermsRegex numberOfMatchesInString:publicRoom.name options:0 range:NSMakeRange(0, publicRoom.name.length)] == 0;
|
||||
}
|
||||
|
||||
if (publicRoom.topic != nil) {
|
||||
shouldAllow &= [self.forbiddenTermsRegex numberOfMatchesInString:publicRoom.topic options:0 range:NSMakeRange(0, publicRoom.topic.length)] == 0;
|
||||
}
|
||||
|
||||
if (shouldAllow) {
|
||||
[filteredRooms addObject:publicRoom];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,10 @@ enum RendezvousFlow: String {
|
||||
case SETUP_ADDITIONAL_DEVICE_V2 = "org.matrix.msc3906.setup.additional_device.v2"
|
||||
}
|
||||
|
||||
// n.b MSC3886/MSC3903/MSC3906 that this is based on are now closed.
|
||||
// However, we want to keep this implementation around for some time.
|
||||
// TODO: define an end-of-life date for this implementation.
|
||||
|
||||
/// Allows communication through a secure channel. Based on MSC3886 and MSC3903
|
||||
@MainActor
|
||||
class RendezvousService {
|
||||
|
||||
@@ -107,6 +107,8 @@
|
||||
|
||||
@property (nonatomic) BOOL customAnimationsEnabled;
|
||||
|
||||
@property (nonatomic) BOOL isLoadingVideo;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MXKAttachmentsViewController
|
||||
@@ -976,8 +978,10 @@
|
||||
navigationBarDisplayTimer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(hideNavigationBar) userInfo:self repeats:NO];
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (!self.isLoadingVideo)
|
||||
{
|
||||
self.isLoadingVideo = YES;
|
||||
|
||||
MXKPieChartView *pieChartView = [[MXKPieChartView alloc] initWithFrame:CGRectMake(0, 0, 40, 40)];
|
||||
pieChartView.progress = 0;
|
||||
pieChartView.progressColor = [UIColor colorWithRed:1 green:1 blue:1 alpha:0.25];
|
||||
@@ -1027,6 +1031,7 @@
|
||||
[selectedCell.moviePlayer.player play];
|
||||
|
||||
[pieChartView removeFromSuperview];
|
||||
self.isLoadingVideo = NO;
|
||||
|
||||
[self hideNavigationBar];
|
||||
}
|
||||
@@ -1042,6 +1047,7 @@
|
||||
MXLogDebug(@"[MXKAttachmentsVC] video download failed");
|
||||
|
||||
[pieChartView removeFromSuperview];
|
||||
self.isLoadingVideo = NO;
|
||||
|
||||
// Display the navigation bar so that the user can leave this screen
|
||||
self.navigationBarContainer.hidden = NO;
|
||||
|
||||
@@ -34,6 +34,9 @@ const CGFloat kTypingCellHeight = 24;
|
||||
{
|
||||
// Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change.
|
||||
id kThemeServiceDidChangeThemeNotificationObserver;
|
||||
|
||||
// The listener to the room retention changes.
|
||||
id retentionListener;
|
||||
}
|
||||
|
||||
// Observe key verification request changes
|
||||
@@ -168,6 +171,31 @@ const CGFloat kTypingCellHeight = 24;
|
||||
self.eventFormatter.eventTypesFilterForMessages = [MXKAppSettings standardAppSettings].eventsFilterForMessages;
|
||||
}
|
||||
|
||||
- (void)setDelegate:(id<MXKDataSourceDelegate>)delegate
|
||||
{
|
||||
[self unregisterRoomSummaryDidRemoveExpiredDataFromStoreNotifications];
|
||||
[self removeRoomRetentionEventListener];
|
||||
|
||||
if (delegate && self.isLive)
|
||||
{
|
||||
if (self.room)
|
||||
{
|
||||
// Remove the potential expired messages from the store
|
||||
if ([self.room.summary removeExpiredRoomContentsFromStore])
|
||||
{
|
||||
[self.mxSession.store commit];
|
||||
}
|
||||
[self addRoomRetentionEventListener];
|
||||
}
|
||||
|
||||
// Observe room history flush (expired content data)
|
||||
[self registerRoomSummaryDidRemoveExpiredDataFromStoreNotifications];
|
||||
[self roomSummaryDidRemoveExpiredDataFromStore];
|
||||
}
|
||||
|
||||
[super setDelegate:delegate];
|
||||
}
|
||||
|
||||
- (void)destroy
|
||||
{
|
||||
if (kThemeServiceDidChangeThemeNotificationObserver)
|
||||
@@ -198,6 +226,9 @@ const CGFloat kTypingCellHeight = 24;
|
||||
[self.mxSession.aggregations.beaconAggregations removeListener:self.beaconInfoSummaryDeletionListener];
|
||||
}
|
||||
|
||||
[self unregisterRoomSummaryDidRemoveExpiredDataFromStoreNotifications];
|
||||
[self removeRoomRetentionEventListener];
|
||||
|
||||
[super destroy];
|
||||
}
|
||||
|
||||
@@ -1284,4 +1315,79 @@ const CGFloat kTypingCellHeight = 24;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - roomSummaryDidRemoveExpiredDataFromStore notifications
|
||||
|
||||
- (void)registerRoomSummaryDidRemoveExpiredDataFromStoreNotifications
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(roomSummaryDidRemoveExpiredDataFromStore:) name:MXRoomSummary.roomSummaryDidRemoveExpiredDataFromStore object:nil];
|
||||
}
|
||||
|
||||
- (void)unregisterRoomSummaryDidRemoveExpiredDataFromStoreNotifications
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:MXRoomSummary.roomSummaryDidRemoveExpiredDataFromStore object:nil];
|
||||
}
|
||||
|
||||
- (void)roomSummaryDidRemoveExpiredDataFromStore:(NSNotification*)notification
|
||||
{
|
||||
MXRoomSummary *roomSummary = notification.object;
|
||||
if (self.mxSession == roomSummary.mxSession && [self.roomId isEqualToString:roomSummary.roomId])
|
||||
{
|
||||
[self roomSummaryDidRemoveExpiredDataFromStore];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)roomSummaryDidRemoveExpiredDataFromStore
|
||||
{
|
||||
// Check whether the first cell data refers to an expired event (this may be a state event
|
||||
MXEvent *firstMessageEvent;
|
||||
for (id<MXKRoomBubbleCellDataStoring> cellData in bubbles)
|
||||
{
|
||||
for (MXEvent *event in cellData.events)
|
||||
{
|
||||
if (!event.isState) {
|
||||
firstMessageEvent = event;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstMessageEvent)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstMessageEvent && firstMessageEvent.originServerTs < self.room.summary.minimumTimestamp)
|
||||
{
|
||||
[self reload];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - room retention event listener
|
||||
|
||||
- (void)addRoomRetentionEventListener
|
||||
{
|
||||
// Register a listener to handle the room retention in live timelines
|
||||
retentionListener = [self.timeline listenToEventsOfTypes:@[MXRoomSummary.roomRetentionStateEventType] onEvent:^(MXEvent *redactionEvent, MXTimelineDirection direction, MXRoomState *roomState) {
|
||||
|
||||
// Consider only live events
|
||||
if (direction == MXTimelineDirectionForwards)
|
||||
{
|
||||
// Remove the potential expired messages from the store
|
||||
if ([self.room.summary removeExpiredRoomContentsFromStore])
|
||||
{
|
||||
[self.mxSession.store commit];
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)removeRoomRetentionEventListener
|
||||
{
|
||||
if (retentionListener)
|
||||
{
|
||||
[self.timeline removeListener:retentionListener];
|
||||
retentionListener = nil;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -237,6 +237,9 @@ extension RoomInfoCoordinator: RoomInfoListCoordinatorDelegate {
|
||||
self.delegate?.roomInfoCoordinatorDidLeaveRoom(self)
|
||||
}
|
||||
|
||||
func roomInfoListCoordinatorDidRequestReportRoom(_ coordinator: RoomInfoListCoordinatorType) {
|
||||
self.delegate?.roomInfoCoordinatorDidRequestReportRoom(self)
|
||||
}
|
||||
}
|
||||
|
||||
extension RoomInfoCoordinator: RoomParticipantsViewControllerDelegate {
|
||||
|
||||
@@ -25,6 +25,7 @@ import MatrixSDK
|
||||
func roomInfoCoordinatorBridgePresenterDelegateDidLeaveRoom(_ coordinatorBridgePresenter: RoomInfoCoordinatorBridgePresenter)
|
||||
func roomInfoCoordinatorBridgePresenter(_ coordinatorBridgePresenter: RoomInfoCoordinatorBridgePresenter, didReplaceRoomWithReplacementId roomId: String)
|
||||
func roomInfoCoordinatorBridgePresenter(_ coordinator: RoomInfoCoordinatorBridgePresenter, viewEventInTimeline event: MXEvent)
|
||||
func roomInfoCoordinatorBridgePresenterDidRequestReportRoom(_ coordinatorBridgePresenter: RoomInfoCoordinatorBridgePresenter)
|
||||
}
|
||||
|
||||
/// RoomInfoCoordinatorBridgePresenter enables to start RoomInfoCoordinator from a view controller.
|
||||
@@ -131,9 +132,14 @@ extension RoomInfoCoordinatorBridgePresenter: RoomInfoCoordinatorDelegate {
|
||||
func roomInfoCoordinator(_ coordinator: RoomInfoCoordinatorType, didReplaceRoomWithReplacementId roomId: String) {
|
||||
self.delegate?.roomInfoCoordinatorBridgePresenter(self, didReplaceRoomWithReplacementId: roomId)
|
||||
}
|
||||
|
||||
func roomInfoCoordinator(_ coordinator: RoomInfoCoordinatorType, viewEventInTimeline event: MXEvent) {
|
||||
self.delegate?.roomInfoCoordinatorBridgePresenter(self, viewEventInTimeline: event)
|
||||
}
|
||||
|
||||
func roomInfoCoordinatorDidRequestReportRoom(_ coordinator: RoomInfoCoordinatorType) {
|
||||
self.delegate?.roomInfoCoordinatorBridgePresenterDidRequestReportRoom(self)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UIAdaptivePresentationControllerDelegate
|
||||
|
||||
@@ -25,6 +25,7 @@ protocol RoomInfoCoordinatorDelegate: AnyObject {
|
||||
func roomInfoCoordinatorDidLeaveRoom(_ coordinator: RoomInfoCoordinatorType)
|
||||
func roomInfoCoordinator(_ coordinator: RoomInfoCoordinatorType, didReplaceRoomWithReplacementId roomId: String)
|
||||
func roomInfoCoordinator(_ coordinator: RoomInfoCoordinatorType, viewEventInTimeline event: MXEvent)
|
||||
func roomInfoCoordinatorDidRequestReportRoom(_ coordinator: RoomInfoCoordinatorType)
|
||||
}
|
||||
|
||||
/// `RoomInfoCoordinatorType` is a protocol describing a Coordinator that handle keybackup setup navigation flow.
|
||||
|
||||
@@ -75,4 +75,7 @@ extension RoomInfoListCoordinator: RoomInfoListViewModelCoordinatorDelegate {
|
||||
self.delegate?.roomInfoListCoordinatorDidLeaveRoom(self)
|
||||
}
|
||||
|
||||
func roomInfoListViewModelDidRequestReportRoom(_ viewModel: RoomInfoListViewModelType) {
|
||||
self.delegate?.roomInfoListCoordinatorDidRequestReportRoom(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ protocol RoomInfoListCoordinatorDelegate: AnyObject {
|
||||
func roomInfoListCoordinator(_ coordinator: RoomInfoListCoordinatorType, wantsToNavigateTo target: RoomInfoListTarget)
|
||||
func roomInfoListCoordinatorDidCancel(_ coordinator: RoomInfoListCoordinatorType)
|
||||
func roomInfoListCoordinatorDidLeaveRoom(_ coordinator: RoomInfoListCoordinatorType)
|
||||
func roomInfoListCoordinatorDidRequestReportRoom(_ coordinator: RoomInfoListCoordinatorType)
|
||||
}
|
||||
|
||||
/// `RoomInfoListCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow.
|
||||
|
||||
@@ -52,4 +52,5 @@ enum RoomInfoListViewAction {
|
||||
case leave
|
||||
case cancel
|
||||
case permalink
|
||||
case report
|
||||
}
|
||||
|
||||
@@ -271,8 +271,16 @@ final class RoomInfoListViewController: UIViewController {
|
||||
rows: [rowLeave],
|
||||
footer: nil)
|
||||
|
||||
let rowReport = Row(type: .destructive, icon: Asset.Images.error.image, text: VectorL10n.roomEventActionReport, accessoryType: .disclosureIndicator) {
|
||||
self.viewModel.process(viewAction: .report)
|
||||
}
|
||||
let sectionReport = Section(header: nil,
|
||||
rows: [rowReport],
|
||||
footer: nil)
|
||||
|
||||
tmpSections.append(sectionSettings)
|
||||
tmpSections.append(sectionLeave)
|
||||
tmpSections.append(sectionReport)
|
||||
|
||||
sections = tmpSections
|
||||
}
|
||||
|
||||
@@ -118,6 +118,8 @@ final class RoomInfoListViewModel: NSObject, RoomInfoListViewModelType {
|
||||
self.coordinatorDelegate?.roomInfoListViewModelDidCancel(self)
|
||||
case .permalink:
|
||||
self.createPermalink()
|
||||
case .report:
|
||||
self.coordinatorDelegate?.roomInfoListViewModelDidRequestReportRoom(self)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ protocol RoomInfoListViewModelCoordinatorDelegate: AnyObject {
|
||||
func roomInfoListViewModelDidCancel(_ viewModel: RoomInfoListViewModelType)
|
||||
func roomInfoListViewModelDidLeaveRoom(_ viewModel: RoomInfoListViewModelType)
|
||||
func roomInfoListViewModel(_ viewModel: RoomInfoListViewModelType, wantsToNavigateTo target: RoomInfoListTarget)
|
||||
func roomInfoListViewModelDidRequestReportRoom(_ viewModel: RoomInfoListViewModelType)
|
||||
}
|
||||
|
||||
/// Protocol describing the view model used by `RoomInfoListViewController`
|
||||
|
||||
@@ -5495,6 +5495,50 @@ static CGSize kThreadListBarButtonItemImageSize;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleReportRoom
|
||||
{
|
||||
// Prompt user to enter a description of the problem content.
|
||||
UIAlertController *reportReasonAlert = [UIAlertController alertControllerWithTitle:[VectorL10n roomActionReportPromptReason]
|
||||
message:nil
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
|
||||
[reportReasonAlert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
|
||||
textField.secureTextEntry = NO;
|
||||
textField.placeholder = nil;
|
||||
textField.keyboardType = UIKeyboardTypeDefault;
|
||||
}];
|
||||
|
||||
MXWeakify(self);
|
||||
[reportReasonAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n ok] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
|
||||
NSString *text = [self->currentAlert textFields].firstObject.text;
|
||||
self->currentAlert = nil;
|
||||
|
||||
[self startActivityIndicator];
|
||||
|
||||
[self.roomDataSource.mxSession.matrixRestClient reportRoom:self.roomDataSource.roomId reason:text success:^{
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
[self stopActivityIndicator];
|
||||
} failure:^(NSError *error) {
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
[self stopActivityIndicator];
|
||||
|
||||
MXLogDebug(@"[RoomVC] Report room (%@) failed", self.roomDataSource.roomId);
|
||||
//Alert user
|
||||
[self showError:error];
|
||||
}];
|
||||
}]];
|
||||
|
||||
[reportReasonAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel] style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) {
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
self->currentAlert = nil;
|
||||
}]];
|
||||
|
||||
[self presentViewController:reportReasonAlert animated:YES completion:nil];
|
||||
self->currentAlert = reportReasonAlert;
|
||||
}
|
||||
|
||||
#pragma mark - UITableViewDelegate
|
||||
|
||||
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
@@ -5740,6 +5784,10 @@ static CGSize kThreadListBarButtonItemImageSize;
|
||||
{
|
||||
[self presentDeclineOptionsFromView:tappedView];
|
||||
}
|
||||
else if (tappedView == previewHeader.reportButton)
|
||||
{
|
||||
[self handleReportRoom];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)presentDeclineOptionsFromView:(UIView *)view
|
||||
@@ -8242,6 +8290,11 @@ static CGSize kThreadListBarButtonItemImageSize;
|
||||
[self reloadRoomWihtEventId:event.eventId threadId:event.threadId forceUpdateRoomMarker:NO];
|
||||
}
|
||||
|
||||
- (void)roomInfoCoordinatorBridgePresenterDidRequestReportRoom:(RoomInfoCoordinatorBridgePresenter *)coordinatorBridgePresenter
|
||||
{
|
||||
[self handleReportRoom];
|
||||
}
|
||||
|
||||
-(void)reloadRoomWihtEventId:(NSString *)eventId
|
||||
threadId:(NSString *)threadId
|
||||
forceUpdateRoomMarker:(BOOL)forceUpdateRoomMarker
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
@property (weak, nonatomic) IBOutlet UIView *buttonsContainer;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *leftButton;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *rightButton;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *reportButton;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *subNoticeLabel;
|
||||
@property (weak, nonatomic) IBOutlet UIView *bottomBorderView;
|
||||
|
||||
|
||||
@@ -51,12 +51,22 @@
|
||||
[self.rightButton setTitle:[BWIL10n join] forState:UIControlStateNormal];
|
||||
[self.rightButton setTitle:[BWIL10n join] forState:UIControlStateHighlighted];
|
||||
|
||||
[self.reportButton setTitle:[VectorL10n roomActionReport] forState:UIControlStateNormal];
|
||||
[self.reportButton setTitle:[VectorL10n roomActionReport] forState:UIControlStateHighlighted];
|
||||
|
||||
tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(reportTapGesture:)];
|
||||
[tap setNumberOfTouchesRequired:1];
|
||||
[tap setNumberOfTapsRequired:1];
|
||||
[tap setDelegate:self];
|
||||
[self.rightButton addGestureRecognizer:tap];
|
||||
self.rightButton.userInteractionEnabled = YES;
|
||||
|
||||
tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(reportTapGesture:)];
|
||||
[tap setNumberOfTouchesRequired:1];
|
||||
[tap setNumberOfTapsRequired:1];
|
||||
[tap setDelegate:self];
|
||||
[self.reportButton addGestureRecognizer:tap];
|
||||
self.reportButton.userInteractionEnabled = YES;
|
||||
}
|
||||
|
||||
-(void)customizeViewRendering
|
||||
@@ -96,6 +106,7 @@
|
||||
{
|
||||
[self.rightButton setTitleColor: ThemeService.shared.theme.backgroundColor forState:UIControlStateNormal];
|
||||
}
|
||||
[self.reportButton setTitleColor:ThemeService.shared.theme.warningColor forState:UIControlStateNormal];
|
||||
}
|
||||
|
||||
- (void)refreshDisplay
|
||||
|
||||
@@ -1,25 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="22505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
|
||||
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
|
||||
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22504"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view contentMode="scaleToFill" id="Cpa-Xt-cwB" customClass="PreviewRoomTitleView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="355"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="386"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="y5s-FK-My4">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="215"/>
|
||||
<color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
|
||||
<color key="backgroundColor" systemColor="groupTableViewBackgroundColor"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="MainHeaderBackground"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="215" id="YtX-K2-uD7"/>
|
||||
@@ -38,7 +34,7 @@
|
||||
</constraints>
|
||||
</view>
|
||||
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="Room Name" textAlignment="center" adjustsFontSizeToFit="NO" minimumFontSize="14" translatesAutoresizingMaskIntoConstraints="NO" id="6uH-I3-RQg">
|
||||
<rect key="frame" x="249" y="126" width="103" height="22"/>
|
||||
<rect key="frame" x="249.5" y="126" width="101" height="23"/>
|
||||
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="DisplayNameTextField"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="18"/>
|
||||
@@ -48,9 +44,9 @@
|
||||
</connections>
|
||||
</textField>
|
||||
<textView opaque="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" editable="NO" text="Room Topic" textAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="Tk0-pA-9a0">
|
||||
<rect key="frame" x="31" y="153" width="538" height="17"/>
|
||||
<rect key="frame" x="31" y="154" width="538" height="16"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="RoomTopic"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<color key="textColor" systemColor="darkTextColor"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<textInputTraits key="textInputTraits"/>
|
||||
</textView>
|
||||
@@ -58,11 +54,11 @@
|
||||
<rect key="frame" x="262" y="184" width="76" height="17"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="RoomMembers"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="14"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<color key="textColor" systemColor="darkTextColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="details_icon" translatesAutoresizingMaskIntoConstraints="NO" id="S3Y-wJ-HOe">
|
||||
<rect key="frame" x="345" y="187" width="6" height="12"/>
|
||||
<rect key="frame" x="345" y="186.5" width="6" height="12"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="RoomMembersDetailsIcon"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="6" id="XTx-6p-2wB"/>
|
||||
@@ -94,17 +90,17 @@
|
||||
</constraints>
|
||||
</view>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="gIX-nY-f6M">
|
||||
<rect key="frame" x="280" y="239" width="41" height="20"/>
|
||||
<rect key="frame" x="279.5" y="239" width="41" height="20"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="PreviewLabel"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="16"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<color key="textColor" systemColor="darkTextColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="WmW-5h-jL1" userLabel="buttonsContainer">
|
||||
<rect key="frame" x="60" y="276" width="480" height="30"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="osd-JP-2az" userLabel="Left Button">
|
||||
<rect key="frame" x="0.0" y="0.0" width="230" height="30"/>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="osd-JP-2az" userLabel="Left Button">
|
||||
<rect key="frame" x="0.0" y="0.0" width="230.5" height="30"/>
|
||||
<color key="backgroundColor" red="0.6886889638" green="1" blue="0.74383144840000004" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="LeftButton"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="16"/>
|
||||
@@ -112,8 +108,8 @@
|
||||
<color key="titleColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</state>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="461-vO-hLZ" userLabel="Right Button">
|
||||
<rect key="frame" x="250" y="0.0" width="230" height="30"/>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="461-vO-hLZ" userLabel="Right Button">
|
||||
<rect key="frame" x="249.5" y="0.0" width="230.5" height="30"/>
|
||||
<color key="backgroundColor" red="0.6886889638" green="1" blue="0.74383144840000004" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="RightButton"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="16"/>
|
||||
@@ -135,15 +131,23 @@
|
||||
<constraint firstItem="osd-JP-2az" firstAttribute="height" secondItem="WmW-5h-jL1" secondAttribute="height" id="zHG-qI-kwU"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Giu-1w-duc">
|
||||
<rect key="frame" x="239.5" y="310" width="121" height="31"/>
|
||||
<color key="tintColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<state key="normal" title="Button"/>
|
||||
<buttonConfiguration key="configuration" style="plain" title="Report content">
|
||||
<fontDescription key="titleFontDescription" type="system" pointSize="14"/>
|
||||
</buttonConfiguration>
|
||||
</button>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="IwA-0X-IYb">
|
||||
<rect key="frame" x="281" y="323" width="38" height="18"/>
|
||||
<rect key="frame" x="281" y="344" width="38" height="18"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="SubNoticeLabel"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<color key="textColor" systemColor="darkTextColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="BCO-DT-F0C">
|
||||
<rect key="frame" x="0.0" y="354" width="600" height="1"/>
|
||||
<rect key="frame" x="0.0" y="375" width="600" height="1"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="BottomBorderView"/>
|
||||
<constraints>
|
||||
@@ -154,15 +158,17 @@
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="RoomTitle"/>
|
||||
<constraints>
|
||||
<constraint firstItem="IwA-0X-IYb" firstAttribute="top" secondItem="WmW-5h-jL1" secondAttribute="bottom" constant="17" id="0ft-2Y-gk3"/>
|
||||
<constraint firstAttribute="trailing" secondItem="WmW-5h-jL1" secondAttribute="trailing" constant="60" id="1b7-i7-NOb"/>
|
||||
<constraint firstAttribute="trailing" secondItem="BkF-x3-7fX" secondAttribute="trailing" id="33r-D5-3uy"/>
|
||||
<constraint firstItem="Giu-1w-duc" firstAttribute="top" secondItem="WmW-5h-jL1" secondAttribute="bottom" constant="4" id="4rk-St-IBy"/>
|
||||
<constraint firstItem="gIX-nY-f6M" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="Cpa-Xt-cwB" secondAttribute="leading" constant="31" id="7Kn-g9-Ya6"/>
|
||||
<constraint firstItem="WmW-5h-jL1" firstAttribute="leading" secondItem="Cpa-Xt-cwB" secondAttribute="leading" constant="60" id="8dy-U2-a3G"/>
|
||||
<constraint firstItem="BCO-DT-F0C" firstAttribute="top" secondItem="IwA-0X-IYb" secondAttribute="bottom" constant="13" id="G2S-aH-ycH"/>
|
||||
<constraint firstItem="BCO-DT-F0C" firstAttribute="leading" secondItem="Cpa-Xt-cwB" secondAttribute="leading" id="H07-HA-2Px"/>
|
||||
<constraint firstItem="y5s-FK-My4" firstAttribute="leading" secondItem="Cpa-Xt-cwB" secondAttribute="leading" id="IU0-R1-jNV"/>
|
||||
<constraint firstAttribute="trailing" secondItem="BCO-DT-F0C" secondAttribute="trailing" id="Izl-ms-lGJ"/>
|
||||
<constraint firstItem="Giu-1w-duc" firstAttribute="centerX" secondItem="Cpa-Xt-cwB" secondAttribute="centerX" id="KCY-vw-EHD"/>
|
||||
<constraint firstItem="IwA-0X-IYb" firstAttribute="top" secondItem="Giu-1w-duc" secondAttribute="bottom" constant="3" id="Kfj-HY-Vdh"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="gIX-nY-f6M" secondAttribute="trailing" constant="31" id="Ljb-d3-KKM"/>
|
||||
<constraint firstItem="BkF-x3-7fX" firstAttribute="leading" secondItem="Cpa-Xt-cwB" secondAttribute="leading" id="Uvb-cK-dQf"/>
|
||||
<constraint firstItem="BkF-x3-7fX" firstAttribute="top" secondItem="Cpa-Xt-cwB" secondAttribute="top" id="Zru-9A-ifB"/>
|
||||
@@ -187,6 +193,7 @@
|
||||
<outlet property="mainHeaderBackgroundHeightConstraint" destination="YtX-K2-uD7" id="47y-aL-t94"/>
|
||||
<outlet property="mainHeaderContainer" destination="BkF-x3-7fX" id="1fp-xz-ETJ"/>
|
||||
<outlet property="previewLabel" destination="gIX-nY-f6M" id="Bxi-6M-rTb"/>
|
||||
<outlet property="reportButton" destination="Giu-1w-duc" id="gZe-uF-CoI"/>
|
||||
<outlet property="rightButton" destination="461-vO-hLZ" id="u8R-Rg-WAv"/>
|
||||
<outlet property="roomAvatar" destination="4yt-FK-V2Z" id="epr-4f-7cE"/>
|
||||
<outlet property="roomMembers" destination="ou0-3Z-weL" id="lRs-fz-QXc"/>
|
||||
@@ -194,9 +201,16 @@
|
||||
<outlet property="roomTopic" destination="Tk0-pA-9a0" id="iAe-bC-f6X"/>
|
||||
<outlet property="subNoticeLabel" destination="IwA-0X-IYb" id="aUb-Pt-V44"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="-27.199999999999999" y="-1.7991004497751126"/>
|
||||
</view>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="details_icon" width="31" height="31"/>
|
||||
<image name="details_icon" width="6" height="11"/>
|
||||
<systemColor name="darkTextColor">
|
||||
<color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</systemColor>
|
||||
<systemColor name="groupTableViewBackgroundColor">
|
||||
<color red="0.94901960784313721" green="0.94901960784313721" blue="0.96862745098039216" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
||||
|
||||
@@ -60,6 +60,16 @@
|
||||
{
|
||||
iconURL = [NSString stringWithFormat:@"%@%@", kMXContentUriScheme, [iconURL substringFromIndex:range.location + range.length]];
|
||||
}
|
||||
// Check also if we are using the authenticated endpoint
|
||||
else
|
||||
{
|
||||
mxMediaPrefix = [NSString stringWithFormat:@"/%@/download/", kMXAuthenticatedContentPrefixPath];
|
||||
range = [iconURL rangeOfString:mxMediaPrefix];
|
||||
if (range.location != NSNotFound)
|
||||
{
|
||||
iconURL = [NSString stringWithFormat:@"%@%@", kMXContentUriScheme, [iconURL substringFromIndex:range.location + range.length]];
|
||||
}
|
||||
}
|
||||
[self.iconImageView setImageURI:iconURL
|
||||
withType:nil
|
||||
andImageOrientation:UIImageOrientationUp
|
||||
|
||||
@@ -196,8 +196,7 @@ typedef NS_ENUM(NSUInteger, PRESENCE)
|
||||
|
||||
typedef NS_ENUM(NSUInteger, ADVANCED)
|
||||
{
|
||||
ADVANCED_SHOW_NSFW_ROOMS_INDEX = 0,
|
||||
ADVANCED_CRASH_REPORT_INDEX,
|
||||
ADVANCED_CRASH_REPORT_INDEX = 0,
|
||||
ADVANCED_ENABLE_RAGESHAKE_INDEX,
|
||||
ADVANCED_MARK_ALL_AS_READ_INDEX,
|
||||
ADVANCED_CLEAR_CACHE_INDEX,
|
||||
@@ -725,6 +724,7 @@ SSOAuthenticationPresenterDelegate>
|
||||
Section *sectionAdvanced = [Section sectionWithTag:SECTION_TAG_ADVANCED];
|
||||
sectionAdvanced.headerTitle = [VectorL10n settingsAdvanced];
|
||||
|
||||
// BWI: #6076 check after merge
|
||||
if (RiotSettings.shared.settingsScreenShowNsfwRoomsOption)
|
||||
{
|
||||
[sectionAdvanced addRowWithTag:ADVANCED_SHOW_NSFW_ROOMS_INDEX];
|
||||
|
||||
@@ -520,8 +520,12 @@ extension ExploreRoomCoordinator: RoomInfoCoordinatorDelegate {
|
||||
self.remove(childCoordinator: coordinator)
|
||||
}
|
||||
}
|
||||
|
||||
func roomInfoCoordinator(_ coordinator: RoomInfoCoordinatorType, viewEventInTimeline event: MXEvent) {
|
||||
|
||||
}
|
||||
|
||||
func roomInfoCoordinatorDidRequestReportRoom(_ coordinator: RoomInfoCoordinatorType) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
68
Riot/SupportingFiles/forbidden_terms.txt
Normal file
68
Riot/SupportingFiles/forbidden_terms.txt
Normal file
@@ -0,0 +1,68 @@
|
||||
anal
|
||||
bbw
|
||||
bdsm
|
||||
beast
|
||||
bestiality
|
||||
blowjob
|
||||
bondage
|
||||
boobs
|
||||
clit
|
||||
cock
|
||||
cuck
|
||||
cum
|
||||
cunt
|
||||
daddy
|
||||
dick
|
||||
dildo
|
||||
erotic
|
||||
exhibitionism
|
||||
faggot
|
||||
femboy
|
||||
fisting
|
||||
flogging
|
||||
fmf
|
||||
foursome
|
||||
futa
|
||||
gangbang
|
||||
gore
|
||||
h3ntai
|
||||
handjob
|
||||
hentai
|
||||
incest
|
||||
jizz
|
||||
kink
|
||||
loli
|
||||
m4f
|
||||
masturbate
|
||||
masturbation
|
||||
mfm
|
||||
milf
|
||||
moresome
|
||||
naked
|
||||
neet
|
||||
nsfw
|
||||
nude
|
||||
nudity
|
||||
orgy
|
||||
pedo
|
||||
pegging
|
||||
penis
|
||||
petplay
|
||||
porn
|
||||
pussy
|
||||
rape
|
||||
rimming
|
||||
sadism
|
||||
sadomasochism
|
||||
sexy
|
||||
shota
|
||||
spank
|
||||
squirt
|
||||
strap-on
|
||||
threesome
|
||||
vagina
|
||||
vibrator
|
||||
voyeur
|
||||
watersports
|
||||
xxx
|
||||
zoo
|
||||
@@ -344,7 +344,7 @@ static NSString *const kEventFormatterTimeFormat = @"HH:mm";
|
||||
// Track e2e failures
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[[BWIAnalyticsHelper shared] getRoomDeviceCountWithRoom:[self->mxSession roomWithRoomId:roomState.roomId] completion:^(NSInteger deviceCount) {
|
||||
[[DecryptionFailureTracker sharedInstance] reportUnableToDecryptErrorForEvent:event withRoomState:roomState myUser:self->mxSession.myUser.userId roomDeviceCount:deviceCount];
|
||||
[[DecryptionFailureTracker sharedInstance] reportUnableToDecryptErrorForEvent:event withRoomState:roomState mySession:self->mxSession];
|
||||
}];
|
||||
});
|
||||
}
|
||||
@@ -665,7 +665,14 @@ withVoiceBroadcastInfoStateEvent:lastVoiceBroadcastInfoEvent
|
||||
- (BOOL)session:(MXSession *)session updateRoomSummary:(MXRoomSummary *)summary withStateEvents:(NSArray<MXEvent *> *)stateEvents roomState:(MXRoomState *)roomState
|
||||
{
|
||||
BOOL updated = [super session:session updateRoomSummary:summary withStateEvents:stateEvents roomState:roomState];
|
||||
|
||||
|
||||
MXEvent* lastRoomRetentionEvent = [self roomRetentionEventFromStateEvents:stateEvents];
|
||||
if (lastRoomRetentionEvent)
|
||||
{
|
||||
summary.others[MXRoomSummary.roomRetentionMaxLifetime] = lastRoomRetentionEvent.content[MXRoomSummary.roomRetentionEventMaxLifetimeKey];
|
||||
updated = YES;
|
||||
}
|
||||
|
||||
// Customisation for EMS Functional Members in direct rooms
|
||||
if (BuildSettings.supportFunctionalMembers && summary.room.isDirect)
|
||||
{
|
||||
@@ -678,7 +685,7 @@ withVoiceBroadcastInfoStateEvent:lastVoiceBroadcastInfoEvent
|
||||
// room name which we'll do twice more in updateRoomSummary:withServerRoomSummary:roomState: anyway.
|
||||
//
|
||||
// So return YES and let that happen there.
|
||||
return YES;
|
||||
updated = YES;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -834,6 +841,12 @@ withVoiceBroadcastInfoStateEvent:lastVoiceBroadcastInfoEvent
|
||||
return [stateEvents filteredArrayUsingPredicate:functionalMembersPredicate].lastObject;
|
||||
}
|
||||
|
||||
- (MXEvent *)roomRetentionEventFromStateEvents:(NSArray<MXEvent *> *)stateEvents
|
||||
{
|
||||
NSPredicate *functionalMembersPredicate = [NSPredicate predicateWithFormat:@"type == %@", kMXEventTypeStringRoomRetention];
|
||||
return [stateEvents filteredArrayUsingPredicate:functionalMembersPredicate].lastObject;
|
||||
}
|
||||
|
||||
#pragma mark - Timestamp formatting
|
||||
|
||||
- (NSString*)dateStringFromDate:(NSDate *)date withTime:(BOOL)time
|
||||
|
||||
@@ -51,4 +51,11 @@
|
||||
+ (NSURL*)fixURLWithSeveralHashKeys:(NSURL*)url;
|
||||
+ (NSData*)mediaConvertMaxImageData:(NSData*) imageData withUTI:(MXKUTI *)uti;
|
||||
|
||||
#pragma mark - Time utilities
|
||||
|
||||
/**
|
||||
* Convert a number of days to a duration in ms.
|
||||
*/
|
||||
+ (uint64_t)durationInMsFromDays:(uint)days;
|
||||
|
||||
@end
|
||||
|
||||
@@ -196,4 +196,11 @@
|
||||
return convertedData;
|
||||
}
|
||||
|
||||
#pragma mark - Time utilities
|
||||
|
||||
+ (uint64_t)durationInMsFromDays:(uint)days
|
||||
{
|
||||
return days * (uint64_t)(86400000);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -41,6 +41,7 @@ targets:
|
||||
- package: WysiwygComposer
|
||||
- package: DeviceKit
|
||||
- package: DTCoreText
|
||||
- package: PostHog
|
||||
|
||||
configFiles:
|
||||
Debug: Debug.xcconfig
|
||||
|
||||
@@ -36,6 +36,7 @@ targets:
|
||||
- package: DeviceKit
|
||||
- package: DTCoreText
|
||||
- package: SwiftJWT
|
||||
- package: PostHog
|
||||
|
||||
configFiles:
|
||||
Debug: Debug.xcconfig
|
||||
|
||||
@@ -23,6 +23,9 @@ import ZXingObjC
|
||||
|
||||
// MARK: - QRLoginService
|
||||
|
||||
// n.b MSC3886/MSC3903/MSC3906 that this is based on are now closed.
|
||||
// However, we want to keep this implementation around for some time.
|
||||
// TODO: define an end-of-life date for this implementation.
|
||||
class QRLoginService: NSObject, QRLoginServiceProtocol {
|
||||
private let client: AuthenticationRestClient
|
||||
private let sessionCreator: SessionCreatorProtocol
|
||||
|
||||
@@ -82,6 +82,10 @@ struct Composer: View {
|
||||
wysiwygViewModel.maximised ? "minimiseButton" : "maximiseButton"
|
||||
}
|
||||
|
||||
private var toggleButtonAccessibilityLabel: String {
|
||||
wysiwygViewModel.maximised ? VectorL10n.wysiwygComposerActionMinimiseAction : VectorL10n.wysiwygComposerActionMaximiseAction
|
||||
}
|
||||
|
||||
private var toggleButtonImageName: String {
|
||||
wysiwygViewModel.maximised ? Asset.Images.minimiseComposer.name : Asset.Images.maximiseComposer.name
|
||||
}
|
||||
@@ -171,6 +175,7 @@ struct Composer: View {
|
||||
.frame(width: 16, height: 16)
|
||||
}
|
||||
.accessibilityIdentifier(toggleButtonAcccessibilityIdentifier)
|
||||
.accessibilityLabel(toggleButtonAccessibilityLabel)
|
||||
.padding(.leading, 12)
|
||||
.padding(.trailing, 4)
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ class AnalyticsTests: XCTestCase {
|
||||
XCTAssertNil(client.pendingUserProperties, "No user properties should have been set yet.")
|
||||
|
||||
// When updating the user properties
|
||||
client.updateUserProperties(AnalyticsEvent.UserProperties(allChatsActiveFilter: nil, ftueUseCaseSelection: .PersonalMessaging, numFavouriteRooms: 4, numSpaces: 5))
|
||||
client.updateUserProperties(AnalyticsEvent.UserProperties(allChatsActiveFilter: nil, ftueUseCaseSelection: .PersonalMessaging, numFavouriteRooms: 4, numSpaces: 5, recoveryState: nil, verificationState: nil))
|
||||
|
||||
// Then the properties should be cached
|
||||
XCTAssertNotNil(client.pendingUserProperties, "The user properties should be cached.")
|
||||
@@ -90,7 +90,7 @@ class AnalyticsTests: XCTestCase {
|
||||
func testMergingUserProperties() {
|
||||
// Given a client with a cached use case user properties
|
||||
let client = PostHogAnalyticsClient()
|
||||
client.updateUserProperties(AnalyticsEvent.UserProperties(allChatsActiveFilter: nil, ftueUseCaseSelection: .PersonalMessaging, numFavouriteRooms: nil, numSpaces: nil))
|
||||
client.updateUserProperties(AnalyticsEvent.UserProperties(allChatsActiveFilter: nil, ftueUseCaseSelection: .PersonalMessaging, numFavouriteRooms: nil, numSpaces: nil, recoveryState: nil, verificationState: nil))
|
||||
|
||||
XCTAssertNotNil(client.pendingUserProperties, "The user properties should be cached.")
|
||||
XCTAssertEqual(client.pendingUserProperties?.ftueUseCaseSelection, .PersonalMessaging, "The use case selection should match.")
|
||||
@@ -98,7 +98,7 @@ class AnalyticsTests: XCTestCase {
|
||||
XCTAssertNil(client.pendingUserProperties?.numSpaces, "The number of spaces should not be set.")
|
||||
|
||||
// When updating the number of spaces
|
||||
client.updateUserProperties(AnalyticsEvent.UserProperties(allChatsActiveFilter: nil, ftueUseCaseSelection: nil, numFavouriteRooms: 4, numSpaces: 5))
|
||||
client.updateUserProperties(AnalyticsEvent.UserProperties(allChatsActiveFilter: nil, ftueUseCaseSelection: nil, numFavouriteRooms: 4, numSpaces: 5, recoveryState: nil, verificationState: nil))
|
||||
|
||||
// Then the new properties should be updated and the existing properties should remain unchanged
|
||||
XCTAssertNotNil(client.pendingUserProperties, "The user properties should be cached.")
|
||||
@@ -107,7 +107,7 @@ class AnalyticsTests: XCTestCase {
|
||||
XCTAssertEqual(client.pendingUserProperties?.numSpaces, 5, "The number of spaces should have been updated.")
|
||||
|
||||
// When updating the number of spaces
|
||||
client.updateUserProperties(AnalyticsEvent.UserProperties(allChatsActiveFilter: .Favourites, ftueUseCaseSelection: nil, numFavouriteRooms: nil, numSpaces: nil))
|
||||
client.updateUserProperties(AnalyticsEvent.UserProperties(allChatsActiveFilter: .Favourites, ftueUseCaseSelection: nil, numFavouriteRooms: nil, numSpaces: nil, recoveryState: nil, verificationState: nil))
|
||||
|
||||
// Then the new properties should be updated and the existing properties should remain unchanged
|
||||
XCTAssertNotNil(client.pendingUserProperties, "The user properties should be cached.")
|
||||
@@ -120,14 +120,15 @@ class AnalyticsTests: XCTestCase {
|
||||
func testSendingUserProperties() {
|
||||
// Given a client with user properties set
|
||||
let client = PostHogAnalyticsClient()
|
||||
client.updateUserProperties(AnalyticsEvent.UserProperties(allChatsActiveFilter: nil, ftueUseCaseSelection: .PersonalMessaging, numFavouriteRooms: nil, numSpaces: nil))
|
||||
client.updateUserProperties(AnalyticsEvent.UserProperties(allChatsActiveFilter: nil, ftueUseCaseSelection: .PersonalMessaging, numFavouriteRooms: nil, numSpaces: nil, recoveryState: nil, verificationState: nil))
|
||||
client.start()
|
||||
|
||||
XCTAssertNotNil(client.pendingUserProperties, "The user properties should be cached.")
|
||||
XCTAssertEqual(client.pendingUserProperties?.ftueUseCaseSelection, .PersonalMessaging, "The use case selection should match.")
|
||||
|
||||
// When sending an event (tests run under Debug configuration so this is sent to the development instance)
|
||||
client.screen(AnalyticsEvent.MobileScreen(durationMs: nil, screenName: .Home))
|
||||
let event = AnalyticsEvent.Signup(authenticationType: .Other)
|
||||
client.capture(event)
|
||||
|
||||
// Then the properties should be cleared
|
||||
XCTAssertNil(client.pendingUserProperties, "The user properties should be cleared.")
|
||||
@@ -136,7 +137,7 @@ class AnalyticsTests: XCTestCase {
|
||||
func testSendingUserPropertiesWithIdentify() {
|
||||
// Given a client with user properties set
|
||||
let client = PostHogAnalyticsClient()
|
||||
client.updateUserProperties(AnalyticsEvent.UserProperties(allChatsActiveFilter: nil, ftueUseCaseSelection: .PersonalMessaging, numFavouriteRooms: nil, numSpaces: nil))
|
||||
client.updateUserProperties(AnalyticsEvent.UserProperties(allChatsActiveFilter: nil, ftueUseCaseSelection: .PersonalMessaging, numFavouriteRooms: nil, numSpaces: nil, recoveryState: nil, verificationState: nil))
|
||||
client.start()
|
||||
|
||||
XCTAssertNotNil(client.pendingUserProperties, "The user properties should be cached.")
|
||||
|
||||
@@ -41,10 +41,23 @@ class DecryptionFailureTrackerTests: XCTestCase {
|
||||
}
|
||||
|
||||
let timeShifter = TimeShifter()
|
||||
var fakeCrypto: FakeCrypto!
|
||||
var fakeSession: FakeSession!
|
||||
var fakeCrossSigning: FakeCrossSigning!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
self.fakeCrypto = FakeCrypto()
|
||||
self.fakeCrossSigning = FakeCrossSigning()
|
||||
self.fakeCrypto.crossSigning = self.fakeCrossSigning
|
||||
self.fakeSession = FakeSession(mockCrypto: self.fakeCrypto)
|
||||
}
|
||||
|
||||
|
||||
func test_grace_period() {
|
||||
|
||||
let myUser = "test@example.com";
|
||||
let myUser = "@test:example.com";
|
||||
fakeSession.mockUserId = myUser;
|
||||
|
||||
let decryptionFailureTracker = DecryptionFailureTracker();
|
||||
decryptionFailureTracker.timeProvider = timeShifter;
|
||||
@@ -61,7 +74,7 @@ class DecryptionFailureTrackerTests: XCTestCase {
|
||||
|
||||
let fakeRoomState = FakeRoomState();
|
||||
fakeRoomState.mockMembers = FakeRoomMembers(joined: [myUser])
|
||||
decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, myUser: myUser);
|
||||
decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, mySession: fakeSession);
|
||||
|
||||
timeShifter.timestamp = TimeInterval(2)
|
||||
|
||||
@@ -82,7 +95,8 @@ class DecryptionFailureTrackerTests: XCTestCase {
|
||||
|
||||
func test_report_ratcheted_key_utd() {
|
||||
|
||||
let myUser = "test@example.com";
|
||||
let myUser = "@test:example.com";
|
||||
fakeSession.mockUserId = myUser;
|
||||
|
||||
let decryptionFailureTracker = DecryptionFailureTracker();
|
||||
decryptionFailureTracker.timeProvider = timeShifter;
|
||||
@@ -99,7 +113,7 @@ class DecryptionFailureTrackerTests: XCTestCase {
|
||||
|
||||
let fakeRoomState = FakeRoomState();
|
||||
fakeRoomState.mockMembers = FakeRoomMembers(joined: [myUser])
|
||||
decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, myUser: myUser);
|
||||
decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, mySession: fakeSession);
|
||||
|
||||
// Pass the max period
|
||||
timeShifter.timestamp = TimeInterval(70)
|
||||
@@ -111,7 +125,8 @@ class DecryptionFailureTrackerTests: XCTestCase {
|
||||
|
||||
func test_report_unspecified_error() {
|
||||
|
||||
let myUser = "test@example.com";
|
||||
let myUser = "@test:example.com";
|
||||
fakeSession.mockUserId = myUser;
|
||||
|
||||
let decryptionFailureTracker = DecryptionFailureTracker();
|
||||
decryptionFailureTracker.timeProvider = timeShifter;
|
||||
@@ -128,7 +143,7 @@ class DecryptionFailureTrackerTests: XCTestCase {
|
||||
|
||||
let fakeRoomState = FakeRoomState();
|
||||
fakeRoomState.mockMembers = FakeRoomMembers(joined: [myUser])
|
||||
decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, myUser: myUser);
|
||||
decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, mySession: fakeSession);
|
||||
|
||||
// Pass the max period
|
||||
timeShifter.timestamp = TimeInterval(70)
|
||||
@@ -142,7 +157,8 @@ class DecryptionFailureTrackerTests: XCTestCase {
|
||||
|
||||
func test_do_not_double_report() {
|
||||
|
||||
let myUser = "test@example.com";
|
||||
let myUser = "@test:example.com";
|
||||
fakeSession.mockUserId = myUser;
|
||||
|
||||
let decryptionFailureTracker = DecryptionFailureTracker();
|
||||
decryptionFailureTracker.timeProvider = timeShifter;
|
||||
@@ -160,7 +176,7 @@ class DecryptionFailureTrackerTests: XCTestCase {
|
||||
let fakeRoomState = FakeRoomState();
|
||||
fakeRoomState.mockMembers = FakeRoomMembers(joined: [myUser])
|
||||
|
||||
decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, myUser: myUser);
|
||||
decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, mySession: fakeSession);
|
||||
|
||||
// Pass the max period
|
||||
timeShifter.timestamp = TimeInterval(70)
|
||||
@@ -171,7 +187,7 @@ class DecryptionFailureTrackerTests: XCTestCase {
|
||||
|
||||
// Try to report again the same event
|
||||
testDelegate.reportedFailure = nil
|
||||
decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, myUser: myUser);
|
||||
decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, mySession: fakeSession);
|
||||
// Pass the grace period
|
||||
timeShifter.timestamp = TimeInterval(10)
|
||||
|
||||
@@ -183,7 +199,8 @@ class DecryptionFailureTrackerTests: XCTestCase {
|
||||
|
||||
func test_ignore_not_member() {
|
||||
|
||||
let myUser = "test@example.com";
|
||||
let myUser = "@test:example.com";
|
||||
fakeSession.mockUserId = myUser;
|
||||
|
||||
let decryptionFailureTracker = DecryptionFailureTracker();
|
||||
decryptionFailureTracker.timeProvider = timeShifter;
|
||||
@@ -203,7 +220,7 @@ class DecryptionFailureTrackerTests: XCTestCase {
|
||||
fakeMembers.mockMembers[myUser] = MXMembership.ban
|
||||
fakeRoomState.mockMembers = fakeMembers
|
||||
|
||||
decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, myUser: myUser);
|
||||
decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, mySession: fakeSession);
|
||||
|
||||
// Pass the grace period
|
||||
timeShifter.timestamp = TimeInterval(5)
|
||||
@@ -217,7 +234,8 @@ class DecryptionFailureTrackerTests: XCTestCase {
|
||||
|
||||
func test_notification_center() {
|
||||
|
||||
let myUser = "test@example.com";
|
||||
let myUser = "@test:example.com";
|
||||
fakeSession.mockUserId = myUser;
|
||||
|
||||
let decryptionFailureTracker = DecryptionFailureTracker();
|
||||
decryptionFailureTracker.timeProvider = timeShifter;
|
||||
@@ -235,7 +253,7 @@ class DecryptionFailureTrackerTests: XCTestCase {
|
||||
let fakeRoomState = FakeRoomState();
|
||||
fakeRoomState.mockMembers = FakeRoomMembers(joined: [myUser])
|
||||
|
||||
decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, myUser: myUser);
|
||||
decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, mySession: fakeSession);
|
||||
|
||||
// Shift time below GRACE_PERIOD
|
||||
timeShifter.timestamp = TimeInterval(2)
|
||||
@@ -257,7 +275,8 @@ class DecryptionFailureTrackerTests: XCTestCase {
|
||||
|
||||
func test_should_report_late_decrypt() {
|
||||
|
||||
let myUser = "test@example.com";
|
||||
let myUser = "@test:example.com";
|
||||
fakeSession.mockUserId = myUser;
|
||||
|
||||
let decryptionFailureTracker = DecryptionFailureTracker();
|
||||
decryptionFailureTracker.timeProvider = timeShifter;
|
||||
@@ -275,7 +294,7 @@ class DecryptionFailureTrackerTests: XCTestCase {
|
||||
let fakeRoomState = FakeRoomState();
|
||||
fakeRoomState.mockMembers = FakeRoomMembers(joined: [myUser])
|
||||
|
||||
decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, myUser: myUser);
|
||||
decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, mySession: fakeSession);
|
||||
|
||||
// Simulate succesful decryption after grace period but before max wait
|
||||
timeShifter.timestamp = TimeInterval(20)
|
||||
@@ -301,7 +320,8 @@ class DecryptionFailureTrackerTests: XCTestCase {
|
||||
|
||||
func test_should_report_permanent_decryption_error() {
|
||||
|
||||
let myUser = "test@example.com";
|
||||
let myUser = "@test:example.com";
|
||||
fakeSession.mockUserId = myUser;
|
||||
|
||||
let decryptionFailureTracker = DecryptionFailureTracker();
|
||||
decryptionFailureTracker.timeProvider = timeShifter;
|
||||
@@ -319,7 +339,7 @@ class DecryptionFailureTrackerTests: XCTestCase {
|
||||
let fakeRoomState = FakeRoomState();
|
||||
fakeRoomState.mockMembers = FakeRoomMembers(joined: [myUser])
|
||||
|
||||
decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, myUser: myUser);
|
||||
decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, mySession: fakeSession);
|
||||
|
||||
// Simulate succesful decryption after max wait
|
||||
timeShifter.timestamp = TimeInterval(70)
|
||||
@@ -337,5 +357,246 @@ class DecryptionFailureTrackerTests: XCTestCase {
|
||||
XCTAssertEqual(analyticsError.timeToDecryptMillis, -1)
|
||||
|
||||
}
|
||||
|
||||
|
||||
func test_should_report_trust_status_at_decryption_time() {
|
||||
|
||||
let myUser = "@test:example.com";
|
||||
fakeSession.mockUserId = myUser;
|
||||
|
||||
let decryptionFailureTracker = DecryptionFailureTracker();
|
||||
decryptionFailureTracker.timeProvider = timeShifter;
|
||||
|
||||
let testDelegate = AnalyticsDelegate();
|
||||
|
||||
decryptionFailureTracker.delegate = testDelegate;
|
||||
|
||||
timeShifter.timestamp = TimeInterval(0)
|
||||
|
||||
let fakeEvent = FakeEvent(id: "$0000");
|
||||
fakeEvent.decryptionError = NSError(domain: MXDecryptingErrorDomain, code: Int(MXDecryptingErrorUnknownInboundSessionIdCode.rawValue))
|
||||
|
||||
// set session as not yet verified
|
||||
fakeCrossSigning.canTrustCrossSigning = false
|
||||
|
||||
let fakeRoomState = FakeRoomState();
|
||||
fakeRoomState.mockMembers = FakeRoomMembers(joined: [myUser])
|
||||
|
||||
decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, mySession: fakeSession);
|
||||
|
||||
// set verified now
|
||||
fakeCrossSigning.canTrustCrossSigning = true
|
||||
|
||||
// Simulate succesful decryption after max wait
|
||||
timeShifter.timestamp = TimeInterval(70)
|
||||
|
||||
decryptionFailureTracker.checkFailures();
|
||||
|
||||
// Event should have been reported as a late decrypt
|
||||
XCTAssertEqual(testDelegate.reportedFailure?.trustOwnIdentityAtTimeOfFailure, false);
|
||||
|
||||
// Assert that it's converted to -1 for reporting
|
||||
let analyticsError = testDelegate.reportedFailure!.toAnalyticsEvent()
|
||||
|
||||
XCTAssertEqual(analyticsError.userTrustsOwnIdentity, false)
|
||||
|
||||
// Report a new error now that session is verified
|
||||
|
||||
let fakeEvent2 = FakeEvent(id: "$0001");
|
||||
fakeEvent2.decryptionError = NSError(domain: MXDecryptingErrorDomain, code: Int(MXDecryptingErrorUnknownInboundSessionIdCode.rawValue))
|
||||
|
||||
|
||||
decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent2, withRoomState: fakeRoomState, mySession: fakeSession);
|
||||
|
||||
// Simulate permanent UTD
|
||||
timeShifter.timestamp = TimeInterval(140)
|
||||
|
||||
decryptionFailureTracker.checkFailures();
|
||||
|
||||
XCTAssertEqual(testDelegate.reportedFailure?.failedEventId, "$0001");
|
||||
XCTAssertEqual(testDelegate.reportedFailure?.trustOwnIdentityAtTimeOfFailure, true);
|
||||
|
||||
let analyticsError2 = testDelegate.reportedFailure!.toAnalyticsEvent()
|
||||
|
||||
XCTAssertEqual(analyticsError2.userTrustsOwnIdentity, true)
|
||||
|
||||
}
|
||||
|
||||
|
||||
func test_should_report_event_age() {
|
||||
|
||||
let myUser = "@test:example.com";
|
||||
fakeSession.mockUserId = myUser;
|
||||
|
||||
let format = DateFormatter()
|
||||
format.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
|
||||
|
||||
let sessionCreationTimeMillis = format.date(from: "2024-03-09T10:00:00Z")!.timeIntervalSince1970 * 1000
|
||||
|
||||
let now = format.date(from: "2024-03-09T10:02:00Z")!.timeIntervalSince1970
|
||||
|
||||
// 5mn after session was created
|
||||
let postCreationMessageTs = UInt64(format.date(from: "2024-03-09T10:05:00Z")!.timeIntervalSince1970 * 1000)
|
||||
|
||||
let decryptionFailureTracker = DecryptionFailureTracker();
|
||||
decryptionFailureTracker.timeProvider = timeShifter;
|
||||
|
||||
let testDelegate = AnalyticsDelegate();
|
||||
|
||||
decryptionFailureTracker.delegate = testDelegate;
|
||||
|
||||
timeShifter.timestamp = now
|
||||
|
||||
let fakeEvent = FakeEvent(id: "$0000");
|
||||
fakeEvent.mockOrigineServerTs = postCreationMessageTs;
|
||||
fakeEvent.decryptionError = NSError(domain: MXDecryptingErrorDomain, code: Int(MXDecryptingErrorUnknownInboundSessionIdCode.rawValue))
|
||||
|
||||
fakeCrypto.deviceCreationTs = UInt64(sessionCreationTimeMillis)
|
||||
|
||||
let fakeRoomState = FakeRoomState();
|
||||
fakeRoomState.mockMembers = FakeRoomMembers(joined: [myUser])
|
||||
|
||||
decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, mySession: fakeSession);
|
||||
|
||||
// set verified now
|
||||
fakeCrossSigning.canTrustCrossSigning = true
|
||||
|
||||
// Simulate permanent UTD
|
||||
timeShifter.timestamp = now + TimeInterval(70)
|
||||
|
||||
decryptionFailureTracker.checkFailures();
|
||||
|
||||
XCTAssertEqual(testDelegate.reportedFailure?.eventLocalAgeMillis, 5 * 60 * 1000);
|
||||
|
||||
let analyticsError = testDelegate.reportedFailure!.toAnalyticsEvent()
|
||||
|
||||
XCTAssertEqual(analyticsError.eventLocalAgeMillis, 5 * 60 * 1000)
|
||||
|
||||
}
|
||||
|
||||
|
||||
func test_should_report_expected_utds() {
|
||||
|
||||
let myUser = "@test:example.com";
|
||||
fakeSession.mockUserId = myUser;
|
||||
|
||||
let format = DateFormatter()
|
||||
format.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
|
||||
|
||||
let sessionCreationTimeMillis = format.date(from: "2024-03-09T10:00:00Z")!.timeIntervalSince1970 * 1000
|
||||
|
||||
let now = format.date(from: "2024-03-09T10:02:00Z")!.timeIntervalSince1970
|
||||
|
||||
// 1 day before session was created
|
||||
let historicalMessageTs = UInt64(format.date(from: "2024-03-08T10:00:00Z")!.timeIntervalSince1970 * 1000)
|
||||
|
||||
let decryptionFailureTracker = DecryptionFailureTracker();
|
||||
decryptionFailureTracker.timeProvider = timeShifter;
|
||||
|
||||
let testDelegate = AnalyticsDelegate();
|
||||
|
||||
decryptionFailureTracker.delegate = testDelegate;
|
||||
|
||||
timeShifter.timestamp = now
|
||||
|
||||
let fakeEvent = FakeEvent(id: "$0000");
|
||||
fakeEvent.mockOrigineServerTs = historicalMessageTs;
|
||||
fakeEvent.decryptionError = NSError(domain: MXDecryptingErrorDomain, code: Int(MXDecryptingErrorUnknownInboundSessionIdCode.rawValue))
|
||||
|
||||
fakeCrypto.deviceCreationTs = UInt64(sessionCreationTimeMillis)
|
||||
|
||||
let fakeRoomState = FakeRoomState();
|
||||
fakeRoomState.mockMembers = FakeRoomMembers(joined: [myUser])
|
||||
|
||||
fakeCrossSigning.canTrustCrossSigning = false
|
||||
|
||||
decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, mySession: fakeSession);
|
||||
|
||||
// set verified now
|
||||
fakeCrossSigning.canTrustCrossSigning = true
|
||||
|
||||
// Simulate permanent UTD
|
||||
timeShifter.timestamp = now + TimeInterval(70)
|
||||
|
||||
decryptionFailureTracker.checkFailures();
|
||||
|
||||
// Event should have been reported as a late decrypt
|
||||
XCTAssertEqual(testDelegate.reportedFailure?.eventLocalAgeMillis, -24 * 60 * 60 * 1000);
|
||||
|
||||
let analyticsError = testDelegate.reportedFailure!.toAnalyticsEvent()
|
||||
|
||||
XCTAssertEqual(analyticsError.name, .HistoricalMessage)
|
||||
|
||||
}
|
||||
|
||||
|
||||
func test_should_report_is_matrix_org_and_is_federated() {
|
||||
|
||||
let myUser = "@test:example.com";
|
||||
fakeSession.mockUserId = myUser;
|
||||
|
||||
let decryptionFailureTracker = DecryptionFailureTracker();
|
||||
decryptionFailureTracker.timeProvider = timeShifter;
|
||||
|
||||
let testDelegate = AnalyticsDelegate();
|
||||
|
||||
decryptionFailureTracker.delegate = testDelegate;
|
||||
|
||||
timeShifter.timestamp = TimeInterval(0)
|
||||
|
||||
let fakeEvent = FakeEvent(id: "$0000");
|
||||
fakeEvent.sender = "@bob:example.com"
|
||||
fakeEvent.decryptionError = NSError(domain: MXDecryptingErrorDomain, code: Int(MXDecryptingErrorUnknownInboundSessionIdCode.rawValue))
|
||||
|
||||
// set session as not yet verified
|
||||
fakeCrossSigning.canTrustCrossSigning = false
|
||||
|
||||
let fakeRoomState = FakeRoomState();
|
||||
fakeRoomState.mockMembers = FakeRoomMembers(joined: [myUser])
|
||||
|
||||
decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, mySession: fakeSession);
|
||||
|
||||
// Simulate succesful decryption after max wait
|
||||
timeShifter.timestamp = TimeInterval(70)
|
||||
|
||||
decryptionFailureTracker.checkFailures();
|
||||
|
||||
XCTAssertEqual(testDelegate.reportedFailure?.isMatrixOrg, false);
|
||||
XCTAssertEqual(testDelegate.reportedFailure?.isFederated, false);
|
||||
|
||||
|
||||
let analyticsError = testDelegate.reportedFailure!.toAnalyticsEvent()
|
||||
|
||||
XCTAssertEqual(analyticsError.isMatrixDotOrg, false)
|
||||
XCTAssertEqual(analyticsError.isFederated, false)
|
||||
|
||||
// Report a new error now that session is verified
|
||||
|
||||
let fakeEvent2 = FakeEvent(id: "$0001");
|
||||
fakeEvent2.sender = "@bob:example.com"
|
||||
fakeEvent2.decryptionError = NSError(domain: MXDecryptingErrorDomain, code: Int(MXDecryptingErrorUnknownInboundSessionIdCode.rawValue))
|
||||
|
||||
fakeSession.mockUserId = "@test:matrix.org";
|
||||
fakeRoomState.mockMembers = FakeRoomMembers(joined: [fakeSession.mockUserId])
|
||||
|
||||
decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent2, withRoomState: fakeRoomState, mySession: fakeSession);
|
||||
|
||||
// Simulate permanent UTD
|
||||
timeShifter.timestamp = TimeInterval(140)
|
||||
|
||||
decryptionFailureTracker.checkFailures();
|
||||
|
||||
XCTAssertEqual(testDelegate.reportedFailure?.failedEventId, "$0001");
|
||||
XCTAssertEqual(testDelegate.reportedFailure?.isMatrixOrg, true);
|
||||
XCTAssertEqual(testDelegate.reportedFailure?.isFederated, true);
|
||||
|
||||
let analyticsError2 = testDelegate.reportedFailure!.toAnalyticsEvent()
|
||||
|
||||
XCTAssertEqual(analyticsError2.isMatrixDotOrg, true)
|
||||
XCTAssertEqual(analyticsError2.isFederated, true)
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//
|
||||
//
|
||||
// Copyright 2024 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -15,6 +15,8 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import PostHog
|
||||
@testable import Element
|
||||
|
||||
|
||||
class FakeEvent: MXEvent {
|
||||
@@ -22,6 +24,7 @@ class FakeEvent: MXEvent {
|
||||
var mockEventId: String;
|
||||
var mockSender: String!;
|
||||
var mockDecryptionError: Error?
|
||||
var mockOrigineServerTs: UInt64 = 0
|
||||
|
||||
init(id: String) {
|
||||
mockEventId = id
|
||||
@@ -31,7 +34,7 @@ class FakeEvent: MXEvent {
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
|
||||
override var sender: String! {
|
||||
get { return mockSender }
|
||||
set { mockSender = newValue }
|
||||
@@ -47,6 +50,12 @@ class FakeEvent: MXEvent {
|
||||
set { mockDecryptionError = newValue }
|
||||
}
|
||||
|
||||
override var originServerTs: UInt64 {
|
||||
get { return mockOrigineServerTs }
|
||||
set { mockOrigineServerTs = newValue }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +89,7 @@ class FakeRoomMember: MXRoomMember {
|
||||
get { return mockUserId }
|
||||
set { mockUserId = newValue }
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -107,3 +116,364 @@ class FakeRoomMembers: MXRoomMembers {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class FakeSession: MXSession {
|
||||
|
||||
var mockCrypto: MXCrypto? = FakeCrypto()
|
||||
|
||||
var mockUserId: String = "@alice:localhost"
|
||||
|
||||
init(mockCrypto: MXCrypto? = nil) {
|
||||
self.mockCrypto = mockCrypto
|
||||
super.init()
|
||||
}
|
||||
|
||||
override var crypto: MXCrypto? {
|
||||
get {
|
||||
return mockCrypto
|
||||
}
|
||||
set {
|
||||
// nothing
|
||||
}
|
||||
}
|
||||
|
||||
override var myUserId: String! {
|
||||
get {
|
||||
return mockUserId
|
||||
}
|
||||
set {
|
||||
mockUserId = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FakeCrypto: NSObject, MXCrypto {
|
||||
|
||||
|
||||
var version: String = ""
|
||||
|
||||
var deviceCurve25519Key: String?
|
||||
|
||||
var deviceEd25519Key: String?
|
||||
|
||||
var deviceCreationTs: UInt64 = 0
|
||||
|
||||
var backup: MXKeyBackup?
|
||||
|
||||
var keyVerificationManager: MXKeyVerificationManager = FakeKeyVerificationManager()
|
||||
|
||||
var crossSigning: MXCrossSigning = FakeCrossSigning()
|
||||
|
||||
var recoveryService: MXRecoveryService! = nil
|
||||
|
||||
var dehydrationService: MatrixSDK.DehydrationService! = nil
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
func start(_ onComplete: (() -> Void)?, failure: ((Swift.Error) -> Void)? = nil) {
|
||||
|
||||
}
|
||||
|
||||
func close(_ deleteStore: Bool) {
|
||||
|
||||
}
|
||||
|
||||
func isRoomEncrypted(_ roomId: String) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func encryptEventContent(_ eventContent: [AnyHashable : Any], withType eventType: String, in room: MXRoom, success: (([AnyHashable : Any], String) -> Void)?, failure: ((Swift.Error) -> Void)? = nil) -> MXHTTPOperation? {
|
||||
return nil
|
||||
}
|
||||
|
||||
func decryptEvents(_ events: [MXEvent], inTimeline timeline: String?, onComplete: (([MXEventDecryptionResult]) -> Void)? = nil) {
|
||||
|
||||
}
|
||||
|
||||
func ensureEncryption(inRoom roomId: String, success: (() -> Void)?, failure: ((Swift.Error) -> Void)? = nil) -> MXHTTPOperation? {
|
||||
return nil
|
||||
}
|
||||
|
||||
func eventDeviceInfo(_ event: MXEvent) -> MXDeviceInfo? {
|
||||
return nil
|
||||
}
|
||||
|
||||
func discardOutboundGroupSessionForRoom(withRoomId roomId: String, onComplete: (() -> Void)? = nil) {
|
||||
|
||||
}
|
||||
|
||||
func handle(_ syncResponse: MXSyncResponse, onComplete: @escaping () -> Void) {
|
||||
|
||||
}
|
||||
|
||||
func setDeviceVerification(_ verificationStatus: MXDeviceVerification, forDevice deviceId: String, ofUser userId: String, success: (() -> Void)?, failure: ((Swift.Error) -> Void)? = nil) {
|
||||
|
||||
}
|
||||
|
||||
func setUserVerification(_ verificationStatus: Bool, forUser userId: String, success: (() -> Void)?, failure: ((Swift.Error) -> Void)? = nil) {
|
||||
|
||||
}
|
||||
|
||||
func trustLevel(forUser userId: String) -> MXUserTrustLevel {
|
||||
return MXUserTrustLevel.init(crossSigningVerified: true, locallyVerified: true)
|
||||
}
|
||||
|
||||
func deviceTrustLevel(forDevice deviceId: String, ofUser userId: String) -> MXDeviceTrustLevel? {
|
||||
return nil
|
||||
}
|
||||
|
||||
func trustLevelSummary(forUserIds userIds: [String], forceDownload: Bool, success: ((MXUsersTrustLevelSummary?) -> Void)?, failure: ((Swift.Error) -> Void)? = nil) {
|
||||
|
||||
}
|
||||
|
||||
func downloadKeys(_ userIds: [String], forceDownload: Bool, success: ((MXUsersDevicesMap<MXDeviceInfo>?, [String : MXCrossSigningInfo]?) -> Void)?, failure: ((Swift.Error) -> Void)? = nil) -> MXHTTPOperation? {
|
||||
return nil
|
||||
}
|
||||
|
||||
func devices(forUser userId: String) -> [String : MXDeviceInfo] {
|
||||
return [:];
|
||||
}
|
||||
|
||||
func device(withDeviceId deviceId: String, ofUser userId: String) -> MXDeviceInfo? {
|
||||
return nil
|
||||
}
|
||||
|
||||
func exportRoomKeys(withPassword password: String, success: ((Data) -> Void)?, failure: ((Swift.Error) -> Void)? = nil) {
|
||||
|
||||
}
|
||||
|
||||
func importRoomKeys(_ keyFile: Data, withPassword password: String, success: ((UInt, UInt) -> Void)?, failure: ((Swift.Error) -> Void)? = nil) {
|
||||
|
||||
}
|
||||
|
||||
func reRequestRoomKey(for event: MXEvent) {
|
||||
|
||||
}
|
||||
|
||||
var globalBlacklistUnverifiedDevices: Bool = false
|
||||
|
||||
func isBlacklistUnverifiedDevices(inRoom roomId: String) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func setBlacklistUnverifiedDevicesInRoom(_ roomId: String, blacklist: Bool) {
|
||||
|
||||
}
|
||||
|
||||
func invalidateCache(_ done: @escaping () -> Void) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class FakeCrossSigning: NSObject, MXCrossSigning {
|
||||
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
var state: MXCrossSigningState = MXCrossSigningState.trustCrossSigning
|
||||
|
||||
var myUserCrossSigningKeys: MXCrossSigningInfo? = nil
|
||||
|
||||
var canTrustCrossSigning: Bool = true
|
||||
|
||||
var canCrossSign: Bool = true
|
||||
|
||||
var hasAllPrivateKeys: Bool = true
|
||||
|
||||
func refreshState(success: ((Bool) -> Void)?, failure: ((Swift.Error) -> Void)? = nil) {
|
||||
|
||||
}
|
||||
|
||||
func setup(withPassword password: String, success: @escaping () -> Void, failure: @escaping (Swift.Error) -> Void) {
|
||||
|
||||
}
|
||||
|
||||
func setup(withAuthParams authParams: [AnyHashable : Any], success: @escaping () -> Void, failure: @escaping (Swift.Error) -> Void) {
|
||||
|
||||
}
|
||||
|
||||
func crossSignDevice(withDeviceId deviceId: String, userId: String, success: @escaping () -> Void, failure: @escaping (Swift.Error) -> Void) {
|
||||
|
||||
}
|
||||
|
||||
func signUser(withUserId userId: String, success: @escaping () -> Void, failure: @escaping (Swift.Error) -> Void) {
|
||||
|
||||
}
|
||||
|
||||
func crossSigningKeys(forUser userId: String) -> MXCrossSigningInfo? {
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class FakeKeyVerificationManager: NSObject, MXKeyVerificationManager {
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
func requestVerificationByToDevice(withUserId userId: String, deviceIds: [String]?, methods: [String], success: @escaping (MXKeyVerificationRequest) -> Void, failure: @escaping (Swift.Error) -> Void) {
|
||||
|
||||
}
|
||||
|
||||
func requestVerificationByDM(withUserId userId: String, roomId: String?, fallbackText: String, methods: [String], success: @escaping (MXKeyVerificationRequest) -> Void, failure: @escaping (Swift.Error) -> Void) {
|
||||
|
||||
}
|
||||
|
||||
var pendingRequests: [MXKeyVerificationRequest] = []
|
||||
|
||||
func beginKeyVerification(from request: MXKeyVerificationRequest, method: String, success: @escaping (MXKeyVerificationTransaction) -> Void, failure: @escaping (Swift.Error) -> Void) {
|
||||
|
||||
}
|
||||
|
||||
func transactions(_ complete: @escaping ([MXKeyVerificationTransaction]) -> Void) {
|
||||
|
||||
}
|
||||
|
||||
func keyVerification(fromKeyVerificationEvent event: MXEvent, roomId: String, success: @escaping (MXKeyVerification) -> Void, failure: @escaping (Swift.Error) -> Void) -> MXHTTPOperation? {
|
||||
return nil
|
||||
}
|
||||
|
||||
func qrCodeTransaction(withTransactionId transactionId: String) -> MXQRCodeTransaction? {
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeQRCodeTransaction(withTransactionId transactionId: String) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class MockPostHog: PostHogProtocol {
|
||||
|
||||
private var enabled = false
|
||||
|
||||
func optIn() {
|
||||
enabled = true
|
||||
}
|
||||
|
||||
func optOut() {
|
||||
enabled = false
|
||||
}
|
||||
|
||||
func reset() { }
|
||||
|
||||
func flush() {
|
||||
|
||||
}
|
||||
|
||||
var capturePropertiesUserPropertiesUnderlyingCallsCount = 0
|
||||
var capturePropertiesUserPropertiesCallsCount: Int {
|
||||
get {
|
||||
if Thread.isMainThread {
|
||||
return capturePropertiesUserPropertiesUnderlyingCallsCount
|
||||
} else {
|
||||
var returnValue: Int? = nil
|
||||
DispatchQueue.main.sync {
|
||||
returnValue = capturePropertiesUserPropertiesUnderlyingCallsCount
|
||||
}
|
||||
|
||||
return returnValue!
|
||||
}
|
||||
}
|
||||
set {
|
||||
if Thread.isMainThread {
|
||||
capturePropertiesUserPropertiesUnderlyingCallsCount = newValue
|
||||
} else {
|
||||
DispatchQueue.main.sync {
|
||||
capturePropertiesUserPropertiesUnderlyingCallsCount = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var capturePropertiesUserPropertiesCalled: Bool {
|
||||
return capturePropertiesUserPropertiesCallsCount > 0
|
||||
}
|
||||
var capturePropertiesUserPropertiesReceivedArguments: (event: String, properties: [String: Any]?, userProperties: [String: Any]?)?
|
||||
var capturePropertiesUserPropertiesReceivedInvocations: [(event: String, properties: [String: Any]?, userProperties: [String: Any]?)] = []
|
||||
var capturePropertiesUserPropertiesClosure: ((String, [String: Any]?, [String: Any]?) -> Void)?
|
||||
|
||||
func capture(_ event: String, properties: [String: Any]?, userProperties: [String: Any]?) {
|
||||
if !enabled { return }
|
||||
capturePropertiesUserPropertiesCallsCount += 1
|
||||
capturePropertiesUserPropertiesReceivedArguments = (event: event, properties: properties, userProperties: userProperties)
|
||||
capturePropertiesUserPropertiesReceivedInvocations.append((event: event, properties: properties, userProperties: userProperties))
|
||||
capturePropertiesUserPropertiesClosure?(event, properties, userProperties)
|
||||
}
|
||||
|
||||
var screenPropertiesUnderlyingCallsCount = 0
|
||||
var screenPropertiesCallsCount: Int {
|
||||
get {
|
||||
if Thread.isMainThread {
|
||||
return screenPropertiesUnderlyingCallsCount
|
||||
} else {
|
||||
var returnValue: Int? = nil
|
||||
DispatchQueue.main.sync {
|
||||
returnValue = screenPropertiesUnderlyingCallsCount
|
||||
}
|
||||
|
||||
return returnValue!
|
||||
}
|
||||
}
|
||||
set {
|
||||
if Thread.isMainThread {
|
||||
screenPropertiesUnderlyingCallsCount = newValue
|
||||
} else {
|
||||
DispatchQueue.main.sync {
|
||||
screenPropertiesUnderlyingCallsCount = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var screenPropertiesCalled: Bool {
|
||||
return screenPropertiesCallsCount > 0
|
||||
}
|
||||
var screenPropertiesReceivedArguments: (screenTitle: String, properties: [String: Any]?)?
|
||||
var screenPropertiesReceivedInvocations: [(screenTitle: String, properties: [String: Any]?)] = []
|
||||
var screenPropertiesClosure: ((String, [String: Any]?) -> Void)?
|
||||
|
||||
func screen(_ screenTitle: String, properties: [String: Any]?) {
|
||||
if !enabled { return }
|
||||
screenPropertiesCallsCount += 1
|
||||
screenPropertiesReceivedArguments = (screenTitle: screenTitle, properties: properties)
|
||||
screenPropertiesReceivedInvocations.append((screenTitle: screenTitle, properties: properties))
|
||||
screenPropertiesClosure?(screenTitle, properties)
|
||||
}
|
||||
|
||||
func isFeatureEnabled(_ feature: String) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func identify(_ distinctId: String) {
|
||||
|
||||
}
|
||||
|
||||
func identify(_ distinctId: String, userProperties: [String : Any]?) {
|
||||
|
||||
}
|
||||
|
||||
func isOptOut() -> Bool {
|
||||
!enabled
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
class MockPostHogFactory: PostHogFactory {
|
||||
var mock: PostHogProtocol!
|
||||
|
||||
init(mock: PostHogProtocol) {
|
||||
self.mock = mock
|
||||
}
|
||||
|
||||
func createPostHog(config: PostHogConfig) -> PostHogProtocol {
|
||||
mock
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
113
RiotTests/PostHogAnalyticsClientTests.swift
Normal file
113
RiotTests/PostHogAnalyticsClientTests.swift
Normal file
@@ -0,0 +1,113 @@
|
||||
//
|
||||
// Copyright 2024 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
|
||||
@testable import Element
|
||||
import AnalyticsEvents
|
||||
|
||||
class PostHogAnalyticsClientTests: XCTestCase {
|
||||
|
||||
private var posthogMock: MockPostHog!
|
||||
|
||||
override func setUp() {
|
||||
posthogMock = MockPostHog()
|
||||
}
|
||||
|
||||
func testSuperPropertiesAddedToAllCaptured() {
|
||||
let analyticsClient = PostHogAnalyticsClient(posthogFactory: MockPostHogFactory(mock: posthogMock))
|
||||
analyticsClient.start()
|
||||
|
||||
let superProperties = AnalyticsEvent.SuperProperties(appPlatform: .EI, cryptoSDK: .Rust, cryptoSDKVersion: "0.0")
|
||||
|
||||
analyticsClient.updateSuperProperties(superProperties)
|
||||
// It should be the same for any event
|
||||
let someEvent = AnalyticsEvent.CallEnded(durationMs: 0, isVideo: false, numParticipants: 1, placed: true)
|
||||
analyticsClient.capture(someEvent)
|
||||
|
||||
let capturedEvent = posthogMock.capturePropertiesUserPropertiesReceivedArguments
|
||||
|
||||
// All the super properties should have been added
|
||||
XCTAssertEqual(capturedEvent?.properties?["cryptoSDK"] as? String, AnalyticsEvent.SuperProperties.CryptoSDK.Rust.rawValue)
|
||||
XCTAssertEqual(capturedEvent?.properties?["appPlatform"] as? String, AnalyticsEvent.SuperProperties.AppPlatform.EI.rawValue)
|
||||
XCTAssertEqual(capturedEvent?.properties?["cryptoSDKVersion"] as? String, "0.0")
|
||||
|
||||
// Other properties should be there
|
||||
XCTAssertEqual(capturedEvent?.properties?["isVideo"] as? Bool, false)
|
||||
|
||||
// Should also work for screens
|
||||
|
||||
analyticsClient.screen(AnalyticsEvent.MobileScreen.init(durationMs: 0, screenName: .Home))
|
||||
|
||||
|
||||
let capturedScreen = posthogMock.screenPropertiesReceivedArguments
|
||||
|
||||
|
||||
XCTAssertEqual(capturedScreen?.properties?["cryptoSDK"] as? String, AnalyticsEvent.SuperProperties.CryptoSDK.Rust.rawValue)
|
||||
XCTAssertEqual(capturedScreen?.properties?["appPlatform"] as? String, AnalyticsEvent.SuperProperties.AppPlatform.EI.rawValue)
|
||||
XCTAssertEqual(capturedScreen?.properties?["cryptoSDKVersion"] as? String, "0.0")
|
||||
|
||||
|
||||
XCTAssertEqual(capturedScreen?.screenTitle, AnalyticsEvent.MobileScreen.ScreenName.Home.rawValue)
|
||||
|
||||
|
||||
}
|
||||
|
||||
func testSuperPropertiesCanBeUdpated() {
|
||||
let analyticsClient = PostHogAnalyticsClient(posthogFactory: MockPostHogFactory(mock: posthogMock))
|
||||
analyticsClient.start()
|
||||
|
||||
let superProperties = AnalyticsEvent.SuperProperties(appPlatform: .EI, cryptoSDK: .Rust, cryptoSDKVersion: "0.0")
|
||||
|
||||
analyticsClient.updateSuperProperties(superProperties)
|
||||
// It should be the same for any event
|
||||
let someEvent = AnalyticsEvent.CallEnded(durationMs: 0, isVideo: false, numParticipants: 1, placed: true)
|
||||
analyticsClient.capture(someEvent)
|
||||
|
||||
let capturedEvent = posthogMock.capturePropertiesUserPropertiesReceivedArguments
|
||||
|
||||
//
|
||||
XCTAssertEqual(capturedEvent?.properties?["cryptoSDKVersion"] as? String, "0.0")
|
||||
|
||||
analyticsClient.updateSuperProperties(AnalyticsEvent.SuperProperties(appPlatform: .EI, cryptoSDK: .Rust, cryptoSDKVersion: "1.0"))
|
||||
|
||||
|
||||
analyticsClient.capture(someEvent)
|
||||
|
||||
let secondCapturedEvent = posthogMock.capturePropertiesUserPropertiesReceivedArguments
|
||||
|
||||
XCTAssertEqual(secondCapturedEvent?.properties?["cryptoSDKVersion"] as? String, "1.0")
|
||||
}
|
||||
|
||||
func testSuperPropertiesDontOverrideEventProperties() {
|
||||
let analyticsClient = PostHogAnalyticsClient(posthogFactory: MockPostHogFactory(mock: posthogMock))
|
||||
analyticsClient.start()
|
||||
|
||||
// Super property for cryptoSDK is rust
|
||||
let superProperties = AnalyticsEvent.SuperProperties(appPlatform: nil, cryptoSDK: .Rust, cryptoSDKVersion: nil)
|
||||
|
||||
analyticsClient.updateSuperProperties(superProperties)
|
||||
|
||||
// This event as a similar named property `cryptoSDK` with Legacy value
|
||||
let someEvent = AnalyticsEvent.Error(context: nil, cryptoModule: nil, cryptoSDK: .Legacy, domain: .E2EE, eventLocalAgeMillis: nil, isFederated: nil, isMatrixDotOrg: nil, name: .OlmKeysNotSentError, timeToDecryptMillis: nil, userTrustsOwnIdentity: nil, wasVisibleToUser: nil)
|
||||
|
||||
analyticsClient.capture(someEvent)
|
||||
|
||||
let capturedEvent = posthogMock.capturePropertiesUserPropertiesReceivedArguments
|
||||
|
||||
XCTAssertEqual(capturedEvent?.properties?["cryptoSDK"] as? String, AnalyticsEvent.Error.CryptoSDK.Legacy.rawValue)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -42,7 +42,7 @@ cp -R ../../../.. /tmp/$REPO_NAME
|
||||
mv /tmp/$REPO_NAME .
|
||||
else
|
||||
echo "Git clone $REPO_URL with branch/tag $TAG..."
|
||||
git clone $REPO_URL --depth=1 --branch $TAG
|
||||
git clone --recursive $REPO_URL --depth=1 --branch $TAG
|
||||
fi
|
||||
|
||||
cd $REPO_NAME
|
||||
@@ -55,11 +55,6 @@ bundle update
|
||||
# Update fastlane plugins
|
||||
bundle exec fastlane update_plugins
|
||||
|
||||
# Use appropriated dependencies according to the current branch
|
||||
if [ "$LOCAL_SOURCE" != true ]; then
|
||||
bundle exec fastlane point_dependencies_to_same_feature
|
||||
fi
|
||||
|
||||
# Build
|
||||
bundle exec fastlane app_store build_number:$BUILD_NUMBER git_tag:$TAG
|
||||
|
||||
|
||||
1
matrix-ios-sdk
Submodule
1
matrix-ios-sdk
Submodule
Submodule matrix-ios-sdk added at 1c4b3f5c32
@@ -49,7 +49,7 @@ include:
|
||||
packages:
|
||||
AnalyticsEvents:
|
||||
url: https://github.com/matrix-org/matrix-analytics-events
|
||||
exactVersion: 0.15.0
|
||||
exactVersion: 0.23.1
|
||||
Mapbox:
|
||||
url: https://github.com/maplibre/maplibre-gl-native-distribution
|
||||
minVersion: 5.12.2
|
||||
@@ -66,8 +66,8 @@ packages:
|
||||
minVersion: 3.5.0
|
||||
maxVersion: 3.5.0
|
||||
WysiwygComposer:
|
||||
url: https://github.com/matrix-org/matrix-wysiwyg-composer-swift
|
||||
version: 2.29.0
|
||||
url: https://github.com/matrix-org/matrix-rich-text-editor-swift
|
||||
version: 2.37.3
|
||||
DeviceKit:
|
||||
url: https://github.com/devicekit/DeviceKit
|
||||
majorVersion: 4.7.0
|
||||
|
||||
Reference in New Issue
Block a user