mirror of
https://github.com/vector-im/element-ios.git
synced 2024-09-28 23:32:41 +00:00
Merge branch 'develop' into ismail/4384_room_summary_store
This commit is contained in:
commit
58df3d3309
120 changed files with 3683 additions and 1012 deletions
|
@ -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:
|
||||
|
|
28
CHANGES.md
28
CHANGES.md
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
3
Podfile
3
Podfile
|
@ -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'
|
||||
|
|
44
Podfile.lock
44
Podfile.lock
|
@ -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
|
||||
|
|
15
Riot/Assets/Images.xcassets/Common/information_button.imageset/Contents.json
vendored
Normal file
15
Riot/Assets/Images.xcassets/Common/information_button.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "information_button.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true
|
||||
}
|
||||
}
|
91
Riot/Assets/Images.xcassets/Common/information_button.imageset/information_button.pdf
vendored
Normal file
91
Riot/Assets/Images.xcassets/Common/information_button.imageset/information_button.pdf
vendored
Normal 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
|
6
Riot/Assets/Images.xcassets/Contacts/Contents.json
Normal file
6
Riot/Assets/Images.xcassets/Contacts/Contents.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
23
Riot/Assets/Images.xcassets/Contacts/find_your_contacts_facepile.imageset/Contents.json
vendored
Normal file
23
Riot/Assets/Images.xcassets/Contacts/find_your_contacts_facepile.imageset/Contents.json
vendored
Normal 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
|
||||
}
|
||||
}
|
BIN
Riot/Assets/Images.xcassets/Contacts/find_your_contacts_facepile.imageset/facepile.png
vendored
Normal file
BIN
Riot/Assets/Images.xcassets/Contacts/find_your_contacts_facepile.imageset/facepile.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
BIN
Riot/Assets/Images.xcassets/Contacts/find_your_contacts_facepile.imageset/facepile@2x.png
vendored
Normal file
BIN
Riot/Assets/Images.xcassets/Contacts/find_your_contacts_facepile.imageset/facepile@2x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 43 KiB |
BIN
Riot/Assets/Images.xcassets/Contacts/find_your_contacts_facepile.imageset/facepile@3x.png
vendored
Normal file
BIN
Riot/Assets/Images.xcassets/Contacts/find_your_contacts_facepile.imageset/facepile@3x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 87 KiB |
6
Riot/Assets/Images.xcassets/Integrations/Contents.json
Normal file
6
Riot/Assets/Images.xcassets/Integrations/Contents.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
15
Riot/Assets/Images.xcassets/Integrations/integration_manager_iconpile.imageset/Contents.json
vendored
Normal file
15
Riot/Assets/Images.xcassets/Integrations/integration_manager_iconpile.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "integration_manager_iconpile.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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.";
|
||||
|
|
|
@ -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 %@";
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
29
Riot/Categories/Array.swift
Normal file
29
Riot/Categories/Array.swift
Normal 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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -80,6 +80,9 @@ final class AppCoordinator: NSObject, AppCoordinatorType {
|
|||
self.setupLogger()
|
||||
self.setupTheme()
|
||||
|
||||
// Setup navigation router store
|
||||
_ = NavigationRouterStore.shared
|
||||
|
||||
if BuildSettings.enableSideMenu {
|
||||
self.addSideMenu()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -125,6 +125,7 @@
|
|||
}
|
||||
|
||||
[self.roomAvatar vc_setRoomAvatarImageWith:roomCellData.avatarUrl
|
||||
roomId:roomCellData.roomIdentifier
|
||||
displayName:roomCellData.roomDisplayname
|
||||
mediaManager:roomCellData.mxSession.mediaManager];
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -964,7 +964,7 @@
|
|||
|
||||
if (contact)
|
||||
{
|
||||
ContactDetailsViewController *contactDetailsViewController = [ContactDetailsViewController contactDetailsViewController];
|
||||
ContactDetailsViewController *contactDetailsViewController = [ContactDetailsViewController instantiate];
|
||||
contactDetailsViewController.enableVoipCall = NO;
|
||||
contactDetailsViewController.contact = contact;
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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 }
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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++;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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 }
|
||||
}
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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];
|
||||
|
|
87
Riot/Modules/Contacts/Views/FindYourContactsFooterView.swift
Normal file
87
Riot/Modules/Contacts/Views/FindYourContactsFooterView.swift
Normal 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)
|
||||
}
|
||||
}
|
100
Riot/Modules/Contacts/Views/FindYourContactsFooterView.xib
Normal file
100
Riot/Modules/Contacts/Views/FindYourContactsFooterView.xib
Normal 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>
|
|
@ -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];
|
||||
|
|
|
@ -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];;
|
||||
|
|
|
@ -131,6 +131,7 @@
|
|||
}
|
||||
|
||||
[self.roomAvatar vc_setRoomAvatarImageWith:roomCellData.avatarUrl
|
||||
roomId:roomCellData.roomIdentifier
|
||||
displayName:roomCellData.roomDisplayname
|
||||
mediaManager:roomCellData.mxSession.mediaManager];
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
242
Riot/Modules/Room/RoomCoordinator.swift
Normal file
242
Riot/Modules/Room/RoomCoordinator.swift
Normal 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)
|
||||
}
|
||||
}
|
145
Riot/Modules/Room/RoomCoordinatorBridgePresenter.swift
Normal file
145
Riot/Modules/Room/RoomCoordinatorBridgePresenter.swift
Normal 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)
|
||||
}
|
||||
}
|
76
Riot/Modules/Room/RoomCoordinatorParameters.swift
Normal file
76
Riot/Modules/Room/RoomCoordinatorParameters.swift
Normal 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)
|
||||
}
|
||||
}
|
48
Riot/Modules/Room/RoomCoordinatorProtocol.swift
Normal file
48
Riot/Modules/Room/RoomCoordinatorProtocol.swift
Normal 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)?)
|
||||
}
|
24
Riot/Modules/Room/RoomIdentifiable.swift
Normal file
24
Riot/Modules/Room/RoomIdentifiable.swift
Normal 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 }
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -24,5 +24,4 @@ enum ServiceTermsModalScreenViewAction {
|
|||
case display(MXLoginPolicyData)
|
||||
case accept
|
||||
case decline
|
||||
case cancel
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
|
@ -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>
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -395,7 +395,7 @@ extension SettingsIdentityServerViewController: ServiceTermsModalCoordinatorBrid
|
|||
self.hideTerms(accepted: false)
|
||||
}
|
||||
|
||||
func serviceTermsModalCoordinatorBridgePresenterDelegateDidCancel(_ coordinatorBridgePresenter: ServiceTermsModalCoordinatorBridgePresenter) {
|
||||
func serviceTermsModalCoordinatorBridgePresenterDelegateDidClose(_ coordinatorBridgePresenter: ServiceTermsModalCoordinatorBridgePresenter) {
|
||||
self.hideTerms(accepted: false)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
||||
/**
|
||||
|
|
29
Riot/Modules/TabBar/TabBarCoordinatorParameters.swift
Normal file
29
Riot/Modules/TabBar/TabBarCoordinatorParameters.swift
Normal 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
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
97
Riot/Routers/NavigationRouterStore.swift
Normal file
97
Riot/Routers/NavigationRouterStore.swift
Normal 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)
|
||||
}
|
||||
}
|
25
Riot/Routers/NavigationRouterStoreProtocol.swift
Normal file
25
Riot/Routers/NavigationRouterStoreProtocol.swift
Normal 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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,3 +40,6 @@
|
|||
#import "BuildInfo.h"
|
||||
#import "RoomMemberDetailsViewController.h"
|
||||
#import "Tools.h"
|
||||
#import "RoomViewController.h"
|
||||
#import "ContactDetailsViewController.h"
|
||||
#import "GroupDetailsViewController.h"
|
||||
|
|
|
@ -71,6 +71,7 @@
|
|||
if (roomCellData)
|
||||
{
|
||||
[self.avatarImageView vc_setRoomAvatarImageWith:roomCellData.avatarUrl
|
||||
roomId:roomCellData.roomIdentifier
|
||||
displayName:roomCellData.roomDisplayname
|
||||
mediaManager:roomCellData.mxSession.mediaManager];
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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
Loading…
Reference in a new issue