Merge branch 'develop' into ismail/4384_room_summary_store

This commit is contained in:
ismailgulek 2021-10-12 11:49:55 +03:00
commit 58df3d3309
No known key found for this signature in database
GPG key ID: E96336D42D9470A9
120 changed files with 3683 additions and 1012 deletions

View file

@ -11,7 +11,8 @@ disabled_rules:
- large_tuple
- shorthand_operator
- vertical_parameter_alignment
- identifier_name
- identifier_name
- inclusive_language # Disabled until MasterTabBarController refactoring complete
# some rules are only opt-in
opt_in_rules:

View file

@ -1,3 +1,31 @@
## Changes in 1.6.2 (2021-10-08)
🙌 Improvements
- Upgrade MatrixKit version ([v0.16.5](https://github.com/matrix-org/matrix-ios-kit/releases/tag/v0.16.5)).
- URL Previews: Use attributed string whitespace for cell heights and stop breaking up the bubble data. ([#4896](https://github.com/vector-im/element-ios/issues/4896))
- Replaced localizable strings with generated ones throughout the code. Fixed various translation issues. ([#4899](https://github.com/vector-im/element-ios/issues/4899))
- Voice Message scrubbing should require a slightly longer press, to avoid accidental scrubbing when scrolling the timeline ([#4935](https://github.com/vector-im/element-ios/issues/4935))
- Pods: Update ffmpeg-kit-ios-audio, FLEX, FlowCommoniOS, Reusable and SwiftLint. ([#4939](https://github.com/vector-im/element-ios/issues/4939))
- Service Terms: Track an analytics value on accept/decline of an identity server. ([#4955](https://github.com/vector-im/element-ios/issues/4955))
🐛 Bugfixes
- RecentsDataSource: Memory leak in [RecentsDataSource dataSource:didStateChange:]. ([#4193](https://github.com/vector-im/element-ios/pull/4193))
- i18n: Standardise casing of identity server and integration manager. ([#4559](https://github.com/vector-im/element-ios/issues/4559))
- MasterTabBarController: Listen to `MXSpaceNotificationCounter` to update the notification badge ([#4898](https://github.com/vector-im/element-ios/issues/4898))
- Fixed unintentional voice message drafts on automatically cancelled recordings (under 1 second) ([#4970](https://github.com/vector-im/element-ios/issues/4970))
🧱 Build
- Element Alpha: Build on macOS 11 to fix iOS 15 installation error. ([#4937](https://github.com/vector-im/element-ios/issues/4937))
- Bundler: Update CocoaPods and fastlane and xcode-install. ([#4951](https://github.com/vector-im/element-ios/issues/4951))
📄 Documentation
- Update PR template with a checkbox for accessibility and self review. ([#4920](https://github.com/vector-im/element-ios/issues/4920))
## Changes in 1.6.1 (2021-09-30)
🙌 Improvements

View file

@ -15,5 +15,5 @@
//
// Version
MARKETING_VERSION = 1.6.2
CURRENT_PROJECT_VERSION = 1.6.2
MARKETING_VERSION = 1.6.3
CURRENT_PROJECT_VERSION = 1.6.3

View file

@ -13,7 +13,7 @@ use_frameworks!
# - `{ {kit spec hash} => {sdk spec hash}` to depend on specific pod options (:git => …, :podspec => …) for each repo. Used by Fastfile during CI
#
# Warning: our internal tooling depends on the name of this variable name, so be sure not to change it
$matrixKitVersion = '= 0.16.4'
$matrixKitVersion = '= 0.16.5'
# $matrixKitVersion = :local
# $matrixKitVersion = {'develop' => 'develop'}
@ -48,6 +48,7 @@ abstract_target 'RiotPods' do
pod 'GBDeviceInfo', '~> 6.6.0'
pod 'Reusable', '~> 4.1'
pod 'KeychainAccess', '~> 4.2.2'
pod 'WeakDictionary', '~> 2.0'
# Piwik for analytics
pod 'MatomoTracker', '~> 7.4.1'

View file

@ -45,7 +45,7 @@ PODS:
- GBDeviceInfo/Core (6.6.0)
- GZIP (1.3.0)
- HPGrowingTextView (1.1)
- JitsiMeetSDK (3.5.0)
- JitsiMeetSDK (3.10.2)
- KeychainAccess (4.2.2)
- KituraContracts (1.2.1):
- LoggerAPI (~> 1.7)
@ -58,30 +58,30 @@ PODS:
- MatomoTracker (7.4.1):
- MatomoTracker/Core (= 7.4.1)
- MatomoTracker/Core (7.4.1)
- MatrixKit (0.16.4):
- MatrixKit (0.16.5):
- Down (~> 0.11.0)
- DTCoreText (~> 1.6.25)
- HPGrowingTextView (~> 1.1)
- libPhoneNumber-iOS (~> 0.9.13)
- MatrixKit/Core (= 0.16.4)
- MatrixSDK (= 0.20.4)
- MatrixKit/Core (0.16.4):
- MatrixKit/Core (= 0.16.5)
- MatrixSDK (= 0.20.5)
- MatrixKit/Core (0.16.5):
- Down (~> 0.11.0)
- DTCoreText (~> 1.6.25)
- HPGrowingTextView (~> 1.1)
- libPhoneNumber-iOS (~> 0.9.13)
- MatrixSDK (= 0.20.4)
- MatrixSDK (0.20.4):
- MatrixSDK/Core (= 0.20.4)
- MatrixSDK/Core (0.20.4):
- MatrixSDK (= 0.20.5)
- MatrixSDK (0.20.5):
- MatrixSDK/Core (= 0.20.5)
- MatrixSDK/Core (0.20.5):
- AFNetworking (~> 4.0.0)
- GZIP (~> 1.3.0)
- libbase58 (~> 0.1.4)
- OLMKit (~> 3.2.5)
- Realm (= 10.7.6)
- Realm (= 10.16.0)
- SwiftyBeaver (= 1.9.5)
- MatrixSDK/JingleCallStack (0.20.4):
- JitsiMeetSDK (= 3.5.0)
- MatrixSDK/JingleCallStack (0.20.5):
- JitsiMeetSDK (= 3.10.2)
- MatrixSDK/Core
- OLMKit (3.2.5):
- OLMKit/olmc (= 3.2.5)
@ -89,9 +89,9 @@ PODS:
- OLMKit/olmc (3.2.5)
- OLMKit/olmcpp (3.2.5)
- ReadMoreTextView (3.0.1)
- Realm (10.7.6):
- Realm/Headers (= 10.7.6)
- Realm/Headers (10.7.6)
- Realm (10.16.0):
- Realm/Headers (= 10.16.0)
- Realm/Headers (10.16.0)
- Reusable (4.1.2):
- Reusable/Storyboard (= 4.1.2)
- Reusable/View (= 4.1.2)
@ -124,7 +124,7 @@ DEPENDENCIES:
- KeychainAccess (~> 4.2.2)
- KTCenterFlowLayout (~> 1.3.1)
- MatomoTracker (~> 7.4.1)
- MatrixKit (= 0.16.4)
- MatrixKit (= 0.16.5)
- MatrixSDK
- MatrixSDK/JingleCallStack
- OLMKit
@ -195,7 +195,7 @@ SPEC CHECKSUMS:
GBDeviceInfo: ed0db16230d2fa280e1cbb39a5a7f60f6946aaec
GZIP: 416858efbe66b41b206895ac6dfd5493200d95b3
HPGrowingTextView: 88a716d97fb853bcb08a4a08e4727da17efc9b19
JitsiMeetSDK: ef6ebbad2237c0e3ea6ff61fea78745f9543b238
JitsiMeetSDK: 2f118fa770f23e518f3560fc224fae3ac7062223
KeychainAccess: c0c4f7f38f6fc7bbe58f5702e25f7bd2f65abf51
KituraContracts: e845e60dc8627ad0a76fa55ef20a45451d8f830b
KTCenterFlowLayout: 6e02b50ab2bd865025ae82fe266ed13b6d9eaf97
@ -204,11 +204,11 @@ SPEC CHECKSUMS:
LoggerAPI: ad9c4a6f1e32f518fdb43a1347ac14d765ab5e3d
Logging: beeb016c9c80cf77042d62e83495816847ef108b
MatomoTracker: 24a846c9d3aa76933183fe9d47fd62c9efa863fb
MatrixKit: e2836912d2dc2072a276526e92679d094b2bd6b5
MatrixSDK: 317928f6ef7bbffebbf7dbf9ca9dad4920695f1e
MatrixKit: a37efb94bb7c53b5dc912f0fd35971861b6c28bf
MatrixSDK: 417fac309f510b5f8ac121ba8abe3b897953e1ce
OLMKit: 9fb4799c4a044dd2c06bda31ec31a12191ad30b5
ReadMoreTextView: 19147adf93abce6d7271e14031a00303fe28720d
Realm: ed860452717c8db8f4bf832b6807f7f2ce708839
Realm: b6027801398f3743fc222f096faa85281b506e6c
Reusable: 6bae6a5e8aa793c9c441db0213c863a64bce9136
SideMenu: f583187d21c5b1dd04c72002be544b555a2627a2
SwiftBase32: 9399c25a80666dc66b51e10076bf591e3bbb8f17
@ -219,6 +219,6 @@ SPEC CHECKSUMS:
zxcvbn-ios: fef98b7c80f1512ff0eec47ac1fa399fc00f7e3c
ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb
PODFILE CHECKSUM: 367d7a514c7d8ea3cc4b0adb5878c8a192c7c2c8
PODFILE CHECKSUM: e189a08f2a6f081d6eb0f57aaa898833f27a9adb
COCOAPODS: 1.10.1
COCOAPODS: 1.11.2

View file

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

View file

@ -0,0 +1,91 @@
%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 19.184572 19.000000 cm
0.552000 0.592000 0.648000 scn
9.087891 0.000000 m
14.071289 0.000000 18.184570 4.113281 18.184570 9.087891 c
18.184570 14.062500 14.062500 18.175781 9.079102 18.175781 c
4.104492 18.175781 0.000000 14.062500 0.000000 9.087891 c
0.000000 4.113281 4.113281 0.000000 9.087891 0.000000 c
h
9.087891 1.810547 m
5.053711 1.810547 1.828125 5.053711 1.828125 9.087891 c
1.828125 13.122070 5.053711 16.356445 9.079102 16.356445 c
13.113281 16.356445 16.356445 13.122070 16.365234 9.087891 c
16.374023 5.053711 13.122070 1.810547 9.087891 1.810547 c
h
9.079102 7.628906 m
9.562500 7.628906 9.843750 7.901367 9.852539 8.411133 c
9.984375 12.638672 l
10.001953 13.157227 9.615234 13.535156 9.070312 13.535156 c
8.525391 13.535156 8.147461 13.166016 8.165039 12.647461 c
8.288086 8.411133 l
8.305664 7.910156 8.586914 7.628906 9.079102 7.628906 c
h
9.079102 4.710938 m
9.650391 4.710938 10.116211 5.124023 10.116211 5.686523 c
10.116211 6.240234 9.659180 6.653320 9.079102 6.653320 c
8.507812 6.653320 8.041992 6.240234 8.041992 5.686523 c
8.041992 5.132812 8.516602 4.710938 9.079102 4.710938 c
h
f
n
Q
endstream
endobj
3 0 obj
1184
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 20.000000 20.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
<< /Type /Catalog
/Pages 5 0 R
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000001274 00000 n
0000001297 00000 n
0000001470 00000 n
0000001544 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
1603
%%EOF

View file

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

View file

@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "facepile.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "facepile@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "facepile@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

View file

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

View file

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

View file

@ -0,0 +1,522 @@
%PDF-1.7
1 0 obj
<< /BBox [ 0.000000 0.000000 110.000000 46.000000 ]
/Resources << >>
/Subtype /Form
/Length 2 0 R
/Group << /Type /Group
/S /Transparency
>>
/Type /XObject
>>
stream
/DeviceRGB CS
/DeviceRGB cs
q
q
1.000000 0.000000 -0.000000 1.000000 46.000000 13.067383 cm
0.450980 0.490196 0.549020 scn
8.832680 0.000023 m
3.859828 0.578173 0.000000 4.804639 0.000000 9.932617 c
0.000000 15.455465 4.477152 19.932617 10.000000 19.932617 c
15.152602 19.932617 19.395014 16.035631 19.940670 11.028222 c
19.906002 11.044100 19.870073 11.058149 19.832964 11.070212 c
16.719622 12.082235 13.238947 11.455502 10.848575 9.043321 c
8.464049 6.637041 7.840186 3.135887 8.832680 0.000023 c
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 46.000000 13.067383 cm
0.450980 0.490196 0.549020 scn
10.722207 0.654825 m
19.219536 9.152152 l
16.732798 9.960095 14.059622 9.433351 12.260846 7.618164 c
10.476121 5.817157 9.948320 3.147501 10.722207 0.654825 c
h
f
n
Q
54.832680 13.067406 m
49.859829 13.645554 46.000000 17.872021 46.000000 23.000000 c
46.000000 28.522848 50.477154 33.000000 56.000000 33.000000 c
61.152603 33.000000 65.395012 29.103014 65.940674 24.095604 c
65.906006 24.111483 65.870071 24.125532 65.832962 24.137596 c
62.719620 25.149618 59.238945 24.522884 56.848576 22.110703 c
54.464050 19.704424 53.840187 16.203270 54.832680 13.067406 c
h
W*
n
56.722206 13.722206 m
65.219536 22.219536 l
62.732796 23.027477 60.059624 22.500732 58.260845 20.685547 c
56.476120 18.884541 55.948318 16.214884 56.722206 13.722206 c
h
W*
n
q
1.000000 0.000000 -0.000000 1.000000 46.000000 13.067383 cm
0.450980 0.490196 0.549020 scn
8.832680 0.000023 m
8.717499 -0.990683 l
9.053570 -1.029755 9.386534 -0.895788 9.601898 -0.634851 c
9.817264 -0.373913 9.885659 -0.021587 9.783568 0.300978 c
8.832680 0.000023 l
h
19.940670 11.028222 m
19.525362 10.121425 l
19.851667 9.971979 20.233105 10.009373 20.524176 10.219343 c
20.815245 10.429314 20.971058 10.779479 20.932178 11.136267 c
19.940670 11.028222 l
h
19.832964 11.070212 m
20.141296 12.018734 l
20.141291 12.018736 l
19.832964 11.070212 l
h
10.848575 9.043321 m
11.557022 8.341278 l
11.557022 8.341278 l
10.848575 9.043321 l
h
10.722207 0.654825 m
9.769680 0.359100 l
9.873262 0.025461 10.143859 -0.229664 10.483012 -0.313446 c
10.822165 -0.397228 11.180433 -0.297455 11.427460 -0.050428 c
10.722207 0.654825 l
h
19.219536 9.152152 m
19.924788 8.446899 l
20.170168 8.692279 20.270367 9.047563 20.189354 9.384994 c
20.108341 9.722424 19.857763 9.993490 19.527727 10.100719 c
19.219536 9.152152 l
h
12.260846 7.618164 m
12.969294 6.916121 l
12.969294 6.916121 l
12.260846 7.618164 l
h
8.947861 0.990728 m
4.472036 1.511093 0.997378 5.316795 0.997378 9.932617 c
-0.997378 9.932617 l
-0.997378 4.292482 3.247620 -0.354748 8.717499 -0.990683 c
8.947861 0.990728 l
h
0.997378 9.932617 m
0.997378 14.904629 5.027989 18.935240 10.000000 18.935240 c
10.000000 20.929995 l
3.926316 20.929995 -0.997378 16.006302 -0.997378 9.932617 c
0.997378 9.932617 l
h
10.000000 18.935240 m
14.638034 18.935240 18.458044 15.427099 18.949162 10.920177 c
20.932178 11.136267 l
20.331984 16.644163 15.667171 20.929995 10.000000 20.929995 c
10.000000 18.935240 l
h
20.355978 11.935019 m
20.286608 11.966791 20.214968 11.994786 20.141296 12.018734 c
19.524632 10.121691 l
19.525362 10.121425 l
20.355978 11.935019 l
h
20.141291 12.018736 m
16.712317 13.133358 12.825830 12.455570 10.140127 9.745363 c
11.557022 8.341278 l
13.652063 10.455433 16.726927 11.031113 19.524637 10.121689 c
20.141291 12.018736 l
h
10.140127 9.745363 m
7.462934 7.043747 6.791740 3.143171 7.881791 -0.300932 c
9.783568 0.300978 l
8.888631 3.128603 9.465164 6.230335 11.557022 8.341278 c
10.140127 9.745363 l
h
11.427460 -0.050428 m
19.924788 8.446899 l
18.514284 9.857405 l
10.016954 1.360079 l
11.427460 -0.050428 l
h
19.527727 10.100719 m
16.723921 11.011679 13.645474 10.432379 11.552399 8.320207 c
12.969294 6.916121 l
14.473769 8.434322 16.741674 8.908512 18.911345 8.203585 c
19.527727 10.100719 l
h
11.552399 8.320207 m
9.477654 6.226535 8.899933 3.160538 9.769680 0.359100 c
11.674734 0.950550 l
10.996708 3.134464 11.474587 5.407779 12.969294 6.916121 c
11.552399 8.320207 l
h
f
n
Q
Q
endstream
endobj
2 0 obj
3998
endobj
3 0 obj
<< /BBox [ 0.000000 0.000000 110.000000 46.000000 ]
/Resources << >>
/Subtype /Form
/Length 4 0 R
/Group << /Type /Group
/S /Transparency
>>
/Type /XObject
>>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 39.257812 0.000000 cm
0.890196 0.909804 0.941176 scn
37.742256 23.000000 m
37.742256 10.297451 27.444805 0.000000 14.742256 0.000000 c
9.132510 0.000000 3.991834 2.008327 0.000000 5.345207 c
4.760142 9.730907 7.742256 16.017200 7.742256 23.000000 c
7.742256 29.982801 4.760141 36.269093 0.000000 40.654793 c
3.991834 43.991673 9.132510 46.000000 14.742256 46.000000 c
27.444805 46.000000 37.742256 35.702549 37.742256 23.000000 c
h
f*
n
Q
endstream
endobj
4 0 obj
506
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
1.000000 0.000000 -0.000000 1.000000 71.212158 0.000000 cm
0.890196 0.909804 0.941176 scn
-0.000005 6.274433 m
4.200984 10.596851 6.787903 16.496323 6.787903 23.000000 c
6.787903 29.503679 4.200984 35.403149 -0.000005 39.725567 c
4.119314 43.615345 9.675125 46.000000 15.787903 46.000000 c
28.490452 46.000000 38.787903 35.702549 38.787903 23.000000 c
38.787903 10.297451 28.490452 0.000000 15.787903 0.000000 c
9.675125 0.000000 4.119314 2.384655 -0.000005 6.274433 c
h
f*
n
Q
q
1.000000 0.000000 -0.000000 1.000000 39.257812 0.000000 cm
0.890196 0.909804 0.941176 scn
37.742264 23.000000 m
37.742264 10.297451 27.444813 0.000000 14.742264 0.000000 c
9.132518 0.000000 3.991842 2.008327 0.000008 5.345207 c
4.760150 9.730907 7.742264 16.017200 7.742264 23.000000 c
7.742264 29.982801 4.760149 36.269093 0.000008 40.654793 c
3.991842 43.991673 9.132518 46.000000 14.742264 46.000000 c
27.444813 46.000000 37.742264 35.702549 37.742264 23.000000 c
h
f*
n
Q
q
/E1 gs
/X1 Do
Q
q
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
0.890196 0.909804 0.941176 scn
46.000000 23.000000 m
46.000000 10.297451 35.702549 0.000000 23.000000 0.000000 c
10.297451 0.000000 0.000000 10.297451 0.000000 23.000000 c
0.000000 35.702549 10.297451 46.000000 23.000000 46.000000 c
35.702549 46.000000 46.000000 35.702549 46.000000 23.000000 c
h
f*
n
Q
q
q
1.000000 -0.000000 -0.000000 1.000000 79.000000 12.999998 cm
0.450980 0.490196 0.549020 scn
0.000000 16.000000 m
0.000000 18.209139 1.790861 20.000000 4.000000 20.000000 c
15.999997 20.000000 l
18.209137 20.000000 19.999998 18.209139 19.999998 16.000000 c
19.999998 4.000003 l
19.999998 1.790863 18.209137 0.000002 15.999998 0.000002 c
4.000000 0.000002 l
1.790862 0.000002 0.000000 1.790863 0.000000 4.000002 c
0.000000 16.000000 l
h
f
n
Q
79.000000 29.000000 m
79.000000 31.209137 80.790863 33.000000 83.000000 33.000000 c
95.000000 33.000000 l
97.209137 33.000000 99.000000 31.209139 99.000000 29.000000 c
99.000000 17.000000 l
99.000000 14.790861 97.209137 13.000000 95.000000 13.000000 c
83.000000 13.000000 l
80.790863 13.000000 79.000000 14.790861 79.000000 17.000000 c
79.000000 29.000000 l
h
W*
n
q
1.000000 -0.000000 -0.000000 1.000000 79.000000 12.999998 cm
0.450980 0.490196 0.549020 scn
4.000000 18.000000 m
15.999997 18.000000 l
15.999997 22.000000 l
4.000000 22.000000 l
4.000000 18.000000 l
h
17.999998 16.000000 m
17.999998 4.000003 l
21.999998 4.000003 l
21.999998 16.000000 l
17.999998 16.000000 l
h
15.999998 2.000002 m
4.000000 2.000002 l
4.000000 -1.999998 l
15.999998 -1.999998 l
15.999998 2.000002 l
h
2.000000 4.000002 m
2.000000 16.000000 l
-2.000000 16.000000 l
-2.000000 4.000002 l
2.000000 4.000002 l
h
4.000000 2.000002 m
2.895431 2.000002 2.000000 2.895433 2.000000 4.000002 c
-2.000000 4.000002 l
-2.000000 0.686293 0.686293 -1.999998 4.000000 -1.999998 c
4.000000 2.000002 l
h
17.999998 4.000003 m
17.999998 2.895433 17.104567 2.000002 15.999998 2.000002 c
15.999998 -1.999998 l
19.313707 -1.999998 21.999998 0.686295 21.999998 4.000003 c
17.999998 4.000003 l
h
15.999997 18.000000 m
17.104567 18.000000 17.999998 17.104568 17.999998 16.000000 c
21.999998 16.000000 l
21.999998 19.313709 19.313705 22.000000 15.999997 22.000000 c
15.999997 18.000000 l
h
4.000000 22.000000 m
0.686291 22.000000 -2.000000 19.313707 -2.000000 16.000000 c
2.000000 16.000000 l
2.000000 17.104568 2.895431 18.000000 4.000000 18.000000 c
4.000000 22.000000 l
h
f
n
Q
Q
q
1.000000 -0.000000 -0.000000 1.000000 81.000000 14.999998 cm
1.000000 1.000000 1.000000 scn
16.000000 8.000000 m
16.000000 3.581722 12.418278 0.000000 8.000000 0.000000 c
3.581722 0.000000 0.000000 3.581722 0.000000 8.000000 c
0.000000 12.418278 3.581722 16.000000 8.000000 16.000000 c
12.418278 16.000000 16.000000 12.418278 16.000000 8.000000 c
h
f
n
Q
q
1.000000 -0.000000 -0.000000 1.000000 89.000000 19.896606 cm
0.450980 0.490196 0.549020 scn
0.750000 7.103394 m
0.750000 7.517607 0.414214 7.853394 0.000000 7.853394 c
-0.414214 7.853394 -0.750000 7.517607 -0.750000 7.103394 c
0.750000 7.103394 l
h
0.000000 3.290894 m
-0.750000 3.290894 l
-0.750000 3.041655 -0.626185 2.808699 -0.419605 2.669257 c
0.000000 3.290894 l
h
2.080395 0.981757 m
2.423716 0.750016 2.889895 0.840468 3.121636 1.183789 c
3.353378 1.527109 3.262925 1.993289 2.919605 2.225030 c
2.080395 0.981757 l
h
-0.750000 7.103394 m
-0.750000 3.290894 l
0.750000 3.290894 l
0.750000 7.103394 l
-0.750000 7.103394 l
h
-0.419605 2.669257 m
2.080395 0.981757 l
2.919605 2.225030 l
0.419605 3.912530 l
-0.419605 2.669257 l
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 14.000000 15.000000 cm
0.450980 0.490196 0.549020 scn
0.000000 14.727272 m
0.000000 16.534750 1.465250 18.000000 3.272727 18.000000 c
14.727273 18.000000 l
16.534750 18.000000 18.000000 16.534750 18.000000 14.727272 c
18.000000 3.272727 l
18.000000 1.465250 16.534750 0.000000 14.727273 0.000000 c
3.272727 0.000000 l
1.465250 0.000000 0.000000 1.465250 0.000000 3.272727 c
0.000000 14.727272 l
h
8.181818 12.681818 m
8.181818 11.100275 6.899724 9.818182 5.318182 9.818182 c
3.736639 9.818182 2.454545 11.100275 2.454545 12.681818 c
2.454545 14.263361 3.736639 15.545454 5.318182 15.545454 c
6.899724 15.545454 8.181818 14.263361 8.181818 12.681818 c
h
5.318182 2.454546 m
6.899724 2.454546 8.181818 3.736639 8.181818 5.318182 c
8.181818 6.899724 6.899724 8.181818 5.318182 8.181818 c
3.736639 8.181818 2.454545 6.899724 2.454545 5.318182 c
2.454545 3.736639 3.736639 2.454546 5.318182 2.454546 c
h
15.545454 5.318182 m
15.545454 3.736639 14.263361 2.454546 12.681818 2.454546 c
11.100276 2.454546 9.818182 3.736639 9.818182 5.318182 c
9.818182 6.899724 11.100276 8.181818 12.681818 8.181818 c
14.263361 8.181818 15.545454 6.899724 15.545454 5.318182 c
h
12.681818 9.818182 m
14.263361 9.818182 15.545454 11.100275 15.545454 12.681818 c
15.545454 14.263361 14.263361 15.545454 12.681818 15.545454 c
11.100276 15.545454 9.818182 14.263361 9.818182 12.681818 c
9.818182 11.100275 11.100276 9.818182 12.681818 9.818182 c
h
f*
n
Q
q
1.000000 0.000000 -0.000000 1.000000 14.000000 15.000000 cm
0.450980 0.490196 0.549020 scn
0.000000 14.727272 m
0.000000 16.534750 1.465250 18.000000 3.272727 18.000000 c
14.727273 18.000000 l
16.534750 18.000000 18.000000 16.534750 18.000000 14.727272 c
18.000000 3.272727 l
18.000000 1.465250 16.534750 0.000000 14.727273 0.000000 c
3.272727 0.000000 l
1.465250 0.000000 0.000000 1.465250 0.000000 3.272727 c
0.000000 14.727272 l
h
8.181818 12.681818 m
8.181818 11.100275 6.899724 9.818182 5.318182 9.818182 c
3.736639 9.818182 2.454545 11.100275 2.454545 12.681818 c
2.454545 14.263361 3.736639 15.545454 5.318182 15.545454 c
6.899724 15.545454 8.181818 14.263361 8.181818 12.681818 c
h
5.318182 2.454546 m
6.899724 2.454546 8.181818 3.736639 8.181818 5.318182 c
8.181818 6.899724 6.899724 8.181818 5.318182 8.181818 c
3.736639 8.181818 2.454545 6.899724 2.454545 5.318182 c
2.454545 3.736639 3.736639 2.454546 5.318182 2.454546 c
h
15.545454 5.318182 m
15.545454 3.736639 14.263361 2.454546 12.681818 2.454546 c
11.100276 2.454546 9.818182 3.736639 9.818182 5.318182 c
9.818182 6.899724 11.100276 8.181818 12.681818 8.181818 c
14.263361 8.181818 15.545454 6.899724 15.545454 5.318182 c
h
12.681818 9.818182 m
14.263361 9.818182 15.545454 11.100275 15.545454 12.681818 c
15.545454 14.263361 14.263361 15.545454 12.681818 15.545454 c
11.100276 15.545454 9.818182 14.263361 9.818182 12.681818 c
9.818182 11.100275 11.100276 9.818182 12.681818 9.818182 c
h
f*
n
Q
endstream
endobj
7 0 obj
7483
endobj
8 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 110.000000 46.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
<< /Type /Catalog
/Pages 9 0 R
>>
endobj
xref
0 11
0000000000 65535 f
0000000010 00000 n
0000004257 00000 n
0000004280 00000 n
0000005035 00000 n
0000005057 00000 n
0000005355 00000 n
0000012894 00000 n
0000012917 00000 n
0000013091 00000 n
0000013165 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 10 0 R
/Size 11
>>
startxref
13225
%%EOF

View file

@ -18,6 +18,6 @@
"NSCameraUsageDescription" = "The camera is used to take photos and videos, make video calls.";
"NSPhotoLibraryUsageDescription" = "The photo library is used to send photos and videos.";
"NSMicrophoneUsageDescription" = "Element needs to access your microphone to make and receive calls, take videos, and record voice messages.";
"NSContactsUsageDescription" = "To discover contacts already using Matrix, Element can send email addresses and phone numbers in your address book to your chosen Matrix identity server. Where supported, personal data is hashed before sending - please check your identity server's privacy policy for more details.";
"NSContactsUsageDescription" = "Element will show your contacts so you can invite them to chat.";
"NSCalendarsUsageDescription" = "See your scheduled meetings in the app.";
"NSFaceIDUsageDescription" = "Face ID is used to access your app.";

View file

@ -178,7 +178,7 @@
"room_creation_keep_private" = "Keep private";
"room_creation_make_private" = "Make private";
"room_creation_wait_for_creation" = "A room is already being created. Please wait.";
"room_creation_invite_another_user" = "Search / invite by User ID, Name or email";
"room_creation_invite_another_user" = "User ID, name or email";
"room_creation_error_invite_user_by_email_without_identity_server" = "No identity server is configured so you cannot add a participant with an email.";
"room_creation_dm_error" = "We couldn't create your DM. Please check the users you want to invite and try again.";
@ -244,8 +244,15 @@ Tap the + to start adding people.";
"contacts_address_book_no_contact" = "No local contacts";
"contacts_address_book_permission_required" = "Permission required to access local contacts";
"contacts_address_book_permission_denied" = "You didn't allow %@ to access your local contacts";
"contacts_address_book_permission_denied_alert_title" = "Contacts disabled";
"contacts_address_book_permission_denied_alert_message" = "To enable contacts, go to your device settings.";
"contacts_user_directory_section" = "USER DIRECTORY";
"contacts_user_directory_offline_section" = "USER DIRECTORY (offline)";
"find_your_contacts_title" = "Start by listing your contacts";
"find_your_contacts_message" = "Let %@ show your contacts so you can quickly start chatting with those you know best.";
"find_your_contacts_button_title" = "Find your contacts";
"find_your_contacts_footer" = "This can be disabled anytime from settings.";
"find_your_contacts_identity_service_error" = "Unable to connect to the identity server.";
// Chat participants
"room_participants_title" = "Participants";
@ -462,7 +469,8 @@ Tap the + to start adding people.";
"settings_integrations" = "INTEGRATIONS";
"settings_user_interface" = "USER INTERFACE";
"settings_ignored_users" = "IGNORED USERS";
"settings_contacts" = "LOCAL CONTACTS";
"settings_contacts" = "DEVICE CONTACTS";
"settings_phone_contacts" = "PHONE CONTACTS";
"settings_advanced" = "ADVANCED";
"settings_other" = "OTHER";
"settings_labs" = "LABS";
@ -550,8 +558,9 @@ Tap the + to start adding people.";
"settings_unignore_user" = "Show all messages from %@?";
"settings_contacts_discover_matrix_users" = "Use emails and phone numbers to discover users";
"settings_contacts_enable_sync" = "Find your contacts";
"settings_contacts_phonebook_country" = "Phonebook country";
"settings_contacts_enable_sync_description" = "This will use your identity server to connect you with your contacts, and help them find you.";
"settings_labs_e2e_encryption" = "End-to-End Encryption";
"settings_labs_e2e_encryption_prompt_message" = "To finish setting up encryption you must log in again.";
@ -1020,18 +1029,21 @@ Tap the + to start adding people.";
"gdpr_consent_not_given_alert_review_now_action" = "Review now";
// Service terms
"service_terms_modal_title" = "Terms Of Service";
"service_terms_modal_message" = "To continue you need to accept the terms of this service (%@).";
"service_terms_modal_title_message" = "To continue, accept the below terms and conditions";
"service_terms_modal_accept_button" = "Accept";
"service_terms_modal_decline_button" = "Decline";
"service_terms_modal_footer" = "This can be disabled anytime in settings.";
"service_terms_modal_description_for_identity_server_1" = "Find others by phone or email";
"service_terms_modal_description_for_identity_server_2" = "Be found by phone or email";
"service_terms_modal_description_for_integration_manager" = "Use Bots, bridges, widgets and sticker packs";
"service_terms_modal_table_header_identity_server" = "IDENTITY SERVER TERMS";
"service_terms_modal_table_header_integration_manager" = "INTEGRATION MANAGER TERMS";
"service_terms_modal_description_identity_server" = "This will allow someone to find you if they have your phone number or email saved in their phone contacts.";
"service_terms_modal_description_integration_manager" = "This will allow you to use bots, bridges, widgets and sticker packs.";
// Service terms - Variant for identity server when displayed out of a context
"service_terms_modal_title_identity_server" = "Contact discovery";
"service_terms_modal_message_identity_server" = "Accept the terms of the identity server (%@) to discover contacts.";
// Alert explaining what an identity server / integration manager is.
"service_terms_modal_information_title_identity_server" = "Identity Server";
"service_terms_modal_information_title_integration_manager" = "Integration Manager";
"service_terms_modal_information_description_identity_server" = "An identity server helps you find your contacts, by looking up their phone number or email address, to see if they already have an account.";
"service_terms_modal_information_description_integration_manager" = "An integration manager lets you add features from third parties.";
"service_terms_modal_policy_checkbox_accessibility_hint" = "Check to accept %@";

View file

@ -1885,6 +1885,32 @@ permanent authorization for you to choose that version for the
Library.
</pre>
</li>
<li>
<b>WeakDictionary</b> (<a href="https://github.com/nicholascross/WeakDictionary">https://github.com/nicholascross/WeakDictionary/</a>)
<br/><br/>
MIT License
<br/><br/>
Copyright (c) 2016 Nicholas Cross
<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

@ -0,0 +1,29 @@
//
// 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 Array where Element: Equatable {
/// Remove first collection element that is equal to the given `object`
/// Credits: https://stackoverflow.com/a/45008042
mutating func vc_removeFirstOccurrence(of object: Element) {
guard let index = firstIndex(of: object) else {
return
}
remove(at: index)
}
}

View file

@ -17,9 +17,9 @@
import Foundation
extension MXKImageView {
@objc func vc_setRoomAvatarImage(with url: String?, displayName: String, mediaManager: MXMediaManager) {
@objc func vc_setRoomAvatarImage(with url: String?, roomId: String, displayName: String, mediaManager: MXMediaManager) {
// Use the display name to prepare the default avatar image.
let avatarImage = AvatarGenerator.generateAvatar(forText: displayName)
let avatarImage = AvatarGenerator.generateAvatar(forMatrixItem: roomId, withDisplayName: displayName)
if let avatarUrl = url {
self.enableInMemoryCache = true

View file

@ -19,32 +19,20 @@
#import "AvatarGenerator.h"
#ifdef IS_SHARE_EXTENSION
#import "RiotShareExtension-Swift.h"
#else
#import "Riot-Swift.h"
#endif
@implementation MXRoomSummary (Riot)
- (void)setRoomAvatarImageIn:(MXKImageView*)mxkImageView
{
// Use the room display name to prepare the default avatar image.
NSString *avatarDisplayName = self.displayname;
UIImage* avatarImage = [AvatarGenerator generateAvatarForMatrixItem:self.roomId withDisplayName:avatarDisplayName];
if (self.avatar)
{
mxkImageView.enableInMemoryCache = YES;
[mxkImageView setImageURI:self.avatar
withType:nil
andImageOrientation:UIImageOrientationUp
toFitViewSize:mxkImageView.frame.size
withMethod:MXThumbnailingMethodCrop
previewImage:avatarImage
mediaManager:self.mxSession.mediaManager];
}
else
{
mxkImageView.image = avatarImage;
}
mxkImageView.contentMode = UIViewContentModeScaleAspectFill;
[mxkImageView vc_setRoomAvatarImageWith:self.avatar
roomId:self.roomId
displayName:self.displayname
mediaManager:self.mxSession.mediaManager];
}
- (RoomEncryptionTrustLevel)roomEncryptionTrustLevel

View file

@ -103,4 +103,16 @@ extension UIViewController {
return fabImageView
}
/// Set leftBarButtonItem with split view display mode button if there is no leftBarButtonItem defined and splitViewController exists.
/// To be Used when view controller is displayed as detail controller in split view.
func vc_setupDisplayModeLeftBarButtonItemIfNeeded() {
guard let splitViewController = self.splitViewController, self.navigationItem.leftBarButtonItem == nil else {
return
}
// If there is no leftBarButtonItem defined,
// set split view display mode button as left bar button item
self.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem
}
}

View file

@ -60,6 +60,7 @@ internal enum Asset {
internal static let errorIcon = ImageAsset(name: "error_icon")
internal static let faceidIcon = ImageAsset(name: "faceid_icon")
internal static let group = ImageAsset(name: "group")
internal static let informationButton = ImageAsset(name: "information_button")
internal static let monitor = ImageAsset(name: "monitor")
internal static let placeholder = ImageAsset(name: "placeholder")
internal static let plusIcon = ImageAsset(name: "plus_icon")
@ -74,6 +75,7 @@ internal enum Asset {
internal static let touchidIcon = ImageAsset(name: "touchid_icon")
internal static let addGroupParticipant = ImageAsset(name: "add_group_participant")
internal static let removeIconBlue = ImageAsset(name: "remove_icon_blue")
internal static let findYourContactsFacepile = ImageAsset(name: "find_your_contacts_facepile")
internal static let captureAvatar = ImageAsset(name: "capture_avatar")
internal static let e2eBlocked = ImageAsset(name: "e2e_blocked")
internal static let e2eUnencrypted = ImageAsset(name: "e2e_unencrypted")
@ -95,6 +97,7 @@ internal enum Asset {
internal static let plusFloatingAction = ImageAsset(name: "plus_floating_action")
internal static let versionCheckCloseIcon = ImageAsset(name: "version_check_close_icon")
internal static let versionCheckInfoIcon = ImageAsset(name: "version_check_info_icon")
internal static let integrationManagerIconpile = ImageAsset(name: "integration_manager_iconpile")
internal static let closeBanner = ImageAsset(name: "close_banner")
internal static let importFilesButton = ImageAsset(name: "import_files_button")
internal static let keyBackupLogo = ImageAsset(name: "key_backup_logo")

View file

@ -559,6 +559,14 @@ public class VectorL10n: NSObject {
public static func contactsAddressBookPermissionDenied(_ p1: String) -> String {
return VectorL10n.tr("Vector", "contacts_address_book_permission_denied", p1)
}
/// To enable contacts, go to your device settings.
public static var contactsAddressBookPermissionDeniedAlertMessage: String {
return VectorL10n.tr("Vector", "contacts_address_book_permission_denied_alert_message")
}
/// Contacts disabled
public static var contactsAddressBookPermissionDeniedAlertTitle: String {
return VectorL10n.tr("Vector", "contacts_address_book_permission_denied_alert_title")
}
/// Permission required to access local contacts
public static var contactsAddressBookPermissionRequired: String {
return VectorL10n.tr("Vector", "contacts_address_book_permission_required")
@ -1403,6 +1411,26 @@ public class VectorL10n: NSObject {
public static var fileUploadErrorUnsupportedFileTypeMessage: String {
return VectorL10n.tr("Vector", "file_upload_error_unsupported_file_type_message")
}
/// Find your contacts
public static var findYourContactsButtonTitle: String {
return VectorL10n.tr("Vector", "find_your_contacts_button_title")
}
/// This can be disabled anytime from settings.
public static var findYourContactsFooter: String {
return VectorL10n.tr("Vector", "find_your_contacts_footer")
}
/// Unable to connect to the identity server.
public static var findYourContactsIdentityServiceError: String {
return VectorL10n.tr("Vector", "find_your_contacts_identity_service_error")
}
/// Let %@ show your contacts so you can quickly start chatting with those you know best.
public static func findYourContactsMessage(_ p1: String) -> String {
return VectorL10n.tr("Vector", "find_your_contacts_message", p1)
}
/// Start by listing your contacts
public static var findYourContactsTitle: String {
return VectorL10n.tr("Vector", "find_your_contacts_title")
}
/// To continue using the %@ homeserver you must review and agree to the terms and conditions.
public static func gdprConsentNotGivenAlertMessage(_ p1: String) -> String {
return VectorL10n.tr("Vector", "gdpr_consent_not_given_alert_message", p1)
@ -2463,7 +2491,7 @@ public class VectorL10n: NSObject {
public static var roomCreationErrorInviteUserByEmailWithoutIdentityServer: String {
return VectorL10n.tr("Vector", "room_creation_error_invite_user_by_email_without_identity_server")
}
/// Search / invite by User ID, Name or email
/// User ID, name or email
public static var roomCreationInviteAnotherUser: String {
return VectorL10n.tr("Vector", "room_creation_invite_another_user")
}
@ -4011,37 +4039,49 @@ public class VectorL10n: NSObject {
public static var serviceTermsModalDeclineButton: String {
return VectorL10n.tr("Vector", "service_terms_modal_decline_button")
}
/// Find others by phone or email
public static var serviceTermsModalDescriptionForIdentityServer1: String {
return VectorL10n.tr("Vector", "service_terms_modal_description_for_identity_server_1")
/// This will allow someone to find you if they have your phone number or email saved in their phone contacts.
public static var serviceTermsModalDescriptionIdentityServer: String {
return VectorL10n.tr("Vector", "service_terms_modal_description_identity_server")
}
/// Be found by phone or email
public static var serviceTermsModalDescriptionForIdentityServer2: String {
return VectorL10n.tr("Vector", "service_terms_modal_description_for_identity_server_2")
/// This will allow you to use bots, bridges, widgets and sticker packs.
public static var serviceTermsModalDescriptionIntegrationManager: String {
return VectorL10n.tr("Vector", "service_terms_modal_description_integration_manager")
}
/// Use Bots, bridges, widgets and sticker packs
public static var serviceTermsModalDescriptionForIntegrationManager: String {
return VectorL10n.tr("Vector", "service_terms_modal_description_for_integration_manager")
/// This can be disabled anytime in settings.
public static var serviceTermsModalFooter: String {
return VectorL10n.tr("Vector", "service_terms_modal_footer")
}
/// To continue you need to accept the terms of this service (%@).
public static func serviceTermsModalMessage(_ p1: String) -> String {
return VectorL10n.tr("Vector", "service_terms_modal_message", p1)
/// An identity server helps you find your contacts, by looking up their phone number or email address, to see if they already have an account.
public static var serviceTermsModalInformationDescriptionIdentityServer: String {
return VectorL10n.tr("Vector", "service_terms_modal_information_description_identity_server")
}
/// Accept the terms of the identity server (%@) to discover contacts.
public static func serviceTermsModalMessageIdentityServer(_ p1: String) -> String {
return VectorL10n.tr("Vector", "service_terms_modal_message_identity_server", p1)
/// An integration manager lets you add features from third parties.
public static var serviceTermsModalInformationDescriptionIntegrationManager: String {
return VectorL10n.tr("Vector", "service_terms_modal_information_description_integration_manager")
}
/// Identity Server
public static var serviceTermsModalInformationTitleIdentityServer: String {
return VectorL10n.tr("Vector", "service_terms_modal_information_title_identity_server")
}
/// Integration Manager
public static var serviceTermsModalInformationTitleIntegrationManager: String {
return VectorL10n.tr("Vector", "service_terms_modal_information_title_integration_manager")
}
/// Check to accept %@
public static func serviceTermsModalPolicyCheckboxAccessibilityHint(_ p1: String) -> String {
return VectorL10n.tr("Vector", "service_terms_modal_policy_checkbox_accessibility_hint", p1)
}
/// Terms Of Service
public static var serviceTermsModalTitle: String {
return VectorL10n.tr("Vector", "service_terms_modal_title")
/// IDENTITY SERVER TERMS
public static var serviceTermsModalTableHeaderIdentityServer: String {
return VectorL10n.tr("Vector", "service_terms_modal_table_header_identity_server")
}
/// Contact discovery
public static var serviceTermsModalTitleIdentityServer: String {
return VectorL10n.tr("Vector", "service_terms_modal_title_identity_server")
/// INTEGRATION MANAGER TERMS
public static var serviceTermsModalTableHeaderIntegrationManager: String {
return VectorL10n.tr("Vector", "service_terms_modal_table_header_integration_manager")
}
/// To continue, accept the below terms and conditions
public static var serviceTermsModalTitleMessage: String {
return VectorL10n.tr("Vector", "service_terms_modal_title_message")
}
/// Invalid credentials
public static var settingsAdd3pidInvalidPasswordMessage: String {
@ -4127,13 +4167,17 @@ public class VectorL10n: NSObject {
public static var settingsConfirmPassword: String {
return VectorL10n.tr("Vector", "settings_confirm_password")
}
/// LOCAL CONTACTS
/// DEVICE CONTACTS
public static var settingsContacts: String {
return VectorL10n.tr("Vector", "settings_contacts")
}
/// Use emails and phone numbers to discover users
public static var settingsContactsDiscoverMatrixUsers: String {
return VectorL10n.tr("Vector", "settings_contacts_discover_matrix_users")
/// Find your contacts
public static var settingsContactsEnableSync: String {
return VectorL10n.tr("Vector", "settings_contacts_enable_sync")
}
/// This will use your identity server to connect you with your contacts, and help them find you.
public static var settingsContactsEnableSyncDescription: String {
return VectorL10n.tr("Vector", "settings_contacts_enable_sync_description")
}
/// Phonebook country
public static var settingsContactsPhonebookCountry: String {
@ -4543,6 +4587,10 @@ public class VectorL10n: NSObject {
public static var settingsPasswordUpdated: String {
return VectorL10n.tr("Vector", "settings_password_updated")
}
/// PHONE CONTACTS
public static var settingsPhoneContacts: String {
return VectorL10n.tr("Vector", "settings_phone_contacts")
}
/// Phone
public static var settingsPhoneNumber: String {
return VectorL10n.tr("Vector", "settings_phone_number")

View file

@ -41,6 +41,7 @@ import DesignKit
var textPrimaryColor: UIColor { get }
var textSecondaryColor: UIColor { get }
var textTertiaryColor: UIColor { get }
var textQuinaryColor: UIColor { get }
var tintColor: UIColor { get }
var tintBackgroundColor: UIColor { get }

View file

@ -42,6 +42,7 @@ class DarkTheme: NSObject, Theme {
var textPrimaryColor: UIColor = UIColor(rgb: 0xFFFFFF)
var textSecondaryColor: UIColor = UIColor(rgb: 0xA9B2BC)
var textTertiaryColor: UIColor = UIColor(rgb: 0x8E99A4)
var textQuinaryColor: UIColor = UIColor(rgb: 0x394049)
var tintColor: UIColor = UIColor(displayP3Red: 0.05098039216, green: 0.7450980392, blue: 0.5450980392, alpha: 1.0)
var tintBackgroundColor: UIColor = UIColor(rgb: 0x1F6954)

View file

@ -42,6 +42,7 @@ class DefaultTheme: NSObject, Theme {
var textPrimaryColor: UIColor = UIColor(rgb: 0x17191C)
var textSecondaryColor: UIColor = UIColor(rgb: 0x737D8C)
var textTertiaryColor: UIColor = UIColor(rgb: 0x8D99A5)
var textQuinaryColor: UIColor = UIColor(rgb: 0xE3E8F0)
var tintColor: UIColor = UIColor(displayP3Red: 0.05098039216, green: 0.7450980392, blue: 0.5450980392, alpha: 1.0)
var tintBackgroundColor: UIColor = UIColor(rgb: 0xe9fff9)

View file

@ -80,6 +80,9 @@ final class AppCoordinator: NSObject, AppCoordinatorType {
self.setupLogger()
self.setupTheme()
// Setup navigation router store
_ = NavigationRouterStore.shared
if BuildSettings.enableSideMenu {
self.addSideMenu()
}

View file

@ -87,7 +87,7 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe
NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUniversalLinkDidChangeNotification";
@interface LegacyAppDelegate () <GDPRConsentViewControllerDelegate, KeyVerificationCoordinatorBridgePresenterDelegate, ServiceTermsModalCoordinatorBridgePresenterDelegate, PushNotificationServiceDelegate, SetPinCoordinatorBridgePresenterDelegate, CallPresenterDelegate, SpaceDetailPresenterDelegate>
@interface LegacyAppDelegate () <GDPRConsentViewControllerDelegate, KeyVerificationCoordinatorBridgePresenterDelegate, PushNotificationServiceDelegate, SetPinCoordinatorBridgePresenterDelegate, CallPresenterDelegate, SpaceDetailPresenterDelegate>
{
/**
Reachability observer
@ -201,7 +201,6 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
@property (weak, nonatomic) UIAlertController *incomingKeyVerificationRequestAlertController;
@property (nonatomic, strong) ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter;
@property (nonatomic, strong) SlidingModalPresenter *slidingModalPresenter;
@property (nonatomic, strong) SetPinCoordinatorBridgePresenter *setPinCoordinatorBridgePresenter;
@property (nonatomic, strong) SpaceDetailPresenter *spaceDetailPresenter;
@ -674,9 +673,6 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
// Register to GDPR consent not given notification
[self registerUserConsentNotGivenNotification];
// Register to identity server terms not signed notification
[self registerIdentityServiceTermsNotSignedNotification];
// Start monitoring reachability
[[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
@ -1847,6 +1843,13 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
[self checkLocalPrivateKeysInSession:mxSession];
[self.pushNotificationService checkPushKitPushersInSession:mxSession];
// Validate the availability of local contact sync for any changes to the
// authorization of contacts access that may have occurred since the last launch.
if (BuildSettings.allowLocalContactsAccess)
{
[MXKContactManager.sharedManager validateSyncLocalContactsState];
}
}
else if (mxSession.state == MXSessionStateClosed)
{
@ -4131,82 +4134,6 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
}
}
#pragma mark - Identity server service terms
// Observe identity server terms not signed notification
- (void)registerIdentityServiceTermsNotSignedNotification
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleIdentityServiceTermsNotSignedNotification:) name:MXIdentityServiceTermsNotSignedNotification object:nil];
}
- (void)handleIdentityServiceTermsNotSignedNotification:(NSNotification*)notification
{
MXLogDebug(@"[AppDelegate] IS Terms: handleIdentityServiceTermsNotSignedNotification.");
NSString *baseURL;
NSString *accessToken;
MXJSONModelSetString(baseURL, notification.userInfo[MXIdentityServiceNotificationIdentityServerKey]);
MXJSONModelSetString(accessToken, notification.userInfo[MXIdentityServiceNotificationAccessTokenKey]);
[self presentIdentityServerTermsWithBaseURL:baseURL andAccessToken:accessToken];
}
- (void)presentIdentityServerTermsWithBaseURL:(NSString*)baseURL andAccessToken:(NSString*)accessToken
{
MXSession *mxSession = self.mxSessions.firstObject;
if (!mxSession || !baseURL || !accessToken || self.serviceTermsModalCoordinatorBridgePresenter.isPresenting)
{
return;
}
ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter = [[ServiceTermsModalCoordinatorBridgePresenter alloc] initWithSession:mxSession
baseUrl:baseURL
serviceType:MXServiceTypeIdentityService
outOfContext:YES
accessToken:accessToken];
serviceTermsModalCoordinatorBridgePresenter.delegate = self;
[serviceTermsModalCoordinatorBridgePresenter presentFrom:self.presentedViewController animated:YES];
self.serviceTermsModalCoordinatorBridgePresenter = serviceTermsModalCoordinatorBridgePresenter;
}
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidAccept:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter
{
[coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
}];
self.serviceTermsModalCoordinatorBridgePresenter = nil;
}
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidDecline:(ServiceTermsModalCoordinatorBridgePresenter *)coordinatorBridgePresenter session:(MXSession *)session
{
MXLogDebug(@"[AppDelegate] IS Terms: User has declined the use of the default IS.");
// The user does not want to use the proposed IS.
// Disable IS feature on user's account
[session setIdentityServer:nil andAccessToken:nil];
[session setAccountDataIdentityServer:nil success:^{
} failure:^(NSError *error) {
MXLogDebug(@"[AppDelegate] IS Terms: Error: %@", error);
}];
[coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
}];
self.serviceTermsModalCoordinatorBridgePresenter = nil;
}
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidCancel:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter
{
[coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
}];
self.serviceTermsModalCoordinatorBridgePresenter = nil;
}
#pragma mark - Settings
- (void)setupUserDefaults

View file

@ -417,7 +417,7 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro
// Update here the index of the current selected cell (if any) - Useful in landscape mode with split view controller.
NSIndexPath *currentSelectedCellIndexPath = nil;
MasterTabBarController *masterTabBarController = [AppDelegate theDelegate].masterTabBarController;
if (masterTabBarController.currentRoomViewController)
if (masterTabBarController.selectedRoomId)
{
// Look for the rank of this selected room in displayed recents
currentSelectedCellIndexPath = [self.dataSource cellIndexPathWithRoomId:masterTabBarController.selectedRoomId andMatrixSession:masterTabBarController.selectedRoomSession];

View file

@ -125,6 +125,7 @@
}
[self.roomAvatar vc_setRoomAvatarImageWith:roomCellData.avatarUrl
roomId:roomCellData.roomIdentifier
displayName:roomCellData.roomDisplayname
mediaManager:roomCellData.mxSession.mediaManager];
}

View file

@ -323,7 +323,7 @@
// Update here the index of the current selected cell (if any) - Useful in landscape mode with split view controller.
NSIndexPath *currentSelectedCellIndexPath = nil;
MasterTabBarController *masterTabBarController = [AppDelegate theDelegate].masterTabBarController;
if (masterTabBarController.currentGroupDetailViewController)
if (masterTabBarController.selectedGroup)
{
// Look for the rank of this selected group in displayed groups
currentSelectedCellIndexPath = [self.dataSource cellIndexPathWithGroupId:masterTabBarController.selectedGroup.groupId];

View file

@ -807,7 +807,7 @@
contact = [[MXKContact alloc] initMatrixContactWithDisplayName:userId andMatrixID:userId];
}
ContactDetailsViewController *contactDetailsViewController = [ContactDetailsViewController contactDetailsViewController];
ContactDetailsViewController *contactDetailsViewController = [ContactDetailsViewController instantiate];
contactDetailsViewController.enableVoipCall = NO;
contactDetailsViewController.contact = contact;

View file

@ -964,7 +964,7 @@
if (contact)
{
ContactDetailsViewController *contactDetailsViewController = [ContactDetailsViewController contactDetailsViewController];
ContactDetailsViewController *contactDetailsViewController = [ContactDetailsViewController instantiate];
contactDetailsViewController.enableVoipCall = NO;
contactDetailsViewController.contact = contact;

View file

@ -0,0 +1,55 @@
// File created from ScreenTemplate
// $ createScreen.sh Communities GroupDetails
/*
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 UIKit
final class GroupDetailsCoordinator: GroupDetailsCoordinatorProtocol {
// MARK: - Properties
// MARK: Private
private let parameters: GroupDetailsCoordinatorParameters
private let groupDetailsViewController: GroupDetailsViewController
// MARK: Public
// Must be used only internally
var childCoordinators: [Coordinator] = []
weak var delegate: GroupDetailsCoordinatorDelegate?
// MARK: - Setup
init(parameters: GroupDetailsCoordinatorParameters) {
self.parameters = parameters
let groupDetailsViewController: GroupDetailsViewController = GroupDetailsViewController.instantiate()
self.groupDetailsViewController = groupDetailsViewController
}
// MARK: - Public
func start() {
self.groupDetailsViewController.setGroup(self.parameters.group, withMatrixSession: self.parameters.session)
}
func toPresentable() -> UIViewController {
return self.groupDetailsViewController
}
}

View file

@ -0,0 +1,29 @@
// File created from ScreenTemplate
// $ createScreen.sh Communities GroupDetails
/*
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
/// GroupDetailsCoordinator input parameters
struct GroupDetailsCoordinatorParameters {
/// The Matrix session
let session: MXSession
/// The group for which the details are displayed
let group: MXGroup
}

View file

@ -0,0 +1,28 @@
// File created from ScreenTemplate
// $ createScreen.sh Communities GroupDetails
/*
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
protocol GroupDetailsCoordinatorDelegate: AnyObject {
func groupDetailsCoordinatorDidCancel(_ coordinator: GroupDetailsCoordinatorProtocol)
}
/// `GroupDetailsCoordinatorProtocol` is a protocol describing a Coordinator that handle communities navigation flow.
protocol GroupDetailsCoordinatorProtocol: Coordinator, Presentable {
var delegate: GroupDetailsCoordinatorDelegate? { get }
}

View file

@ -35,7 +35,7 @@
@discussion This is the designated initializer for programmatic instantiation.
@return An initialized `GroupDetailsViewController` object if successful, `nil` otherwise.
*/
+ (instancetype)groupDetailsViewController;
+ (instancetype)instantiate;
/**
Set the group for which the details are displayed.

View file

@ -55,7 +55,7 @@
bundle:[NSBundle bundleForClass:self.class]];
}
+ (instancetype)groupDetailsViewController
+ (instancetype)instantiate
{
return [[[self class] alloc] initWithNibName:NSStringFromClass(self.class)
bundle:[NSBundle bundleForClass:self.class]];
@ -117,6 +117,9 @@
[self initWithTitles:titles viewControllers:viewControllers defaultSelected:0];
[super viewDidLoad];
// Display leftBarButtonItems or leftBarButtonItem to the right of the Back button
self.navigationItem.leftItemsSupplementBackButton = YES;
}
- (UIStatusBarStyle)preferredStatusBarStyle

View file

@ -66,6 +66,19 @@
*/
@property (weak, nonatomic) IBOutlet UITableView *contactsTableView;
/**
When true, the footer that allows the user to enable local contacts sync will
never be shown. When false, the footer will shown when the user hasn't enabled
contact sync.
*/
@property (nonatomic) BOOL disableFindYourContactsFooter;
/**
Indicates when there's an active search. This is used to determine when the contacts
access footer should be hidden in order to list the results from the server.
*/
@property (nonatomic) BOOL contactsAreFilteredWithSearch;
/**
If YES, the table view will scroll at the top on the next data source refresh.
It comes back to NO after each refresh.

View file

@ -28,7 +28,7 @@
#define CONTACTS_TABLEVC_DEFAULT_SECTION_HEADER_HEIGHT 30.0
#define CONTACTS_TABLEVC_LOCALCONTACTS_SECTION_HEADER_HEIGHT 65.0
@interface ContactsTableViewController ()
@interface ContactsTableViewController () <FindYourContactsFooterViewDelegate, ServiceTermsModalCoordinatorBridgePresenterDelegate>
{
/**
Observe kAppDelegateDidTapStatusBarNotification to handle tap on clock status bar.
@ -41,6 +41,10 @@
id kThemeServiceDidChangeThemeNotificationObserver;
}
@property (nonatomic, strong) FindYourContactsFooterView *findYourContactsFooterView;
@property (nonatomic, strong) ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter;
@end
@implementation ContactsTableViewController
@ -65,6 +69,10 @@
{
[super finalizeInit];
// By default, allow the find your contacts footer to be
// shown when local contacts sync hasn't been enabled.
self.disableFindYourContactsFooter = NO;
// Setup `MXKViewControllerHandling` properties
self.enableBarTintColorStatusChange = NO;
self.rageShakeManager = [RageShakeManager sharedManager];
@ -92,6 +100,7 @@
// Hide line separators of empty cells
self.contactsTableView.tableFooterView = [[UIView alloc] init];
self.contactsAreFilteredWithSearch = NO;
// Observe user interface theme change.
kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
@ -150,20 +159,6 @@
// Screen tracking
[[Analytics sharedInstance] trackScreen:_screenName];
if (BuildSettings.allowLocalContactsAccess)
{
// Check whether the access to the local contacts has not been already asked
// and check that the user has decided to use or not to use an identity server
if ([CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts] == CNAuthorizationStatusNotDetermined
|| !contactsDataSource.mxSession.hasAccountDataIdentityServerValue)
{
// Allow by default the local contacts sync in order to discover matrix users.
// This setting change will trigger the loading of the local contacts, which will automatically
// ask user permission to access their local contacts.
[MXKAppSettings standardAppSettings].syncLocalContacts = YES;
}
}
// Observe kAppDelegateDidTapStatusBarNotification.
kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
@ -171,16 +166,18 @@
}];
// Load the local contacts for display.
[self refreshLocalContacts];
[self refreshContactsTable];
// Show the contacts access footer if necessary.
[self updateFooterViewVisibility];
}
- (void)viewDidAppear:(BOOL)animated
- (void)viewDidLayoutSubviews
{
[super viewDidAppear:animated];
// Load the local contacts for display.
// In viewDidAppear as it may trigger a request for contacts access.
[self refreshLocalContacts];
[super viewDidLayoutSubviews];
[self updateFooterViewHeight];
}
- (void)viewWillDisappear:(BOOL)animated
@ -203,6 +200,83 @@
#pragma mark -
/**
Creates a new `FindYourContactsFooterView` and caches it in
the `findYourContactsFooterView` property before returning it for use.
*/
- (FindYourContactsFooterView*)makeFooterView
{
FindYourContactsFooterView *footerView = [FindYourContactsFooterView instantiate];
footerView.delegate = self;
self.findYourContactsFooterView = footerView;
return footerView;
}
/**
Checks whether local contacts sync is ready to use or if there are any search results
in the table, hiding the find your contacts footer if so. Otherwise the footer is shown
so long as it hasn't been disabled.
*/
- (void)updateFooterViewVisibility
{
if (!BuildSettings.allowLocalContactsAccess || self.disableFindYourContactsFooter)
{
self.contactsTableView.tableFooterView = [[UIView alloc] init];
return;
}
// With contacts access granted, contact sync enabled and an identity server, the footer can be hidden.
if ([CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts] == CNAuthorizationStatusAuthorized
&& MXKAppSettings.standardAppSettings.syncLocalContacts
&& contactsDataSource.mxSession.identityService.areAllTermsAgreed)
{
self.contactsTableView.tableFooterView = [[UIView alloc] init];
return;
}
// If the footer is to be shown, hide it when there's an active search.
if (self.contactsAreFilteredWithSearch)
{
self.contactsTableView.tableFooterView = [[UIView alloc] init];
return;
}
self.contactsTableView.tableFooterView = self.findYourContactsFooterView ?: [self makeFooterView];
[self updateFooterViewHeight];
}
/**
Updates the height of the find your contacts footer to fill all available space.
*/
- (void)updateFooterViewHeight
{
if (self.findYourContactsFooterView && self.findYourContactsFooterView == self.contactsTableView.tableFooterView)
{
// Calculate the natural size of the footer
CGSize fittingSize = CGSizeMake(self.view.frame.size.width, UILayoutFittingCompressedSize.height);
CGSize footerSize = [self.findYourContactsFooterView systemLayoutSizeFittingSize:fittingSize];
// Calculate the height available for the footer
CGFloat availableHeight = self.contactsTableView.bounds.size.height - self.contactsTableView.adjustedContentInset.top - self.contactsTableView.adjustedContentInset.bottom;
if (self.contactsTableView.tableHeaderView)
{
availableHeight -= self.contactsTableView.tableHeaderView.frame.size.height;
}
// Fill all available height unless the footer is larger, in which case use its natural height
CGFloat finalHeight = availableHeight > footerSize.height ? availableHeight : footerSize.height;
self.findYourContactsFooterView.frame = CGRectMake(self.findYourContactsFooterView.frame.origin.x,
self.findYourContactsFooterView.frame.origin.y,
self.findYourContactsFooterView.frame.size.width,
finalHeight);
// This assignment is technically redundant, but does prompt the table view to recalculate its content size
self.contactsTableView.tableFooterView = self.findYourContactsFooterView;
}
}
- (void)displayList:(ContactsDataSource*)listDataSource
{
// Cancel registration on existing dataSource if any
@ -228,42 +302,10 @@
return;
}
// Do not scan local contacts in background if the user has not decided yet about using
// an identity server
BOOL doRefreshLocalContacts = NO;
for (MXSession *session in self.mxSessions)
{
if (session.hasAccountDataIdentityServerValue)
{
doRefreshLocalContacts = YES;
break;
}
}
// Check whether the application is allowed to access the local contacts.
if (doRefreshLocalContacts
if (MXKAppSettings.standardAppSettings.syncLocalContacts
&& contactsDataSource.mxSession.identityService.areAllTermsAgreed
&& [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts] == CNAuthorizationStatusAuthorized)
{
// Check the user permission for syncing local contacts. This permission was handled independently on previous application version.
if (![MXKAppSettings standardAppSettings].syncLocalContacts)
{
// Check whether it was not requested yet.
if (![MXKAppSettings standardAppSettings].syncLocalContactsPermissionRequested)
{
[MXKAppSettings standardAppSettings].syncLocalContactsPermissionRequested = YES;
[MXKContactManager requestUserConfirmationForLocalContactsSyncInViewController:self completionHandler:^(BOOL granted) {
if (granted)
{
// Allow local contacts sync in order to discover matrix users.
[MXKAppSettings standardAppSettings].syncLocalContacts = YES;
}
}];
}
}
// Refresh the local contacts list.
[[MXKContactManager sharedManager] refreshLocalContacts];
}
@ -292,7 +334,7 @@
// Update here the index of the current selected cell (if any) - Useful in landscape mode with split view controller.
NSIndexPath *currentSelectedCellIndexPath = nil;
MasterTabBarController *masterTabBarController = [AppDelegate theDelegate].masterTabBarController;
if (masterTabBarController.currentContactDetailViewController)
if (masterTabBarController.selectedContact)
{
// Look for the rank of this selected contact in displayed recents
currentSelectedCellIndexPath = [contactsDataSource cellIndexPathWithContact:masterTabBarController.selectedContact];
@ -321,6 +363,16 @@
}
}
- (void)setContactsAreFilteredWithSearch:(BOOL)contactsAreFilteredWithSearch
{
// Filter out redundant assignments.
if (_contactsAreFilteredWithSearch != contactsAreFilteredWithSearch)
{
_contactsAreFilteredWithSearch = contactsAreFilteredWithSearch;
[self updateFooterViewVisibility];
}
}
#pragma mark - MXKDataSourceDelegate
- (Class<MXKCellRendering>)cellViewClassForCellData:(MXKCellData*)cellData
@ -426,6 +478,8 @@
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
[contactsDataSource searchWithPattern:searchText forceReset:NO];
self.contactsAreFilteredWithSearch = searchText.length ? YES : NO;
}
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
@ -460,4 +514,126 @@
[self withdrawViewControllerAnimated:YES completion:nil];
}
#pragma mark - FindYourContactsFooterViewDelegate
- (void)contactsFooterViewDidRequestFindContacts:(FindYourContactsFooterView *)footerView
{
// First check the identity if service terms have already been accepted
if (self->contactsDataSource.mxSession.identityService.areAllTermsAgreed)
{
// If they have we only require local contacts access.
[self checkAccessForContacts];
}
else
{
MXWeakify(self);
// The preparation can take some time so indicate this to the user
[self startActivityIndicator];
footerView.isActionEnabled = NO;
[self->contactsDataSource.mxSession prepareIdentityServiceForTermsWithDefault:RiotSettings.shared.identityServerUrlString
success:^(MXSession *session, NSString *baseURL, NSString *accessToken) {
MXStrongifyAndReturnIfNil(self);
[self stopActivityIndicator];
footerView.isActionEnabled = YES;
// Present the terms of the identity server.
[self presentIdentityServerTermsWithSession:session baseURL:baseURL andAccessToken:accessToken];
} failure:^(NSError *error) {
// The error was already logged before the block is called
MXStrongifyAndReturnIfNil(self);
[self stopActivityIndicator];
footerView.isActionEnabled = YES;
// Alert the user that something went wrong.
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:VectorL10n.findYourContactsIdentityServiceError
message:nil
preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:MatrixKitL10n.ok
style:UIAlertActionStyleDefault
handler:nil]];
[self presentViewController:alertController animated:YES completion:nil];
}];
}
}
- (void)checkAccessForContacts
{
MXWeakify(self);
// Check for contacts access, showing a pop-up if necessary.
[MXKTools checkAccessForContacts:VectorL10n.contactsAddressBookPermissionDeniedAlertTitle
withManualChangeMessage:VectorL10n.contactsAddressBookPermissionDeniedAlertMessage
showPopUpInViewController:self
completionHandler:^(BOOL granted) {
MXStrongifyAndReturnIfNil(self);
if (granted)
{
// When granted, local contacts can be shown.
[self showLocalContacts];
}
}];
}
- (void)showLocalContacts
{
// Enable local contacts sync and display.
MXKAppSettings.standardAppSettings.syncLocalContacts = YES;
self->contactsDataSource.showLocalContacts = YES;
// Attempt to refresh the contacts manager.
[self refreshLocalContacts];
// Hide the find your contacts footer.
[self updateFooterViewVisibility];
}
#pragma mark - Identity server service terms
- (void)presentIdentityServerTermsWithSession:(MXSession*)mxSession baseURL:(NSString*)baseURL andAccessToken:(NSString*)accessToken
{
if (!mxSession || !baseURL || !accessToken || self.serviceTermsModalCoordinatorBridgePresenter.isPresenting)
{
return;
}
ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter = [[ServiceTermsModalCoordinatorBridgePresenter alloc] initWithSession:mxSession
baseUrl:baseURL
serviceType:MXServiceTypeIdentityService
accessToken:accessToken];
serviceTermsModalCoordinatorBridgePresenter.delegate = self;
[serviceTermsModalCoordinatorBridgePresenter presentFrom:self animated:YES];
self.serviceTermsModalCoordinatorBridgePresenter = serviceTermsModalCoordinatorBridgePresenter;
}
#pragma mark ServiceTermsModalCoordinatorBridgePresenterDelegate
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidAccept:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter
{
[coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
[self checkAccessForContacts];
}];
self.serviceTermsModalCoordinatorBridgePresenter = nil;
}
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidDecline:(ServiceTermsModalCoordinatorBridgePresenter *)coordinatorBridgePresenter session:(MXSession *)session
{
[coordinatorBridgePresenter dismissWithAnimated:YES completion:nil];
self.serviceTermsModalCoordinatorBridgePresenter = nil;
}
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidClose:(ServiceTermsModalCoordinatorBridgePresenter *)coordinatorBridgePresenter
{
self.serviceTermsModalCoordinatorBridgePresenter = nil;
}
@end

View file

@ -50,6 +50,14 @@ typedef enum : NSUInteger
NSMutableArray<MXKContact*> *filteredMatrixContacts;
}
/**
Whether the data source should include local contacts in the table view. The default
value is set at initialisation to match the `MXKAppSettings` value for `syncLocalContacts`.
Note: After updating this property, the table view's data will need to be reloaded for it to have
any effect.
*/
@property (nonatomic) BOOL showLocalContacts;
/**
Get the contact at the given index path.

View file

@ -92,6 +92,16 @@
return self;
}
- (instancetype)initWithMatrixSession:(MXSession *)mxSession
{
self = [super initWithMatrixSession:mxSession];
if (self) {
// Only show local contacts when contact sync is enabled and the identity server terms of service have been accepted.
_showLocalContacts = MXKAppSettings.standardAppSettings.syncLocalContacts && self.mxSession.identityService.areAllTermsAgreed;
}
return self;
}
- (void)destroy
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXKContactManagerDidUpdateMatrixContactsNotification object:nil];
@ -473,8 +483,8 @@
searchInputSection = count++;
}
// Keep visible the header for the both contact sections, even if their are empty.
if (BuildSettings.allowLocalContactsAccess)
// Keep visible the header for the both contact sections, even if they're are empty.
if (BuildSettings.allowLocalContactsAccess && self.showLocalContacts && [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts] == CNAuthorizationStatusAuthorized)
{
filteredLocalContactsSection = count++;
}
@ -489,7 +499,7 @@
}
// Keep visible the local contact header, even if the section is empty.
if (BuildSettings.allowLocalContactsAccess)
if (BuildSettings.allowLocalContactsAccess && self.showLocalContacts && [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts] == CNAuthorizationStatusAuthorized)
{
filteredLocalContactsSection = count++;
}

View file

@ -0,0 +1,56 @@
// File created from ScreenTemplate
// $ createScreen.sh Contacts ContactDetails
/*
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 UIKit
final class ContactDetailsCoordinator: ContactDetailsCoordinatorProtocol {
// MARK: - Properties
// MARK: Private
private let parameters: ContactDetailsCoordinatorParameters
private let contactDetailsViewController: ContactDetailsViewController
// MARK: Public
// Must be used only internally
var childCoordinators: [Coordinator] = []
weak var delegate: ContactDetailsCoordinatorDelegate?
// MARK: - Setup
init(parameters: ContactDetailsCoordinatorParameters) {
self.parameters = parameters
let contactDetailsViewController: ContactDetailsViewController = ContactDetailsViewController.instantiate()
contactDetailsViewController.contact = self.parameters.contact
contactDetailsViewController.enableVoipCall = self.parameters.enableVoipCall
self.contactDetailsViewController = contactDetailsViewController
}
// MARK: - Public
func start() {
}
func toPresentable() -> UIViewController {
return self.contactDetailsViewController
}
}

View file

@ -0,0 +1,35 @@
// File created from ScreenTemplate
// $ createScreen.sh Contacts ContactDetails
/*
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
/// ContactDetailsCoordinator input parameters
struct ContactDetailsCoordinatorParameters {
/// The displayed contact
let contact: MXKContact
/// Enable voip call (voice/video). NO by default
let enableVoipCall: Bool
init(contact: MXKContact,
enableVoipCall: Bool = false) {
self.contact = contact
self.enableVoipCall = enableVoipCall
}
}

View file

@ -0,0 +1,28 @@
// File created from ScreenTemplate
// $ createScreen.sh Contacts ContactDetails
/*
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
protocol ContactDetailsCoordinatorDelegate: AnyObject {
func contactDetailsCoordinatorDidCancel(_ coordinator: ContactDetailsCoordinatorProtocol)
}
/// `ContactDetailsCoordinatorProtocol` is a protocol describing a Coordinator that handle contact details navigation flow.
protocol ContactDetailsCoordinatorProtocol: Coordinator, Presentable {
var delegate: ContactDetailsCoordinatorDelegate? { get }
}

View file

@ -69,7 +69,7 @@ typedef enum : NSUInteger
@discussion This is the designated initializer for programmatic instantiation.
@return An initialized `ContactDetailsViewController` object if successful, `nil` otherwise.
*/
+ (instancetype)contactDetailsViewController;
+ (instancetype)instantiate;
@end

View file

@ -98,7 +98,7 @@
bundle:[NSBundle bundleForClass:self.class]];
}
+ (instancetype)contactDetailsViewController
+ (instancetype)instantiate
{
return [[[self class] alloc] initWithNibName:NSStringFromClass(self.class)
bundle:[NSBundle bundleForClass:self.class]];
@ -142,6 +142,9 @@
// Define directly the navigation titleView with the custom title view instance. Do not use anymore a container.
self.navigationItem.titleView = contactTitleView;
// Display leftBarButtonItems or leftBarButtonItem to the right of the Back button
self.navigationItem.leftItemsSupplementBackButton = YES;
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)];
[tap setNumberOfTouchesRequired:1];
[tap setNumberOfTapsRequired:1];

View file

@ -0,0 +1,87 @@
//
// 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
import Reusable
@objc protocol FindYourContactsFooterViewDelegate {
func contactsFooterViewDidRequestFindContacts(_ footerView: FindYourContactsFooterView)
}
@objcMembers
class FindYourContactsFooterView: UIView, NibLoadable, Themable {
// MARK: - Properties
weak var delegate: FindYourContactsFooterViewDelegate?
/// Whether or not the view's button responds to taps.
var isActionEnabled: Bool {
get { button.isEnabled }
set { button.isEnabled = newValue }
}
@IBOutlet weak private var containerView: UIView!
@IBOutlet weak private var titleLabel: UILabel!
@IBOutlet weak private var messageLabel: UILabel!
@IBOutlet weak private var button: CustomRoundedButton!
@IBOutlet weak private var footerLabel: UILabel!
// MARK: - Setup
static func instantiate() -> Self {
let view = Self.loadFromNib()
view.update(theme: ThemeService.shared().theme)
return view
}
override func awakeFromNib() {
super.awakeFromNib()
containerView.layer.cornerRadius = 8
button.layer.cornerRadius = 8
titleLabel.text = VectorL10n.findYourContactsTitle
messageLabel.text = VectorL10n.findYourContactsMessage(BuildSettings.bundleDisplayName)
button.setTitle(VectorL10n.findYourContactsButtonTitle, for: .normal)
footerLabel.text = VectorL10n.findYourContactsFooter
}
func update(theme: Theme) {
tintColor = theme.colors.accent
containerView.backgroundColor = theme.colors.quinaryContent
titleLabel.font = theme.fonts.bodySB
titleLabel.textColor = theme.colors.primaryContent
messageLabel.font = theme.fonts.body
messageLabel.textColor = theme.colors.secondaryContent
button.titleLabel?.font = theme.fonts.body
button.backgroundColor = theme.colors.accent
button.setTitleColor(theme.colors.background, for: .normal)
footerLabel.font = theme.fonts.footnote.withSize(13)
footerLabel.textColor = theme.colors.tertiaryContent
}
// MARK: - Action
@IBAction private func buttonAction(_ sender: Any) {
delegate?.contactsFooterViewDidRequestFindContacts(self)
}
}

View file

@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB" customClass="FindYourContactsFooterView" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="320" height="424"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="67f-7H-bSH">
<rect key="frame" x="16" y="45.5" width="288" height="362.5"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="find_your_contacts_facepile" translatesAutoresizingMaskIntoConstraints="NO" id="q3p-2U-JA5">
<rect key="frame" x="89" y="46" width="110" height="46"/>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Start by listing your contacts" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="9oC-ce-hpT">
<rect key="frame" x="20" y="138" width="248" height="21"/>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Let Element show your contacts so you can quickly start chatting with those you know best." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="B3j-1i-EAf">
<rect key="frame" x="20" y="171" width="248" height="61"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="v93-cs-jXa" customClass="CustomRoundedButton" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="16" y="257" width="256" height="45"/>
<constraints>
<constraint firstAttribute="height" constant="45" id="Spc-d4-SvP"/>
</constraints>
<state key="normal" title="Allow contacts access"/>
<connections>
<action selector="buttonAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="XLO-8r-P8S"/>
</connections>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="This can be disabled anytime from settings" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0A6-6s-nu6">
<rect key="frame" x="20" y="314" width="248" height="31.5"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="29" id="MZF-wd-sd6"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="13"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" systemColor="opaqueSeparatorColor"/>
<constraints>
<constraint firstItem="0A6-6s-nu6" firstAttribute="top" secondItem="v93-cs-jXa" secondAttribute="bottom" constant="12" id="9zY-sf-Hrg"/>
<constraint firstItem="q3p-2U-JA5" firstAttribute="top" secondItem="67f-7H-bSH" secondAttribute="top" constant="46" id="Bpl-IE-fhm"/>
<constraint firstAttribute="trailing" secondItem="9oC-ce-hpT" secondAttribute="trailing" constant="20" symbolic="YES" id="D9c-Dn-jPU"/>
<constraint firstAttribute="bottom" secondItem="0A6-6s-nu6" secondAttribute="bottom" constant="17" id="EYu-Qu-iQn"/>
<constraint firstAttribute="trailing" secondItem="B3j-1i-EAf" secondAttribute="trailing" constant="20" symbolic="YES" id="Hg6-yF-XYk"/>
<constraint firstAttribute="trailing" secondItem="0A6-6s-nu6" secondAttribute="trailing" constant="20" symbolic="YES" id="HpJ-VI-vji"/>
<constraint firstItem="v93-cs-jXa" firstAttribute="top" secondItem="B3j-1i-EAf" secondAttribute="bottom" constant="25" id="NCh-1k-Wmw"/>
<constraint firstAttribute="width" relation="lessThanOrEqual" constant="343" id="Ty6-Fa-X0Y"/>
<constraint firstItem="v93-cs-jXa" firstAttribute="leading" secondItem="67f-7H-bSH" secondAttribute="leading" constant="16" id="aF8-bI-HgQ"/>
<constraint firstItem="9oC-ce-hpT" firstAttribute="leading" secondItem="67f-7H-bSH" secondAttribute="leading" constant="20" symbolic="YES" id="bli-Ms-T40"/>
<constraint firstItem="B3j-1i-EAf" firstAttribute="top" secondItem="9oC-ce-hpT" secondAttribute="bottom" constant="12" id="dAh-bo-3yO"/>
<constraint firstItem="q3p-2U-JA5" firstAttribute="centerX" secondItem="67f-7H-bSH" secondAttribute="centerX" id="giT-QS-qOz"/>
<constraint firstAttribute="trailing" secondItem="v93-cs-jXa" secondAttribute="trailing" constant="16" id="mWk-gL-jIf"/>
<constraint firstItem="B3j-1i-EAf" firstAttribute="leading" secondItem="67f-7H-bSH" secondAttribute="leading" constant="20" symbolic="YES" id="pgw-mQ-EiG"/>
<constraint firstItem="0A6-6s-nu6" firstAttribute="leading" secondItem="67f-7H-bSH" secondAttribute="leading" constant="20" symbolic="YES" id="r3U-zK-ma2"/>
<constraint firstItem="9oC-ce-hpT" firstAttribute="top" secondItem="q3p-2U-JA5" secondAttribute="bottom" constant="46" id="s1P-kS-bhX"/>
</constraints>
</view>
</subviews>
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
<constraints>
<constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="67f-7H-bSH" secondAttribute="bottom" constant="16" id="6sY-dx-OLw"/>
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="67f-7H-bSH" secondAttribute="trailing" constant="16" id="NHG-OU-6GO"/>
<constraint firstItem="67f-7H-bSH" firstAttribute="centerX" secondItem="vUN-kp-3ea" secondAttribute="centerX" id="QLf-8P-5HB"/>
<constraint firstItem="67f-7H-bSH" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="16" id="fqj-2T-EL6"/>
<constraint firstItem="67f-7H-bSH" firstAttribute="top" relation="greaterThanOrEqual" secondItem="iN0-l3-epB" secondAttribute="top" constant="16" id="yU6-gF-alq"/>
</constraints>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<connections>
<outlet property="button" destination="v93-cs-jXa" id="ANT-X6-MXQ"/>
<outlet property="containerView" destination="67f-7H-bSH" id="jcP-BN-jNZ"/>
<outlet property="footerLabel" destination="0A6-6s-nu6" id="GOr-Nv-eVE"/>
<outlet property="messageLabel" destination="B3j-1i-EAf" id="Edi-X9-bma"/>
<outlet property="titleLabel" destination="9oC-ce-hpT" id="rOi-fH-kO0"/>
</connections>
<point key="canvasLocation" x="63.768115942028992" y="308.70535714285711"/>
</view>
</objects>
<resources>
<image name="find_your_contacts_facepile" width="110" height="46"/>
<systemColor name="opaqueSeparatorColor">
<color red="0.77647058823529413" green="0.77647058823529413" blue="0.78431372549019607" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
</resources>
</document>

View file

@ -243,7 +243,7 @@
// Update here the index of the current selected cell (if any) - Useful in landscape mode with split view controller.
NSIndexPath *currentSelectedCellIndexPath = nil;
if (masterTabBarController.currentRoomViewController)
if (masterTabBarController.selectedRoomId)
{
// Look for the rank of this selected room in displayed recents
currentSelectedCellIndexPath = [dataSource cellIndexPathWithRoomId:masterTabBarController.selectedRoomId andMatrixSession:masterTabBarController.selectedRoomSession];

View file

@ -91,6 +91,7 @@
[titles addObject:[VectorL10n searchPeople]];
peopleSearchViewController = [ContactsTableViewController contactsTableViewController];
peopleSearchViewController.contactsTableViewControllerDelegate = self;
peopleSearchViewController.disableFindYourContactsFooter = YES;
[viewControllers addObject:peopleSearchViewController];
// add Files tab
@ -247,6 +248,7 @@
// Init the search for people
peopleSearchDataSource = [[ContactsDataSource alloc] initWithMatrixSession:mainSession];
peopleSearchDataSource.showLocalContacts = NO;
peopleSearchDataSource.areSectionsShrinkable = YES;
peopleSearchDataSource.displaySearchInputInContactsList = YES;
peopleSearchDataSource.contactCellAccessoryImage = [[UIImage imageNamed: @"disclosure_icon"] vc_tintedImageUsingColor:ThemeService.shared.theme.textSecondaryColor];;

View file

@ -131,6 +131,7 @@
}
[self.roomAvatar vc_setRoomAvatarImageWith:roomCellData.avatarUrl
roomId:roomCellData.roomIdentifier
displayName:roomCellData.roomDisplayname
mediaManager:roomCellData.mxSession.mediaManager];
}

View file

@ -745,7 +745,6 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ";
ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter = [[ServiceTermsModalCoordinatorBridgePresenter alloc] initWithSession:mxSession baseUrl:config.baseUrl
serviceType:MXServiceTypeIntegrationManager
outOfContext:NO
accessToken:config.scalarToken];
serviceTermsModalCoordinatorBridgePresenter.delegate = self;
@ -762,14 +761,6 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ";
self.serviceTermsModalCoordinatorBridgePresenter = nil;
}
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidCancel:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter
{
[coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
[self withdrawViewControllerAnimated:YES completion:nil];
}];
self.serviceTermsModalCoordinatorBridgePresenter = nil;
}
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidDecline:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter session:(MXSession * _Nonnull)session
{
[coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
@ -778,4 +769,9 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ";
self.serviceTermsModalCoordinatorBridgePresenter = nil;
}
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidClose:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter
{
self.serviceTermsModalCoordinatorBridgePresenter = nil;
}
@end

View file

@ -151,7 +151,6 @@
ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter = [[ServiceTermsModalCoordinatorBridgePresenter alloc] initWithSession:widget.mxSession baseUrl:config.baseUrl
serviceType:MXServiceTypeIntegrationManager
outOfContext:NO
accessToken:config.scalarToken];
serviceTermsModalCoordinatorBridgePresenter.delegate = self;
@ -173,16 +172,15 @@
self.serviceTermsModalCoordinatorBridgePresenter = nil;
}
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidCancel:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter
{
[coordinatorBridgePresenter dismissWithAnimated:YES completion:nil];
self.serviceTermsModalCoordinatorBridgePresenter = nil;
}
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidDecline:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter session:(MXSession * _Nonnull)session
{
[coordinatorBridgePresenter dismissWithAnimated:YES completion:nil];
self.serviceTermsModalCoordinatorBridgePresenter = nil;
}
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidClose:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter
{
self.serviceTermsModalCoordinatorBridgePresenter = nil;
}
@end

View file

@ -656,8 +656,7 @@ NSString *const kJavascriptSendResponseToPostMessageAPI = @"riotIOS.sendResponse
MXLogDebug(@"[WidgetVC] presentTerms for %@", config.baseUrl);
ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter = [[ServiceTermsModalCoordinatorBridgePresenter alloc] initWithSession:widget.mxSession baseUrl:config.baseUrl
serviceType:MXServiceTypeIntegrationManager
outOfContext:NO
serviceType:MXServiceTypeIntegrationManager
accessToken:config.scalarToken];
serviceTermsModalCoordinatorBridgePresenter.delegate = self;
@ -677,14 +676,6 @@ NSString *const kJavascriptSendResponseToPostMessageAPI = @"riotIOS.sendResponse
self.serviceTermsModalCoordinatorBridgePresenter = nil;
}
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidCancel:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter
{
[coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
[self withdrawViewControllerAnimated:YES completion:nil];
}];
self.serviceTermsModalCoordinatorBridgePresenter = nil;
}
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidDecline:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter session:(MXSession * _Nonnull)session
{
[coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
@ -693,4 +684,9 @@ NSString *const kJavascriptSendResponseToPostMessageAPI = @"riotIOS.sendResponse
self.serviceTermsModalCoordinatorBridgePresenter = nil;
}
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidClose:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter
{
self.serviceTermsModalCoordinatorBridgePresenter = nil;
}
@end

View file

@ -61,7 +61,7 @@ final class KeyBackupRecoverCoordinatorBridgePresenter: NSObject {
MXLog.debug("[KeyBackupRecoverCoordinatorBridgePresenter] Push complete security from \(navigationController)")
let navigationRouter = NavigationRouter(navigationController: navigationController)
let navigationRouter = NavigationRouterStore.shared.navigationRouter(for: navigationController)
let keyBackupSetupCoordinator = KeyBackupRecoverCoordinator(session: self.session, keyBackupVersion: keyBackupVersion, navigationRouter: navigationRouter)
keyBackupSetupCoordinator.delegate = self

View file

@ -101,7 +101,7 @@ final class KeyVerificationCoordinatorBridgePresenter: NSObject {
MXLog.debug("[KeyVerificationCoordinatorBridgePresenter] Push complete security from \(navigationController)")
let navigationRouter = NavigationRouter(navigationController: navigationController)
let navigationRouter = NavigationRouterStore.shared.navigationRouter(for: navigationController)
let keyVerificationCoordinator = KeyVerificationCoordinator(session: self.session, flow: .completeSecurity(isNewSignIn), navigationRouter: navigationRouter)
keyVerificationCoordinator.delegate = self

View file

@ -0,0 +1,242 @@
// File created from ScreenTemplate
// $ createScreen.sh Room Room
/*
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 UIKit
final class RoomCoordinator: NSObject, RoomCoordinatorProtocol {
// MARK: - Properties
// MARK: Private
private let parameters: RoomCoordinatorParameters
private let roomViewController: RoomViewController
private let activityIndicatorPresenter: ActivityIndicatorPresenterType
private var selectedEventId: String?
private var roomDataSourceManager: MXKRoomDataSourceManager {
return MXKRoomDataSourceManager.sharedManager(forMatrixSession: self.parameters.session)
}
/// Indicate true if the Coordinator has started once
private var hasStartedOnce: Bool {
return self.roomViewController.delegate != nil
}
private var navigationRouter: NavigationRouterType? {
var finalNavigationRouter: NavigationRouterType?
if let navigationRouter = self.parameters.navigationRouter {
finalNavigationRouter = navigationRouter
} else if let navigationRouterStore = self.parameters.navigationRouterStore, let currentNavigationController = self.roomViewController.navigationController {
// If no navigationRouter has been provided, try to get the navigation router from the current RoomViewController navigation controller if exists
finalNavigationRouter = navigationRouterStore.navigationRouter(for: currentNavigationController)
}
return finalNavigationRouter
}
// MARK: Public
// Must be used only internally
var childCoordinators: [Coordinator] = []
weak var delegate: RoomCoordinatorDelegate?
var canReleaseRoomDataSource: Bool {
// If the displayed data is not a preview, let the manager release the room data source
// (except if the view controller has the room data source ownership).
return self.parameters.previewData == nil && self.roomViewController.roomDataSource != nil && self.roomViewController.hasRoomDataSourceOwnership == false
}
// MARK: - Setup
init(parameters: RoomCoordinatorParameters) {
self.parameters = parameters
self.selectedEventId = parameters.eventId
self.roomViewController = RoomViewController.instantiate()
self.activityIndicatorPresenter = ActivityIndicatorPresenter()
super.init()
}
// MARK: - Public
func start() {
self.start(withCompletion: nil)
}
// NOTE: Completion closure has been added for legacy architecture purpose.
// Remove this completion after LegacyAppDelegate refactor.
func start(withCompletion completion: (() -> Void)?) {
self.roomViewController.delegate = self
// Detect when view controller has been dismissed by gesture when presented modally (not in full screen).
self.roomViewController.presentationController?.delegate = self
if let eventId = self.selectedEventId {
self.loadRoom(withId: self.parameters.roomId, and: eventId, completion: completion)
} else {
self.loadRoom(withId: self.parameters.roomId, completion: completion)
}
// Add `roomViewController` to the NavigationRouter, only if it has been explicitly set as parameter
if let navigationRouter = self.parameters.navigationRouter {
if navigationRouter.modules.isEmpty == false {
navigationRouter.push(self.roomViewController, animated: true, popCompletion: nil)
} else {
navigationRouter.setRootModule(self.roomViewController, popCompletion: nil)
}
}
}
func start(withEventId eventId: String, completion: (() -> Void)?) {
self.selectedEventId = eventId
if self.hasStartedOnce {
self.loadRoom(withId: self.parameters.roomId, and: eventId, completion: completion)
} else {
self.start(withCompletion: completion)
}
}
func toPresentable() -> UIViewController {
return self.roomViewController
}
// MARK: - Private
private func loadRoom(withId roomId: String, completion: (() -> Void)?) {
// Present activity indicator when retrieving roomDataSource for given room ID
self.activityIndicatorPresenter.presentActivityIndicator(on: roomViewController.view, animated: false)
let roomDataSourceManager: MXKRoomDataSourceManager = MXKRoomDataSourceManager.sharedManager(forMatrixSession: self.parameters.session)
// LIVE: Show the room live timeline managed by MXKRoomDataSourceManager
roomDataSourceManager.roomDataSource(forRoom: roomId, create: true, onComplete: { [weak self] (roomDataSource) in
guard let self = self else {
return
}
self.activityIndicatorPresenter.removeCurrentActivityIndicator(animated: true)
if let roomDataSource = roomDataSource {
self.roomViewController.displayRoom(roomDataSource)
}
completion?()
})
}
private func loadRoom(withId roomId: String, and eventId: String, completion: (() -> Void)?) {
// Present activity indicator when retrieving roomDataSource for given room ID
self.activityIndicatorPresenter.presentActivityIndicator(on: roomViewController.view, animated: false)
// Open the room on the requested event
RoomDataSource.load(withRoomId: roomId,
initialEventId: eventId,
andMatrixSession: self.parameters.session) { [weak self] (dataSource) in
guard let self = self else {
return
}
self.activityIndicatorPresenter.removeCurrentActivityIndicator(animated: true)
guard let roomDataSource = dataSource as? RoomDataSource else {
return
}
roomDataSource.markTimelineInitialEvent = true
self.roomViewController.displayRoom(roomDataSource)
// Give the data source ownership to the room view controller.
self.roomViewController.hasRoomDataSourceOwnership = true
completion?()
}
}
}
// MARK: - RoomIdentifiable
extension RoomCoordinator: RoomIdentifiable {
var roomId: String? {
return self.parameters.roomId
}
var mxSession: MXSession? {
self.parameters.session
}
}
// MARK: - UIAdaptivePresentationControllerDelegate
extension RoomCoordinator: UIAdaptivePresentationControllerDelegate {
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
self.delegate?.roomCoordinatorDidDismissInteractively(self)
}
}
// MARK: - RoomViewControllerDelegate
extension RoomCoordinator: RoomViewControllerDelegate {
func roomViewController(_ roomViewController: RoomViewController, showRoomWithId roomID: String) {
self.delegate?.roomCoordinator(self, didSelectRoomWithId: roomID)
}
func roomViewController(_ roomViewController: RoomViewController, showMemberDetails roomMember: MXRoomMember) {
// TODO:
}
func roomViewControllerShowRoomDetails(_ roomViewController: RoomViewController) {
// TODO:
}
func roomViewControllerDidLeaveRoom(_ roomViewController: RoomViewController) {
self.delegate?.roomCoordinatorDidLeaveRoom(self)
}
func roomViewControllerPreviewDidTapCancel(_ roomViewController: RoomViewController) {
self.delegate?.roomCoordinatorDidCancelRoomPreview(self)
}
func roomViewController(_ roomViewController: RoomViewController, startChatWithUserId userId: String, completion: @escaping () -> Void) {
AppDelegate.theDelegate().createDirectChat(withUserId: userId, completion: completion)
}
func roomViewController(_ roomViewController: RoomViewController, showCompleteSecurityFor session: MXSession) {
AppDelegate.theDelegate().presentCompleteSecurity(for: session)
}
func roomViewController(_ roomViewController: RoomViewController, handleUniversalLinkFragment fragment: String, from universalLinkURL: URL?) -> Bool {
return AppDelegate.theDelegate().handleUniversalLinkFragment(fragment, from: universalLinkURL)
}
func roomViewController(_ roomViewController: RoomViewController, handleUniversalLinkURL universalLinkURL: URL) -> Bool {
return AppDelegate.theDelegate().handleUniversalLinkURL(universalLinkURL)
}
}

View file

@ -0,0 +1,145 @@
//
// 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
@objc protocol RoomCoordinatorBridgePresenterDelegate {
func roomCoordinatorBridgePresenterDidLeaveRoom(_ bridgePresenter: RoomCoordinatorBridgePresenter)
func roomCoordinatorBridgePresenterDidCancelRoomPreview(_ bridgePresenter: RoomCoordinatorBridgePresenter)
func roomCoordinatorBridgePresenter(_ bridgePresenter: RoomCoordinatorBridgePresenter, didSelectRoomWithId roomId: String)
func roomCoordinatorBridgePresenterDidDismissInteractively(_ bridgePresenter: RoomCoordinatorBridgePresenter)
}
@objcMembers
class RoomCoordinatorBridgePresenterParameters: NSObject {
/// The matrix session in which the room should be available.
let session: MXSession
/// The room identifier
let roomId: String
/// If not nil, the room will be opened on this event.
let eventId: String?
/// The data for the room preview.
let previewData: RoomPreviewData?
init(session: MXSession,
roomId: String,
eventId: String?,
previewData: RoomPreviewData?) {
self.session = session
self.roomId = roomId
self.eventId = eventId
self.previewData = previewData
}
}
/// RoomCoordinatorBridgePresenter enables to start RoomCoordinator from a view controller.
/// This bridge is used while waiting for global usage of coordinator pattern.
/// **WARNING**: This class breaks the Coordinator abstraction and it has been introduced for **Objective-C compatibility only** (mainly for integration in legacy view controllers). Each bridge should be removed once the underlying Coordinator has been integrated by another Coordinator.
@objcMembers
final class RoomCoordinatorBridgePresenter: NSObject {
// MARK: - Properties
// MARK: Private
private let bridgeParameters: RoomCoordinatorBridgePresenterParameters
private var coordinator: RoomCoordinator?
// MARK: Public
weak var delegate: RoomCoordinatorBridgePresenterDelegate?
// MARK: - Setup
init(parameters: RoomCoordinatorBridgePresenterParameters) {
self.bridgeParameters = parameters
super.init()
}
// MARK: - Public
func present(from viewController: UIViewController, animated: Bool) {
let coordinator = self.createRoomCoordinator()
coordinator.delegate = self
let presentable = coordinator.toPresentable()
presentable.modalPresentationStyle = .formSheet
viewController.present(presentable, animated: animated, completion: nil)
coordinator.start()
self.coordinator = coordinator
}
func push(from navigationController: UINavigationController, animated: Bool) {
let navigationRouter = NavigationRouterStore.shared.navigationRouter(for: navigationController)
let coordinator = self.createRoomCoordinator(with: navigationRouter)
coordinator.delegate = self
coordinator.start() // Will trigger view controller push
self.coordinator = coordinator
}
func dismiss(animated: Bool, completion: (() -> Void)?) {
guard let coordinator = self.coordinator else {
return
}
coordinator.toPresentable().dismiss(animated: animated) {
self.coordinator = nil
completion?()
}
}
// MARK: - Private
private func createRoomCoordinator(with navigationRouter: NavigationRouterType = NavigationRouter(navigationController: RiotNavigationController())) -> RoomCoordinator {
let coordinatorParameters: RoomCoordinatorParameters
if let previewData = self.bridgeParameters.previewData {
coordinatorParameters = RoomCoordinatorParameters(navigationRouter: navigationRouter, previewData: previewData)
} else {
coordinatorParameters = RoomCoordinatorParameters(navigationRouter: navigationRouter, session: self.bridgeParameters.session, roomId: self.bridgeParameters.roomId, eventId: self.bridgeParameters.eventId)
}
return RoomCoordinator(parameters: coordinatorParameters)
}
}
// MARK: - RoomNotificationSettingsCoordinatorDelegate
extension RoomCoordinatorBridgePresenter: RoomCoordinatorDelegate {
func roomCoordinator(_ coordinator: RoomCoordinatorProtocol, didSelectRoomWithId roomId: String) {
self.delegate?.roomCoordinatorBridgePresenter(self, didSelectRoomWithId: roomId)
}
func roomCoordinatorDidLeaveRoom(_ coordinator: RoomCoordinatorProtocol) {
self.delegate?.roomCoordinatorBridgePresenterDidLeaveRoom(self)
}
func roomCoordinatorDidCancelRoomPreview(_ coordinator: RoomCoordinatorProtocol) {
self.delegate?.roomCoordinatorBridgePresenterDidCancelRoomPreview(self)
}
func roomCoordinatorDidDismissInteractively(_ coordinator: RoomCoordinatorProtocol) {
self.delegate?.roomCoordinatorBridgePresenterDidDismissInteractively(self)
}
}

View file

@ -0,0 +1,76 @@
//
// 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
/// RoomCoordinator input parameters
struct RoomCoordinatorParameters {
// MARK: - Properties
/// The navigation router that manage physical navigation
let navigationRouter: NavigationRouterType?
/// The navigation router store that enables to get a NavigationRouter from a navigation controller
/// `navigationRouter` property takes priority on `navigationRouterStore`
let navigationRouterStore: NavigationRouterStoreProtocol?
/// The matrix session in which the room should be available.
let session: MXSession
/// The room identifier
let roomId: String
/// If not nil, the room will be opened on this event.
let eventId: String?
/// The data for the room preview.
let previewData: RoomPreviewData?
// MARK: - Setup
private init(navigationRouter: NavigationRouterType?,
navigationRouterStore: NavigationRouterStoreProtocol?,
session: MXSession,
roomId: String,
eventId: String?,
previewData: RoomPreviewData?) {
self.navigationRouter = navigationRouter
self.navigationRouterStore = navigationRouterStore
self.session = session
self.roomId = roomId
self.eventId = eventId
self.previewData = previewData
}
/// Init to present a joined room
init(navigationRouter: NavigationRouterType? = nil,
navigationRouterStore: NavigationRouterStoreProtocol? = nil,
session: MXSession,
roomId: String,
eventId: String? = nil) {
self.init(navigationRouter: navigationRouter, navigationRouterStore: navigationRouterStore, session: session, roomId: roomId, eventId: eventId, previewData: nil)
}
/// Init to present a room preview
init(navigationRouter: NavigationRouterType? = nil,
navigationRouterStore: NavigationRouterStoreProtocol? = nil,
previewData: RoomPreviewData) {
self.init(navigationRouter: navigationRouter, navigationRouterStore: navigationRouterStore, session: previewData.mxSession, roomId: previewData.roomId, eventId: nil, previewData: previewData)
}
}

View file

@ -0,0 +1,48 @@
// File created from ScreenTemplate
// $ createScreen.sh Room Room
/*
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
protocol RoomCoordinatorDelegate: AnyObject {
func roomCoordinatorDidLeaveRoom(_ coordinator: RoomCoordinatorProtocol)
func roomCoordinatorDidCancelRoomPreview(_ coordinator: RoomCoordinatorProtocol)
func roomCoordinator(_ coordinator: RoomCoordinatorProtocol, didSelectRoomWithId roomId: String)
func roomCoordinatorDidDismissInteractively(_ coordinator: RoomCoordinatorProtocol)
}
/// `RoomCoordinatorProtocol` is a protocol describing a Coordinator that handle room navigation flow.
protocol RoomCoordinatorProtocol: Coordinator, Presentable, RoomIdentifiable {
var delegate: RoomCoordinatorDelegate? { get }
// Indicate if the underlying RoomDataSource can be released
var canReleaseRoomDataSource: Bool { get }
/// Start the Coordinator with a setup completion.
/// NOTE: Completion closure has been added for legacy architecture purpose.
/// Remove this completion after LegacyAppDelegate refactor.
/// - Parameters:
/// - completion: called when the RoomDataSource has finish to load.
func start(withCompletion completion: (() -> Void)?)
/// Use this method when the room screen is already shown and you want to go to a specific event.
/// i.e User tap on push notification message for the current displayed room
/// - Parameters:
/// - eventId: The id of the event to display.
/// - completion: called when the RoomDataSource has finish to load.
func start(withEventId eventId: String, completion: (() -> Void)?)
}

View file

@ -0,0 +1,24 @@
//
// 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
/// `RoomIdentifiable` describes an object tied to a specific room id.
/// Useful to identify existing objects that should be removed when the user leaves a room for example.
protocol RoomIdentifiable {
var roomId: String? { get }
var mxSession: MXSession? { get }
}

View file

@ -73,7 +73,7 @@ final class RoomInfoCoordinatorBridgePresenter: NSObject {
}
func push(from navigationController: UINavigationController, animated: Bool) {
let navigationRouter = NavigationRouter(navigationController: navigationController)
let navigationRouter = NavigationRouterStore.shared.navigationRouter(for: navigationController)
let roomInfoCoordinator = RoomInfoCoordinator(parameters: self.coordinatorParameters, navigationRouter: navigationRouter)
roomInfoCoordinator.delegate = self

View file

@ -410,6 +410,9 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
[self vc_removeBackTitle];
// Display leftBarButtonItems or leftBarButtonItem to the right of the Back button
self.navigationItem.leftItemsSupplementBackButton = YES;
[self setupRemoveJitsiWidgetRemoveView];
// Replace the default input toolbar view.

View file

@ -37,9 +37,9 @@ final class ServiceTermsModalScreenCoordinator: ServiceTermsModalScreenCoordinat
// MARK: - Setup
init(serviceTerms: MXServiceTerms, outOfContext: Bool = false) {
init(serviceTerms: MXServiceTerms) {
let serviceTermsModalScreenViewModel = ServiceTermsModalScreenViewModel(serviceTerms: serviceTerms, outOfContext: outOfContext)
let serviceTermsModalScreenViewModel = ServiceTermsModalScreenViewModel(serviceTerms: serviceTerms)
let serviceTermsModalScreenViewController = ServiceTermsModalScreenViewController.instantiate(with: serviceTermsModalScreenViewModel)
self.serviceTermsModalScreenViewModel = serviceTermsModalScreenViewModel
self.serviceTermsModalScreenViewController = serviceTermsModalScreenViewController
@ -70,8 +70,4 @@ extension ServiceTermsModalScreenCoordinator: ServiceTermsModalScreenViewModelCo
func serviceTermsModalScreenViewModelDidDecline(_ viewModel: ServiceTermsModalScreenViewModelType) {
self.delegate?.serviceTermsModalScreenCoordinatorDidDecline(self)
}
func serviceTermsModalScreenViewModelDidCancel(_ viewModel: ServiceTermsModalScreenViewModelType) {
self.delegate?.serviceTermsModalScreenCoordinatorDidCancel(self)
}
}

View file

@ -22,7 +22,6 @@ protocol ServiceTermsModalScreenCoordinatorDelegate: AnyObject {
func serviceTermsModalScreenCoordinatorDidAccept(_ coordinator: ServiceTermsModalScreenCoordinatorType)
func serviceTermsModalScreenCoordinator(_ coordinator: ServiceTermsModalScreenCoordinatorType, displayPolicy policy: MXLoginPolicyData)
func serviceTermsModalScreenCoordinatorDidDecline(_ coordinator: ServiceTermsModalScreenCoordinatorType)
func serviceTermsModalScreenCoordinatorDidCancel(_ coordinator: ServiceTermsModalScreenCoordinatorType)
}
/// `ServiceTermsModalScreenCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow.

View file

@ -24,5 +24,4 @@ enum ServiceTermsModalScreenViewAction {
case display(MXLoginPolicyData)
case accept
case decline
case cancel
}

View file

@ -1,12 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="V8j-Lb-PgC">
<device id="retina6_1" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="V8j-Lb-PgC">
<device id="retina5_9" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
@ -15,95 +14,131 @@
<objects>
<viewController extendedLayoutIncludesOpaqueBars="YES" automaticallyAdjustsScrollViewInsets="NO" id="V8j-Lb-PgC" customClass="ServiceTermsModalScreenViewController" customModule="Riot" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="EL9-GA-lwo">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="9U2-KL-ZVA">
<rect key="frame" x="0.0" y="44" width="414" height="852"/>
<rect key="frame" x="0.0" y="44" width="375" height="768"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="e7g-um-WO4">
<rect key="frame" x="0.0" y="0.0" width="414" height="852"/>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="voD-3Q-ryt" userLabel="Content">
<rect key="frame" x="5" y="0.0" width="365" height="768"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="voD-3Q-ryt">
<rect key="frame" x="0.0" y="0.0" width="414" height="852"/>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="24" translatesAutoresizingMaskIntoConstraints="NO" id="gff-MZ-3bp">
<rect key="frame" x="20" y="20" width="325" height="195.66666666666666"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="To continue you need to accept the Terms of this service." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bxI-mu-qng">
<rect key="frame" x="20" y="20" width="374" height="100"/>
<constraints>
<constraint firstAttribute="height" constant="100" id="1bP-8m-xrd"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="swq-xw-ItG">
<rect key="frame" x="20" y="128" width="374" height="600"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</tableView>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="dcx-ju-f0Q">
<rect key="frame" x="20" y="736" width="374" height="96"/>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="center" horizontalHuggingPriority="251" verticalHuggingPriority="1000" image="find_your_contacts_facepile" translatesAutoresizingMaskIntoConstraints="NO" id="XWa-N5-VF2">
<rect key="frame" x="0.0" y="0.0" width="325" height="46"/>
</imageView>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="24" translatesAutoresizingMaskIntoConstraints="NO" id="FZk-3I-Zbs">
<rect key="frame" x="0.0" y="70" width="325" height="125.66666666666669"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="DOt-5E-FjF">
<rect key="frame" x="0.0" y="0.0" width="374" height="44"/>
<constraints>
<constraint firstAttribute="height" constant="44" id="d2a-Dw-Pu5"/>
</constraints>
<state key="normal" title="Accept"/>
<connections>
<action selector="acceptButtonAction:" destination="V8j-Lb-PgC" eventType="touchUpInside" id="uvI-tt-Nfj"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="QHr-gh-dAD">
<rect key="frame" x="0.0" y="52" width="374" height="44"/>
<constraints>
<constraint firstAttribute="height" constant="44" id="y6d-Vg-5PP"/>
</constraints>
<state key="normal" title="Decline">
<color key="titleColor" red="1" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
</state>
<connections>
<action selector="declineButtonAction:" destination="V8j-Lb-PgC" eventType="touchUpInside" id="MTU-9k-8yo"/>
</connections>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="To continue, accept the below terms and conditions" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bxI-mu-qng">
<rect key="frame" x="0.0" y="0.0" width="325" height="40.666666666666664"/>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="This will allow someone to find you if they have your phone number or email saved in their phone contacts." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="5fA-rc-XMP">
<rect key="frame" x="0.0" y="64.666666666666657" width="325" height="61"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</stackView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</stackView>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" scrollEnabled="NO" dataMode="prototypes" style="insetGrouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" translatesAutoresizingMaskIntoConstraints="NO" id="swq-xw-ItG">
<rect key="frame" x="0.0" y="235.66666666666669" width="365" height="120"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="bxI-mu-qng" firstAttribute="leading" secondItem="voD-3Q-ryt" secondAttribute="leading" constant="20" symbolic="YES" id="1K2-u8-QsL"/>
<constraint firstAttribute="trailing" secondItem="bxI-mu-qng" secondAttribute="trailing" constant="20" symbolic="YES" id="41D-TP-69f"/>
<constraint firstItem="swq-xw-ItG" firstAttribute="top" secondItem="bxI-mu-qng" secondAttribute="bottom" constant="8" symbolic="YES" id="9Gg-Xb-o2W"/>
<constraint firstItem="bxI-mu-qng" firstAttribute="top" secondItem="voD-3Q-ryt" secondAttribute="top" constant="20" symbolic="YES" id="W1m-x0-TyS"/>
<constraint firstItem="dcx-ju-f0Q" firstAttribute="leading" secondItem="voD-3Q-ryt" secondAttribute="leading" constant="20" symbolic="YES" id="Wcx-d6-M14"/>
<constraint firstAttribute="trailing" secondItem="swq-xw-ItG" secondAttribute="trailing" constant="20" symbolic="YES" id="Y5v-Gg-xkM"/>
<constraint firstAttribute="bottom" secondItem="dcx-ju-f0Q" secondAttribute="bottom" constant="20" symbolic="YES" id="cpa-Lq-Mvs"/>
<constraint firstAttribute="trailing" secondItem="dcx-ju-f0Q" secondAttribute="trailing" constant="20" symbolic="YES" id="eGO-kM-neh"/>
<constraint firstAttribute="width" priority="750" constant="500" id="glD-Sz-73O"/>
<constraint firstItem="dcx-ju-f0Q" firstAttribute="top" secondItem="swq-xw-ItG" secondAttribute="bottom" constant="8" symbolic="YES" id="zYm-2k-kvi"/>
<constraint firstItem="swq-xw-ItG" firstAttribute="leading" secondItem="voD-3Q-ryt" secondAttribute="leading" constant="20" symbolic="YES" id="ze1-Iw-v9U"/>
<constraint firstAttribute="height" constant="120" id="phu-oG-oGN"/>
</constraints>
</view>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="Service Terms Cell" textLabel="inf-zH-Iu8" style="IBUITableViewCellStyleDefault" id="krZ-0R-qfI">
<rect key="frame" x="16" y="49" width="333" height="43.666667938232422"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="krZ-0R-qfI" id="I7f-2P-YXj">
<rect key="frame" x="0.0" y="0.0" width="333" height="43.666667938232422"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="inf-zH-Iu8">
<rect key="frame" x="16" y="0.0" width="301" height="43.666667938232422"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="0.0"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
</tableViewCell>
</prototypes>
</tableView>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="12" translatesAutoresizingMaskIntoConstraints="NO" id="dcx-ju-f0Q">
<rect key="frame" x="20" y="602" width="325" height="146"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="This can be disabled anytime in settings." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Qmt-nE-Lg7">
<rect key="frame" x="0.0" y="0.0" width="325" height="32"/>
<constraints>
<constraint firstAttribute="height" constant="32" id="PCs-bC-vNT"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="13"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="DOt-5E-FjF">
<rect key="frame" x="0.0" y="44" width="325" height="45"/>
<constraints>
<constraint firstAttribute="height" constant="45" id="d2a-Dw-Pu5"/>
</constraints>
<state key="normal" title="Accept"/>
<connections>
<action selector="acceptButtonAction:" destination="V8j-Lb-PgC" eventType="touchUpInside" id="uvI-tt-Nfj"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="QHr-gh-dAD">
<rect key="frame" x="0.0" y="101" width="325" height="45"/>
<constraints>
<constraint firstAttribute="height" constant="45" id="y6d-Vg-5PP"/>
</constraints>
<state key="normal" title="Decline">
<color key="titleColor" red="1" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
</state>
<connections>
<action selector="declineButtonAction:" destination="V8j-Lb-PgC" eventType="touchUpInside" id="MTU-9k-8yo"/>
</connections>
</button>
</subviews>
</stackView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="voD-3Q-ryt" secondAttribute="bottom" id="63a-5e-ptU"/>
<constraint firstItem="voD-3Q-ryt" firstAttribute="centerX" secondItem="e7g-um-WO4" secondAttribute="centerX" id="P2G-mq-gQW"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="voD-3Q-ryt" secondAttribute="trailing" id="QgV-SO-5yf"/>
<constraint firstItem="voD-3Q-ryt" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="e7g-um-WO4" secondAttribute="leading" id="YPo-u1-PtT"/>
<constraint firstItem="voD-3Q-ryt" firstAttribute="top" secondItem="e7g-um-WO4" secondAttribute="top" id="rhQ-96-szL"/>
<constraint firstItem="gff-MZ-3bp" firstAttribute="top" secondItem="voD-3Q-ryt" secondAttribute="top" constant="20" symbolic="YES" id="RbQ-et-xoU"/>
<constraint firstItem="dcx-ju-f0Q" firstAttribute="leading" secondItem="voD-3Q-ryt" secondAttribute="leading" constant="20" symbolic="YES" id="Wcx-d6-M14"/>
<constraint firstAttribute="trailing" secondItem="swq-xw-ItG" secondAttribute="trailing" id="Y5v-Gg-xkM"/>
<constraint firstAttribute="trailing" secondItem="dcx-ju-f0Q" secondAttribute="trailing" constant="20" symbolic="YES" id="eGO-kM-neh"/>
<constraint firstAttribute="width" relation="lessThanOrEqual" constant="500" id="glD-Sz-73O"/>
<constraint firstAttribute="trailing" secondItem="gff-MZ-3bp" secondAttribute="trailing" constant="20" symbolic="YES" id="iF6-Ga-3JK"/>
<constraint firstItem="gff-MZ-3bp" firstAttribute="leading" secondItem="voD-3Q-ryt" secondAttribute="leading" constant="20" symbolic="YES" id="x7i-4D-XSX"/>
<constraint firstAttribute="bottom" secondItem="dcx-ju-f0Q" secondAttribute="bottom" constant="20" symbolic="YES" id="xcR-k0-EIW"/>
<constraint firstItem="dcx-ju-f0Q" firstAttribute="top" secondItem="swq-xw-ItG" secondAttribute="bottom" priority="250" constant="8" symbolic="YES" id="zYm-2k-kvi"/>
<constraint firstItem="swq-xw-ItG" firstAttribute="leading" secondItem="voD-3Q-ryt" secondAttribute="leading" id="ze1-Iw-v9U"/>
<constraint firstItem="swq-xw-ItG" firstAttribute="top" secondItem="gff-MZ-3bp" secondAttribute="bottom" constant="20" id="zfd-m4-7dW"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstItem="e7g-um-WO4" firstAttribute="height" secondItem="9U2-KL-ZVA" secondAttribute="height" priority="500" id="GP8-i4-Fqh"/>
<constraint firstAttribute="trailing" secondItem="e7g-um-WO4" secondAttribute="trailing" id="GyG-Fh-PME"/>
<constraint firstItem="e7g-um-WO4" firstAttribute="width" secondItem="9U2-KL-ZVA" secondAttribute="width" id="Ok2-WQ-Zgc"/>
<constraint firstAttribute="bottom" secondItem="e7g-um-WO4" secondAttribute="bottom" id="Y46-NP-zAc"/>
<constraint firstItem="e7g-um-WO4" firstAttribute="leading" secondItem="9U2-KL-ZVA" secondAttribute="leading" id="aoV-Yh-AcD"/>
<constraint firstItem="e7g-um-WO4" firstAttribute="top" secondItem="9U2-KL-ZVA" secondAttribute="top" id="pFN-bA-SHw"/>
<constraint firstItem="voD-3Q-ryt" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="9U2-KL-ZVA" secondAttribute="leading" id="UAm-kU-6ug"/>
<constraint firstAttribute="trailing" secondItem="voD-3Q-ryt" secondAttribute="trailing" id="VcR-aV-I19"/>
<constraint firstAttribute="bottom" secondItem="voD-3Q-ryt" secondAttribute="bottom" priority="500" id="fvu-aR-JZh"/>
<constraint firstItem="voD-3Q-ryt" firstAttribute="top" secondItem="9U2-KL-ZVA" secondAttribute="top" id="jJg-B5-Qva"/>
<constraint firstItem="voD-3Q-ryt" firstAttribute="centerX" secondItem="9U2-KL-ZVA" secondAttribute="centerX" id="kG2-ZG-S35"/>
<constraint firstItem="voD-3Q-ryt" firstAttribute="height" relation="greaterThanOrEqual" secondItem="9U2-KL-ZVA" secondAttribute="height" id="kWh-0d-9t0"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="voD-3Q-ryt" secondAttribute="trailing" id="mLA-DD-xEj"/>
</constraints>
</scrollView>
</subviews>
<viewLayoutGuide key="safeArea" id="bFg-jh-JZB"/>
<color key="backgroundColor" red="0.94509803921568625" green="0.96078431372549022" blue="0.97254901960784312" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="9U2-KL-ZVA" secondAttribute="bottom" id="7Cb-nY-CsO"/>
@ -111,19 +146,28 @@
<constraint firstItem="bFg-jh-JZB" firstAttribute="trailing" secondItem="9U2-KL-ZVA" secondAttribute="trailing" id="sbD-ek-vGJ"/>
<constraint firstItem="bFg-jh-JZB" firstAttribute="top" secondItem="9U2-KL-ZVA" secondAttribute="top" id="wTB-V6-IHV"/>
</constraints>
<viewLayoutGuide key="safeArea" id="bFg-jh-JZB"/>
</view>
<connections>
<outlet property="acceptButton" destination="DOt-5E-FjF" id="ktw-U4-efQ"/>
<outlet property="declineButton" destination="QHr-gh-dAD" id="QOS-rE-4SP"/>
<outlet property="messageLabel" destination="bxI-mu-qng" id="pbX-aZ-inC"/>
<outlet property="descriptionLabel" destination="5fA-rc-XMP" id="OkU-OE-DgA"/>
<outlet property="footerLabel" destination="Qmt-nE-Lg7" id="BOZ-Qy-Pxj"/>
<outlet property="imageView" destination="XWa-N5-VF2" id="YbG-3e-03Q"/>
<outlet property="scrollView" destination="9U2-KL-ZVA" id="ojG-2y-X7b"/>
<outlet property="tableView" destination="swq-xw-ItG" id="Fwb-Sc-bec"/>
<outlet property="tableViewHeightConstraint" destination="phu-oG-oGN" id="rTd-2V-qVf"/>
<outlet property="titleLabel" destination="bxI-mu-qng" id="pbX-aZ-inC"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="zK0-v6-7Wt" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-3198" y="-647"/>
<point key="canvasLocation" x="-3199.1999999999998" y="-647.29064039408865"/>
</scene>
</scenes>
<resources>
<image name="find_your_contacts_facepile" width="110" height="46"/>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>

View file

@ -22,17 +22,27 @@ final class ServiceTermsModalScreenViewController: UIViewController {
// MARK: - Constants
private enum Constants {
/// Reuse identifier for the prototype cell in the storyboard.
static let cellReuseIdentifier = "Service Terms Cell"
static let minimumTableViewHeight: CGFloat = 120
}
// MARK: - Properties
// MARK: Outlets
@IBOutlet private weak var scrollView: UIScrollView!
@IBOutlet private weak var messageLabel: UILabel!
@IBOutlet private weak var imageView: UIImageView!
@IBOutlet private weak var titleLabel: UILabel!
@IBOutlet private weak var descriptionLabel: UILabel!
@IBOutlet private weak var footerLabel: UILabel!
@IBOutlet private weak var tableView: UITableView!
@IBOutlet private weak var acceptButton: UIButton!
@IBOutlet private weak var declineButton: UIButton!
@IBOutlet private weak var tableViewHeightConstraint: NSLayoutConstraint!
// MARK: Private
private var viewModel: ServiceTermsModalScreenViewModelType!
@ -41,9 +51,8 @@ final class ServiceTermsModalScreenViewController: UIViewController {
private var activityPresenter: ActivityIndicatorPresenter!
private var policies: [MXLoginPolicyData] = []
/// Policies checked by the end user
private var checkedPolicies: Set<Int> = []
private var tableHeaderView: ServiceTermsModalTableHeaderView!
// MARK: - Setup
@ -60,8 +69,6 @@ final class ServiceTermsModalScreenViewController: UIViewController {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.title = VectorL10n.serviceTermsModalTitle
self.setupViews()
self.activityPresenter = ActivityIndicatorPresenter()
@ -75,6 +82,11 @@ final class ServiceTermsModalScreenViewController: UIViewController {
self.viewModel.process(viewAction: .load)
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
tableViewHeightConstraint.constant = max(Constants.minimumTableViewHeight, tableView.contentSize.height)
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return self.theme.statusBarStyle
}
@ -84,19 +96,30 @@ final class ServiceTermsModalScreenViewController: UIViewController {
private func update(theme: Theme) {
self.theme = theme
self.view.backgroundColor = theme.headerBackgroundColor
view.backgroundColor = theme.headerBackgroundColor
if let navigationBar = self.navigationController?.navigationBar {
if let navigationBar = navigationController?.navigationBar {
theme.applyStyle(onNavigationBar: navigationBar)
}
self.messageLabel.textColor = theme.textPrimaryColor
titleLabel.font = theme.fonts.bodySB
titleLabel.textColor = theme.colors.primaryContent
descriptionLabel.font = theme.fonts.body
descriptionLabel.textColor = theme.colors.secondaryContent
tableHeaderView.update(theme: theme)
footerLabel.font = theme.fonts.footnote.withSize(13)
footerLabel.textColor = theme.colors.secondaryContent
self.acceptButton.backgroundColor = theme.backgroundColor
theme.applyStyle(onButton: self.acceptButton)
acceptButton.titleLabel?.font = theme.fonts.body
acceptButton.setTitleColor(theme.colors.background, for: .normal)
acceptButton.backgroundColor = theme.colors.accent
theme.applyStyle(onButton: self.declineButton)
self.declineButton.setTitleColor(self.theme.warningColor, for: .normal)
declineButton.titleLabel?.font = theme.fonts.body
declineButton.setTitleColor(theme.warningColor, for: .normal)
self.refreshViews()
}
@ -110,40 +133,53 @@ final class ServiceTermsModalScreenViewController: UIViewController {
}
private func setupViews() {
let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in
self?.cancelButtonAction()
}
self.navigationItem.rightBarButtonItem = cancelBarButtonItem
self.setupTableView()
self.scrollView.keyboardDismissMode = .interactive
self.messageLabel.text = VectorL10n.serviceTermsModalMessage(self.viewModel.serviceUrl)
self.titleLabel.text = VectorL10n.serviceTermsModalTitleMessage
self.footerLabel.text = VectorL10n.serviceTermsModalFooter
self.tableHeaderView.serviceURLLabel.text = viewModel.serviceUrl
self.acceptButton.setTitle(VectorL10n.serviceTermsModalAcceptButton, for: .normal)
self.acceptButton.setTitle(VectorL10n.serviceTermsModalAcceptButton, for: .highlighted)
self.refreshAcceptButton()
self.acceptButton.layer.cornerRadius = 8
if self.viewModel.outOfContext
&& self.viewModel.serviceType == MXServiceTypeIdentityService {
self.title = VectorL10n.serviceTermsModalTitleIdentityServer
self.messageLabel.text = VectorL10n.serviceTermsModalMessageIdentityServer(self.viewModel.serviceUrl)
self.declineButton.setTitle(VectorL10n.serviceTermsModalDeclineButton, for: .normal)
self.declineButton.setTitle(VectorL10n.serviceTermsModalDeclineButton, for: .highlighted)
self.declineButton.setTitle(VectorL10n.serviceTermsModalDeclineButton, for: .normal)
self.declineButton.setTitle(VectorL10n.serviceTermsModalDeclineButton, for: .highlighted)
if self.viewModel.serviceType == MXServiceTypeIdentityService {
self.descriptionLabel.text = VectorL10n.serviceTermsModalDescriptionIdentityServer
self.tableHeaderView.titleLabel.text = VectorL10n.serviceTermsModalTableHeaderIdentityServer
self.imageView.image = Asset.Images.findYourContactsFacepile.image
} else {
self.declineButton.isHidden = true
self.descriptionLabel.text = VectorL10n.serviceTermsModalDescriptionIntegrationManager
self.tableHeaderView.titleLabel.text = VectorL10n.serviceTermsModalTableHeaderIntegrationManager
self.imageView.image = Asset.Images.integrationManagerIconpile.image
}
}
private func setupTableView() {
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.separatorStyle = .none
self.tableView.alwaysBounceVertical = false
self.tableView.backgroundColor = .clear
self.tableView.register(TableViewCellWithCheckBoxAndLabel.nib(), forCellReuseIdentifier: TableViewCellWithCheckBoxAndLabel.defaultReuseIdentifier())
guard let tableView = tableView else { return }
tableView.delegate = self
tableView.dataSource = self
tableView.separatorStyle = .none
tableView.alwaysBounceVertical = false
tableView.backgroundColor = .clear
tableView.register(TableViewCellWithCheckBoxAndLabel.nib(), forCellReuseIdentifier: TableViewCellWithCheckBoxAndLabel.defaultReuseIdentifier())
tableHeaderView = ServiceTermsModalTableHeaderView.instantiate()
tableHeaderView.delegate = self
tableView.tableHeaderView = tableHeaderView
tableView.addConstraint(NSLayoutConstraint(item: tableView,
attribute: .width,
relatedBy: .equal,
toItem: tableHeaderView,
attribute: .width,
multiplier: 1,
constant: 10))
}
private func render(viewState: ServiceTermsModalScreenViewState) {
@ -161,13 +197,14 @@ final class ServiceTermsModalScreenViewController: UIViewController {
private func renderLoading() {
self.activityPresenter.presentActivityIndicator(on: self.view, animated: true)
self.acceptButton.isEnabled = false
}
private func renderLoaded(policies: [MXLoginPolicyData], alreadyAcceptedPoliciesUrls: [String]) {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
self.policies = policies
self.updateCheckedPolicies(with: alreadyAcceptedPoliciesUrls)
self.acceptButton.isEnabled = true
self.refreshViews()
}
@ -187,21 +224,6 @@ final class ServiceTermsModalScreenViewController: UIViewController {
private func refreshViews() {
self.tableView.reloadData()
self.refreshAcceptButton()
}
private func refreshAcceptButton() {
// Enable the button only if the user has accepted all policies
self.acceptButton.isEnabled = (self.policies.count == self.checkedPolicies.count)
}
// Pre-check policies already accepted by the user
private func updateCheckedPolicies(with acceptedPoliciesUrls: [String]) {
for url in acceptedPoliciesUrls {
if let policyIndex = self.policies.firstIndex(where: { $0.url == url }) {
checkedPolicies.insert(policyIndex)
}
}
}
@ -214,37 +236,6 @@ final class ServiceTermsModalScreenViewController: UIViewController {
@IBAction private func declineButtonAction(_ sender: Any) {
self.viewModel.process(viewAction: .decline)
}
private func cancelButtonAction() {
self.viewModel.process(viewAction: .cancel)
}
@objc private func didTapCheckbox(sender: UITapGestureRecognizer) {
guard let policyIndex = sender.view?.tag else {
return
}
let isCheckBoxSelected: Bool
if self.checkedPolicies.contains(policyIndex) {
self.checkedPolicies.remove(policyIndex)
isCheckBoxSelected = false
} else {
checkedPolicies.insert(policyIndex)
isCheckBoxSelected = true
}
if let checkBoxImageView = sender.view as? UIImageView {
if isCheckBoxSelected {
checkBoxImageView.accessibilityTraits.insert(.selected)
} else {
checkBoxImageView.accessibilityTraits.remove(.selected)
}
}
self.refreshViews()
}
}
@ -259,72 +250,71 @@ extension ServiceTermsModalScreenViewController: ServiceTermsModalScreenViewMode
// MARK: - UITableViewDataSource
extension ServiceTermsModalScreenViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
// Use individual sections for each policy so the cells aren't grouped together.
return policies.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.policies.count
return 1
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
// Reduce the height between sections to only be the footer height value.
return CGFloat.leastNormalMagnitude
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
// Modify the footer size to reduce cell spacing.
return 8.0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: Constants.cellReuseIdentifier, for: indexPath)
guard let cell = tableView.dequeueReusableCell(withIdentifier: TableViewCellWithCheckBoxAndLabel.defaultReuseIdentifier(), for: indexPath) as? TableViewCellWithCheckBoxAndLabel else {
fatalError("\(String(describing: TableViewCellWithCheckBoxAndLabel.self)) should be registered")
}
let policy = policies[indexPath.section]
let policy = policies[indexPath.row]
let checked = checkedPolicies.contains(indexPath.row)
cell.label.attributedText = self.cellLabel(for: policy)
cell.label.font = .systemFont(ofSize: 15)
cell.isEnabled = checked
cell.textLabel?.text = policy.name
cell.textLabel?.textColor = theme.colors.primaryContent
cell.textLabel?.font = theme.fonts.body
cell.vc_setAccessoryDisclosureIndicator(withTheme: self.theme)
cell.backgroundColor = self.theme.backgroundColor
if let checkBox = cell.checkBox, checkBox.gestureRecognizers?.isEmpty ?? true {
let gesture: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapCheckbox))
gesture.numberOfTapsRequired = 1
gesture.numberOfTouchesRequired = 1
checkBox.isUserInteractionEnabled = true
checkBox.tag = indexPath.row
checkBox.addGestureRecognizer(gesture)
checkBox.isAccessibilityElement = true
checkBox.accessibilityTraits = .button
checkBox.accessibilityLabel = VectorL10n.accessibilityCheckboxLabel
checkBox.accessibilityHint = VectorL10n.serviceTermsModalPolicyCheckboxAccessibilityHint(policy.name)
}
cell.accessoryView?.tintColor = theme.colors.quarterlyContent
cell.backgroundColor = theme.colors.background
cell.selectionStyle = .default
return cell
}
func cellLabel(for policy: MXLoginPolicyData) -> NSAttributedString {
// TableViewCellWithCheckBoxAndLabel does not have a detailTextLabel
// Do it by hand
var labelDetail: String = ""
switch self.viewModel.serviceType {
case MXServiceTypeIdentityService:
labelDetail = VectorL10n.serviceTermsModalDescriptionForIdentityServer1
+ "\n"
+ VectorL10n.serviceTermsModalDescriptionForIdentityServer2
case MXServiceTypeIntegrationManager:
labelDetail = VectorL10n.serviceTermsModalDescriptionForIntegrationManager
default: break
}
let label = NSMutableAttributedString(string: policy.name,
attributes: [.foregroundColor: theme.textPrimaryColor])
label.append(NSAttributedString(string: "\n"))
label.append(NSAttributedString(string: labelDetail,
attributes: [.foregroundColor: theme.textSecondaryColor]))
return label
}
}
// MARK: - UITableViewDelegate
extension ServiceTermsModalScreenViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let policy = policies[indexPath.row]
self.viewModel.process(viewAction: .display(policy))
let policy = policies[indexPath.section]
viewModel.process(viewAction: .display(policy))
tableView.deselectRow(at: indexPath, animated: true)
}
}
// MARK: - ServiceTermsModalTableHeaderViewDelegate
extension ServiceTermsModalScreenViewController: ServiceTermsModalTableHeaderViewDelegate {
func tableHeaderViewDidTapInformationButton() {
let title: String
let message: String
if viewModel.serviceType == MXServiceTypeIdentityService {
title = VectorL10n.serviceTermsModalInformationTitleIdentityServer
message = VectorL10n.serviceTermsModalInformationDescriptionIdentityServer
} else {
title = VectorL10n.serviceTermsModalInformationTitleIntegrationManager
message = VectorL10n.serviceTermsModalInformationDescriptionIntegrationManager
}
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: Bundle.mxk_localizedString(forKey: "ok"), style: .default))
present(alertController, animated: true)
}
}

View file

@ -21,7 +21,6 @@ import Foundation
final class ServiceTermsModalScreenViewModel: ServiceTermsModalScreenViewModelType {
// MARK: - Properties
let outOfContext: Bool
// MARK: Private
@ -43,9 +42,8 @@ final class ServiceTermsModalScreenViewModel: ServiceTermsModalScreenViewModelTy
// MARK: - Setup
init(serviceTerms: MXServiceTerms, outOfContext: Bool) {
init(serviceTerms: MXServiceTerms) {
self.serviceTerms = serviceTerms
self.outOfContext = outOfContext
}
// MARK: - Public
@ -60,8 +58,6 @@ final class ServiceTermsModalScreenViewModel: ServiceTermsModalScreenViewModelTy
self.acceptTerms()
case .decline:
self.coordinatorDelegate?.serviceTermsModalScreenViewModelDidDecline(self)
case .cancel:
self.coordinatorDelegate?.serviceTermsModalScreenViewModelDidCancel(self)
}
}
@ -99,6 +95,14 @@ final class ServiceTermsModalScreenViewModel: ServiceTermsModalScreenViewModelTy
return
}
self.update(viewState: .accepted)
// Send a notification to update the identity service immediately.
if self.serviceTerms.serviceType == MXServiceTypeIdentityService {
let userInfo = [MXIdentityServiceNotificationIdentityServerKey: self.serviceTerms.baseUrl]
NotificationCenter.default.post(name: .MXIdentityServiceTermsAccepted, object: nil, userInfo: userInfo)
}
// Notify the delegate.
self.coordinatorDelegate?.serviceTermsModalScreenViewModelDidAccept(self)
}, failure: { [weak self] (error) in

View file

@ -26,7 +26,6 @@ protocol ServiceTermsModalScreenViewModelCoordinatorDelegate: AnyObject {
func serviceTermsModalScreenViewModel(_ coordinator: ServiceTermsModalScreenViewModelType, displayPolicy policy: MXLoginPolicyData)
func serviceTermsModalScreenViewModelDidAccept(_ viewModel: ServiceTermsModalScreenViewModelType)
func serviceTermsModalScreenViewModelDidDecline(_ viewModel: ServiceTermsModalScreenViewModelType)
func serviceTermsModalScreenViewModelDidCancel(_ viewModel: ServiceTermsModalScreenViewModelType)
}
/// Protocol describing the view model used by `ServiceTermsModalScreenViewController`
@ -34,9 +33,6 @@ protocol ServiceTermsModalScreenViewModelType {
var serviceUrl: String { get }
var serviceType: MXServiceType { get }
/// If true, terms are displayed out of a context of a flow (like a background 3pids lookup)
/// In this case, the wording needs to provide more information about the intent
var outOfContext: Bool { get }
var policies: [MXLoginPolicyData]? { get set }
var alreadyAcceptedPoliciesUrls: [String] { get set }

View file

@ -0,0 +1,56 @@
//
// 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
import Reusable
protocol ServiceTermsModalTableHeaderViewDelegate: AnyObject {
func tableHeaderViewDidTapInformationButton()
}
class ServiceTermsModalTableHeaderView: UIView, NibLoadable, Themable {
// MARK: - Properties
weak var delegate: ServiceTermsModalTableHeaderViewDelegate?
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var serviceURLLabel: UILabel!
// MARK: - Setup
static func instantiate() -> Self {
let view = Self.loadFromNib()
view.translatesAutoresizingMaskIntoConstraints = false
view.update(theme: ThemeService.shared().theme)
return view
}
func update(theme: Theme) {
titleLabel.font = theme.fonts.footnote
titleLabel.textColor = theme.colors.secondaryContent
serviceURLLabel.font = theme.fonts.callout
serviceURLLabel.textColor = theme.colors.secondaryContent
}
// MARK: - Action
@IBAction private func buttonAction(_ sender: Any) {
delegate?.tableHeaderViewDidTapInformationButton()
}
}

View file

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB" customClass="ServiceTermsModalTableHeaderView" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="328" height="72"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Server Terms" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="nQB-Sg-E1U">
<rect key="frame" x="16" y="0.0" width="80.5" height="24"/>
<constraints>
<constraint firstAttribute="height" constant="24" id="Lab-Gt-CFg"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="13"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="https://domain.com" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="nYB-Yu-HV9">
<rect key="frame" x="16" y="24" width="141" height="36"/>
<constraints>
<constraint firstAttribute="height" constant="36" id="ggs-su-fvA"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ztE-u7-qV6">
<rect key="frame" x="292" y="1" width="20" height="22"/>
<state key="normal" image="information_button"/>
<connections>
<action selector="buttonAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="EeS-ya-xp5"/>
</connections>
</button>
</subviews>
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="nYB-Yu-HV9" secondAttribute="bottom" constant="12" id="1MG-vA-cVG"/>
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="ztE-u7-qV6" secondAttribute="trailing" constant="16" id="C1f-Ex-0Ip"/>
<constraint firstItem="ztE-u7-qV6" firstAttribute="centerY" secondItem="nQB-Sg-E1U" secondAttribute="centerY" id="C86-gf-hip"/>
<constraint firstItem="ztE-u7-qV6" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="nQB-Sg-E1U" secondAttribute="trailing" constant="8" symbolic="YES" id="Gtr-PA-pv5"/>
<constraint firstItem="nQB-Sg-E1U" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="16" id="Uci-Xz-AGE"/>
<constraint firstItem="nYB-Yu-HV9" firstAttribute="top" secondItem="nQB-Sg-E1U" secondAttribute="bottom" id="VCK-IC-YUJ"/>
<constraint firstItem="nYB-Yu-HV9" firstAttribute="leading" secondItem="nQB-Sg-E1U" secondAttribute="leading" id="epR-po-phu"/>
<constraint firstItem="nQB-Sg-E1U" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="mOs-ee-5dI"/>
</constraints>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<connections>
<outlet property="serviceURLLabel" destination="nYB-Yu-HV9" id="2dD-hK-MjJ"/>
<outlet property="titleLabel" destination="nQB-Sg-E1U" id="PfB-Sr-6fE"/>
</connections>
<point key="canvasLocation" x="40.579710144927539" y="37.834821428571423"/>
</view>
</objects>
<resources>
<image name="information_button" width="20" height="20"/>
</resources>
</document>

View file

@ -19,7 +19,7 @@
import UIKit
@objcMembers
final class ServiceTermsModalCoordinator: ServiceTermsModalCoordinatorType {
final class ServiceTermsModalCoordinator: NSObject, ServiceTermsModalCoordinatorType {
// MARK: - Properties
@ -28,7 +28,6 @@ final class ServiceTermsModalCoordinator: ServiceTermsModalCoordinatorType {
private let navigationRouter: NavigationRouterType
private let session: MXSession
private let serviceTerms: MXServiceTerms
private let outOfContext: Bool
// MARK: Public
@ -38,11 +37,10 @@ final class ServiceTermsModalCoordinator: ServiceTermsModalCoordinatorType {
weak var delegate: ServiceTermsModalCoordinatorDelegate?
// MARK: - Setup
init(session: MXSession, baseUrl: String, serviceType: MXServiceType, outOfContext: Bool, accessToken: String) {
init(session: MXSession, baseUrl: String, serviceType: MXServiceType, accessToken: String) {
self.navigationRouter = NavigationRouter(navigationController: RiotNavigationController())
self.session = session
self.serviceTerms = MXServiceTerms(baseUrl: baseUrl, serviceType: serviceType, matrixSession: session, accessToken: accessToken)
self.outOfContext = outOfContext
}
// MARK: - Public methods
@ -53,6 +51,8 @@ final class ServiceTermsModalCoordinator: ServiceTermsModalCoordinatorType {
rootCoordinator.start()
self.add(childCoordinator: rootCoordinator)
self.toPresentable().presentationController?.delegate = self
self.navigationRouter.setRootModule(rootCoordinator)
}
@ -64,7 +64,7 @@ final class ServiceTermsModalCoordinator: ServiceTermsModalCoordinatorType {
// MARK: - Private methods
private func createServiceTermsModalLoadTermsScreenCoordinator() -> ServiceTermsModalScreenCoordinator {
let coordinator = ServiceTermsModalScreenCoordinator(serviceTerms: self.serviceTerms, outOfContext: self.outOfContext)
let coordinator = ServiceTermsModalScreenCoordinator(serviceTerms: self.serviceTerms)
coordinator.delegate = self
return coordinator
}
@ -87,12 +87,29 @@ final class ServiceTermsModalCoordinator: ServiceTermsModalCoordinatorType {
@objc private func didTapCancelOnPolicyScreen() {
self.removePolicyScreen()
}
/// Removes the identity server from the `MXSession` and it's account data.
private func disableIdentityServer() {
MXLog.debug("[ServiceTermsModalCoordinator] IS Terms: User has declined the use of the default IS.")
// The user does not want to use the proposed IS.
// Disable IS feature on user's account
session.setIdentityServer(nil, andAccessToken: nil)
session.setAccountDataIdentityServer(nil, success: nil) { error in
guard let errorDescription = error?.localizedDescription else { return }
MXLog.error("[ServiceTermsModalCoordinator] IS Terms: Error: \(errorDescription)")
}
}
}
// MARK: - ServiceTermsModalLoadTermsScreenCoordinatorDelegate
extension ServiceTermsModalCoordinator: ServiceTermsModalScreenCoordinatorDelegate {
func serviceTermsModalScreenCoordinatorDidAccept(_ coordinator: ServiceTermsModalScreenCoordinatorType) {
if serviceTerms.serviceType == MXServiceTypeIdentityService {
Analytics.sharedInstance().trackValue(1, category: MXKAnalyticsCategory.contacts.rawValue, name: AnalyticsContactsIdentityServerAccepted)
}
self.delegate?.serviceTermsModalCoordinatorDidAccept(self)
}
@ -101,10 +118,22 @@ extension ServiceTermsModalCoordinator: ServiceTermsModalScreenCoordinatorDelega
}
func serviceTermsModalScreenCoordinatorDidDecline(_ coordinator: ServiceTermsModalScreenCoordinatorType) {
if serviceTerms.serviceType == MXServiceTypeIdentityService {
Analytics.sharedInstance().trackValue(1, category: MXKAnalyticsCategory.contacts.rawValue, name: AnalyticsContactsIdentityServerAccepted)
disableIdentityServer()
}
self.delegate?.serviceTermsModalCoordinatorDidDecline(self)
}
}
func serviceTermsModalScreenCoordinatorDidCancel(_ coordinator: ServiceTermsModalScreenCoordinatorType) {
self.delegate?.serviceTermsModalCoordinatorDidCancel(self)
// MARK: - UIAdaptivePresentationControllerDelegate
extension ServiceTermsModalCoordinator: UIAdaptivePresentationControllerDelegate {
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
if serviceTerms.serviceType == MXServiceTypeIdentityService {
Analytics.sharedInstance().trackValue(0, category: MXKAnalyticsCategory.contacts.rawValue, name: AnalyticsContactsIdentityServerAccepted)
}
self.delegate?.serviceTermsModalCoordinatorDidDismissInteractively(self)
}
}

View file

@ -21,7 +21,7 @@ import Foundation
@objc protocol ServiceTermsModalCoordinatorBridgePresenterDelegate {
func serviceTermsModalCoordinatorBridgePresenterDelegateDidAccept(_ coordinatorBridgePresenter: ServiceTermsModalCoordinatorBridgePresenter)
func serviceTermsModalCoordinatorBridgePresenterDelegateDidDecline(_ coordinatorBridgePresenter: ServiceTermsModalCoordinatorBridgePresenter, session: MXSession)
func serviceTermsModalCoordinatorBridgePresenterDelegateDidCancel(_ coordinatorBridgePresenter: ServiceTermsModalCoordinatorBridgePresenter)
func serviceTermsModalCoordinatorBridgePresenterDelegateDidClose(_ coordinatorBridgePresenter: ServiceTermsModalCoordinatorBridgePresenter)
}
/// ServiceTermsModalCoordinatorBridgePresenter enables to start ServiceTermsModalCoordinator from a view controller.
@ -36,7 +36,6 @@ final class ServiceTermsModalCoordinatorBridgePresenter: NSObject {
private let session: MXSession
private let baseUrl: String
private let serviceType: MXServiceType
private let outOfContext: Bool
private let accessToken: String
private var coordinator: ServiceTermsModalCoordinator?
@ -50,11 +49,10 @@ final class ServiceTermsModalCoordinatorBridgePresenter: NSObject {
// MARK: - Setup
init(session: MXSession, baseUrl: String, serviceType: MXServiceType, outOfContext: Bool = false, accessToken: String) {
init(session: MXSession, baseUrl: String, serviceType: MXServiceType, accessToken: String) {
self.session = session
self.baseUrl = baseUrl
self.serviceType = serviceType
self.outOfContext = outOfContext
self.accessToken = accessToken
super.init()
}
@ -67,10 +65,9 @@ final class ServiceTermsModalCoordinatorBridgePresenter: NSObject {
// }
func present(from viewController: UIViewController, animated: Bool) {
let serviceTermsModalCoordinator = ServiceTermsModalCoordinator(session: self.session, baseUrl: self.baseUrl, serviceType: self.serviceType, outOfContext: self.outOfContext, accessToken: accessToken)
let serviceTermsModalCoordinator = ServiceTermsModalCoordinator(session: self.session, baseUrl: self.baseUrl, serviceType: self.serviceType, accessToken: accessToken)
serviceTermsModalCoordinator.delegate = self
let presentable = serviceTermsModalCoordinator.toPresentable()
presentable.presentationController?.delegate = self
viewController.present(presentable, animated: animated, completion: nil)
serviceTermsModalCoordinator.start()
@ -100,36 +97,13 @@ extension ServiceTermsModalCoordinatorBridgePresenter: ServiceTermsModalCoordina
func serviceTermsModalCoordinatorDidAccept(_ coordinator: ServiceTermsModalCoordinatorType) {
self.delegate?.serviceTermsModalCoordinatorBridgePresenterDelegateDidAccept(self)
if serviceType == MXServiceTypeIdentityService {
Analytics.sharedInstance().trackValue(1, category: kMXKAnalyticsContactsCategory, name: AnalyticsContactsIdentityServerAccepted)
}
}
func serviceTermsModalCoordinatorDidDecline(_ coordinator: ServiceTermsModalCoordinatorType) {
self.delegate?.serviceTermsModalCoordinatorBridgePresenterDelegateDidDecline(self, session: self.session)
if serviceType == MXServiceTypeIdentityService {
Analytics.sharedInstance().trackValue(0, category: kMXKAnalyticsContactsCategory, name: AnalyticsContactsIdentityServerAccepted)
}
}
func serviceTermsModalCoordinatorDidCancel(_ coordinator: ServiceTermsModalCoordinatorType) {
self.delegate?.serviceTermsModalCoordinatorBridgePresenterDelegateDidCancel(self)
if serviceType == MXServiceTypeIdentityService {
Analytics.sharedInstance().trackValue(0, category: kMXKAnalyticsContactsCategory, name: AnalyticsContactsIdentityServerAccepted)
}
}
}
// MARK: - UIAdaptivePresentationControllerDelegate
extension ServiceTermsModalCoordinatorBridgePresenter: UIAdaptivePresentationControllerDelegate {
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
self.delegate?.serviceTermsModalCoordinatorBridgePresenterDelegateDidCancel(self)
if serviceType == MXServiceTypeIdentityService {
Analytics.sharedInstance().trackValue(0, category: kMXKAnalyticsContactsCategory, name: AnalyticsContactsIdentityServerAccepted)
}
func serviceTermsModalCoordinatorDidDismissInteractively(_ coordinator: ServiceTermsModalCoordinatorType) {
self.delegate?.serviceTermsModalCoordinatorBridgePresenterDelegateDidClose(self)
}
}

View file

@ -21,7 +21,7 @@ import Foundation
protocol ServiceTermsModalCoordinatorDelegate: AnyObject {
func serviceTermsModalCoordinatorDidAccept(_ coordinator: ServiceTermsModalCoordinatorType)
func serviceTermsModalCoordinatorDidDecline(_ coordinator: ServiceTermsModalCoordinatorType)
func serviceTermsModalCoordinatorDidCancel(_ coordinator: ServiceTermsModalCoordinatorType)
func serviceTermsModalCoordinatorDidDismissInteractively(_ coordinator: ServiceTermsModalCoordinatorType)
}
/// `ServiceTermsModalCoordinatorType` is a protocol describing a Coordinator that handle keybackup setup navigation flow.

View file

@ -31,7 +31,7 @@ final class SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter: NSObject
private let threePid: MX3PID
private var coordinator: SettingsDiscoveryThreePidDetailsCoordinator?
private var router: NavigationRouter?
private var router: NavigationRouterType?
// MARK: - Setup
@ -45,7 +45,7 @@ final class SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter: NSObject
func push(from navigationController: UINavigationController, animated: Bool, popCompletion: (() -> Void)?) {
let router = NavigationRouter(navigationController: navigationController)
let router = NavigationRouterStore.shared.navigationRouter(for: navigationController)
let settingsDiscoveryThreePidDetailsCoordinator = SettingsDiscoveryThreePidDetailsCoordinator(session: self.session, threePid: self.threePid)

View file

@ -32,7 +32,7 @@ final class SettingsIdentityServerCoordinatorBridgePresenter: NSObject {
// MARK: Private
private let session: MXSession
private var router: NavigationRouter?
private var router: NavigationRouterType?
private var coordinator: SettingsIdentityServerCoordinator?
// MARK: Public
@ -50,7 +50,7 @@ final class SettingsIdentityServerCoordinatorBridgePresenter: NSObject {
func push(from navigationController: UINavigationController, animated: Bool, popCompletion: (() -> Void)?) {
let router = NavigationRouter(navigationController: navigationController)
let router = NavigationRouterStore.shared.navigationRouter(for: navigationController)
let settingsIdentityServerCoordinator = SettingsIdentityServerCoordinator(session: self.session)

View file

@ -395,7 +395,7 @@ extension SettingsIdentityServerViewController: ServiceTermsModalCoordinatorBrid
self.hideTerms(accepted: false)
}
func serviceTermsModalCoordinatorBridgePresenterDelegateDidCancel(_ coordinatorBridgePresenter: ServiceTermsModalCoordinatorBridgePresenter) {
func serviceTermsModalCoordinatorBridgePresenterDelegateDidClose(_ coordinatorBridgePresenter: ServiceTermsModalCoordinatorBridgePresenter) {
self.hideTerms(accepted: false)
}
}

View file

@ -127,7 +127,8 @@ enum
enum {
LOCAL_CONTACTS_SYNC_INDEX,
LOCAL_CONTACTS_PHONEBOOK_COUNTRY_INDEX
LOCAL_CONTACTS_PHONEBOOK_COUNTRY_INDEX,
LOCAL_CONTACTS_SYNC_DESCRIPTION_INDEX
};
enum
@ -179,6 +180,7 @@ SignOutAlertPresenterDelegate,
SingleImagePickerPresenterDelegate,
SettingsDiscoveryTableViewSectionDelegate, SettingsDiscoveryViewModelCoordinatorDelegate,
SettingsIdentityServerCoordinatorBridgePresenterDelegate,
ServiceTermsModalCoordinatorBridgePresenterDelegate,
TableViewSectionsDelegate>
{
// Current alert (if any).
@ -280,6 +282,8 @@ TableViewSectionsDelegate>
@property (nonatomic, strong) UserInteractiveAuthenticationService *userInteractiveAuthenticationService;
@property (nonatomic, strong) ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter;
@end
@implementation SettingsViewController
@ -462,7 +466,12 @@ TableViewSectionsDelegate>
{
[sectionLocalContacts addRowWithTag:LOCAL_CONTACTS_PHONEBOOK_COUNTRY_INDEX];
}
sectionLocalContacts.headerTitle = [VectorL10n settingsContacts];
else
{
[sectionLocalContacts addRowWithTag:LOCAL_CONTACTS_SYNC_DESCRIPTION_INDEX];
}
NSString *headerTitle = UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPhone ? VectorL10n.settingsPhoneContacts : VectorL10n.settingsContacts;
sectionLocalContacts.headerTitle = headerTitle;
[tmpSections addObject:sectionLocalContacts];
}
@ -2196,7 +2205,7 @@ TableViewSectionsDelegate>
MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath];
labelAndSwitchCell.mxkLabel.numberOfLines = 0;
labelAndSwitchCell.mxkLabel.text = [VectorL10n settingsContactsDiscoverMatrixUsers];
labelAndSwitchCell.mxkLabel.text = VectorL10n.settingsContactsEnableSync;
labelAndSwitchCell.mxkSwitch.on = [MXKAppSettings standardAppSettings].syncLocalContacts;
labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor;
labelAndSwitchCell.mxkSwitch.enabled = YES;
@ -2224,6 +2233,15 @@ TableViewSectionsDelegate>
[cell vc_setAccessoryDisclosureIndicatorWithCurrentTheme];
cell.selectionStyle = UITableViewCellSelectionStyleDefault;
}
else if (row == LOCAL_CONTACTS_SYNC_DESCRIPTION_INDEX)
{
MXKTableViewCell *descriptionCell = [self getDefaultTableViewCell:tableView];
descriptionCell.textLabel.text = VectorL10n.settingsContactsEnableSyncDescription;
descriptionCell.textLabel.numberOfLines = 0;
descriptionCell.selectionStyle = UITableViewCellSelectionStyleNone;
cell = descriptionCell;
}
}
else if (section == SECTION_TAG_ADVANCED)
{
@ -3102,17 +3120,51 @@ TableViewSectionsDelegate>
{
if (sender.on)
{
[MXKContactManager requestUserConfirmationForLocalContactsSyncInViewController:self completionHandler:^(BOOL granted) {
[MXKAppSettings standardAppSettings].syncLocalContacts = granted;
// First check if the service terms have already been accepted
MXSession *session = self.mxSessions.firstObject;
if (session.identityService.areAllTermsAgreed)
{
// If they have we only require local contacts access.
[self checkAccessForContacts];
}
else
{
MXWeakify(self);
[self updateSections];
}];
// The preparation can take some time so indicate this to the user
[self startActivityIndicator];
[session prepareIdentityServiceForTermsWithDefault:RiotSettings.shared.identityServerUrlString
success:^(MXSession *session, NSString *baseURL, NSString *accessToken) {
MXStrongifyAndReturnIfNil(self);
[self stopActivityIndicator];
// Present the terms of the identity server.
[self presentIdentityServerTermsWithSession:session baseURL:baseURL andAccessToken:accessToken];
} failure:^(NSError *error) {
MXStrongifyAndReturnIfNil(self);
[self stopActivityIndicator];
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:VectorL10n.findYourContactsIdentityServiceError
message:nil
preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:MatrixKitL10n.ok
style:UIAlertActionStyleDefault
handler:nil]];
[self presentViewController:alertController animated:YES completion:nil];
[MXKAppSettings standardAppSettings].syncLocalContacts = NO;
[self updateSections];
}];
}
}
else
{
[MXKAppSettings standardAppSettings].syncLocalContacts = NO;
[self updateSections];
}
}
@ -4457,6 +4509,28 @@ TableViewSectionsDelegate>
}
}
#pragma mark - Local Contacts Sync
- (void)checkAccessForContacts
{
MXWeakify(self);
// Check for contacts access, showing a pop-up if necessary.
[MXKTools checkAccessForContacts:VectorL10n.contactsAddressBookPermissionDeniedAlertTitle
withManualChangeMessage:VectorL10n.contactsAddressBookPermissionDeniedAlertMessage
showPopUpInViewController:self
completionHandler:^(BOOL granted) {
MXStrongifyAndReturnIfNil(self);
if (granted)
{
// When granted, local contacts can be shown.
[MXKAppSettings standardAppSettings].syncLocalContacts = YES;
[self updateSections];
}
}];
}
#pragma mark - Identity server
@ -4468,7 +4542,25 @@ TableViewSectionsDelegate>
identityServerSettingsCoordinatorBridgePresenter.delegate = self;
}
#pragma mark - SettingsIdentityServerCoordinatorBridgePresenterDelegate
- (void)presentIdentityServerTermsWithSession:(MXSession*)mxSession baseURL:(NSString*)baseURL andAccessToken:(NSString*)accessToken
{
if (!mxSession || !baseURL || !accessToken || self.serviceTermsModalCoordinatorBridgePresenter.isPresenting)
{
return;
}
ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter = [[ServiceTermsModalCoordinatorBridgePresenter alloc] initWithSession:mxSession
baseUrl:baseURL
serviceType:MXServiceTypeIdentityService
accessToken:accessToken];
serviceTermsModalCoordinatorBridgePresenter.delegate = self;
[serviceTermsModalCoordinatorBridgePresenter presentFrom:self animated:YES];
self.serviceTermsModalCoordinatorBridgePresenter = serviceTermsModalCoordinatorBridgePresenter;
}
#pragma mark SettingsIdentityServerCoordinatorBridgePresenterDelegate
- (void)settingsIdentityServerCoordinatorBridgePresenterDelegateDidComplete:(SettingsIdentityServerCoordinatorBridgePresenter *)coordinatorBridgePresenter
{
@ -4476,6 +4568,31 @@ TableViewSectionsDelegate>
[self refreshSettings];
}
#pragma mark ServiceTermsModalCoordinatorBridgePresenterDelegate
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidAccept:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter
{
[coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
[self checkAccessForContacts];
}];
self.serviceTermsModalCoordinatorBridgePresenter = nil;
}
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidDecline:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter session:(MXSession *)session
{
// Disable the contacts toggle as the terms weren't accepted.
[self updateSections];
[coordinatorBridgePresenter dismissWithAnimated:YES completion:nil];
self.serviceTermsModalCoordinatorBridgePresenter = nil;
}
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidClose:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter
{
[self updateSections]; // Disables the contacts toggle as the terms weren't accepted.
self.serviceTermsModalCoordinatorBridgePresenter = nil;
}
#pragma mark - TableViewSectionsDelegate
- (void)tableViewSectionsDidUpdateSections:(TableViewSections *)sections

View file

@ -32,6 +32,12 @@ class SplitViewCoordinatorParameters {
final class SplitViewCoordinator: NSObject, SplitViewCoordinatorType {
// MARK: - Constants
private enum Constants {
static let detailModulesCheckDelay: Double = 0.3
}
// MARK: - Properties
// MARK: Private
@ -42,6 +48,11 @@ final class SplitViewCoordinator: NSObject, SplitViewCoordinatorType {
private weak var masterPresentable: SplitViewMasterPresentable?
private var detailNavigationController: UINavigationController?
private var detailNavigationRouter: NavigationRouterType?
private var selectedNavigationRouter: NavigationRouterType? {
return self.masterPresentable?.selectedNavigationRouter
}
private weak var tabBarCoordinator: TabBarCoordinatorType?
@ -77,13 +88,17 @@ final class SplitViewCoordinator: NSObject, SplitViewCoordinatorType {
self.splitViewController.delegate = self
// Create primary controller
let tabBarCoordinator = self.createTabBarCoordinator()
tabBarCoordinator.delegate = self
tabBarCoordinator.splitViewMasterPresentableDelegate = self
tabBarCoordinator.start(with: spaceId)
let detailNavigationController = self.createDetailNavigationController()
// Create secondary controller
let placeholderDetailViewController = self.createPlaceholderDetailsViewController()
let detailNavigationController = RiotNavigationController(rootViewController: placeholderDetailViewController)
// Setup split view controller
self.splitViewController.viewControllers = [tabBarCoordinator.toPresentable(), detailNavigationController]
self.add(childCoordinator: tabBarCoordinator)
@ -91,8 +106,11 @@ final class SplitViewCoordinator: NSObject, SplitViewCoordinatorType {
self.tabBarCoordinator = tabBarCoordinator
self.masterPresentable = tabBarCoordinator
self.detailNavigationController = detailNavigationController
self.detailNavigationRouter = NavigationRouter(navigationController: detailNavigationController)
self.parameters.router.setRootModule(self.splitViewController)
self.registerNavigationRouterNotifications()
} else {
// Pop to home screen when selecting a new space
self.popToHome(animated: true) {
@ -105,7 +123,7 @@ final class SplitViewCoordinator: NSObject, SplitViewCoordinatorType {
func toPresentable() -> UIViewController {
return self.splitViewController
}
// TODO: Do not expose publicly this method
func restorePlaceholderDetails() {
// Be sure that the primary is then visible too.
@ -113,22 +131,14 @@ final class SplitViewCoordinator: NSObject, SplitViewCoordinatorType {
splitViewController.preferredDisplayMode = .allVisible
}
if splitViewController.viewControllers.count == 2 {
let mainViewController = splitViewController.viewControllers[0]
let emptyDetailsViewController = self.createPlaceholderDetailsViewController()
splitViewController.viewControllers = [mainViewController, emptyDetailsViewController]
}
self.resetDetailNavigationControllerWithPlaceholder(animated: false)
// Release the current selected item (room/contact/group...).
self.tabBarCoordinator?.releaseSelectedItems()
}
func popToHome(animated: Bool, completion: (() -> Void)?) {
if let secondNavController = self.detailNavigationController {
secondNavController.popToRootViewController(animated: animated)
}
self.resetDetailNavigationControllerWithPlaceholder(animated: animated)
// Force back to the main screen if this is not the one that is displayed
self.tabBarCoordinator?.popToHome(animated: animated, completion: completion)
@ -149,54 +159,196 @@ final class SplitViewCoordinator: NSObject, SplitViewCoordinatorType {
return tabBarCoordinator
}
private func createDetailNavigationController() -> UINavigationController {
let placeholderDetailViewController = self.createPlaceholderDetailsViewController()
let detailNavigationController = RiotNavigationController(rootViewController: placeholderDetailViewController)
return detailNavigationController
private func resetDetailNavigationControllerWithPlaceholder(animated: Bool) {
guard let detailNavigationRouter = self.detailNavigationRouter else {
return
}
// Check if placeholder is already shown
if detailNavigationRouter.modules.count == 1 && detailNavigationRouter.modules.last is PlaceholderDetailViewController {
return
}
// Set placeholder screen as root controller of detail navigation controller
let placeholderDetailsVC = self.createPlaceholderDetailsViewController()
detailNavigationRouter.setRootModule(placeholderDetailsVC, hideNavigationBar: false, animated: animated, popCompletion: nil)
}
private func isPlaceholderShown(from secondaryViewController: UIViewController) -> Bool {
if let detailNavigationController = secondaryViewController as? UINavigationController, let topViewController = detailNavigationController.viewControllers.last {
return topViewController is PlaceholderDetailViewController
} else {
return secondaryViewController is PlaceholderDetailViewController
}
}
private func releaseRoomDataSourceIfNeeded(for roomCoordinator: RoomCoordinatorProtocol) {
guard roomCoordinator.canReleaseRoomDataSource,
let session = roomCoordinator.mxSession,
let roomId = roomCoordinator.roomId else {
return
}
let existingRoomCoordinatorWithSameRoomId = self.detailModules.first { presentable -> Bool in
if let currentRoomCoordinator = presentable as? RoomCoordinatorProtocol {
return currentRoomCoordinator.roomId == roomCoordinator.roomId
}
return false
}
guard existingRoomCoordinatorWithSameRoomId == nil else {
MXLog.debug("[SplitViewCoordinator] Do not release RoomDataSource for room id \(roomId), another RoomCoordinator with same room id using it")
return
}
let dataSourceManager = MXKRoomDataSourceManager.sharedManager(forMatrixSession: session)
dataSourceManager?.closeRoomDataSource(withRoomId: roomId, forceClose: false)
}
private func registerNavigationRouterNotifications() {
NotificationCenter.default.addObserver(self, selector: #selector(navigationRouterDidPopViewController(_:)), name: NavigationRouter.didPopModule, object: nil)
}
@objc private func navigationRouterDidPopViewController(_ notification: Notification) {
guard let userInfo = notification.userInfo,
let navigationRouter = userInfo[NavigationRouter.NotificationUserInfoKey.navigationRouter] as? NavigationRouterType,
let poppedController = userInfo[NavigationRouter.NotificationUserInfoKey.viewController] as? UIViewController else {
return
}
// In our split view configuration is possible to have nested navigation controller (see https://blog.malcolmhall.com/2017/01/27/default-behaviour-of-uisplitviewcontroller-collapsesecondaryviewcontroller/)).
// When the split view controller has one column visible with the detail navigation controller nested inside the primary,
// check to see whether the primary navigation controller is popping the detail navigation controller.
// In this case the detail navigation controller will be popped but not its content. It means completions will not be called.
if navigationRouter === self.selectedNavigationRouter,
let poppedNavigationController = poppedController as? UINavigationController,
poppedNavigationController == self.detailNavigationController {
// Clear the detailNavigationRouter to trigger completions associated to each controllers
self.detailNavigationRouter?.popAllModules(animated: false)
}
if let poppedModule = userInfo[NavigationRouter.NotificationUserInfoKey.module] as? Presentable {
if let roomCoordinator = poppedModule as? RoomCoordinatorProtocol {
// If the RoomCoordinator view controller is popped from the detail navigation controller, check if the associated room data source should be released.
// If there is no other RoomCoordinator using the same data source, release it.
// A small delay is set to be sure navigation stack manipulation ended before checking the whole stack.
DispatchQueue.main.asyncAfter(deadline: .now() + Constants.detailModulesCheckDelay) {
self.releaseRoomDataSourceIfNeeded(for: roomCoordinator)
}
}
}
}
}
// MARK: - UISplitViewControllerDelegate
extension SplitViewCoordinator: UISplitViewControllerDelegate {
/// Provide the new secondary view controller for the split view interface.
/// This method returns the view controller to use as the secondary view controller in the expanded split view interface (when 2 column are visible).
/// Sample case: large iPhone goes from portrait to landsacpe.
func splitViewController(_ splitViewController: UISplitViewController, separateSecondaryFrom primaryViewController: UIViewController) -> UIViewController? {
if let detailViewController = self.masterPresentable?.secondViewControllerWhenSeparatedFromPrimary() {
return detailViewController
// If the primary root controller of the UISplitViewController is a UINavigationController,
// it's possible to have nested navigation controllers due to private property `_allowNestedNavigationControllers` set to true (https://blog.malcolmhall.com/2017/01/27/default-behaviour-of-uisplitviewcontroller-collapsesecondaryviewcontroller/).
// So if the top view controller of the primary navigation controller is a navigation controller and it corresponds to the existing `detailNavigationController` instance.
// Return `detailNavigationController` as is, it will be used as the secondary view of the split view controller.
if let topMostNavigationController = self.selectedNavigationRouter?.modules.last as? UINavigationController, topMostNavigationController == self.detailNavigationController {
return self.detailNavigationController
}
// Else return the default empty details view controller from the storyboard.
// Else return the default empty details view controller.
// Be sure that the primary is then visible too.
if splitViewController.displayMode == .primaryHidden {
splitViewController.preferredDisplayMode = .allVisible
}
return self.createPlaceholderDetailsViewController()
// Restore detail navigation controller with placeholder as root
self.resetDetailNavigationControllerWithPlaceholder(animated: false)
// Return up to date detail navigation controller
// In any cases `detailNavigationController` will be used as secondary view of the split view controller.
return self.detailNavigationController
}
/// Adjust the primary view controller and incorporate the secondary view controller into the collapsed interface if needed.
/// Return false to let the split view controller try to incorporate the secondary view controller's content into the collapsed interface,
/// or true to indicate that you do not want the split view controller to do anything with the secondary view controller.
/// Sample case: large iPhone goes from landscape to portrait.
func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
return self.masterPresentable?.collapseDetailViewController ?? false
// If the secondary view is the placeholder screen do not merge the secondary into the primary.
// Note: In this case, the secondaryViewController will be automatically discarded.
if self.isPlaceholderShown(from: secondaryViewController) {
return true
}
// Return false to let the split view controller try to incorporate the secondary view controller's content into the collapsed interface.
// If the primary root controller of a UISplitViewController is a UINavigationController,
// it's possible to have nested navigation controllers due to private property `_allowNestedNavigationControllers` set to true (https://blog.malcolmhall.com/2017/01/27/default-behaviour-of-uisplitviewcontroller-collapsesecondaryviewcontroller/).
// So in this case returning false here will push the `detailNavigationController` on top of the `primaryNavigationController`.
// Sample primary view stack:
// primaryNavigationController[
// MasterTabBarController,
// detailNavigationController[RoomViewController, RoomInfoListViewController]]
// Note that normally pushing a navigation controller on top of a navigation controller don't work.
return false
}
}
/// MARK: - UINavigationControllerDelegate
// MARK: - TabBarCoordinatorDelegate
extension SplitViewCoordinator: TabBarCoordinatorDelegate {
func tabBarCoordinatorDidCompleteAuthentication(_ coordinator: TabBarCoordinatorType) {
self.delegate?.splitViewCoordinatorDidCompleteAuthentication(self)
}
}
/// MARK: - SplitViewMasterPresentableDelegate
// MARK: - SplitViewMasterPresentableDelegate
extension SplitViewCoordinator: SplitViewMasterPresentableDelegate {
func splitViewMasterPresentable(_ presentable: Presentable, wantsToDisplay detailPresentable: Presentable) {
MXLog.debug("[SplitViewCoordinator] splitViewMasterPresentable: \(presentable) wantsToDisplay detailPresentable: \(detailPresentable)")
var detailModules: [Presentable] {
return self.detailNavigationRouter?.modules ?? []
}
func splitViewMasterPresentable(_ presentable: Presentable, wantsToReplaceDetailWith detailPresentable: Presentable, popCompletion: (() -> Void)?) {
MXLog.debug("[SplitViewCoordinator] splitViewMasterPresentable: \(presentable) wantsToReplaceDetailWith detailPresentable: \(detailPresentable)")
guard let detailNavigationController = self.detailNavigationController else {
MXLog.debug("[SplitViewCoordinator] splitViewMasterPresentable: Failed to display because detailNavigationController is nil")
return
}
detailNavigationController.viewControllers = [detailPresentable.toPresentable()]
let detailController = detailPresentable.toPresentable()
// Reset the detail navigation controller with the given detail controller
self.detailNavigationRouter?.setRootModule(detailPresentable, popCompletion: popCompletion)
// This will call first UISplitViewControllerDelegate method: `splitViewController(_:showDetail:sender:)`, if implemented, to give the opportunity to customise `UISplitViewController.showDetailViewController(:sender:)` behavior.
// - If the split view controller is collpased (one column visible):
// The `detailNavigationController` will be pushed on top of the primary navigation controller.
// In fact if the primary root controller of a UISplitViewController is a UINavigationController,
// it's possible to have nested navigation controllers due to private property `_allowNestedNavigationControllers` set to true (https://blog.malcolmhall.com/2017/01/27/default-behaviour-of-uisplitviewcontroller-collapsesecondaryviewcontroller/).
// - Else if the split view controller is not collpased (two column visible)
// It will set the `detailNavigationController` as the secondary view of the split view controller
self.splitViewController.showDetailViewController(detailNavigationController, sender: nil)
// Set leftBarButtonItem with split view display mode button if there is no leftBarButtonItem defined
detailController.vc_setupDisplayModeLeftBarButtonItemIfNeeded()
}
func splitViewMasterPresentable(_ presentable: Presentable, wantsToStack detailPresentable: Presentable, popCompletion: (() -> Void)?) {
guard let detailNavigationRouter = self.detailNavigationRouter else {
MXLog.debug("[SplitViewCoordinator] Failed to stack \(detailPresentable) because detailNavigationRouter is nil")
return
}
detailNavigationRouter.push(detailPresentable, animated: true, popCompletion: popCompletion)
}
}

View file

@ -17,17 +17,30 @@
import UIKit
protocol SplitViewMasterPresentableDelegate: AnyObject {
func splitViewMasterPresentable(_ presentable: Presentable, wantsToDisplay detailPresentable: Presentable)
/// Detail items from the split view
var detailModules: [Presentable] { get }
/// Replace split view detail with the given detailPresentable
func splitViewMasterPresentable(_ presentable: Presentable, wantsToReplaceDetailWith detailPresentable: Presentable, popCompletion: (() -> Void)?)
/// Stack the detailPresentable on the existing split view detail stack
func splitViewMasterPresentable(_ presentable: Presentable, wantsToStack detailPresentable: Presentable, popCompletion: (() -> Void)?)
}
/// `SplitViewMasterPresentableDelegate` default implementation
extension SplitViewMasterPresentableDelegate {
func splitViewMasterPresentable(_ presentable: Presentable, wantsToDisplay detailPresentable: Presentable) {
splitViewMasterPresentable(presentable, wantsToReplaceDetailWith: detailPresentable, popCompletion: nil)
}
}
/// Protocol used by the master view presentable of a UISplitViewController
protocol SplitViewMasterPresentable: AnyObject, Presentable {
var splitViewMasterPresentableDelegate: SplitViewMasterPresentableDelegate? { get set }
var splitViewMasterPresentableDelegate: SplitViewMasterPresentableDelegate? { get set }
/// Indicate true if the detail can be collapsed
var collapseDetailViewController: Bool { get }
/// Return the detail view controller to display when the detail is separated from the master view controller
func secondViewControllerWhenSeparatedFromPrimary() -> UIViewController?
/// Return the currently selected and visible NavigationRouter
/// It will be used to manage detail controllers
var selectedNavigationRouter: NavigationRouterType? { get }
}

View file

@ -50,27 +50,28 @@ final class InviteFriendsHeaderView: UIView, NibLoadable, Themable {
override func awakeFromNib() {
super.awakeFromNib()
self.button.setTitle(VectorL10n.inviteFriendsAction(BuildSettings.bundleDisplayName), for: .normal)
self.button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
button.setTitle(VectorL10n.inviteFriendsAction(BuildSettings.bundleDisplayName), for: .normal)
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
button.layer.cornerRadius = 8
button.layer.borderWidth = 2
}
// MARK: - Public
func update(theme: Theme) {
self.backgroundColor = theme.backgroundColor
button.layer.borderColor = theme.tintColor.cgColor
button.setTitleColor(theme.tintColor, for: .normal)
button.setTitleColor(theme.tintColor.withAlphaComponent(Constants.buttonHighlightedAlpha), for: .highlighted)
button.vc_setBackgroundColor(theme.baseColor, for: .normal)
self.button.setTitleColor(theme.baseTextPrimaryColor, for: .normal)
self.button.setTitleColor(theme.baseTextPrimaryColor.withAlphaComponent(Constants.buttonHighlightedAlpha), for: .highlighted)
self.button.vc_setBackgroundColor(theme.tintColor, for: .normal)
let buttonImage = Asset.Images.shareActionButton.image.vc_tintedImage(usingColor: theme.tintColor)
let buttonImage = Asset.Images.shareActionButton.image.vc_tintedImage(usingColor: theme.baseIconPrimaryColor)
self.button.setImage(buttonImage, for: .normal)
button.setImage(buttonImage, for: .normal)
}
// MARK: - Action
@objc private func buttonAction(_ sender: UIButton) {
self.delegate?.inviteFriendsHeaderView(self, didTapButton: button)
delegate?.inviteFriendsHeaderView(self, didTapButton: button)
}
}

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
@ -11,14 +11,14 @@
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="cxh-dz-aGG" customClass="InviteFriendsHeaderView" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="414" height="70"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="64"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="K8C-8y-oEb" customClass="CustomRoundedButton" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="47" y="10" width="320" height="50"/>
<rect key="frame" x="35.5" y="10" width="343" height="44"/>
<constraints>
<constraint firstAttribute="height" constant="50" id="JuE-b9-RNu"/>
<constraint firstAttribute="width" priority="750" constant="320" id="ujQ-vj-edr"/>
<constraint firstAttribute="height" constant="44" id="JuE-b9-RNu"/>
<constraint firstAttribute="width" priority="750" constant="343" id="ujQ-vj-edr"/>
</constraints>
<inset key="contentEdgeInsets" minX="20" minY="0.0" maxX="20" maxY="0.0"/>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="20" maxY="0.0"/>
@ -35,15 +35,15 @@
<constraints>
<constraint firstItem="K8C-8y-oEb" firstAttribute="top" secondItem="cxh-dz-aGG" secondAttribute="top" constant="10" id="dLb-B4-eBJ"/>
<constraint firstItem="K8C-8y-oEb" firstAttribute="centerX" secondItem="cxh-dz-aGG" secondAttribute="centerX" id="hNU-VW-Hbx"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="K8C-8y-oEb" secondAttribute="trailing" constant="20" id="nXF-QG-u1t"/>
<constraint firstItem="K8C-8y-oEb" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="cxh-dz-aGG" secondAttribute="leading" constant="20" id="rZm-C4-mTe"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="K8C-8y-oEb" secondAttribute="trailing" constant="16" id="nXF-QG-u1t"/>
<constraint firstItem="K8C-8y-oEb" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="cxh-dz-aGG" secondAttribute="leading" constant="16" id="rZm-C4-mTe"/>
<constraint firstAttribute="bottom" secondItem="K8C-8y-oEb" secondAttribute="bottom" constant="10" id="tDj-72-Eek"/>
</constraints>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<connections>
<outlet property="button" destination="K8C-8y-oEb" id="xU3-t7-lLR"/>
</connections>
<point key="canvasLocation" x="114.49275362318842" y="-637.5"/>
<point key="canvasLocation" x="114.49275362318842" y="-639.50892857142856"/>
</view>
</objects>
<resources>

View file

@ -120,9 +120,6 @@
_searchBarView.autocapitalizationType = UITextAutocapitalizationTypeNone;
[self refreshSearchBarItemsColor:_searchBarView];
// Hide line separators of empty cells
self.contactsTableView.tableFooterView = [[UIView alloc] init];
[self.contactsTableView registerClass:ContactTableViewCell.class forCellReuseIdentifier:@"ParticipantTableViewCellId"];
// Redirect table data source
@ -167,13 +164,13 @@
[self refreshSearchBarItemsColor:_searchBarView];
_searchBarHeaderBorder.backgroundColor = ThemeService.shared.theme.headerBorderColor;
// Check the table view style to select its bg color.
self.contactsTableView.backgroundColor = ((self.contactsTableView.style == UITableViewStylePlain) ? ThemeService.shared.theme.backgroundColor : ThemeService.shared.theme.headerBackgroundColor);
self.contactsTableView.backgroundColor = ((self.contactsTableView.style == UITableViewStylePlain) ? ThemeService.shared.theme.baseColor : ThemeService.shared.theme.headerBackgroundColor);
self.view.backgroundColor = self.contactsTableView.backgroundColor;
self.contactsTableView.separatorColor = ThemeService.shared.theme.lineBreakColor;
_searchBarHeaderBorder.backgroundColor = self.contactsTableView.backgroundColor;
if (self.contactsTableView.dataSource)
{
[self.contactsTableView reloadData];
@ -680,7 +677,6 @@
{
// bar tint color
searchBar.barTintColor = searchBar.tintColor = ThemeService.shared.theme.tintColor;
searchBar.tintColor = ThemeService.shared.theme.tintColor;
// FIXME: this all seems incredibly fragile and tied to gutwrenching the current UISearchBar internals.
@ -691,28 +687,27 @@
// Magnifying glass icon.
UIImageView *leftImageView = (UIImageView *)searchBarTextField.leftView;
leftImageView.image = [leftImageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
leftImageView.tintColor = ThemeService.shared.theme.tintColor;
leftImageView.tintColor = ThemeService.shared.theme.textSecondaryColor;
// remove the gray background color
UIView *effectBackgroundTop = [searchBarTextField valueForKey:@"_effectBackgroundTop"];
UIView *effectBackgroundBottom = [searchBarTextField valueForKey:@"_effectBackgroundBottom"];
// Use the theme's grey color.
// The effect views are needed due to minimal style.
// With default style there is a border above the search bar.
searchBarTextField.backgroundColor = ThemeService.shared.theme.textQuinaryColor;
UIView *effectBackgroundTop = [searchBarTextField valueForKey:@"_effectBackgroundTop"];
UIView *effectBackgroundBottom = [searchBarTextField valueForKey:@"_effectBackgroundBottom"];
effectBackgroundTop.hidden = YES;
effectBackgroundBottom.hidden = YES;
// place holder
if (searchBarTextField.placeholder)
{
searchBarTextField.textColor = ThemeService.shared.theme.placeholderTextColor;
}
}
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
[contactsDataSource searchWithPattern:searchText forceReset:NO];
self.contactsAreFilteredWithSearch = searchText.length ? YES : NO;
}
- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar
{
{
self.isAddParticipantSearchBarEditing = YES;
searchBar.showsCancelButton = NO;

View file

@ -1,11 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
@ -25,7 +23,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" keyboardDismissMode="onDrag" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="kNf-Ll-jvH">
<rect key="frame" x="0.0" y="70" width="375" height="597"/>
<rect key="frame" x="0.0" y="50" width="375" height="617"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="accessibilityIdentifier" value="StartChatVCTableView"/>
@ -36,10 +34,10 @@
</connections>
</tableView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Zm7-AB-ZtE">
<rect key="frame" x="0.0" y="20" width="375" height="50"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="50"/>
<subviews>
<searchBar contentMode="redraw" searchBarStyle="minimal" translatesAutoresizingMaskIntoConstraints="NO" id="bsq-3U-VjV">
<rect key="frame" x="0.0" y="0.0" width="375" height="50"/>
<rect key="frame" x="8" y="0.0" width="359" height="50"/>
<textInputTraits key="textInputTraits" returnKeyType="done"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="accessibilityIdentifier" value="StartChatVCSearchBarView"/>
@ -60,9 +58,9 @@
<accessibility key="accessibilityConfiguration" identifier="StartChatVCSearchBar"/>
<constraints>
<constraint firstItem="gcy-W7-89G" firstAttribute="leading" secondItem="Zm7-AB-ZtE" secondAttribute="leading" id="4Yn-dN-O2U"/>
<constraint firstItem="bsq-3U-VjV" firstAttribute="leading" secondItem="Zm7-AB-ZtE" secondAttribute="leading" id="6ze-Az-ymf"/>
<constraint firstItem="bsq-3U-VjV" firstAttribute="leading" secondItem="Zm7-AB-ZtE" secondAttribute="leading" constant="8" id="6ze-Az-ymf"/>
<constraint firstAttribute="bottom" secondItem="bsq-3U-VjV" secondAttribute="bottom" id="KDW-SI-sG6"/>
<constraint firstAttribute="trailing" secondItem="bsq-3U-VjV" secondAttribute="trailing" id="ZlE-SL-UfQ"/>
<constraint firstAttribute="trailing" secondItem="bsq-3U-VjV" secondAttribute="trailing" constant="8" id="ZlE-SL-UfQ"/>
<constraint firstAttribute="trailing" secondItem="gcy-W7-89G" secondAttribute="trailing" id="hqD-vA-OM5"/>
<constraint firstAttribute="bottom" secondItem="gcy-W7-89G" secondAttribute="bottom" id="ibU-h7-mHt"/>
<constraint firstAttribute="height" constant="50" id="kSM-fg-IHB"/>
@ -70,6 +68,7 @@
</constraints>
</view>
</subviews>
<viewLayoutGuide key="safeArea" id="Dt9-ew-iCA"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="kNf-Ll-jvH" secondAttribute="bottom" id="0YZ-gM-dTC"/>
@ -80,7 +79,7 @@
<constraint firstAttribute="trailing" secondItem="Zm7-AB-ZtE" secondAttribute="trailing" id="rMP-c5-JSU"/>
<constraint firstItem="kNf-Ll-jvH" firstAttribute="top" secondItem="Zm7-AB-ZtE" secondAttribute="bottom" id="tYv-VV-8dI"/>
</constraints>
<viewLayoutGuide key="safeArea" id="Dt9-ew-iCA"/>
<point key="canvasLocation" x="135" y="115"/>
</view>
</objects>
</document>

View file

@ -19,17 +19,13 @@
#import "AuthenticationViewController.h"
#import "RoomPreviewData.h"
#import "HomeViewController.h"
#import "FavouritesViewController.h"
#import "PeopleViewController.h"
#import "RoomsViewController.h"
#import "GroupsViewController.h"
#import "RoomViewController.h"
#import "ContactDetailsViewController.h"
#import "GroupDetailsViewController.h"
#import "UnifiedSearchViewController.h"
#define TABBAR_HOME_INDEX 0
#define TABBAR_FAVOURITES_INDEX 1
#define TABBAR_PEOPLE_INDEX 2
@ -175,23 +171,17 @@ typedef NS_ENUM(NSUInteger, MasterTabBarIndex) {
@property (nonatomic, readonly) RoomsViewController *roomsViewController;
@property (nonatomic, readonly) GroupsViewController *groupsViewController;
// The current unified search screen if any
@property (nonatomic, weak) UnifiedSearchViewController *unifiedSearchViewController;
// References on the currently selected room and its view controller
@property (nonatomic, readonly) RoomViewController *currentRoomViewController;
// References on the currently selected room
@property (nonatomic, readonly) NSString *selectedRoomId;
@property (nonatomic, readonly) NSString *selectedEventId;
@property (nonatomic, readonly) MXSession *selectedRoomSession;
@property (nonatomic, readonly) MXKRoomDataSource *selectedRoomDataSource;
@property (nonatomic, readonly) RoomPreviewData *selectedRoomPreviewData;
// References on the currently selected contact and its view controller
@property (nonatomic, readonly) ContactDetailsViewController *currentContactDetailViewController;
// References on the currently selected contact
@property (nonatomic, readonly) MXKContact *selectedContact;
// References on the currently selected group and its view controller
@property (nonatomic, readonly) GroupDetailsViewController *currentGroupDetailViewController;
// References on the currently selected group
@property (nonatomic, readonly) MXGroup *selectedGroup;
@property (nonatomic, readonly) MXSession *selectedGroupSession;
@ -203,13 +193,18 @@ typedef NS_ENUM(NSUInteger, MasterTabBarIndex) {
- (void)removeTabAt:(MasterTabBarIndex)index;
- (void)selectTabAtIndex:(MasterTabBarIndex)tabBarIndex;
@end
@protocol MasterTabBarControllerDelegate <NSObject>
- (void)masterTabBarControllerDidCompleteAuthentication:(MasterTabBarController *)masterTabBarController;
- (void)masterTabBarController:(MasterTabBarController*)masterTabBarController wantsToDisplayDetailViewController:(UIViewController*)detailViewController;
- (void)masterTabBarController:(MasterTabBarController*)masterTabBarController needsSideMenuIconWithNotification:(BOOL)displayNotification;
- (void)masterTabBarController:(MasterTabBarController *)masterTabBarController didSelectRoomWithId:(NSString*)roomId andEventId:(NSString*)eventId inMatrixSession:(MXSession*)matrixSession completion:(void (^)(void))completion;
- (void)masterTabBarController:(MasterTabBarController *)masterTabBarController didSelectRoomPreviewWithData:(RoomPreviewData*)roomPreviewData;
- (void)masterTabBarController:(MasterTabBarController *)masterTabBarController didSelectContact:(MXKContact*)contact;
- (void)masterTabBarController:(MasterTabBarController *)masterTabBarController didSelectGroup:(MXGroup*)group inMatrixSession:(MXSession*)matrixSession;
@end

View file

@ -17,8 +17,6 @@
#import "MasterTabBarController.h"
#import "UnifiedSearchViewController.h"
#import "RecentsDataSource.h"
#import "GroupsDataSource.h"
@ -234,12 +232,6 @@
[[AppDelegate theDelegate] checkAppVersion];
}
if (self.unifiedSearchViewController)
{
[self.unifiedSearchViewController destroy];
self.unifiedSearchViewController = nil;
}
}
- (void)viewDidDisappear:(BOOL)animated
@ -325,6 +317,12 @@
}
}
- (void)selectTabAtIndex:(MasterTabBarIndex)tabBarIndex
{
NSInteger index = [self indexOfTabItemWithTag:tabBarIndex];
self.selectedIndex = index;
}
#pragma mark -
- (NSArray<MXSession*>*)mxSessions
@ -588,60 +586,6 @@
}
}
- (void)showRoomDetails
{
[self releaseCurrentDetailsViewController];
if (_selectedRoomPreviewData)
{
// Replace the rootviewcontroller with a room view controller
// Get the RoomViewController from the storyboard
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]];
_currentRoomViewController = [storyboard instantiateViewControllerWithIdentifier:@"RoomViewControllerStoryboardId"];
[self.masterTabBarDelegate masterTabBarController:self wantsToDisplayDetailViewController:_currentRoomViewController];
[_currentRoomViewController displayRoomPreview:_selectedRoomPreviewData];
_selectedRoomPreviewData = nil;
[self setupLeftBarButtonItem];
}
else
{
MXWeakify(self);
void (^openRoomDataSource)(MXKRoomDataSource *roomDataSource) = ^(MXKRoomDataSource *roomDataSource) {
MXStrongifyAndReturnIfNil(self);
// Replace the rootviewcontroller with a room view controller
// Get the RoomViewController from the storyboard
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]];
self->_currentRoomViewController = [storyboard instantiateViewControllerWithIdentifier:@"RoomViewControllerStoryboardId"];
[self.masterTabBarDelegate masterTabBarController:self wantsToDisplayDetailViewController:self.currentRoomViewController];
[self.currentRoomViewController displayRoom:roomDataSource];
[self setupLeftBarButtonItem];
};
if (_selectedRoomDataSource)
{
// If the room data source is already loaded, display it
openRoomDataSource(_selectedRoomDataSource);
_selectedRoomDataSource = nil;
}
else
{
// Else, load it. The user may see the EmptyDetailsViewControllerStoryboardId
// screen in this case
[self dataSourceOfRoomToDisplay:^(MXKRoomDataSource *roomDataSource) {
openRoomDataSource(roomDataSource);
}];
}
}
}
- (void)selectRoomWithId:(NSString*)roomId andEventId:(NSString*)eventId inMatrixSession:(MXSession*)matrixSession
{
[self selectRoomWithId:roomId andEventId:eventId inMatrixSession:matrixSession completion:nil];
@ -649,98 +593,51 @@
- (void)selectRoomWithId:(NSString*)roomId andEventId:(NSString*)eventId inMatrixSession:(MXSession*)matrixSession completion:(void (^)(void))completion
{
if (_selectedRoomId && [_selectedRoomId isEqualToString:roomId]
&& _selectedEventId && [_selectedEventId isEqualToString:eventId]
&& _selectedRoomSession && _selectedRoomSession == matrixSession)
{
// Nothing to do
if (completion)
{
completion();
}
return;
}
[self releaseSelectedItem];
_selectedRoomId = roomId;
_selectedEventId = eventId;
_selectedRoomSession = matrixSession;
_selectedRoomSession = matrixSession;
if (roomId && matrixSession)
{
// Preload the data source before performing the segue
MXWeakify(self);
[self dataSourceOfRoomToDisplay:^(MXKRoomDataSource *roomDataSource) {
MXStrongifyAndReturnIfNil(self);
self->_selectedRoomDataSource = roomDataSource;
[self showRoomDetails];
if (completion)
{
completion();
}
}];
}
else
{
[self releaseSelectedItem];
if (completion)
{
completion();
}
}
[self.masterTabBarDelegate masterTabBarController:self didSelectRoomWithId:roomId andEventId:eventId inMatrixSession:matrixSession completion:completion];
[self refreshSelectedControllerSelectedCellIfNeeded];
}
- (void)showRoomPreview:(RoomPreviewData *)roomPreviewData
{
[self releaseSelectedItem];
_selectedRoomPreviewData = roomPreviewData;
_selectedRoomId = roomPreviewData.roomId;
_selectedRoomSession = roomPreviewData.mxSession;
[self showRoomDetails];
[self.masterTabBarDelegate masterTabBarController:self didSelectRoomPreviewWithData:roomPreviewData];
[self refreshSelectedControllerSelectedCellIfNeeded];
}
- (void)selectContact:(MXKContact*)contact
{
[self releaseSelectedItem];
_selectedContact = contact;
[self showContactDetails];
}
- (void)showContactDetails
{
[self releaseCurrentDetailsViewController];
[self.masterTabBarDelegate masterTabBarController:self didSelectContact:contact];
// Replace the rootviewcontroller with a contact details view controller
_currentContactDetailViewController = [ContactDetailsViewController contactDetailsViewController];
_currentContactDetailViewController.enableVoipCall = NO;
_currentContactDetailViewController.contact = _selectedContact;
[self.masterTabBarDelegate masterTabBarController:self wantsToDisplayDetailViewController:_currentContactDetailViewController];
[self setupLeftBarButtonItem];
[self refreshSelectedControllerSelectedCellIfNeeded];
}
- (void)selectGroup:(MXGroup*)group inMatrixSession:(MXSession*)matrixSession
{
[self releaseSelectedItem];
_selectedGroup = group;
_selectedGroupSession = matrixSession;
[self showGroupDetails];
}
- (void)showGroupDetails
{
[self releaseCurrentDetailsViewController];
[self.masterTabBarDelegate masterTabBarController:self didSelectGroup:group inMatrixSession:matrixSession];
// Replace the rootviewcontroller with a group details view controller
_currentGroupDetailViewController = [GroupDetailsViewController groupDetailsViewController];
[_currentGroupDetailViewController setGroup:_selectedGroup withMatrixSession:_selectedGroupSession];
[self.masterTabBarDelegate masterTabBarController:self wantsToDisplayDetailViewController:_currentGroupDetailViewController];
[self setupLeftBarButtonItem];
[self refreshSelectedControllerSelectedCellIfNeeded];
}
- (void)releaseSelectedItem
@ -748,15 +645,12 @@
_selectedRoomId = nil;
_selectedEventId = nil;
_selectedRoomSession = nil;
_selectedRoomDataSource = nil;
_selectedRoomPreviewData = nil;
_selectedContact = nil;
_selectedGroup = nil;
_selectedGroupSession = nil;
[self releaseCurrentDetailsViewController];
_selectedGroupSession = nil;
}
- (NSUInteger)missedDiscussionsCount
@ -844,84 +738,6 @@
#pragma mark -
/**
Load the data source of the room to open.
@param onComplete a block providing the loaded room data source.
*/
- (void)dataSourceOfRoomToDisplay:(void (^)(MXKRoomDataSource *roomDataSource))onComplete
{
// Check whether an event has been selected from messages or files search tab.
MXEvent *selectedSearchEvent = self.unifiedSearchViewController.selectedSearchEvent;
MXSession *selectedSearchEventSession = self.unifiedSearchViewController.selectedSearchEventSession;
if (!selectedSearchEvent)
{
if (!_selectedEventId)
{
// LIVE: Show the room live timeline managed by MXKRoomDataSourceManager
MXKRoomDataSourceManager *roomDataSourceManager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:_selectedRoomSession];
[roomDataSourceManager roomDataSourceForRoom:_selectedRoomId create:YES onComplete:^(MXKRoomDataSource *roomDataSource) {
onComplete(roomDataSource);
}];
}
else
{
// Open the room on the requested event
[RoomDataSource loadRoomDataSourceWithRoomId:_selectedRoomId initialEventId:_selectedEventId andMatrixSession:_selectedRoomSession onComplete:^(id roomDataSource) {
((RoomDataSource*)roomDataSource).markTimelineInitialEvent = YES;
// Give the data source ownership to the room view controller.
self.currentRoomViewController.hasRoomDataSourceOwnership = YES;
onComplete(roomDataSource);
}];
}
}
else
{
// Search result: Create a temp timeline from the selected event
[RoomDataSource loadRoomDataSourceWithRoomId:selectedSearchEvent.roomId initialEventId:selectedSearchEvent.eventId andMatrixSession:selectedSearchEventSession onComplete:^(id roomDataSource) {
[roomDataSource finalizeInitialization];
((RoomDataSource*)roomDataSource).markTimelineInitialEvent = YES;
// Give the data source ownership to the room view controller.
self.currentRoomViewController.hasRoomDataSourceOwnership = YES;
onComplete(roomDataSource);
}];
}
}
- (void)setupLeftBarButtonItem
{
if (self.splitViewController)
{
// Refresh selected cell without scrolling the selected cell (We suppose it's visible here)
[self refreshCurrentSelectedCell:NO];
if (_currentRoomViewController)
{
_currentRoomViewController.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;
_currentRoomViewController.navigationItem.leftItemsSupplementBackButton = YES;
}
else if (_currentContactDetailViewController)
{
_currentContactDetailViewController.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;
_currentContactDetailViewController.navigationItem.leftItemsSupplementBackButton = YES;
}
else if (_currentGroupDetailViewController)
{
_currentGroupDetailViewController.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;
_currentGroupDetailViewController.navigationItem.leftItemsSupplementBackButton = YES;
}
}
}
-(void)setupTitleView
{
titleView = [MainTitleView new];
@ -936,6 +752,15 @@
[super presentViewController:viewControllerToPresent animated:flag completion:completion];
}
- (void)refreshSelectedControllerSelectedCellIfNeeded
{
if (self.splitViewController)
{
// Refresh selected cell without scrolling the selected cell (We suppose it's visible here)
[self refreshCurrentSelectedCell:NO];
}
}
// Made the actual selected view controller update its selected cell.
- (void)refreshCurrentSelectedCell:(BOOL)forceVisible
{
@ -947,37 +772,6 @@
}
}
- (void)releaseCurrentDetailsViewController
{
// Release the existing details view controller (if any).
if (_currentRoomViewController)
{
// If the displayed data is not a preview, let the manager release the room data source
// (except if the view controller has the room data source ownership).
if (!_currentRoomViewController.roomPreviewData && _currentRoomViewController.roomDataSource && !_currentRoomViewController.hasRoomDataSourceOwnership)
{
MXSession *mxSession = _currentRoomViewController.roomDataSource.mxSession;
MXKRoomDataSourceManager *roomDataSourceManager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:mxSession];
// Let the manager release live room data sources where the user is in
[roomDataSourceManager closeRoomDataSourceWithRoomId:_currentRoomViewController.roomDataSource.roomId forceClose:NO];
}
[_currentRoomViewController destroy];
_currentRoomViewController = nil;
}
else if (_currentContactDetailViewController)
{
[_currentContactDetailViewController destroy];
_currentContactDetailViewController = nil;
}
else if (_currentGroupDetailViewController)
{
[_currentGroupDetailViewController destroy];
_currentGroupDetailViewController = nil;
}
}
- (void)setHidden:(BOOL)hidden
{
_hidden = hidden;

View file

@ -18,18 +18,6 @@
import UIKit
/// TabBarCoordinator input parameters
class TabBarCoordinatorParameters {
let userSessionsService: UserSessionsService
let appNavigator: AppNavigatorProtocol
init(userSessionsService: UserSessionsService, appNavigator: AppNavigatorProtocol) {
self.userSessionsService = userSessionsService
self.appNavigator = appNavigator
}
}
@objcMembers
final class TabBarCoordinator: NSObject, TabBarCoordinatorType {
@ -39,8 +27,10 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType {
private let parameters: TabBarCoordinatorParameters
/// Completion called when `popToHomeAnimated:` has been completed.
private var popToHomeViewControllerCompletion: (() -> Void)?
// Indicate if the Coordinator has started once
private var hasStartedOnce: Bool {
return self.masterTabBarController != nil
}
// TODO: Move MasterTabBarController navigation code here
// and if possible use a simple: `private let tabBarController: UITabBarController`
@ -57,6 +47,10 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType {
return parameters.userSessionsService.mainUserSession?.matrixSession
}
private var isTabBarControllerTopMostController: Bool {
return self.navigationRouter.modules.last is MasterTabBarController
}
// MARK: Public
// Must be used only internally
@ -85,8 +79,8 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType {
func start(with spaceId: String?) {
self.currentSpaceId = spaceId
// If start has been done once do setup view controllers again
if self.masterTabBarController == nil {
// If start has been done once do not setup view controllers again
if self.hasStartedOnce == false {
let masterTabBarController = self.createMasterTabBarController()
masterTabBarController.masterTabBarDelegate = self
self.masterTabBarController = masterTabBarController
@ -102,20 +96,18 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType {
}
self.registerUserSessionsServiceNotifications()
self.registerSessionChange()
if let homeViewController = homeViewControllerWrapperViewController {
let versionCheckCoordinator = VersionCheckCoordinator(rootViewController: masterTabBarController,
bannerPresenter: homeViewController,
themeService: ThemeService.shared())
versionCheckCoordinator.start()
add(childCoordinator: versionCheckCoordinator)
}
}
self.updateMasterTabBarController(with: spaceId)
self.registerUserSessionsServiceNotifications()
self.registerSessionChange()
if let homeViewController = homeViewControllerWrapperViewController {
let versionCheckCoordinator = VersionCheckCoordinator(rootViewController: masterTabBarController,
bannerPresenter: homeViewController,
themeService: ThemeService.shared())
versionCheckCoordinator.start()
add(childCoordinator: versionCheckCoordinator)
}
}
func toPresentable() -> UIViewController {
@ -127,43 +119,70 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType {
}
func popToHome(animated: Bool, completion: (() -> Void)?) {
// Force back to the main screen if this is not the one that is displayed
if masterTabBarController != masterNavigationController.visibleViewController {
// Listen to the masterNavigationController changes
// We need to be sure that masterTabBarController is back to the screen
popToHomeViewControllerCompletion = completion
masterNavigationController.delegate = self
let didPopToHome: (() -> Void) = {
// For unknown reason, the navigation bar is not restored correctly by [popToViewController:animated:]
// when a ViewController has hidden it (see MXKAttachmentsViewController).
// Patch: restore navigation bar by default here.
self.masterNavigationController.isNavigationBarHidden = false
if masterNavigationController.viewControllers.last == masterTabBarController {
self.navigationController(masterNavigationController, didShow: masterTabBarController, animated: false)
// Release the current selected item (room/contact/...).
self.masterTabBarController.releaseSelectedItem()
// Select home tab
self.masterTabBarController.selectTab(at: .home)
completion?()
}
// If MasterTabBarController is not visible because there is a modal above it
// but still the top view controller of navigation controller
if self.isTabBarControllerTopMostController {
didPopToHome()
} else {
masterNavigationController.popToViewController(masterTabBarController, animated: animated)
// Otherwise MasterTabBarController is not the top controller of the navigation controller
// Waiting for `self.navigationRouter` popping to MasterTabBarController
var token: NSObjectProtocol?
token = NotificationCenter.default.addObserver(forName: NavigationRouter.didPopModule, object: self.navigationRouter, queue: OperationQueue.main) { [weak self] (notification) in
guard let self = self else {
return
}
// If MasterTabBarController is now the top most controller in navigation controller stack call the completion
if self.isTabBarControllerTopMostController {
didPopToHome()
if let token = token {
NotificationCenter.default.removeObserver(token)
}
}
}
// Pop to root view controller
self.navigationRouter.popToRootModule(animated: animated)
}
} else {
// Tab bar controller is already visible
// Select the Home tab
masterTabBarController.selectedIndex = Int(TABBAR_HOME_INDEX)
masterTabBarController.selectTab(at: .home)
completion?()
}
}
// MARK: - SplitViewMasterPresentable
var collapseDetailViewController: Bool {
if (masterTabBarController.currentRoomViewController == nil) && (masterTabBarController.currentContactDetailViewController == nil) && (masterTabBarController.currentGroupDetailViewController == nil) {
// Return YES to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded.
return true
} else {
return false
}
}
func secondViewControllerWhenSeparatedFromPrimary() -> UIViewController? {
// Return the top view controller of the master navigation controller, if it is a navigation controller itself.
if let topViewController = masterNavigationController.topViewController as? UINavigationController {
// Keep the detail scene
return topViewController
}
return nil
var selectedNavigationRouter: NavigationRouterType? {
return self.navigationRouter
}
// MARK: - Private methods
@ -302,6 +321,10 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType {
self.parameters.appNavigator.sideMenu.show(from: self.masterTabBarController, animated: true)
}
private func dismissSideMenu(animated: Bool) {
self.parameters.appNavigator.sideMenu.dismiss(animated: animated)
}
// FIXME: Should be displayed per tab.
private func showSettings() {
let viewController = self.createSettingsViewController()
@ -313,23 +336,96 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType {
private func showUnifiedSearch() {
let viewController = self.createUnifiedSearchController()
self.masterTabBarController.unifiedSearchViewController = viewController
self.navigationRouter.push(viewController, animated: true, popCompletion: nil)
}
// FIXME: Should be displayed from a tab.
private func showContactDetails() {
// TODO: Implement
private func showContactDetails(with contact: MXKContact) {
let coordinatorParameters = ContactDetailsCoordinatorParameters(contact: contact)
let coordinator = ContactDetailsCoordinator(parameters: coordinatorParameters)
coordinator.start()
self.add(childCoordinator: coordinator)
self.replaceSplitViewDetails(with: coordinator) { [weak self] in
self?.remove(childCoordinator: coordinator)
}
}
// FIXME: Should be displayed from a tab.
private func showRoomDetails() {
// TODO: Implement
private func showGroupDetails(with group: MXGroup, for matrixSession: MXSession) {
let coordinatorParameters = GroupDetailsCoordinatorParameters(session: matrixSession, group: group)
let coordinator = GroupDetailsCoordinator(parameters: coordinatorParameters)
coordinator.start()
self.add(childCoordinator: coordinator)
self.replaceSplitViewDetails(with: coordinator) {
[weak self] in
self?.remove(childCoordinator: coordinator)
}
}
// FIXME: Should be displayed from a tab.
private func showGroupDetails() {
// TODO: Implement
private func showRoom(with roomId: String) {
guard let matrixSession = self.parameters.userSessionsService.mainUserSession?.matrixSession else {
return
}
self.showRoom(with: roomId, eventId: nil, matrixSession: matrixSession)
}
private func showRoom(with roomId: String, eventId: String?, matrixSession: MXSession, completion: (() -> Void)? = nil) {
// RoomCoordinator will be presented by the split view.
// As we don't know which navigation controller instance will be used,
// give the NavigationRouterStore instance and let it find the associated navigation controller
let roomCoordinatorParameters = RoomCoordinatorParameters(navigationRouterStore: NavigationRouterStore.shared, session: matrixSession, roomId: roomId, eventId: eventId)
self.showRoom(with: roomCoordinatorParameters, completion: completion)
}
private func showRoomPreview(with previewData: RoomPreviewData) {
// RoomCoordinator will be presented by the split view
// We don't which navigation controller instance will be used
// Give the NavigationRouterStore instance and let it find the associated navigation controller if needed
let roomCoordinatorParameters = RoomCoordinatorParameters(navigationRouterStore: NavigationRouterStore.shared, previewData: previewData)
self.showRoom(with: roomCoordinatorParameters)
}
private func showRoom(with parameters: RoomCoordinatorParameters, completion: (() -> Void)? = nil) {
if let topRoomCoordinator = self.splitViewMasterPresentableDelegate?.detailModules.last as? RoomCoordinatorProtocol,
parameters.roomId == topRoomCoordinator.roomId && parameters.session == topRoomCoordinator.mxSession {
// RoomCoordinator with the same room id and Matrix session is shown
if let eventId = parameters.eventId {
// If there is an event id ask the RoomCoordinator to start with this one
topRoomCoordinator.start(withEventId: eventId, completion: completion)
} else {
// If there is no event id defined do nothing
completion?()
}
return
}
let coordinator = RoomCoordinator(parameters: parameters)
coordinator.delegate = self
coordinator.start(withCompletion: completion)
self.add(childCoordinator: coordinator)
self.replaceSplitViewDetails(with: coordinator) {
[weak self] in
// NOTE: The RoomDataSource releasing is handled in SplitViewCoordinator
self?.remove(childCoordinator: coordinator)
}
}
/// If the split view is collapsed (one column visible) it will push the Presentable on the primary navigation controller, otherwise it will show the Presentable as the secondary view of the split view.
private func replaceSplitViewDetails(with presentable: Presentable, popCompletion: (() -> Void)? = nil) {
self.splitViewMasterPresentableDelegate?.splitViewMasterPresentable(self, wantsToReplaceDetailWith: presentable, popCompletion: popCompletion)
}
// MARK: UserSessions management
@ -387,45 +483,27 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType {
}
}
// MARK: - UINavigationControllerDelegate
extension TabBarCoordinator: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
if viewController == masterTabBarController {
masterNavigationController.delegate = nil
// For unknown reason, the navigation bar is not restored correctly by [popToViewController:animated:]
// when a ViewController has hidden it (see MXKAttachmentsViewController).
// Patch: restore navigation bar by default here.
masterNavigationController.isNavigationBarHidden = false
// Release the current selected item (room/contact/...).
masterTabBarController.releaseSelectedItem()
if let popToHomeViewControllerCompletion = self.popToHomeViewControllerCompletion {
let popToHomeViewControllerCompletion2: (() -> Void)? = popToHomeViewControllerCompletion
self.popToHomeViewControllerCompletion = nil
DispatchQueue.main.async {
popToHomeViewControllerCompletion2?()
}
}
}
}
}
// MARK: - MasterTabBarControllerDelegate
extension TabBarCoordinator: MasterTabBarControllerDelegate {
func masterTabBarController(_ masterTabBarController: MasterTabBarController!, didSelectRoomPreviewWith roomPreviewData: RoomPreviewData!) {
self.showRoomPreview(with: roomPreviewData)
}
func masterTabBarController(_ masterTabBarController: MasterTabBarController!, didSelect contact: MXKContact!) {
self.showContactDetails(with: contact)
}
func masterTabBarControllerDidCompleteAuthentication(_ masterTabBarController: MasterTabBarController!) {
self.delegate?.tabBarCoordinatorDidCompleteAuthentication(self)
}
func masterTabBarController(_ masterTabBarController: MasterTabBarController!, wantsToDisplayDetailViewController detailViewController: UIViewController!) {
self.splitViewMasterPresentableDelegate?.splitViewMasterPresentable(self, wantsToDisplay: detailViewController)
func masterTabBarController(_ masterTabBarController: MasterTabBarController!, didSelectRoomWithId roomId: String!, andEventId eventId: String!, inMatrixSession matrixSession: MXSession!, completion: (() -> Void)!) {
self.showRoom(with: roomId, eventId: eventId, matrixSession: matrixSession, completion: completion)
}
func masterTabBarController(_ masterTabBarController: MasterTabBarController!, didSelect group: MXGroup!, inMatrixSession matrixSession: MXSession!) {
self.showGroupDetails(with: group, for: matrixSession)
}
func masterTabBarController(_ masterTabBarController: MasterTabBarController!, needsSideMenuIconWithNotification displayNotification: Bool) {
@ -439,6 +517,26 @@ extension TabBarCoordinator: MasterTabBarControllerDelegate {
}
}
// MARK: - RoomCoordinatorDelegate
extension TabBarCoordinator: RoomCoordinatorDelegate {
func roomCoordinatorDidDismissInteractively(_ coordinator: RoomCoordinatorProtocol) {
self.remove(childCoordinator: coordinator)
}
func roomCoordinatorDidLeaveRoom(_ coordinator: RoomCoordinatorProtocol) {
self.navigationRouter.popModule(animated: true)
}
func roomCoordinatorDidCancelRoomPreview(_ coordinator: RoomCoordinatorProtocol) {
self.navigationRouter.popModule(animated: true)
}
func roomCoordinator(_ coordinator: RoomCoordinatorProtocol, didSelectRoomWithId roomId: String) {
self.showRoom(with: roomId)
}
}
// MARK: - UIGestureRecognizerDelegate
/**

View file

@ -0,0 +1,29 @@
//
// 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
/// TabBarCoordinator input parameters
class TabBarCoordinatorParameters {
let userSessionsService: UserSessionsService
let appNavigator: AppNavigatorProtocol
init(userSessionsService: UserSessionsService, appNavigator: AppNavigatorProtocol) {
self.userSessionsService = userSessionsService
self.appNavigator = appNavigator
}
}

View file

@ -15,6 +15,7 @@
*/
import UIKit
import WeakDictionary
/// `NavigationRouter` is a concrete implementation of NavigationRouterType.
final class NavigationRouter: NSObject, NavigationRouterType {
@ -26,9 +27,21 @@ final class NavigationRouter: NSObject, NavigationRouterType {
private var completions: [UIViewController : () -> Void]
private let navigationController: UINavigationController
/// Stores the association between the added Presentable and his view controller.
/// They can be the same if the controller is not added via his Coordinator or it is a simple UIViewController.
private var storedModules = WeakDictionary<UIViewController, AnyObject>()
// MARK: Public
/// Returns the presentables associated to each view controller
var modules: [Presentable] {
return self.viewControllers.map { (viewController) -> Presentable in
return self.module(for: viewController)
}
}
/// Return the view controllers stack
var viewControllers: [UIViewController] {
return navigationController.viewControllers
}
@ -39,6 +52,18 @@ final class NavigationRouter: NSObject, NavigationRouterType {
self.completions = [:]
super.init()
self.navigationController.delegate = self
// Post local notification on NavigationRouter creation
let userInfo: [String: Any] = [NavigationRouter.NotificationUserInfoKey.navigationRouter: self,
NavigationRouter.NotificationUserInfoKey.navigationController: navigationController]
NotificationCenter.default.post(name: NavigationRouter.didCreate, object: self, userInfo: userInfo)
}
deinit {
// Post local notification on NavigationRouter deinit
let userInfo: [String: Any] = [NavigationRouter.NotificationUserInfoKey.navigationRouter: self,
NavigationRouter.NotificationUserInfoKey.navigationController: navigationController]
NotificationCenter.default.post(name: NavigationRouter.willDestroy, object: self, userInfo: userInfo)
}
// MARK: - Public
@ -60,35 +85,115 @@ final class NavigationRouter: NSObject, NavigationRouterType {
// Avoid setting a UINavigationController onto stack
guard controller is UINavigationController == false else {
MXLog.error("Cannot add a UINavigationController to NavigationRouter")
return
}
// Call all completions so all coordinators can be deallocated
for presentable in completions.keys {
runCompletion(for: presentable)
self.addModule(module, for: controller)
let controllersToPop = self.navigationController.viewControllers.reversed()
controllersToPop.forEach {
self.willPopViewController($0)
}
if let popCompletion = popCompletion {
completions[controller] = popCompletion
}
self.willPushViewController(controller)
navigationController.setViewControllers([controller], animated: animated)
navigationController.isNavigationBarHidden = hideNavigationBar
// Pop old view controllers
controllersToPop.forEach {
self.didPopViewController($0)
}
// Add again controller to module association, in case same module instance is added back
self.addModule(module, for: controller)
self.didPushViewController(controller)
}
func setModules(_ modules: [Presentable], hideNavigationBar: Bool, animated: Bool) {
MXLog.debug("[NavigationRouter] Set modules \(modules)")
let controllers = modules.map { (presentable) -> UIViewController in
let controller = presentable.toPresentable()
self.addModule(presentable, for: controller)
return controller
}
let controllersToPop = self.navigationController.viewControllers.reversed()
controllersToPop.forEach {
self.willPopViewController($0)
}
controllers.forEach {
self.willPushViewController($0)
}
// Set new view controllers
navigationController.setViewControllers(controllers, animated: animated)
navigationController.isNavigationBarHidden = hideNavigationBar
// Pop old view controllers
controllersToPop.forEach {
self.didPopViewController($0)
}
// Add again controller to module association, in case same modules instance are added back
modules.forEach { (presentable) in
self.addModule(presentable, for: presentable.toPresentable())
}
controllers.forEach {
self.didPushViewController($0)
}
}
func popToRootModule(animated: Bool) {
MXLog.debug("[NavigationRouter] Pop to root module")
let controllers = self.navigationController.viewControllers
if controllers.count > 1 {
let controllersToPop = controllers[1..<controllers.count]
controllersToPop.reversed().forEach {
self.willPopViewController($0)
}
}
if let controllers = navigationController.popToRootViewController(animated: animated) {
controllers.forEach { runCompletion(for: $0) }
controllers.reversed().forEach {
self.didPopViewController($0)
}
}
}
func popToModule(_ module: Presentable, animated: Bool) {
MXLog.debug("[NavigationRouter] Pop to module \(module)")
if let controllers = navigationController.popToViewController(module.toPresentable(), animated: animated) {
controllers.forEach { runCompletion(for: $0) }
let controller = module.toPresentable()
let controllersBeforePop = self.navigationController.viewControllers
if let controllerIndex = controllersBeforePop.firstIndex(of: controller) {
let controllersToPop = controllersBeforePop[controllerIndex..<controllersBeforePop.count]
controllersToPop.reversed().forEach {
self.willPopViewController($0)
}
}
if let controllers = navigationController.popToViewController(controller, animated: animated) {
controllers.reversed().forEach {
self.didPopViewController($0)
}
}
}
@ -99,22 +204,55 @@ final class NavigationRouter: NSObject, NavigationRouterType {
// Avoid pushing UINavigationController onto stack
guard controller is UINavigationController == false else {
MXLog.error("Cannot push a UINavigationController to NavigationRouter")
return
}
self.addModule(module, for: controller)
if let completion = popCompletion {
completions[controller] = completion
}
navigationController.pushViewController(controller, animated: animated)
self.willPushViewController(controller)
navigationController.pushViewController(controller, animated: animated)
self.didPushViewController(controller)
}
func popModule(animated: Bool = true) {
MXLog.debug("[NavigationRouter] Pop module")
if let controller = navigationController.popViewController(animated: animated) {
runCompletion(for: controller)
if let lastController = navigationController.viewControllers.last {
self.willPopViewController(lastController)
}
if let controller = navigationController.popViewController(animated: animated) {
self.didPopViewController(controller)
}
}
func popAllModules(animated: Bool) {
MXLog.debug("[NavigationRouter] Pop all modules")
let controllersToPop = self.navigationController.viewControllers.reversed()
controllersToPop.forEach {
self.willPopViewController($0)
}
navigationController.setViewControllers([], animated: animated)
controllersToPop.forEach {
self.didPopViewController($0)
}
}
func contains(_ module: Presentable) -> Bool {
let controller = module.toPresentable()
return self.navigationController.viewControllers.contains(controller)
}
// MARK: Presentable
@ -125,6 +263,22 @@ final class NavigationRouter: NSObject, NavigationRouterType {
// MARK: - Private
private func module(for viewController: UIViewController) -> Presentable {
guard let module = self.storedModules[viewController] as? Presentable else {
return viewController
}
return module
}
private func addModule(_ module: Presentable, for viewController: UIViewController) {
self.storedModules[viewController] = module as AnyObject
}
private func removeModule(for viewController: UIViewController) {
self.storedModules[viewController] = nil
}
private func runCompletion(for controller: UIViewController) {
guard let completion = completions[controller] else {
return
@ -132,11 +286,51 @@ final class NavigationRouter: NSObject, NavigationRouterType {
completion()
completions.removeValue(forKey: controller)
}
private func willPushViewController(_ viewController: UIViewController) {
self.postNotification(withName: NavigationRouter.willPushModule, for: viewController)
}
private func didPushViewController(_ viewController: UIViewController) {
self.postNotification(withName: NavigationRouter.didPushModule, for: viewController)
}
private func willPopViewController(_ viewController: UIViewController) {
self.postNotification(withName: NavigationRouter.willPopModule, for: viewController)
}
private func didPopViewController(_ viewController: UIViewController) {
// Call completion closure associated to the view controller
// So associated coordinator can be deallocated
runCompletion(for: viewController)
self.postNotification(withName: NavigationRouter.didPopModule, for: viewController)
self.removeModule(for: viewController)
}
private func postNotification(withName name: Notification.Name, for viewController: UIViewController) {
let module = self.module(for: viewController)
let userInfo: [String: Any] = [
NotificationUserInfoKey.navigationRouter: self,
NotificationUserInfoKey.module: module,
NotificationUserInfoKey.viewController: viewController
]
NotificationCenter.default.post(name: name, object: self, userInfo: userInfo)
}
}
// MARK: - UINavigationControllerDelegate
extension NavigationRouter: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
// TODO: Try to post `NavigationRouter.willPopModule` notification here
}
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
// Ensure the view controller is popping
@ -147,6 +341,37 @@ extension NavigationRouter: UINavigationControllerDelegate {
MXLog.debug("[NavigationRouter] Poppped module: \(poppedViewController)")
runCompletion(for: poppedViewController)
self.didPopViewController(poppedViewController)
}
}
// MARK: - NavigationRouter notification constants
extension NavigationRouter {
// MARK: Notification names
public static let willPushModule = Notification.Name("NavigationRouterWillPushModule")
public static let didPushModule = Notification.Name("NavigationRouterDidPushModule")
public static let willPopModule = Notification.Name("NavigationRouterWillPopModule")
public static let didPopModule = Notification.Name("NavigationRouterDidPopModule")
public static let didCreate = Notification.Name("NavigationRouterDidCreate")
public static let willDestroy = Notification.Name("NavigationRouterWillDestroy")
// MARK: Notification keys
public struct NotificationUserInfoKey {
/// The associated view controller (UIViewController).
static let viewController = "viewController"
/// The associated module (Presentable), can the view controller itself or is Coordinator
static let module = "module"
/// The navigation router that send the notification (NavigationRouterType)
static let navigationRouter = "navigationRouter"
/// The navigation controller (UINavigationController) associated to the navigation router
static let navigationController = "navigationController"
}
}

View file

@ -0,0 +1,97 @@
//
// 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 WeakDictionary
/// `NavigationRouterStore` enables to get a NavigationRouter from a UINavigationController instance.
class NavigationRouterStore: NavigationRouterStoreProtocol {
// MARK: - Constants
static let shared = NavigationRouterStore()
// MARK: - Properties
// FIXME: WeakDictionary does not work with protocol
// Find a way to use NavigationRouterType as value
private var navigationRouters = WeakDictionary<UINavigationController, NavigationRouter>()
// MARK: - Setup
/// As we are ensuring that there is only one navigation controller per NavigationRouter, the class here should be used as a singleton.
private init() {
self.registerNavigationRouterNotifications()
}
// MARK: - Public
func navigationRouter(for navigationController: UINavigationController) -> NavigationRouterType {
if let existingNavigationRouter = self.findNavigationRouter(for: navigationController) {
return existingNavigationRouter
}
let navigationRouter = NavigationRouter(navigationController: RiotNavigationController())
return navigationRouter
}
// MARK: - Private
private func findNavigationRouter(for navigationController: UINavigationController) -> NavigationRouterType? {
return self.navigationRouters[navigationController]
}
private func removeNavigationRouter(for navigationController: UINavigationController) {
self.navigationRouters[navigationController] = nil
}
private func registerNavigationRouterNotifications() {
NotificationCenter.default.addObserver(self, selector: #selector(navigationRouterDidCreate(_:)), name: NavigationRouter.didCreate, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(navigationRouterWillDestroy(_:)), name: NavigationRouter.willDestroy, object: nil)
}
@objc private func navigationRouterDidCreate(_ notification: Notification) {
guard let userInfo = notification.userInfo,
let navigationRouter = userInfo[NavigationRouter.NotificationUserInfoKey.navigationRouter] as? NavigationRouterType,
let navigationController = userInfo[NavigationRouter.NotificationUserInfoKey.navigationController] as? UINavigationController else {
return
}
if let existingNavigationRouter = self.findNavigationRouter(for: navigationController) {
fatalError("\(existingNavigationRouter) is already tied to the same navigation controller as \(navigationRouter). We should have only one NavigationRouter per navigation controller")
} else {
// FIXME: WeakDictionary does not work with protocol
// Find a way to avoid this cast
self.navigationRouters[navigationController] = navigationRouter as? NavigationRouter
}
}
@objc private func navigationRouterWillDestroy(_ notification: Notification) {
guard let userInfo = notification.userInfo,
let navigationRouter = userInfo[NavigationRouter.NotificationUserInfoKey.navigationRouter] as? NavigationRouterType,
let navigationController = userInfo[NavigationRouter.NotificationUserInfoKey.navigationController] as? UINavigationController else {
return
}
if let existingNavigationRouter = self.findNavigationRouter(for: navigationController), existingNavigationRouter !== navigationRouter {
fatalError("\(existingNavigationRouter) is already tied to the same navigation controller as \(navigationRouter). We should have only one NavigationRouter per navigation controller")
}
self.removeNavigationRouter(for: navigationController)
}
}

View file

@ -0,0 +1,25 @@
//
// 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
/// `NavigationRouterStoreProtocol` describes a structure that enables to get a NavigationRouter from a UINavigationController instance.
protocol NavigationRouterStoreProtocol {
/// Gets the existing navigation router for the supplied controller, creating a new one if it doesn't yet exist.
/// Note: The store only holds a weak reference to the returned router. It is the caller's responsibility to retain it.
func navigationRouter(for navigationController: UINavigationController) -> NavigationRouterType
}

View file

@ -40,6 +40,13 @@ protocol NavigationRouterType: AnyObject, Presentable {
/// - Parameter popCompletion: Completion called when `module` is removed from the navigation stack.
func setRootModule(_ module: Presentable, hideNavigationBar: Bool, animated: Bool, popCompletion: (() -> Void)?)
/// Set view controllers stack of navigation controller
/// - Parameters:
/// - modules: The presentables stack to set.
/// - hideNavigationBar: Specify true to hide the UINavigationBar.
/// - animated: Specify true to animate the transition.
func setModules(_ modules: [Presentable], hideNavigationBar: Bool, animated: Bool)
/// Pop to root view controller of navigation controller and remove all others
///
/// - Parameter animated: Specify true to animate the transition.
@ -62,12 +69,22 @@ protocol NavigationRouterType: AnyObject, Presentable {
/// - Parameter animated: Specify true to animate the transition.
func popModule(animated: Bool)
/// Pops all view controllers
///
/// - Parameter animated: Specify true to animate the transition.
func popAllModules(animated: Bool)
/// Returns the modules that are currently in the navigation stack
var modules: [Presentable] { get }
/// Check if the navigation controller contains the given presentable.
/// - Parameter module: The presentable for which to check the existence.
func contains(_ module: Presentable) -> Bool
}
// `NavigationRouterType` default implementation
extension NavigationRouterType {
func setRootModule(_ module: Presentable) {
setRootModule(module, hideNavigationBar: false, animated: false, popCompletion: nil)
}
@ -75,4 +92,8 @@ extension NavigationRouterType {
func setRootModule(_ module: Presentable, popCompletion: (() -> Void)?) {
setRootModule(module, hideNavigationBar: false, animated: false, popCompletion: popCompletion)
}
func setModules(_ modules: [Presentable], animated: Bool) {
setModules(modules, hideNavigationBar: false, animated: animated)
}
}

View file

@ -40,3 +40,6 @@
#import "BuildInfo.h"
#import "RoomMemberDetailsViewController.h"
#import "Tools.h"
#import "RoomViewController.h"
#import "ContactDetailsViewController.h"
#import "GroupDetailsViewController.h"

View file

@ -71,6 +71,7 @@
if (roomCellData)
{
[self.avatarImageView vc_setRoomAvatarImageWith:roomCellData.avatarUrl
roomId:roomCellData.roomIdentifier
displayName:roomCellData.roomDisplayname
mediaManager:roomCellData.mxSession.mediaManager];

View file

@ -43,6 +43,7 @@ targets:
- path: ../Riot/Utils/AvatarGenerator.m
- path: ../Config/BuildSettings.swift
- path: ../Riot/Categories/Character.swift
- path: ../Riot/Categories/MXKImageView.swift
- path: ../Riot/Categories/MXRoom+Riot.m
- path: ../Config/Configurable.swift
- path: ../Config/CommonConfiguration.swift

View file

@ -0,0 +1,27 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import SwiftUI
/// A SwiftUI `PreferenceKey` for `CGRect` values such as a view's frame.
@available(iOS 14.0, *)
struct FramePreferenceKey: PreferenceKey {
static var defaultValue: CGRect = .zero
static func reduce(value: inout CGRect, nextValue: () -> CGRect) {
value = nextValue()
}
}

View file

@ -31,11 +31,13 @@ struct ViewFrameReader: View {
@Binding var frame: CGRect
var body: some View {
GeometryReader { geo -> Color in
DispatchQueue.main.async {
frame = geo.frame(in: .local)
}
return .clear
GeometryReader { geometry in
Color.clear
.preference(key: FramePreferenceKey.self,
value: geometry.frame(in: .local))
}
.onPreferenceChange(FramePreferenceKey.self) {
frame = $0
}
}
}

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