Merge pull request #5270 from vector-im/doug/5035_posthog

Replace Matomo with PostHog
This commit is contained in:
Doug 2021-12-16 18:18:01 +00:00 committed by GitHub
commit 62c521a8b2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
112 changed files with 3358 additions and 605 deletions

View file

@ -165,8 +165,20 @@ final class BuildSettings: NSObject {
static let roomsAllowToJoinPublicRooms: Bool = true
// MARK: - Analytics
static let analyticsServerUrl = URL(string: "https://piwik.riot.im/piwik.php")
static let analyticsAppId = "14"
#if DEBUG
/// Host to use for PostHog analytics during development. Set to nil to disable analytics in debug builds.
static let analyticsHost: String? = "https://posthog-poc.lab.element.dev"
/// Public key for submitting analytics during development. Set to nil to disable analytics in debug builds.
static let analyticsKey: String? = "rs-pJjsYJTuAkXJfhaMmPUNBhWliDyTKLOOxike6ck8"
#else
/// Host to use for PostHog analytics. Set to nil to disable analytics.
static let analyticsHost: String? = "https://posthog.hss.element.io"
/// Public key for submitting analytics. Set to nil to disable analytics.
static let analyticsKey: String? = "phc_Jzsm6DTm6V2705zeU5dcNvQDlonOR68XvX2sh1sEOHO"
#endif
/// The URL to open with more information about analytics terms.
static let analyticsTermsURL = URL(string: "https://element.io/cookie-policy")!
// MARK: - Bug report

View file

@ -3,7 +3,7 @@ source 'https://cdn.cocoapods.org/'
# Uncomment this line to define a global platform for your project
platform :ios, '12.1'
# Use frameforks to allow usage of pod written in Swift (like PiwikTracker)
# Use frameworks to allow usage of pods written in Swift
use_frameworks!
# Different flavours of pods to MatrixSDK. Can be one of:
@ -67,8 +67,10 @@ abstract_target 'RiotPods' do
pod 'KeychainAccess', '~> 4.2.2'
pod 'WeakDictionary', '~> 2.0'
# Piwik for analytics
pod 'MatomoTracker', '~> 7.4.1'
# PostHog for analytics
pod 'PostHog', '~> 1.4.4'
pod 'AnalyticsEvents', :git => 'https://github.com/matrix-org/matrix-analytics-events.git', :branch => 'release/swift'
# pod 'AnalyticsEvents', :path => '../matrix-analytics-events/AnalyticsEvents.podspec'
# Remove warnings from "bad" pods
pod 'OLMKit', :inhibit_warnings => true

View file

@ -14,6 +14,7 @@ PODS:
- AFNetworking/Serialization (4.0.1)
- AFNetworking/UIKit (4.0.1):
- AFNetworking/NSURLSession
- AnalyticsEvents (0.1.0)
- BlueCryptor (1.0.32)
- BlueECC (1.2.5)
- BlueRSA (1.0.200)
@ -56,9 +57,6 @@ PODS:
- LoggerAPI (1.9.200):
- Logging (~> 1.1)
- Logging (1.4.0)
- MatomoTracker (7.4.1):
- MatomoTracker/Core (= 7.4.1)
- MatomoTracker/Core (7.4.1)
- MatrixSDK (0.20.15):
- MatrixSDK/Core (= 0.20.15)
- MatrixSDK/Core (0.20.15):
@ -76,6 +74,7 @@ PODS:
- OLMKit/olmcpp (= 3.2.5)
- OLMKit/olmc (3.2.5)
- OLMKit/olmcpp (3.2.5)
- PostHog (1.4.4)
- ReadMoreTextView (3.0.1)
- Realm (10.16.0):
- Realm/Headers (= 10.16.0)
@ -103,6 +102,7 @@ PODS:
- ZXingObjC/All (3.6.5)
DEPENDENCIES:
- AnalyticsEvents (from `https://github.com/matrix-org/matrix-analytics-events.git`, branch `release/swift`)
- DGCollectionViewLeftAlignFlowLayout (~> 1.0.4)
- Down (~> 0.11.0)
- DSWaveformImage (~> 6.1.1)
@ -116,10 +116,10 @@ DEPENDENCIES:
- KeychainAccess (~> 4.2.2)
- KTCenterFlowLayout (~> 1.3.1)
- libPhoneNumber-iOS (~> 0.9.13)
- MatomoTracker (~> 7.4.1)
- MatrixSDK (= 0.20.15)
- MatrixSDK/JingleCallStack (= 0.20.15)
- OLMKit
- PostHog (~> 1.4.4)
- ReadMoreTextView (~> 3.0.1)
- Reusable (~> 4.1)
- SideMenu (~> 6.5)
@ -157,9 +157,9 @@ SPEC REPOS:
- libPhoneNumber-iOS
- LoggerAPI
- Logging
- MatomoTracker
- MatrixSDK
- OLMKit
- PostHog
- ReadMoreTextView
- Realm
- Reusable
@ -173,8 +173,19 @@ SPEC REPOS:
- zxcvbn-ios
- ZXingObjC
EXTERNAL SOURCES:
AnalyticsEvents:
:branch: release/swift
:git: https://github.com/matrix-org/matrix-analytics-events.git
CHECKOUT OPTIONS:
AnalyticsEvents:
:commit: f1805ad7c3fafa7fd9c6e2eaa9e0165f8142ecd2
:git: https://github.com/matrix-org/matrix-analytics-events.git
SPEC CHECKSUMS:
AFNetworking: 7864c38297c79aaca1500c33288e429c3451fdce
AnalyticsEvents: 333bf47d67dc628fadd29ce887b7ac93d8bd6e05
BlueCryptor: b0aee3d9b8f367b49b30de11cda90e1735571c24
BlueECC: 0d18e93347d3ec6d41416de21c1ffa4d4cd3c2cc
BlueRSA: dfeef51db96bcc4edec654956c1581adbda4e6a3
@ -198,9 +209,9 @@ SPEC CHECKSUMS:
libPhoneNumber-iOS: 0a32a9525cf8744fe02c5206eb30d571e38f7d75
LoggerAPI: ad9c4a6f1e32f518fdb43a1347ac14d765ab5e3d
Logging: beeb016c9c80cf77042d62e83495816847ef108b
MatomoTracker: 24a846c9d3aa76933183fe9d47fd62c9efa863fb
MatrixSDK: 2f4d3aacb1c53e2785f0be71d24b8e62e5c5c056
OLMKit: 9fb4799c4a044dd2c06bda31ec31a12191ad30b5
PostHog: 4b6321b521569092d4ef3a02238d9435dbaeb99f
ReadMoreTextView: 19147adf93abce6d7271e14031a00303fe28720d
Realm: b6027801398f3743fc222f096faa85281b506e6c
Reusable: 6bae6a5e8aa793c9c441db0213c863a64bce9136
@ -214,6 +225,6 @@ SPEC CHECKSUMS:
zxcvbn-ios: fef98b7c80f1512ff0eec47ac1fa399fc00f7e3c
ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb
PODFILE CHECKSUM: 989bcc8b1857dc64a9b810ddaf4446903adbe162
PODFILE CHECKSUM: e60814fe2084a7dca3f82c3a1c4a1b763ae822c0
COCOAPODS: 1.11.2

View file

@ -0,0 +1,123 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 1.000000 -1.000000 cm
0.049479 0.742188 0.545395 scn
10.000000 23.000000 m
3.947715 23.000000 -1.000000 18.052284 -1.000000 12.000000 c
1.000000 12.000000 l
1.000000 16.947716 5.052285 21.000000 10.000000 21.000000 c
10.000000 23.000000 l
h
-1.000000 12.000000 m
-1.000000 5.947716 3.947715 1.000000 10.000000 1.000000 c
10.000000 3.000000 l
5.052285 3.000000 1.000000 7.052285 1.000000 12.000000 c
-1.000000 12.000000 l
h
10.000000 1.000000 m
16.052284 1.000000 21.000000 5.947716 21.000000 12.000000 c
19.000000 12.000000 l
19.000000 7.052285 14.947715 3.000000 10.000000 3.000000 c
10.000000 1.000000 l
h
21.000000 12.000000 m
21.000000 18.052284 16.052284 23.000000 10.000000 23.000000 c
10.000000 21.000000 l
14.947715 21.000000 19.000000 16.947716 19.000000 12.000000 c
21.000000 12.000000 l
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 5.545532 4.655060 cm
0.049479 0.742188 0.545395 scn
0.717378 6.153159 m
0.332610 6.549356 -0.300487 6.558620 -0.696684 6.173852 c
-1.092881 5.789084 -1.102146 5.155987 -0.717378 4.759790 c
0.717378 6.153159 l
h
3.257576 2.102139 m
2.540198 1.405455 l
2.728505 1.211555 2.987285 1.102139 3.257576 1.102139 c
3.527867 1.102139 3.786646 1.211555 3.974954 1.405455 c
3.257576 2.102139 l
h
11.626469 9.284243 m
12.011237 9.680439 12.001972 10.313537 11.605776 10.698304 c
11.209579 11.083073 10.576482 11.073808 10.191713 10.677610 c
11.626469 9.284243 l
h
-0.717378 4.759790 m
2.540198 1.405455 l
3.974954 2.798823 l
0.717378 6.153159 l
-0.717378 4.759790 l
h
3.974954 1.405455 m
11.626469 9.284243 l
10.191713 10.677610 l
2.540198 2.798823 l
3.974954 1.405455 l
h
f
n
Q
endstream
endobj
3 0 obj
1679
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 22.000000 22.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000001769 00000 n
0000001792 00000 n
0000001965 00000 n
0000002039 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
2098
%%EOF

View file

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "AnalyticsTick.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View file

@ -0,0 +1,641 @@
%PDF-1.7
1 0 obj
<< /Type /XObject
/Length 2 0 R
/Group << /Type /Group
/S /Transparency
>>
/Subtype /Form
/Resources << /ExtGState << /E2 << /ca 0.400000 >>
/E1 << /ca 0.400000 >>
>> >>
/BBox [ 0.000000 0.000000 119.000000 93.000000 ]
>>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 14.875000 -0.000015 cm
0.049479 0.742188 0.545395 scn
0.000000 44.521278 m
0.000000 69.109695 20.036579 89.042557 44.625000 89.042557 c
44.625000 89.042557 l
69.213417 89.042557 89.250000 69.109695 89.250000 44.521278 c
89.250000 44.521278 l
89.250000 19.932861 69.213417 0.000000 44.625000 0.000000 c
44.625000 0.000000 l
20.036579 0.000000 0.000000 19.932861 0.000000 44.521278 c
0.000000 44.521278 l
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 52.227402 46.594299 cm
1.000000 1.000000 1.000000 scn
0.000000 20.730219 m
0.000000 22.447567 1.395428 23.839752 3.116776 23.839752 c
14.592426 23.839752 23.895279 14.558517 23.895279 3.109533 c
23.895279 1.392185 22.499851 0.000000 20.778503 0.000000 c
19.057156 0.000000 17.661728 1.392185 17.661728 3.109533 c
17.661728 11.123821 11.149731 17.620686 3.116776 17.620686 c
1.395428 17.620686 0.000000 19.012871 0.000000 20.730219 c
h
f*
n
Q
q
-1.000000 0.000000 -0.000000 -1.000000 66.772202 42.448242 cm
1.000000 1.000000 1.000000 scn
0.000000 20.730206 m
0.000000 22.447552 1.395429 23.839737 3.116779 23.839737 c
14.592443 23.839737 23.895306 14.558502 23.895306 3.109520 c
23.895306 1.392172 22.499878 -0.000011 20.778526 -0.000011 c
19.057177 -0.000011 17.661749 1.392172 17.661749 3.109520 c
17.661749 11.123808 11.149744 17.620672 3.116779 17.620672 c
1.395429 17.620672 0.000000 19.012857 0.000000 20.730206 c
h
f*
n
Q
q
-0.000000 1.000000 -1.000000 -0.000000 57.366512 37.265598 cm
1.000000 1.000000 1.000000 scn
0.000000 20.722975 m
0.000000 22.444323 1.392186 23.839752 3.109534 23.839752 c
14.558520 23.839752 23.839758 14.536892 23.839758 3.061234 c
23.839758 1.339886 22.447573 -0.055544 20.730225 -0.055544 c
19.012875 -0.055544 17.620689 1.339886 17.620689 3.061234 c
17.620689 11.094195 11.123824 17.606197 3.109534 17.606197 c
1.392186 17.606197 0.000000 19.001625 0.000000 20.722975 c
h
f*
n
Q
q
-0.000000 -1.000000 1.000000 -0.000000 61.633194 51.777008 cm
1.000000 1.000000 1.000000 scn
0.000000 20.722975 m
0.000000 22.444324 1.392186 23.839752 3.109534 23.839752 c
14.558520 23.839752 23.839758 14.536893 23.839758 3.061237 c
23.839758 1.339888 22.447573 -0.055540 20.730225 -0.055540 c
19.012875 -0.055540 17.620689 1.339888 17.620689 3.061237 c
17.620689 11.094196 11.123824 17.606197 3.109534 17.606197 c
1.392186 17.606197 0.000000 19.001627 0.000000 20.722975 c
h
f*
n
Q
q
1.000000 0.000000 -0.000000 1.000000 28.123089 16.695465 cm
1.000000 1.000000 1.000000 scn
10.448288 0.000008 m
11.057861 0.000008 11.560491 0.448122 11.646045 1.077618 c
12.576446 7.617959 13.464069 8.514189 19.795069 9.229038 c
20.436726 9.303724 20.917965 9.826525 20.917965 10.434681 c
20.917965 11.053506 20.447418 11.554968 19.805763 11.640323 c
13.506846 12.461866 12.694082 13.262072 11.646045 19.802414 c
11.539103 20.431910 11.057861 20.869354 10.448288 20.869354 c
9.849410 20.869354 9.346780 20.431910 9.250531 19.791744 c
8.330826 13.251402 7.443202 12.355172 1.112203 11.640323 c
0.470547 11.565637 0.000000 11.053506 0.000000 10.434681 c
0.000000 9.826525 0.459853 9.314394 1.112203 9.229038 c
7.411120 8.354148 8.191800 7.607290 9.250531 1.066948 c
9.368169 0.437452 9.860105 0.000008 10.448288 0.000008 c
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 28.123089 15.195465 cm
0.049479 0.742188 0.545395 scn
11.646045 2.577618 m
10.903506 2.683247 l
10.902877 2.678619 l
11.646045 2.577618 l
h
19.795069 10.729038 m
19.879219 9.983770 l
19.881781 9.984068 l
19.795069 10.729038 l
h
19.805763 13.140323 m
19.904661 13.883777 l
19.902761 13.884024 l
19.805763 13.140323 l
h
11.646045 21.302414 m
12.386630 21.421087 l
12.385450 21.428030 l
11.646045 21.302414 l
h
9.250531 21.291744 m
8.508834 21.403259 l
8.507838 21.396183 l
9.250531 21.291744 l
h
1.112203 13.140323 m
1.028052 13.885592 l
1.025491 13.885293 l
1.112203 13.140323 l
h
1.112203 10.729038 m
1.215387 11.471931 l
1.209505 11.472700 l
1.112203 10.729038 l
h
9.250531 2.566948 m
8.510169 2.447100 l
8.511623 2.438120 l
8.513294 2.429176 l
9.250531 2.566948 l
h
10.448288 0.750008 m
11.450765 0.750008 12.255557 1.493191 12.389213 2.476614 c
10.902877 2.678619 l
10.865425 2.403053 10.664957 2.250008 10.448288 2.250008 c
10.448288 0.750008 l
h
12.388570 2.471989 m
12.620602 4.103085 12.844038 5.334888 13.138243 6.288948 c
13.429995 7.235054 13.776924 7.858273 14.225985 8.307880 c
15.140809 9.223818 16.666159 9.620979 19.879219 9.983774 c
19.710920 11.474303 l
16.592981 11.122249 14.509018 10.713870 13.164680 9.367895 c
12.484159 8.686545 12.039045 7.814714 11.704848 6.730967 c
11.373105 5.655174 11.136688 4.322321 10.903521 2.683245 c
12.388570 2.471989 l
h
19.881781 9.984068 m
20.873766 10.099530 21.667965 10.918526 21.667965 11.934681 c
20.167965 11.934681 l
20.167965 11.734525 19.999683 11.507918 19.708359 11.474010 c
19.881781 9.984068 l
h
21.667965 11.934681 m
21.667965 12.966555 20.881077 13.753887 19.904659 13.883774 c
19.706867 12.396872 l
20.013762 12.356048 20.167965 12.140457 20.167965 11.934681 c
21.667965 11.934681 l
h
19.902761 13.884024 m
18.330524 14.089085 17.151228 14.287123 16.235826 14.561028 c
15.331247 14.831694 14.731587 15.163220 14.286853 15.607450 c
13.840208 16.053589 13.492528 16.670565 13.191763 17.611713 c
12.888441 18.560867 12.648228 19.788363 12.386598 21.421082 c
10.905493 21.183746 l
11.167881 19.546295 11.422276 18.221136 11.762950 17.155106 c
12.106180 16.081072 12.551881 15.220337 13.226795 14.546188 c
13.903622 13.870131 14.753702 13.438795 15.805837 13.123979 c
16.847151 12.812399 18.131544 12.602333 19.708765 12.396622 c
19.902761 13.884024 l
h
12.385450 21.428030 m
12.223795 22.379583 11.461336 23.119354 10.448288 23.119354 c
10.448288 21.619354 l
10.654386 21.619354 10.854410 21.484234 10.906639 21.176800 c
12.385450 21.428030 l
h
10.448288 23.119354 m
9.455997 23.119354 8.656921 22.387987 8.508867 21.403254 c
9.992196 21.180237 l
10.036638 21.475830 10.242823 21.619354 10.448288 21.619354 c
10.448288 23.119354 l
h
8.507838 21.396183 m
8.278477 19.765110 8.056973 18.533388 7.764218 17.579443 c
7.473907 16.633463 7.127907 16.010515 6.679636 15.561167 c
5.766263 14.645599 4.241331 14.248406 1.028053 13.885587 c
1.196352 12.395059 l
4.314074 12.747088 6.398453 13.155437 7.741569 14.501781 c
8.421542 15.183390 8.865580 16.055490 9.198210 17.139366 c
9.528394 18.215273 9.762733 19.548208 9.993224 21.187307 c
8.507838 21.396183 l
h
1.025491 13.885293 m
0.028613 13.769261 -0.750000 12.956783 -0.750000 11.934681 c
0.750000 11.934681 l
0.750000 12.150229 0.912482 12.362013 1.198914 12.395352 c
1.025491 13.885293 l
h
-0.750000 11.934681 m
-0.750000 10.922595 0.016880 10.115961 1.014900 9.985377 c
1.209505 11.472700 l
0.902826 11.512827 0.750000 11.730454 0.750000 11.934681 c
-0.750000 11.934681 l
h
1.009022 9.986170 m
2.582546 9.767614 3.760892 9.563175 4.675031 9.286510 c
5.578484 9.013078 6.174878 8.682938 6.616406 8.241908 c
7.059544 7.799271 7.404263 7.187467 7.703608 6.250257 c
8.005574 5.304842 8.245770 4.080437 8.510169 2.447100 c
9.990894 2.686794 l
9.725928 4.323629 9.471515 5.645210 9.132493 6.706642 c
8.790851 7.776280 8.348207 8.632186 7.676467 9.303166 c
7.003119 9.975756 6.157125 10.405144 5.109545 10.722197 c
4.072651 11.036015 2.791318 11.253017 1.215384 11.471908 c
1.009022 9.986170 l
h
8.513294 2.429176 m
8.688872 1.489628 9.455215 0.750008 10.448288 0.750008 c
10.448288 2.250008 l
10.264993 2.250008 10.047464 2.385277 9.987769 2.704720 c
8.513294 2.429176 l
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 72.573807 58.608093 cm
1.000000 1.000000 1.000000 scn
8.619835 -0.000011 m
9.122732 -0.000011 9.537402 0.373419 9.607985 0.897997 c
10.375565 6.348283 11.107853 7.095141 16.330927 7.690849 c
16.860292 7.753087 17.257317 8.188755 17.257317 8.695551 c
17.257317 9.211239 16.869114 9.629124 16.339748 9.700253 c
11.143145 10.384872 10.472614 11.051710 9.607985 16.501997 c
9.519756 17.026575 9.122732 17.391113 8.619835 17.391113 c
8.125761 17.391113 7.711091 17.026575 7.631686 16.493105 c
6.872929 11.042820 6.140640 10.295961 0.917567 9.700253 c
0.388201 9.638015 0.000000 9.211239 0.000000 8.695551 c
0.000000 8.188755 0.379379 7.761978 0.917567 7.690849 c
6.114172 6.961774 6.758233 6.339392 7.631686 0.889107 c
7.728737 0.364527 8.134583 -0.000011 8.619835 -0.000011 c
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 72.573807 57.608093 cm
0.049479 0.742188 0.545395 scn
9.607985 1.897997 m
9.112861 1.967726 l
9.112450 1.964672 l
9.607985 1.897997 l
h
16.330927 8.690849 m
16.387587 8.194067 l
16.389311 8.194269 l
16.330927 8.690849 l
h
16.339748 10.700253 m
16.406334 11.195801 l
16.405056 11.195970 l
16.339748 10.700253 l
h
9.607985 17.501997 m
10.101830 17.580339 l
10.101059 17.584925 l
9.607985 17.501997 l
h
7.631686 17.493105 m
7.137113 17.566721 l
7.136462 17.562048 l
7.631686 17.493105 l
h
0.917567 10.700253 m
0.860907 11.197035 l
0.859183 11.196833 l
0.917567 10.700253 l
h
0.917567 8.690849 m
0.987038 9.186015 l
0.983079 9.186539 l
0.917567 8.690849 l
h
7.631686 1.889107 m
7.137843 1.809963 l
7.140029 1.798147 l
7.631686 1.889107 l
h
8.619835 0.499989 m
9.388696 0.499989 10.001672 1.074373 10.103518 1.831324 c
9.112450 1.964672 l
9.073133 1.672462 8.856770 1.499989 8.619835 1.499989 c
8.619835 0.499989 l
h
10.103099 1.828268 m
10.294624 3.188222 10.480069 4.223436 10.725984 5.028956 c
10.970345 5.829387 11.264832 6.369568 11.654185 6.763333 c
12.442908 7.560994 13.744701 7.892640 16.387587 8.194070 c
16.274267 9.187629 l
13.694078 8.893350 12.018190 8.553713 10.943106 7.466446 c
10.400556 6.917747 10.041607 6.212054 9.769561 5.320940 c
9.499068 4.434915 9.305134 3.332915 9.112870 1.967726 c
10.103099 1.828268 l
h
16.389311 8.194269 m
17.155991 8.284410 17.757317 8.920855 17.757317 9.695551 c
16.757317 9.695551 l
16.757317 9.456655 16.564592 9.221766 16.272543 9.187428 c
16.389311 8.194269 l
h
17.757317 9.695551 m
17.757317 10.482604 17.162529 11.094193 16.406334 11.195800 c
16.273165 10.204706 l
16.575699 10.164056 16.757317 9.939874 16.757317 9.695551 c
17.757317 9.695551 l
h
16.405056 11.195970 m
15.107601 11.366901 14.126670 11.532858 13.361839 11.764020 c
12.604449 11.992933 12.090017 12.277006 11.704502 12.665974 c
11.317406 13.056538 11.022324 13.591100 10.770527 14.386979 c
10.517109 15.187984 10.317721 16.219311 10.101810 17.580338 c
9.114160 17.423656 l
9.330563 16.059540 9.539227 14.963654 9.817105 14.085340 c
10.096603 13.201900 10.456060 12.505035 10.994250 11.962027 c
11.534022 11.417421 12.215625 11.065775 13.072525 10.806786 c
13.921983 10.550046 14.973595 10.375915 16.274443 10.204536 c
16.405056 11.195970 l
h
10.101059 17.584925 m
9.977288 18.320837 9.395552 18.891113 8.619835 18.891113 c
8.619835 17.891113 l
8.849913 17.891113 9.062225 17.732313 9.114909 17.419067 c
10.101059 17.584925 l
h
8.619835 18.891113 m
7.859531 18.891113 7.250215 18.326427 7.137135 17.566717 c
8.126238 17.419493 l
8.171968 17.726725 8.391990 17.891113 8.619835 17.891113 c
8.619835 18.891113 l
h
7.136462 17.562048 m
6.947139 16.202108 6.763302 15.166948 6.518592 14.361504 c
6.275430 13.561155 5.981721 13.021152 5.592998 12.627558 c
4.805452 11.830145 3.503936 11.498478 0.860908 11.197033 c
0.974226 10.203474 l
3.554271 10.497736 5.230436 10.837353 6.304493 11.924868 c
6.846570 12.473737 7.204643 13.179608 7.475406 14.070805 c
7.744622 14.956905 7.936855 16.058958 8.126910 17.424164 c
7.136462 17.562048 l
h
0.859183 11.196833 m
0.089259 11.106312 -0.500000 10.475979 -0.500000 9.695551 c
0.500000 9.695551 l
0.500000 9.946500 0.687143 10.169718 0.975950 10.203673 c
0.859183 11.196833 l
h
-0.500000 9.695551 m
-0.500000 8.923503 0.079786 8.297226 0.852054 8.195160 c
0.983079 9.186539 l
0.678971 9.226731 0.500000 9.454006 0.500000 9.695551 c
-0.500000 9.695551 l
h
0.848098 8.195699 m
2.146421 8.013546 3.126409 7.842214 3.889960 7.608789 c
4.646159 7.377612 5.157835 7.094736 5.540681 6.708459 c
5.924912 6.320785 6.217544 5.790504 6.468155 4.997945 c
6.720429 4.200129 6.919805 3.171420 7.137986 1.809986 c
8.125387 1.968225 l
7.906841 3.331934 7.698164 4.424881 7.421624 5.299438 c
7.143422 6.179253 6.786478 6.872063 6.250936 7.412404 c
5.714010 7.954142 5.035716 8.304206 4.182313 8.565100 c
3.336262 8.823746 2.287016 9.003614 0.987036 9.186000 c
0.848098 8.195699 l
h
7.140029 1.798147 m
7.274719 1.070122 7.860904 0.499989 8.619835 0.499989 c
8.619835 1.499989 l
8.408263 1.499989 8.182755 1.658932 8.123343 1.980066 c
7.140029 1.798147 l
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 107.227104 75.129669 cm
0.049479 0.742188 0.545395 scn
5.619252 -0.000010 m
5.947090 -0.000010 6.217412 0.238985 6.263425 0.574716 c
6.763808 4.062898 7.241186 4.540888 10.646096 4.922141 c
10.991189 4.961974 11.250008 5.240800 11.250008 5.565150 c
11.250008 5.895190 10.996941 6.162637 10.651848 6.208159 c
7.264192 6.646316 6.827075 7.073092 6.263425 10.561275 c
6.205909 10.897006 5.947090 11.130310 5.619252 11.130310 c
5.297166 11.130310 5.026845 10.897006 4.975080 10.555585 c
4.480448 7.067402 4.003070 6.589413 0.598160 6.208159 c
0.253068 6.168327 0.000000 5.895190 0.000000 5.565150 c
0.000000 5.240800 0.247316 4.967664 0.598160 4.922141 c
3.985816 4.455533 4.405678 4.057208 4.975080 0.569025 c
5.038347 0.233294 5.302918 -0.000010 5.619252 -0.000010 c
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 103.008408 84.868683 cm
0.049479 0.742188 0.545395 scn
3.160831 -0.000001 m
3.345240 -0.000001 3.497297 0.134433 3.523178 0.323282 c
3.804644 2.285384 4.073170 2.554254 5.988433 2.768708 c
6.182547 2.791114 6.328133 2.947954 6.328133 3.130401 c
6.328133 3.316049 6.185782 3.466487 5.991668 3.492094 c
4.086111 3.738557 3.840232 3.978619 3.523178 5.940721 c
3.490826 6.129570 3.345240 6.260803 3.160831 6.260803 c
2.979658 6.260803 2.827601 6.129570 2.798484 5.937521 c
2.520254 3.975418 2.251728 3.706549 0.336465 3.492094 c
0.142351 3.469688 0.000000 3.316049 0.000000 3.130401 c
0.000000 2.947954 0.139115 2.794315 0.336465 2.768708 c
2.242023 2.506241 2.478195 2.282184 2.798484 0.320081 c
2.834072 0.131233 2.982893 -0.000001 3.160831 -0.000001 c
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 103.711479 69.564484 cm
0.049479 0.742188 0.545395 scn
3.863233 0.000004 m
4.088622 0.000004 4.274468 0.164312 4.306101 0.395127 c
4.650115 2.793253 4.978312 3.121871 7.319186 3.383983 c
7.556437 3.411368 7.734375 3.603061 7.734375 3.826052 c
7.734375 4.052955 7.560391 4.236824 7.323141 4.268121 c
4.994129 4.569354 4.693611 4.862762 4.306101 7.260888 c
4.266560 7.491703 4.088622 7.652100 3.863233 7.652100 c
3.641799 7.652100 3.455953 7.491703 3.420365 7.256976 c
3.080306 4.858850 2.752109 4.530232 0.411235 4.268121 c
0.173984 4.240736 0.000000 4.052955 0.000000 3.826052 c
0.000000 3.603061 0.170030 3.415280 0.411235 3.383983 c
2.740246 3.063190 3.028902 2.789341 3.420365 0.391215 c
3.463861 0.160400 3.645753 0.000004 3.863233 0.000004 c
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 -0.070938 78.607880 cm
0.049479 0.742188 0.545395 scn
5.268045 -0.000012 m
5.575393 -0.000012 5.828820 0.224045 5.871957 0.538792 c
6.341066 3.808963 6.788608 4.257079 9.980709 4.614503 c
10.304233 4.651846 10.546875 4.913247 10.546875 5.217325 c
10.546875 5.526737 10.309625 5.777468 9.986101 5.820146 c
6.810176 6.230918 6.400379 6.631021 5.871957 9.901192 c
5.818036 10.215940 5.575393 10.434662 5.268045 10.434662 c
4.966090 10.434662 4.712663 10.215940 4.664135 9.895857 c
4.200418 6.625686 3.752876 6.177571 0.560775 5.820146 c
0.237251 5.782803 0.000000 5.526737 0.000000 5.217325 c
0.000000 4.913247 0.231859 4.657181 0.560775 4.614503 c
3.736700 4.177058 4.130321 3.803629 4.664135 0.533457 c
4.723447 0.218710 4.971482 -0.000012 5.268045 -0.000012 c
h
f
n
Q
q
/E1 gs
1.000000 0.000000 -0.000000 1.000000 9.772858 88.346924 cm
0.049479 0.742188 0.545395 scn
2.458421 -0.000008 m
2.601850 -0.000008 2.720115 0.104552 2.740246 0.251434 c
2.959164 1.777514 3.168016 1.986634 4.657663 2.153433 c
4.808641 2.170859 4.921874 2.292846 4.921874 2.434749 c
4.921874 2.579142 4.811157 2.696150 4.660179 2.716066 c
3.178081 2.907759 2.986843 3.094474 2.740246 4.620554 c
2.715083 4.767436 2.601850 4.869507 2.458421 4.869507 c
2.317508 4.869507 2.199242 4.767436 2.176596 4.618064 c
1.960194 3.091985 1.751342 2.882864 0.261695 2.716066 c
0.110717 2.698639 0.000000 2.579142 0.000000 2.434749 c
0.000000 2.292846 0.108201 2.173349 0.261695 2.153433 c
1.743793 1.949291 1.927482 1.775024 2.176596 0.248944 c
2.204275 0.102062 2.320024 -0.000008 2.458421 -0.000008 c
h
f
n
Q
q
/E2 gs
1.000000 0.000000 -0.000000 1.000000 13.288479 82.086121 cm
0.049479 0.742188 0.545395 scn
2.458421 -0.000008 m
2.601850 -0.000008 2.720116 0.104552 2.740247 0.251434 c
2.959164 1.777514 3.168017 1.986634 4.657664 2.153433 c
4.808642 2.170859 4.921875 2.292846 4.921875 2.434749 c
4.921875 2.579142 4.811158 2.696150 4.660181 2.716066 c
3.178082 2.907759 2.986844 3.094474 2.740247 4.620554 c
2.715084 4.767436 2.601850 4.869507 2.458421 4.869507 c
2.317509 4.869507 2.199243 4.767436 2.176596 4.618064 c
1.960195 3.091985 1.751342 2.882864 0.261695 2.716066 c
0.110717 2.698639 0.000000 2.579142 0.000000 2.434749 c
0.000000 2.292846 0.108201 2.173349 0.261695 2.153433 c
1.743793 1.949291 1.927483 1.775024 2.176596 0.248944 c
2.204276 0.102062 2.320025 -0.000008 2.458421 -0.000008 c
h
f
n
Q
endstream
endobj
2 0 obj
17245
endobj
3 0 obj
<< /Type /XObject
/Length 4 0 R
/Group << /Type /Group
/S /Transparency
>>
/Subtype /Form
/Resources << >>
/BBox [ 0.000000 0.000000 119.000000 93.000000 ]
>>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
0.000000 0.000000 0.000000 scn
0.000000 93.000000 m
119.000000 93.000000 l
119.000000 0.000000 l
0.000000 0.000000 l
0.000000 93.000000 l
h
f
n
Q
endstream
endobj
4 0 obj
234
endobj
5 0 obj
<< /XObject << /X1 1 0 R >>
/ExtGState << /E1 << /SMask << /Type /Mask
/G 3 0 R
/S /Alpha
>>
/Type /ExtGState
>> >>
>>
endobj
6 0 obj
<< /Length 7 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
/E1 gs
/X1 Do
Q
endstream
endobj
7 0 obj
46
endobj
8 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 119.000000 93.000000 ]
/Resources 5 0 R
/Contents 6 0 R
/Parent 9 0 R
>>
endobj
9 0 obj
<< /Kids [ 8 0 R ]
/Count 1
/Type /Pages
>>
endobj
10 0 obj
<< /Pages 9 0 R
/Type /Catalog
>>
endobj
xref
0 11
0000000000 65535 f
0000000010 00000 n
0000017630 00000 n
0000017654 00000 n
0000018137 00000 n
0000018159 00000 n
0000018457 00000 n
0000018559 00000 n
0000018580 00000 n
0000018754 00000 n
0000018828 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 10 0 R
/Size 11
>>
startxref
18888
%%EOF

View file

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "AnalyticsLogo.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View file

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -41,6 +41,7 @@
"retry" = "Retry";
"on" = "On";
"off" = "Off";
"enable" = "Enable";
"cancel" = "Cancel";
"save" = "Save";
"join" = "Join";
@ -77,6 +78,7 @@
// Accessibility
"accessibility_checkbox_label" = "checkbox";
"accessibility_button_label" = "button";
// Authentication
"auth_login" = "Log in";
@ -577,7 +579,7 @@ Tap the + to start adding people.";
"settings_term_conditions" = "Terms & Conditions";
"settings_privacy_policy" = "Privacy Policy";
"settings_third_party_notices" = "Third-party Notices";
"settings_send_crash_report" = "Send anon crash & usage data";
"settings_analytics_and_crash_data" = "Send crash and analytics data";
"settings_enable_rageshake" = "Rage shake to report bug";
"settings_clear_cache" = "Clear cache";
@ -945,8 +947,24 @@ Tap the + to start adding people.";
"no_voip_title" = "Incoming call";
"no_voip" = "%@ is calling you but %@ does not support calls yet.\nYou can ignore this notification and answer the call from another device or you can reject it.";
// Crash report
"google_analytics_use_prompt" = "Would you like to help improve %@ by automatically reporting anonymous crash reports and usage data?";
// Analytics
"analytics_prompt_title" = "Help improve %@";
"analytics_prompt_message_new_user" = "Help us identify issues and improve Element by sharing anonymous usage data. To understand how people use multiple devices, well generate a random identifier, shared by your devices.";
"analytics_prompt_message_upgrade" = "You previously consented to share anonymous usage data with us. Now, to help understand how people use multiple devices, well generate a random identifier, shared by your devices.";
/* Note: The placeholder is for the contents of analytics_prompt_terms_link_new_user */
"analytics_prompt_terms_new_user" = "You can read all our terms %@.";
"analytics_prompt_terms_link_new_user" = "here";
/* Note: The placeholder is for the contents of analytics_prompt_terms_link_upgrade */
"analytics_prompt_terms_upgrade" = "Read all our terms %@. Is that OK?";
"analytics_prompt_terms_link_upgrade" = "here";
/* Note: The word "don't" is formatted in bold */
"analytics_prompt_point_1" = "We <b>don't</b> record or profile any account data";
/* Note: The word "don't" is formatted in bold */
"analytics_prompt_point_2" = "We <b>don't</b> share information with third parties";
"analytics_prompt_point_3" = "You can turn this off anytime in settings";
"analytics_prompt_not_now" = "Not now";
"analytics_prompt_yes" = "Yes, that's fine";
"analytics_prompt_stop" = "Stop sharing";
// Crypto
"e2e_enabling_on_app_update" = "%@ now supports end-to-end encryption but you need to log in again to enable it.\n\nYou can do it now or later from the application settings.";

View file

@ -1897,6 +1897,34 @@ Library.
SOFTWARE.
<br/><br/>
</li>
<li>
<b>PostHog iOS</b> (<a href="https://github.com/PostHog/posthog-ios">https://github.com/PostHog/posthog-ios</a>)
<br/><br/>
The MIT License (MIT)
<br/><br/>
Copyright (c) 2020 PostHog (part of Hiberly Inc)
<br/><br/>
Copyright (c) 2016 Segment.io, Inc.
<br/><br/>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
<br/><br/>
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
<br/><br/>
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
<br/><br/>
</li>
</ul>
</body>
</html>

View file

@ -20,6 +20,8 @@ internal typealias AssetImageTypeAlias = ImageAsset.Image
// swiftlint:disable identifier_name line_length nesting type_body_length type_name
internal enum Asset {
internal enum Images {
internal static let analyticsCheckmark = ImageAsset(name: "AnalyticsCheckmark")
internal static let analyticsLogo = ImageAsset(name: "AnalyticsLogo")
internal static let socialLoginButtonApple = ImageAsset(name: "social_login_button_apple")
internal static let socialLoginButtonFacebook = ImageAsset(name: "social_login_button_facebook")
internal static let socialLoginButtonGithub = ImageAsset(name: "social_login_button_github")

View file

@ -15,6 +15,10 @@ public class VectorL10n: NSObject {
public static var accept: String {
return VectorL10n.tr("Vector", "accept")
}
/// button
public static var accessibilityButtonLabel: String {
return VectorL10n.tr("Vector", "accessibility_button_label")
}
/// checkbox
public static var accessibilityCheckboxLabel: String {
return VectorL10n.tr("Vector", "accessibility_checkbox_label")
@ -31,6 +35,58 @@ public class VectorL10n: NSObject {
public static func activeCallDetails(_ p1: String) -> String {
return VectorL10n.tr("Vector", "active_call_details", p1)
}
/// Help us identify issues and improve Element by sharing anonymous usage data. To understand how people use multiple devices, well generate a random identifier, shared by your devices.
public static var analyticsPromptMessageNewUser: String {
return VectorL10n.tr("Vector", "analytics_prompt_message_new_user")
}
/// You previously consented to share anonymous usage data with us. Now, to help understand how people use multiple devices, well generate a random identifier, shared by your devices.
public static var analyticsPromptMessageUpgrade: String {
return VectorL10n.tr("Vector", "analytics_prompt_message_upgrade")
}
/// Not now
public static var analyticsPromptNotNow: String {
return VectorL10n.tr("Vector", "analytics_prompt_not_now")
}
/// We <b>don't</b> record or profile any account data
public static var analyticsPromptPoint1: String {
return VectorL10n.tr("Vector", "analytics_prompt_point_1")
}
/// We <b>don't</b> share information with third parties
public static var analyticsPromptPoint2: String {
return VectorL10n.tr("Vector", "analytics_prompt_point_2")
}
/// You can turn this off anytime in settings
public static var analyticsPromptPoint3: String {
return VectorL10n.tr("Vector", "analytics_prompt_point_3")
}
/// Stop sharing
public static var analyticsPromptStop: String {
return VectorL10n.tr("Vector", "analytics_prompt_stop")
}
/// here
public static var analyticsPromptTermsLinkNewUser: String {
return VectorL10n.tr("Vector", "analytics_prompt_terms_link_new_user")
}
/// here
public static var analyticsPromptTermsLinkUpgrade: String {
return VectorL10n.tr("Vector", "analytics_prompt_terms_link_upgrade")
}
/// You can read all our terms %@.
public static func analyticsPromptTermsNewUser(_ p1: String) -> String {
return VectorL10n.tr("Vector", "analytics_prompt_terms_new_user", p1)
}
/// Read all our terms %@. Is that OK?
public static func analyticsPromptTermsUpgrade(_ p1: String) -> String {
return VectorL10n.tr("Vector", "analytics_prompt_terms_upgrade", p1)
}
/// Help improve %@
public static func analyticsPromptTitle(_ p1: String) -> String {
return VectorL10n.tr("Vector", "analytics_prompt_title", p1)
}
/// Yes, that's fine
public static var analyticsPromptYes: String {
return VectorL10n.tr("Vector", "analytics_prompt_yes")
}
/// Please review and accept the policies of this homeserver:
public static var authAcceptPolicies: String {
return VectorL10n.tr("Vector", "auth_accept_policies")
@ -1235,6 +1291,10 @@ public class VectorL10n: NSObject {
public static var emojiPickerTitle: String {
return VectorL10n.tr("Vector", "emoji_picker_title")
}
/// Enable
public static var enable: String {
return VectorL10n.tr("Vector", "enable")
}
/// Send an encrypted message
public static var encryptedRoomMessagePlaceholder: String {
return VectorL10n.tr("Vector", "encrypted_room_message_placeholder")
@ -1439,10 +1499,6 @@ public class VectorL10n: NSObject {
public static var gdprConsentNotGivenAlertReviewNowAction: String {
return VectorL10n.tr("Vector", "gdpr_consent_not_given_alert_review_now_action")
}
/// Would you like to help improve %@ by automatically reporting anonymous crash reports and usage data?
public static func googleAnalyticsUsePrompt(_ p1: String) -> String {
return VectorL10n.tr("Vector", "google_analytics_use_prompt", p1)
}
/// Home
public static var groupDetailsHome: String {
return VectorL10n.tr("Vector", "group_details_home")
@ -4227,6 +4283,10 @@ public class VectorL10n: NSObject {
public static var settingsAdvanced: String {
return VectorL10n.tr("Vector", "settings_advanced")
}
/// Send crash and analytics data
public static var settingsAnalyticsAndCrashData: String {
return VectorL10n.tr("Vector", "settings_analytics_and_crash_data")
}
/// Call invitations
public static var settingsCallInvitations: String {
return VectorL10n.tr("Vector", "settings_call_invitations")
@ -4755,10 +4815,6 @@ public class VectorL10n: NSObject {
public static var settingsSecurity: String {
return VectorL10n.tr("Vector", "settings_security")
}
/// Send anon crash & usage data
public static var settingsSendCrashReport: String {
return VectorL10n.tr("Vector", "settings_send_crash_report")
}
/// SENDING IMAGES AND VIDEOS
public static var settingsSendingMedia: String {
return VectorL10n.tr("Vector", "settings_sending_media")

View file

@ -1,65 +0,0 @@
/*
Copyright 2018 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/Foundation.h>
#import <MatrixSDK/MatrixSDK.h>
// Metrics related to notifications
FOUNDATION_EXPORT NSString *const AnalyticsNoficationsCategory;
FOUNDATION_EXPORT NSString *const AnalyticsNoficationsTimeToDisplayContent;
/**
The analytics value for accept/decline of the identity server's terms.
*/
FOUNDATION_EXPORT NSString *const AnalyticsContactsIdentityServerAccepted;
/**
`Analytics` sends analytics to an analytics tool.
*/
@interface Analytics : NSObject <MXAnalyticsDelegate>
/**
Returns the shared Analytics manager.
@return the shared Analytics manager.
*/
+ (instancetype)sharedInstance;
/**
Start doing analytics if the settings `enableCrashReport` is enabled.
*/
- (void)start;
/**
Stop doing analytics.
*/
- (void)stop;
/**
Track a screen display.
@param screenName the name of the displayed screen.
*/
- (void)trackScreen:(NSString*)screenName;
/**
Flush analytics data.
*/
- (void)dispatch;
@end

View file

@ -1,162 +0,0 @@
/*
Copyright 2018 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 "Analytics.h"
#import "GeneratedInterface-Swift.h"
NSString *const AnalyticsNoficationsCategory = @"notifications";
NSString *const AnalyticsNoficationsTimeToDisplayContent = @"timelineDisplay";
NSString *const AnalyticsContactsIdentityServerAccepted = @"identityServerAccepted";
// Duration data will be visible under the Piwik category called "Performance".
// Other values will be visible in "Metrics".
// Some Matomo screenshots are available at https://github.com/vector-im/element-ios/pull/3789.
NSString *const kAnalyticsPerformanceCategory = @"Performance";
NSString *const kAnalyticsMetricsCategory = @"Metrics";
@import MatomoTracker;
@interface Analytics ()
{
MatomoTracker *matomoTracker;
}
@end
@implementation Analytics
+ (instancetype)sharedInstance
{
static Analytics *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[Analytics alloc] init];
});
return sharedInstance;
}
- (instancetype)init
{
self = [super init];
if (self)
{
matomoTracker = [[MatomoTracker alloc] initWithSiteId:BuildSettings.analyticsAppId
baseURL:BuildSettings.analyticsServerUrl
userAgent:@"iOSMatomoTracker"];
[self migrateFromFourPointFourSharedInstance];
}
return self;
}
- (void)migrateFromFourPointFourSharedInstance
{
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"migratedFromFourPointFourSharedInstance"]) return;
[matomoTracker copyFromOldSharedInstance];
[[NSUserDefaults standardUserDefaults] setBool:true forKey:@"migratedFromFourPointFourSharedInstance"];
}
- (void)start
{
// Check whether the user has enabled the sending of crash reports.
if (RiotSettings.shared.enableCrashReport)
{
matomoTracker.isOptedOut = NO;
[matomoTracker setCustomVariableWithIndex:1 name:@"App Platform" value:@"iOS Platform"];
[matomoTracker setCustomVariableWithIndex:2 name:@"App Version" value:[AppDelegate theDelegate].appVersion];
// The language is either the one selected by the user within the app
// or, else, the one configured by the OS
NSString *language = [NSBundle mxk_language] ? [NSBundle mxk_language] : [[NSBundle mainBundle] preferredLocalizations][0];
[matomoTracker setCustomVariableWithIndex:4 name:@"Chosen Language" value:language];
MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject;
if (account)
{
[matomoTracker setCustomVariableWithIndex:7 name:@"Homeserver URL" value:account.mxCredentials.homeServer];
[matomoTracker setCustomVariableWithIndex:8 name:@"Identity Server URL" value:account.identityServerURL];
}
// TODO: We should also track device and os version
// But that needs to be decided for all platforms
// Catch and log crashes
[MXLogger logCrashes:YES];
[MXLogger setBuildVersion:[AppDelegate theDelegate].build];
#ifdef DEBUG
// Disable analytics in debug as it pollutes stats
matomoTracker.isOptedOut = YES;
#endif
}
else
{
MXLogDebug(@"[AppDelegate] The user decided to not send analytics");
matomoTracker.isOptedOut = YES;
[MXLogger logCrashes:NO];
}
}
- (void)stop
{
matomoTracker.isOptedOut = YES;
[MXLogger logCrashes:NO];
}
- (void)trackScreen:(NSString *)screenName
{
// Use the same pattern as Android
NSString *appName = [[NSBundle mainBundle] infoDictionary][@"CFBundleDisplayName"];
NSString *appVersion = [AppDelegate theDelegate].appVersion;
[matomoTracker trackWithView:@[@"ios", appName, appVersion, screenName]
url:nil];
}
- (void)dispatch
{
[matomoTracker dispatch];
}
#pragma mark - MXAnalyticsDelegate
- (void)trackDuration:(NSTimeInterval)seconds category:(NSString*)category name:(NSString*)name
{
// Report time in ms to make figures look better in Matomo
NSNumber *value = @(seconds * 1000);
[matomoTracker trackWithEventWithCategory:kAnalyticsPerformanceCategory
action:category
name:name
number:value
url:nil];
}
- (void)trackValue:(NSNumber*)value category:(NSString*)category name:(NSString*)name
{
[matomoTracker trackWithEventWithCategory:kAnalyticsMetricsCategory
action:category
name:name
number:value
url:nil];
}
@end

View file

@ -1,51 +0,0 @@
/*
Copyright 2018 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/Foundation.h>
/**
Failure reasons as defined in https://docs.google.com/document/d/1es7cTCeJEXXfRCTRgZerAM2Wg5ZerHjvlpfTW-gsOfI.
*/
struct DecryptionFailureReasonStruct
{
__unsafe_unretained NSString * const unspecified;
__unsafe_unretained NSString * const olmKeysNotSent;
__unsafe_unretained NSString * const olmIndexError;
__unsafe_unretained NSString * const unexpected;
};
extern const struct DecryptionFailureReasonStruct DecryptionFailureReason;
/**
`DecryptionFailure` represents a decryption failure.
*/
@interface DecryptionFailure : NSObject
/**
The id of the event that was unabled to decrypt.
*/
@property (nonatomic) NSString *failedEventId;
/**
The time the failure has been reported.
*/
@property (nonatomic, readonly) NSTimeInterval ts;
/**
Decryption failure reason.
*/
@property (nonatomic) NSString *reason;
@end

View file

@ -1,38 +0,0 @@
/*
Copyright 2018 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 "DecryptionFailure.h"
const struct DecryptionFailureReasonStruct DecryptionFailureReason = {
.unspecified = @"unspecified_error",
.olmKeysNotSent = @"olm_keys_not_sent_error",
.olmIndexError = @"olm_index_error",
.unexpected = @"unexpected_error"
};
@implementation DecryptionFailure
- (instancetype)init
{
self = [super init];
if (self)
{
_ts = [NSDate date].timeIntervalSince1970;
}
return self;
}
@end

View file

@ -23,7 +23,8 @@ final class RiotSettings: NSObject {
// MARK: - Constants
public enum UserDefaultsKeys {
static let enableCrashReport = "enableCrashReport"
static let enableAnalytics = "enableAnalytics"
static let matomoAnalytics = "enableCrashReport"
static let notificationsShowDecryptedContent = "showDecryptedContent"
static let allowStunServerFallback = "allowStunServerFallback"
static let pinRoomsWithMissedNotificationsOnHome = "pinRoomsWithMissedNotif"
@ -100,13 +101,31 @@ final class RiotSettings: NSObject {
// MARK: Other
/// Indicate if `enableCrashReport` settings has been set once.
var isEnableCrashReportHasBeenSetOnce: Bool {
return RiotSettings.defaults.object(forKey: UserDefaultsKeys.enableCrashReport) != nil
/// Whether the user was previously shown the Matomo analytics prompt.
var hasSeenAnalyticsPrompt: Bool {
RiotSettings.defaults.object(forKey: UserDefaultsKeys.enableAnalytics) != nil
}
@UserDefault(key: UserDefaultsKeys.enableCrashReport, defaultValue: false, storage: defaults)
var enableCrashReport
/// Whether the user has both seen the Matomo analytics prompt and declined it.
var hasDeclinedMatomoAnalytics: Bool {
RiotSettings.defaults.object(forKey: UserDefaultsKeys.matomoAnalytics) != nil && !RiotSettings.defaults.bool(forKey: UserDefaultsKeys.matomoAnalytics)
}
/// Whether the user previously accepted the Matomo analytics prompt.
/// This allows these users to be shown a different prompt to explain the changes.
var hasAcceptedMatomoAnalytics: Bool {
RiotSettings.defaults.bool(forKey: UserDefaultsKeys.matomoAnalytics)
}
/// `true` when the user has opted in to send analytics.
@UserDefault(key: UserDefaultsKeys.enableAnalytics, defaultValue: false, storage: defaults)
var enableAnalytics
/// Indicates if the device has already called identify for this session to PostHog.
/// This is separate to `enableAnalytics` as logging out will leave analytics
/// enabled but reset identification.
@UserDefault(key: "isIdentifiedForAnalytics", defaultValue: false, storage: defaults)
var isIdentifiedForAnalytics
@UserDefault(key: "enableRageShake", defaultValue: false, storage: defaults)
var enableRageShake

View file

@ -0,0 +1,259 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import PostHog
import AnalyticsEvents
/// A class responsible for managing an analytics client
/// and sending events through this client.
@objcMembers class Analytics: NSObject {
// MARK: - Properties
/// The singleton instance to be used within the Riot target.
static let shared = Analytics()
/// The analytics client to send events with.
private var client: AnalyticsClientProtocol = PostHogAnalyticsClient()
/// The service used to interact with account data settings.
private var service: AnalyticsService?
/// Whether or not the object is enabled and sending events to the server.
var isRunning: Bool { client.isRunning }
/// Whether to show the user the analytics opt in prompt.
var shouldShowAnalyticsPrompt: Bool {
// Only show the prompt once, and when analytics are configured in BuildSettings.
!RiotSettings.shared.hasSeenAnalyticsPrompt && PHGPostHogConfiguration.standard != nil
}
/// Indicates whether the user previously accepted Matomo analytics and should be shown the upgrade prompt.
var promptShouldDisplayUpgradeMessage: Bool {
RiotSettings.shared.hasAcceptedMatomoAnalytics
}
// MARK: - Public
/// Opts in to analytics tracking with the supplied session.
/// - Parameter session: An optional session to use to when reading/generating the analytics ID.
/// The session will be ignored if not running.
func optIn(with session: MXSession?) {
RiotSettings.shared.enableAnalytics = true
startIfEnabled()
guard let session = session else { return }
useAnalyticsSettings(from: session)
}
/// Stops analytics tracking and calls `reset` to clear any IDs and event queues.
func optOut() {
RiotSettings.shared.enableAnalytics = false
// The order is important here. PostHog ignores the reset if stopped.
reset()
client.stop()
MXLog.debug("[Analytics] Stopped.")
}
/// Starts the analytics client if the user has opted in, otherwise does nothing.
func startIfEnabled() {
guard RiotSettings.shared.enableAnalytics, !isRunning else { return }
client.start()
// Sanity check in case something went wrong.
guard client.isRunning else { return }
MXLog.debug("[Analytics] Started.")
// Catch and log crashes
MXLogger.logCrashes(true)
MXLogger.setBuildVersion(AppDelegate.theDelegate().build)
}
/// Use the analytics settings from the supplied session to configure analytics.
/// For now this is only used for (pseudonymous) identification.
/// - Parameter session: The session to read analytics settings from.
func useAnalyticsSettings(from session: MXSession) {
guard
RiotSettings.shared.enableAnalytics,
!RiotSettings.shared.isIdentifiedForAnalytics
else { return }
let service = AnalyticsService(session: session)
self.service = service
service.settings { [weak self] result in
guard let self = self else { return }
switch result {
case .success(let settings):
self.identify(with: settings)
self.service = nil
case .failure:
MXLog.error("[Analytics] Failed to use analytics settings. Will continue to run without analytics ID.")
self.service = nil
}
}
}
/// Resets the any IDs and event queues in the analytics client. This method should
/// be called on sign-out to maintain opt-in status, whilst ensuring the next
/// account used isn't associated with the previous one.
/// Note: **MUST** be called before stopping PostHog or the reset is ignored.
func reset() {
client.reset()
MXLog.debug("[Analytics] Reset.")
RiotSettings.shared.isIdentifiedForAnalytics = false
// Stop collecting crash logs
MXLogger.logCrashes(false)
}
/// Flushes the event queue in the analytics client, uploading all pending events.
/// Normally events are sent in batches. Call this method when you need an event
/// to be sent immediately.
func forceUpload() {
client.flush()
}
// MARK: - Private
/// Identify (pseudonymously) any future events with the ID from the analytics account data settings.
/// - Parameter settings: The settings to use for identification. The ID must be set *before* calling this method.
private func identify(with settings: AnalyticsSettings) {
guard let id = settings.id else {
MXLog.error("[Analytics] identify(with:) called before an ID has been generated.")
return
}
client.identify(id: id)
MXLog.debug("[Analytics] Identified.")
RiotSettings.shared.isIdentifiedForAnalytics = true
}
/// Capture an event in the `client`.
/// - Parameter event: The event to capture.
private func capture(event: AnalyticsEventProtocol) {
client.capture(event)
}
}
// MARK: - Public tracking methods
// The following methods are exposed for compatibility with Objective-C as
// the `capture` method and the generated events cannot be bridged from Swift.
extension Analytics {
/// Track the presentation of a screen
/// - Parameters:
/// - screen: The screen that was shown.
/// - milliseconds: An optional value representing how long the screen was shown for in milliseconds.
func trackScreen(_ screen: AnalyticsScreen, duration milliseconds: Int?) {
let event = AnalyticsEvent.Screen(durationMs: milliseconds, screenName: screen.screenName)
client.screen(event)
}
/// The the presentation of a screen without including a duration
/// - Parameter screen: The screen that was shown
func trackScreen(_ screen: AnalyticsScreen) {
trackScreen(screen, duration: nil)
}
/// Track an element that has been tapped
/// - Parameters:
/// - tap: The element that was tapped
/// - index: The index of the element, if it's in a list of elements
func trackTap(_ tap: AnalyticsUIElement, index: Int?) {
let event = AnalyticsEvent.Click(index: index, name: tap.elementName)
client.capture(event)
}
/// Track an element that has been tapped without including an index
/// - Parameters:
/// - tap: The element that was tapped
func trackTap(_ tap: AnalyticsUIElement) {
trackTap(tap, index: nil)
}
/// Track an E2EE error that occurred
/// - Parameters:
/// - reason: The error that occurred.
/// - count: The number of times that error occurred.
func trackE2EEError(_ reason: DecryptionFailureReason, count: Int) {
for _ in 0..<count {
let event = AnalyticsEvent.Error(context: nil, domain: .E2EE, name: reason.errorName)
capture(event: event)
}
}
/// Track whether the user accepted or declined the terms to an identity server.
/// **Note** This method isn't currently implemented.
/// - Parameter accepted: Whether the terms were accepted.
func trackIdentityServerAccepted(_ accepted: Bool) {
// Do we still want to track this?
}
}
// MARK: - MXAnalyticsDelegate
extension Analytics: MXAnalyticsDelegate {
func trackDuration(_ milliseconds: Int, name: MXTaskProfileName, units: UInt) {
guard let analyticsName = name.analyticsName else {
MXLog.warning("[Analytics] Attempt to capture unknown profile task: \(name.rawValue)")
return
}
let event = AnalyticsEvent.PerformanceTimer(context: nil, itemCount: Int(units), name: analyticsName, timeMs: milliseconds)
capture(event: event)
}
func trackCallStarted(withVideo isVideo: Bool, numberOfParticipants: Int, incoming isIncoming: Bool) {
let event = AnalyticsEvent.CallStarted(isVideo: isVideo, numParticipants: numberOfParticipants, placed: !isIncoming)
capture(event: event)
}
func trackCallEnded(withDuration duration: Int, video isVideo: Bool, numberOfParticipants: Int, incoming isIncoming: Bool) {
let event = AnalyticsEvent.CallEnded(durationMs: duration, isVideo: isVideo, numParticipants: numberOfParticipants, placed: !isIncoming)
capture(event: event)
}
func trackCallError(with reason: __MXCallHangupReason, video isVideo: Bool, numberOfParticipants: Int, incoming isIncoming: Bool) {
let callEvent = AnalyticsEvent.CallError(isVideo: isVideo, numParticipants: numberOfParticipants, placed: !isIncoming)
let event = AnalyticsEvent.Error(context: nil, domain: .VOIP, name: reason.errorName)
capture(event: callEvent)
capture(event: event)
}
func trackCreatedRoom(asDM isDM: Bool) {
let event = AnalyticsEvent.CreatedRoom(isDM: isDM)
capture(event: event)
}
func trackJoinedRoom(asDM isDM: Bool, memberCount: UInt) {
guard let roomSize = AnalyticsEvent.JoinedRoom.RoomSize(memberCount: memberCount) else {
MXLog.warning("[Analytics] Attempt to capture joined room with invalid member count: \(memberCount)")
return
}
let event = AnalyticsEvent.JoinedRoom(isDM: isDM, roomSize: roomSize)
capture(event: event)
}
/// **Note** This method isn't currently implemented.
func trackContactsAccessGranted(_ granted: Bool) {
// Do we still want to track this?
}
}

View file

@ -0,0 +1,48 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import AnalyticsEvents
/// A protocol representing an analytics client.
protocol AnalyticsClientProtocol {
/// Whether the analytics client is currently reporting data or ignoring it.
var isRunning: Bool { get }
/// Starts the analytics client reporting data.
func start()
/// Associate the client with an ID. This is persisted until `reset` is called.
/// - Parameter id: The ID to associate with the user.
func identify(id: String)
/// Reset all stored properties and any event queues on the client. Note that
/// the client will remain active, but in a fresh unidentified state.
func reset()
/// Stop the analytics client reporting data.
func stop()
/// Send any queued events immediately.
func flush()
/// Capture the supplied analytics event.
/// - Parameter event: The event to capture.
func capture(_ event: AnalyticsEventProtocol)
/// Capture the supplied analytics screen event.
/// - Parameter event: The screen event to capture.
func screen(_ event: AnalyticsScreenProtocol)
}

View file

@ -0,0 +1,114 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
import AnalyticsEvents
@objc enum AnalyticsScreen: Int {
case sidebar
case home
case favourites
case people
case rooms
case searchRooms
case searchMessages
case searchPeople
case searchFiles
case room
case roomDetails
case roomMembers
case user
case roomSearch
case roomUploads
case roomSettings
case roomNotifications
case roomDirectory
case switchDirectory
case startChat
case createRoom
case settings
case settingsSecurity
case settingsDefaultNotifications
case settingsMentionsAndKeywords
case deactivateAccount
case group
case myGroups
case inviteFriends
/// The screen name reported to the AnalyticsEvent.
var screenName: AnalyticsEvent.Screen.ScreenName {
switch self {
case .sidebar:
return .MobileSidebar
case .home:
return .Home
case .favourites:
return .MobileFavourites
case .people:
return .MobilePeople
case .rooms:
return .MobileRooms
case .searchRooms:
return .MobileSearchRooms
case .searchMessages:
return .MobileSearchMessages
case .searchPeople:
return .MobileSearchPeople
case .searchFiles:
return .MobileSearchFiles
case .room:
return .Room
case .roomDetails:
return .RoomDetails
case .roomMembers:
return .RoomMembers
case .user:
return .User
case .roomSearch:
return .RoomSearch
case .roomUploads:
return .RoomUploads
case .roomSettings:
return .RoomSettings
case .roomNotifications:
return .RoomNotifications
case .roomDirectory:
return .RoomDirectory
case .switchDirectory:
return .MobileSwitchDirectory
case .startChat:
return .StartChat
case .createRoom:
return .CreateRoom
case .settings:
return .Settings
case .settingsSecurity:
return .SettingsSecurity
case .settingsDefaultNotifications:
return .SettingsDefaultNotifications
case .settingsMentionsAndKeywords:
return .SettingsMentionsAndKeywords
case .deactivateAccount:
return .DeactivateAccount
case .group:
return .Group
case .myGroups:
return .MyGroups
case .inviteFriends:
return .MobileInviteFriends
}
}
}

View file

@ -0,0 +1,82 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import UIKit
/// An object to record how long a screen has been presented for and
/// report the screen's display to the `Analytics` object.
@objcMembers class AnalyticsScreenTimer: NSObject {
// MARK: - Properties
/// The screen being tracked.
private let screen: AnalyticsScreen
/// The date that the screen was presented to the user.
private var startDate: Date?
/// Whether the app was backgrounded whilst the screen was being presented.
private var didPause = false
/// The duration in milliseconds that the screen has been shown for. The value will
/// be reported as `nil` if the timer isn't running, or if the app was backgrounded
/// during the screen's display.
private var duration: Int? {
guard let startDate = startDate else {
MXLog.warning("[AnalyticsScreenTimer] Duration requested on a stopped timer!")
return nil
}
// Consider the duration invalid if the app has been backgrounded
guard !didPause else { return nil }
let timeInterval = Date().timeIntervalSince(startDate)
return Int(timeInterval * 1000)
}
// MARK: - Setup
/// Create a new screen timer for the specified screen.
/// - Parameter screen: The screen that should be timed.
init(screen: AnalyticsScreen) {
self.screen = screen
super.init()
NotificationCenter.default.addObserver(self, selector: #selector(pause), name: UIApplication.willResignActiveNotification, object: nil)
}
// MARK: - Public
/// Start the timer.
func start() {
startDate = Date()
}
/// Stop the timer and report the screen to `Analytics`.
func stop() {
guard let duration = duration else { return }
Analytics.shared.trackScreen(screen, duration: duration)
self.startDate = nil
}
// MARK: - Private
/// Record that the timer has been interrupted by the app moving to the background.
@objc private func pause() {
didPause = true
}
}

View file

@ -0,0 +1,77 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
enum AnalyticsServiceError: Error {
/// The session supplied to the service does not have a state of `MXSessionStateRunning`.
case sessionIsNotRunning
/// An error occurred but the session did not report what it was.
case unknown
}
/// A service responsible for handling the `im.vector.analytics` event from the user's account data.
class AnalyticsService {
let session: MXSession
/// Creates an analytics service with the supplied session.
/// - Parameter session: The session to use when reading analytics settings from account data.
init(session: MXSession) {
self.session = session
}
/// The analytics settings for the current user. Calling this method will check whether the settings already
/// contain an `id` property and if not, will add one to the account data before calling the completion.
/// - Parameter completion: A completion handler that will be called when the request completes.
///
/// The request will fail if the service's session does not have the `MXSessionStateRunning` state.
func settings(completion: @escaping (Result<AnalyticsSettings, Error>) -> Void) {
// Only use the session if it is running otherwise we could wipe out an existing analytics ID.
guard session.state == .running else {
MXLog.warning("[AnalyticsService] Aborting attempt to read analytics settings. The session may not be up-to-date.")
completion(.failure(AnalyticsServiceError.sessionIsNotRunning))
return
}
let settings = AnalyticsSettings(accountData: session.accountData)
// The id has already be set so we are done here.
if settings.id != nil {
completion(.success(settings))
return
}
// Create a new ID and modify the event dictionary.
let id = UUID().uuidString
var eventDictionary = settings.dictionary
eventDictionary[AnalyticsSettings.Constants.idKey] = id
session.setAccountData(eventDictionary, forType: AnalyticsSettings.eventType) { [weak self] in
guard let self = self else {
completion(.failure(AnalyticsServiceError.unknown))
return
}
MXLog.debug("[AnalyticsService] Successfully updated analytics settings in account data.")
let settings = AnalyticsSettings(accountData: self.session.accountData)
completion(.success(settings))
} failure: { error in
MXLog.warning("[AnalyticsService] Failed to update analytics settings.")
completion(.failure(error ?? AnalyticsServiceError.unknown))
}
}
}

View file

@ -0,0 +1,65 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
/// An analytics settings event from the user's account data.
struct AnalyticsSettings {
static let eventType = "im.vector.analytics"
enum Constants {
static let idKey = "id"
static let webOptInKey = "pseudonymousAnalyticsOptIn"
}
/// A randomly generated analytics token for this user.
/// This is suggested to be a UUID string.
let id: String?
/// Whether the user has opted in on web or not. This is unused on iOS but necessary
/// to store here so that it's value is preserved when updating the account data if we
/// generated an ID on iOS.
///
/// `true` if opted in on web, `false` if opted out on web and `nil` if the web prompt is not yet seen.
private let webOptIn: Bool?
}
extension AnalyticsSettings {
// Private as AnalyticsSettings should only be created from an MXSession
private init(dictionary: Dictionary<AnyHashable, Any>?) {
self.id = dictionary?[Constants.idKey] as? String
self.webOptIn = dictionary?[Constants.webOptInKey] as? Bool
}
/// A dictionary representation of the settings.
var dictionary: Dictionary<AnyHashable, Any> {
var dictionary = [AnyHashable: Any]()
dictionary[Constants.idKey] = id
dictionary[Constants.webOptInKey] = webOptIn
return dictionary
}
}
// MARK: - Public initializer
extension AnalyticsSettings {
/// Create the analytics settings from account data.
/// - Parameter accountData: The account data to read the event from.
init(accountData: MXAccountData) {
self.init(dictionary: accountData.accountData(forEventType: AnalyticsSettings.eventType))
}
}

View file

@ -0,0 +1,32 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import AnalyticsEvents
/// A tappable UI element that can be track in Analytics.
@objc enum AnalyticsUIElement: Int {
case sendMessageButton
/// The element name reported to the AnalyticsEvent.
var elementName: AnalyticsEvent.Click.Name {
switch self {
// Note: This is a test element that doesn't need to be captured.
// It will likely be removed when the AnalyticsEvent.Click is updated.
case .sendMessageButton:
return .SendMessageButton
}
}
}

View file

@ -0,0 +1,53 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import AnalyticsEvents
/// Failure reasons as defined in https://docs.google.com/document/d/1es7cTCeJEXXfRCTRgZerAM2Wg5ZerHjvlpfTW-gsOfI.
@objc enum DecryptionFailureReason: Int {
case unspecified
case olmKeysNotSent
case olmIndexError
case unexpected
var errorName: AnalyticsEvent.Error.Name {
switch self {
case .unspecified:
return .OlmUnspecifiedError
case .olmKeysNotSent:
return .OlmKeysNotSentError
case .olmIndexError:
return .OlmIndexError
case .unexpected:
return .UnknownError
}
}
}
/// `DecryptionFailure` represents a decryption failure.
@objcMembers class DecryptionFailure: NSObject {
/// The id of the event that was unabled to decrypt.
let failedEventId: String
/// The time the failure has been reported.
let ts: TimeInterval = Date().timeIntervalSince1970
/// Decryption failure reason.
let reason: DecryptionFailureReason
init(failedEventId: String, reason: DecryptionFailureReason) {
self.failedEventId = failedEventId
self.reason = reason
}
}

View file

@ -16,8 +16,9 @@
#import <Foundation/Foundation.h>
#import "DecryptionFailure.h"
@class DecryptionFailureTracker;
@class Analytics;
@import MatrixSDK;
@interface DecryptionFailureTracker : NSObject
@ -32,7 +33,7 @@
/**
The delegate object to receive analytics events.
*/
@property (nonatomic, weak) id<MXAnalyticsDelegate> delegate;
@property (nonatomic, weak) Analytics *delegate;
/**
Report an event unable to decrypt.

View file

@ -15,6 +15,7 @@
*/
#import "DecryptionFailureTracker.h"
#import "GeneratedInterface-Swift.h"
// Call `checkFailures` every `CHECK_INTERVAL`
@ -90,31 +91,32 @@ NSString *const kDecryptionFailureTrackerAnalyticsCategory = @"e2e.failure";
return;
}
DecryptionFailure *decryptionFailure = [[DecryptionFailure alloc] init];
decryptionFailure.failedEventId = event.eventId;
NSString *failedEventId = event.eventId;
DecryptionFailureReason reason;
// Categorise the error
switch (event.decryptionError.code)
{
case MXDecryptingErrorUnknownInboundSessionIdCode:
decryptionFailure.reason = DecryptionFailureReason.olmKeysNotSent;
reason = DecryptionFailureReasonOlmKeysNotSent;
break;
case MXDecryptingErrorOlmCode:
decryptionFailure.reason = DecryptionFailureReason.olmIndexError;
reason = DecryptionFailureReasonOlmIndexError;
break;
case MXDecryptingErrorEncryptionNotEnabledCode:
case MXDecryptingErrorUnableToDecryptCode:
decryptionFailure.reason = DecryptionFailureReason.unexpected;
reason = DecryptionFailureReasonUnexpected;
break;
default:
decryptionFailure.reason = DecryptionFailureReason.unspecified;
reason = DecryptionFailureReasonUnspecified;
break;
}
reportedFailures[event.eventId] = decryptionFailure;
reportedFailures[event.eventId] = [[DecryptionFailure alloc] initWithFailedEventId:failedEventId
reason:reason];
}
- (void)dispatch
@ -152,17 +154,17 @@ NSString *const kDecryptionFailureTrackerAnalyticsCategory = @"e2e.failure";
if (failuresToTrack.count)
{
// Sort failures by error reason
NSMutableDictionary<NSString*, NSNumber*> *failuresCounts = [NSMutableDictionary dictionary];
NSMutableDictionary<NSNumber*, NSNumber*> *failuresCounts = [NSMutableDictionary dictionary];
for (DecryptionFailure *failure in failuresToTrack)
{
failuresCounts[failure.reason] = @(failuresCounts[failure.reason].unsignedIntegerValue + 1);
failuresCounts[@(failure.reason)] = @(failuresCounts[@(failure.reason)].unsignedIntegerValue + 1);
}
MXLogDebug(@"[DecryptionFailureTracker] trackFailures: %@", failuresCounts);
for (NSString *reason in failuresCounts)
for (NSNumber *reason in failuresCounts)
{
[_delegate trackValue:failuresCounts[reason] category:kDecryptionFailureTrackerAnalyticsCategory name:reason];
[self.delegate trackE2EEError:reason.integerValue count:failuresCounts[reason].integerValue];
}
}
}

View file

@ -1,5 +1,5 @@
//
// Copyright 2020 The Matrix.org Foundation C.I.C
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -14,20 +14,23 @@
// limitations under the License.
//
#import <Foundation/Foundation.h>
import AnalyticsEvents
typedef NSString *const MXKAnalyticsCategory NS_TYPED_EXTENSIBLE_ENUM;
/**
The analytics category for local contacts.
*/
static MXKAnalyticsCategory const MXKAnalyticsCategoryContacts = @"localContacts";
typedef NSString *const MXKAnalyticsName NS_TYPED_EXTENSIBLE_ENUM;
/**
The analytics value for accept/decline of local contacts access.
*/
static MXKAnalyticsName const MXKAnalyticsNameContactsAccessGranted = @"accessGranted";
extension AnalyticsEvent.JoinedRoom.RoomSize {
init?(memberCount: UInt) {
switch memberCount {
case 2:
self = .Two
case 3...10:
self = .ThreeToTen
case 11...100:
self = .ElevenToOneHundred
case 101...1000:
self = .OneHundredAndOneToAThousand
case 1001...:
self = .MoreThanAThousand
default:
return nil
}
}
}

View file

@ -0,0 +1,38 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import AnalyticsEvents
extension __MXCallHangupReason {
var errorName: AnalyticsEvent.Error.Name {
switch self {
case .userHangup:
return .VoipUserHangup
case .inviteTimeout:
return .VoipInviteTimeout
case .iceFailed:
return .VoipIceFailed
case .iceTimeout:
return .VoipIceTimeout
case .userMediaFailed:
return .VoipUserMediaFailed
case .unknownError:
return .UnknownError
default:
return .UnknownError
}
}
}

View file

@ -0,0 +1,42 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import AnalyticsEvents
extension MXTaskProfileName {
var analyticsName: AnalyticsEvent.PerformanceTimer.Name? {
switch self {
case .startupIncrementalSync:
return .StartupIncrementalSync
case .startupInitialSync:
return .StartupInitialSync
case .startupLaunchScreen:
return .StartupLaunchScreen
case .startupStorePreload:
return .StartupStorePreload
case .startupMountData:
return .StartupStoreReady
case .initialSyncRequest:
return .InitialSyncRequest
case .initialSyncParsing:
return .InitialSyncParsing
case .notificationsOpenEvent:
return .NotificationsOpenEvent
default:
return nil
}
}
}

View file

@ -0,0 +1,28 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import PostHog
extension PHGPostHogConfiguration {
static var standard: PHGPostHogConfiguration? {
guard let apiKey = BuildSettings.analyticsKey, let host = BuildSettings.analyticsHost else { return nil }
let configuration = PHGPostHogConfiguration(apiKey: apiKey, host: host)
configuration.shouldSendDeviceID = false
return configuration
}
}

View file

@ -0,0 +1,65 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import PostHog
import AnalyticsEvents
/// An analytics client that reports events to a PostHog server.
class PostHogAnalyticsClient: AnalyticsClientProtocol {
/// The PHGPostHog object used to report events.
private var postHog: PHGPostHog?
var isRunning: Bool { postHog?.enabled ?? false }
func start() {
// Only start if analytics have been configured in BuildSettings
guard let configuration = PHGPostHogConfiguration.standard else { return }
if postHog == nil {
postHog = PHGPostHog(configuration: configuration)
}
postHog?.enable()
}
func identify(id: String) {
postHog?.identify(id)
}
func reset() {
postHog?.reset()
}
func stop() {
postHog?.disable()
// 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
}
func flush() {
postHog?.flush()
}
func capture(_ event: AnalyticsEventProtocol) {
postHog?.capture(event.eventName, properties: event.properties)
}
func screen(_ event: AnalyticsScreenProtocol) {
postHog?.screen(event.screenName.rawValue, properties: event.properties)
}
}

View file

@ -22,7 +22,6 @@
#import "JitsiViewController.h"
#import "RageShakeManager.h"
#import "Analytics.h"
#import "ThemeService.h"
#import "UniversalLink.h"

View file

@ -433,16 +433,16 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
_isAppForeground = NO;
_handleSelfVerificationRequest = YES;
// Configure our analytics. It will indeed start if the option is enabled
Analytics *analytics = [Analytics sharedInstance];
// Configure our analytics. It will start if the option is enabled
Analytics *analytics = Analytics.shared;
[MXSDKOptions sharedInstance].analyticsDelegate = analytics;
[DecryptionFailureTracker sharedInstance].delegate = [Analytics sharedInstance];
[DecryptionFailureTracker sharedInstance].delegate = analytics;
MXBaseProfiler *profiler = [MXBaseProfiler new];
profiler.analytics = analytics;
[MXSDKOptions sharedInstance].profiler = profiler;
[analytics start];
[analytics startIfEnabled];
self.localAuthenticationService = [[LocalAuthenticationService alloc] initWithPinCodePreferences:[PinCodePreferences shared]];
@ -587,7 +587,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
// Analytics: Force to send the pending actions
[[DecryptionFailureTracker sharedInstance] dispatch];
[[Analytics sharedInstance] dispatch];
[Analytics.shared forceUpload];
}
- (void)applicationWillEnterForeground:(UIApplication *)application
@ -648,9 +648,13 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
MXLogDebug(@"[AppDelegate] afterAppUnlockedByPin");
// Check if there is crash log to send
if (RiotSettings.shared.enableCrashReport)
if (RiotSettings.shared.enableAnalytics)
{
#if DEBUG
// Don't show alerts for crashes during development.
#else
[self checkExceptionToReport];
#endif
}
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
@ -1880,6 +1884,11 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
[self.pushNotificationService checkPushKitPushersInSession:mxSession];
}
else if (mxSession.state == MXSessionStateRunning)
{
// Configure analytics from the session if necessary
[Analytics.shared useAnalyticsSettingsFrom:mxSession];
}
else if (mxSession.state == MXSessionStateClosed)
{
[self removeMatrixSession:mxSession];
@ -2225,6 +2234,9 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
// Reset push notification store
[self.pushNotificationStore reset];
// Reset analytics
[Analytics.shared reset];
#ifdef MX_CALL_STACK_ENDPOINT
// Erase all created certificates and private keys by MXEndpointCallStack
for (MXKAccount *account in MXKAccountManager.sharedManager.accounts)
@ -2390,8 +2402,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
launchAnimationContainerView = launchLoadingView;
[MXSDKOptions.sharedInstance.profiler startMeasuringTaskWithName:kMXAnalyticsStartupLaunchScreen
category:kMXAnalyticsStartupCategory];
[MXSDKOptions.sharedInstance.profiler startMeasuringTaskWithName:MXTaskProfileNameStartupLaunchScreen];
}
}
@ -2400,7 +2411,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
if (launchAnimationContainerView)
{
id<MXProfiler> profiler = MXSDKOptions.sharedInstance.profiler;
MXTaskProfile *launchTaskProfile = [profiler taskProfileWithName:kMXAnalyticsStartupLaunchScreen category:kMXAnalyticsStartupCategory];
MXTaskProfile *launchTaskProfile = [profiler taskProfileWithName:MXTaskProfileNameStartupLaunchScreen];
if (launchTaskProfile)
{
[profiler stopMeasuringTaskWithProfile:launchTaskProfile];

View file

@ -309,9 +309,6 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0;
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// Screen tracking
[[Analytics sharedInstance] trackScreen:@"Authentication"];
[_keyboardAvoider startAvoiding];
}
@ -330,7 +327,7 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0;
return;
}
// Verify that the app does not show the authentification screean whereas
// Verify that the app does not show the authentication screen whereas
// the user has already logged in.
// This bug rarely happens (https://github.com/vector-im/riot-ios/issues/1643)
// but it invites the user to log in again. They will then lose all their

View file

@ -18,6 +18,7 @@
#import "MatrixKit.h"
@class RootTabEmptyView;
@class AnalyticsScreenTimer;
/**
Notification to be posted when recents data is ready. Notification object will be the RecentsViewController instance.
@ -85,16 +86,16 @@ FOUNDATION_EXPORT NSString *const RecentsViewControllerDataReadyNotification;
*/
@property (nonatomic) CGFloat stickyHeaderHeight;
/**
The analytics instance screen name (Default is "RecentsScreen").
*/
@property (nonatomic) NSString *screenName;
/**
Empty view to display when there is no item to show on the screen.
*/
@property (nonatomic, weak) RootTabEmptyView *emptyView;
/**
The screen timer used for analytics if they've been enabled. The default value is nil.
*/
@property (nonatomic) AnalyticsScreenTimer *screenTimer;
/**
Return the sticky header for the specified section of the table view

View file

@ -106,9 +106,6 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro
self.enableBarTintColorStatusChange = NO;
self.rageShakeManager = [RageShakeManager sharedManager];
// Set default screen name
_screenName = @"RecentsScreen";
// Enable the search bar in the recents table, and remove the search option from the navigation bar.
_enableSearchBar = YES;
self.enableBarButtonSearch = NO;
@ -259,9 +256,6 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro
{
[super viewWillAppear:animated];
// Screen tracking
[[Analytics sharedInstance] trackScreen:_screenName];
// Reset back user interactions
self.userInteractionEnabled = YES;
@ -329,11 +323,14 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro
// the selected room (if any) is highlighted.
[self refreshCurrentSelectedCell:YES];
}
[self.screenTimer start];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[self.screenTimer stop];
}
- (void)viewDidLayoutSubviews

View file

@ -42,6 +42,8 @@
__weak id kThemeServiceDidChangeThemeNotificationObserver;
}
@property (nonatomic) AnalyticsScreenTimer *screenTimer;
@end
@implementation GroupsViewController
@ -74,6 +76,8 @@
// Set itself as delegate by default.
self.delegate = self;
self.screenTimer = [[AnalyticsScreenTimer alloc] initWithScreen:AnalyticsScreenMyGroups];
}
- (void)viewDidLoad
@ -203,9 +207,6 @@
{
[super viewWillAppear:animated];
// Screen tracking
[[Analytics sharedInstance] trackScreen:@"Groups"];
// Deselect the current selected row, it will be restored on viewDidAppear (if any)
NSIndexPath *indexPath = [self.groupsTableView indexPathForSelectedRow];
if (indexPath)
@ -258,11 +259,14 @@
// the selected group (if any) is highlighted.
[self refreshCurrentSelectedCell:YES];
}
[self.screenTimer start];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[self.screenTimer stop];
}
#pragma mark - Override MXKGroupListViewController

View file

@ -48,6 +48,8 @@
@property (nonatomic, readonly) DTHTMLAttributedStringBuilderWillFlushCallback longDescriptionSanitizationCallback;
@property (nonatomic) AnalyticsScreenTimer *screenTimer;
@end
@implementation GroupHomeViewController
@ -95,6 +97,8 @@
MXStrongifyAndReturnIfNil(self);
[element sanitizeWith:allowedHTMLTags bodyFont:self->_groupLongDescription.font imageHandler:[self groupLongDescriptionImageHandler]];
};
self.screenTimer = [[AnalyticsScreenTimer alloc] initWithScreen:AnalyticsScreenGroup];
}
- (void)viewDidLoad
@ -205,9 +209,6 @@
{
[super viewWillAppear:animated];
// Screen tracking
[[Analytics sharedInstance] trackScreen:@"GroupDetailsHome"];
// Release the potential pushed view controller
[self releasePushedViewController];
@ -259,6 +260,18 @@
[self cancelRegistrationOnGroupChangeNotifications];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self.screenTimer start];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[self.screenTimer stop];
}
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];

View file

@ -219,9 +219,6 @@
{
[super viewWillAppear:animated];
// Screen tracking
[[Analytics sharedInstance] trackScreen:@"GroupDetailsPeople"];
// Release the potential pushed view controller
[self releasePushedViewController];

View file

@ -183,9 +183,6 @@
{
[super viewWillAppear:animated];
// Screen tracking
[[Analytics sharedInstance] trackScreen:@"GroupDetailsRooms"];
// Release the potential pushed view controller
[self releasePushedViewController];

View file

@ -136,9 +136,6 @@
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// Screen tracking
[[Analytics sharedInstance] trackScreen:@"GroupDetails"];
}
- (void)viewWillDisappear:(BOOL)animated

View file

@ -19,6 +19,7 @@
#import "ContactTableViewCell.h"
@class ContactsTableViewController;
@class AnalyticsScreenTimer;
/**
`ContactsTableViewController` delegate.
@ -85,11 +86,6 @@
*/
@property (nonatomic) BOOL shouldScrollToTopOnRefresh;
/**
The analytics instance screen name (Default is "ContactsTable").
*/
@property (nonatomic) NSString *screenName;
/**
Callback used to take into account the change of the user interface theme.
*/
@ -124,5 +120,10 @@
*/
@property (nonatomic, weak) id<ContactsTableViewControllerDelegate> contactsTableViewControllerDelegate;
/**
The screen timer used for analytics if they've been enabled. The default value is nil.
*/
@property (nonatomic) AnalyticsScreenTimer *screenTimer;
@end

View file

@ -76,8 +76,6 @@
// Setup `MXKViewControllerHandling` properties
self.enableBarTintColorStatusChange = NO;
self.rageShakeManager = [RageShakeManager sharedManager];
_screenName = @"ContactsTable";
}
- (void)viewDidLoad
@ -159,9 +157,6 @@
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// Screen tracking
[[Analytics sharedInstance] trackScreen:_screenName];
MXWeakify(self);
@ -182,6 +177,12 @@
[self updateFooterViewVisibility];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self.screenTimer start];
}
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
@ -206,6 +207,12 @@
}
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[self.screenTimer stop];
}
#pragma mark -
/**

View file

@ -232,9 +232,6 @@
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// Screen tracking
[[Analytics sharedInstance] trackScreen:@"ContactDetails"];
// Hide the bottom border of the navigation bar to display the expander header
[self hideNavigationBarBorder:YES];

View file

@ -53,6 +53,7 @@ final class EnterNewRoomDetailsViewController: UIViewController {
item.isEnabled = false
return item
}()
private var screenTimer = AnalyticsScreenTimer(screen: .createRoom)
private enum RowType {
case `default`
@ -215,10 +216,17 @@ final class EnterNewRoomDetailsViewController: UIViewController {
self.keyboardAvoider?.startAvoiding()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
screenTimer.start()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
self.keyboardAvoider?.stopAvoiding()
screenTimer.stop()
}
override var preferredStatusBarStyle: UIStatusBarStyle {

View file

@ -39,9 +39,9 @@
{
[super finalizeInit];
self.screenName = @"Favourites";
self.enableDragging = YES;
self.screenTimer = [[AnalyticsScreenTimer alloc] initWithScreen:AnalyticsScreenFavourites];
}
- (void)viewDidLoad

View file

@ -17,6 +17,8 @@
#import "MatrixKit.h"
@class AnalyticsScreenTimer;
/**
`HomeFilesSearchViewController` displays the files search in user's rooms under a `HomeViewController` segment.
*/
@ -27,4 +29,9 @@
*/
@property (nonatomic, readonly) MXEvent *selectedEvent;
/**
The screen timer used for analytics if they've been enabled. The default value is nil.
*/
@property (nonatomic) AnalyticsScreenTimer *screenTimer;
@end

View file

@ -109,9 +109,6 @@
{
[super viewWillAppear:animated];
// Screen tracking
[[Analytics sharedInstance] trackScreen:@"FilesGlobalSearch"];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refreshSearchResult:) name:kMXSessionDidLeaveRoomNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refreshSearchResult:) name:kMXSessionNewRoomNotification object:nil];
}
@ -124,6 +121,18 @@
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionNewRoomNotification object:nil];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self.screenTimer start];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[self.screenTimer stop];
}
#pragma mark -
- (void)refreshSearchResult:(NSNotification *)notif

View file

@ -17,6 +17,8 @@
#import "MatrixKit.h"
@class AnalyticsScreenTimer;
/**
`HomeMessagesSearchViewController` displays messages search in user's rooms under a `HomeViewController` segment.
*/
@ -27,4 +29,9 @@
*/
@property (nonatomic, readonly) MXEvent *selectedEvent;
/**
The screen timer used for analytics if they've been enabled. The default value is nil.
*/
@property (nonatomic) AnalyticsScreenTimer *screenTimer;
@end

View file

@ -115,9 +115,6 @@
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// Screen tracking
[[Analytics sharedInstance] trackScreen:@"MessagesGlobalSearch"];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refreshSearchResult:) name:kMXSessionDidLeaveRoomNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refreshSearchResult:) name:kMXSessionNewRoomNotification object:nil];
@ -131,6 +128,18 @@
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionNewRoomNotification object:nil];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self.screenTimer start];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[self.screenTimer stop];
}
#pragma mark -
- (void)refreshSearchResult:(NSNotification *)notif

View file

@ -35,6 +35,8 @@
id kThemeServiceDidChangeThemeNotificationObserver;
}
@property (nonatomic) AnalyticsScreenTimer *screenTimer;
@end
@implementation DirectoryViewController
@ -46,6 +48,8 @@
// Setup `MXKViewControllerHandling` properties
self.enableBarTintColorStatusChange = NO;
self.rageShakeManager = [RageShakeManager sharedManager];
self.screenTimer = [[AnalyticsScreenTimer alloc] initWithScreen:AnalyticsScreenRoomDirectory];
}
- (void)viewDidLoad
@ -106,9 +110,6 @@
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// Screen tracking
[[Analytics sharedInstance] trackScreen:@"Directory"];
// Observe kAppDelegateDidTapStatusBarNotificationObserver.
kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
@ -135,6 +136,8 @@
// the selected room (if any) is highlighted.
[self refreshCurrentSelectedCell:YES];
}
[self.screenTimer start];
}
- (void)viewWillDisappear:(BOOL)animated
@ -148,6 +151,12 @@
[super viewWillDisappear:animated];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[self.screenTimer stop];
}
- (void)displayWitDataSource:(PublicRoomsDirectoryDataSource *)dataSource2
{
// Let the data source provide cells

View file

@ -79,12 +79,13 @@
[titles addObject:[VectorL10n searchRooms]];
recentsViewController = [RecentsViewController recentListViewController];
recentsViewController.screenTimer = [[AnalyticsScreenTimer alloc] initWithScreen:AnalyticsScreenSearchRooms];
recentsViewController.enableSearchBar = NO;
recentsViewController.screenName = @"UnifiedSearchRooms";
[viewControllers addObject:recentsViewController];
[titles addObject:[VectorL10n searchMessages]];
messagesSearchViewController = [HomeMessagesSearchViewController searchViewController];
messagesSearchViewController.screenTimer = [[AnalyticsScreenTimer alloc] initWithScreen:AnalyticsScreenSearchMessages];
[viewControllers addObject:messagesSearchViewController];
// Add search People tab
@ -92,11 +93,13 @@
peopleSearchViewController = [ContactsTableViewController contactsTableViewController];
peopleSearchViewController.contactsTableViewControllerDelegate = self;
peopleSearchViewController.disableFindYourContactsFooter = YES;
peopleSearchViewController.screenTimer = [[AnalyticsScreenTimer alloc] initWithScreen:AnalyticsScreenSearchPeople];
[viewControllers addObject:peopleSearchViewController];
// add Files tab
[titles addObject:[VectorL10n searchFiles]];
filesSearchViewController = [HomeFilesSearchViewController searchViewController];
filesSearchViewController.screenTimer = [[AnalyticsScreenTimer alloc] initWithScreen:AnalyticsScreenSearchFiles];
[viewControllers addObject:filesSearchViewController];
[self initWithTitles:titles viewControllers:viewControllers defaultSelected:0];
@ -144,9 +147,6 @@
{
[super viewWillAppear:animated];
// Screen tracking
[[Analytics sharedInstance] trackScreen:@"UnifiedSearch"];
// Let's child display the loading not the home view controller
if (self.activityIndicator)
{

View file

@ -69,7 +69,7 @@
selectedRoomId = nil;
selectedCollectionViewContentOffset = -1;
self.screenName = @"Home";
self.screenTimer = [[AnalyticsScreenTimer alloc] initWithScreen:AnalyticsScreenHome];
}
- (void)viewDidLoad

View file

@ -27,7 +27,6 @@
#import "MXKAppSettings.h"
#import <MatrixSDK/MXTools.h>
#import "MXKSwiftHeader.h"
#import "MXKAnalyticsConstants.h"
#pragma mark - Constants definitions
@ -884,9 +883,7 @@ manualChangeMessageForVideo:(NSString*)manualChangeMessageForVideo
// Request address book access
[[CNContactStore new] requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {
[MXSDKOptions.sharedInstance.analyticsDelegate trackValue:[NSNumber numberWithBool:granted]
category:MXKAnalyticsCategoryContacts
name:MXKAnalyticsNameContactsAccessGranted];
[MXSDKOptions.sharedInstance.analyticsDelegate trackContactsAccessGranted:granted];
dispatch_async(dispatch_get_main_queue(), ^{

View file

@ -163,9 +163,6 @@
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// Screen tracking
[[Analytics sharedInstance] trackScreen:@"MediaAlbumContent"];
self.navigationItem.title = _assetsCollection.localizedTitle;

View file

@ -212,9 +212,6 @@
[super viewWillAppear:animated];
[self userInterfaceThemeDidChange];
// Screen tracking
[[Analytics sharedInstance] trackScreen:@"MediaPicker"];
if (!userAlbumsQueue)
{

View file

@ -73,5 +73,7 @@ final class InviteFriendsPresenter: NSObject {
}
self.presentingViewController?.present(viewController, animated: animated, completion: nil)
Analytics.shared.trackScreen(.inviteFriends, duration: nil)
}
}

View file

@ -51,7 +51,7 @@
directRoomsSectionNumber = 0;
self.screenName = @"People";
self.screenTimer = [[AnalyticsScreenTimer alloc] initWithScreen:AnalyticsScreenPeople];
}
- (void)viewDidLoad

View file

@ -73,14 +73,6 @@
return ThemeService.shared.theme.statusBarStyle;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// Screen tracking
[[Analytics sharedInstance] trackScreen:@"AttachmentsViewer"];
}
- (void)destroy
{
[super destroy];

View file

@ -16,6 +16,8 @@ limitations under the License.
#import "MatrixKit.h"
@class AnalyticsScreenTimer;
/**
This view controller displays the attachments of a room. Only one matrix session is handled by this view controller.
*/
@ -23,4 +25,9 @@ limitations under the License.
@property (nonatomic) BOOL showCancelBarButtonItem;
/**
The screen timer used for analytics if they've been enabled. The default value is nil.
*/
@property (nonatomic) AnalyticsScreenTimer *screenTimer;
@end

View file

@ -110,6 +110,14 @@
[UIView setAnimationsEnabled:NO];
[self roomInputToolbarView:self.inputToolbarView heightDidChanged:0 completion:nil];
[UIView setAnimationsEnabled:YES];
[self.screenTimer start];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[self.screenTimer stop];
}
- (void)userInterfaceThemeDidChange

View file

@ -104,6 +104,8 @@
@property(nonatomic, strong) UserVerificationCoordinatorBridgePresenter *userVerificationCoordinatorBridgePresenter;
@property(nonatomic) AnalyticsScreenTimer *screenTimer;
@end
@implementation RoomMemberDetailsViewController
@ -139,6 +141,8 @@
// Keep visible the status bar by default.
isStatusBarHidden = NO;
self.screenTimer = [[AnalyticsScreenTimer alloc] initWithScreen:AnalyticsScreenUser];
}
- (void)viewDidLoad
@ -239,9 +243,6 @@
{
[super viewWillAppear:animated];
// Screen tracking
[[Analytics sharedInstance] trackScreen:@"RoomMemberDetails"];
[self userInterfaceThemeDidChange];
// Hide the bottom border of the navigation bar to display the expander header
@ -264,6 +265,18 @@
self.bottomImageView.hidden = YES;
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self.screenTimer start];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[self.screenTimer stop];
}
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinator
{
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];

View file

@ -92,6 +92,11 @@
*/
@property (nonatomic, weak) id<RoomParticipantsViewControllerDelegate> delegate;
/**
The screen timer used for analytics if they've been enabled. The default value is nil.
*/
@property (nonatomic) AnalyticsScreenTimer *screenTimer;
/**
Returns the `UINib` object initialized for a `RoomParticipantsViewController`.

View file

@ -245,9 +245,6 @@
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// Screen tracking
[[Analytics sharedInstance] trackScreen:@"RoomParticipants"];
// Refresh display
[self refreshTableView];
@ -268,6 +265,8 @@
[contactsPickerViewController destroy];
contactsPickerViewController = nil;
}
[self.screenTimer start];
}
- (void)viewWillDisappear:(BOOL)animated
@ -284,6 +283,12 @@
[self searchBarCancelButtonClicked:_searchBarView];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[self.screenTimer stop];
}
- (void)withdrawViewControllerAnimated:(BOOL)animated completion:(void (^)(void))completion
{
// Check whether the current view controller is displayed inside a segmented view controller in order to withdraw the right item

View file

@ -39,9 +39,11 @@ final class RoomInfoCoordinator: NSObject, RoomInfoCoordinatorType {
participants.enableMention = true
participants.mxRoom = self.room
participants.delegate = self
participants.screenTimer = AnalyticsScreenTimer(screen: .roomMembers)
let files = RoomFilesViewController()
files.finalizeInit()
files.screenTimer = AnalyticsScreenTimer(screen: .roomUploads)
MXKRoomDataSource.load(withRoomId: self.room.roomId, andMatrixSession: self.session) { (dataSource) in
guard let dataSource = dataSource as? MXKRoomDataSource else { return }
dataSource.filterMessagesWithURL = true
@ -52,6 +54,7 @@ final class RoomInfoCoordinator: NSObject, RoomInfoCoordinatorType {
let settings = RoomSettingsViewController()
settings.finalizeInit()
settings.screenTimer = AnalyticsScreenTimer(screen: .roomSettings)
settings.initWith(self.session, andRoomId: self.room.roomId)
if self.room.isDirect {

View file

@ -40,6 +40,7 @@ final class RoomInfoListViewController: UIViewController {
private var errorPresenter: MXKErrorPresentation!
private var activityPresenter: ActivityIndicatorPresenter!
private var isRoomDirect: Bool = false
private var screenTimer = AnalyticsScreenTimer(screen: .roomDetails)
private lazy var closeButton: CloseButton = {
let button = CloseButton()
@ -128,12 +129,22 @@ final class RoomInfoListViewController: UIViewController {
return self.theme.statusBarStyle
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
screenTimer.start()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
mainTableView.vc_relayoutHeaderView()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
screenTimer.stop()
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
coordinator.animate(alongsideTransition: {_ in
self.basicInfoView.updateTrimmingOnTopic()

View file

@ -256,6 +256,8 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
@property (nonatomic, strong) UserSuggestionCoordinatorBridge *userSuggestionCoordinator;
@property (nonatomic, weak) IBOutlet UIView *userSuggestionContainerView;
@property (nonatomic) AnalyticsScreenTimer *screenTimer;
@end
@implementation RoomViewController
@ -338,6 +340,8 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
_voiceMessageController = [[VoiceMessageController alloc] initWithThemeService:ThemeService.shared mediaServiceProvider:VoiceMessageMediaServiceProvider.sharedProvider];
self.voiceMessageController.delegate = self;
self.screenTimer = [[AnalyticsScreenTimer alloc] initWithScreen:AnalyticsScreenRoom];
}
- (void)viewDidLoad
@ -567,9 +571,6 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
{
[super viewWillAppear:animated];
// Screen tracking
[[Analytics sharedInstance] trackScreen:@"ChatRoom"];
// Refresh the room title view
[self refreshRoomTitle];
@ -610,8 +611,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
[self.roomDataSource reload];
[LegacyAppDelegate theDelegate].lastNavigatedRoomIdFromPush = nil;
notificationTaskProfile = [MXSDKOptions.sharedInstance.profiler startMeasuringTaskWithName:AnalyticsNoficationsTimeToDisplayContent
category:AnalyticsNoficationsCategory];
notificationTaskProfile = [MXSDKOptions.sharedInstance.profiler startMeasuringTaskWithName:MXTaskProfileNameNotificationsOpenEvent];
}
}
@ -707,6 +707,9 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
hasJitsiCall = NO;
[self reloadBubblesTable:YES];
}
// Screen tracking
[self.screenTimer start];
}
- (void)viewDidDisappear:(BOOL)animated
@ -742,6 +745,8 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
hasJitsiCall = YES;
[self reloadBubblesTable:YES];
}
[self.screenTimer stop];
}
- (void)viewDidLayoutSubviews

View file

@ -109,9 +109,6 @@
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// Screen tracking
[[Analytics sharedInstance] trackScreen:@"RoomFilesSearch"];
// Observe kAppDelegateDidTapStatusBarNotificationObserver.
kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {

View file

@ -111,9 +111,6 @@
{
[super viewWillAppear:animated];
// Screen tracking
[[Analytics sharedInstance] trackScreen:@"RoomMessagesSearch"];
// Observe kAppDelegateDidTapStatusBarNotificationObserver.
kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {

View file

@ -33,6 +33,8 @@
MXKSearchDataSource *filesSearchDataSource;
}
@property (nonatomic) AnalyticsScreenTimer *screenTimer;
@end
@implementation RoomSearchViewController
@ -49,6 +51,8 @@
[super finalizeInit];
// The navigation bar tint color and the rageShake Manager are handled by super (see SegmentedViewController).
self.screenTimer = [[AnalyticsScreenTimer alloc] initWithScreen:AnalyticsScreenRoomSearch];
}
- (void)viewDidLoad
@ -106,9 +110,6 @@
[self.activityIndicator stopAnimating];
self.activityIndicator = nil;
}
// Screen tracking
[[Analytics sharedInstance] trackScreen:@"RoomsSearch"];
// Enable the search field by default at the screen opening
if (self.searchBarHidden)
@ -124,6 +125,8 @@
// Refresh the search results.
// Note: We wait for 'viewDidAppear' call to consider the actual view size during this update.
[self updateSearch];
[self.screenTimer start];
}
- (void)viewWillDisappear:(BOOL)animated
@ -138,6 +141,12 @@
[super viewWillDisappear:animated];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[self.screenTimer stop];
}
- (UIStatusBarStyle)preferredStatusBarStyle
{
return ThemeService.shared.theme.statusBarStyle;

View file

@ -19,6 +19,8 @@
#import "MediaPickerViewController.h"
#import "TableViewCellWithCheckBoxes.h"
@class AnalyticsScreenTimer;
/**
List the settings fields. Used to preselect/edit a field
*/
@ -52,5 +54,10 @@ typedef enum : NSUInteger {
*/
@property (nonatomic) RoomSettingsViewControllerField selectedRoomSettingsField;
/**
The screen timer used for analytics if they've been enabled. The default value is nil.
*/
@property (nonatomic) AnalyticsScreenTimer *screenTimer;
@end

View file

@ -311,9 +311,6 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// Screen tracking
[[Analytics sharedInstance] trackScreen:@"RoomSettings"];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didUpdateRules:) name:kMXNotificationCenterDidUpdateRules object:nil];
@ -334,6 +331,8 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti
{
self.selectedRoomSettingsField = _selectedRoomSettingsField;
}
[self.screenTimer start];
}
- (void)viewWillDisappear:(BOOL)animated
@ -351,6 +350,12 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti
}
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[self.screenTimer stop];
}
// Those methods are called when the viewcontroller is added or removed from a container view controller.
- (void)willMoveToParentViewController:(nullable UIViewController *)parent
{

View file

@ -38,6 +38,9 @@
// Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change.
id kThemeServiceDidChangeThemeNotificationObserver;
}
@property (nonatomic) AnalyticsScreenTimer *screenTimer;
@end
@implementation DirectoryServerPickerViewController
@ -49,6 +52,8 @@
// Setup `MXKViewControllerHandling` properties
self.enableBarTintColorStatusChange = NO;
self.rageShakeManager = [RageShakeManager sharedManager];
self.screenTimer = [[AnalyticsScreenTimer alloc] initWithScreen:AnalyticsScreenSwitchDirectory];
}
- (void)destroy
@ -145,9 +150,6 @@
{
[super viewWillAppear:animated];
// Screen tracking
[[Analytics sharedInstance] trackScreen:@"DirectoryServerPicker"];
// Observe kAppDelegateDidTapStatusBarNotificationObserver.
kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
@ -158,6 +160,12 @@
[dataSource loadData];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self.screenTimer start];
}
- (void)viewWillDisappear:(BOOL)animated
{
if (kAppDelegateDidTapStatusBarNotificationObserver)
@ -169,6 +177,12 @@
[super viewWillDisappear:animated];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[self.screenTimer stop];
}
- (void)displayWithDataSource:(MXKDirectoryServersDataSource*)theDataSource
onComplete:(void (^)(id<MXKDirectoryServerCellDataStoring> cellData))onComplete;
{

View file

@ -40,7 +40,7 @@
{
[super finalizeInit];
self.screenName = @"Rooms";
self.screenTimer = [[AnalyticsScreenTimer alloc] initWithScreen:AnalyticsScreenRooms];
}
- (void)viewDidLoad

View file

@ -63,6 +63,7 @@ final class ShowDirectoryCoordinator: ShowDirectoryCoordinatorType {
private func createDirectoryServerPickerViewController() -> DirectoryServerPickerViewController {
let controller = DirectoryServerPickerViewController()
controller.finalizeInit()
let dataSource: MXKDirectoryServersDataSource = MXKDirectoryServersDataSource(matrixSession: session)
dataSource.finalizeInitialization()
dataSource.roomDirectoryServers = BuildSettings.publicRoomsDirectoryServers

View file

@ -68,6 +68,8 @@ final class ShowDirectoryViewController: UIViewController {
}()
private var sections: [ShowDirectorySection] = []
private let screenTimer = AnalyticsScreenTimer(screen: .roomDirectory)
// MARK: - Setup
@ -104,10 +106,17 @@ final class ShowDirectoryViewController: UIViewController {
self.keyboardAvoider?.startAvoiding()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
screenTimer.start()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
self.keyboardAvoider?.stopAvoiding()
screenTimer.stop()
}
override var preferredStatusBarStyle: UIStatusBarStyle {

View file

@ -107,7 +107,7 @@ extension ServiceTermsModalCoordinator: ServiceTermsModalScreenCoordinatorDelega
func serviceTermsModalScreenCoordinatorDidAccept(_ coordinator: ServiceTermsModalScreenCoordinatorType) {
if serviceTerms.serviceType == MXServiceTypeIdentityService {
Analytics.sharedInstance().trackValue(1, category: MXKAnalyticsCategory.contacts.rawValue, name: AnalyticsContactsIdentityServerAccepted)
Analytics.shared.trackIdentityServerAccepted(true)
}
self.delegate?.serviceTermsModalCoordinatorDidAccept(self)
@ -119,7 +119,7 @@ extension ServiceTermsModalCoordinator: ServiceTermsModalScreenCoordinatorDelega
func serviceTermsModalScreenCoordinatorDidDecline(_ coordinator: ServiceTermsModalScreenCoordinatorType) {
if serviceTerms.serviceType == MXServiceTypeIdentityService {
Analytics.sharedInstance().trackValue(1, category: MXKAnalyticsCategory.contacts.rawValue, name: AnalyticsContactsIdentityServerAccepted)
Analytics.shared.trackIdentityServerAccepted(false)
disableIdentityServer()
}
@ -131,7 +131,7 @@ extension ServiceTermsModalCoordinator: ServiceTermsModalScreenCoordinatorDelega
extension ServiceTermsModalCoordinator: UIAdaptivePresentationControllerDelegate {
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
if serviceTerms.serviceType == MXServiceTypeIdentityService {
Analytics.sharedInstance().trackValue(0, category: MXKAnalyticsCategory.contacts.rawValue, name: AnalyticsContactsIdentityServerAccepted)
Analytics.shared.trackIdentityServerAccepted(false)
}
self.delegate?.serviceTermsModalCoordinatorDidDismissInteractively(self)

View file

@ -47,6 +47,8 @@ static CGFloat const kTextFontSize = 15.0;
@property (weak, nonatomic) id <NSObject> themeDidChangeNotificationObserver;
@property (nonatomic) AnalyticsScreenTimer *screenTimer;
@end
#pragma mark - Implementation
@ -62,6 +64,12 @@ static CGFloat const kTextFontSize = 15.0;
return viewController;
}
- (void)finalizeInit
{
[super finalizeInit];
self.screenTimer = [[AnalyticsScreenTimer alloc] initWithScreen:AnalyticsScreenDeactivateAccount];
}
- (void)destroy
{
id<NSObject> notificationObserver = self.themeDidChangeNotificationObserver;
@ -95,9 +103,12 @@ static CGFloat const kTextFontSize = 15.0;
[super viewWillAppear:animated];
[self userInterfaceThemeDidChange];
// Screen tracking
[[Analytics sharedInstance] trackScreen:@"DeactivateAccount"];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self.screenTimer start];
}
- (void)viewDidLayoutSubviews
@ -107,6 +118,12 @@ static CGFloat const kTextFontSize = 15.0;
[self.deactivateAcccountButton.layer setCornerRadius:kButtonCornerRadius];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[self.screenTimer stop];
}
- (UIStatusBarStyle)preferredStatusBarStyle
{
return ThemeService.shared.theme.statusBarStyle;

View file

@ -106,14 +106,6 @@
}
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// Screen tracking
[[Analytics sharedInstance] trackScreen:@"CountryPicker"];
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath;
{
cell.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor;

View file

@ -95,14 +95,6 @@
return ThemeService.shared.theme.statusBarStyle;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// Screen tracking
[[Analytics sharedInstance] trackScreen:@"CountryPicker"];
}
- (void)destroy
{
[super destroy];

View file

@ -161,9 +161,6 @@ enum {
{
[super viewWillAppear:animated];
// Screen tracking
[[Analytics sharedInstance] trackScreen:@"ManageSession"];
// Release the potential pushed view controller
[self releasePushedViewController];

View file

@ -119,6 +119,8 @@ TableViewSectionsDelegate>
@property (nonatomic, strong) SetPinCoordinatorBridgePresenter *setPinCoordinatorBridgePresenter;
@property (nonatomic, strong) CrossSigningSetupCoordinatorBridgePresenter *crossSigningSetupCoordinatorBridgePresenter;
@property (nonatomic) AnalyticsScreenTimer *screenTimer;
@end
@implementation SecurityViewController
@ -142,6 +144,8 @@ TableViewSectionsDelegate>
// Setup `MXKViewControllerHandling` properties
self.enableBarTintColorStatusChange = NO;
self.rageShakeManager = [RageShakeManager sharedManager];
self.screenTimer = [[AnalyticsScreenTimer alloc] initWithScreen:AnalyticsScreenSettingsSecurity];
}
- (void)viewDidLoad
@ -250,9 +254,6 @@ TableViewSectionsDelegate>
{
[super viewWillAppear:animated];
// Screen tracking
[[Analytics sharedInstance] trackScreen:@"Security"];
// Release the potential pushed view controller
[self releasePushedViewController];
@ -268,6 +269,12 @@ TableViewSectionsDelegate>
[self loadCrossSigning];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self.screenTimer start];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
@ -279,6 +286,12 @@ TableViewSectionsDelegate>
}
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[self.screenTimer stop];
}
#pragma mark - Internal methods
- (void)updateSections

View file

@ -283,6 +283,8 @@ TableViewSectionsDelegate>
@property (nonatomic) BOOL isPreparingIdentityService;
@property (nonatomic, strong) ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter;
@property (nonatomic) AnalyticsScreenTimer *screenTimer;
@end
@implementation SettingsViewController
@ -315,6 +317,8 @@ TableViewSectionsDelegate>
isSavingInProgress = NO;
isResetPwdInProgress = NO;
is3PIDBindingInProgress = NO;
self.screenTimer = [[AnalyticsScreenTimer alloc] initWithScreen:AnalyticsScreenSettings];
}
- (void)updateSections
@ -776,9 +780,6 @@ TableViewSectionsDelegate>
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// Screen tracking
[[Analytics sharedInstance] trackScreen:@"Settings"];
// Refresh display
[self refreshSettings];
@ -808,6 +809,8 @@ TableViewSectionsDelegate>
[self releasePushedViewController];
[self.settingsDiscoveryTableViewSection reload];
[self.screenTimer start];
}
- (void)viewWillDisappear:(BOOL)animated
@ -851,6 +854,12 @@ TableViewSectionsDelegate>
}
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[self.screenTimer stop];
}
#pragma mark - Internal methods
- (void)pushViewController:(UIViewController*)viewController
@ -2251,11 +2260,11 @@ TableViewSectionsDelegate>
{
MXKTableViewCellWithLabelAndSwitch* sendCrashReportCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath];
sendCrashReportCell.mxkLabel.text = [VectorL10n settingsSendCrashReport];
sendCrashReportCell.mxkSwitch.on = RiotSettings.shared.enableCrashReport;
sendCrashReportCell.mxkLabel.text = VectorL10n.settingsAnalyticsAndCrashData;
sendCrashReportCell.mxkSwitch.on = RiotSettings.shared.enableAnalytics;
sendCrashReportCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor;
sendCrashReportCell.mxkSwitch.enabled = YES;
[sendCrashReportCell.mxkSwitch addTarget:self action:@selector(toggleSendCrashReport:) forControlEvents:UIControlEventTouchUpInside];
[sendCrashReportCell.mxkSwitch addTarget:self action:@selector(toggleAnalytics:) forControlEvents:UIControlEventTouchUpInside];
cell = sendCrashReportCell;
}
@ -3115,27 +3124,20 @@ TableViewSectionsDelegate>
[[MXKRoomDataSourceManager sharedManagerForMatrixSession:self.mainSession] reset];
}
- (void)toggleSendCrashReport:(id)sender
- (void)toggleAnalytics:(UISwitch *)sender
{
BOOL enable = RiotSettings.shared.enableCrashReport;
if (enable)
if (sender.isOn)
{
MXLogDebug(@"[SettingsViewController] disable automatic crash report and analytics sending");
RiotSettings.shared.enableCrashReport = NO;
[[Analytics sharedInstance] stop];
// Remove potential crash file.
[MXLogger deleteCrashLog];
MXLogDebug(@"[SettingsViewController] enable automatic crash report and analytics sending");
[Analytics.shared optInWith:self.mainSession];
}
else
{
MXLogDebug(@"[SettingsViewController] enable automatic crash report and analytics sending");
MXLogDebug(@"[SettingsViewController] disable automatic crash report and analytics sending");
[Analytics.shared optOut];
RiotSettings.shared.enableCrashReport = YES;
[[Analytics sharedInstance] start];
// Remove potential crash file.
[MXLogger deleteCrashLog];
}
}

View file

@ -48,6 +48,7 @@ final class SideMenuViewController: UIViewController {
private var keyboardAvoider: KeyboardAvoider?
private var errorPresenter: MXKErrorPresentation!
private var activityPresenter: ActivityIndicatorPresenter!
private var screenTimer = AnalyticsScreenTimer(screen: .sidebar)
private var sideMenuActionViews: [SideMenuActionView] = []
private weak var sideMenuVersionView: SideMenuVersionView?
@ -86,8 +87,14 @@ final class SideMenuViewController: UIViewController {
navigationController?.setNavigationBarHidden(true, animated: animated)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
screenTimer.start()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
screenTimer.stop()
}
override var preferredStatusBarStyle: UIStatusBarStyle {

View file

@ -73,8 +73,6 @@
{
[super finalizeInit];
self.screenName = @"StartChat";
_isAddParticipantSearchBarEditing = NO;
// Prepare room participants
@ -82,6 +80,8 @@
// Assign itself as delegate
self.contactsTableViewControllerDelegate = self;
self.screenTimer = [[AnalyticsScreenTimer alloc] initWithScreen:AnalyticsScreenStartChat];
}
- (void)viewDidLoad

View file

@ -193,5 +193,6 @@ typedef NS_ENUM(NSUInteger, MasterTabBarIndex) {
- (void)masterTabBarController:(MasterTabBarController *)masterTabBarController didSelectRoomPreviewWithParameters:(RoomPreviewNavigationParameters*)roomPreviewNavigationParameters completion:(void (^)(void))completion;
- (void)masterTabBarController:(MasterTabBarController *)masterTabBarController didSelectContact:(MXKContact*)contact withPresentationParameters:(ScreenPresentationParameters*)presentationParameters;
- (void)masterTabBarController:(MasterTabBarController *)masterTabBarController didSelectGroup:(MXGroup*)group inMatrixSession:(MXSession*)matrixSession presentationParameters:(ScreenPresentationParameters*)presentationParameters;
- (void)masterTabBarController:(MasterTabBarController *)masterTabBarController shouldPresentAnalyticsPromptForMatrixSession:(MXSession*)matrixSession;
@end

View file

@ -70,6 +70,11 @@
@property(nonatomic) BOOL reviewSessionAlertHasBeenDisplayed;
/**
A flag to indicate that the analytics prompt should be shown during `-addMatrixSession:`.
*/
@property(nonatomic) BOOL presentAnalyticsPromptOnAddSession;
@end
@implementation MasterTabBarController
@ -196,11 +201,18 @@
if (!authIsShown)
{
// Check whether the user has been already prompted to send crash reports.
// (Check whether 'enableCrashReport' flag has been set once)
if (!RiotSettings.shared.isEnableCrashReportHasBeenSetOnce)
// Check whether the user should be prompted to send analytics.
if (Analytics.shared.shouldShowAnalyticsPrompt)
{
[self promptUserBeforeUsingAnalytics];
MXSession *mxSession = self.mxSessions.firstObject;
if (mxSession)
{
[self promptUserBeforeUsingAnalyticsForSession:mxSession];
}
else
{
self.presentAnalyticsPromptOnAddSession = YES;
}
}
[self refreshTabBarBadges];
@ -405,6 +417,12 @@
return;
}
if (self.presentAnalyticsPromptOnAddSession)
{
self.presentAnalyticsPromptOnAddSession = NO;
[self promptUserBeforeUsingAnalyticsForSession:mxSession];
}
// Check whether the controller'€™s view is loaded into memory.
if (self.homeViewController)
{
@ -921,50 +939,14 @@
#pragma mark -
- (void)promptUserBeforeUsingAnalytics
- (void)promptUserBeforeUsingAnalyticsForSession:(MXSession *)mxSession
{
MXLogDebug(@"[MasterTabBarController]: Invite the user to send crash reports");
__weak typeof(self) weakSelf = self;
[currentAlert dismissViewControllerAnimated:NO completion:nil];
NSString *appDisplayName = [[NSBundle mainBundle] infoDictionary][@"CFBundleDisplayName"];
currentAlert = [UIAlertController alertControllerWithTitle:[VectorL10n googleAnalyticsUsePrompt:appDisplayName] message:nil preferredStyle:UIAlertControllerStyleAlert];
[currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n no]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
RiotSettings.shared.enableCrashReport = NO;
if (weakSelf)
{
typeof(self) self = weakSelf;
self->currentAlert = nil;
}
}]];
[currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n yes]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
RiotSettings.shared.enableCrashReport = YES;
if (weakSelf)
{
typeof(self) self = weakSelf;
self->currentAlert = nil;
}
[[Analytics sharedInstance] start];
}]];
[currentAlert mxk_setAccessibilityIdentifier: @"HomeVCUseAnalyticsAlert"];
[self presentViewController:currentAlert animated:YES completion:nil];
// Analytics aren't collected on iOS 12 & 13.
if (@available(iOS 14.0, *))
{
MXLogDebug(@"[MasterTabBarController]: Invite the user to send analytics");
[self.masterTabBarDelegate masterTabBarController:self shouldPresentAnalyticsPromptForMatrixSession:mxSession];
}
}
#pragma mark - Review session

View file

@ -487,6 +487,19 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType {
self.splitViewMasterPresentableDelegate?.splitViewMasterPresentableWantsToResetDetail(self)
}
@available(iOS 14.0, *)
private func presentAnalyticsPrompt(with session: MXSession) {
let parameters = AnalyticsPromptCoordinatorParameters(session: session, navigationRouter: navigationRouter)
let coordinator = AnalyticsPromptCoordinator(parameters: parameters)
coordinator.completion = { [weak self, weak coordinator] in
guard let self = self, let coordinator = coordinator else { return }
self.remove(childCoordinator: coordinator)
}
coordinator.start()
add(childCoordinator: coordinator)
}
// MARK: UserSessions management
private func registerUserSessionsServiceNotifications() {
@ -578,6 +591,12 @@ extension TabBarCoordinator: MasterTabBarControllerDelegate {
self.masterTabBarController.navigationItem.leftBarButtonItem = sideMenuBarButtonItem
}
func masterTabBarController(_ masterTabBarController: MasterTabBarController!, shouldPresentAnalyticsPromptForMatrixSession matrixSession: MXSession!) {
if #available(iOS 14.0, *) {
presentAnalyticsPrompt(with: matrixSession)
}
}
}
// MARK: - RoomCoordinatorDelegate

View file

@ -120,9 +120,6 @@
{
[super viewWillAppear:animated];
// Screen tracking
[[Analytics sharedInstance] trackScreen:@"UnknowDevices"];
[self.tableView reloadData];
}

View file

@ -45,6 +45,7 @@
#import "RoomInputToolbarView.h"
#import "NSArray+Element.h"
#import "ShareItemSender.h"
#import "HTMLFormatter.h"
// MatrixKit common imports, shared with all targets
#import "MatrixKit-Bridging-Header.h"
@ -62,4 +63,3 @@
#import "MXKRoomDataSourceManager.h"
#import "MXRoom+Sync.h"
#import "UIAlertController+MatrixKit.h"
#import "MXKAnalyticsConstants.h"

View file

@ -0,0 +1,38 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
NS_ASSUME_NONNULL_BEGIN
@interface HTMLFormatter : NSObject
/** Builds an attributed string from a string containing html.
@param htmlString The html string to use.
@param allowedTags The html tags that should be allowed.
@param fontSize The default font size to use.
Note: It is recommended to include "p" and "body" tags in
`allowedTags` as these are often added when parsing.
*/
- (NSAttributedString * _Nonnull)formatHTML:(NSString * _Nonnull)htmlString
withAllowedTags:(NSArray<NSString *> * _Nonnull)allowedTags
fontSize:(CGFloat)fontSize;
@end
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,61 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#import "HTMLFormatter.h"
#import "GeneratedInterface-Swift.h"
@implementation HTMLFormatter
- (NSAttributedString *)formatHTML:(NSString *)htmlString withAllowedTags:(NSArray<NSString *> *)allowedTags fontSize:(CGFloat)fontSize
{
// TODO: This method should be more general purpose and usable from MXKEventFormatter and GroupHomeViewController
// FIXME: The implementation is currently in Objective-C as there is a crash in the callback when implemented in Swift
UIFont *font = [UIFont systemFontOfSize:fontSize];
// Do some sanitisation before finalizing the string
DTHTMLAttributedStringBuilderWillFlushCallback sanitizeCallback = ^(DTHTMLElement *element) {
[element sanitizeWith:allowedTags bodyFont:font imageHandler:nil];
};
NSDictionary *options = @{
DTUseiOS6Attributes: @(YES), // Enable it to be able to display the attributed string in a UITextView
DTDefaultFontFamily: font.familyName,
DTDefaultFontName: font.fontName,
DTDefaultFontSize: @(font.pointSize),
DTDefaultLinkDecoration: @(NO),
DTWillFlushBlockCallBack: sanitizeCallback
};
// Do not use the default HTML renderer of NSAttributedString because this method
// runs on the UI thread which we want to avoid because renderHTMLString is called
// most of the time from a background thread.
// Use DTCoreText HTML renderer instead.
// Using DTCoreText, which renders static string, helps to avoid code injection attacks
// that could happen with the default HTML renderer of NSAttributedString which is a
// webview.
NSAttributedString *string = [[NSAttributedString alloc] initWithHTMLData:[htmlString dataUsingEncoding:NSUTF8StringEncoding] options:options documentAttributes:NULL];
// Apply additional treatments
string = [MXKTools removeDTCoreTextArtifacts:string];
if (!string) {
return [[NSAttributedString alloc] initWithString:htmlString];
}
return string;
}
@end

View file

@ -0,0 +1,35 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
extension HTMLFormatter {
/// Builds an attributed string by replacing a `%@` placeholder with the supplied link text and URL.
/// - Parameters:
/// - string: The string to be formatted.
/// - link: The link text to be inserted.
/// - url: The URL to be linked to.
/// - Returns: An attributed string.
func format(_ string: String, with link: String, using url: URL) -> NSAttributedString {
let baseString = NSMutableAttributedString(string: string)
let attributedLink = NSAttributedString(string: link, attributes: [.link: url])
let linkRange = (baseString.string as NSString).range(of: "%@")
baseString.replaceCharacters(in: linkRange, with: attributedLink)
return baseString
}
}

View file

@ -0,0 +1,128 @@
// File created from SimpleUserProfileExample
// $ createScreen.sh AnalyticsPrompt AnalyticsPrompt
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
// The state is never modified so this is unnecessary.
enum AnalyticsPromptStateAction { }
enum AnalyticsPromptViewAction {
/// Enable analytics.
case enable
/// Disable analytics.
case disable
/// Open the service terms link.
case openTermsURL
}
enum AnalyticsPromptViewModelResult {
/// Enable analytics.
case enable
/// Disable analytics.
case disable
}
struct AnalyticsPromptViewState: BindableState {
/// The type of prompt to display.
let promptType: AnalyticsPromptType
/// Localized attributed strings created in the coordinator.
let strings: AnalyticsPromptStringsProtocol
}
/// A collection of strings for the UI that need to be created in
/// the coordinator or mocked in the RiotSwiftUI target.
protocol AnalyticsPromptStringsProtocol {
var appDisplayName: String { get }
var point1: NSAttributedString { get }
var point2: NSAttributedString { get }
var termsNewUser: NSAttributedString { get }
var termsUpgrade: NSAttributedString { get }
}
enum AnalyticsPromptType {
case newUser(termsString: NSAttributedString)
case upgrade(termsString: NSAttributedString)
}
extension AnalyticsPromptType {
/// The main description string that should be displayed.
var message: String {
switch self {
case .newUser:
return VectorL10n.analyticsPromptMessageNewUser
case .upgrade:
return VectorL10n.analyticsPromptMessageUpgrade
}
}
/// The terms string that should be displayed.
var termsStrings: NSAttributedString {
switch self {
case .newUser(let termsString), .upgrade(let termsString):
return termsString
}
}
/// The title for the enable button.
var enableButtonTitle: String {
switch self {
case .newUser:
return VectorL10n.enable
case .upgrade:
return VectorL10n.analyticsPromptYes
}
}
/// The title for the disable button.
var disableButtonTitle: String {
switch self {
case .newUser:
return VectorL10n.analyticsPromptNotNow
case .upgrade:
return VectorL10n.analyticsPromptStop
}
}
}
extension AnalyticsPromptType: CaseIterable {
static var allCases: [AnalyticsPromptType] {
let strings = MockAnalyticsPromptStrings()
return [
.newUser(termsString: strings.termsNewUser),
.upgrade(termsString: strings.termsUpgrade)
]
}
}
extension AnalyticsPromptType: Identifiable {
var id: String {
switch self {
case .newUser:
return "newUser"
case .upgrade:
return "upgrade"
}
}
}
// For the RiotSwiftUI target presentation.
extension AnalyticsPromptType: CustomStringConvertible {
var description: String { id }
}

View file

@ -0,0 +1,78 @@
// File created from SimpleUserProfileExample
// $ createScreen.sh AnalyticsPrompt AnalyticsPrompt
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import SwiftUI
import Combine
@available(iOS 14, *)
typealias AnalyticsPromptViewModelType = StateStoreViewModel<AnalyticsPromptViewState,
AnalyticsPromptStateAction,
AnalyticsPromptViewAction>
@available(iOS 14, *)
class AnalyticsPromptViewModel: AnalyticsPromptViewModelType {
// MARK: - Properties
// MARK: Private
let termsURL: URL
// MARK: Public
var completion: ((AnalyticsPromptViewModelResult) -> Void)?
// MARK: - Setup
/// Initialize a view model with the specified prompt type and app display name.
init(promptType: AnalyticsPromptType, strings: AnalyticsPromptStringsProtocol, termsURL: URL) {
self.termsURL = termsURL
super.init(initialViewState: AnalyticsPromptViewState(promptType: promptType, strings: strings))
}
// MARK: - Public
override func process(viewAction: AnalyticsPromptViewAction) {
switch viewAction {
case .enable:
enable()
case .disable:
disable()
case .openTermsURL:
openTermsURL()
}
}
override class func reducer(state: inout AnalyticsPromptViewState, action: AnalyticsPromptStateAction) {
// There is no mutable state to reduce :)
}
/// Enable analytics. The call to the Analytics class is made in the completion.
private func enable() {
completion?(.enable)
}
/// Disable analytics. The call to the Analytics class is made in the completion.
private func disable() {
completion?(.disable)
}
/// Open the service terms link.
private func openTermsURL() {
UIApplication.shared.open(termsURL)
}
}

View file

@ -0,0 +1,104 @@
// File created from SimpleUserProfileExample
// $ createScreen.sh AnalyticsPrompt AnalyticsPrompt
/*
Copyright 2021 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import SwiftUI
struct AnalyticsPromptCoordinatorParameters {
/// The session to use if analytics are enabled.
let session: MXSession
/// The navigation router used to display the prompt.
let navigationRouter: NavigationRouterType
}
final class AnalyticsPromptCoordinator: Coordinator {
// MARK: - Properties
// MARK: Private
private let parameters: AnalyticsPromptCoordinatorParameters
private let analyticsPromptHostingController: UIViewController
private var _analyticsPromptViewModel: Any? = nil
@available(iOS 14.0, *)
fileprivate var analyticsPromptViewModel: AnalyticsPromptViewModel {
return _analyticsPromptViewModel as! AnalyticsPromptViewModel
}
// MARK: Public
// Must be used only internally
var childCoordinators: [Coordinator] = []
var completion: (() -> Void)?
// MARK: - Setup
@available(iOS 14.0, *)
init(parameters: AnalyticsPromptCoordinatorParameters) {
self.parameters = parameters
let strings = AnalyticsPromptStrings()
let promptType: AnalyticsPromptType
if Analytics.shared.promptShouldDisplayUpgradeMessage {
promptType = .upgrade(termsString: strings.termsUpgrade)
} else {
promptType = .newUser(termsString: strings.termsNewUser)
}
let viewModel = AnalyticsPromptViewModel(promptType: promptType, strings: strings, termsURL: BuildSettings.analyticsTermsURL)
let view = AnalyticsPrompt(viewModel: viewModel.context)
_analyticsPromptViewModel = viewModel
analyticsPromptHostingController = VectorHostingController(rootView: view)
}
// MARK: - Public
func start() {
guard #available(iOS 14.0, *) else {
MXLog.debug("[AnalyticsPromptCoordinator] start: Invalid iOS version, returning.")
return
}
MXLog.debug("[AnalyticsPromptCoordinator] did start.")
parameters.navigationRouter.present(toPresentable(), animated: true)
analyticsPromptViewModel.completion = { [weak self] result in
MXLog.debug("[AnalyticsPromptCoordinator] AnalyticsPromptViewModel did complete with result: \(result).")
guard let self = self else { return }
switch result {
case .enable:
Analytics.shared.optIn(with: self.parameters.session)
self.parameters.navigationRouter.dismissModule(animated: true, completion: nil)
self.completion?()
case .disable:
Analytics.shared.optOut()
self.parameters.navigationRouter.dismissModule(animated: true, completion: nil)
self.completion?()
}
}
}
func toPresentable() -> UIViewController {
return self.analyticsPromptHostingController
}
}

View file

@ -0,0 +1,33 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
@available(iOS 14.0, *)
struct AnalyticsPromptStrings: AnalyticsPromptStringsProtocol {
let appDisplayName = AppInfo.current.displayName
let point1 = HTMLFormatter().formatHTML(VectorL10n.analyticsPromptPoint1, withAllowedTags: ["b", "p"], fontSize: UIFont.systemFontSize)
let point2 = HTMLFormatter().formatHTML(VectorL10n.analyticsPromptPoint2, withAllowedTags: ["b", "p"], fontSize: UIFont.systemFontSize)
let termsNewUser = HTMLFormatter().format(VectorL10n.analyticsPromptTermsNewUser("%@"),
with: VectorL10n.analyticsPromptTermsLinkNewUser,
using: BuildSettings.analyticsTermsURL)
let termsUpgrade = HTMLFormatter().format(VectorL10n.analyticsPromptTermsUpgrade("%@"),
with: VectorL10n.analyticsPromptTermsLinkUpgrade,
using: BuildSettings.analyticsTermsURL)
}

View file

@ -0,0 +1,56 @@
// File created from SimpleUserProfileExample
// $ createScreen.sh AnalyticsPrompt AnalyticsPrompt
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
import SwiftUI
/// Using an enum for the screen allows you define the different state cases with
/// the relevant associated data for each case.
@available(iOS 14.0, *)
enum MockAnalyticsPromptScreenState: MockScreenState, CaseIterable {
/// The type of prompt to display.
case promptType(AnalyticsPromptType)
/// The associated screen
var screenType: Any.Type {
AnalyticsPrompt.self
}
/// A list of screen state definitions
static var allCases: [MockAnalyticsPromptScreenState] {
AnalyticsPromptType.allCases.map { MockAnalyticsPromptScreenState.promptType($0) }
}
/// Generate the view struct for the screen state.
var screenView: ([Any], AnyView) {
let promptType: AnalyticsPromptType
switch self {
case .promptType(let analyticsPromptType):
promptType = analyticsPromptType
}
let viewModel = AnalyticsPromptViewModel(promptType: promptType,
strings: MockAnalyticsPromptStrings(),
termsURL: URL(string: "https://element.io/cookie-policy")!)
return (
[promptType, viewModel],
AnyView(AnalyticsPrompt(viewModel: viewModel.context)
.addDependency(MockAvatarService.example))
)
}
}

View file

@ -0,0 +1,52 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import UIKit
struct MockAnalyticsPromptStrings: AnalyticsPromptStringsProtocol {
var appDisplayName = "Element"
let point1: NSAttributedString
let point2: NSAttributedString
let termsNewUser: NSAttributedString
let termsUpgrade: NSAttributedString
let shortString = NSAttributedString(string: "This is a short string.")
let longString = NSAttributedString(string: "This is a very long string that will be used to test the layout over multiple lines of text to ensure everything is correct.")
init() {
let point1 = NSMutableAttributedString(string: "We ")
point1.append(NSAttributedString(string: "don't", attributes: [.font: UIFont.boldSystemFont(ofSize: UIFont.systemFontSize)]))
point1.append(NSAttributedString(string: " record or profile any account data"))
self.point1 = point1
let point2 = NSMutableAttributedString(string: "We ")
point2.append(NSAttributedString(string: "don't", attributes: [.font: UIFont.boldSystemFont(ofSize: UIFont.systemFontSize)]))
point2.append(NSAttributedString(string: " share information with third parties"))
self.point2 = point2
let termsNewUser = NSMutableAttributedString(string: "You can read all our terms ")
termsNewUser.append(NSAttributedString(string: "here", attributes: [.link: URL(string: "https://element.io/cookie-policy")!]))
termsNewUser.append(NSAttributedString(string: "."))
self.termsNewUser = termsNewUser
let termsUpgrade = NSMutableAttributedString(string: "Read all our terms ")
termsUpgrade.append(NSAttributedString(string: "here", attributes: [.link: URL(string: "https://element.io/cookie-policy")!]))
termsUpgrade.append(NSAttributedString(string: ". Is that OK?"))
self.termsUpgrade = termsUpgrade
}
}

Some files were not shown because too many files have changed in this diff Show more