23
Riot/Assets/Images.xcassets/SideMenu/side_menu_notif_icon.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "side_menu_notif_icon.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "side_menu_notif_icon@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "side_menu_notif_icon@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
Riot/Assets/Images.xcassets/SideMenu/side_menu_notif_icon.imageset/side_menu_notif_icon.png
vendored
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
Riot/Assets/Images.xcassets/SideMenu/side_menu_notif_icon.imageset/side_menu_notif_icon@2x.png
vendored
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
Riot/Assets/Images.xcassets/SideMenu/side_menu_notif_icon.imageset/side_menu_notif_icon@3x.png
vendored
Normal file
After Width: | Height: | Size: 2.8 KiB |
23
Riot/Assets/Images.xcassets/Spaces/space_home_icon.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "space_home_icon.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "space_home_icon@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "space_home_icon@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
Riot/Assets/Images.xcassets/Spaces/space_home_icon.imageset/space_home_icon.png
vendored
Normal file
After Width: | Height: | Size: 328 B |
BIN
Riot/Assets/Images.xcassets/Spaces/space_home_icon.imageset/space_home_icon@2x.png
vendored
Normal file
After Width: | Height: | Size: 541 B |
BIN
Riot/Assets/Images.xcassets/Spaces/space_home_icon.imageset/space_home_icon@3x.png
vendored
Normal file
After Width: | Height: | Size: 703 B |
26
Riot/Assets/Images.xcassets/Spaces/space_menu_close.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "space_menu_close.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "space_menu_close@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "space_menu_close@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
BIN
Riot/Assets/Images.xcassets/Spaces/space_menu_close.imageset/space_menu_close.png
vendored
Normal file
After Width: | Height: | Size: 261 B |
BIN
Riot/Assets/Images.xcassets/Spaces/space_menu_close.imageset/space_menu_close@2x.png
vendored
Normal file
After Width: | Height: | Size: 393 B |
BIN
Riot/Assets/Images.xcassets/Spaces/space_menu_close.imageset/space_menu_close@3x.png
vendored
Normal file
After Width: | Height: | Size: 548 B |
26
Riot/Assets/Images.xcassets/Spaces/space_menu_leave.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "space_menu_leave.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "space_menu_leave@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "space_menu_leave@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
BIN
Riot/Assets/Images.xcassets/Spaces/space_menu_leave.imageset/space_menu_leave.png
vendored
Normal file
After Width: | Height: | Size: 287 B |
BIN
Riot/Assets/Images.xcassets/Spaces/space_menu_leave.imageset/space_menu_leave@2x.png
vendored
Normal file
After Width: | Height: | Size: 397 B |
BIN
Riot/Assets/Images.xcassets/Spaces/space_menu_leave.imageset/space_menu_leave@3x.png
vendored
Normal file
After Width: | Height: | Size: 544 B |
26
Riot/Assets/Images.xcassets/Spaces/space_menu_members.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "space_menu_members.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "space_menu_members@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "space_menu_members@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
BIN
Riot/Assets/Images.xcassets/Spaces/space_menu_members.imageset/space_menu_members.png
vendored
Normal file
After Width: | Height: | Size: 806 B |
BIN
Riot/Assets/Images.xcassets/Spaces/space_menu_members.imageset/space_menu_members@2x.png
vendored
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
Riot/Assets/Images.xcassets/Spaces/space_menu_members.imageset/space_menu_members@3x.png
vendored
Normal file
After Width: | Height: | Size: 2.2 KiB |
26
Riot/Assets/Images.xcassets/Spaces/space_menu_rooms.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "space_menu_rooms.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "space_menu_rooms@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "space_menu_rooms@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
BIN
Riot/Assets/Images.xcassets/Spaces/space_menu_rooms.imageset/space_menu_rooms.png
vendored
Normal file
After Width: | Height: | Size: 757 B |
BIN
Riot/Assets/Images.xcassets/Spaces/space_menu_rooms.imageset/space_menu_rooms@2x.png
vendored
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
Riot/Assets/Images.xcassets/Spaces/space_menu_rooms.imageset/space_menu_rooms@3x.png
vendored
Normal file
After Width: | Height: | Size: 1.7 KiB |
26
Riot/Assets/Images.xcassets/Spaces/space_room_icon.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "space_room_icon.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "space_room_icon@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "space_room_icon@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
BIN
Riot/Assets/Images.xcassets/Spaces/space_room_icon.imageset/space_room_icon.png
vendored
Normal file
After Width: | Height: | Size: 447 B |
BIN
Riot/Assets/Images.xcassets/Spaces/space_room_icon.imageset/space_room_icon@2x.png
vendored
Normal file
After Width: | Height: | Size: 744 B |
BIN
Riot/Assets/Images.xcassets/Spaces/space_room_icon.imageset/space_room_icon@3x.png
vendored
Normal file
After Width: | Height: | Size: 797 B |
26
Riot/Assets/Images.xcassets/Spaces/space_type_icon.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "space_type_icon.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "space_type_icon@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "space_type_icon@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
BIN
Riot/Assets/Images.xcassets/Spaces/space_type_icon.imageset/space_type_icon.png
vendored
Normal file
After Width: | Height: | Size: 459 B |
BIN
Riot/Assets/Images.xcassets/Spaces/space_type_icon.imageset/space_type_icon@2x.png
vendored
Normal file
After Width: | Height: | Size: 815 B |
BIN
Riot/Assets/Images.xcassets/Spaces/space_type_icon.imageset/space_type_icon@3x.png
vendored
Normal file
After Width: | Height: | Size: 1.1 KiB |
26
Riot/Assets/Images.xcassets/Spaces/space_user_icon.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "space_user_icon.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "space_user_icon@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "space_user_icon@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
BIN
Riot/Assets/Images.xcassets/Spaces/space_user_icon.imageset/space_user_icon.png
vendored
Normal file
After Width: | Height: | Size: 567 B |
BIN
Riot/Assets/Images.xcassets/Spaces/space_user_icon.imageset/space_user_icon@2x.png
vendored
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
Riot/Assets/Images.xcassets/Spaces/space_user_icon.imageset/space_user_icon@3x.png
vendored
Normal file
After Width: | Height: | Size: 1.3 KiB |
26
Riot/Assets/Images.xcassets/Spaces/spaces_more.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "spaces_more.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "spaces_more@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "spaces_more@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
BIN
Riot/Assets/Images.xcassets/Spaces/spaces_more.imageset/spaces_more.png
vendored
Normal file
After Width: | Height: | Size: 304 B |
BIN
Riot/Assets/Images.xcassets/Spaces/spaces_more.imageset/spaces_more@2x.png
vendored
Normal file
After Width: | Height: | Size: 527 B |
BIN
Riot/Assets/Images.xcassets/Spaces/spaces_more.imageset/spaces_more@3x.png
vendored
Normal file
After Width: | Height: | Size: 408 B |
|
@ -63,6 +63,7 @@
|
|||
"switch" = "Switch";
|
||||
"more" = "More";
|
||||
"less" = "Less";
|
||||
"open" = "Open";
|
||||
"done" = "Done";
|
||||
|
||||
// Call Bar
|
||||
|
@ -190,6 +191,7 @@
|
|||
"room_recents_low_priority_section" = "LOW PRIORITY";
|
||||
"room_recents_server_notice_section" = "SYSTEM ALERTS";
|
||||
"room_recents_invites_section" = "INVITES";
|
||||
"room_recents_suggested_rooms_section" = "SUGGESTED ROOMS";
|
||||
"room_recents_start_chat_with" = "Start chat";
|
||||
"room_recents_create_empty_room" = "Create room";
|
||||
"room_recents_join_room" = "Join room";
|
||||
|
@ -1695,6 +1697,36 @@ Tap the + to start adding people.";
|
|||
"space_beta_announce_subtitle" = "The new version of communities";
|
||||
"space_beta_announce_information" = "Spaces are a new way to group rooms and people. They’re not on iOS yet, but you can use them now on Web and Desktop.";
|
||||
|
||||
"spaces_home_space_title" = "Home";
|
||||
"spaces_left_panel_title" = "Spaces";
|
||||
"leave_space_title" = "Leave %@";
|
||||
"leave_space_message" = "Are you sure you want to leave %@? Do you also want to leave all rooms and spaces of this space?";
|
||||
"leave_space_message_admin_warning" = "You are admin of this space, ensure that you have transferred admin right to another member before leaving.";
|
||||
"leave_space_only_action" = "Don't leave any rooms";
|
||||
"leave_space_and_all_rooms_action" = "Leave all rooms and spaces";
|
||||
"spaces_explore_rooms" = "Explore rooms";
|
||||
"spaces_suggested_room" = "Suggested";
|
||||
"space_tag" = "space";
|
||||
"spaces_empty_space_title" = "This space has no rooms (yet)";
|
||||
"spaces_empty_space_detail" = "Some rooms may be hidden because they’re private and you need an invite.";
|
||||
"spaces_no_result_found_title" = "No results found";
|
||||
"spaces_no_room_found_detail" = "Some results may be hidden because they’re private and you need an invite to join them.";
|
||||
"spaces_no_member_found_detail" = "Looking for someone not in %@? For now, you can invite them on web or desktop.";
|
||||
"spaces_coming_soon_title" = "Coming soon";
|
||||
"spaces_add_rooms_coming_soon_title" = "Adding rooms coming soon";
|
||||
"spaces_invites_coming_soon_title" = "Invites coming soon";
|
||||
"spaces_coming_soon_detail" = "This feature hasn’t been implemented here, but it’s on the way. For now, you can do that with Element on your computer.";
|
||||
"space_participants_action_remove" = "Remove from this space";
|
||||
"space_participants_action_ban" = "Ban from this space";
|
||||
|
||||
"space_private_join_rule" = "Private space";
|
||||
"space_public_join_rule" = "Public space";
|
||||
|
||||
// Mark: Avatar
|
||||
|
||||
"space_avatar_view_accessibility_label" = "avatar";
|
||||
"space_avatar_view_accessibility_hint" = "Change space avatar";
|
||||
|
||||
// Mark: - User avatar view
|
||||
|
||||
"user_avatar_view_accessibility_label" = "avatar";
|
||||
|
|
32
Riot/Categories/MXKImageView.swift
Normal file
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension MXKImageView {
|
||||
@objc func vc_setRoomAvatarImage(with url: String?, displayName: String, mediaManager: MXMediaManager) {
|
||||
// Use the display name to prepare the default avatar image.
|
||||
let avatarImage = AvatarGenerator.generateAvatar(forText: displayName)
|
||||
|
||||
if let avatarUrl = url {
|
||||
self.enableInMemoryCache = true
|
||||
self.setImageURI(avatarUrl, withType: nil, andImageOrientation: .up, toFitViewSize: self.frame.size, with: MXThumbnailingMethodCrop, previewImage: avatarImage, mediaManager: mediaManager)
|
||||
} else {
|
||||
self.image = avatarImage
|
||||
}
|
||||
self.contentMode = .scaleAspectFill
|
||||
}
|
||||
}
|
|
@ -32,6 +32,8 @@ typedef NS_ENUM(NSUInteger, RoomEncryptionTrustLevel) {
|
|||
*/
|
||||
@interface MXRoomSummary (Riot)
|
||||
|
||||
@property(nonatomic, readonly) BOOL isJoined;
|
||||
|
||||
/**
|
||||
Set the room avatar in the dedicated MXKImageView.
|
||||
The riot style implies to use in order :
|
||||
|
|
|
@ -77,4 +77,9 @@
|
|||
return roomEncryptionTrustLevel;
|
||||
}
|
||||
|
||||
- (BOOL)isJoined
|
||||
{
|
||||
return self.membership == MXMembershipJoin || self.membershipTransitionState == MXMembershipTransitionStateJoined;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -32,6 +32,16 @@ extension UIView {
|
|||
}
|
||||
}
|
||||
|
||||
/// Add a subview matching the safe area of the parent view using autolayout
|
||||
@objc func vc_addSubViewMatchingParentSafeArea(_ subView: UIView) {
|
||||
self.addSubview(subView)
|
||||
subView.translatesAutoresizingMaskIntoConstraints = false
|
||||
subView.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor).isActive = true
|
||||
subView.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor).isActive = true
|
||||
subView.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor).isActive = true
|
||||
subView.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor).isActive = true
|
||||
}
|
||||
|
||||
@objc func vc_removeAllSubviews() {
|
||||
for subView in self.subviews {
|
||||
subView.removeFromSuperview()
|
||||
|
|
|
@ -184,8 +184,18 @@ internal enum Asset {
|
|||
internal static let sideMenuActionIconSettings = ImageAsset(name: "side_menu_action_icon_settings")
|
||||
internal static let sideMenuActionIconShare = ImageAsset(name: "side_menu_action_icon_share")
|
||||
internal static let sideMenuIcon = ImageAsset(name: "side_menu_icon")
|
||||
internal static let sideMenuNotifIcon = ImageAsset(name: "side_menu_notif_icon")
|
||||
internal static let featureUnavaibleArtwork = ImageAsset(name: "feature_unavaible_artwork")
|
||||
internal static let featureUnavaibleArtworkDark = ImageAsset(name: "feature_unavaible_artwork_dark")
|
||||
internal static let spaceHomeIcon = ImageAsset(name: "space_home_icon")
|
||||
internal static let spaceMenuClose = ImageAsset(name: "space_menu_close")
|
||||
internal static let spaceMenuLeave = ImageAsset(name: "space_menu_leave")
|
||||
internal static let spaceMenuMembers = ImageAsset(name: "space_menu_members")
|
||||
internal static let spaceMenuRooms = ImageAsset(name: "space_menu_rooms")
|
||||
internal static let spaceRoomIcon = ImageAsset(name: "space_room_icon")
|
||||
internal static let spaceTypeIcon = ImageAsset(name: "space_type_icon")
|
||||
internal static let spaceUserIcon = ImageAsset(name: "space_user_icon")
|
||||
internal static let spacesMore = ImageAsset(name: "spaces_more")
|
||||
internal static let tabFavourites = ImageAsset(name: "tab_favourites")
|
||||
internal static let tabGroups = ImageAsset(name: "tab_groups")
|
||||
internal static let tabHome = ImageAsset(name: "tab_home")
|
||||
|
|
|
@ -244,11 +244,36 @@ internal enum StoryboardScene {
|
|||
|
||||
internal static let initialScene = InitialSceneType<Riot.SimpleScreenTemplateViewController>(storyboard: SimpleScreenTemplateViewController.self)
|
||||
}
|
||||
internal enum SpaceChildRoomDetailViewController: StoryboardType {
|
||||
internal static let storyboardName = "SpaceChildRoomDetailViewController"
|
||||
|
||||
internal static let initialScene = InitialSceneType<Riot.SpaceChildRoomDetailViewController>(storyboard: SpaceChildRoomDetailViewController.self)
|
||||
}
|
||||
internal enum SpaceDetailViewController: StoryboardType {
|
||||
internal static let storyboardName = "SpaceDetailViewController"
|
||||
|
||||
internal static let initialScene = InitialSceneType<Riot.SpaceDetailViewController>(storyboard: SpaceDetailViewController.self)
|
||||
}
|
||||
internal enum SpaceExploreRoomViewController: StoryboardType {
|
||||
internal static let storyboardName = "SpaceExploreRoomViewController"
|
||||
|
||||
internal static let initialScene = InitialSceneType<Riot.SpaceExploreRoomViewController>(storyboard: SpaceExploreRoomViewController.self)
|
||||
}
|
||||
internal enum SpaceFeatureUnaivableViewController: StoryboardType {
|
||||
internal static let storyboardName = "SpaceFeatureUnaivableViewController"
|
||||
|
||||
internal static let initialScene = InitialSceneType<Riot.SpaceFeatureUnaivableViewController>(storyboard: SpaceFeatureUnaivableViewController.self)
|
||||
}
|
||||
internal enum SpaceListViewController: StoryboardType {
|
||||
internal static let storyboardName = "SpaceListViewController"
|
||||
|
||||
internal static let initialScene = InitialSceneType<Riot.SpaceListViewController>(storyboard: SpaceListViewController.self)
|
||||
}
|
||||
internal enum SpaceMenuViewController: StoryboardType {
|
||||
internal static let storyboardName = "SpaceMenuViewController"
|
||||
|
||||
internal static let initialScene = InitialSceneType<Riot.SpaceMenuViewController>(storyboard: SpaceMenuViewController.self)
|
||||
}
|
||||
internal enum TemplateScreenViewController: StoryboardType {
|
||||
internal static let storyboardName = "TemplateScreenViewController"
|
||||
|
||||
|
|
|
@ -2083,6 +2083,26 @@ public class VectorL10n: NSObject {
|
|||
public static var leave: String {
|
||||
return VectorL10n.tr("Vector", "leave")
|
||||
}
|
||||
/// Leave all rooms and spaces
|
||||
public static var leaveSpaceAndAllRoomsAction: String {
|
||||
return VectorL10n.tr("Vector", "leave_space_and_all_rooms_action")
|
||||
}
|
||||
/// Are you sure you want to leave %@? Do you also want to leave all rooms and spaces of this space?
|
||||
public static func leaveSpaceMessage(_ p1: String) -> String {
|
||||
return VectorL10n.tr("Vector", "leave_space_message", p1)
|
||||
}
|
||||
/// You are admin of this space, ensure that you have transferred admin right to another member before leaving.
|
||||
public static var leaveSpaceMessageAdminWarning: String {
|
||||
return VectorL10n.tr("Vector", "leave_space_message_admin_warning")
|
||||
}
|
||||
/// Don't leave any rooms
|
||||
public static var leaveSpaceOnlyAction: String {
|
||||
return VectorL10n.tr("Vector", "leave_space_only_action")
|
||||
}
|
||||
/// Leave %@
|
||||
public static func leaveSpaceTitle(_ p1: String) -> String {
|
||||
return VectorL10n.tr("Vector", "leave_space_title", p1)
|
||||
}
|
||||
/// Less
|
||||
public static var less: String {
|
||||
return VectorL10n.tr("Vector", "less")
|
||||
|
@ -2191,6 +2211,10 @@ public class VectorL10n: NSObject {
|
|||
public static var on: String {
|
||||
return VectorL10n.tr("Vector", "on")
|
||||
}
|
||||
/// Open
|
||||
public static var `open`: String {
|
||||
return VectorL10n.tr("Vector", "open")
|
||||
}
|
||||
/// or
|
||||
public static var or: String {
|
||||
return VectorL10n.tr("Vector", "or")
|
||||
|
@ -3419,6 +3443,10 @@ public class VectorL10n: NSObject {
|
|||
public static var roomRecentsStartChatWith: String {
|
||||
return VectorL10n.tr("Vector", "room_recents_start_chat_with")
|
||||
}
|
||||
/// SUGGESTED ROOMS
|
||||
public static var roomRecentsSuggestedRoomsSection: String {
|
||||
return VectorL10n.tr("Vector", "room_recents_suggested_rooms_section")
|
||||
}
|
||||
/// Can't find this room. Make sure it exists
|
||||
public static var roomRecentsUnknownRoomErrorMessage: String {
|
||||
return VectorL10n.tr("Vector", "room_recents_unknown_room_error_message")
|
||||
|
@ -4791,6 +4819,14 @@ public class VectorL10n: NSObject {
|
|||
public static var socialLoginListTitleSignUp: String {
|
||||
return VectorL10n.tr("Vector", "social_login_list_title_sign_up")
|
||||
}
|
||||
/// Change space avatar
|
||||
public static var spaceAvatarViewAccessibilityHint: String {
|
||||
return VectorL10n.tr("Vector", "space_avatar_view_accessibility_hint")
|
||||
}
|
||||
/// avatar
|
||||
public static var spaceAvatarViewAccessibilityLabel: String {
|
||||
return VectorL10n.tr("Vector", "space_avatar_view_accessibility_label")
|
||||
}
|
||||
/// BETA
|
||||
public static var spaceBetaAnnounceBadge: String {
|
||||
return VectorL10n.tr("Vector", "space_beta_announce_badge")
|
||||
|
@ -4819,6 +4855,78 @@ public class VectorL10n: NSObject {
|
|||
public static var spaceFeatureUnavailableTitle: String {
|
||||
return VectorL10n.tr("Vector", "space_feature_unavailable_title")
|
||||
}
|
||||
/// Ban from this space
|
||||
public static var spaceParticipantsActionBan: String {
|
||||
return VectorL10n.tr("Vector", "space_participants_action_ban")
|
||||
}
|
||||
/// Remove from this space
|
||||
public static var spaceParticipantsActionRemove: String {
|
||||
return VectorL10n.tr("Vector", "space_participants_action_remove")
|
||||
}
|
||||
/// Private space
|
||||
public static var spacePrivateJoinRule: String {
|
||||
return VectorL10n.tr("Vector", "space_private_join_rule")
|
||||
}
|
||||
/// Public space
|
||||
public static var spacePublicJoinRule: String {
|
||||
return VectorL10n.tr("Vector", "space_public_join_rule")
|
||||
}
|
||||
/// space
|
||||
public static var spaceTag: String {
|
||||
return VectorL10n.tr("Vector", "space_tag")
|
||||
}
|
||||
/// Adding rooms coming soon
|
||||
public static var spacesAddRoomsComingSoonTitle: String {
|
||||
return VectorL10n.tr("Vector", "spaces_add_rooms_coming_soon_title")
|
||||
}
|
||||
/// This feature hasn’t been implemented here, but it’s on the way. For now, you can do that with Element on your computer.
|
||||
public static var spacesComingSoonDetail: String {
|
||||
return VectorL10n.tr("Vector", "spaces_coming_soon_detail")
|
||||
}
|
||||
/// Coming soon
|
||||
public static var spacesComingSoonTitle: String {
|
||||
return VectorL10n.tr("Vector", "spaces_coming_soon_title")
|
||||
}
|
||||
/// Some rooms may be hidden because they’re private and you need an invite.
|
||||
public static var spacesEmptySpaceDetail: String {
|
||||
return VectorL10n.tr("Vector", "spaces_empty_space_detail")
|
||||
}
|
||||
/// This space has no rooms (yet)
|
||||
public static var spacesEmptySpaceTitle: String {
|
||||
return VectorL10n.tr("Vector", "spaces_empty_space_title")
|
||||
}
|
||||
/// Explore rooms
|
||||
public static var spacesExploreRooms: String {
|
||||
return VectorL10n.tr("Vector", "spaces_explore_rooms")
|
||||
}
|
||||
/// Home
|
||||
public static var spacesHomeSpaceTitle: String {
|
||||
return VectorL10n.tr("Vector", "spaces_home_space_title")
|
||||
}
|
||||
/// Invites coming soon
|
||||
public static var spacesInvitesComingSoonTitle: String {
|
||||
return VectorL10n.tr("Vector", "spaces_invites_coming_soon_title")
|
||||
}
|
||||
/// Spaces
|
||||
public static var spacesLeftPanelTitle: String {
|
||||
return VectorL10n.tr("Vector", "spaces_left_panel_title")
|
||||
}
|
||||
/// Looking for someone not in %@? For now, you can invite them on web or desktop.
|
||||
public static func spacesNoMemberFoundDetail(_ p1: String) -> String {
|
||||
return VectorL10n.tr("Vector", "spaces_no_member_found_detail", p1)
|
||||
}
|
||||
/// No results found
|
||||
public static var spacesNoResultFoundTitle: String {
|
||||
return VectorL10n.tr("Vector", "spaces_no_result_found_title")
|
||||
}
|
||||
/// Some results may be hidden because they’re private and you need an invite to join them.
|
||||
public static var spacesNoRoomFoundDetail: String {
|
||||
return VectorL10n.tr("Vector", "spaces_no_room_found_detail")
|
||||
}
|
||||
/// Suggested
|
||||
public static var spacesSuggestedRoom: String {
|
||||
return VectorL10n.tr("Vector", "spaces_suggested_room")
|
||||
}
|
||||
/// Start
|
||||
public static var start: String {
|
||||
return VectorL10n.tr("Vector", "start")
|
||||
|
|
|
@ -34,6 +34,10 @@ extension UserSessionsService {
|
|||
@objcMembers
|
||||
class UserSessionsService: NSObject {
|
||||
|
||||
// MARK: - Singleton
|
||||
|
||||
static public let shared: UserSessionsService = UserSessionsService()
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
|
|
@ -97,6 +97,14 @@
|
|||
*/
|
||||
- (instancetype)initWithPublicRoom:(MXPublicRoom*)publicRoom andSession:(MXSession*)mxSession;
|
||||
|
||||
/**
|
||||
Contructors.
|
||||
|
||||
@param childInfo MXSpaceChildInfo instance that describes the child.
|
||||
@param mxSession the session to open the room preview with.
|
||||
*/
|
||||
- (instancetype)initWithSpaceChildInfo:(MXSpaceChildInfo*)childInfo andSession:(MXSession*)mxSession;
|
||||
|
||||
/**
|
||||
Attempt to peek into the room to get room data (state, messages history, etc).
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
|
||||
#import "RoomPreviewData.h"
|
||||
#import <MatrixSDK-Swift.h>
|
||||
|
||||
@implementation RoomPreviewData
|
||||
|
||||
|
@ -79,6 +80,21 @@
|
|||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithSpaceChildInfo:(MXSpaceChildInfo*)childInfo andSession:(MXSession*)mxSession
|
||||
{
|
||||
self = [self init];
|
||||
if (self)
|
||||
{
|
||||
_roomId = childInfo.childRoomId;
|
||||
_roomName = childInfo.name;
|
||||
_roomAvatarUrl = childInfo.avatarUrl;
|
||||
_roomTopic = childInfo.topic;
|
||||
_numJoinedMembers = childInfo.activeMemberCount;
|
||||
_mxSession = mxSession;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
if (_roomDataSource)
|
||||
|
|
|
@ -55,6 +55,8 @@ final class AppCoordinator: NSObject, AppCoordinatorType {
|
|||
private var mainMatrixSession: MXSession? {
|
||||
return self.userSessionsService.mainUserSession?.matrixSession
|
||||
}
|
||||
|
||||
private var currentSpaceId: String?
|
||||
|
||||
// MARK: Public
|
||||
|
||||
|
@ -65,7 +67,7 @@ final class AppCoordinator: NSObject, AppCoordinatorType {
|
|||
init(router: RootRouterType, window: UIWindow) {
|
||||
self.rootRouter = router
|
||||
self.customSchemeURLParser = CustomSchemeURLParser()
|
||||
self.userSessionsService = UserSessionsService()
|
||||
self.userSessionsService = UserSessionsService.shared
|
||||
|
||||
super.init()
|
||||
|
||||
|
@ -148,7 +150,7 @@ final class AppCoordinator: NSObject, AppCoordinatorType {
|
|||
|
||||
private func addSideMenu() {
|
||||
let appInfo = AppInfo.current
|
||||
let coordinatorParameters = SideMenuCoordinatorParameters(userSessionsService: self.userSessionsService, appInfo: appInfo)
|
||||
let coordinatorParameters = SideMenuCoordinatorParameters(appNavigator: self.appNavigator, userSessionsService: self.userSessionsService, appInfo: appInfo)
|
||||
|
||||
let coordinator = SideMenuCoordinator(parameters: coordinatorParameters)
|
||||
coordinator.delegate = self
|
||||
|
@ -187,6 +189,29 @@ final class AppCoordinator: NSObject, AppCoordinatorType {
|
|||
FLEXManager.shared.showExplorer()
|
||||
#endif
|
||||
}
|
||||
|
||||
fileprivate func navigate(to destination: AppNavigatorDestination) {
|
||||
switch destination {
|
||||
case .homeSpace:
|
||||
MXLog.verbose("Switch to home space")
|
||||
self.navigateToSpace(with: nil)
|
||||
case .space(let spaceId):
|
||||
MXLog.verbose("Switch to space with id: \(spaceId)")
|
||||
self.navigateToSpace(with: spaceId)
|
||||
}
|
||||
}
|
||||
|
||||
private func navigateToSpace(with spaceId: String?) {
|
||||
guard spaceId != self.currentSpaceId else {
|
||||
MXLog.verbose("Space with id: \(String(describing: spaceId)) is already selected")
|
||||
return
|
||||
}
|
||||
|
||||
self.currentSpaceId = spaceId
|
||||
|
||||
// Reload split view with selected space id
|
||||
self.splitViewCoordinator?.start(with: spaceId)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - LegacyAppDelegateDelegate
|
||||
|
@ -218,6 +243,10 @@ extension AppCoordinator: LegacyAppDelegateDelegate {
|
|||
func legacyAppDelegate(_ legacyAppDelegate: LegacyAppDelegate!, didRemove account: MXKAccount!) {
|
||||
self.userSessionsService.removeUserSession(relatedToAccount: account)
|
||||
}
|
||||
|
||||
func legacyAppDelegate(_ legacyAppDelegate: LegacyAppDelegate!, didNavigateToSpaceWithId spaceId: String!) {
|
||||
self.sideMenuCoordinator?.select(spaceWithId: spaceId)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SplitViewCoordinatorDelegate
|
||||
|
@ -239,6 +268,8 @@ extension AppCoordinator: SideMenuCoordinatorDelegate {
|
|||
fileprivate class AppNavigator: AppNavigatorProtocol {
|
||||
// swiftlint:enable private_over_fileprivate
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
private unowned let appCoordinator: AppCoordinator
|
||||
|
||||
let alert: AlertPresentable
|
||||
|
@ -251,8 +282,16 @@ fileprivate class AppNavigator: AppNavigatorProtocol {
|
|||
return SideMenuPresenter(sideMenuCoordinator: sideMenuCoordinator)
|
||||
}()
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(appCoordinator: AppCoordinator) {
|
||||
self.appCoordinator = appCoordinator
|
||||
self.alert = AppAlertPresenter(legacyAppDelegate: appCoordinator.legacyAppDelegate)
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func navigate(to destination: AppNavigatorDestination) {
|
||||
self.appCoordinator.navigate(to: destination)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,9 +18,13 @@ import Foundation
|
|||
|
||||
/// AppNavigatorProtocol abstract a navigator at app level.
|
||||
/// It enables to perform the navigation within the global app scope (open the side menu, open a room and so on)
|
||||
/// Note: Use a destination enum like presented here https://www.swiftbysundell.com/articles/navigation-in-swift/#where-to-navigator or use simple methods like Element Android Navigator
|
||||
/// Note: Presentation of the pattern here https://www.swiftbysundell.com/articles/navigation-in-swift/#where-to-navigator
|
||||
protocol AppNavigatorProtocol {
|
||||
|
||||
var sideMenu: SideMenuPresentable { get }
|
||||
var alert: AlertPresentable { get }
|
||||
|
||||
/// Navigate to a destination screen or a state
|
||||
/// Do not use protocol with associatedtype for the moment like presented here https://www.swiftbysundell.com/articles/navigation-in-swift/#where-to-navigator use a separate enum
|
||||
func navigate(to destination: AppNavigatorDestination)
|
||||
}
|
27
Riot/Modules/Application/AppNavigatorDestination.swift
Normal file
|
@ -0,0 +1,27 @@
|
|||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Supported destinations used by AppNavigator to navigate in screen hierarchy
|
||||
enum AppNavigatorDestination {
|
||||
|
||||
/// Show home space
|
||||
case homeSpace
|
||||
|
||||
/// Show a space with specific id
|
||||
case space(_ spaceId: String)
|
||||
}
|
|
@ -241,6 +241,29 @@ UINavigationControllerDelegate
|
|||
*/
|
||||
- (BOOL)handleUniversalLinkURL:(NSURL*)universalLinkURL;
|
||||
|
||||
/**
|
||||
Extract params from the URL fragment part (after '#') of a vector.im Universal link:
|
||||
|
||||
The fragment can contain a '?'. So there are two kinds of parameters: path params and query params.
|
||||
It is in the form of /[pathParam1]/[pathParam2]?[queryParam1Key]=[queryParam1Value]&[queryParam2Key]=[queryParam2Value]
|
||||
@note this method should be private but is used by RoomViewController. This should be moved to a univresal link parser class
|
||||
|
||||
@param fragment the fragment to parse.
|
||||
@param outPathParams the decoded path params.
|
||||
@param outQueryParams the decoded query params. If there is no query params, it will be nil.
|
||||
*/
|
||||
- (void)parseUniversalLinkFragment:(NSString*)fragment outPathParams:(NSArray<NSString*> **)outPathParams outQueryParams:(NSMutableDictionary **)outQueryParams;
|
||||
|
||||
/**
|
||||
Open the dedicated space with the given ID.
|
||||
|
||||
This method will open only joined or invited spaces.
|
||||
@note this method is temporary and should be moved to a dedicated coordinator
|
||||
|
||||
@param spaceId ID of the space.
|
||||
*/
|
||||
- (void)openSpaceWithId:(NSString*)spaceId;
|
||||
|
||||
#pragma mark - App version management
|
||||
|
||||
/**
|
||||
|
@ -271,4 +294,6 @@ UINavigationControllerDelegate
|
|||
|
||||
- (void)legacyAppDelegate:(LegacyAppDelegate*)legacyAppDelegate didRemoveAccount:(MXKAccount*)account;
|
||||
|
||||
- (void)legacyAppDelegate:(LegacyAppDelegate*)legacyAppDelegate didNavigateToSpaceWithId:(NSString*)spaceId;
|
||||
|
||||
@end
|
||||
|
|
|
@ -87,7 +87,7 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe
|
|||
|
||||
NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUniversalLinkDidChangeNotification";
|
||||
|
||||
@interface LegacyAppDelegate () <GDPRConsentViewControllerDelegate, KeyVerificationCoordinatorBridgePresenterDelegate, ServiceTermsModalCoordinatorBridgePresenterDelegate, PushNotificationServiceDelegate, SetPinCoordinatorBridgePresenterDelegate, CallPresenterDelegate>
|
||||
@interface LegacyAppDelegate () <GDPRConsentViewControllerDelegate, KeyVerificationCoordinatorBridgePresenterDelegate, ServiceTermsModalCoordinatorBridgePresenterDelegate, PushNotificationServiceDelegate, SetPinCoordinatorBridgePresenterDelegate, CallPresenterDelegate, SpaceDetailPresenterDelegate>
|
||||
{
|
||||
/**
|
||||
Reachability observer
|
||||
|
@ -204,6 +204,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
|||
@property (nonatomic, strong) ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter;
|
||||
@property (nonatomic, strong) SlidingModalPresenter *slidingModalPresenter;
|
||||
@property (nonatomic, strong) SetPinCoordinatorBridgePresenter *setPinCoordinatorBridgePresenter;
|
||||
@property (nonatomic, strong) SpaceDetailPresenter *spaceDetailPresenter;
|
||||
|
||||
/**
|
||||
Used to manage on boarding steps, like create DM with riot bot
|
||||
|
@ -1344,8 +1345,11 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
|||
|
||||
if (room.summary.roomType == MXRoomTypeSpace)
|
||||
{
|
||||
// Indicates that spaces are not supported
|
||||
[self.spaceFeatureUnavailablePresenter presentUnavailableFeatureFrom:self.presentedViewController animated:YES];
|
||||
[self restoreInitialDisplay:^{
|
||||
self.spaceDetailPresenter = [SpaceDetailPresenter new];
|
||||
self.spaceDetailPresenter.delegate = self;
|
||||
[self.spaceDetailPresenter presentForSpaceWithId:room.roomId from:self.masterNavigationController sourceView:nil session:account.mxSession animated:YES];
|
||||
}];
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1361,9 +1365,9 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
|||
// So, come back to the home VC and show its loading wheel while processing
|
||||
[self restoreInitialDisplay:^{
|
||||
|
||||
if ([_masterTabBarController.selectedViewController isKindOfClass:MXKViewController.class])
|
||||
if ([_masterTabBarController.selectedViewController isKindOfClass:MXKActivityHandlingViewController.class])
|
||||
{
|
||||
MXKViewController *homeViewController = (MXKViewController*)_masterTabBarController.selectedViewController;
|
||||
MXKActivityHandlingViewController *homeViewController = (MXKActivityHandlingViewController*)_masterTabBarController.selectedViewController;
|
||||
|
||||
[homeViewController startActivityIndicator];
|
||||
|
||||
|
@ -1458,24 +1462,21 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
|||
roomPreviewData.viaServers = queryParams[@"via"];
|
||||
}
|
||||
|
||||
// Is it a link to an event of a room?
|
||||
// If yes, the event will be displayed once the room is joined
|
||||
roomPreviewData.eventId = (pathParams.count >= 3) ? pathParams[2] : nil;
|
||||
|
||||
// Try to get more information about the room before opening its preview
|
||||
[roomPreviewData peekInRoom:^(BOOL succeeded) {
|
||||
|
||||
// Note: the activity indicator will not disappear if the session is not ready
|
||||
[homeViewController stopActivityIndicator];
|
||||
|
||||
// If no data is available for this room, we name it with the known room alias (if any).
|
||||
if (!succeeded && universalLinkFragmentPendingRoomAlias[roomIdOrAlias])
|
||||
[account.mxSession.matrixRestClient roomSummaryWith:roomIdOrAlias via:roomPreviewData.viaServers success:^(MXPublicRoom *room) {
|
||||
if ([room.roomTypeString isEqualToString:MXRoomTypeStringSpace])
|
||||
{
|
||||
roomPreviewData.roomName = universalLinkFragmentPendingRoomAlias[roomIdOrAlias];
|
||||
[homeViewController stopActivityIndicator];
|
||||
|
||||
self.spaceDetailPresenter = [SpaceDetailPresenter new];
|
||||
self.spaceDetailPresenter.delegate = self;
|
||||
[self.spaceDetailPresenter presentForSpaceWithPublicRoom:room from:self.masterNavigationController sourceView:nil session:account.mxSession animated:YES];
|
||||
}
|
||||
universalLinkFragmentPendingRoomAlias = nil;
|
||||
|
||||
[self showRoomPreview:roomPreviewData];
|
||||
else
|
||||
{
|
||||
[self peekInRoomWithId:roomIdOrAlias forPreviewData:roomPreviewData params:pathParams];
|
||||
}
|
||||
} failure:^(NSError *error) {
|
||||
[self peekInRoomWithId:roomIdOrAlias forPreviewData:roomPreviewData params:pathParams];
|
||||
}];
|
||||
}
|
||||
|
||||
|
@ -1607,6 +1608,33 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
|||
}
|
||||
}
|
||||
|
||||
- (void)peekInRoomWithId:(NSString*)roomIdOrAlias forPreviewData:(RoomPreviewData *)roomPreviewData params:(NSArray<NSString*> *)pathParams
|
||||
{
|
||||
// Is it a link to an event of a room?
|
||||
// If yes, the event will be displayed once the room is joined
|
||||
roomPreviewData.eventId = (pathParams.count >= 3) ? pathParams[2] : nil;
|
||||
|
||||
MXWeakify(self);
|
||||
// Try to get more information about the room before opening its preview
|
||||
[roomPreviewData peekInRoom:^(BOOL succeeded) {
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
|
||||
MXKViewController *homeViewController = (MXKViewController*)self.masterTabBarController.selectedViewController;
|
||||
|
||||
// Note: the activity indicator will not disappear if the session is not ready
|
||||
[homeViewController stopActivityIndicator];
|
||||
|
||||
// If no data is available for this room, we name it with the known room alias (if any).
|
||||
if (!succeeded && self->universalLinkFragmentPendingRoomAlias[roomIdOrAlias])
|
||||
{
|
||||
roomPreviewData.roomName = self->universalLinkFragmentPendingRoomAlias[roomIdOrAlias];
|
||||
}
|
||||
self->universalLinkFragmentPendingRoomAlias = nil;
|
||||
|
||||
[self showRoomPreview:roomPreviewData];
|
||||
}];
|
||||
}
|
||||
|
||||
/**
|
||||
Extract params from the URL fragment part (after '#') of a vector.im Universal link:
|
||||
|
||||
|
@ -4382,4 +4410,50 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
|||
[URLPreviewService.shared clearStore];
|
||||
}
|
||||
|
||||
#pragma mark - Spaces
|
||||
|
||||
-(void)openSpaceWithId:(NSString *)spaceId
|
||||
{
|
||||
MXSession *session = mxSessionArray.firstObject;
|
||||
if ([session.spaceService getSpaceWithId:spaceId]) {
|
||||
[self restoreInitialDisplay:^{
|
||||
[self.delegate legacyAppDelegate:self didNavigateToSpaceWithId:spaceId];
|
||||
}];
|
||||
}
|
||||
else
|
||||
{
|
||||
MXWeakify(self);
|
||||
__block __weak id observer = [[NSNotificationCenter defaultCenter] addObserverForName:MXSpaceService.didBuildSpaceGraph object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:observer];
|
||||
|
||||
if ([session.spaceService getSpaceWithId:spaceId]) {
|
||||
[self restoreInitialDisplay:^{
|
||||
[self.delegate legacyAppDelegate:self didNavigateToSpaceWithId:spaceId];
|
||||
}];
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - SpaceDetailPresenterDelegate
|
||||
|
||||
- (void)spaceDetailPresenterDidComplete:(SpaceDetailPresenter *)presenter
|
||||
{
|
||||
self.spaceDetailPresenter = nil;
|
||||
}
|
||||
|
||||
- (void)spaceDetailPresenter:(SpaceDetailPresenter *)presenter didOpenSpaceWithId:(NSString *)spaceId
|
||||
{
|
||||
self.spaceDetailPresenter = nil;
|
||||
[self openSpaceWithId:spaceId];
|
||||
}
|
||||
|
||||
- (void)spaceDetailPresenter:(SpaceDetailPresenter *)presenter didJoinSpaceWithId:(NSString *)spaceId
|
||||
{
|
||||
self.spaceDetailPresenter = nil;
|
||||
[self openSpaceWithId:spaceId];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -32,7 +32,7 @@ class AvatarView: UIView, Themable {
|
|||
|
||||
// MARK: Private
|
||||
|
||||
private var theme: Theme?
|
||||
private(set) var theme: Theme?
|
||||
|
||||
// MARK: Public
|
||||
|
||||
|
@ -106,7 +106,18 @@ class AvatarView: UIView, Themable {
|
|||
return
|
||||
}
|
||||
|
||||
let defaultavatarImage = AvatarGenerator.generateAvatar(forMatrixItem: viewData.matrixItemId, withDisplayName: viewData.displayName)
|
||||
let defaultAvatarImage: UIImage?
|
||||
var defaultAvatarImageContentMode: UIView.ContentMode = .scaleAspectFill
|
||||
|
||||
switch viewData.fallbackImage {
|
||||
case .matrixItem(let matrixItemId, let matrixItemDisplayName):
|
||||
defaultAvatarImage = AvatarGenerator.generateAvatar(forMatrixItem: matrixItemId, withDisplayName: matrixItemDisplayName)
|
||||
case .image(let image, let contentMode):
|
||||
defaultAvatarImage = image
|
||||
defaultAvatarImageContentMode = contentMode ?? .scaleAspectFill
|
||||
case .none:
|
||||
defaultAvatarImage = nil
|
||||
}
|
||||
|
||||
if let avatarUrl = viewData.avatarUrl {
|
||||
avatarImageView.setImageURI(avatarUrl,
|
||||
|
@ -114,13 +125,13 @@ class AvatarView: UIView, Themable {
|
|||
andImageOrientation: .up,
|
||||
toFitViewSize: avatarImageView.frame.size,
|
||||
with: MXThumbnailingMethodScale,
|
||||
previewImage: defaultavatarImage,
|
||||
previewImage: defaultAvatarImage,
|
||||
mediaManager: viewData.mediaManager)
|
||||
avatarImageView.contentMode = .scaleAspectFill
|
||||
} else {
|
||||
avatarImageView.image = defaultavatarImage
|
||||
avatarImageView.image = defaultAvatarImage
|
||||
avatarImageView.contentMode = defaultAvatarImageContentMode
|
||||
}
|
||||
|
||||
avatarImageView.contentMode = .scaleAspectFill
|
||||
}
|
||||
|
||||
func updateView() {
|
||||
|
|
34
Riot/Modules/Common/Avatar/AvatarViewData.swift
Normal file
|
@ -0,0 +1,34 @@
|
|||
//
|
||||
// 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
|
||||
|
||||
struct AvatarViewData: AvatarViewDataProtocol {
|
||||
/// Matrix item identifier (user id or room id)
|
||||
var matrixItemId: String
|
||||
|
||||
/// Matrix item display name (user or room display name)
|
||||
var displayName: String?
|
||||
|
||||
/// Matrix item avatar URL (user or room avatar url)
|
||||
var avatarUrl: String?
|
||||
|
||||
/// Matrix media handler
|
||||
var mediaManager: MXMediaManager
|
||||
|
||||
/// Fallback image used when avatarUrl is nil
|
||||
var fallbackImage: AvatarFallbackImage?
|
||||
}
|
|
@ -14,7 +14,18 @@
|
|||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
enum AvatarFallbackImage {
|
||||
|
||||
/// matrixItem represent a Matrix item like a room, space, user
|
||||
/// matrixItemId: Matrix item identifier (user id or room id)
|
||||
/// displayName: Matrix item display name (user or room display name)
|
||||
case matrixItem(_ matrixItemId: String, _ displayName: String?)
|
||||
|
||||
/// Normal image with optional content mode
|
||||
case image(_ image: UIImage, _ contentMode: UIView.ContentMode? = nil)
|
||||
}
|
||||
|
||||
/// AvatarViewDataProtocol describe a view data that should be given to an AvatarView sublcass
|
||||
protocol AvatarViewDataProtocol: AvatarProtocol {
|
||||
|
@ -25,8 +36,11 @@ protocol AvatarViewDataProtocol: AvatarProtocol {
|
|||
var displayName: String? { get }
|
||||
|
||||
/// Matrix item avatar URL (user or room avatar url)
|
||||
var avatarUrl: String? { get }
|
||||
var avatarUrl: String? { get }
|
||||
|
||||
/// Matrix media handler
|
||||
var mediaManager: MXMediaManager { get }
|
||||
|
||||
/// Fallback image used when avatarUrl is nil
|
||||
var fallbackImage: AvatarFallbackImage? { get }
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#import "RecentCellData.h"
|
||||
|
||||
#import "MXRoom+Riot.h"
|
||||
#import "MatrixSDK-Swift.h"
|
||||
|
||||
@implementation RecentCellData
|
||||
// trick to hide the mother class property as it is readonly one.
|
||||
|
@ -57,7 +58,7 @@
|
|||
- (void)update
|
||||
{
|
||||
[super update];
|
||||
roomDisplayname = self.roomSummary.displayname;
|
||||
roomDisplayname = self.spaceChildInfo ? self.spaceChildInfo.name: self.roomSummary.displayname;
|
||||
if (!roomDisplayname.length)
|
||||
{
|
||||
roomDisplayname = [NSBundle mxk_localizedStringForKey:@"room_displayname_empty_room"];
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
|
||||
#import "PublicRoomsDirectoryDataSource.h"
|
||||
|
||||
@class MXSpace;
|
||||
|
||||
/**
|
||||
List the different modes used to prepare the recents data source.
|
||||
Each mode corresponds to an application tab: Home, Favourites, People and Rooms.
|
||||
|
@ -72,6 +74,7 @@ extern NSString *const kRecentsDataSourceTapOnDirectoryServerChange;
|
|||
@property (nonatomic) NSInteger conversationSection;
|
||||
@property (nonatomic) NSInteger lowPrioritySection;
|
||||
@property (nonatomic) NSInteger serverNoticeSection;
|
||||
@property (nonatomic) NSInteger suggestedRoomsSection;
|
||||
|
||||
@property (nonatomic, readonly) NSArray* invitesCellDataArray;
|
||||
@property (nonatomic, readonly) NSArray* favoriteCellDataArray;
|
||||
|
@ -79,6 +82,7 @@ extern NSString *const kRecentsDataSourceTapOnDirectoryServerChange;
|
|||
@property (nonatomic, readonly) NSArray* conversationCellDataArray;
|
||||
@property (nonatomic, readonly) NSArray* lowPriorityCellDataArray;
|
||||
@property (nonatomic, readonly) NSArray* serverNoticeCellDataArray;
|
||||
@property (nonatomic, readonly) NSArray* suggestedRoomCellDataArray;
|
||||
|
||||
@property (nonatomic, readonly) SecureBackupBannerDisplay secureBackupBannerDisplay;
|
||||
@property (nonatomic, readonly) CrossSigningBannerDisplay crossSigningBannerDisplay;
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
#define RECENTSDATASOURCE_SECTION_LOWPRIORITY 0x10
|
||||
#define RECENTSDATASOURCE_SECTION_SERVERNOTICE 0x20
|
||||
#define RECENTSDATASOURCE_SECTION_PEOPLE 0x40
|
||||
#define RECENTSDATASOURCE_SECTION_SUGGESTED 0x80
|
||||
|
||||
#define RECENTSDATASOURCE_DEFAULT_SECTION_HEADER_HEIGHT 30.0
|
||||
|
||||
|
@ -60,7 +61,7 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou
|
|||
@end
|
||||
|
||||
@implementation RecentsDataSource
|
||||
@synthesize directorySection, invitesSection, favoritesSection, peopleSection, conversationSection, lowPrioritySection, serverNoticeSection, secureBackupBannerSection, crossSigningBannerSection;
|
||||
@synthesize directorySection, invitesSection, favoritesSection, peopleSection, conversationSection, lowPrioritySection, serverNoticeSection, suggestedRoomsSection, secureBackupBannerSection, crossSigningBannerSection;
|
||||
@synthesize hiddenCellIndexPath, droppingCellIndexPath, droppingCellBackGroundView;
|
||||
|
||||
- (instancetype)init
|
||||
|
@ -84,10 +85,17 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou
|
|||
|
||||
// Set default data and view classes
|
||||
[self registerCellDataClass:RecentCellData.class forCellIdentifier:kMXKRecentCellIdentifier];
|
||||
|
||||
[self registerSpaceServiceDidBuildGraphNotification];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self unregisterSpaceServiceDidBuildGraphNotification];
|
||||
}
|
||||
|
||||
- (void)resetSectionIndexes
|
||||
{
|
||||
crossSigningBannerSection = -1;
|
||||
|
@ -99,9 +107,9 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou
|
|||
conversationSection = -1;
|
||||
lowPrioritySection = -1;
|
||||
serverNoticeSection = -1;
|
||||
suggestedRoomsSection = -1;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Properties
|
||||
|
||||
- (NSArray *)invitesCellDataArray
|
||||
|
@ -128,6 +136,10 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou
|
|||
{
|
||||
return state.serverNoticeCellDataArray;
|
||||
}
|
||||
- (NSArray *)suggestedRoomCellDataArray
|
||||
{
|
||||
return state.suggestedRoomCellDataArray;
|
||||
}
|
||||
|
||||
- (NSUInteger)missedFavouriteDiscussionsCount
|
||||
{
|
||||
|
@ -165,7 +177,6 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou
|
|||
return state.unsentMessagesGroupDiscussionsCount;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (void)setDelegate:(id<MXKDataSourceDelegate>)delegate andRecentsDataSourceMode:(RecentsDataSourceMode)recentsDataSourceMode
|
||||
|
@ -212,6 +223,23 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou
|
|||
return stickyHeader;
|
||||
}
|
||||
|
||||
#pragma mark - Space Service notifications
|
||||
|
||||
- (void)registerSpaceServiceDidBuildGraphNotification
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(spaceServiceDidBuildGraphNotification:) name:MXSpaceService.didBuildSpaceGraph object:nil];
|
||||
}
|
||||
|
||||
- (void)spaceServiceDidBuildGraphNotification:(NSNotification*)notification
|
||||
{
|
||||
[self forceRefresh];
|
||||
}
|
||||
|
||||
- (void)unregisterSpaceServiceDidBuildGraphNotification
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:MXSpaceService.didBuildSpaceGraph object:nil];
|
||||
}
|
||||
|
||||
#pragma mark - Key backup setup banner
|
||||
|
||||
- (void)registerKeyBackupStateDidChangeNotification
|
||||
|
@ -491,6 +519,11 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou
|
|||
{
|
||||
serverNoticeSection = sectionsCount++;
|
||||
}
|
||||
|
||||
if (self.suggestedRoomCellDataArray.count > 0)
|
||||
{
|
||||
suggestedRoomsSection = sectionsCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return sectionsCount;
|
||||
|
@ -543,6 +576,10 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou
|
|||
{
|
||||
count = self.invitesCellDataArray.count;
|
||||
}
|
||||
else if (section == suggestedRoomsSection && !(shrinkedSectionsBitMask & RECENTSDATASOURCE_SECTION_SUGGESTED))
|
||||
{
|
||||
count = self.suggestedRoomCellDataArray.count;
|
||||
}
|
||||
|
||||
// Adjust this count according to the potential dragged cell.
|
||||
if ([self isMovingCellSection:section])
|
||||
|
@ -624,6 +661,11 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou
|
|||
title = NSLocalizedStringFromTable(@"room_recents_invites_section", @"Vector", nil);
|
||||
}
|
||||
}
|
||||
else if (section == suggestedRoomsSection)
|
||||
{
|
||||
count = self.suggestedRoomCellDataArray.count;
|
||||
title = NSLocalizedStringFromTable(@"room_recents_suggested_rooms_section", @"Vector", nil);
|
||||
}
|
||||
|
||||
if (count)
|
||||
{
|
||||
|
@ -675,6 +717,10 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou
|
|||
{
|
||||
sectionArray = self.serverNoticeCellDataArray;
|
||||
}
|
||||
else if (section == suggestedRoomsSection)
|
||||
{
|
||||
sectionArray = self.suggestedRoomCellDataArray;
|
||||
}
|
||||
|
||||
BOOL highlight = NO;
|
||||
for (id<MXKRecentCellDataStoring> cellData in sectionArray)
|
||||
|
@ -760,6 +806,10 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou
|
|||
{
|
||||
sectionBitwise = RECENTSDATASOURCE_SECTION_INVITES;
|
||||
}
|
||||
else if (section == suggestedRoomsSection)
|
||||
{
|
||||
sectionBitwise = RECENTSDATASOURCE_SECTION_SUGGESTED;
|
||||
}
|
||||
}
|
||||
|
||||
if (sectionBitwise)
|
||||
|
@ -957,6 +1007,13 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou
|
|||
cellData = self.invitesCellDataArray[cellDataIndex];
|
||||
}
|
||||
}
|
||||
else if (tableSection == suggestedRoomsSection)
|
||||
{
|
||||
if (cellDataIndex < self.suggestedRoomCellDataArray.count)
|
||||
{
|
||||
cellData = self.suggestedRoomCellDataArray[cellDataIndex];
|
||||
}
|
||||
}
|
||||
|
||||
return cellData;
|
||||
}
|
||||
|
@ -1013,9 +1070,19 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou
|
|||
{
|
||||
id<MXKRecentCellDataStoring> cellDataStoring = cellDataArray[index];
|
||||
|
||||
if ([roomId isEqualToString:cellDataStoring.roomSummary.roomId] && (matrixSession == cellDataStoring.roomSummary.room.mxSession))
|
||||
if (cellDataStoring.roomSummary)
|
||||
{
|
||||
return index;
|
||||
if ([roomId isEqualToString:cellDataStoring.roomSummary.roomId] && (matrixSession == cellDataStoring.roomSummary.room.mxSession))
|
||||
{
|
||||
return index;
|
||||
}
|
||||
}
|
||||
else if (cellDataStoring.spaceChildInfo)
|
||||
{
|
||||
if ([roomId isEqualToString:cellDataStoring.spaceChildInfo.name])
|
||||
{
|
||||
return index;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1117,7 +1184,22 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou
|
|||
indexPath = [NSIndexPath indexPathForRow:index inSection:serverNoticeSection];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!indexPath && (suggestedRoomsSection >= 0))
|
||||
{
|
||||
index = [self cellIndexPosWithRoomId:roomId andMatrixSession:matrixSession within:self.serverNoticeCellDataArray];
|
||||
|
||||
if (index != NSNotFound)
|
||||
{
|
||||
// Check whether the low priority rooms are shrinked
|
||||
if (shrinkedSectionsBitMask & RECENTSDATASOURCE_SECTION_SUGGESTED)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
indexPath = [NSIndexPath indexPathForRow:index inSection:serverNoticeSection];
|
||||
}
|
||||
}
|
||||
|
||||
return indexPath;
|
||||
}
|
||||
|
||||
|
@ -1133,13 +1215,13 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou
|
|||
|
||||
NSMutableArray<id<MXKRecentCellDataStoring>> *cells = [NSMutableArray new];
|
||||
NSInteger count = recentsDataSource.numberOfCells;
|
||||
|
||||
|
||||
for (NSUInteger index = 0; index < count; index++)
|
||||
{
|
||||
id<MXKRecentCellDataStoring> cell = [recentsDataSource cellDataAtIndex:index];
|
||||
[cells addObject:cell];
|
||||
}
|
||||
|
||||
|
||||
MXWeakify(self);
|
||||
[self computeStateAsyncWithCells:cells recentsDataSourceMode:self.recentsDataSourceMode matrixSession:recentsDataSource.mxSession onComplete:^(RecentsDataSourceState *newState) {
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
|
@ -1179,7 +1261,8 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou
|
|||
NSMutableArray<id<MXKRecentCellDataStoring>> *conversationCellDataArray = [NSMutableArray new];
|
||||
NSMutableArray<id<MXKRecentCellDataStoring>> *lowPriorityCellDataArray = [NSMutableArray new];
|
||||
NSMutableArray<id<MXKRecentCellDataStoring>> *serverNoticeCellDataArray = [NSMutableArray new];
|
||||
|
||||
NSMutableArray<id<MXKRecentCellDataStoring>> *suggestedRoomCellDataArray = [NSMutableArray new];
|
||||
|
||||
MissedDiscussionsCount *favouriteMissedDiscussionsCount = [MissedDiscussionsCount new];
|
||||
MissedDiscussionsCount *directMissedDiscussionsCount = [MissedDiscussionsCount new];
|
||||
MissedDiscussionsCount *groupMissedDiscussionsCount = [MissedDiscussionsCount new];
|
||||
|
@ -1206,7 +1289,7 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou
|
|||
}
|
||||
else if (room.summary.membership == MXMembershipInvite)
|
||||
{
|
||||
if (!MXSDKOptions.sharedInstance.autoAcceptRoomInvites)
|
||||
if (room.summary.roomType != MXRoomTypeSpace && !MXSDKOptions.sharedInstance.autoAcceptRoomInvites)
|
||||
{
|
||||
[invitesCellDataArray addObject:recentCellDataStoring];
|
||||
}
|
||||
|
@ -1215,6 +1298,14 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou
|
|||
{
|
||||
[peopleCellDataArray addObject:recentCellDataStoring];
|
||||
}
|
||||
else if (recentCellDataStoring.isSuggestedRoom)
|
||||
{
|
||||
MXRoomSummary *roomSummary = [mxSession roomSummaryWithRoomId:recentCellDataStoring.spaceChildInfo.childRoomId];
|
||||
if (!roomSummary.isJoined)
|
||||
{
|
||||
[suggestedRoomCellDataArray addObject:recentCellDataStoring];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Hide spaces from home (keep space invites)
|
||||
|
@ -1253,13 +1344,22 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou
|
|||
}
|
||||
else if (recentsDataSourceMode == RecentsDataSourceModeRooms)
|
||||
{
|
||||
if (recentCellDataStoring.isSuggestedRoom)
|
||||
{
|
||||
MXRoomSummary *roomSummary = [mxSession roomSummaryWithRoomId:recentCellDataStoring.spaceChildInfo.childRoomId];
|
||||
BOOL isJoined = roomSummary.membership == MXMembershipJoin || roomSummary.membershipTransitionState == MXMembershipTransitionStateJoined;
|
||||
if (!isJoined)
|
||||
{
|
||||
[suggestedRoomCellDataArray addObject:recentCellDataStoring];
|
||||
}
|
||||
}
|
||||
// Consider only non direct rooms.
|
||||
if (!room.isDirect)
|
||||
else if (!room.isDirect)
|
||||
{
|
||||
// Keep only the invites, the favourites and the rooms without tag and room type different from space
|
||||
if (room.summary.membership == MXMembershipInvite)
|
||||
{
|
||||
if (!MXSDKOptions.sharedInstance.autoAcceptRoomInvites)
|
||||
if (room.summary.roomType != MXRoomTypeSpace && !MXSDKOptions.sharedInstance.autoAcceptRoomInvites)
|
||||
{
|
||||
[invitesCellDataArray addObject:recentCellDataStoring];
|
||||
}
|
||||
|
@ -1348,6 +1448,15 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou
|
|||
// Sort each rooms collection by considering first the rooms with some missed notifs, the rooms with unread, then the others.
|
||||
comparator = ^NSComparisonResult(id<MXKRecentCellDataStoring> recentCellData1, id<MXKRecentCellDataStoring> recentCellData2) {
|
||||
|
||||
if (recentCellData1.spaceChildInfo && !recentCellData2.spaceChildInfo)
|
||||
{
|
||||
return NSOrderedDescending;
|
||||
}
|
||||
if (recentCellData2.spaceChildInfo && !recentCellData1.spaceChildInfo)
|
||||
{
|
||||
return NSOrderedAscending;
|
||||
}
|
||||
|
||||
if (recentCellData1.roomSummary.room.sentStatus != RoomSentStatusOk
|
||||
&& recentCellData2.roomSummary.room.sentStatus == RoomSentStatusOk)
|
||||
{
|
||||
|
@ -1417,6 +1526,15 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou
|
|||
// Sort each rooms collection by considering first the rooms with some unread messages then the others.
|
||||
comparator = ^NSComparisonResult(id<MXKRecentCellDataStoring> recentCellData1, id<MXKRecentCellDataStoring> recentCellData2) {
|
||||
|
||||
if (recentCellData1.spaceChildInfo && !recentCellData2.spaceChildInfo)
|
||||
{
|
||||
return NSOrderedDescending;
|
||||
}
|
||||
if (recentCellData2.spaceChildInfo && !recentCellData1.spaceChildInfo)
|
||||
{
|
||||
return NSOrderedAscending;
|
||||
}
|
||||
|
||||
if (recentCellData1.roomSummary.room.sentStatus != RoomSentStatusOk
|
||||
&& recentCellData2.roomSummary.room.sentStatus == RoomSentStatusOk)
|
||||
{
|
||||
|
@ -1489,6 +1607,7 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou
|
|||
}
|
||||
|
||||
MXLogDebug(@"[RecentsDataSource] refreshRoomsSections: Done in %.0fms", [[NSDate date] timeIntervalSinceDate:startDate] * 1000);
|
||||
MXLogDebug(@"[Spaces] refreshRoomsSections with %ld suggested room", suggestedRoomCellDataArray.count);
|
||||
|
||||
return [[RecentsDataSourceState alloc]
|
||||
initWithInvitesCellDataArray:invitesCellDataArray
|
||||
|
@ -1497,6 +1616,7 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou
|
|||
conversationCellDataArray:conversationCellDataArray
|
||||
lowPriorityCellDataArray:lowPriorityCellDataArray
|
||||
serverNoticeCellDataArray:serverNoticeCellDataArray
|
||||
suggestedRoomCellDataArray:suggestedRoomCellDataArray
|
||||
favouriteMissedDiscussionsCount:favouriteMissedDiscussionsCount
|
||||
directMissedDiscussionsCount:directMissedDiscussionsCount
|
||||
groupMissedDiscussionsCount:groupMissedDiscussionsCount
|
||||
|
|
|
@ -29,6 +29,7 @@ class RecentsDataSourceState: NSObject {
|
|||
let conversationCellDataArray: [MXKRecentCellDataStoring]
|
||||
let lowPriorityCellDataArray: [MXKRecentCellDataStoring]
|
||||
let serverNoticeCellDataArray: [MXKRecentCellDataStoring]
|
||||
let suggestedRoomCellDataArray: [MXKRecentCellDataStoring]
|
||||
|
||||
// MARK: Notifications counts
|
||||
let favouriteMissedDiscussionsCount: MissedDiscussionsCount
|
||||
|
@ -47,6 +48,7 @@ class RecentsDataSourceState: NSObject {
|
|||
conversationCellDataArray: [MXKRecentCellDataStoring],
|
||||
lowPriorityCellDataArray: [MXKRecentCellDataStoring],
|
||||
serverNoticeCellDataArray: [MXKRecentCellDataStoring],
|
||||
suggestedRoomCellDataArray: [MXKRecentCellDataStoring],
|
||||
favouriteMissedDiscussionsCount: MissedDiscussionsCount,
|
||||
directMissedDiscussionsCount: MissedDiscussionsCount,
|
||||
groupMissedDiscussionsCount: MissedDiscussionsCount,
|
||||
|
@ -58,6 +60,7 @@ class RecentsDataSourceState: NSObject {
|
|||
self.conversationCellDataArray = conversationCellDataArray
|
||||
self.lowPriorityCellDataArray = lowPriorityCellDataArray
|
||||
self.serverNoticeCellDataArray = serverNoticeCellDataArray
|
||||
self.suggestedRoomCellDataArray = suggestedRoomCellDataArray
|
||||
self.favouriteMissedDiscussionsCount = favouriteMissedDiscussionsCount
|
||||
self.directMissedDiscussionsCount = directMissedDiscussionsCount
|
||||
self.groupMissedDiscussionsCount = groupMissedDiscussionsCount
|
||||
|
|
|
@ -72,6 +72,8 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro
|
|||
|
||||
@property (nonatomic, strong) RoomsDirectoryCoordinatorBridgePresenter *roomsDirectoryCoordinatorBridgePresenter;
|
||||
|
||||
@property (nonatomic, strong) ExploreRoomCoordinatorBridgePresenter *exploreRoomsCoordinatorBridgePresenter;
|
||||
|
||||
@property (nonatomic, strong) SpaceFeatureUnavailablePresenter *spaceFeatureUnavailablePresenter;
|
||||
|
||||
@property (nonatomic, strong) CustomSizedPresentationController *customSizedPresentationController;
|
||||
|
@ -1950,7 +1952,13 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro
|
|||
return;
|
||||
}
|
||||
|
||||
if (RiotSettings.shared.roomsAllowToJoinPublicRooms)
|
||||
if (self.dataSource.currentSpace)
|
||||
{
|
||||
self.exploreRoomsCoordinatorBridgePresenter = [[ExploreRoomCoordinatorBridgePresenter alloc] initWithSession:self.mainSession spaceId:self.dataSource.currentSpace.spaceId];
|
||||
self.exploreRoomsCoordinatorBridgePresenter.delegate = self;
|
||||
[self.exploreRoomsCoordinatorBridgePresenter presentFrom:self animated:YES];
|
||||
}
|
||||
else if (RiotSettings.shared.roomsAllowToJoinPublicRooms)
|
||||
{
|
||||
self.roomsDirectoryCoordinatorBridgePresenter = [[RoomsDirectoryCoordinatorBridgePresenter alloc] initWithSession:self.mainSession dataSource:[self.recentsDataSource.publicRoomsDirectoryDataSource copy]];
|
||||
self.roomsDirectoryCoordinatorBridgePresenter.delegate = self;
|
||||
|
@ -2064,6 +2072,18 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro
|
|||
[self dispayRoomWithRoomId:roomId inMatrixSession:matrixSession];
|
||||
}
|
||||
|
||||
- (void)recentListViewController:(MXKRecentListViewController *)recentListViewController didSelectSuggestedRoom:(MXSpaceChildInfo *)childInfo
|
||||
{
|
||||
RoomPreviewData *previewData = [[RoomPreviewData alloc] initWithSpaceChildInfo:childInfo andSession:self.mainSession];
|
||||
[self startActivityIndicator];
|
||||
MXWeakify(self);
|
||||
[previewData peekInRoom:^(BOOL succeeded) {
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
[self stopActivityIndicator];
|
||||
[[AppDelegate theDelegate].masterTabBarController showRoomPreview:previewData];
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - UISearchBarDelegate
|
||||
|
||||
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
|
||||
|
@ -2270,6 +2290,16 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro
|
|||
}
|
||||
}
|
||||
|
||||
#pragma mark - ExploreRoomCoordinatorBridgePresenterDelegate
|
||||
|
||||
- (void)exploreRoomCoordinatorBridgePresenterDelegateDidComplete:(ExploreRoomCoordinatorBridgePresenter *)coordinatorBridgePresenter {
|
||||
MXWeakify(self);
|
||||
[coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
self.exploreRoomsCoordinatorBridgePresenter = nil;
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - RoomNotificationSettingsCoordinatorBridgePresenterDelegate
|
||||
-(void)roomNotificationSettingsCoordinatorBridgePresenterDelegateDidComplete:(RoomNotificationSettingsCoordinatorBridgePresenter *)coordinatorBridgePresenter
|
||||
{
|
||||
|
|
|
@ -76,7 +76,7 @@
|
|||
self.lastEventDate.text = roomCellData.lastEventDate;
|
||||
|
||||
// Manage lastEventAttributedTextMessage optional property
|
||||
if ([roomCellData respondsToSelector:@selector(lastEventAttributedTextMessage)])
|
||||
if (!roomCellData.spaceChildInfo && [roomCellData respondsToSelector:@selector(lastEventAttributedTextMessage)])
|
||||
{
|
||||
// Force the default text color for the last message (cancel highlighted message color)
|
||||
NSMutableAttributedString *lastEventDescription = [[NSMutableAttributedString alloc] initWithAttributedString:roomCellData.lastEventAttributedTextMessage];
|
||||
|
@ -124,7 +124,14 @@
|
|||
self.roomTitle.font = [UIFont systemFontOfSize:17 weight:UIFontWeightMedium];
|
||||
}
|
||||
|
||||
[roomCellData.roomSummary setRoomAvatarImageIn:self.roomAvatar];
|
||||
if (roomCellData.spaceChildInfo)
|
||||
{
|
||||
[self.roomAvatar vc_setRoomAvatarImageWith:roomCellData.spaceChildInfo.avatarUrl displayName:roomCellData.spaceChildInfo.displayName mediaManager:roomCellData.recentsDataSource.mxSession.mediaManager];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self.roomAvatar vc_setRoomAvatarImageWith:roomCellData.roomSummary.avatar displayName:roomCellData.roomSummary.displayname mediaManager:roomCellData.roomSummary.mxSession.mediaManager];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -86,16 +86,7 @@
|
|||
{
|
||||
_showCustomAccessoryView = show;
|
||||
|
||||
if (show)
|
||||
{
|
||||
self.customAccessViewWidthConstraint.constant = 25;
|
||||
self.customAccessoryViewLeadingConstraint.constant = 13;
|
||||
}
|
||||
else
|
||||
{
|
||||
self.customAccessViewWidthConstraint.constant = 0;
|
||||
self.customAccessoryViewLeadingConstraint.constant = 0;
|
||||
}
|
||||
self.customAccessViewWidthConstraint.constant = show ? 25 : 0;
|
||||
}
|
||||
|
||||
- (void)setShowMatrixIdInDisplayName:(BOOL)showMatrixIdInDisplayName
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
#import "MXRoom+Riot.h"
|
||||
|
||||
@interface HomeViewController () <SecureBackupSetupCoordinatorBridgePresenterDelegate>
|
||||
@interface HomeViewController () <SecureBackupSetupCoordinatorBridgePresenterDelegate, SpaceMembersCoordinatorBridgePresenterDelegate>
|
||||
{
|
||||
RecentsDataSource *recentsDataSource;
|
||||
|
||||
|
@ -48,6 +48,8 @@
|
|||
|
||||
@property (nonatomic, assign, readwrite) BOOL roomListDataReady;
|
||||
|
||||
@property(nonatomic) SpaceMembersCoordinatorBridgePresenter *spaceMembersCoordinatorBridgePresenter;
|
||||
|
||||
@end
|
||||
|
||||
@implementation HomeViewController
|
||||
|
@ -254,7 +256,72 @@
|
|||
[self cancelEditionMode:YES];
|
||||
}
|
||||
|
||||
[super onPlusButtonPressed];
|
||||
if (recentsDataSource.currentSpace != nil)
|
||||
{
|
||||
[self showPlusMenuForSpace];
|
||||
}
|
||||
else
|
||||
{
|
||||
[super onPlusButtonPressed];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)showPlusMenuForSpace
|
||||
{
|
||||
__weak typeof(self) weakSelf = self;
|
||||
|
||||
[currentAlert dismissViewControllerAnimated:NO completion:nil];
|
||||
|
||||
currentAlert = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet];
|
||||
|
||||
[currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"spaces_explore_rooms", @"Vector", nil)
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction * action) {
|
||||
|
||||
if (weakSelf)
|
||||
{
|
||||
typeof(self) self = weakSelf;
|
||||
self->currentAlert = nil;
|
||||
|
||||
[self showRoomDirectory];
|
||||
}
|
||||
|
||||
}]];
|
||||
|
||||
[currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_details_people", @"Vector", nil)
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction * action) {
|
||||
|
||||
if (weakSelf)
|
||||
{
|
||||
typeof(self) self = weakSelf;
|
||||
self->currentAlert = nil;
|
||||
|
||||
self.spaceMembersCoordinatorBridgePresenter = [[SpaceMembersCoordinatorBridgePresenter alloc] initWithUserSessionsService:[UserSessionsService shared] session:self.mainSession spaceId:self.dataSource.currentSpace.spaceId];
|
||||
self.spaceMembersCoordinatorBridgePresenter.delegate = self;
|
||||
[self.spaceMembersCoordinatorBridgePresenter presentFrom:self animated:YES];
|
||||
}
|
||||
|
||||
}]];
|
||||
|
||||
|
||||
[currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"]
|
||||
style:UIAlertActionStyleCancel
|
||||
handler:^(UIAlertAction * action) {
|
||||
|
||||
if (weakSelf)
|
||||
{
|
||||
typeof(self) self = weakSelf;
|
||||
self->currentAlert = nil;
|
||||
}
|
||||
|
||||
}]];
|
||||
|
||||
[currentAlert popoverPresentationController].sourceView = plusButtonImageView;
|
||||
[currentAlert popoverPresentationController].sourceRect = plusButtonImageView.bounds;
|
||||
|
||||
[currentAlert mxk_setAccessibilityIdentifier:@"RecentsVCCreateRoomAlert"];
|
||||
[self presentViewController:currentAlert animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)cancelEditionMode:(BOOL)forceRefresh
|
||||
|
@ -562,7 +629,14 @@
|
|||
|
||||
id<MXKRecentCellDataStoring> renderedCellData = (id<MXKRecentCellDataStoring>)roomCollectionViewCell.renderedCellData;
|
||||
|
||||
[self.delegate recentListViewController:self didSelectRoom:renderedCellData.roomSummary.roomId inMatrixSession:renderedCellData.roomSummary.room.mxSession];
|
||||
if (renderedCellData.isSuggestedRoom)
|
||||
{
|
||||
[self.delegate recentListViewController:self didSelectSuggestedRoom:renderedCellData.spaceChildInfo];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self.delegate recentListViewController:self didSelectRoom:renderedCellData.roomSummary.roomId inMatrixSession:renderedCellData.roomSummary.room.mxSession];
|
||||
}
|
||||
}
|
||||
|
||||
// Hide the keyboard when user select a room
|
||||
|
@ -861,7 +935,17 @@
|
|||
+ recentsDataSource.peopleCellDataArray.count
|
||||
+ recentsDataSource.conversationCellDataArray.count
|
||||
+ recentsDataSource.lowPriorityCellDataArray.count
|
||||
+ recentsDataSource.serverNoticeCellDataArray.count;
|
||||
+ recentsDataSource.serverNoticeCellDataArray.count
|
||||
+ recentsDataSource.suggestedRoomCellDataArray.count;
|
||||
}
|
||||
|
||||
#pragma mark - SpaceMembersCoordinatorBridgePresenterDelegate
|
||||
|
||||
- (void)spaceMembersCoordinatorBridgePresenterDelegateDidComplete:(SpaceMembersCoordinatorBridgePresenter *)coordinatorBridgePresenter
|
||||
{
|
||||
[coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
|
||||
self.spaceMembersCoordinatorBridgePresenter = nil;
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
class HomeViewControllerWithBannerWrapperViewController: UIViewController, BannerPresentationProtocol {
|
||||
class HomeViewControllerWithBannerWrapperViewController: MXKActivityHandlingViewController, BannerPresentationProtocol {
|
||||
|
||||
@objc let homeViewController: HomeViewController
|
||||
private var bannerContainerView: UIView!
|
||||
|
@ -41,6 +41,8 @@ class HomeViewControllerWithBannerWrapperViewController: UIViewController, Banne
|
|||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
homeViewController.willMove(toParent: self)
|
||||
|
||||
view.backgroundColor = .clear
|
||||
|
||||
stackView = UIStackView()
|
||||
|
@ -49,7 +51,7 @@ class HomeViewControllerWithBannerWrapperViewController: UIViewController, Banne
|
|||
stackView.alignment = .fill
|
||||
|
||||
view.vc_addSubViewMatchingParent(stackView)
|
||||
|
||||
|
||||
addChild(homeViewController)
|
||||
stackView.addArrangedSubview(homeViewController.view)
|
||||
homeViewController.didMove(toParent: self)
|
||||
|
|
|
@ -130,7 +130,14 @@
|
|||
|
||||
}
|
||||
|
||||
[roomCellData.roomSummary setRoomAvatarImageIn:self.roomAvatar];
|
||||
if (roomCellData.roomSummary)
|
||||
{
|
||||
[self.roomAvatar vc_setRoomAvatarImageWith:roomCellData.roomSummary.avatar displayName:roomCellData.roomSummary.displayname mediaManager:roomCellData.roomSummary.mxSession.mediaManager];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self.roomAvatar vc_setRoomAvatarImageWith:roomCellData.spaceChildInfo.avatarUrl displayName:roomCellData.spaceChildInfo.displayName mediaManager:roomCellData.recentsDataSource.mxSession.mediaManager];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -171,7 +178,7 @@
|
|||
{
|
||||
if (roomCellData)
|
||||
{
|
||||
return roomCellData.roomSummary.roomId;
|
||||
return roomCellData.spaceChildInfo ? roomCellData.spaceChildInfo.childRoomId : roomCellData.roomSummary.roomId;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
|
|
@ -26,12 +26,14 @@
|
|||
|
||||
#import "Riot-Swift.h"
|
||||
|
||||
@interface PeopleViewController ()
|
||||
@interface PeopleViewController () <SpaceMembersCoordinatorBridgePresenterDelegate>
|
||||
{
|
||||
NSInteger directRoomsSectionNumber;
|
||||
RecentsDataSource *recentsDataSource;
|
||||
}
|
||||
|
||||
@property(nonatomic) SpaceMembersCoordinatorBridgePresenter *spaceMembersCoordinatorBridgePresenter;
|
||||
|
||||
@end
|
||||
|
||||
@implementation PeopleViewController
|
||||
|
@ -119,7 +121,16 @@
|
|||
|
||||
- (void)onPlusButtonPressed
|
||||
{
|
||||
[self performSegueWithIdentifier:@"presentStartChat" sender:self];
|
||||
if (self.dataSource.currentSpace != nil)
|
||||
{
|
||||
self.spaceMembersCoordinatorBridgePresenter = [[SpaceMembersCoordinatorBridgePresenter alloc] initWithUserSessionsService:[UserSessionsService shared] session:self.mainSession spaceId:self.dataSource.currentSpace.spaceId];
|
||||
self.spaceMembersCoordinatorBridgePresenter.delegate = self;
|
||||
[self.spaceMembersCoordinatorBridgePresenter presentFrom:self animated:YES];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self performSegueWithIdentifier:@"presentStartChat" sender:self];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
@ -172,4 +183,13 @@
|
|||
+ recentsDataSource.conversationCellDataArray.count;
|
||||
}
|
||||
|
||||
#pragma mark - SpaceMembersCoordinatorBridgePresenterDelegate
|
||||
|
||||
- (void)spaceMembersCoordinatorBridgePresenterDelegateDidComplete:(SpaceMembersCoordinatorBridgePresenter *)coordinatorBridgePresenter
|
||||
{
|
||||
[coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
|
||||
self.spaceMembersCoordinatorBridgePresenter = nil;
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -465,6 +465,11 @@
|
|||
[[AppDelegate theDelegate] presentCompleteSecurityForSession:self.mainSession];
|
||||
}
|
||||
|
||||
- (void)showRoomWithId:(NSString*)roomId
|
||||
{
|
||||
[[AppDelegate theDelegate] showRoom:roomId andEventId:nil withMatrixSession:self.mainSession];
|
||||
}
|
||||
|
||||
#pragma mark - Hide/Show navigation bar border
|
||||
|
||||
- (void)hideNavigationBarBorder:(BOOL)isHidden
|
||||
|
@ -518,7 +523,10 @@
|
|||
{
|
||||
isOneself = YES;
|
||||
|
||||
[otherActionsArray addObject:@(MXKRoomMemberDetailsActionLeave)];
|
||||
if (self.enableLeave)
|
||||
{
|
||||
[otherActionsArray addObject:@(MXKRoomMemberDetailsActionLeave)];
|
||||
}
|
||||
|
||||
if (oneSelfPowerLevel >= [powerLevels minimumPowerLevelForSendingEventAsStateEvent:kMXEventTypeStringRoomPowerLevels])
|
||||
{
|
||||
|
@ -754,10 +762,24 @@
|
|||
title = NSLocalizedStringFromTable(@"room_participants_action_leave", @"Vector", nil);
|
||||
break;
|
||||
case MXKRoomMemberDetailsActionKick:
|
||||
title = NSLocalizedStringFromTable(@"room_participants_action_remove", @"Vector", nil);
|
||||
if (self.mxRoom.summary.roomType == MXRoomTypeSpace)
|
||||
{
|
||||
title = NSLocalizedStringFromTable(@"space_participants_action_remove", @"Vector", nil);
|
||||
}
|
||||
else
|
||||
{
|
||||
title = NSLocalizedStringFromTable(@"room_participants_action_remove", @"Vector", nil);
|
||||
}
|
||||
break;
|
||||
case MXKRoomMemberDetailsActionBan:
|
||||
title = NSLocalizedStringFromTable(@"room_participants_action_ban", @"Vector", nil);
|
||||
if (self.mxRoom.summary.roomType == MXRoomTypeSpace)
|
||||
{
|
||||
title = NSLocalizedStringFromTable(@"space_participants_action_ban", @"Vector", nil);
|
||||
}
|
||||
else
|
||||
{
|
||||
title = NSLocalizedStringFromTable(@"room_participants_action_ban", @"Vector", nil);
|
||||
}
|
||||
break;
|
||||
case MXKRoomMemberDetailsActionUnban:
|
||||
title = NSLocalizedStringFromTable(@"room_participants_action_unban", @"Vector", nil);
|
||||
|
@ -1047,7 +1069,7 @@
|
|||
if (indexPath.row < directChatsArray.count)
|
||||
{
|
||||
// Open this room
|
||||
[[AppDelegate theDelegate] showRoom:directChatsArray[indexPath.row] andEventId:nil withMatrixSession:self.mainSession];
|
||||
[self showRoomWithId:directChatsArray[indexPath.row]];
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -85,6 +85,7 @@
|
|||
@property (nonatomic) BOOL enableMention;
|
||||
|
||||
@property (nonatomic) BOOL showCancelBarButtonItem;
|
||||
@property (nonatomic) BOOL showParticipantCustomAccessoryView;
|
||||
|
||||
/**
|
||||
The delegate for the view controller.
|
||||
|
|
|
@ -84,6 +84,7 @@
|
|||
// Setup `MXKViewControllerHandling` properties
|
||||
self.enableBarTintColorStatusChange = NO;
|
||||
self.rageShakeManager = [RageShakeManager sharedManager];
|
||||
self.showParticipantCustomAccessoryView = YES;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad
|
||||
|
@ -113,7 +114,11 @@
|
|||
|
||||
self.navigationItem.title = NSLocalizedStringFromTable(@"room_participants_title", @"Vector", nil);
|
||||
|
||||
if (self.mxRoom.isDirect)
|
||||
if (self.mxRoom.summary.roomType == MXRoomTypeSpace)
|
||||
{
|
||||
_searchBarView.placeholder = NSLocalizedStringFromTable(@"search_default_placeholder", @"Vector", nil);
|
||||
}
|
||||
else if (self.mxRoom.isDirect)
|
||||
{
|
||||
_searchBarView.placeholder = NSLocalizedStringFromTable(@"room_participants_filter_room_members_for_dm", @"Vector", nil);
|
||||
}
|
||||
|
@ -340,7 +345,11 @@
|
|||
{
|
||||
self.searchBarHeader.hidden = NO;
|
||||
|
||||
if (self.mxRoom.isDirect)
|
||||
if (self.mxRoom.summary.roomType == MXRoomTypeSpace)
|
||||
{
|
||||
self.searchBarView.placeholder = NSLocalizedStringFromTable(@"search_default_placeholder", @"Vector", nil);
|
||||
}
|
||||
else if (self.mxRoom.isDirect)
|
||||
{
|
||||
self.searchBarView.placeholder = NSLocalizedStringFromTable(@"room_participants_filter_room_members_for_dm", @"Vector", nil);
|
||||
}
|
||||
|
@ -870,6 +879,19 @@
|
|||
}
|
||||
}
|
||||
|
||||
- (void)showDetailFor:(MXRoomMember* _Nonnull)member from:(UIView* _Nullable)sourceView {
|
||||
memberDetailsViewController = [RoomMemberDetailsViewController roomMemberDetailsViewController];
|
||||
|
||||
// Set delegate to handle action on member (start chat, mention)
|
||||
memberDetailsViewController.delegate = self;
|
||||
memberDetailsViewController.enableMention = _enableMention;
|
||||
memberDetailsViewController.enableVoipCall = NO;
|
||||
|
||||
[memberDetailsViewController displayRoomMember:member withMatrixRoom:self.mxRoom];
|
||||
|
||||
[self pushViewController:memberDetailsViewController];
|
||||
}
|
||||
|
||||
#pragma mark - UITableView data source
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
|
||||
|
@ -948,6 +970,7 @@
|
|||
{
|
||||
ContactTableViewCell* participantCell = [tableView dequeueReusableCellWithIdentifier:@"ParticipantTableViewCellId" forIndexPath:indexPath];
|
||||
participantCell.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||
participantCell.showCustomAccessoryView = self.showParticipantCustomAccessoryView;
|
||||
|
||||
participantCell.mxRoom = self.mxRoom;
|
||||
|
||||
|
@ -1185,16 +1208,8 @@
|
|||
|
||||
if (contact.mxMember)
|
||||
{
|
||||
memberDetailsViewController = [RoomMemberDetailsViewController roomMemberDetailsViewController];
|
||||
|
||||
// Set delegate to handle action on member (start chat, mention)
|
||||
memberDetailsViewController.delegate = self;
|
||||
memberDetailsViewController.enableMention = _enableMention;
|
||||
memberDetailsViewController.enableVoipCall = NO;
|
||||
|
||||
[memberDetailsViewController displayRoomMember:contact.mxMember withMatrixRoom:self.mxRoom];
|
||||
|
||||
[self pushViewController:memberDetailsViewController];
|
||||
UITableViewCell *selectedCell = [tableView cellForRowAtIndexPath:indexPath];
|
||||
[self showDetailFor:contact.mxMember from:selectedCell];
|
||||
}
|
||||
|
||||
[tableView deselectRowAtIndexPath:indexPath animated:YES];
|
||||
|
|
|
@ -19,26 +19,18 @@ import Reusable
|
|||
|
||||
class RoomNotificationSettingsAvatarView: UIView {
|
||||
|
||||
@IBOutlet weak var avatarView: MXKImageView!
|
||||
@IBOutlet weak var avatarView: RoomAvatarView!
|
||||
@IBOutlet weak var nameLabel: UILabel!
|
||||
|
||||
func configure(viewData: AvatarViewDataProtocol) {
|
||||
let avatarImage = AvatarGenerator.generateAvatar(forMatrixItem: viewData.matrixItemId, withDisplayName: viewData.displayName)
|
||||
avatarView.fill(with: viewData)
|
||||
|
||||
if let avatarUrl = viewData.avatarUrl {
|
||||
avatarView.enableInMemoryCache = true
|
||||
|
||||
avatarView.setImageURI(avatarUrl,
|
||||
withType: nil,
|
||||
andImageOrientation: .up,
|
||||
toFitViewSize: avatarView.frame.size,
|
||||
with: MXThumbnailingMethodCrop,
|
||||
previewImage: avatarImage,
|
||||
mediaManager: viewData.mediaManager)
|
||||
} else {
|
||||
avatarView.image = avatarImage
|
||||
switch viewData.fallbackImage {
|
||||
case .matrixItem(_, let matrixItemDisplayName):
|
||||
nameLabel.text = matrixItemDisplayName
|
||||
default:
|
||||
nameLabel.text = nil
|
||||
}
|
||||
nameLabel.text = viewData.displayName
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<rect key="frame" x="0.0" y="0.0" width="414" height="192"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES"/>
|
||||
<subviews>
|
||||
<view autoresizesSubviews="NO" clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="q3Z-S1-Py9" customClass="MXKImageView">
|
||||
<view autoresizesSubviews="NO" clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="q3Z-S1-Py9" customClass="RoomAvatarView" customModule="Riot" customModuleProvider="target">
|
||||
<rect key="frame" x="167" y="20" width="80" height="80"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
|
|
127
Riot/Modules/Room/RoomViewController+Spaces.swift
Normal file
|
@ -0,0 +1,127 @@
|
|||
//
|
||||
// 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
|
||||
|
||||
/// this extension is temprorary and implements navigation to the Space bootom sheet. This should be moved to an universal link flow coordinator
|
||||
extension RoomViewController {
|
||||
@objc func handleSpaceUniversalLink(with url: URL) {
|
||||
let url = Tools.fixURL(withSeveralHashKeys: url)
|
||||
|
||||
var pathParamsObjc: NSArray?
|
||||
var queryParamsObjc: NSMutableDictionary?
|
||||
AppDelegate.theDelegate().parseUniversalLinkFragment(url?.fragment, outPathParams: &pathParamsObjc, outQueryParams: &queryParamsObjc)
|
||||
|
||||
// Sanity check
|
||||
guard let pathParams = pathParamsObjc as? [String], pathParams.count > 0 else {
|
||||
MXLog.error("[RoomViewController] Universal link: Error: No path parameters")
|
||||
return
|
||||
}
|
||||
|
||||
var roomIdOrAliasParam: String?
|
||||
var eventIdParam: String?
|
||||
var userIdParam: String?
|
||||
var groupIdParam: String?
|
||||
|
||||
// Check permalink to room or event
|
||||
if pathParams[0] == "room" && pathParams.count >= 2 {
|
||||
|
||||
// The link is the form of "/room/[roomIdOrAlias]" or "/room/[roomIdOrAlias]/[eventId]"
|
||||
roomIdOrAliasParam = pathParams[1]
|
||||
|
||||
// Is it a link to an event of a room?
|
||||
eventIdParam = pathParams.count >= 3 ? pathParams[2] : nil
|
||||
|
||||
} else if pathParams[0] == "group" && pathParams.count >= 2 {
|
||||
|
||||
// The link is the form of "/group/[groupId]"
|
||||
groupIdParam = pathParams[1]
|
||||
|
||||
} else if (pathParams[0].hasPrefix("#") || pathParams[0].hasPrefix("!")) && pathParams.count >= 1 {
|
||||
|
||||
// The link is the form of "/#/[roomIdOrAlias]" or "/#/[roomIdOrAlias]/[eventId]"
|
||||
// Such links come from matrix.to permalinks
|
||||
roomIdOrAliasParam = pathParams[0]
|
||||
eventIdParam = pathParams.count >= 2 ? pathParams[1] : nil
|
||||
|
||||
} else if pathParams[0] == "user" && pathParams.count == 2 { // Check permalink to a user
|
||||
// The link is the form of "/user/userId"
|
||||
userIdParam = pathParams[1]
|
||||
} else if pathParams[0].hasPrefix("@") && pathParams.count == 1 {
|
||||
// The link is the form of "/#/[userId]"
|
||||
// Such links come from matrix.to permalinks
|
||||
userIdParam = pathParams[0]
|
||||
}
|
||||
|
||||
guard let roomIdOrAlias = roomIdOrAliasParam else {
|
||||
AppDelegate.theDelegate().handleUniversalLinkURL(url)
|
||||
return
|
||||
}
|
||||
|
||||
self.startActivityIndicator()
|
||||
|
||||
var viaServers: [String] = []
|
||||
if let queryParams = queryParamsObjc as? [String: Any], let via = queryParams["via"] as? [String] {
|
||||
viaServers = via
|
||||
}
|
||||
|
||||
if roomIdOrAlias.hasPrefix("#") {
|
||||
self.mainSession.matrixRestClient.roomId(forRoomAlias: roomIdOrAlias) { [weak self] response in
|
||||
guard let self = self else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let roomId = response.value else {
|
||||
self.stopActivityIndicator()
|
||||
|
||||
if response.error != nil {
|
||||
let errorMessage = VectorL10n.roomDoesNotExist(roomIdOrAlias)
|
||||
AppDelegate.theDelegate().showAlert(withTitle: nil, message: errorMessage)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
self.requestSummaryAndShowSpaceDetail(forRoomWithId: roomId, via: viaServers, from: url)
|
||||
}
|
||||
} else {
|
||||
self.requestSummaryAndShowSpaceDetail(forRoomWithId: roomIdOrAlias, via: viaServers, from: url)
|
||||
}
|
||||
}
|
||||
|
||||
private func requestSummaryAndShowSpaceDetail(forRoomWithId roomId: String, via: [String], from url: URL?) {
|
||||
if self.mainSession.spaceService.getSpace(withId: roomId) != nil {
|
||||
self.stopActivityIndicator()
|
||||
self.showSpaceDetail(withId: roomId)
|
||||
return
|
||||
}
|
||||
|
||||
self.mainSession.matrixRestClient.roomSummary(with: roomId, via: via) { [weak self] response in
|
||||
guard let self = self else {
|
||||
return
|
||||
}
|
||||
|
||||
self.stopActivityIndicator()
|
||||
|
||||
guard let publicRoom = response.value, publicRoom.roomTypeString == MXRoomTypeString.space.rawValue else {
|
||||
AppDelegate.theDelegate().handleUniversalLinkURL(url)
|
||||
return
|
||||
}
|
||||
|
||||
self.showSpaceDetail(with: publicRoom)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -86,6 +86,10 @@ extern NSNotificationName const RoomGroupCallTileTappedNotification;
|
|||
*/
|
||||
- (void)displayRoomPreview:(RoomPreviewData*)roomPreviewData;
|
||||
|
||||
- (void)showSpaceDetailWithPublicRoom:(MXPublicRoom *)publicRoom;
|
||||
|
||||
- (void)showSpaceDetailWithId:(NSString *)spaceId;
|
||||
|
||||
/**
|
||||
Action used to handle some buttons.
|
||||
*/
|
||||
|
|
|
@ -137,7 +137,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
|
|||
@interface RoomViewController () <UISearchBarDelegate, UIGestureRecognizerDelegate, UIScrollViewAccessibilityDelegate, RoomTitleViewTapGestureDelegate, RoomParticipantsViewControllerDelegate, MXKRoomMemberDetailsViewControllerDelegate, ContactsTableViewControllerDelegate, MXServerNoticesDelegate, RoomContextualMenuViewControllerDelegate,
|
||||
ReactionsMenuViewModelCoordinatorDelegate, EditHistoryCoordinatorBridgePresenterDelegate, MXKDocumentPickerPresenterDelegate, EmojiPickerCoordinatorBridgePresenterDelegate,
|
||||
ReactionHistoryCoordinatorBridgePresenterDelegate, CameraPresenterDelegate, MediaPickerCoordinatorBridgePresenterDelegate,
|
||||
RoomDataSourceDelegate, RoomCreationModalCoordinatorBridgePresenterDelegate, RoomInfoCoordinatorBridgePresenterDelegate, DialpadViewControllerDelegate, RemoveJitsiWidgetViewDelegate, VoiceMessageControllerDelegate>
|
||||
RoomDataSourceDelegate, RoomCreationModalCoordinatorBridgePresenterDelegate, RoomInfoCoordinatorBridgePresenterDelegate, DialpadViewControllerDelegate, RemoveJitsiWidgetViewDelegate, VoiceMessageControllerDelegate, SpaceDetailPresenterDelegate>
|
||||
{
|
||||
|
||||
// The preview header
|
||||
|
@ -244,8 +244,10 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
|
|||
@property (nonatomic, strong) CustomSizedPresentationController *customSizedPresentationController;
|
||||
@property (nonatomic, getter=isActivitiesViewExpanded) BOOL activitiesViewExpanded;
|
||||
@property (nonatomic, getter=isScrollToBottomHidden) BOOL scrollToBottomHidden;
|
||||
@property (nonatomic, getter=isMissedDiscussionsBadgeHidden) BOOL missedDiscussionsBadgeHidden;
|
||||
|
||||
@property (nonatomic, strong) VoiceMessageController *voiceMessageController;
|
||||
@property (nonatomic, strong) SpaceDetailPresenter *spaceDetailPresenter;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -1423,12 +1425,6 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
|
|||
}
|
||||
}
|
||||
|
||||
- (void)setShowMissedDiscussionsBadge:(BOOL)showMissedDiscussionsBadge
|
||||
{
|
||||
missedDiscussionsBadgeLabel.hidden = !showMissedDiscussionsBadge;
|
||||
missedDiscussionsDotView.hidden = !showMissedDiscussionsBadge;
|
||||
}
|
||||
|
||||
- (void)setScrollToBottomHidden:(BOOL)scrollToBottomHidden
|
||||
{
|
||||
if (_scrollToBottomHidden != scrollToBottomHidden)
|
||||
|
@ -1452,6 +1448,13 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
|
|||
}];
|
||||
}
|
||||
|
||||
- (void)setMissedDiscussionsBadgeHidden:(BOOL)missedDiscussionsBadgeHidden{
|
||||
_missedDiscussionsBadgeHidden = missedDiscussionsBadgeHidden;
|
||||
|
||||
missedDiscussionsBadgeLabel.hidden = missedDiscussionsBadgeHidden;
|
||||
missedDiscussionsDotView.hidden = missedDiscussionsBadgeHidden;
|
||||
}
|
||||
|
||||
#pragma mark - Internals
|
||||
|
||||
- (UIBarButtonItem *)videoCallBarButtonItem
|
||||
|
@ -2190,7 +2193,8 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
|
|||
}
|
||||
else
|
||||
{
|
||||
return [[AppDelegate theDelegate] handleUniversalLinkURL:universalLinkURL];
|
||||
[self handleSpaceUniversalLinkWith:universalLinkURL];
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4961,16 +4965,16 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
|
|||
- (void)refreshMissedDiscussionsCount:(BOOL)force
|
||||
{
|
||||
// Ignore this action when no room is displayed
|
||||
if (!self.roomDataSource || !missedDiscussionsBadgeLabel
|
||||
if (!self.showMissedDiscussionsBadge || !self.roomDataSource || !missedDiscussionsBadgeLabel
|
||||
|| [UIDevice currentDevice].userInterfaceIdiom != UIUserInterfaceIdiomPhone
|
||||
|| ([[UIScreen mainScreen] nativeBounds].size.height > 2532 && UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)))
|
||||
{
|
||||
self.showMissedDiscussionsBadge = NO;
|
||||
self.missedDiscussionsBadgeHidden = YES;
|
||||
return;
|
||||
}
|
||||
|
||||
self.showMissedDiscussionsBadge = YES;
|
||||
|
||||
self.missedDiscussionsBadgeHidden = NO;
|
||||
|
||||
NSUInteger highlightCount = 0;
|
||||
NSUInteger missedCount = [[AppDelegate theDelegate].masterTabBarController missedDiscussionsCount];
|
||||
|
||||
|
@ -6478,4 +6482,37 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
|
|||
}];
|
||||
}
|
||||
|
||||
- (void)showSpaceDetailWithPublicRoom:(MXPublicRoom *)publicRoom
|
||||
{
|
||||
self.spaceDetailPresenter = [SpaceDetailPresenter new];
|
||||
self.spaceDetailPresenter.delegate = self;
|
||||
[self.spaceDetailPresenter presentForSpaceWithPublicRoom:publicRoom from:self sourceView:nil session:self.mainSession animated:YES];
|
||||
}
|
||||
|
||||
- (void)showSpaceDetailWithId:(NSString *)spaceId
|
||||
{
|
||||
self.spaceDetailPresenter = [SpaceDetailPresenter new];
|
||||
self.spaceDetailPresenter.delegate = self;
|
||||
[self.spaceDetailPresenter presentForSpaceWithId:spaceId from:self sourceView:nil session:self.mainSession animated:YES];
|
||||
}
|
||||
|
||||
#pragma mark - SpaceDetailPresenterDelegate
|
||||
|
||||
- (void)spaceDetailPresenterDidComplete:(SpaceDetailPresenter *)presenter
|
||||
{
|
||||
self.spaceDetailPresenter = nil;
|
||||
}
|
||||
|
||||
- (void)spaceDetailPresenter:(SpaceDetailPresenter *)presenter didOpenSpaceWithId:(NSString *)spaceId
|
||||
{
|
||||
self.spaceDetailPresenter = nil;
|
||||
[[LegacyAppDelegate theDelegate] openSpaceWithId:spaceId];
|
||||
}
|
||||
|
||||
- (void)spaceDetailPresenter:(SpaceDetailPresenter *)presenter didJoinSpaceWithId:(NSString *)spaceId
|
||||
{
|
||||
self.spaceDetailPresenter = nil;
|
||||
[[LegacyAppDelegate theDelegate] openSpaceWithId:spaceId];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -25,7 +25,11 @@ final class RoomAvatarView: AvatarView, NibOwnerLoadable {
|
|||
|
||||
@IBOutlet private weak var cameraBadgeContainerView: UIView!
|
||||
|
||||
// MARK: Setup
|
||||
// MARK: Public
|
||||
|
||||
var showCameraBadgeOnFallbackImage: Bool = false
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
private func commonInit() {
|
||||
}
|
||||
|
@ -74,6 +78,14 @@ final class RoomAvatarView: AvatarView, NibOwnerLoadable {
|
|||
override func updateAvatarImageView(with viewData: AvatarViewDataProtocol) {
|
||||
super.updateAvatarImageView(with: viewData)
|
||||
|
||||
self.cameraBadgeContainerView.isHidden = viewData.avatarUrl != nil
|
||||
let hideCameraBadge: Bool
|
||||
|
||||
if self.showCameraBadgeOnFallbackImage {
|
||||
hideCameraBadge = viewData.avatarUrl != nil
|
||||
} else {
|
||||
hideCameraBadge = true
|
||||
}
|
||||
|
||||
self.cameraBadgeContainerView.isHidden = hideCameraBadge
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,4 +25,8 @@ struct RoomAvatarViewData: AvatarViewDataProtocol {
|
|||
var matrixItemId: String {
|
||||
return roomId
|
||||
}
|
||||
|
||||
var fallbackImage: AvatarFallbackImage? {
|
||||
return .matrixItem(matrixItemId, displayName)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,6 +70,8 @@ final class RoomCreationIntroCellContentView: UIView, NibLoadable, Themable {
|
|||
self.addParticipantsButton.addTarget(self, action: #selector(socialButtonAction(_:)), for: .touchUpInside)
|
||||
|
||||
self.addParticipantsLabel.text = VectorL10n.roomIntroCellAddParticipantsAction
|
||||
|
||||
self.roomAvatarView.showCameraBadgeOnFallbackImage = true
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
|
|
|
@ -103,8 +103,13 @@
|
|||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
|
||||
{
|
||||
// Hide the header to merge Invites and Rooms into a single list.
|
||||
return 0.0;
|
||||
if ([tableView numberOfSections] <= 1)
|
||||
{
|
||||
// Hide the header to merge Invites and Rooms into a single list.
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return [super tableView:tableView heightForHeaderInSection:section];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
@ -155,7 +160,8 @@
|
|||
- (NSUInteger)totalItemCounts
|
||||
{
|
||||
return recentsDataSource.conversationCellDataArray.count
|
||||
+ recentsDataSource.invitesCellDataArray.count;
|
||||
+ recentsDataSource.invitesCellDataArray.count
|
||||
+ recentsDataSource.suggestedRoomCellDataArray.count;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -27,7 +27,19 @@ struct DirectoryRoomTableViewCellVM {
|
|||
|
||||
// TODO: Use AvatarView subclass in the cell view
|
||||
func setAvatar(in avatarImageView: MXKImageView) {
|
||||
let avatarImage = AvatarGenerator.generateAvatar(forMatrixItem: self.avatarViewData.matrixItemId, withDisplayName: title)
|
||||
|
||||
let defaultAvatarImage: UIImage?
|
||||
var defaultAvatarImageContentMode: UIView.ContentMode = .scaleAspectFill
|
||||
|
||||
switch self.avatarViewData.fallbackImage {
|
||||
case .matrixItem(let matrixItemId, let matrixItemDisplayName):
|
||||
defaultAvatarImage = AvatarGenerator.generateAvatar(forMatrixItem: matrixItemId, withDisplayName: matrixItemDisplayName)
|
||||
case .image(let image, let contentMode):
|
||||
defaultAvatarImage = image
|
||||
defaultAvatarImageContentMode = contentMode ?? .scaleAspectFill
|
||||
case .none:
|
||||
defaultAvatarImage = nil
|
||||
}
|
||||
|
||||
if let avatarUrl = self.avatarViewData.avatarUrl {
|
||||
avatarImageView.enableInMemoryCache = true
|
||||
|
@ -37,10 +49,12 @@ struct DirectoryRoomTableViewCellVM {
|
|||
andImageOrientation: .up,
|
||||
toFitViewSize: avatarImageView.frame.size,
|
||||
with: MXThumbnailingMethodCrop,
|
||||
previewImage: avatarImage,
|
||||
previewImage: defaultAvatarImage,
|
||||
mediaManager: self.avatarViewData.mediaManager)
|
||||
avatarImageView.contentMode = .scaleAspectFill
|
||||
} else {
|
||||
avatarImageView.image = avatarImage
|
||||
avatarImageView.image = defaultAvatarImage
|
||||
avatarImageView.contentMode = defaultAvatarImageContentMode
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,16 +22,27 @@ import SideMenu
|
|||
import SafariServices
|
||||
|
||||
class SideMenuCoordinatorParameters {
|
||||
let appNavigator: AppNavigatorProtocol
|
||||
let userSessionsService: UserSessionsService
|
||||
let appInfo: AppInfo
|
||||
|
||||
init(userSessionsService: UserSessionsService, appInfo: AppInfo) {
|
||||
init(appNavigator: AppNavigatorProtocol,
|
||||
userSessionsService: UserSessionsService,
|
||||
appInfo: AppInfo) {
|
||||
self.appNavigator = appNavigator
|
||||
self.userSessionsService = userSessionsService
|
||||
self.appInfo = appInfo
|
||||
}
|
||||
}
|
||||
|
||||
final class SideMenuCoordinator: SideMenuCoordinatorType {
|
||||
final class SideMenuCoordinator: NSObject, SideMenuCoordinatorType {
|
||||
|
||||
// MARK: - Constants
|
||||
|
||||
private enum SideMenu {
|
||||
static let widthRatio: CGFloat = 0.82
|
||||
static let maxWidthiPad: CGFloat = 320.0
|
||||
}
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
|
@ -40,12 +51,20 @@ final class SideMenuCoordinator: SideMenuCoordinatorType {
|
|||
private let parameters: SideMenuCoordinatorParameters
|
||||
private var sideMenuViewModel: SideMenuViewModelType
|
||||
|
||||
private weak var spaceListCoordinator: SpaceListCoordinatorType?
|
||||
|
||||
private lazy var sideMenuNavigationViewController: SideMenuNavigationController = {
|
||||
return self.createSideMenuNavigationController(with: self.sideMenuViewController)
|
||||
}()
|
||||
|
||||
private let sideMenuViewController: SideMenuViewController
|
||||
|
||||
let spaceMenuPresenter = SpaceMenuPresenter()
|
||||
let spaceDetailPresenter = SpaceDetailPresenter()
|
||||
|
||||
private var exploreRoomCoordinator: ExploreRoomCoordinator?
|
||||
private var membersCoordinator: SpaceMembersCoordinator?
|
||||
|
||||
// MARK: Public
|
||||
|
||||
// Must be used only internally
|
||||
|
@ -69,9 +88,13 @@ final class SideMenuCoordinator: SideMenuCoordinatorType {
|
|||
self.sideMenuViewModel.coordinatorDelegate = self
|
||||
|
||||
self.sideMenuNavigationViewController.sideMenuDelegate = self
|
||||
self.sideMenuNavigationViewController.dismissOnRotation = false
|
||||
|
||||
// Set the sideMenuNavigationViewController as default left menu
|
||||
SideMenuManager.default.leftMenuNavigationController = self.sideMenuNavigationViewController
|
||||
|
||||
self.addSpaceListIfNeeded()
|
||||
self.registerUserSessionsServiceNotifications()
|
||||
}
|
||||
|
||||
func toPresentable() -> UIViewController {
|
||||
|
@ -86,21 +109,73 @@ final class SideMenuCoordinator: SideMenuCoordinatorType {
|
|||
return self.sideMenuNavigationViewController.sideMenuManager.addPanGestureToPresent(toView: view)
|
||||
}
|
||||
|
||||
func select(spaceWithId spaceId: String) {
|
||||
self.spaceListCoordinator?.select(spaceWithId: spaceId)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func createSideMenuNavigationController(with rootViewController: UIViewController) -> SideMenuNavigationController {
|
||||
|
||||
var sideMenuSettings = SideMenuSettings()
|
||||
sideMenuSettings.presentationStyle = .viewSlideOut
|
||||
sideMenuSettings.menuWidth = self.getMenuWidth()
|
||||
|
||||
let navigationController = SideMenuNavigationController(rootViewController: rootViewController, settings: sideMenuSettings)
|
||||
|
||||
// Present side menu to the left
|
||||
navigationController.leftSide = true
|
||||
|
||||
// FIX: SideMenuSettings are not taken into account at init apply them again
|
||||
navigationController.settings = sideMenuSettings
|
||||
|
||||
return navigationController
|
||||
}
|
||||
|
||||
private func getMenuWidth() -> CGFloat {
|
||||
let appScreenRect = UIApplication.shared.keyWindow?.bounds ?? UIWindow().bounds
|
||||
let minimumSize = min(appScreenRect.width, appScreenRect.height)
|
||||
|
||||
let menuWidth: CGFloat
|
||||
|
||||
if UIDevice.current.isPhone {
|
||||
menuWidth = round(minimumSize * SideMenu.widthRatio)
|
||||
} else {
|
||||
// Set a max menu width on iPad
|
||||
menuWidth = min(round(minimumSize * SideMenu.widthRatio), SideMenu.maxWidthiPad * SideMenu.widthRatio)
|
||||
}
|
||||
|
||||
return menuWidth
|
||||
}
|
||||
|
||||
private func addSpaceListIfNeeded() {
|
||||
guard self.spaceListCoordinator == nil else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let mainMatrixSession = self.parameters.userSessionsService.mainUserSession?.matrixSession else {
|
||||
return
|
||||
}
|
||||
|
||||
self.addSpaceList(with: mainMatrixSession)
|
||||
}
|
||||
|
||||
private func addSpaceList(with matrixSession: MXSession) {
|
||||
let parameters = SpaceListCoordinatorParameters(session: matrixSession)
|
||||
|
||||
let spaceListCoordinator = SpaceListCoordinator(parameters: parameters)
|
||||
spaceListCoordinator.delegate = self
|
||||
spaceListCoordinator.start()
|
||||
|
||||
let spaceListPresentable = spaceListCoordinator.toPresentable()
|
||||
|
||||
// sideMenuViewController.spaceListContainerView can be nil, load controller view to avoid this case
|
||||
self.sideMenuViewController.loadViewIfNeeded()
|
||||
|
||||
self.sideMenuViewController.vc_addChildViewController(viewController: spaceListPresentable, onView: self.sideMenuViewController.spaceListContainerView)
|
||||
|
||||
self.add(childCoordinator: spaceListCoordinator)
|
||||
|
||||
self.spaceListCoordinator = spaceListCoordinator
|
||||
}
|
||||
|
||||
private func createSettingsViewController() -> SettingsViewController {
|
||||
let viewController: SettingsViewController = SettingsViewController.instantiate()
|
||||
viewController.loadViewIfNeeded()
|
||||
|
@ -136,12 +211,65 @@ final class SideMenuCoordinator: SideMenuCoordinatorType {
|
|||
self.sideMenuNavigationViewController.present(safariViewController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
private func showExploreRooms(spaceId: String, session: MXSession) {
|
||||
let exploreRoomCoordinator = ExploreRoomCoordinator(session: session, spaceId: spaceId)
|
||||
exploreRoomCoordinator.delegate = self
|
||||
let presentable = exploreRoomCoordinator.toPresentable()
|
||||
presentable.presentationController?.delegate = self
|
||||
self.sideMenuViewController.present(presentable, animated: true, completion: nil)
|
||||
exploreRoomCoordinator.start()
|
||||
|
||||
self.exploreRoomCoordinator = exploreRoomCoordinator
|
||||
}
|
||||
|
||||
private func showMembers(spaceId: String, session: MXSession) {
|
||||
let parameters = SpaceMembersCoordinatorParameters(userSessionsService: self.parameters.userSessionsService, session: session, spaceId: spaceId)
|
||||
let spaceMembersCoordinator = SpaceMembersCoordinator(parameters: parameters)
|
||||
spaceMembersCoordinator.delegate = self
|
||||
let presentable = spaceMembersCoordinator.toPresentable()
|
||||
presentable.presentationController?.delegate = self
|
||||
self.sideMenuViewController.present(presentable, animated: true, completion: nil)
|
||||
spaceMembersCoordinator.start()
|
||||
|
||||
self.membersCoordinator = spaceMembersCoordinator
|
||||
}
|
||||
|
||||
private func showInviteFriends(from sourceView: UIView?) {
|
||||
let myUserId = self.parameters.userSessionsService.mainUserSession?.userId ?? ""
|
||||
|
||||
let inviteFriendsPresenter = InviteFriendsPresenter()
|
||||
inviteFriendsPresenter.present(for: myUserId, from: self.sideMenuViewController, sourceView: sourceView, animated: true)
|
||||
}
|
||||
|
||||
private func showMenu(forSpaceWithId spaceId: String, from sourceView: UIView?) {
|
||||
guard let session = self.parameters.userSessionsService.mainUserSession?.matrixSession else {
|
||||
return
|
||||
}
|
||||
self.spaceMenuPresenter.delegate = self
|
||||
self.spaceMenuPresenter.present(forSpaceWithId: spaceId, from: self.sideMenuViewController, sourceView: sourceView, session: session, animated: true)
|
||||
}
|
||||
|
||||
private func showSpaceDetail(forSpaceWithId spaceId: String, from sourceView: UIView?) {
|
||||
guard let session = self.parameters.userSessionsService.mainUserSession?.matrixSession else {
|
||||
return
|
||||
}
|
||||
self.spaceDetailPresenter.delegate = self
|
||||
self.spaceDetailPresenter.present(forSpaceWithId: spaceId, from: self.sideMenuViewController, sourceView: sourceView, session: session, animated: true)
|
||||
}
|
||||
|
||||
// MARK: UserSessions management
|
||||
|
||||
private func registerUserSessionsServiceNotifications() {
|
||||
|
||||
// Listen only notifications from the current UserSessionsService instance
|
||||
let userSessionService = self.parameters.userSessionsService
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(userSessionsServiceDidAddUserSession(_:)), name: UserSessionsService.didAddUserSession, object: userSessionService)
|
||||
}
|
||||
|
||||
@objc private func userSessionsServiceDidAddUserSession(_ notification: Notification) {
|
||||
self.addSpaceListIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SideMenuViewModelCoordinatorDelegate
|
||||
|
@ -179,3 +307,84 @@ extension SideMenuCoordinator: SideMenuNavigationControllerDelegate {
|
|||
func sideMenuDidDisappear(menu: SideMenuNavigationController, animated: Bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SideMenuNavigationControllerDelegate
|
||||
extension SideMenuCoordinator: SpaceListCoordinatorDelegate {
|
||||
func spaceListCoordinatorDidSelectHomeSpace(_ coordinator: SpaceListCoordinatorType) {
|
||||
self.parameters.appNavigator.sideMenu.dismiss(animated: true) {
|
||||
|
||||
}
|
||||
self.parameters.appNavigator.navigate(to: .homeSpace)
|
||||
}
|
||||
|
||||
func spaceListCoordinator(_ coordinator: SpaceListCoordinatorType, didSelectSpaceWithId spaceId: String) {
|
||||
self.parameters.appNavigator.sideMenu.dismiss(animated: true) {
|
||||
|
||||
}
|
||||
self.parameters.appNavigator.navigate(to: .space(spaceId))
|
||||
}
|
||||
|
||||
func spaceListCoordinator(_ coordinator: SpaceListCoordinatorType, didSelectInviteWithId spaceId: String, from sourceView: UIView?) {
|
||||
self.showSpaceDetail(forSpaceWithId: spaceId, from: sourceView)
|
||||
}
|
||||
|
||||
func spaceListCoordinator(_ coordinator: SpaceListCoordinatorType, didPressMoreForSpaceWithId spaceId: String, from sourceView: UIView) {
|
||||
self.showMenu(forSpaceWithId: spaceId, from: sourceView)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SpaceMenuPresenterDelegate
|
||||
extension SideMenuCoordinator: SpaceMenuPresenterDelegate {
|
||||
func spaceMenuPresenter(_ presenter: SpaceMenuPresenter, didCompleteWith action: SpaceMenuPresenter.Actions, forSpaceWithId spaceId: String, with session: MXSession) {
|
||||
presenter.dismiss(animated: false) {
|
||||
switch action {
|
||||
case .exploreRooms:
|
||||
self.showExploreRooms(spaceId: spaceId, session: session)
|
||||
case .exploreMembers:
|
||||
self.showMembers(spaceId: spaceId, session: session)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SideMenuCoordinator: SpaceDetailPresenterDelegate {
|
||||
func spaceDetailPresenter(_ presenter: SpaceDetailPresenter, didJoinSpaceWithId spaceId: String) {
|
||||
self.spaceListCoordinator?.select(spaceWithId: spaceId)
|
||||
}
|
||||
|
||||
func spaceDetailPresenter(_ presenter: SpaceDetailPresenter, didOpenSpaceWithId spaceId: String) {
|
||||
// this use case cannot happen here as the space list open directly joined spaces on tap
|
||||
self.spaceListCoordinator?.revertItemSelection()
|
||||
}
|
||||
|
||||
func spaceDetailPresenterDidComplete(_ presenter: SpaceDetailPresenter) {
|
||||
self.spaceListCoordinator?.revertItemSelection()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ExploreRoomCoordinatorDelegate
|
||||
extension SideMenuCoordinator: ExploreRoomCoordinatorDelegate {
|
||||
func exploreRoomCoordinatorDidComplete(_ coordinator: ExploreRoomCoordinatorType) {
|
||||
self.exploreRoomCoordinator?.toPresentable().dismiss(animated: true) {
|
||||
self.exploreRoomCoordinator = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SpaceMembersCoordinatorDelegate
|
||||
extension SideMenuCoordinator: SpaceMembersCoordinatorDelegate {
|
||||
func spaceMembersCoordinatorDidCancel(_ coordinator: SpaceMembersCoordinatorType) {
|
||||
self.membersCoordinator?.toPresentable().dismiss(animated: true) {
|
||||
self.membersCoordinator = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UIAdaptivePresentationControllerDelegate
|
||||
extension SideMenuCoordinator: UIAdaptivePresentationControllerDelegate {
|
||||
|
||||
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
|
||||
self.exploreRoomCoordinator = nil
|
||||
self.membersCoordinator = nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,4 +28,5 @@ protocol SideMenuCoordinatorType: Coordinator, Presentable {
|
|||
|
||||
@discardableResult func addScreenEdgePanGesturesToPresent(to view: UIView) -> UIScreenEdgePanGestureRecognizer
|
||||
@discardableResult func addPanGestureToPresent(to view: UIView) -> UIPanGestureRecognizer
|
||||
func select(spaceWithId spaceId: String)
|
||||
}
|
||||
|
|
|
@ -16,126 +16,97 @@
|
|||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<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"/>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="uTs-MO-piF">
|
||||
<rect key="frame" x="0.0" y="44" width="414" height="92"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="e7g-um-WO4">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="222"/>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="NuF-pw-IzO" customClass="UserAvatarView" customModule="Riot" customModuleProvider="target">
|
||||
<rect key="frame" x="20" y="20" width="52" height="52"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" secondItem="NuF-pw-IzO" secondAttribute="height" multiplier="1:1" id="aPR-9H-XC7"/>
|
||||
<constraint firstAttribute="width" constant="52" id="v7Z-9B-ROI"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="pj0-XK-IJ2">
|
||||
<rect key="frame" x="87" y="23" width="307" height="46"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="voD-3Q-ryt">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="222"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="uTs-MO-piF">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="158"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="NuF-pw-IzO" customClass="UserAvatarView" customModule="Riot" customModuleProvider="target">
|
||||
<rect key="frame" x="20" y="30" width="52" height="52"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" secondItem="NuF-pw-IzO" secondAttribute="height" multiplier="1:1" id="aPR-9H-XC7"/>
|
||||
<constraint firstAttribute="width" constant="52" id="v7Z-9B-ROI"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="pj0-XK-IJ2">
|
||||
<rect key="frame" x="20" y="92" width="374" height="46"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bbo-IX-VUb">
|
||||
<rect key="frame" x="0.0" y="0.0" width="374" height="24"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle3"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="VWw-Gn-nd0">
|
||||
<rect key="frame" x="0.0" y="28" width="374" height="18"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="pj0-XK-IJ2" secondAttribute="bottom" constant="20" id="GyA-NG-zuK"/>
|
||||
<constraint firstAttribute="trailing" secondItem="pj0-XK-IJ2" secondAttribute="trailing" constant="20" id="Y1J-eh-n41"/>
|
||||
<constraint firstItem="pj0-XK-IJ2" firstAttribute="leading" secondItem="uTs-MO-piF" secondAttribute="leading" constant="20" id="oqk-zx-vwa"/>
|
||||
<constraint firstItem="NuF-pw-IzO" firstAttribute="leading" secondItem="uTs-MO-piF" secondAttribute="leading" constant="20" id="rSh-ot-aqo"/>
|
||||
<constraint firstItem="pj0-XK-IJ2" firstAttribute="top" secondItem="NuF-pw-IzO" secondAttribute="bottom" constant="10" id="wMq-kI-hIR"/>
|
||||
<constraint firstItem="NuF-pw-IzO" firstAttribute="top" secondItem="uTs-MO-piF" secondAttribute="top" constant="30" id="woS-eb-vCr"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="M9u-6y-ybq">
|
||||
<rect key="frame" x="0.0" y="158" width="414" height="64"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="jh2-Rr-gGK">
|
||||
<rect key="frame" x="20" y="0.0" width="374" height="44"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="d2f-GW-Q3t" customClass="SideMenuActionView" customModule="Riot" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="374" height="44"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="44" id="RaC-Fc-LVI"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="d2f-GW-Q3t" firstAttribute="width" secondItem="jh2-Rr-gGK" secondAttribute="width" id="OtD-wt-AZr"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="jh2-Rr-gGK" firstAttribute="leading" secondItem="M9u-6y-ybq" secondAttribute="leading" constant="20" id="4H1-E7-NKg"/>
|
||||
<constraint firstAttribute="bottom" secondItem="jh2-Rr-gGK" secondAttribute="bottom" constant="20" id="Ml9-0O-ZAG"/>
|
||||
<constraint firstItem="jh2-Rr-gGK" firstAttribute="top" secondItem="M9u-6y-ybq" secondAttribute="top" id="dTl-ZO-glj"/>
|
||||
<constraint firstAttribute="trailing" secondItem="jh2-Rr-gGK" secondAttribute="trailing" constant="20" id="uW2-nD-nhl"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bbo-IX-VUb">
|
||||
<rect key="frame" x="0.0" y="0.0" width="307" height="24"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle3"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="VWw-Gn-nd0">
|
||||
<rect key="frame" x="0.0" y="28" width="307" height="18"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="pj0-XK-IJ2" secondAttribute="bottom" constant="20" id="GyA-NG-zuK"/>
|
||||
<constraint firstItem="pj0-XK-IJ2" firstAttribute="centerY" secondItem="NuF-pw-IzO" secondAttribute="centerY" id="Ryy-Un-b4P"/>
|
||||
<constraint firstAttribute="trailing" secondItem="pj0-XK-IJ2" secondAttribute="trailing" constant="20" id="Y1J-eh-n41"/>
|
||||
<constraint firstItem="pj0-XK-IJ2" firstAttribute="leading" secondItem="NuF-pw-IzO" secondAttribute="trailing" constant="15" id="dY6-O4-aq7"/>
|
||||
<constraint firstItem="NuF-pw-IzO" firstAttribute="leading" secondItem="uTs-MO-piF" secondAttribute="leading" constant="20" id="rSh-ot-aqo"/>
|
||||
<constraint firstItem="NuF-pw-IzO" firstAttribute="top" secondItem="uTs-MO-piF" secondAttribute="top" constant="20" id="woS-eb-vCr"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="d7a-wj-MoP">
|
||||
<rect key="frame" x="0.0" y="136" width="414" height="672"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="M9u-6y-ybq">
|
||||
<rect key="frame" x="0.0" y="808" width="414" height="54"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="jh2-Rr-gGK">
|
||||
<rect key="frame" x="20" y="0.0" width="374" height="44"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="d2f-GW-Q3t" customClass="SideMenuActionView" customModule="Riot" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="374" height="44"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="M9u-6y-ybq" firstAttribute="leading" secondItem="voD-3Q-ryt" secondAttribute="leading" id="BY4-9k-kWW"/>
|
||||
<constraint firstAttribute="bottom" secondItem="M9u-6y-ybq" secondAttribute="bottom" id="JgN-P3-Gr2"/>
|
||||
<constraint firstItem="M9u-6y-ybq" firstAttribute="top" secondItem="uTs-MO-piF" secondAttribute="bottom" id="MuD-JP-iy9"/>
|
||||
<constraint firstAttribute="width" priority="750" constant="500" id="glD-Sz-73O"/>
|
||||
<constraint firstItem="uTs-MO-piF" firstAttribute="leading" secondItem="voD-3Q-ryt" secondAttribute="leading" id="kQ7-oa-oSs"/>
|
||||
<constraint firstItem="uTs-MO-piF" firstAttribute="top" secondItem="voD-3Q-ryt" secondAttribute="top" id="m0n-kQ-UAA"/>
|
||||
<constraint firstAttribute="trailing" secondItem="uTs-MO-piF" secondAttribute="trailing" id="oWE-b2-UKq"/>
|
||||
<constraint firstAttribute="trailing" secondItem="M9u-6y-ybq" secondAttribute="trailing" id="wLj-aM-UyK"/>
|
||||
<constraint firstAttribute="height" constant="44" id="RaC-Fc-LVI"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</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="d2f-GW-Q3t" firstAttribute="width" secondItem="jh2-Rr-gGK" secondAttribute="width" id="OtD-wt-AZr"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<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="jh2-Rr-gGK" firstAttribute="leading" secondItem="M9u-6y-ybq" secondAttribute="leading" constant="20" id="4H1-E7-NKg"/>
|
||||
<constraint firstAttribute="bottom" secondItem="jh2-Rr-gGK" secondAttribute="bottom" constant="10" id="Ml9-0O-ZAG"/>
|
||||
<constraint firstItem="jh2-Rr-gGK" firstAttribute="top" secondItem="M9u-6y-ybq" secondAttribute="top" id="dTl-ZO-glj"/>
|
||||
<constraint firstAttribute="trailing" secondItem="jh2-Rr-gGK" secondAttribute="trailing" constant="20" id="uW2-nD-nhl"/>
|
||||
</constraints>
|
||||
</scrollView>
|
||||
</view>
|
||||
</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"/>
|
||||
<constraint firstItem="9U2-KL-ZVA" firstAttribute="leading" secondItem="bFg-jh-JZB" secondAttribute="leading" id="GdQ-hK-muG"/>
|
||||
<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"/>
|
||||
<constraint firstItem="bFg-jh-JZB" firstAttribute="bottom" secondItem="M9u-6y-ybq" secondAttribute="bottom" id="39t-nU-ci3"/>
|
||||
<constraint firstItem="uTs-MO-piF" firstAttribute="leading" secondItem="bFg-jh-JZB" secondAttribute="leading" id="NaR-V6-0ns"/>
|
||||
<constraint firstItem="d7a-wj-MoP" firstAttribute="top" secondItem="pj0-XK-IJ2" secondAttribute="bottom" priority="250" constant="20" id="TPa-Kf-kaT"/>
|
||||
<constraint firstAttribute="trailing" secondItem="d7a-wj-MoP" secondAttribute="trailing" id="UEj-UR-cGJ"/>
|
||||
<constraint firstItem="d7a-wj-MoP" firstAttribute="top" secondItem="uTs-MO-piF" secondAttribute="bottom" id="epm-HJ-Glc"/>
|
||||
<constraint firstItem="d7a-wj-MoP" firstAttribute="leading" secondItem="EL9-GA-lwo" secondAttribute="leading" id="f1d-7J-hNw"/>
|
||||
<constraint firstItem="M9u-6y-ybq" firstAttribute="leading" secondItem="bFg-jh-JZB" secondAttribute="leading" id="iFr-i7-3RB"/>
|
||||
<constraint firstItem="bFg-jh-JZB" firstAttribute="trailing" secondItem="uTs-MO-piF" secondAttribute="trailing" id="keO-Hx-S2U"/>
|
||||
<constraint firstItem="M9u-6y-ybq" firstAttribute="top" secondItem="d7a-wj-MoP" secondAttribute="bottom" id="nNw-XQ-3Mu"/>
|
||||
<constraint firstItem="bFg-jh-JZB" firstAttribute="top" secondItem="uTs-MO-piF" secondAttribute="top" id="nRc-Xs-kjp"/>
|
||||
<constraint firstItem="d7a-wj-MoP" firstAttribute="top" relation="greaterThanOrEqual" secondItem="NuF-pw-IzO" secondAttribute="bottom" constant="20" id="oOb-84-BE1"/>
|
||||
<constraint firstItem="M9u-6y-ybq" firstAttribute="trailing" secondItem="bFg-jh-JZB" secondAttribute="trailing" id="vxI-Qt-760"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="menuItemsStackView" destination="jh2-Rr-gGK" id="mTS-AO-avQ"/>
|
||||
<outlet property="scrollView" destination="9U2-KL-ZVA" id="ojG-2y-X7b"/>
|
||||
<outlet property="spaceListContainerView" destination="d7a-wj-MoP" id="TzM-3u-PqG"/>
|
||||
<outlet property="userAvatarView" destination="NuF-pw-IzO" id="Xyh-Rl-hW4"/>
|
||||
<outlet property="userDisplayNameLabel" destination="bbo-IX-VUb" id="8vG-CB-Fgo"/>
|
||||
<outlet property="userIdLabel" destination="VWw-Gn-nd0" id="4gK-yt-uR9"/>
|
||||
|
|
|
@ -29,8 +29,8 @@ final class SideMenuViewController: UIViewController {
|
|||
// MARK: - Properties
|
||||
|
||||
// MARK: Outlets
|
||||
|
||||
@IBOutlet private weak var scrollView: UIScrollView!
|
||||
|
||||
@IBOutlet weak var spaceListContainerView: UIView!
|
||||
|
||||
// User info
|
||||
@IBOutlet private weak var userAvatarView: UserAvatarView!
|
||||
|
@ -103,7 +103,7 @@ final class SideMenuViewController: UIViewController {
|
|||
theme.applyStyle(onNavigationBar: navigationBar)
|
||||
}
|
||||
|
||||
self.view.backgroundColor = theme.headerBackgroundColor
|
||||
self.view.backgroundColor = theme.colors.background
|
||||
|
||||
self.userAvatarView.update(theme: theme)
|
||||
self.userDisplayNameLabel.textColor = theme.textPrimaryColor
|
||||
|
|
|
@ -71,11 +71,12 @@ final class SideMenuViewModel: SideMenuViewModelType {
|
|||
}
|
||||
|
||||
private func userAvatarViewData(from mxSession: MXSession) -> UserAvatarViewData? {
|
||||
guard let userId = mxSession.myUserId, let mediaManager = mxSession.mediaManager else {
|
||||
guard let userId = mxSession.myUserId, let mediaManager = mxSession.mediaManager, let myUser = mxSession.myUser else {
|
||||
return nil
|
||||
}
|
||||
let userDisplayName = mxSession.myUser.displayname
|
||||
let avatarUrl = mxSession.myUser.avatarUrl
|
||||
|
||||
let userDisplayName = myUser.displayname
|
||||
let avatarUrl = myUser.avatarUrl
|
||||
|
||||
return UserAvatarViewData(userId: userId,
|
||||
displayName: userDisplayName,
|
||||
|
@ -103,7 +104,8 @@ final class SideMenuViewModel: SideMenuViewModelType {
|
|||
.feedback
|
||||
]
|
||||
|
||||
let appVersion = self.appInfo.appVersion?.bundleShortVersion
|
||||
// Hide app version
|
||||
let appVersion: String? = nil
|
||||
|
||||
let viewData = SideMenuViewData(userAvatarViewData: userAvatarViewData, sideMenuItems: sideMenuItems, appVersion: appVersion)
|
||||
|
||||
|
|
103
Riot/Modules/Spaces/Avatar/SpaceAvatarView.swift
Normal file
|
@ -0,0 +1,103 @@
|
|||
//
|
||||
// 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 Reusable
|
||||
|
||||
final class SpaceAvatarView: AvatarView, NibOwnerLoadable {
|
||||
|
||||
// MARK: - Constants
|
||||
|
||||
private enum Constants {
|
||||
static let cornerRadius: CGFloat = 8.0
|
||||
}
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Outlets
|
||||
|
||||
@IBOutlet private weak var cameraBadgeContainerView: UIView!
|
||||
|
||||
// MARK: Public
|
||||
|
||||
var showCameraBadgeOnFallbackImage: Bool = false
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
private func commonInit() {
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
self.loadNibContent()
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
self.loadNibContent()
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
// MARK: - Lifecycle
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
self.avatarImageView.layer.cornerRadius = Constants.cornerRadius
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
override func fill(with viewData: AvatarViewDataProtocol) {
|
||||
self.updateAvatarImageView(with: viewData)
|
||||
|
||||
// Fix layoutSubviews not triggered issue
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Overrides
|
||||
|
||||
override func updateAccessibilityTraits() {
|
||||
if self.isUserInteractionEnabled {
|
||||
self.vc_setupAccessibilityTraitsButton(withTitle: VectorL10n.spaceAvatarViewAccessibilityLabel, hint: VectorL10n.spaceAvatarViewAccessibilityHint, isEnabled: true)
|
||||
} else {
|
||||
self.vc_setupAccessibilityTraitsImage(withTitle: VectorL10n.spaceAvatarViewAccessibilityLabel)
|
||||
}
|
||||
}
|
||||
|
||||
override func updateAvatarImageView(with viewData: AvatarViewDataProtocol) {
|
||||
super.updateAvatarImageView(with: viewData)
|
||||
|
||||
let hideCameraBadge: Bool
|
||||
|
||||
if self.showCameraBadgeOnFallbackImage {
|
||||
hideCameraBadge = viewData.avatarUrl != nil
|
||||
} else {
|
||||
hideCameraBadge = true
|
||||
}
|
||||
|
||||
self.cameraBadgeContainerView.isHidden = hideCameraBadge
|
||||
}
|
||||
|
||||
override func update(theme: Theme) {
|
||||
super.update(theme: theme)
|
||||
|
||||
self.avatarImageView.defaultBackgroundColor = theme.colors.tile
|
||||
}
|
||||
}
|
75
Riot/Modules/Spaces/Avatar/SpaceAvatarView.xib
Normal file
|
@ -0,0 +1,75 @@
|
|||
<?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" colorMatched="YES">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="SpaceAvatarView" customModule="Riot" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="avatarImageView" destination="ln9-Sd-GKd" id="9Zd-LM-hgl"/>
|
||||
<outlet property="cameraBadgeContainerView" destination="0YT-CK-WjK" id="T31-nq-8PB"/>
|
||||
</connections>
|
||||
</placeholder>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view contentMode="scaleToFill" id="iN0-l3-epB">
|
||||
<rect key="frame" x="0.0" y="0.0" width="80" height="80"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="iK1-yG-fEu">
|
||||
<rect key="frame" x="0.0" y="0.0" width="80" height="80"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ln9-Sd-GKd" customClass="MXKImageView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="80" height="80"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="0YT-CK-WjK">
|
||||
<rect key="frame" x="56" y="56" width="24" height="24"/>
|
||||
<subviews>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="capture_avatar" translatesAutoresizingMaskIntoConstraints="NO" id="vGE-Mx-xPX">
|
||||
<rect key="frame" x="0.0" y="0.0" width="24" height="24"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="24" id="AIT-k4-SJZ"/>
|
||||
<constraint firstItem="vGE-Mx-xPX" firstAttribute="leading" secondItem="0YT-CK-WjK" secondAttribute="leading" id="IoL-FC-x3t"/>
|
||||
<constraint firstAttribute="trailing" secondItem="vGE-Mx-xPX" secondAttribute="trailing" id="KDL-CV-LNm"/>
|
||||
<constraint firstItem="vGE-Mx-xPX" firstAttribute="top" secondItem="0YT-CK-WjK" secondAttribute="top" id="PLP-FV-9fe"/>
|
||||
<constraint firstAttribute="bottom" secondItem="vGE-Mx-xPX" secondAttribute="bottom" id="bIo-ge-7Ud"/>
|
||||
<constraint firstAttribute="width" constant="24" id="ugV-gr-fbC"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="0YT-CK-WjK" secondAttribute="trailing" id="8HT-uc-Dd2"/>
|
||||
<constraint firstAttribute="trailing" secondItem="ln9-Sd-GKd" secondAttribute="trailing" id="ABF-Wz-ZZy"/>
|
||||
<constraint firstAttribute="height" constant="80" id="GdE-dy-bxN"/>
|
||||
<constraint firstAttribute="width" constant="80" id="NCu-Xg-4p3"/>
|
||||
<constraint firstAttribute="bottom" secondItem="ln9-Sd-GKd" secondAttribute="bottom" id="NZp-ao-R0Q"/>
|
||||
<constraint firstItem="ln9-Sd-GKd" firstAttribute="top" secondItem="iK1-yG-fEu" secondAttribute="top" id="VZt-Uu-toa"/>
|
||||
<constraint firstItem="ln9-Sd-GKd" firstAttribute="leading" secondItem="iK1-yG-fEu" secondAttribute="leading" id="jEs-Ac-ock"/>
|
||||
<constraint firstAttribute="bottom" secondItem="0YT-CK-WjK" secondAttribute="bottom" id="wIN-TR-RZm"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="iK1-yG-fEu" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="GZx-C7-FF8"/>
|
||||
<constraint firstAttribute="trailing" secondItem="iK1-yG-fEu" secondAttribute="trailing" id="UPO-MZ-XUZ"/>
|
||||
<constraint firstAttribute="bottom" secondItem="iK1-yG-fEu" secondAttribute="bottom" id="g7y-SL-f3s"/>
|
||||
<constraint firstItem="iK1-yG-fEu" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="imn-uW-TTP"/>
|
||||
</constraints>
|
||||
<nil key="simulatedTopBarMetrics"/>
|
||||
<nil key="simulatedBottomBarMetrics"/>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<point key="canvasLocation" x="-433" y="-802"/>
|
||||
</view>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="capture_avatar" width="25" height="25"/>
|
||||
</resources>
|
||||
</document>
|
139
Riot/Modules/Spaces/SpaceDetail/SpaceDetailPresenter.swift
Normal file
|
@ -0,0 +1,139 @@
|
|||
//
|
||||
// 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
|
||||
|
||||
/// Presenter for space detail screen
|
||||
class SpaceDetailPresenter: NSObject {
|
||||
|
||||
// MARK: - Constants
|
||||
|
||||
enum Actions {
|
||||
case exploreRooms
|
||||
case exploreMembers
|
||||
}
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
@objc public weak var delegate: SpaceDetailPresenterDelegate?
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private weak var presentingViewController: UIViewController?
|
||||
private var viewModel: SpaceDetailViewModel!
|
||||
private weak var sourceView: UIView?
|
||||
private lazy var slidingModalPresenter: SlidingModalPresenter = {
|
||||
return SlidingModalPresenter()
|
||||
}()
|
||||
private var session: MXSession!
|
||||
private var spaceId: String!
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
@objc func present(forSpaceWithId spaceId: String,
|
||||
from viewController: UIViewController,
|
||||
sourceView: UIView?,
|
||||
session: MXSession,
|
||||
animated: Bool) {
|
||||
self.session = session
|
||||
self.spaceId = spaceId
|
||||
|
||||
self.viewModel = SpaceDetailViewModel(session: session, spaceId: spaceId)
|
||||
self.viewModel.coordinatorDelegate = self
|
||||
self.presentingViewController = viewController
|
||||
self.sourceView = sourceView
|
||||
|
||||
self.show(with: session)
|
||||
}
|
||||
|
||||
@objc func present(forSpaceWithPublicRoom publicRoom: MXPublicRoom,
|
||||
from viewController: UIViewController,
|
||||
sourceView: UIView?,
|
||||
session: MXSession,
|
||||
animated: Bool) {
|
||||
self.session = session
|
||||
self.spaceId = publicRoom.roomId
|
||||
|
||||
self.viewModel = SpaceDetailViewModel(session: session, publicRoom: publicRoom)
|
||||
self.viewModel.coordinatorDelegate = self
|
||||
self.presentingViewController = viewController
|
||||
self.sourceView = sourceView
|
||||
|
||||
self.show(with: session)
|
||||
}
|
||||
|
||||
func dismiss(animated: Bool, completion: (() -> Void)?) {
|
||||
self.presentingViewController?.dismiss(animated: animated, completion: completion)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func show(with session: MXSession) {
|
||||
let viewController = SpaceDetailViewController.instantiate(mediaManager: session.mediaManager, viewModel: self.viewModel)
|
||||
self.present(viewController, animated: true)
|
||||
}
|
||||
|
||||
private func present(_ viewController: SpaceDetailViewController, animated: Bool) {
|
||||
|
||||
if UIDevice.current.isPhone {
|
||||
guard let rootViewController = self.presentingViewController else {
|
||||
MXLog.error("[SpaceDetailPresenter] present no rootViewController found")
|
||||
return
|
||||
}
|
||||
|
||||
slidingModalPresenter.present(viewController, from: rootViewController.presentedViewController ?? rootViewController, animated: true, completion: nil)
|
||||
} else {
|
||||
// Configure source view when view controller is presented with a popover
|
||||
viewController.modalPresentationStyle = .popover
|
||||
if let sourceView = self.sourceView, let popoverPresentationController = viewController.popoverPresentationController {
|
||||
popoverPresentationController.sourceView = sourceView
|
||||
popoverPresentationController.sourceRect = sourceView.bounds
|
||||
}
|
||||
|
||||
self.presentingViewController?.present(viewController, animated: animated, completion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SpaceDetailModelViewModelCoordinatorDelegate
|
||||
|
||||
extension SpaceDetailPresenter: SpaceDetailModelViewModelCoordinatorDelegate {
|
||||
func spaceDetailViewModelDidJoin(_ viewModel: SpaceDetailViewModelType) {
|
||||
self.dismiss(animated: true) {
|
||||
self.delegate?.spaceDetailPresenter(self, didJoinSpaceWithId: self.spaceId)
|
||||
}
|
||||
}
|
||||
|
||||
func spaceDetailViewModelDidOpen(_ viewModel: SpaceDetailViewModelType) {
|
||||
self.dismiss(animated: false) {
|
||||
self.delegate?.spaceDetailPresenter(self, didOpenSpaceWithId: self.spaceId)
|
||||
}
|
||||
}
|
||||
|
||||
func spaceDetailViewModelDidCancel(_ viewModel: SpaceDetailViewModelType) {
|
||||
self.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
func spaceDetailViewModelDidDismiss(_ viewModel: SpaceDetailViewModelType) {
|
||||
self.delegate?.spaceDetailPresenterDidComplete(self)
|
||||
}
|
||||
}
|
||||
|
||||
@objc protocol SpaceDetailPresenterDelegate: AnyObject {
|
||||
func spaceDetailPresenterDidComplete(_ presenter: SpaceDetailPresenter)
|
||||
func spaceDetailPresenter(_ presenter: SpaceDetailPresenter, didJoinSpaceWithId spaceId: String)
|
||||
func spaceDetailPresenter(_ presenter: SpaceDetailPresenter, didOpenSpaceWithId spaceId: String)
|
||||
}
|
27
Riot/Modules/Spaces/SpaceDetail/SpaceDetailViewAction.swift
Normal file
|
@ -0,0 +1,27 @@
|
|||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// `SpaceDetailViewController` view actions exposed to view model
|
||||
enum SpaceDetailViewAction {
|
||||
case loadData
|
||||
case join
|
||||
case open
|
||||
case leave
|
||||
case dismiss
|
||||
case dismissed
|
||||
}
|
|
@ -0,0 +1,238 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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="Y6W-OH-hqX">
|
||||
<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="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Space Detail View Controller-->
|
||||
<scene sceneID="s0d-6b-0kx">
|
||||
<objects>
|
||||
<viewController extendedLayoutIncludesOpaqueBars="YES" id="Y6W-OH-hqX" customClass="SpaceDetailViewController" customModule="Riot" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="5EZ-qb-Rvc">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="842"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<view clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="22X-aK-4D2">
|
||||
<rect key="frame" x="16" y="16" width="382" height="76"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="yVi-9K-5iE" customClass="RoomAvatarView" customModule="Riot" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="4" width="32" height="32"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="32" id="OZZ-dW-Uuc"/>
|
||||
<constraint firstAttribute="width" constant="32" id="qAF-jw-btk"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="display name invited you" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" adjustsFontForContentSizeCategory="YES" translatesAutoresizingMaskIntoConstraints="NO" id="oZk-F6-3nn">
|
||||
<rect key="frame" x="44" y="0.0" width="306" height="17"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="@userid:matrix.org" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" adjustsFontForContentSizeCategory="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Vw0-9q-U23">
|
||||
<rect key="frame" x="44" y="19" width="306" height="17"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="GbA-LS-7G8">
|
||||
<rect key="frame" x="0.0" y="50" width="382" height="1"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="1" id="PSu-zU-1Vr"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="Vw0-9q-U23" secondAttribute="trailing" constant="32" id="35Q-0D-eaP"/>
|
||||
<constraint firstItem="Vw0-9q-U23" firstAttribute="leading" secondItem="yVi-9K-5iE" secondAttribute="trailing" constant="12" id="5jU-Wj-fli"/>
|
||||
<constraint firstAttribute="height" constant="76" id="GXB-6p-EIC"/>
|
||||
<constraint firstAttribute="trailing" secondItem="GbA-LS-7G8" secondAttribute="trailing" id="HI3-0y-tyY"/>
|
||||
<constraint firstItem="Vw0-9q-U23" firstAttribute="bottom" secondItem="yVi-9K-5iE" secondAttribute="bottom" id="IxE-pA-5bx"/>
|
||||
<constraint firstItem="oZk-F6-3nn" firstAttribute="leading" secondItem="yVi-9K-5iE" secondAttribute="trailing" constant="12" id="UAC-PF-u3I"/>
|
||||
<constraint firstItem="yVi-9K-5iE" firstAttribute="leading" secondItem="22X-aK-4D2" secondAttribute="leading" id="VDT-b0-SqV"/>
|
||||
<constraint firstItem="oZk-F6-3nn" firstAttribute="top" secondItem="22X-aK-4D2" secondAttribute="top" id="Xkw-oz-SUD"/>
|
||||
<constraint firstItem="GbA-LS-7G8" firstAttribute="top" secondItem="yVi-9K-5iE" secondAttribute="bottom" constant="14" id="eWu-Q8-50q"/>
|
||||
<constraint firstAttribute="trailing" secondItem="oZk-F6-3nn" secondAttribute="trailing" constant="32" id="hSf-zB-m6e"/>
|
||||
<constraint firstItem="GbA-LS-7G8" firstAttribute="leading" secondItem="22X-aK-4D2" secondAttribute="leading" id="o4Q-41-AfC"/>
|
||||
<constraint firstItem="yVi-9K-5iE" firstAttribute="top" secondItem="22X-aK-4D2" secondAttribute="top" constant="4" id="x9I-eQ-C0t"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="dxd-y5-bn4">
|
||||
<rect key="frame" x="374" y="16" width="24" height="24"/>
|
||||
<color key="backgroundColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="24" id="TTq-xS-O71"/>
|
||||
<constraint firstAttribute="width" constant="24" id="gmo-Ip-cjr"/>
|
||||
</constraints>
|
||||
<state key="normal" image="space_menu_close"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
|
||||
<integer key="value" value="14"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
</userDefinedRuntimeAttributes>
|
||||
<connections>
|
||||
<action selector="closeActionWithSender:" destination="Y6W-OH-hqX" eventType="touchUpInside" id="TIh-gS-svg"/>
|
||||
</connections>
|
||||
</button>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="aSn-OV-epF" customClass="SpaceAvatarView" customModule="Riot" customModuleProvider="target">
|
||||
<rect key="frame" x="16" y="92" width="66" height="66"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="66" id="N0q-nk-kG6"/>
|
||||
<constraint firstAttribute="width" secondItem="aSn-OV-epF" secondAttribute="height" multiplier="1:1" id="X3x-O0-Cpp"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="1000" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3Mp-yr-jUa">
|
||||
<rect key="frame" x="16" y="182" width="382" height="17"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="space_type_icon" translatesAutoresizingMaskIntoConstraints="NO" id="5eT-si-nJh">
|
||||
<rect key="frame" x="16" y="212" width="16" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="16" id="Hmz-Ud-9NT"/>
|
||||
<constraint firstAttribute="height" constant="16" id="sak-fG-0Id"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="1000" text="44" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ko6-Oy-KB4">
|
||||
<rect key="frame" x="37" y="212" width="17" height="16"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleCallout"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="VRt-iQ-AXx">
|
||||
<rect key="frame" x="16" y="244" width="382" height="488"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Rg1-rU-wKD">
|
||||
<rect key="frame" x="0.0" y="0.0" width="382" height="488"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="Rg1-rU-wKD" firstAttribute="height" relation="greaterThanOrEqual" secondItem="VRt-iQ-AXx" secondAttribute="height" id="AWs-5c-xxU"/>
|
||||
<constraint firstItem="Rg1-rU-wKD" firstAttribute="bottom" secondItem="ynp-n0-iet" secondAttribute="bottom" id="EqK-uS-dLH"/>
|
||||
<constraint firstItem="Rg1-rU-wKD" firstAttribute="width" secondItem="VRt-iQ-AXx" secondAttribute="width" id="F6n-H6-yE4"/>
|
||||
<constraint firstItem="Rg1-rU-wKD" firstAttribute="leading" secondItem="ynp-n0-iet" secondAttribute="leading" id="Rsa-EB-og8"/>
|
||||
<constraint firstItem="Rg1-rU-wKD" firstAttribute="trailing" secondItem="ynp-n0-iet" secondAttribute="trailing" constant="382" id="X2l-nM-1cX"/>
|
||||
<constraint firstItem="Rg1-rU-wKD" firstAttribute="top" secondItem="ynp-n0-iet" secondAttribute="top" id="XXS-M9-hY3"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="contentLayoutGuide" id="ynp-n0-iet"/>
|
||||
<viewLayoutGuide key="frameLayoutGuide" id="REH-HY-tM4"/>
|
||||
</scrollView>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="xQH-D8-TVA">
|
||||
<rect key="frame" x="16" y="748" width="382" height="44"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="44" id="UHd-DI-8BX"/>
|
||||
</constraints>
|
||||
<state key="normal" title="OK"/>
|
||||
<connections>
|
||||
<action selector="joinActionWithSender:" destination="Y6W-OH-hqX" eventType="touchUpInside" id="mE9-eR-UXZ"/>
|
||||
</connections>
|
||||
</button>
|
||||
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="N1F-Ko-xvc">
|
||||
<rect key="frame" x="16" y="748" width="382" height="44"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="utA-Mz-rmH">
|
||||
<rect key="frame" x="0.0" y="0.0" width="189" height="44"/>
|
||||
<state key="normal" title="DECLINE"/>
|
||||
<connections>
|
||||
<action selector="leaveActionWithSender:" destination="Y6W-OH-hqX" eventType="touchUpInside" id="O7a-Cd-3oX"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="JPi-uh-vpV">
|
||||
<rect key="frame" x="205" y="0.0" width="177" height="44"/>
|
||||
<state key="normal" title="ACCEPT"/>
|
||||
<connections>
|
||||
<action selector="joinActionWithSender:" destination="Y6W-OH-hqX" eventType="touchUpInside" id="4eZ-Um-eDh"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="JPi-uh-vpV" firstAttribute="leading" secondItem="utA-Mz-rmH" secondAttribute="trailing" constant="16" id="7Ga-c0-OCJ"/>
|
||||
<constraint firstItem="utA-Mz-rmH" firstAttribute="width" secondItem="JPi-uh-vpV" secondAttribute="width" multiplier="1.06897" id="8QQ-go-9xV"/>
|
||||
<constraint firstAttribute="trailing" secondItem="JPi-uh-vpV" secondAttribute="trailing" id="I13-lD-tED"/>
|
||||
<constraint firstAttribute="bottom" secondItem="utA-Mz-rmH" secondAttribute="bottom" id="QiF-ea-a0Y"/>
|
||||
<constraint firstItem="utA-Mz-rmH" firstAttribute="top" secondItem="N1F-Ko-xvc" secondAttribute="top" id="U4Y-G0-YSz"/>
|
||||
<constraint firstItem="JPi-uh-vpV" firstAttribute="top" secondItem="N1F-Ko-xvc" secondAttribute="top" id="aPS-mC-5DE"/>
|
||||
<constraint firstAttribute="bottom" secondItem="JPi-uh-vpV" secondAttribute="bottom" id="nIm-Mj-Ich"/>
|
||||
<constraint firstItem="utA-Mz-rmH" firstAttribute="leading" secondItem="N1F-Ko-xvc" secondAttribute="leading" id="yib-Eb-TGj"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="vDu-zF-Fre"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="5eT-si-nJh" firstAttribute="leading" secondItem="3Mp-yr-jUa" secondAttribute="leading" id="0Mu-pd-nWz"/>
|
||||
<constraint firstItem="vDu-zF-Fre" firstAttribute="trailing" secondItem="N1F-Ko-xvc" secondAttribute="trailing" constant="16" id="0pd-eF-og1"/>
|
||||
<constraint firstItem="5eT-si-nJh" firstAttribute="top" secondItem="3Mp-yr-jUa" secondAttribute="bottom" constant="13" id="0z7-5Y-ha9"/>
|
||||
<constraint firstItem="vDu-zF-Fre" firstAttribute="trailing" secondItem="xQH-D8-TVA" secondAttribute="trailing" constant="16" id="2GI-ap-K0l"/>
|
||||
<constraint firstItem="vDu-zF-Fre" firstAttribute="trailing" secondItem="dxd-y5-bn4" secondAttribute="trailing" constant="16" id="3FU-wC-Uy7"/>
|
||||
<constraint firstItem="aSn-OV-epF" firstAttribute="width" secondItem="aSn-OV-epF" secondAttribute="height" multiplier="1:1" id="6ng-Dc-MPf"/>
|
||||
<constraint firstItem="ko6-Oy-KB4" firstAttribute="centerY" secondItem="5eT-si-nJh" secondAttribute="centerY" id="9D6-Kl-bei"/>
|
||||
<constraint firstItem="3Mp-yr-jUa" firstAttribute="top" secondItem="aSn-OV-epF" secondAttribute="bottom" constant="24" id="9h6-Ic-hmQ"/>
|
||||
<constraint firstItem="N1F-Ko-xvc" firstAttribute="bottom" secondItem="xQH-D8-TVA" secondAttribute="bottom" id="HfP-a7-LJV"/>
|
||||
<constraint firstItem="3Mp-yr-jUa" firstAttribute="leading" secondItem="vDu-zF-Fre" secondAttribute="leading" constant="16" id="HxH-M7-Fy1"/>
|
||||
<constraint firstItem="xQH-D8-TVA" firstAttribute="top" secondItem="VRt-iQ-AXx" secondAttribute="bottom" constant="16" id="MfS-3y-K9f"/>
|
||||
<constraint firstItem="vDu-zF-Fre" firstAttribute="bottom" secondItem="xQH-D8-TVA" secondAttribute="bottom" constant="16" id="MhW-nH-ei4"/>
|
||||
<constraint firstItem="VRt-iQ-AXx" firstAttribute="leading" secondItem="vDu-zF-Fre" secondAttribute="leading" constant="16" id="OWC-of-jEz"/>
|
||||
<constraint firstItem="VRt-iQ-AXx" firstAttribute="top" secondItem="5eT-si-nJh" secondAttribute="bottom" constant="16" id="OsF-8y-uSK"/>
|
||||
<constraint firstItem="xQH-D8-TVA" firstAttribute="leading" secondItem="vDu-zF-Fre" secondAttribute="leading" constant="16" id="PRN-1R-lXK"/>
|
||||
<constraint firstItem="vDu-zF-Fre" firstAttribute="trailing" secondItem="3Mp-yr-jUa" secondAttribute="trailing" constant="16" id="Rg7-2n-fPo"/>
|
||||
<constraint firstItem="vDu-zF-Fre" firstAttribute="trailing" secondItem="VRt-iQ-AXx" secondAttribute="trailing" constant="16" id="T3h-BY-ke9"/>
|
||||
<constraint firstItem="N1F-Ko-xvc" firstAttribute="leading" secondItem="vDu-zF-Fre" secondAttribute="leading" constant="16" id="XIm-NG-zh8"/>
|
||||
<constraint firstItem="ko6-Oy-KB4" firstAttribute="leading" secondItem="5eT-si-nJh" secondAttribute="trailing" constant="5" id="e98-Ro-OCy"/>
|
||||
<constraint firstItem="dxd-y5-bn4" firstAttribute="top" secondItem="vDu-zF-Fre" secondAttribute="top" constant="16" id="fBu-Zb-akl"/>
|
||||
<constraint firstItem="aSn-OV-epF" firstAttribute="top" secondItem="22X-aK-4D2" secondAttribute="bottom" id="g4t-WH-Kbz"/>
|
||||
<constraint firstItem="N1F-Ko-xvc" firstAttribute="top" secondItem="xQH-D8-TVA" secondAttribute="top" id="hE9-gh-tH4"/>
|
||||
<constraint firstItem="aSn-OV-epF" firstAttribute="leading" secondItem="vDu-zF-Fre" secondAttribute="leading" constant="16" id="hem-oS-VnE"/>
|
||||
<constraint firstItem="vDu-zF-Fre" firstAttribute="trailing" secondItem="22X-aK-4D2" secondAttribute="trailing" constant="16" id="q8c-eN-9im"/>
|
||||
<constraint firstItem="22X-aK-4D2" firstAttribute="top" secondItem="vDu-zF-Fre" secondAttribute="top" constant="16" id="ufQ-NF-PUt"/>
|
||||
<constraint firstItem="22X-aK-4D2" firstAttribute="leading" secondItem="vDu-zF-Fre" secondAttribute="leading" constant="16" id="vdh-02-FdI"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<modalPageSheetSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<connections>
|
||||
<outlet property="acceptButton" destination="JPi-uh-vpV" id="MuZ-ah-AIm"/>
|
||||
<outlet property="avatarView" destination="aSn-OV-epF" id="kgk-RU-l5L"/>
|
||||
<outlet property="closeButton" destination="dxd-y5-bn4" id="T5W-Ah-JMq"/>
|
||||
<outlet property="declineButton" destination="utA-Mz-rmH" id="UwR-LU-rv5"/>
|
||||
<outlet property="inviteActionPanel" destination="N1F-Ko-xvc" id="yjc-4a-nkf"/>
|
||||
<outlet property="inviterAvatarView" destination="yVi-9K-5iE" id="qBp-MT-d3U"/>
|
||||
<outlet property="inviterIdLabel" destination="Vw0-9q-U23" id="RxX-Zo-frz"/>
|
||||
<outlet property="inviterPanelHeight" destination="GXB-6p-EIC" id="VCL-wF-kiK"/>
|
||||
<outlet property="inviterSeparatorView" destination="GbA-LS-7G8" id="wzH-LB-Hsv"/>
|
||||
<outlet property="inviterTitleLabel" destination="oZk-F6-3nn" id="1Ih-UD-XYM"/>
|
||||
<outlet property="joinButton" destination="xQH-D8-TVA" id="PUa-fv-FOK"/>
|
||||
<outlet property="joinButtonBottomMargin" destination="MhW-nH-ei4" id="w7A-jz-twK"/>
|
||||
<outlet property="joinButtonTopMargin" destination="MfS-3y-K9f" id="90t-7l-MPe"/>
|
||||
<outlet property="spaceTypeIconView" destination="5eT-si-nJh" id="AIS-HH-xrs"/>
|
||||
<outlet property="spaceTypeLabel" destination="ko6-Oy-KB4" id="QhM-7w-ipS"/>
|
||||
<outlet property="titleLabel" destination="3Mp-yr-jUa" id="Dhq-d3-4lb"/>
|
||||
<outlet property="topicLabel" destination="Rg1-rU-wKD" id="EYy-cs-M08"/>
|
||||
<outlet property="topicScrollView" destination="VRt-iQ-AXx" id="6Ti-Cm-gcP"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="Ief-a0-LHa" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-117.39130434782609" y="69.642857142857139"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="space_menu_close" width="10" height="10.5"/>
|
||||
<image name="space_type_icon" width="12" height="12.5"/>
|
||||
<systemColor name="systemBackgroundColor">
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
278
Riot/Modules/Spaces/SpaceDetail/SpaceDetailViewController.swift
Normal file
|
@ -0,0 +1,278 @@
|
|||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
class SpaceDetailViewController: UIViewController {
|
||||
|
||||
// MARK: - Constants
|
||||
|
||||
private enum Constants {
|
||||
static let popoverWidth: CGFloat = 320
|
||||
static let topicMaxHeight: CGFloat = 105
|
||||
}
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private var theme: Theme!
|
||||
private var mediaManager: MXMediaManager!
|
||||
private var viewModel: SpaceDetailViewModelType!
|
||||
private var errorPresenter: MXKErrorPresentation!
|
||||
private var activityPresenter: ActivityIndicatorPresenter!
|
||||
private var isJoined: Bool = false
|
||||
|
||||
// MARK: Outlets
|
||||
|
||||
@IBOutlet private weak var inviterPanelHeight: NSLayoutConstraint!
|
||||
@IBOutlet private weak var inviterAvatarView: RoomAvatarView!
|
||||
@IBOutlet private weak var inviterTitleLabel: UILabel!
|
||||
@IBOutlet private weak var inviterIdLabel: UILabel!
|
||||
@IBOutlet private weak var inviterSeparatorView: UIView!
|
||||
|
||||
@IBOutlet private weak var avatarView: SpaceAvatarView!
|
||||
@IBOutlet private weak var titleLabel: UILabel!
|
||||
@IBOutlet private weak var closeButton: UIButton!
|
||||
@IBOutlet private weak var spaceTypeIconView: UIImageView!
|
||||
@IBOutlet private weak var spaceTypeLabel: UILabel!
|
||||
@IBOutlet private weak var topicLabel: UILabel!
|
||||
@IBOutlet private weak var topicScrollView: UIScrollView!
|
||||
|
||||
@IBOutlet private weak var joinButtonTopMargin: NSLayoutConstraint!
|
||||
@IBOutlet private weak var joinButtonBottomMargin: NSLayoutConstraint!
|
||||
@IBOutlet private weak var joinButton: UIButton!
|
||||
@IBOutlet private weak var declineButton: UIButton!
|
||||
@IBOutlet private weak var acceptButton: UIButton!
|
||||
@IBOutlet private weak var inviteActionPanel: UIView!
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
class func instantiate(mediaManager: MXMediaManager, viewModel: SpaceDetailViewModelType!) -> SpaceDetailViewController {
|
||||
let viewController = StoryboardScene.SpaceDetailViewController.initialScene.instantiate()
|
||||
viewController.mediaManager = mediaManager
|
||||
viewController.viewModel = viewModel
|
||||
viewController.theme = ThemeService.shared().theme
|
||||
return viewController
|
||||
}
|
||||
|
||||
// MARK: - Life cycle
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
// Do any additional setup after loading the view.
|
||||
|
||||
self.setupViews()
|
||||
self.activityPresenter = ActivityIndicatorPresenter()
|
||||
self.errorPresenter = MXKErrorAlertPresentation()
|
||||
|
||||
self.registerThemeServiceDidChangeThemeNotification()
|
||||
self.update(theme: self.theme)
|
||||
|
||||
self.viewModel.viewDelegate = self
|
||||
self.viewModel.process(viewAction: .loadData)
|
||||
}
|
||||
|
||||
override var preferredStatusBarStyle: UIStatusBarStyle {
|
||||
return self.theme.statusBarStyle
|
||||
}
|
||||
|
||||
override var preferredContentSize: CGSize {
|
||||
get {
|
||||
return CGSize(width: Constants.popoverWidth, height: self.intrisicHeight(with: Constants.popoverWidth))
|
||||
}
|
||||
set {
|
||||
super.preferredContentSize = newValue
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
|
||||
self.viewModel.process(viewAction: .dismissed)
|
||||
}
|
||||
|
||||
// MARK: - IBActions
|
||||
|
||||
@IBAction private func closeAction(sender: UIButton) {
|
||||
self.viewModel.process(viewAction: .dismiss)
|
||||
}
|
||||
|
||||
@IBAction private func joinAction(sender: UIButton) {
|
||||
if isJoined {
|
||||
self.viewModel.process(viewAction: .open)
|
||||
} else {
|
||||
self.viewModel.process(viewAction: .join)
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction private func leaveAction(sender: UIButton) {
|
||||
self.viewModel.process(viewAction: .leave)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func update(theme: Theme) {
|
||||
self.theme = theme
|
||||
|
||||
self.view.backgroundColor = theme.colors.background
|
||||
|
||||
self.inviterAvatarView.update(theme: theme)
|
||||
self.inviterTitleLabel.textColor = theme.colors.secondaryContent
|
||||
self.inviterTitleLabel.font = theme.fonts.calloutSB
|
||||
self.inviterIdLabel.textColor = theme.colors.secondaryContent
|
||||
self.inviterIdLabel.font = theme.fonts.footnote
|
||||
self.inviterSeparatorView.backgroundColor = theme.colors.navigation
|
||||
|
||||
self.titleLabel.textColor = theme.colors.primaryContent
|
||||
self.titleLabel.font = theme.fonts.title3SB
|
||||
self.closeButton.backgroundColor = theme.roomInputTextBorder
|
||||
self.closeButton.tintColor = theme.noticeSecondaryColor
|
||||
self.avatarView.update(theme: theme)
|
||||
|
||||
self.spaceTypeIconView.tintColor = theme.colors.tertiaryContent
|
||||
self.spaceTypeLabel.font = theme.fonts.callout
|
||||
self.spaceTypeLabel.textColor = theme.colors.tertiaryContent
|
||||
self.topicLabel.font = theme.fonts.caption1
|
||||
self.topicLabel.textColor = theme.colors.tertiaryContent
|
||||
|
||||
apply(theme: theme, on: self.joinButton)
|
||||
apply(theme: theme, on: self.acceptButton)
|
||||
|
||||
self.declineButton.layer.borderColor = theme.colors.alert.cgColor
|
||||
self.declineButton.tintColor = theme.colors.alert
|
||||
self.declineButton.setTitleColor(theme.colors.alert, for: .normal)
|
||||
self.declineButton.titleLabel?.font = theme.fonts.body
|
||||
}
|
||||
|
||||
private func apply(theme: Theme, on button: UIButton) {
|
||||
button.backgroundColor = theme.colors.accent
|
||||
button.tintColor = theme.colors.background
|
||||
button.setTitleColor(theme.colors.background, for: .normal)
|
||||
button.titleLabel?.font = theme.fonts.bodySB
|
||||
}
|
||||
|
||||
private func registerThemeServiceDidChangeThemeNotification() {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil)
|
||||
}
|
||||
|
||||
@objc private func themeDidChange() {
|
||||
self.update(theme: ThemeService.shared().theme)
|
||||
}
|
||||
|
||||
private func setupViews() {
|
||||
self.closeButton.layer.masksToBounds = true
|
||||
self.closeButton.layer.cornerRadius = self.closeButton.bounds.height / 2
|
||||
|
||||
self.setup(button: self.joinButton, withTitle: VectorL10n.join)
|
||||
self.setup(button: self.acceptButton, withTitle: VectorL10n.accept)
|
||||
self.setup(button: self.declineButton, withTitle: VectorL10n.decline)
|
||||
self.declineButton.layer.borderWidth = 1.0
|
||||
}
|
||||
|
||||
private func setup(button: UIButton, withTitle title: String) {
|
||||
button.layer.masksToBounds = true
|
||||
button.layer.cornerRadius = 8.0
|
||||
button.setTitle(title.uppercased(), for: .normal)
|
||||
}
|
||||
|
||||
private func render(viewState: SpaceDetailViewState) {
|
||||
switch viewState {
|
||||
case .loading:
|
||||
self.renderLoading()
|
||||
case .loaded(let parameters):
|
||||
self.renderLoaded(parameters: parameters)
|
||||
case .error(let error):
|
||||
self.render(error: error)
|
||||
}
|
||||
}
|
||||
|
||||
private func renderLoading() {
|
||||
self.activityPresenter.presentActivityIndicator(on: self.view, animated: true)
|
||||
}
|
||||
|
||||
private func renderLoaded(parameters: SpaceDetailLoadedParameters) {
|
||||
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
|
||||
|
||||
switch parameters.membership {
|
||||
case .invite:
|
||||
self.joinButton.isHidden = true
|
||||
self.inviteActionPanel.isHidden = false
|
||||
case .join:
|
||||
self.inviterPanelHeight.constant = 0
|
||||
self.joinButton.setTitle(VectorL10n.open, for: .normal)
|
||||
self.isJoined = true
|
||||
default:
|
||||
self.inviterPanelHeight.constant = 0
|
||||
}
|
||||
|
||||
let avatarViewData = AvatarViewData(matrixItemId: parameters.spaceId, displayName: parameters.displayName, avatarUrl: parameters.avatarUrl, mediaManager: self.mediaManager, fallbackImage: .matrixItem(parameters.spaceId, parameters.displayName))
|
||||
|
||||
self.titleLabel.text = parameters.displayName
|
||||
self.avatarView.fill(with: avatarViewData)
|
||||
self.topicLabel.text = parameters.topic
|
||||
|
||||
let joinRuleString = parameters.joinRule == .public ? VectorL10n.spacePublicJoinRule : VectorL10n.spacePrivateJoinRule
|
||||
|
||||
let membersCount = parameters.membersCount
|
||||
let membersString = membersCount == 1 ? VectorL10n.roomTitleOneMember : VectorL10n.roomTitleMembers("\(membersCount)")
|
||||
self.spaceTypeLabel.text = "\(joinRuleString) · \(membersString)"
|
||||
|
||||
self.inviterIdLabel.text = parameters.inviterId
|
||||
if let inviterId = parameters.inviterId {
|
||||
self.inviterTitleLabel.text = "\(parameters.inviter?.displayname ?? inviterId) invited you"
|
||||
|
||||
if let inviter = parameters.inviter {
|
||||
let avatarViewData = AvatarViewData(matrixItemId: inviter.userId, displayName: inviter.displayname, avatarUrl: inviter.avatarUrl, mediaManager: self.mediaManager, fallbackImage: .matrixItem(inviter.userId, inviter.displayname))
|
||||
self.inviterAvatarView.fill(with: avatarViewData)
|
||||
}
|
||||
}
|
||||
|
||||
view.layoutIfNeeded()
|
||||
}
|
||||
|
||||
private func render(error: Error) {
|
||||
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
|
||||
self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil)
|
||||
}
|
||||
|
||||
private func intrisicHeight(with width: CGFloat) -> CGFloat {
|
||||
let topicHeight = min(self.topicLabel.sizeThatFits(CGSize(width: width - self.topicScrollView.frame.minX * 2, height: 0)).height, Constants.topicMaxHeight)
|
||||
return self.topicScrollView.frame.minY + topicHeight + self.joinButton.frame.height + self.joinButtonTopMargin.constant + self.joinButtonBottomMargin.constant
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SlidingModalPresentable
|
||||
|
||||
extension SpaceDetailViewController: SlidingModalPresentable {
|
||||
|
||||
func allowsDismissOnBackgroundTap() -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func layoutHeightFittingWidth(_ width: CGFloat) -> CGFloat {
|
||||
return self.intrisicHeight(with: width) + self.joinButtonTopMargin.constant + self.joinButtonBottomMargin.constant
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - SpaceDetailViewModelViewDelegate
|
||||
|
||||
extension SpaceDetailViewController: SpaceDetailViewModelViewDelegate {
|
||||
func spaceDetailViewModel(_ viewModel: SpaceDetailViewModelType, didUpdateViewState viewSate: SpaceDetailViewState) {
|
||||
self.render(viewState: viewSate)
|
||||
}
|
||||
}
|
130
Riot/Modules/Spaces/SpaceDetail/SpaceDetailViewModel.swift
Normal file
|
@ -0,0 +1,130 @@
|
|||
//
|
||||
// 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
|
||||
|
||||
/// View model used by `SpaceDetailViewController`
|
||||
class SpaceDetailViewModel: SpaceDetailViewModelType {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
weak var coordinatorDelegate: SpaceDetailModelViewModelCoordinatorDelegate?
|
||||
weak var viewDelegate: SpaceDetailViewModelViewDelegate?
|
||||
|
||||
private let session: MXSession
|
||||
private let spaceId: String
|
||||
private let publicRoom: MXPublicRoom?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(session: MXSession, spaceId: String) {
|
||||
self.session = session
|
||||
self.spaceId = spaceId
|
||||
self.publicRoom = nil
|
||||
}
|
||||
|
||||
init(session: MXSession, publicRoom: MXPublicRoom) {
|
||||
self.session = session
|
||||
self.publicRoom = publicRoom
|
||||
self.spaceId = publicRoom.roomId
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func process(viewAction: SpaceDetailViewAction) {
|
||||
switch viewAction {
|
||||
case .loadData:
|
||||
self.loadData()
|
||||
case .join:
|
||||
self.join()
|
||||
case .leave:
|
||||
self.leave()
|
||||
case .open:
|
||||
self.coordinatorDelegate?.spaceDetailViewModelDidOpen(self)
|
||||
case .dismiss:
|
||||
self.coordinatorDelegate?.spaceDetailViewModelDidCancel(self)
|
||||
case .dismissed:
|
||||
self.coordinatorDelegate?.spaceDetailViewModelDidDismiss(self)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func update(viewState: SpaceDetailViewState) {
|
||||
self.viewDelegate?.spaceDetailViewModel(self, didUpdateViewState: viewState)
|
||||
}
|
||||
|
||||
private func loadData() {
|
||||
if let publicRoom = self.publicRoom {
|
||||
self.update(viewState: .loaded(SpaceDetailLoadedParameters(spaceId: publicRoom.roomId, displayName: publicRoom.displayname(), topic: publicRoom.topic, avatarUrl: publicRoom.avatarUrl, joinRule: publicRoom.worldReadable ? .public : .private, membership: .unknown, inviterId: nil, inviter: nil, membersCount: UInt(publicRoom.numJoinedMembers))))
|
||||
} else {
|
||||
guard let space = self.session.spaceService.getSpace(withId: self.spaceId), let summary = space.summary else {
|
||||
MXLog.error("[SpaceDetailViewModel] setupViews: no space found")
|
||||
return
|
||||
}
|
||||
|
||||
let parameters = SpaceDetailLoadedParameters(spaceId: space.spaceId, displayName: summary.displayname, topic: summary.topic, avatarUrl: summary.avatar, joinRule: nil, membership: summary.membership, inviterId: nil, inviter: nil, membersCount: 0)
|
||||
self.update(viewState: .loaded(parameters))
|
||||
|
||||
self.update(viewState: .loading)
|
||||
space.room.state { state in
|
||||
let joinRule = state?.joinRule
|
||||
let membersCount = summary.membersCount.members
|
||||
|
||||
var inviterId: String?
|
||||
var inviter: MXUser?
|
||||
state?.stateEvents.forEach({ event in
|
||||
if event.wireEventType == .roomMember && event.stateKey == self.session.myUserId {
|
||||
guard let userId = event.sender else {
|
||||
return
|
||||
}
|
||||
inviterId = userId
|
||||
inviter = self.session.user(withUserId: userId)
|
||||
}
|
||||
})
|
||||
|
||||
let parameters = SpaceDetailLoadedParameters(spaceId: space.spaceId, displayName: summary.displayname, topic: summary.topic, avatarUrl: summary.avatar, joinRule: joinRule, membership: summary.membership, inviterId: inviterId, inviter: inviter, membersCount: membersCount)
|
||||
self.update(viewState: .loaded(parameters))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func join() {
|
||||
self.update(viewState: .loading)
|
||||
self.session.joinRoom(self.spaceId) { [weak self] (response) in
|
||||
guard let self = self else { return }
|
||||
switch response {
|
||||
case .success:
|
||||
self.coordinatorDelegate?.spaceDetailViewModelDidJoin(self)
|
||||
case .failure(let error):
|
||||
self.update(viewState: .error(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func leave() {
|
||||
self.update(viewState: .loading)
|
||||
self.session.leaveRoom(self.spaceId) { [weak self] (response) in
|
||||
guard let self = self else { return }
|
||||
switch response {
|
||||
case .success:
|
||||
self.process(viewAction: .dismiss)
|
||||
case .failure(let error):
|
||||
self.update(viewState: .error(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
//
|
||||
// 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 SpaceDetailViewModelViewDelegate: AnyObject {
|
||||
func spaceDetailViewModel(_ viewModel: SpaceDetailViewModelType, didUpdateViewState viewSate: SpaceDetailViewState)
|
||||
}
|
||||
|
||||
protocol SpaceDetailModelViewModelCoordinatorDelegate: AnyObject {
|
||||
func spaceDetailViewModelDidCancel(_ viewModel: SpaceDetailViewModelType)
|
||||
func spaceDetailViewModelDidDismiss(_ viewModel: SpaceDetailViewModelType)
|
||||
func spaceDetailViewModelDidOpen(_ viewModel: SpaceDetailViewModelType)
|
||||
func spaceDetailViewModelDidJoin(_ viewModel: SpaceDetailViewModelType)
|
||||
}
|
||||
|
||||
/// Protocol describing the view model used by `SpaceDetailViewController`
|
||||
protocol SpaceDetailViewModelType {
|
||||
|
||||
var viewDelegate: SpaceDetailViewModelViewDelegate? { get set }
|
||||
var coordinatorDelegate: SpaceDetailModelViewModelCoordinatorDelegate? { get set }
|
||||
|
||||
func process(viewAction: SpaceDetailViewAction)
|
||||
}
|
36
Riot/Modules/Spaces/SpaceDetail/SpaceDetailViewState.swift
Normal file
|
@ -0,0 +1,36 @@
|
|||
//
|
||||
// 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
|
||||
|
||||
struct SpaceDetailLoadedParameters {
|
||||
let spaceId: String
|
||||
let displayName: String?
|
||||
let topic: String?
|
||||
let avatarUrl: String?
|
||||
let joinRule: MXRoomJoinRule?
|
||||
let membership: MXMembership
|
||||
let inviterId: String?
|
||||
let inviter: MXUser?
|
||||
let membersCount: UInt
|
||||
}
|
||||
|
||||
/// SpaceDetailViewController view state
|
||||
enum SpaceDetailViewState {
|
||||
case loading
|
||||
case loaded(_ paremeters: SpaceDetailLoadedParameters)
|
||||
case error(Error)
|
||||
}
|
88
Riot/Modules/Spaces/SpaceList/SpaceListCoordinator.swift
Normal file
|
@ -0,0 +1,88 @@
|
|||
// File created from ScreenTemplate
|
||||
// $ createScreen.sh Spaces/SpaceList SpaceList
|
||||
/*
|
||||
Copyright 2020 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
|
||||
|
||||
/// Side menu space list
|
||||
final class SpaceListCoordinator: SpaceListCoordinatorType {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let parameters: SpaceListCoordinatorParameters
|
||||
private var spaceListViewModel: SpaceListViewModelType
|
||||
private let spaceListViewController: SpaceListViewController
|
||||
|
||||
// MARK: Public
|
||||
|
||||
// Must be used only internally
|
||||
var childCoordinators: [Coordinator] = []
|
||||
|
||||
weak var delegate: SpaceListCoordinatorDelegate?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(parameters: SpaceListCoordinatorParameters) {
|
||||
self.parameters = parameters
|
||||
|
||||
let spaceListViewModel = SpaceListViewModel(session: self.parameters.session)
|
||||
let spaceListViewController = SpaceListViewController.instantiate(with: spaceListViewModel)
|
||||
self.spaceListViewModel = spaceListViewModel
|
||||
self.spaceListViewController = spaceListViewController
|
||||
}
|
||||
|
||||
// MARK: - Public methods
|
||||
|
||||
func start() {
|
||||
self.spaceListViewModel.coordinatorDelegate = self
|
||||
}
|
||||
|
||||
func toPresentable() -> UIViewController {
|
||||
return self.spaceListViewController
|
||||
}
|
||||
|
||||
func revertItemSelection() {
|
||||
self.spaceListViewModel.revertItemSelection()
|
||||
}
|
||||
|
||||
func select(spaceWithId spaceId: String) {
|
||||
self.spaceListViewModel.select(spaceWithId: spaceId)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SpaceListViewModelCoordinatorDelegate
|
||||
extension SpaceListCoordinator: SpaceListViewModelCoordinatorDelegate {
|
||||
|
||||
func spaceListViewModelDidSelectHomeSpace(_ viewModel: SpaceListViewModelType) {
|
||||
self.delegate?.spaceListCoordinatorDidSelectHomeSpace(self)
|
||||
}
|
||||
|
||||
func spaceListViewModel(_ viewModel: SpaceListViewModelType, didSelectSpaceWithId spaceId: String) {
|
||||
self.delegate?.spaceListCoordinator(self, didSelectSpaceWithId: spaceId)
|
||||
}
|
||||
|
||||
func spaceListViewModel(_ viewModel: SpaceListViewModelType, didSelectInviteWithId spaceId: String, from sourceView: UIView?) {
|
||||
self.delegate?.spaceListCoordinator(self, didSelectInviteWithId: spaceId, from: sourceView)
|
||||
}
|
||||
|
||||
func spaceListViewModel(_ viewModel: SpaceListViewModelType, didPressMoreForSpaceWithId spaceId: String, from sourceView: UIView) {
|
||||
self.delegate?.spaceListCoordinator(self, didPressMoreForSpaceWithId: spaceId, from: sourceView)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
||||
class SpaceListCoordinatorParameters {
|
||||
let session: MXSession
|
||||
|
||||
init(session: MXSession) {
|
||||
self.session = session
|
||||
}
|
||||
}
|
33
Riot/Modules/Spaces/SpaceList/SpaceListCoordinatorType.swift
Normal file
|
@ -0,0 +1,33 @@
|
|||
// File created from ScreenTemplate
|
||||
// $ createScreen.sh Spaces/SpaceList SpaceList
|
||||
/*
|
||||
Copyright 2020 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 SpaceListCoordinatorDelegate: AnyObject {
|
||||
func spaceListCoordinatorDidSelectHomeSpace(_ coordinator: SpaceListCoordinatorType)
|
||||
func spaceListCoordinator(_ coordinator: SpaceListCoordinatorType, didSelectSpaceWithId spaceId: String)
|
||||
func spaceListCoordinator(_ coordinator: SpaceListCoordinatorType, didSelectInviteWithId spaceId: String, from sourceView: UIView?)
|
||||
func spaceListCoordinator(_ coordinator: SpaceListCoordinatorType, didPressMoreForSpaceWithId spaceId: String, from sourceView: UIView)
|
||||
}
|
||||
|
||||
/// `SpaceListCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow.
|
||||
protocol SpaceListCoordinatorType: Coordinator, Presentable {
|
||||
var delegate: SpaceListCoordinatorDelegate? { get }
|
||||
func revertItemSelection()
|
||||
func select(spaceWithId spaceId: String)
|
||||
}
|