mirror of
https://github.com/vector-im/element-ios.git
synced 2024-09-28 15:22:39 +00:00
Merge branch 'develop' into doug/fix_warnings
# Conflicts: # Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewController.swift
This commit is contained in:
commit
989f19696d
72 changed files with 1920 additions and 116 deletions
11
CHANGES.rst
11
CHANGES.rst
|
@ -5,10 +5,17 @@ Changes to be released in next version
|
|||
*
|
||||
|
||||
🙌 Improvements
|
||||
*
|
||||
* Room Notification Settings: Ability to change between "All Messages", "Mentions and Keywords" and "None". Not yet exposed in Element UI. (#4458).
|
||||
* Add support for sending slow motion videos (#4483).
|
||||
|
||||
🐛 Bugfix
|
||||
*
|
||||
* VoIP: Do not present ended calls.
|
||||
* More fixes to Main.storyboard layout on iPhone 12 Pro Max (#4527)
|
||||
* Fix crash on Apple Silicon Macs.
|
||||
* Media Picker: Generate video thumbnails with the correct orientation (#4515).
|
||||
* Directory List (pop-up one): Fix duplicate rooms being shown (#4537).
|
||||
* Use different title for scan button for self verification (#4525).
|
||||
* it's easy for the back button to trigger a leftpanel reveal (#4438).
|
||||
|
||||
⚠️ API Changes
|
||||
*
|
||||
|
|
|
@ -295,6 +295,7 @@ final class BuildSettings: NSObject {
|
|||
static let roomSettingsScreenShowFlairSettings: Bool = true
|
||||
static let roomSettingsScreenShowAdvancedSettings: Bool = true
|
||||
static let roomSettingsScreenAdvancedShowEncryptToVerifiedOption: Bool = true
|
||||
static let roomSettingsScreenShowNotificationsV2: Bool = false
|
||||
|
||||
// MARK: - Room Member Screen
|
||||
|
||||
|
|
|
@ -151,7 +151,7 @@
|
|||
<!--People View Controller-->
|
||||
<scene sceneID="Qba-PP-lco">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="PeopleViewController" id="IGB-jr-yFz" customClass="PeopleViewController" sceneMemberID="viewController">
|
||||
<viewController storyboardIdentifier="PeopleViewController" extendedLayoutIncludesOpaqueBars="YES" id="IGB-jr-yFz" customClass="PeopleViewController" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="Mhy-d3-Jh6"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="Hkk-qB-8tq"/>
|
||||
|
@ -177,7 +177,7 @@
|
|||
<!--Favourites View Controller-->
|
||||
<scene sceneID="z6B-k5-ano">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="FavouritesViewController" id="HnD-LA-psC" customClass="FavouritesViewController" sceneMemberID="viewController">
|
||||
<viewController storyboardIdentifier="FavouritesViewController" extendedLayoutIncludesOpaqueBars="YES" id="HnD-LA-psC" customClass="FavouritesViewController" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="pOc-AC-QkD"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="W6L-Au-CaZ"/>
|
||||
|
@ -463,7 +463,7 @@
|
|||
<!--Rooms View Controller-->
|
||||
<scene sceneID="SDg-Pp-8Uj">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="RoomsViewController" id="HPQ-zg-lZR" customClass="RoomsViewController" sceneMemberID="viewController">
|
||||
<viewController storyboardIdentifier="RoomsViewController" extendedLayoutIncludesOpaqueBars="YES" id="HPQ-zg-lZR" customClass="RoomsViewController" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="Hkg-kw-ioH"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="UI8-oQ-9M9"/>
|
||||
|
@ -581,7 +581,7 @@
|
|||
</scene>
|
||||
</scenes>
|
||||
<inferredMetricsTieBreakers>
|
||||
<segue reference="mhb-l9-pM3"/>
|
||||
<segue reference="Tfl-tq-LQp"/>
|
||||
<segue reference="f5u-Y1-7nt"/>
|
||||
</inferredMetricsTieBreakers>
|
||||
<resources>
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "room_action_notification_muted.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "room_action_notification_muted@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "room_action_notification_muted@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 508 B |
Binary file not shown.
After Width: | Height: | Size: 846 B |
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
23
Riot/Assets/Images.xcassets/Room/notifications.imageset/Contents.json
vendored
Normal file
23
Riot/Assets/Images.xcassets/Room/notifications.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "notifications.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "notifications@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "notifications@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
Riot/Assets/Images.xcassets/Room/notifications.imageset/notifications.png
vendored
Normal file
BIN
Riot/Assets/Images.xcassets/Room/notifications.imageset/notifications.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 451 B |
BIN
Riot/Assets/Images.xcassets/Room/notifications.imageset/notifications@2x.png
vendored
Normal file
BIN
Riot/Assets/Images.xcassets/Room/notifications.imageset/notifications@2x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 704 B |
BIN
Riot/Assets/Images.xcassets/Room/notifications.imageset/notifications@3x.png
vendored
Normal file
BIN
Riot/Assets/Images.xcassets/Room/notifications.imageset/notifications@3x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 1,018 B |
|
@ -710,6 +710,7 @@ Tap the + to start adding people.";
|
|||
"room_details_topic" = "Topic";
|
||||
"room_details_favourite_tag" = "Favourite";
|
||||
"room_details_low_priority_tag" = "Low priority";
|
||||
"room_details_notifs" = "Notifications";
|
||||
"room_details_mute_notifs" = "Mute notifications";
|
||||
"room_details_direct_chat" = "Direct Chat";
|
||||
"room_details_access_section"="Who can access this room?";
|
||||
|
@ -772,6 +773,17 @@ Tap the + to start adding people.";
|
|||
"room_details_copy_room_address" = "Copy Room Address";
|
||||
"room_details_copy_room_url" = "Copy Room URL";
|
||||
|
||||
// Room Notification Settings
|
||||
"room_notifs_settings_notify_me_for" = "Notify me for";
|
||||
"room_notifs_settings_all_messages" = "All Messages";
|
||||
"room_notifs_settings_mentions_and_keywords" = "Mentions and Keywords only";
|
||||
"room_notifs_settings_none" = "None";
|
||||
"room_notifs_settings_done_action" = "Done";
|
||||
"room_notifs_settings_cancel_action" = "Cancel";
|
||||
"room_notifs_settings_manage_notifications" = "You can manage notifications in %@";
|
||||
"room_notifs_settings_account_settings" = "Account settings";
|
||||
"room_notifs_settings_encrypted_room_notice" = "Please note that mentions & keyword notifications are not available in encrypted rooms on mobile.";
|
||||
|
||||
// Group Details
|
||||
"group_details_title" = "Community Details";
|
||||
"group_details_home" = "Home";
|
||||
|
@ -1375,6 +1387,7 @@ Tap the + to start adding people.";
|
|||
"key_verification_verify_qr_code_information_other_device" = "Scan the code below to verify:";
|
||||
"key_verification_verify_qr_code_emoji_information" = "Verify by comparing unique emoji.";
|
||||
"key_verification_verify_qr_code_scan_code_action" = "Scan their code";
|
||||
"key_verification_verify_qr_code_scan_code_other_device_action" = "Scan with this device";
|
||||
"key_verification_verify_qr_code_cannot_scan_action" = "Can't scan?";
|
||||
"key_verification_verify_qr_code_start_emoji_action" = "Verify by emoji";
|
||||
|
||||
|
|
|
@ -85,6 +85,7 @@ internal enum Asset {
|
|||
internal static let roomActionFavourite = ImageAsset(name: "room_action_favourite")
|
||||
internal static let roomActionLeave = ImageAsset(name: "room_action_leave")
|
||||
internal static let roomActionNotification = ImageAsset(name: "room_action_notification")
|
||||
internal static let roomActionNotificationMuted = ImageAsset(name: "room_action_notification_muted")
|
||||
internal static let roomActionPriorityHigh = ImageAsset(name: "room_action_priority_high")
|
||||
internal static let roomActionPriorityLow = ImageAsset(name: "room_action_priority_low")
|
||||
internal static let homeEmptyScreenArtwork = ImageAsset(name: "home_empty_screen_artwork")
|
||||
|
@ -144,6 +145,7 @@ internal enum Asset {
|
|||
internal static let membersListIcon = ImageAsset(name: "members_list_icon")
|
||||
internal static let modIcon = ImageAsset(name: "mod_icon")
|
||||
internal static let moreReactions = ImageAsset(name: "more_reactions")
|
||||
internal static let notifications = ImageAsset(name: "notifications")
|
||||
internal static let scrollup = ImageAsset(name: "scrollup")
|
||||
internal static let roomsEmptyScreenArtwork = ImageAsset(name: "rooms_empty_screen_artwork")
|
||||
internal static let roomsEmptyScreenArtworkDark = ImageAsset(name: "rooms_empty_screen_artwork_dark")
|
||||
|
|
|
@ -172,6 +172,11 @@ internal enum StoryboardScene {
|
|||
|
||||
internal static let initialScene = InitialSceneType<Riot.RoomInfoListViewController>(storyboard: RoomInfoListViewController.self)
|
||||
}
|
||||
internal enum RoomNotificationSettingsViewController: StoryboardType {
|
||||
internal static let storyboardName = "RoomNotificationSettingsViewController"
|
||||
|
||||
internal static let initialScene = InitialSceneType<Riot.RoomNotificationSettingsViewController>(storyboard: RoomNotificationSettingsViewController.self)
|
||||
}
|
||||
internal enum SecretsRecoveryWithKeyViewController: StoryboardType {
|
||||
internal static let storyboardName = "SecretsRecoveryWithKeyViewController"
|
||||
|
||||
|
|
|
@ -2014,6 +2014,10 @@ internal enum VectorL10n {
|
|||
internal static var keyVerificationVerifyQrCodeScanCodeAction: String {
|
||||
return VectorL10n.tr("Vector", "key_verification_verify_qr_code_scan_code_action")
|
||||
}
|
||||
/// Scan with this device
|
||||
internal static var keyVerificationVerifyQrCodeScanCodeOtherDeviceAction: String {
|
||||
return VectorL10n.tr("Vector", "key_verification_verify_qr_code_scan_code_other_device_action")
|
||||
}
|
||||
/// QR code has been successfully validated.
|
||||
internal static var keyVerificationVerifyQrCodeScanOtherCodeSuccessMessage: String {
|
||||
return VectorL10n.tr("Vector", "key_verification_verify_qr_code_scan_other_code_success_message")
|
||||
|
@ -2710,6 +2714,10 @@ internal enum VectorL10n {
|
|||
internal static var roomDetailsNoLocalAddressesForDm: String {
|
||||
return VectorL10n.tr("Vector", "room_details_no_local_addresses_for_dm")
|
||||
}
|
||||
/// Notifications
|
||||
internal static var roomDetailsNotifs: String {
|
||||
return VectorL10n.tr("Vector", "room_details_notifs")
|
||||
}
|
||||
/// Members
|
||||
internal static var roomDetailsPeople: String {
|
||||
return VectorL10n.tr("Vector", "room_details_people")
|
||||
|
@ -3018,6 +3026,42 @@ internal enum VectorL10n {
|
|||
internal static var roomNoPrivilegesToCreateGroupCall: String {
|
||||
return VectorL10n.tr("Vector", "room_no_privileges_to_create_group_call")
|
||||
}
|
||||
/// Account settings
|
||||
internal static var roomNotifsSettingsAccountSettings: String {
|
||||
return VectorL10n.tr("Vector", "room_notifs_settings_account_settings")
|
||||
}
|
||||
/// All Messages
|
||||
internal static var roomNotifsSettingsAllMessages: String {
|
||||
return VectorL10n.tr("Vector", "room_notifs_settings_all_messages")
|
||||
}
|
||||
/// Cancel
|
||||
internal static var roomNotifsSettingsCancelAction: String {
|
||||
return VectorL10n.tr("Vector", "room_notifs_settings_cancel_action")
|
||||
}
|
||||
/// Done
|
||||
internal static var roomNotifsSettingsDoneAction: String {
|
||||
return VectorL10n.tr("Vector", "room_notifs_settings_done_action")
|
||||
}
|
||||
/// Please note that mentions & keyword notifications are not available in encrypted rooms on mobile.
|
||||
internal static var roomNotifsSettingsEncryptedRoomNotice: String {
|
||||
return VectorL10n.tr("Vector", "room_notifs_settings_encrypted_room_notice")
|
||||
}
|
||||
/// You can manage notifications in %@
|
||||
internal static func roomNotifsSettingsManageNotifications(_ p1: String) -> String {
|
||||
return VectorL10n.tr("Vector", "room_notifs_settings_manage_notifications", p1)
|
||||
}
|
||||
/// Mentions and Keywords only
|
||||
internal static var roomNotifsSettingsMentionsAndKeywords: String {
|
||||
return VectorL10n.tr("Vector", "room_notifs_settings_mentions_and_keywords")
|
||||
}
|
||||
/// None
|
||||
internal static var roomNotifsSettingsNone: String {
|
||||
return VectorL10n.tr("Vector", "room_notifs_settings_none")
|
||||
}
|
||||
/// Notify me for
|
||||
internal static var roomNotifsSettingsNotifyMeFor: String {
|
||||
return VectorL10n.tr("Vector", "room_notifs_settings_notify_me_for")
|
||||
}
|
||||
/// Connectivity to the server has been lost.
|
||||
internal static var roomOfflineNotification: String {
|
||||
return VectorL10n.tr("Vector", "room_offline_notification")
|
||||
|
|
|
@ -393,7 +393,9 @@ class CallPresenter: NSObject {
|
|||
if let oldCallVC = self.callVCs.values.first,
|
||||
self.presentedCallVC == nil,
|
||||
!self.uiOperationQueue.containsPresentCallVCOperation,
|
||||
!self.uiOperationQueue.containsEnterPiPOperation {
|
||||
!self.uiOperationQueue.containsEnterPiPOperation,
|
||||
let oldCall = oldCallVC.mxCall,
|
||||
oldCall.state != .ended {
|
||||
// present the call screen after dismissing this one
|
||||
self.presentCallVC(oldCallVC)
|
||||
}
|
||||
|
|
|
@ -351,6 +351,9 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
|||
NSURL *messageSoundURL = [[NSBundle mainBundle] URLForResource:@"message" withExtension:@"caf"];
|
||||
AudioServicesCreateSystemSoundID((__bridge CFURLRef)messageSoundURL, &_messageSound);
|
||||
|
||||
// Set app info now as Mac (Designed for iPad) accesses it before didFinishLaunching is called
|
||||
self.appInfo = AppInfo.current;
|
||||
|
||||
MXLogDebug(@"[AppDelegate] willFinishLaunchingWithOptions: Done");
|
||||
|
||||
return YES;
|
||||
|
@ -371,8 +374,6 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
|||
|
||||
_configuration = [AppConfiguration new];
|
||||
|
||||
self.appInfo = AppInfo.current;
|
||||
|
||||
// Log app information
|
||||
NSString *appDisplayName = self.appInfo.displayName;
|
||||
NSString* appVersion = self.appVersion;
|
||||
|
|
|
@ -161,10 +161,15 @@
|
|||
- (void)makeDirectEditedRoom:(BOOL)isDirect;
|
||||
|
||||
/**
|
||||
Enable/disable the notifications for the selected room.
|
||||
*/
|
||||
Enable/disable the notifications for the selected room.
|
||||
*/
|
||||
- (void)muteEditedRoomNotifications:(BOOL)mute;
|
||||
|
||||
/**
|
||||
Edit notification settings for the selected room.
|
||||
*/
|
||||
- (void)changeEditedRoomNotificationSettings;
|
||||
|
||||
/**
|
||||
Show room directory.
|
||||
*/
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
|
||||
#import "Riot-Swift.h"
|
||||
|
||||
@interface RecentsViewController () <CreateRoomCoordinatorBridgePresenterDelegate, RoomsDirectoryCoordinatorBridgePresenterDelegate>
|
||||
@interface RecentsViewController () <CreateRoomCoordinatorBridgePresenterDelegate, RoomsDirectoryCoordinatorBridgePresenterDelegate, RoomNotificationSettingsCoordinatorBridgePresenterDelegate>
|
||||
{
|
||||
// Tell whether a recents refresh is pending (suspended during editing mode).
|
||||
BOOL isRefreshPending;
|
||||
|
@ -74,6 +74,8 @@
|
|||
|
||||
@property (nonatomic, strong) CustomSizedPresentationController *customSizedPresentationController;
|
||||
|
||||
@property (nonatomic, strong) RoomNotificationSettingsCoordinatorBridgePresenter *roomNotificationSettingsCoordinatorBridgePresenter;
|
||||
|
||||
@end
|
||||
|
||||
@implementation RecentsViewController
|
||||
|
@ -1031,12 +1033,31 @@
|
|||
UIContextualAction *muteAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive
|
||||
title:title
|
||||
handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) {
|
||||
[self muteEditedRoomNotifications:!isMuted];
|
||||
|
||||
if ([BuildSettings roomSettingsScreenShowNotificationsV2])
|
||||
{
|
||||
[self changeEditedRoomNotificationSettings];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self muteEditedRoomNotifications:!isMuted];
|
||||
}
|
||||
|
||||
|
||||
completionHandler(YES);
|
||||
}];
|
||||
muteAction.backgroundColor = actionBackgroundColor;
|
||||
|
||||
UIImage *notificationImage = [UIImage imageNamed:@"room_action_notification"];
|
||||
UIImage *notificationImage;
|
||||
if([BuildSettings roomSettingsScreenShowNotificationsV2])
|
||||
{
|
||||
notificationImage = isMuted ? [UIImage imageNamed:@"room_action_notification_muted"] : [UIImage imageNamed:@"room_action_notification"];
|
||||
}
|
||||
else
|
||||
{
|
||||
notificationImage = [UIImage imageNamed:@"room_action_notification"];
|
||||
}
|
||||
|
||||
notificationImage = [notificationImage vc_tintedImageUsingColor:isMuted ? unselectedColor : selectedColor];
|
||||
muteAction.image = [notificationImage vc_notRenderedImage];
|
||||
|
||||
|
@ -1298,6 +1319,23 @@
|
|||
}
|
||||
}
|
||||
|
||||
- (void)changeEditedRoomNotificationSettings
|
||||
{
|
||||
if (editedRoomId)
|
||||
{
|
||||
// Check whether the user didn't leave the room
|
||||
MXRoom *room = [self.mainSession roomWithRoomId:editedRoomId];
|
||||
if (room)
|
||||
{
|
||||
// navigate
|
||||
self.roomNotificationSettingsCoordinatorBridgePresenter = [[RoomNotificationSettingsCoordinatorBridgePresenter alloc] initWithRoom:room];
|
||||
self.roomNotificationSettingsCoordinatorBridgePresenter.delegate = self;
|
||||
[self.roomNotificationSettingsCoordinatorBridgePresenter presentFrom:self animated:YES];
|
||||
}
|
||||
[self cancelEditionMode:isRefreshPending];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)muteEditedRoomNotifications:(BOOL)mute
|
||||
{
|
||||
if (editedRoomId)
|
||||
|
@ -1307,27 +1345,27 @@
|
|||
if (room)
|
||||
{
|
||||
[self startActivityIndicator];
|
||||
|
||||
|
||||
if (mute)
|
||||
{
|
||||
[room mentionsOnly:^{
|
||||
|
||||
|
||||
[self stopActivityIndicator];
|
||||
|
||||
|
||||
// Leave editing mode
|
||||
[self cancelEditionMode:isRefreshPending];
|
||||
|
||||
[self cancelEditionMode:self->isRefreshPending];
|
||||
|
||||
}];
|
||||
}
|
||||
else
|
||||
{
|
||||
[room allMessages:^{
|
||||
|
||||
|
||||
[self stopActivityIndicator];
|
||||
|
||||
|
||||
// Leave editing mode
|
||||
[self cancelEditionMode:isRefreshPending];
|
||||
|
||||
[self cancelEditionMode:self->isRefreshPending];
|
||||
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
@ -2219,4 +2257,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
#pragma mark - RoomNotificationSettingsCoordinatorBridgePresenterDelegate
|
||||
-(void)roomNotificationSettingsCoordinatorBridgePresenterDelegateDidComplete:(RoomNotificationSettingsCoordinatorBridgePresenter *)coordinatorBridgePresenter
|
||||
{
|
||||
[coordinatorBridgePresenter dismissWithAnimated:YES completion:nil];
|
||||
self.roomNotificationSettingsCoordinatorBridgePresenter = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
39
Riot/Modules/Common/SectionHeaders/TitleHeaderView.swift
Normal file
39
Riot/Modules/Common/SectionHeaders/TitleHeaderView.swift
Normal file
|
@ -0,0 +1,39 @@
|
|||
//
|
||||
// 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
|
||||
|
||||
class TitleHeaderView: UITableViewHeaderFooterView {
|
||||
|
||||
@IBOutlet weak var label: UILabel!
|
||||
|
||||
func update(title: String) {
|
||||
label.text = title.uppercased()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
extension TitleHeaderView: NibReusable {}
|
||||
extension TitleHeaderView: Themable {
|
||||
|
||||
func update(theme: Theme) {
|
||||
contentView.backgroundColor = theme.headerBackgroundColor
|
||||
label.textColor = theme.headerTextSecondaryColor
|
||||
label.font = theme.fonts.body
|
||||
}
|
||||
}
|
39
Riot/Modules/Common/SectionHeaders/TitleHeaderView.xib
Normal file
39
Riot/Modules/Common/SectionHeaders/TitleHeaderView.xib
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" id="Yhn-wn-PmC" customClass="TitleHeaderView" customModule="Riot" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Yhn-wn-PmC" id="o2E-Jb-B0E">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<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="Gq6-Mz-QTu">
|
||||
<rect key="frame" x="20" y="11" width="374" height="22"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="Gq6-Mz-QTu" firstAttribute="bottom" secondItem="o2E-Jb-B0E" secondAttribute="bottomMargin" id="3FQ-Oa-GSW"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="Gq6-Mz-QTu" secondAttribute="trailing" id="NOy-36-cTp"/>
|
||||
<constraint firstItem="Gq6-Mz-QTu" firstAttribute="top" secondItem="o2E-Jb-B0E" secondAttribute="topMargin" id="iyg-1J-QRk"/>
|
||||
<constraint firstItem="Gq6-Mz-QTu" firstAttribute="leading" secondItem="o2E-Jb-B0E" secondAttribute="leadingMargin" id="rPi-h4-Odq"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<outlet property="label" destination="Gq6-Mz-QTu" id="dWm-UN-U4w"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="-86" y="-242"/>
|
||||
</tableViewCell>
|
||||
</objects>
|
||||
</document>
|
|
@ -54,20 +54,20 @@
|
|||
self.titleLabel.text = NSLocalizedStringFromTable(@"directory_search_results_title", @"Vector", nil);
|
||||
|
||||
// Do we need to display like ">20 results found" or "18 results found"?
|
||||
NSString *descriptionLabel = (publicRoomsDirectoryDataSource.moreThanRoomsCount && publicRoomsDirectoryDataSource.roomsCount > 0) ? NSLocalizedStringFromTable(@"directory_search_results_more_than", @"Vector", nil) : NSLocalizedStringFromTable(@"directory_search_results", @"Vector", nil);
|
||||
NSString *descriptionLabel = (publicRoomsDirectoryDataSource.searchResultsCountIsLimited && publicRoomsDirectoryDataSource.searchResultsCount > 0) ? NSLocalizedStringFromTable(@"directory_search_results_more_than", @"Vector", nil) : NSLocalizedStringFromTable(@"directory_search_results", @"Vector", nil);
|
||||
|
||||
self.descriptionLabel.text = [NSString stringWithFormat:descriptionLabel,
|
||||
publicRoomsDirectoryDataSource.roomsCount,
|
||||
publicRoomsDirectoryDataSource.searchResultsCount,
|
||||
publicRoomsDirectoryDataSource.searchPattern];
|
||||
}
|
||||
else
|
||||
{
|
||||
self.titleLabel.text = NSLocalizedStringFromTable(@"directory_cell_title", @"Vector", nil);
|
||||
self.descriptionLabel.text = [NSString stringWithFormat:NSLocalizedStringFromTable(@"directory_cell_description", @"Vector", nil),
|
||||
publicRoomsDirectoryDataSource.roomsCount];
|
||||
publicRoomsDirectoryDataSource.searchResultsCount];
|
||||
}
|
||||
|
||||
if (publicRoomsDirectoryDataSource.roomsCount)
|
||||
if (publicRoomsDirectoryDataSource.searchResultsCount)
|
||||
{
|
||||
self.userInteractionEnabled = YES;
|
||||
self.chevronImageView.hidden = NO;
|
||||
|
|
|
@ -347,7 +347,16 @@
|
|||
|
||||
tableViewCell.notificationsButton.tag = room.isMute || room.isMentionsOnly;
|
||||
[tableViewCell.notificationsButton addTarget:self action:@selector(onNotificationsButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
|
||||
tableViewCell.notificationsImageView.image = [UIImage imageNamed:@"room_action_notification"];
|
||||
|
||||
if ([BuildSettings roomSettingsScreenShowNotificationsV2])
|
||||
{
|
||||
tableViewCell.notificationsImageView.image = tableViewCell.notificationsButton.tag ? [UIImage imageNamed:@"room_action_notification_muted"] : [UIImage imageNamed:@"room_action_notification"];
|
||||
}
|
||||
else
|
||||
{
|
||||
tableViewCell.notificationsImageView.image = [UIImage imageNamed:@"room_action_notification"];
|
||||
}
|
||||
|
||||
tableViewCell.notificationsImageView.tintColor = tableViewCell.notificationsButton.tag ? unselectedColor : selectedColor;
|
||||
|
||||
// Get the room tag (use only the first one).
|
||||
|
@ -663,8 +672,15 @@
|
|||
MXRoom *room = [self.mainSession roomWithRoomId:editedRoomId];
|
||||
if (room)
|
||||
{
|
||||
UIButton *button = (UIButton*)sender;
|
||||
[self muteEditedRoomNotifications:!button.tag];
|
||||
if ([BuildSettings roomSettingsScreenShowNotificationsV2])
|
||||
{
|
||||
[self changeEditedRoomNotificationSettings];
|
||||
}
|
||||
else
|
||||
{
|
||||
UIButton *button = (UIButton*)sender;
|
||||
[self muteEditedRoomNotifications:!button.tag];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -143,7 +143,9 @@ final class KeyVerificationVerifyByScanningViewController: UIViewController {
|
|||
self.titleLabel.text = VectorL10n.keyVerificationVerifyQrCodeTitle
|
||||
self.informationLabel.text = VectorL10n.keyVerificationVerifyQrCodeInformation
|
||||
|
||||
self.scanCodeButton.setTitle(VectorL10n.keyVerificationVerifyQrCodeScanCodeAction, for: .normal)
|
||||
// Hide until we have the type of the verification request
|
||||
self.scanCodeButton.isHidden = true
|
||||
|
||||
self.cannotScanButton.setTitle(VectorL10n.keyVerificationVerifyQrCodeCannotScanAction, for: .normal)
|
||||
}
|
||||
|
||||
|
@ -157,8 +159,8 @@ final class KeyVerificationVerifyByScanningViewController: UIViewController {
|
|||
self.render(error: error)
|
||||
case .scannedCodeValidated(let isValid):
|
||||
self.renderScannedCode(valid: isValid)
|
||||
case .cancelled(let reason):
|
||||
self.renderCancelled(reason: reason)
|
||||
case .cancelled(let reason, let verificationKind):
|
||||
self.renderCancelled(reason: reason, verificationKind: verificationKind)
|
||||
case .cancelledByMe(let reason):
|
||||
self.renderCancelledByMe(reason: reason)
|
||||
}
|
||||
|
@ -195,10 +197,13 @@ final class KeyVerificationVerifyByScanningViewController: UIViewController {
|
|||
switch viewData.verificationKind {
|
||||
case .user:
|
||||
informationText = VectorL10n.keyVerificationVerifyQrCodeInformation
|
||||
self.scanCodeButton.setTitle(VectorL10n.keyVerificationVerifyQrCodeScanCodeAction, for: .normal)
|
||||
default:
|
||||
informationText = VectorL10n.keyVerificationVerifyQrCodeInformationOtherDevice
|
||||
self.scanCodeButton.setTitle(VectorL10n.keyVerificationVerifyQrCodeScanCodeOtherDeviceAction, for: .normal)
|
||||
}
|
||||
|
||||
self.scanCodeButton.isHidden = false
|
||||
self.informationLabel.text = informationText
|
||||
}
|
||||
}
|
||||
|
@ -231,12 +236,21 @@ final class KeyVerificationVerifyByScanningViewController: UIViewController {
|
|||
}
|
||||
}
|
||||
|
||||
private func renderCancelled(reason: MXTransactionCancelCode) {
|
||||
private func renderCancelled(reason: MXTransactionCancelCode,
|
||||
verificationKind: KeyVerificationKind) {
|
||||
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
|
||||
|
||||
self.stopQRCodeScanningIfPresented()
|
||||
|
||||
self.errorPresenter.presentError(from: self.alertPresentingViewController, title: "", message: VectorL10n.deviceVerificationCancelled, animated: true) {
|
||||
// if we're verifying with someone else, let the user know they cancelled.
|
||||
// if we're verifying our own device, assume the user probably knows since it was them who
|
||||
// cancelled on their other device
|
||||
if verificationKind == .user {
|
||||
self.errorPresenter.presentError(from: self.alertPresentingViewController, title: "", message: VectorL10n.deviceVerificationCancelled, animated: true) {
|
||||
self.dismissQRCodeScanningIfPresented(animated: false)
|
||||
self.viewModel.process(viewAction: .cancel)
|
||||
}
|
||||
} else {
|
||||
self.dismissQRCodeScanningIfPresented(animated: false)
|
||||
self.viewModel.process(viewAction: .cancel)
|
||||
}
|
||||
|
|
|
@ -225,7 +225,7 @@ final class KeyVerificationVerifyByScanningViewModel: KeyVerificationVerifyBySca
|
|||
return
|
||||
}
|
||||
self.unregisterTransactionDidStateChangeNotification()
|
||||
self.update(viewState: .cancelled(reason))
|
||||
self.update(viewState: .cancelled(cancelCode: reason, verificationKind: verificationKind))
|
||||
case MXSASTransactionStateCancelledByMe:
|
||||
guard let reason = transaction.reasonCancelCode else {
|
||||
return
|
||||
|
@ -251,7 +251,7 @@ final class KeyVerificationVerifyByScanningViewModel: KeyVerificationVerifyBySca
|
|||
return
|
||||
}
|
||||
self.unregisterTransactionDidStateChangeNotification()
|
||||
self.update(viewState: .cancelled(reason))
|
||||
self.update(viewState: .cancelled(cancelCode: reason, verificationKind: verificationKind))
|
||||
case .cancelledByMe:
|
||||
guard let reason = transaction.reasonCancelCode else {
|
||||
return
|
||||
|
|
|
@ -29,7 +29,7 @@ enum KeyVerificationVerifyByScanningViewState {
|
|||
case loading
|
||||
case loaded(viewData: KeyVerificationVerifyByScanningViewData)
|
||||
case scannedCodeValidated(isValid: Bool)
|
||||
case cancelled(MXTransactionCancelCode)
|
||||
case cancelled(cancelCode: MXTransactionCancelCode, verificationKind: KeyVerificationKind)
|
||||
case cancelledByMe(MXTransactionCancelCode)
|
||||
case error(Error)
|
||||
}
|
||||
|
|
|
@ -83,8 +83,8 @@ extension MediaPickerCoordinator: MediaPickerViewControllerDelegate {
|
|||
self.delegate?.mediaPickerCoordinator(self, didSelectImageData: imageData, withUTI: uti)
|
||||
}
|
||||
|
||||
func mediaPickerController(_ mediaPickerController: MediaPickerViewController!, didSelectVideo videoURL: URL!) {
|
||||
self.delegate?.mediaPickerCoordinator(self, didSelectVideoAt: videoURL)
|
||||
func mediaPickerController(_ mediaPickerController: MediaPickerViewController!, didSelectVideo videoAsset: AVAsset!) {
|
||||
self.delegate?.mediaPickerCoordinator(self, didSelectVideo: videoAsset)
|
||||
}
|
||||
|
||||
func mediaPickerController(_ mediaPickerController: MediaPickerViewController!, didSelect assets: [PHAsset]!) {
|
||||
|
|
|
@ -20,7 +20,7 @@ import Foundation
|
|||
|
||||
@objc protocol MediaPickerCoordinatorBridgePresenterDelegate {
|
||||
func mediaPickerCoordinatorBridgePresenter(_ coordinatorBridgePresenter: MediaPickerCoordinatorBridgePresenter, didSelectImageData imageData: Data, withUTI uti: MXKUTI?)
|
||||
func mediaPickerCoordinatorBridgePresenter(_ coordinatorBridgePresenter: MediaPickerCoordinatorBridgePresenter, didSelectVideoAt url: URL)
|
||||
func mediaPickerCoordinatorBridgePresenter(_ coordinatorBridgePresenter: MediaPickerCoordinatorBridgePresenter, didSelectVideo videoAsset: AVAsset)
|
||||
func mediaPickerCoordinatorBridgePresenter(_ coordinatorBridgePresenter: MediaPickerCoordinatorBridgePresenter, didSelectAssets assets: [PHAsset])
|
||||
func mediaPickerCoordinatorBridgePresenterDidCancel(_ coordinatorBridgePresenter: MediaPickerCoordinatorBridgePresenter)
|
||||
}
|
||||
|
@ -110,8 +110,8 @@ extension MediaPickerCoordinatorBridgePresenter: MediaPickerCoordinatorDelegate
|
|||
self.delegate?.mediaPickerCoordinatorBridgePresenter(self, didSelectImageData: imageData, withUTI: uti)
|
||||
}
|
||||
|
||||
func mediaPickerCoordinator(_ coordinator: MediaPickerCoordinatorType, didSelectVideoAt url: URL) {
|
||||
self.delegate?.mediaPickerCoordinatorBridgePresenter(self, didSelectVideoAt: url)
|
||||
func mediaPickerCoordinator(_ coordinator: MediaPickerCoordinatorType, didSelectVideo videoAsset: AVAsset) {
|
||||
self.delegate?.mediaPickerCoordinatorBridgePresenter(self, didSelectVideo: videoAsset)
|
||||
}
|
||||
|
||||
func mediaPickerCoordinator(_ coordinator: MediaPickerCoordinatorType, didSelectAssets assets: [PHAsset]) {
|
||||
|
|
|
@ -20,7 +20,7 @@ import Foundation
|
|||
|
||||
protocol MediaPickerCoordinatorDelegate: AnyObject {
|
||||
func mediaPickerCoordinator(_ coordinator: MediaPickerCoordinatorType, didSelectImageData imageData: Data, withUTI uti: MXKUTI?)
|
||||
func mediaPickerCoordinator(_ coordinator: MediaPickerCoordinatorType, didSelectVideoAt url: URL)
|
||||
func mediaPickerCoordinator(_ coordinator: MediaPickerCoordinatorType, didSelectVideo videoAsset: AVAsset)
|
||||
func mediaPickerCoordinator(_ coordinator: MediaPickerCoordinatorType, didSelectAssets assets: [PHAsset])
|
||||
func mediaPickerCoordinatorDidCancel(_ coordinator: MediaPickerCoordinatorType)
|
||||
}
|
||||
|
|
|
@ -39,9 +39,9 @@
|
|||
Tells the delegate that the user select a video.
|
||||
|
||||
@param mediaPickerController the `MediaPickerViewController` instance.
|
||||
@param videoURL the local url of the video to send.
|
||||
@param videoAsset an `AVAsset` that represents the video to send.
|
||||
*/
|
||||
- (void)mediaPickerController:(MediaPickerViewController *)mediaPickerController didSelectVideo:(NSURL*)videoURL;
|
||||
- (void)mediaPickerController:(MediaPickerViewController *)mediaPickerController didSelectVideo:(AVAsset*)videoAsset;
|
||||
|
||||
/**
|
||||
Tells the delegate that the user wants to cancel media picking.
|
||||
|
|
|
@ -608,28 +608,19 @@
|
|||
|
||||
if (asset)
|
||||
{
|
||||
if ([asset isKindOfClass:[AVURLAsset class]])
|
||||
{
|
||||
MXLogDebug(@"[MediaPickerVC] didSelectAsset: Got AVAsset for video");
|
||||
AVURLAsset *avURLAsset = (AVURLAsset*)asset;
|
||||
MXLogDebug(@"[MediaPickerVC] didSelectAsset: Got AVAsset for video");
|
||||
|
||||
// Validate first the selected video
|
||||
[self validateSelectedVideo:asset responseHandler:^(BOOL isValidated) {
|
||||
|
||||
if (isValidated)
|
||||
{
|
||||
[self.delegate mediaPickerController:self didSelectVideo:asset];
|
||||
}
|
||||
|
||||
// Validate first the selected video
|
||||
[self validateSelectedVideo:[avURLAsset URL] responseHandler:^(BOOL isValidated) {
|
||||
|
||||
if (isValidated)
|
||||
{
|
||||
[self.delegate mediaPickerController:self didSelectVideo:[avURLAsset URL]];
|
||||
}
|
||||
|
||||
self->isValidationInProgress = NO;
|
||||
|
||||
}];
|
||||
}
|
||||
else
|
||||
{
|
||||
MXLogDebug(@"[MediaPickerVC] Selected video asset is not initialized from an URL!");
|
||||
self->isValidationInProgress = NO;
|
||||
}
|
||||
|
||||
}];
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -693,7 +684,7 @@
|
|||
[self setNeedsStatusBarAppearanceUpdate];
|
||||
}
|
||||
|
||||
- (void)validateSelectedVideo:(NSURL*)selectedVideoURL responseHandler:(void (^)(BOOL isValidated))handler
|
||||
- (void)validateSelectedVideo:(AVAsset*)selectedVideo responseHandler:(void (^)(BOOL isValidated))handler
|
||||
{
|
||||
[self dismissImageValidationView];
|
||||
|
||||
|
@ -727,15 +718,16 @@
|
|||
videoPlayer = [[AVPlayerViewController alloc] init];
|
||||
if (videoPlayer)
|
||||
{
|
||||
AVPlayerItem *item = [AVPlayerItem playerItemWithAsset:selectedVideo];
|
||||
videoPlayer.allowsPictureInPicturePlayback = NO;
|
||||
videoPlayer.updatesNowPlayingInfoCenter = NO;
|
||||
videoPlayer.player = [AVPlayer playerWithURL:selectedVideoURL];
|
||||
videoPlayer.player = [AVPlayer playerWithPlayerItem:item];
|
||||
videoPlayer.videoGravity = AVLayerVideoGravityResizeAspect;
|
||||
videoPlayer.showsPlaybackControls = NO;
|
||||
|
||||
// create a thumbnail for the first frame
|
||||
AVAsset *asset = [AVAsset assetWithURL:selectedVideoURL];
|
||||
AVAssetImageGenerator *generator = [AVAssetImageGenerator assetImageGeneratorWithAsset:asset];
|
||||
AVAssetImageGenerator *generator = [AVAssetImageGenerator assetImageGeneratorWithAsset:selectedVideo];
|
||||
generator.appliesPreferredTrackTransform = YES;
|
||||
CGImageRef thumbnailRef = [generator copyCGImageAtTime:kCMTimeZero actualTime:nil error:nil];
|
||||
|
||||
// set thumbnail on validationView
|
||||
|
|
|
@ -135,7 +135,7 @@ extension SingleImagePickerPresenter: MediaPickerCoordinatorBridgePresenterDeleg
|
|||
self.delegate?.singleImagePickerPresenter(self, didSelectImageData: imageData, withUTI: uti)
|
||||
}
|
||||
|
||||
func mediaPickerCoordinatorBridgePresenter(_ coordinatorBridgePresenter: MediaPickerCoordinatorBridgePresenter, didSelectVideoAt url: URL) {
|
||||
func mediaPickerCoordinatorBridgePresenter(_ coordinatorBridgePresenter: MediaPickerCoordinatorBridgePresenter, didSelectVideo videoAsset: AVAsset) {
|
||||
self.delegate?.singleImagePickerPresenterDidCancel(self)
|
||||
}
|
||||
|
||||
|
|
|
@ -62,11 +62,16 @@
|
|||
@property (nonatomic, readonly) NSString *directoryServerDisplayname;
|
||||
|
||||
/**
|
||||
The number of public rooms matching `searchPattern`.
|
||||
It is accurate only if 'moreThanRoomsCount' is NO.
|
||||
The number of public rooms that have been fetched so far.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSUInteger roomsCount;
|
||||
|
||||
/**
|
||||
The total number of public rooms matching `searchPattern`.
|
||||
It is accurate only if 'searchResultsCountIsLimited' is NO.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSUInteger searchResultsCount;
|
||||
|
||||
/**
|
||||
In case of search with a lot of matching public rooms, we cannot return an accurate
|
||||
value except by paginating the full list of rooms, which is not expected.
|
||||
|
@ -74,7 +79,7 @@
|
|||
This flag indicates that we know that there is more matching rooms than we got
|
||||
so far.
|
||||
*/
|
||||
@property (nonatomic, readonly) BOOL moreThanRoomsCount;
|
||||
@property (nonatomic, readonly) BOOL searchResultsCountIsLimited;
|
||||
|
||||
/**
|
||||
The maximum number of public rooms to retrieve during a pagination.
|
||||
|
|
|
@ -165,6 +165,11 @@ static NSString *const kNSFWKeyword = @"nsfw";
|
|||
}
|
||||
}
|
||||
|
||||
- (NSUInteger)roomsCount
|
||||
{
|
||||
return rooms.count;
|
||||
}
|
||||
|
||||
- (NSIndexPath*)cellIndexPathWithRoomId:(NSString*)roomId andMatrixSession:(MXSession*)matrixSession
|
||||
{
|
||||
NSIndexPath *indexPath = nil;
|
||||
|
@ -217,8 +222,8 @@ static NSString *const kNSFWKeyword = @"nsfw";
|
|||
// Reset all pagination vars
|
||||
[rooms removeAllObjects];
|
||||
nextBatch = nil;
|
||||
_roomsCount = 0;
|
||||
_moreThanRoomsCount = NO;
|
||||
_searchResultsCount = 0;
|
||||
_searchResultsCountIsLimited = NO;
|
||||
_hasReachedPaginationEnd = NO;
|
||||
}
|
||||
|
||||
|
@ -264,14 +269,14 @@ static NSString *const kNSFWKeyword = @"nsfw";
|
|||
if (!self->_searchPattern)
|
||||
{
|
||||
// When there is no search, we can use totalRoomCountEstimate returned by the server
|
||||
self->_roomsCount = publicRoomsResponse.totalRoomCountEstimate;
|
||||
self->_moreThanRoomsCount = NO;
|
||||
self->_searchResultsCount = publicRoomsResponse.totalRoomCountEstimate;
|
||||
self->_searchResultsCountIsLimited = NO;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Else we can only display something like ">20 matching rooms"
|
||||
self->_roomsCount = self->rooms.count;
|
||||
self->_moreThanRoomsCount = publicRoomsResponse.nextBatch ? YES : NO;
|
||||
self->_searchResultsCount = self->rooms.count;
|
||||
self->_searchResultsCountIsLimited = publicRoomsResponse.nextBatch ? YES : NO;
|
||||
}
|
||||
|
||||
// Detect pagination end
|
||||
|
|
|
@ -887,8 +887,10 @@ const CGFloat kTypingCellHeight = 24;
|
|||
success:(void (^)(NSString *eventId))success
|
||||
failure:(void (^)(NSError *error))failure
|
||||
{
|
||||
AVURLAsset *videoAsset = [AVURLAsset assetWithURL:videoLocalURL];
|
||||
UIImage *videoThumbnail = [MXKVideoThumbnailGenerator.shared generateThumbnailFrom:videoLocalURL];
|
||||
[self sendVideo:videoLocalURL withThumbnail:videoThumbnail success:success failure:failure];
|
||||
|
||||
[self sendVideoAsset:videoAsset withThumbnail:videoThumbnail success:success failure:failure];
|
||||
}
|
||||
|
||||
- (void)acceptVerificationRequestForEventId:(NSString*)eventId success:(void(^)(void))success failure:(void(^)(NSError*))failure
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
//
|
||||
// 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
|
||||
|
||||
class RoomNotificationSettingsAvatarView: UIView {
|
||||
|
||||
@IBOutlet weak var avatarView: MXKImageView!
|
||||
@IBOutlet weak var nameLabel: UILabel!
|
||||
|
||||
func configure(viewData: AvatarViewDataProtocol) {
|
||||
let avatarImage = AvatarGenerator.generateAvatar(forMatrixItem: viewData.matrixItemId, withDisplayName: viewData.displayName)
|
||||
|
||||
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
|
||||
}
|
||||
nameLabel.text = viewData.displayName
|
||||
}
|
||||
}
|
||||
|
||||
extension RoomNotificationSettingsAvatarView: NibLoadable { }
|
||||
extension RoomNotificationSettingsAvatarView: Themable {
|
||||
func update(theme: Theme) {
|
||||
nameLabel?.font = theme.fonts.title3SB
|
||||
nameLabel?.textColor = theme.textPrimaryColor
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view contentMode="scaleToFill" id="iN0-l3-epB" customClass="RoomNotificationSettingsAvatarView" customModule="Riot" customModuleProvider="target">
|
||||
<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">
|
||||
<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>
|
||||
<constraint firstAttribute="height" constant="80" id="tFe-kl-KGy"/>
|
||||
<constraint firstAttribute="width" constant="80" id="tOu-Mt-atQ"/>
|
||||
</constraints>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
|
||||
<integer key="value" value="40"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</view>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pBK-NN-5cI">
|
||||
<rect key="frame" x="182" y="108" width="50" height="24"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="24" id="yzb-2V-G1M"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="20"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="q3Z-S1-Py9" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" constant="20" id="1EV-ZD-9aG"/>
|
||||
<constraint firstItem="pBK-NN-5cI" firstAttribute="top" secondItem="q3Z-S1-Py9" secondAttribute="bottom" constant="8" id="Rcf-3z-sEY"/>
|
||||
<constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="pBK-NN-5cI" secondAttribute="bottom" constant="60" id="V3E-vX-joC"/>
|
||||
<constraint firstItem="pBK-NN-5cI" firstAttribute="centerX" secondItem="q3Z-S1-Py9" secondAttribute="centerX" id="vNG-KB-1Vd"/>
|
||||
<constraint firstItem="q3Z-S1-Py9" firstAttribute="centerX" secondItem="iN0-l3-epB" secondAttribute="centerX" id="wKJ-BP-2RA"/>
|
||||
</constraints>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<connections>
|
||||
<outlet property="avatarView" destination="q3Z-S1-Py9" id="cLO-Y3-6If"/>
|
||||
<outlet property="nameLabel" destination="pBK-NN-5cI" id="Wt8-Oe-9mK"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="86.956521739130437" y="99.776785714285708"/>
|
||||
</view>
|
||||
</objects>
|
||||
</document>
|
|
@ -0,0 +1,100 @@
|
|||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
import Foundation
|
||||
|
||||
@objc protocol RoomNotificationSettingsCoordinatorBridgePresenterDelegate {
|
||||
func roomNotificationSettingsCoordinatorBridgePresenterDelegateDidComplete(_ coordinatorBridgePresenter: RoomNotificationSettingsCoordinatorBridgePresenter)
|
||||
}
|
||||
|
||||
/// RoomNotificationSettingsCoordinatorBridgePresenter enables to start RoomNotificationSettingsCoordinator from a view controller.
|
||||
/// This bridge is used while waiting for global usage of coordinator pattern.
|
||||
/// It breaks the Coordinator abstraction and it has been introduced for Objective-C compatibility (mainly for integration in legacy view controllers).
|
||||
/// Each bridge should be removed once the underlying Coordinator has been integrated by another Coordinator.
|
||||
@objcMembers
|
||||
final class RoomNotificationSettingsCoordinatorBridgePresenter: NSObject {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let room: MXRoom
|
||||
private var coordinator: RoomNotificationSettingsCoordinator?
|
||||
|
||||
// MARK: Public
|
||||
|
||||
weak var delegate: RoomNotificationSettingsCoordinatorBridgePresenterDelegate?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(room: MXRoom) {
|
||||
self.room = room
|
||||
super.init()
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
// NOTE: Default value feature is not compatible with Objective-C.
|
||||
// func present(from viewController: UIViewController, animated: Bool) {
|
||||
// self.present(from: viewController, animated: animated)
|
||||
// }
|
||||
|
||||
func present(from viewController: UIViewController, animated: Bool) {
|
||||
let roomNotificationSettingsCoordinator = RoomNotificationSettingsCoordinator(room: room)
|
||||
roomNotificationSettingsCoordinator.delegate = self
|
||||
let presentable = roomNotificationSettingsCoordinator.toPresentable()
|
||||
let navigationController = RiotNavigationController(rootViewController: presentable)
|
||||
navigationController.modalPresentationStyle = .formSheet
|
||||
presentable.presentationController?.delegate = self
|
||||
viewController.present(navigationController, animated: animated, completion: nil)
|
||||
roomNotificationSettingsCoordinator.start()
|
||||
|
||||
self.coordinator = roomNotificationSettingsCoordinator
|
||||
}
|
||||
|
||||
func dismiss(animated: Bool, completion: (() -> Void)?) {
|
||||
guard let coordinator = self.coordinator else {
|
||||
return
|
||||
}
|
||||
coordinator.toPresentable().dismiss(animated: animated) {
|
||||
self.coordinator = nil
|
||||
|
||||
if let completion = completion {
|
||||
completion()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - RoomNotificationSettingsCoordinatorDelegate
|
||||
extension RoomNotificationSettingsCoordinatorBridgePresenter: RoomNotificationSettingsCoordinatorDelegate {
|
||||
func roomNotificationSettingsCoordinatorDidCancel(_ coordinator: RoomNotificationSettingsCoordinatorType) {
|
||||
self.delegate?.roomNotificationSettingsCoordinatorBridgePresenterDelegateDidComplete(self)
|
||||
}
|
||||
|
||||
func roomNotificationSettingsCoordinatorDidComplete(_ coordinator: RoomNotificationSettingsCoordinatorType) {
|
||||
self.delegate?.roomNotificationSettingsCoordinatorBridgePresenterDelegateDidComplete(self)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UIAdaptivePresentationControllerDelegate
|
||||
|
||||
extension RoomNotificationSettingsCoordinatorBridgePresenter: UIAdaptivePresentationControllerDelegate {
|
||||
|
||||
func roomNotificationSettingsCoordinatorDidComplete(_ presentationController: UIPresentationController) {
|
||||
self.delegate?.roomNotificationSettingsCoordinatorBridgePresenterDelegateDidComplete(self)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
//
|
||||
// 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 RoomNotificationSettingsCellViewData {
|
||||
let notificicationState: RoomNotificationState
|
||||
let selected: Bool
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
// File created from ScreenTemplate
|
||||
// $ createScreen.sh Room/NotificationSettings RoomNotificationSettings
|
||||
/*
|
||||
Copyright 2021 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
final class RoomNotificationSettingsCoordinator: RoomNotificationSettingsCoordinatorType {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
private var roomNotificationSettingsViewModel: RoomNotificationSettingsViewModelType
|
||||
private let roomNotificationSettingsViewController: RoomNotificationSettingsViewController
|
||||
|
||||
// MARK: Public
|
||||
|
||||
// Must be used only internally
|
||||
var childCoordinators: [Coordinator] = []
|
||||
|
||||
weak var delegate: RoomNotificationSettingsCoordinatorDelegate?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(room: MXRoom, showAvatar: Bool = true) {
|
||||
let repository = RoomNotificationSettingsService(room: room)
|
||||
|
||||
let avatarData = showAvatar ? RoomAvatarViewData(
|
||||
roomId: room.roomId,
|
||||
displayName: room.summary.displayname,
|
||||
avatarUrl: room.summary.avatar,
|
||||
mediaManager: room.mxSession.mediaManager
|
||||
) : nil
|
||||
let roomNotificationSettingsViewModel = RoomNotificationSettingsViewModel(roomNotificationService: repository, roomEncrypted: room.summary.isEncrypted, avatarViewData: avatarData)
|
||||
let roomNotificationSettingsViewController = RoomNotificationSettingsViewController.instantiate(with: roomNotificationSettingsViewModel)
|
||||
self.roomNotificationSettingsViewModel = roomNotificationSettingsViewModel
|
||||
self.roomNotificationSettingsViewController = roomNotificationSettingsViewController
|
||||
}
|
||||
|
||||
// MARK: - Public methods
|
||||
|
||||
func start() {
|
||||
self.roomNotificationSettingsViewModel.coordinatorDelegate = self
|
||||
}
|
||||
|
||||
func toPresentable() -> UIViewController {
|
||||
return self.roomNotificationSettingsViewController
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - RoomNotificationSettingsViewModelCoordinatorDelegate
|
||||
extension RoomNotificationSettingsCoordinator: RoomNotificationSettingsViewModelCoordinatorDelegate {
|
||||
|
||||
func roomNotificationSettingsViewModelDidComplete(_ viewModel: RoomNotificationSettingsViewModelType) {
|
||||
self.delegate?.roomNotificationSettingsCoordinatorDidComplete(self)
|
||||
}
|
||||
|
||||
func roomNotificationSettingsViewModelDidCancel(_ viewModel: RoomNotificationSettingsViewModelType) {
|
||||
self.delegate?.roomNotificationSettingsCoordinatorDidCancel(self)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
// File created from ScreenTemplate
|
||||
// $ createScreen.sh Room/NotificationSettings RoomNotificationSettings
|
||||
/*
|
||||
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 RoomNotificationSettingsCoordinatorDelegate: AnyObject {
|
||||
func roomNotificationSettingsCoordinatorDidComplete(_ coordinator: RoomNotificationSettingsCoordinatorType)
|
||||
func roomNotificationSettingsCoordinatorDidCancel(_ coordinator: RoomNotificationSettingsCoordinatorType)
|
||||
}
|
||||
|
||||
/// `RoomNotificationSettingsCoordinatorType` is a protocol describing a Coordinator that handles changes to the room navigation settings navigation flow.
|
||||
protocol RoomNotificationSettingsCoordinatorType: Coordinator, Presentable {
|
||||
var delegate: RoomNotificationSettingsCoordinatorDelegate? { get }
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Reusable
|
||||
|
||||
class RoomNotificationSettingsFooter: UITableViewHeaderFooterView {
|
||||
|
||||
struct State {
|
||||
let showEncryptedNotice: Bool
|
||||
let showAccountLink: Bool
|
||||
}
|
||||
|
||||
@IBOutlet weak var label: UILabel!
|
||||
|
||||
func update(footerState: State) {
|
||||
|
||||
// Don't include link until global settings in place
|
||||
// let paragraphStyle = NSMutableParagraphStyle()
|
||||
// paragraphStyle.lineHeightMultiple = 1.16
|
||||
// let paragraphAttributes: [NSAttributedString.Key: Any] = [
|
||||
// NSAttributedString.Key.kern: -0.08,
|
||||
// NSAttributedString.Key.paragraphStyle: paragraphStyle,
|
||||
// NSAttributedString.Key.font: UIFont.systemFont(ofSize: 13.0)
|
||||
// ]
|
||||
// let linkStr = VectorL10n.roomNotifsSettingsAccountSettings
|
||||
// let formatStr = VectorL10n.roomNotifsSettingsManageNotifications(linkStr)
|
||||
//
|
||||
// let formattedStr = String(format: formatStr, arguments: [linkStr])
|
||||
// let footer0 = NSMutableAttributedString(string: formattedStr, attributes: paragraphAttributes)
|
||||
// let linkRange = (footer0.string as NSString).range(of: linkStr)
|
||||
// footer0.addAttribute(NSAttributedString.Key.link, value: Constants.linkToAccountSettings, range: linkRange)
|
||||
|
||||
label.text = footerState.showEncryptedNotice ? VectorL10n.roomNotifsSettingsEncryptedRoomNotice : nil
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension RoomNotificationSettingsFooter: NibReusable {}
|
||||
extension RoomNotificationSettingsFooter: Themable {
|
||||
|
||||
func update(theme: Theme) {
|
||||
contentView.backgroundColor = theme.headerBackgroundColor
|
||||
label.textColor = theme.headerTextSecondaryColor
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" id="Ecs-Zf-2tR" customClass="RoomNotificationSettingsFooter" customModule="Riot" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="76"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Ecs-Zf-2tR" id="f2j-NO-hdS">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="76"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<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="xlF-dY-Ud8">
|
||||
<rect key="frame" x="20" y="16" width="374" height="44"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="xlF-dY-Ud8" firstAttribute="bottom" secondItem="f2j-NO-hdS" secondAttribute="bottom" constant="-16" id="BWu-8S-Qnj"/>
|
||||
<constraint firstItem="xlF-dY-Ud8" firstAttribute="top" secondItem="f2j-NO-hdS" secondAttribute="top" constant="16" id="Lpw-g0-S41"/>
|
||||
<constraint firstItem="xlF-dY-Ud8" firstAttribute="leading" secondItem="f2j-NO-hdS" secondAttribute="leadingMargin" id="Zf5-c0-agj"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="xlF-dY-Ud8" secondAttribute="trailing" id="wIa-z6-N5E"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<outlet property="label" destination="xlF-dY-Ud8" id="qhR-4x-7Yt"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="37" y="-210"/>
|
||||
</tableViewCell>
|
||||
</objects>
|
||||
</document>
|
|
@ -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
|
||||
|
||||
typealias UpdateRoomNotificationStateCompletion = () -> Void
|
||||
typealias RoomNotificationStateCallback = (RoomNotificationState) -> Void
|
||||
|
||||
protocol RoomNotificationSettingsServiceType {
|
||||
|
||||
func observeNotificationState(listener: @escaping RoomNotificationStateCallback)
|
||||
func update(state: RoomNotificationState, completion: @escaping UpdateRoomNotificationStateCompletion)
|
||||
var notificationState: RoomNotificationState { get }
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
// File created from ScreenTemplate
|
||||
// $ createScreen.sh Room/NotificationSettings RoomNotificationSettings
|
||||
/*
|
||||
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
|
||||
/// RoomNotificationSettingsViewController view actions exposed to view model
|
||||
enum RoomNotificationSettingsViewAction {
|
||||
case load
|
||||
case selectNotificationState(RoomNotificationState)
|
||||
case save
|
||||
case cancel
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
<?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="V8j-Lb-PgC">
|
||||
<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>
|
||||
<!--Room Notification Settings View Controller-->
|
||||
<scene sceneID="mt5-wz-YKA">
|
||||
<objects>
|
||||
<viewController extendedLayoutIncludesOpaqueBars="YES" automaticallyAdjustsScrollViewInsets="NO" id="V8j-Lb-PgC" customClass="RoomNotificationSettingsViewController" customModule="Riot" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="EL9-GA-lwo">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" translatesAutoresizingMaskIntoConstraints="NO" id="1Jo-Pf-c9m">
|
||||
<rect key="frame" x="0.0" y="44" width="414" height="818"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="V8j-Lb-PgC" id="pQ7-Q4-4cn"/>
|
||||
<outlet property="delegate" destination="V8j-Lb-PgC" id="snv-x4-IWg"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
</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 firstItem="bFg-jh-JZB" firstAttribute="bottom" secondItem="1Jo-Pf-c9m" secondAttribute="bottom" id="TYv-T2-NmY"/>
|
||||
<constraint firstItem="1Jo-Pf-c9m" firstAttribute="leading" secondItem="bFg-jh-JZB" secondAttribute="leading" id="f6H-cf-mjJ"/>
|
||||
<constraint firstItem="1Jo-Pf-c9m" firstAttribute="top" secondItem="bFg-jh-JZB" secondAttribute="top" id="gcX-5S-aMb"/>
|
||||
<constraint firstItem="bFg-jh-JZB" firstAttribute="trailing" secondItem="1Jo-Pf-c9m" secondAttribute="trailing" id="hJ7-5d-23W"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="mainTableView" destination="1Jo-Pf-c9m" id="Edg-Ng-fo9"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="zK0-v6-7Wt" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-3198" y="-647"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<systemColor name="systemBackgroundColor">
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
|
@ -0,0 +1,233 @@
|
|||
// File created from ScreenTemplate
|
||||
// $ createScreen.sh Room/NotificationSettings RoomNotificationSettings
|
||||
/*
|
||||
Copyright 2021 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import UIKit
|
||||
|
||||
final class RoomNotificationSettingsViewController: UIViewController {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
private enum Constants {
|
||||
static let linkToAccountSettings = "linkToAccountSettings"
|
||||
}
|
||||
|
||||
// MARK: Outlets
|
||||
|
||||
@IBOutlet private weak var mainTableView: UITableView!
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private var viewModel: RoomNotificationSettingsViewModelType!
|
||||
private var theme: Theme!
|
||||
private var errorPresenter: MXKErrorPresentation!
|
||||
private var activityPresenter: ActivityIndicatorPresenter!
|
||||
private lazy var avatarView: RoomNotificationSettingsAvatarView = {
|
||||
RoomNotificationSettingsAvatarView.loadFromNib()
|
||||
}()
|
||||
|
||||
private struct Row {
|
||||
var cellViewData: RoomNotificationSettingsCellViewData
|
||||
var action: (() -> Void)?
|
||||
}
|
||||
|
||||
private struct Section {
|
||||
var title: String
|
||||
var rows: [Row]
|
||||
var footerState: RoomNotificationSettingsFooter.State
|
||||
}
|
||||
|
||||
private var sections: [Section] = [] {
|
||||
didSet {
|
||||
mainTableView.reloadData()
|
||||
}
|
||||
}
|
||||
|
||||
private var viewState: RoomNotificationSettingsViewStateType!
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
class func instantiate(with viewModel: RoomNotificationSettingsViewModelType) -> RoomNotificationSettingsViewController {
|
||||
let viewController = StoryboardScene.RoomNotificationSettingsViewController.initialScene.instantiate()
|
||||
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.
|
||||
|
||||
setupViews()
|
||||
activityPresenter = ActivityIndicatorPresenter()
|
||||
errorPresenter = MXKErrorAlertPresentation()
|
||||
|
||||
registerThemeServiceDidChangeThemeNotification()
|
||||
update(theme: theme)
|
||||
|
||||
viewModel.viewDelegate = self
|
||||
viewModel.process(viewAction: .load)
|
||||
}
|
||||
|
||||
override var preferredStatusBarStyle: UIStatusBarStyle {
|
||||
return theme.statusBarStyle
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func update(theme: Theme) {
|
||||
self.theme = theme
|
||||
|
||||
view.backgroundColor = theme.headerBackgroundColor
|
||||
mainTableView.backgroundColor = theme.headerBackgroundColor
|
||||
|
||||
if let navigationBar = navigationController?.navigationBar {
|
||||
theme.applyStyle(onNavigationBar: navigationBar)
|
||||
}
|
||||
mainTableView.reloadData()
|
||||
}
|
||||
|
||||
private func registerThemeServiceDidChangeThemeNotification() {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil)
|
||||
}
|
||||
|
||||
@objc private func themeDidChange() {
|
||||
update(theme: ThemeService.shared().theme)
|
||||
}
|
||||
|
||||
private func setupViews() {
|
||||
|
||||
self.title = VectorL10n.roomDetailsNotifs
|
||||
let doneBarButtonItem = MXKBarButtonItem(title: VectorL10n.roomNotifsSettingsDoneAction, style: .plain) { [weak self] in
|
||||
self?.viewModel.process(viewAction: .save)
|
||||
}
|
||||
|
||||
let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.roomNotifsSettingsCancelAction, style: .plain) { [weak self] in
|
||||
self?.viewModel.process(viewAction: .cancel)
|
||||
}
|
||||
|
||||
if navigationController?.navigationBar.backItem == nil {
|
||||
navigationItem.leftBarButtonItem = cancelBarButtonItem
|
||||
}
|
||||
navigationItem.rightBarButtonItem = doneBarButtonItem
|
||||
mainTableView.register(cellType: RoomNotificationSettingsCell.self)
|
||||
mainTableView.register(headerFooterViewType: RoomNotificationSettingsFooter.self)
|
||||
mainTableView.register(headerFooterViewType: TitleHeaderView.self)
|
||||
mainTableView.sectionFooterHeight = UITableView.automaticDimension
|
||||
mainTableView.sectionHeaderHeight = UITableView.automaticDimension
|
||||
mainTableView.estimatedSectionFooterHeight = 50
|
||||
mainTableView.estimatedSectionHeaderHeight = 30
|
||||
}
|
||||
|
||||
private func render(viewState: RoomNotificationSettingsViewStateType) {
|
||||
|
||||
if viewState.saving {
|
||||
activityPresenter.presentActivityIndicator(on: view, animated: true)
|
||||
} else {
|
||||
activityPresenter.removeCurrentActivityIndicator(animated: true)
|
||||
}
|
||||
self.viewState = viewState
|
||||
if let avatarData = viewState.avatarData {
|
||||
mainTableView.tableHeaderView = avatarView
|
||||
avatarView.configure(viewData: avatarData)
|
||||
avatarView.update(theme: theme)
|
||||
}
|
||||
updateSections()
|
||||
}
|
||||
|
||||
private func updateSections() {
|
||||
let rows = viewState.notificationOptions.map({ (setting) -> Row in
|
||||
let cellViewData = RoomNotificationSettingsCellViewData(notificicationState: setting, selected: viewState.notificationState == setting)
|
||||
return Row(cellViewData: cellViewData,
|
||||
action: {
|
||||
self.viewModel.process(viewAction: .selectNotificationState(setting))
|
||||
})
|
||||
})
|
||||
let footerState = RoomNotificationSettingsFooter.State(showEncryptedNotice: viewState.roomEncrypted, showAccountLink: false)
|
||||
let section0 = Section(title: VectorL10n.roomNotifsSettingsNotifyMeFor, rows: rows, footerState: footerState)
|
||||
sections = [
|
||||
section0
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UITableViewDataSource
|
||||
extension RoomNotificationSettingsViewController: UITableViewDataSource {
|
||||
|
||||
func numberOfSections(in tableView: UITableView) -> Int {
|
||||
return sections.count
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return sections[section].rows.count
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let row = sections[indexPath.section].rows[indexPath.row]
|
||||
let cell: RoomNotificationSettingsCell = tableView.dequeueReusableCell(for: indexPath)
|
||||
cell.update(state: row.cellViewData)
|
||||
cell.update(theme: theme)
|
||||
return cell
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
||||
return UITableView.automaticDimension
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
|
||||
return UITableView.automaticDimension
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - UITableViewDelegate
|
||||
extension RoomNotificationSettingsViewController: UITableViewDelegate {
|
||||
|
||||
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
||||
guard let headerView: TitleHeaderView = tableView.dequeueReusableHeaderFooterView() else { return nil }
|
||||
headerView.update(title: sections[section].title)
|
||||
headerView.update(theme: theme)
|
||||
return headerView
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
|
||||
guard let footerView: RoomNotificationSettingsFooter = tableView.dequeueReusableHeaderFooterView() else { return nil }
|
||||
let footerState = sections[section].footerState
|
||||
footerView.update(footerState: footerState)
|
||||
footerView.update(theme: theme)
|
||||
return footerView
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
|
||||
let row = sections[indexPath.section].rows[indexPath.row]
|
||||
row.action?()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - RoomNotificationSettingsViewModelViewDelegate
|
||||
extension RoomNotificationSettingsViewController: RoomNotificationSettingsViewModelViewDelegate {
|
||||
|
||||
func roomNotificationSettingsViewModel(_ viewModel: RoomNotificationSettingsViewModelType, didUpdateViewState viewSate: RoomNotificationSettingsViewStateType) {
|
||||
render(viewState: viewSate)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
// File created from ScreenTemplate
|
||||
// $ createScreen.sh Room/NotificationSettings RoomNotificationSettings
|
||||
/*
|
||||
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
|
||||
|
||||
final class RoomNotificationSettingsViewModel: RoomNotificationSettingsViewModelType {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let roomNotificationService: RoomNotificationSettingsServiceType
|
||||
private var state: RoomNotificationSettingsViewState {
|
||||
willSet {
|
||||
update(viewState: newValue)
|
||||
}
|
||||
}
|
||||
// MARK: Public
|
||||
|
||||
weak var viewDelegate: RoomNotificationSettingsViewModelViewDelegate?
|
||||
|
||||
weak var coordinatorDelegate: RoomNotificationSettingsViewModelCoordinatorDelegate?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(roomNotificationService: RoomNotificationSettingsServiceType, roomEncrypted: Bool, avatarViewData: AvatarViewDataProtocol?) {
|
||||
self.roomNotificationService = roomNotificationService
|
||||
|
||||
let notificationState = Self.mapNotificationStateOnRead(encrypted: roomEncrypted, state: roomNotificationService.notificationState)
|
||||
self.state = RoomNotificationSettingsViewState(roomEncrypted: roomEncrypted, saving: false, notificationState: notificationState, avatarData: avatarViewData)
|
||||
self.roomNotificationService.observeNotificationState { [weak self] state in
|
||||
guard let self = self else { return }
|
||||
|
||||
self.state.notificationState = Self.mapNotificationStateOnRead(encrypted: roomEncrypted, state: state)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func process(viewAction: RoomNotificationSettingsViewAction) {
|
||||
switch viewAction {
|
||||
case .load:
|
||||
update(viewState: self.state)
|
||||
case .selectNotificationState(let state):
|
||||
self.state.notificationState = state
|
||||
case .save:
|
||||
self.state.saving = true
|
||||
roomNotificationService.update(state: state.notificationState) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.state.saving = false
|
||||
self.coordinatorDelegate?.roomNotificationSettingsViewModelDidComplete(self)
|
||||
}
|
||||
case .cancel:
|
||||
coordinatorDelegate?.roomNotificationSettingsViewModelDidCancel(self)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private static func mapNotificationStateOnRead(encrypted: Bool, state: RoomNotificationState) -> RoomNotificationState {
|
||||
if encrypted, case .mentionsAndKeywordsOnly = state {
|
||||
// Notifications not supported on encrypted rooms, map mentionsOnly to mute on read
|
||||
return .mute
|
||||
} else {
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
private func update(viewState: RoomNotificationSettingsViewStateType) {
|
||||
self.viewDelegate?.roomNotificationSettingsViewModel(self, didUpdateViewState: viewState)
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
// File created from ScreenTemplate
|
||||
// $ createScreen.sh Room/NotificationSettings RoomNotificationSettings
|
||||
/*
|
||||
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 RoomNotificationSettingsViewModelViewDelegate: AnyObject {
|
||||
func roomNotificationSettingsViewModel(_ viewModel: RoomNotificationSettingsViewModelType, didUpdateViewState viewState: RoomNotificationSettingsViewStateType)
|
||||
}
|
||||
|
||||
protocol RoomNotificationSettingsViewModelCoordinatorDelegate: AnyObject {
|
||||
func roomNotificationSettingsViewModelDidComplete(_ viewModel: RoomNotificationSettingsViewModelType)
|
||||
func roomNotificationSettingsViewModelDidCancel(_ viewModel: RoomNotificationSettingsViewModelType)
|
||||
}
|
||||
|
||||
/// Protocol describing the view model used by `RoomNotificationSettingsViewController`
|
||||
protocol RoomNotificationSettingsViewModelType {
|
||||
|
||||
var viewDelegate: RoomNotificationSettingsViewModelViewDelegate? { get set }
|
||||
var coordinatorDelegate: RoomNotificationSettingsViewModelCoordinatorDelegate? { get set }
|
||||
|
||||
func process(viewAction: RoomNotificationSettingsViewAction)
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
// File created from ScreenTemplate
|
||||
// $ createScreen.sh Room/NotificationSettings RoomNotificationSettings
|
||||
/*
|
||||
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
|
||||
|
||||
/// RoomNotificationSettingsViewController view state
|
||||
struct RoomNotificationSettingsViewState: RoomNotificationSettingsViewStateType {
|
||||
let roomEncrypted: Bool
|
||||
var saving: Bool
|
||||
var notificationState: RoomNotificationState
|
||||
var notificationOptions: [RoomNotificationState] {
|
||||
if roomEncrypted {
|
||||
return [.all, .mute]
|
||||
} else {
|
||||
return RoomNotificationState.allCases
|
||||
}
|
||||
}
|
||||
let avatarData: AvatarViewDataProtocol?
|
||||
}
|
||||
|
||||
protocol RoomNotificationSettingsViewStateType {
|
||||
var saving: Bool { get }
|
||||
var roomEncrypted: Bool { get }
|
||||
var notificationOptions: [RoomNotificationState] { get }
|
||||
var notificationState: RoomNotificationState { get }
|
||||
var avatarData: AvatarViewDataProtocol? { get }
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum RoomNotificationState: CaseIterable {
|
||||
case all
|
||||
case mentionsAndKeywordsOnly
|
||||
case mute
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
import UIKit
|
||||
import Reusable
|
||||
|
||||
class RoomNotificationSettingsCell: UITableViewCell {
|
||||
|
||||
func update(state: RoomNotificationSettingsCellViewData) {
|
||||
textLabel?.text = state.notificicationState.title
|
||||
if state.selected {
|
||||
accessoryView = UIImageView(image: Asset.Images.checkmark.image)
|
||||
} else {
|
||||
accessoryView = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension RoomNotificationSettingsCell: Reusable {}
|
||||
|
||||
extension RoomNotificationSettingsCell: Themable {
|
||||
func update(theme: Theme) {
|
||||
textLabel?.font = theme.fonts.body
|
||||
textLabel?.textColor = theme.textPrimaryColor
|
||||
backgroundColor = theme.backgroundColor
|
||||
contentView.backgroundColor = .clear
|
||||
tintColor = theme.tintColor
|
||||
selectedBackgroundView = UIView()
|
||||
selectedBackgroundView?.backgroundColor = theme.selectedBackgroundColor
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate extension RoomNotificationState {
|
||||
var title: String {
|
||||
switch self {
|
||||
case .all:
|
||||
return VectorL10n.roomNotifsSettingsAllMessages
|
||||
case .mentionsAndKeywordsOnly:
|
||||
return VectorL10n.roomNotifsSettingsMentionsAndKeywords
|
||||
case .mute:
|
||||
return VectorL10n.roomNotifsSettingsNone
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,325 @@
|
|||
//
|
||||
// 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
|
||||
|
||||
final class RoomNotificationSettingsService: RoomNotificationSettingsServiceType {
|
||||
|
||||
typealias Completion = () -> Void
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let room: MXRoom
|
||||
|
||||
private var notificationCenterDidUpdateObserver: NSObjectProtocol?
|
||||
private var notificationCenterDidFailObserver: NSObjectProtocol?
|
||||
|
||||
private var observers: [ObjectIdentifier] = []
|
||||
|
||||
// MARK: Public
|
||||
|
||||
var notificationState: RoomNotificationState {
|
||||
room.notificationState
|
||||
}
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(room: MXRoom) {
|
||||
self.room = room
|
||||
}
|
||||
|
||||
deinit {
|
||||
observers.forEach(NotificationCenter.default.removeObserver)
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func observeNotificationState(listener: @escaping RoomNotificationStateCallback) {
|
||||
|
||||
let observer = NotificationCenter.default.addObserver(
|
||||
forName: NSNotification.Name(rawValue: kMXNotificationCenterDidUpdateRules),
|
||||
object: nil,
|
||||
queue: OperationQueue.main) { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
listener(self.room.notificationState)
|
||||
}
|
||||
observers += [ObjectIdentifier(observer)]
|
||||
}
|
||||
|
||||
func update(state: RoomNotificationState, completion: @escaping Completion) {
|
||||
switch state {
|
||||
case .all:
|
||||
allMessages(completion: completion)
|
||||
case .mentionsAndKeywordsOnly:
|
||||
mentionsOnly(completion: completion)
|
||||
case .mute:
|
||||
mute(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func mute(completion: @escaping Completion) {
|
||||
guard !room.isMuted else {
|
||||
completion()
|
||||
return
|
||||
}
|
||||
|
||||
if let rule = room.roomPushRule {
|
||||
removePushRule(rule: rule) {
|
||||
self.mute(completion: completion)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
guard let rule = room.overridePushRule else {
|
||||
self.addPushRuleToMute(completion: completion)
|
||||
return
|
||||
}
|
||||
|
||||
guard notificationCenterDidUpdateObserver == nil else {
|
||||
MXLog.debug("[RoomNotificationSettingsService] Request in progress: ignore push rule update")
|
||||
completion()
|
||||
return
|
||||
}
|
||||
|
||||
// if the user defined one, use it
|
||||
if rule.actionsContains(actionType: MXPushRuleActionTypeDontNotify) {
|
||||
enablePushRule(rule: rule, completion: completion)
|
||||
} else {
|
||||
removePushRule(rule: rule) {
|
||||
self.addPushRuleToMute(completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func mentionsOnly(completion: @escaping Completion) {
|
||||
guard !room.isMentionsOnly else {
|
||||
completion()
|
||||
return
|
||||
}
|
||||
|
||||
if let rule = room.overridePushRule, room.isMuted {
|
||||
removePushRule(rule: rule) {
|
||||
self.mentionsOnly(completion: completion)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
guard let rule = room.roomPushRule else {
|
||||
addPushRuleToMentionOnly(completion: completion)
|
||||
return
|
||||
}
|
||||
|
||||
guard notificationCenterDidUpdateObserver == nil else {
|
||||
MXLog.debug("[MXRoom+Riot] Request in progress: ignore push rule update")
|
||||
completion()
|
||||
return
|
||||
}
|
||||
|
||||
// if the user defined one, use it
|
||||
if rule.actionsContains(actionType: MXPushRuleActionTypeDontNotify) {
|
||||
enablePushRule(rule: rule, completion: completion)
|
||||
} else {
|
||||
removePushRule(rule: rule) {
|
||||
self.addPushRuleToMentionOnly(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private func allMessages(completion: @escaping Completion) {
|
||||
if !room.isMentionsOnly && !room.isMuted {
|
||||
completion()
|
||||
return
|
||||
}
|
||||
|
||||
if let rule = room.overridePushRule, room.isMuted {
|
||||
removePushRule(rule: rule) {
|
||||
self.allMessages(completion: completion)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if let rule = room.roomPushRule, room.isMentionsOnly {
|
||||
removePushRule(rule: rule, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
private func addPushRuleToMentionOnly(completion: @escaping Completion) {
|
||||
handleUpdateCallback(completion) { [weak self] in
|
||||
guard let self = self else { return true }
|
||||
return self.room.roomPushRule != nil
|
||||
}
|
||||
handleFailureCallback(completion)
|
||||
|
||||
room.mxSession.notificationCenter.addRoomRule(
|
||||
room.roomId,
|
||||
notify: false,
|
||||
sound: false,
|
||||
highlight: false)
|
||||
}
|
||||
|
||||
private func addPushRuleToMute(completion: @escaping Completion) {
|
||||
guard let roomId = room.roomId else {
|
||||
return
|
||||
}
|
||||
handleUpdateCallback(completion) { [weak self] in
|
||||
guard let self = self else { return true }
|
||||
return self.room.overridePushRule != nil
|
||||
}
|
||||
handleFailureCallback(completion)
|
||||
|
||||
room.mxSession.notificationCenter.addOverrideRule(
|
||||
withId: roomId,
|
||||
conditions: [["kind": "event_match", "key": "room_id", "pattern": roomId]],
|
||||
notify: false,
|
||||
sound: false,
|
||||
highlight: false
|
||||
)
|
||||
}
|
||||
|
||||
private func removePushRule(rule: MXPushRule, completion: @escaping Completion) {
|
||||
handleUpdateCallback(completion) { [weak self] in
|
||||
guard let self = self else { return true }
|
||||
return self.room.mxSession.notificationCenter.rule(byId: rule.ruleId) == nil
|
||||
}
|
||||
handleFailureCallback(completion)
|
||||
|
||||
room.mxSession.notificationCenter.removeRule(rule)
|
||||
}
|
||||
|
||||
private func enablePushRule(rule: MXPushRule, completion: @escaping Completion) {
|
||||
handleUpdateCallback(completion) {
|
||||
// No way to check whether this notification concerns the push rule. Consider the change is applied.
|
||||
return true
|
||||
}
|
||||
handleFailureCallback(completion)
|
||||
|
||||
room.mxSession.notificationCenter.enableRule(rule, isEnabled: true)
|
||||
}
|
||||
|
||||
private func handleUpdateCallback(_ completion: @escaping Completion, releaseCheck: @escaping () -> Bool) {
|
||||
notificationCenterDidUpdateObserver = NotificationCenter.default.addObserver(
|
||||
forName: NSNotification.Name(rawValue: kMXNotificationCenterDidUpdateRules),
|
||||
object: nil,
|
||||
queue: OperationQueue.main) { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
if releaseCheck() {
|
||||
self.removeObservers()
|
||||
completion()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func handleFailureCallback(_ completion: @escaping Completion) {
|
||||
notificationCenterDidFailObserver = NotificationCenter.default.addObserver(
|
||||
forName: NSNotification.Name(rawValue: kMXNotificationCenterDidFailRulesUpdate),
|
||||
object: nil,
|
||||
queue: OperationQueue.main) { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
self.removeObservers()
|
||||
completion()
|
||||
}
|
||||
}
|
||||
|
||||
func removeObservers() {
|
||||
if let observer = self.notificationCenterDidUpdateObserver {
|
||||
NotificationCenter.default.removeObserver(observer)
|
||||
self.notificationCenterDidUpdateObserver = nil
|
||||
}
|
||||
|
||||
if let observer = self.notificationCenterDidFailObserver {
|
||||
NotificationCenter.default.removeObserver(observer)
|
||||
self.notificationCenterDidFailObserver = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We could move these to their own file and make available in global namespace or move to sdk but they are only used here at the moment
|
||||
fileprivate extension MXRoom {
|
||||
|
||||
typealias Completion = () -> Void
|
||||
func getRoomRule(from rules: [Any]) -> MXPushRule? {
|
||||
guard let pushRules = rules as? [MXPushRule] else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return pushRules.first(where: { self.roomId == $0.ruleId })
|
||||
}
|
||||
|
||||
var overridePushRule: MXPushRule? {
|
||||
getRoomRule(from: mxSession.notificationCenter.rules.global.override)
|
||||
}
|
||||
|
||||
var roomPushRule: MXPushRule? {
|
||||
getRoomRule(from: mxSession.notificationCenter.rules.global.room)
|
||||
}
|
||||
|
||||
var notificationState: RoomNotificationState {
|
||||
|
||||
if isMuted {
|
||||
return .mute
|
||||
}
|
||||
if isMentionsOnly {
|
||||
return .mentionsAndKeywordsOnly
|
||||
}
|
||||
return .all
|
||||
}
|
||||
|
||||
var isMuted: Bool {
|
||||
// Check whether an override rule has been defined with the roomm id as rule id.
|
||||
// This kind of rule is created to mute the room
|
||||
guard let rule = self.overridePushRule,
|
||||
rule.actionsContains(actionType: MXPushRuleActionTypeDontNotify),
|
||||
rule.conditionIsEnabled(kind: .eventMatch, for: roomId) else {
|
||||
return false
|
||||
}
|
||||
return rule.enabled
|
||||
}
|
||||
|
||||
var isMentionsOnly: Bool {
|
||||
// Check push rules at room level
|
||||
guard let rule = roomPushRule else { return false }
|
||||
return rule.enabled && rule.actionsContains(actionType: MXPushRuleActionTypeDontNotify)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fileprivate extension MXPushRule {
|
||||
func actionsContains(actionType: MXPushRuleActionType) -> Bool {
|
||||
guard let actions = actions as? [MXPushRuleAction] else {
|
||||
return false
|
||||
}
|
||||
return actions.contains(where: { $0.actionType == actionType })
|
||||
}
|
||||
|
||||
func conditionIsEnabled(kind: MXPushRuleConditionType, for roomId: String) -> Bool {
|
||||
guard let conditions = conditions as? [MXPushRuleCondition] else {
|
||||
return false
|
||||
}
|
||||
let ruleContainsCondition = conditions.contains { condition in
|
||||
guard case kind = MXPushRuleConditionType(identifier: condition.kind),
|
||||
let key = condition.parameters["key"] as? String,
|
||||
let pattern = condition.parameters["pattern"] as? String
|
||||
else { return false }
|
||||
return key == "room_id" && pattern == roomId
|
||||
}
|
||||
return ruleContainsCondition && enabled
|
||||
}
|
||||
}
|
|
@ -137,6 +137,12 @@ final class RoomInfoCoordinator: NSObject, RoomInfoCoordinatorType {
|
|||
return coordinator
|
||||
}
|
||||
|
||||
private func createRoomNotificationSettingsCoordinator() -> RoomNotificationSettingsCoordinator {
|
||||
let coordinator = RoomNotificationSettingsCoordinator(room: room, showAvatar: false)
|
||||
coordinator.delegate = self
|
||||
return coordinator
|
||||
}
|
||||
|
||||
private func showRoomDetails(with target: RoomInfoListTarget, animated: Bool) {
|
||||
switch target {
|
||||
case .integrations:
|
||||
|
@ -152,8 +158,16 @@ final class RoomInfoCoordinator: NSObject, RoomInfoCoordinatorType {
|
|||
self.navigationRouter.push(search, animated: animated, popCompletion: nil)
|
||||
}
|
||||
})
|
||||
case .notifications:
|
||||
let coordinator = createRoomNotificationSettingsCoordinator()
|
||||
coordinator.start()
|
||||
self.add(childCoordinator: coordinator)
|
||||
self.navigationRouter.push(coordinator, animated: true, popCompletion: nil)
|
||||
default:
|
||||
segmentedViewController.selectedIndex = target.tabIndex
|
||||
guard let tabIndex = target.tabIndex else {
|
||||
fatalError("No settings tab index for this target.")
|
||||
}
|
||||
segmentedViewController.selectedIndex = tabIndex
|
||||
|
||||
if case .settings(let roomSettingsField) = target {
|
||||
roomSettingsViewController?.selectedRoomSettingsField = roomSettingsField
|
||||
|
@ -184,3 +198,14 @@ extension RoomInfoCoordinator: RoomParticipantsViewControllerDelegate {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
extension RoomInfoCoordinator: RoomNotificationSettingsCoordinatorDelegate {
|
||||
func roomNotificationSettingsCoordinatorDidComplete(_ coordinator: RoomNotificationSettingsCoordinatorType) {
|
||||
self.navigationRouter.popModule(animated: true)
|
||||
}
|
||||
|
||||
func roomNotificationSettingsCoordinatorDidCancel(_ coordinator: RoomNotificationSettingsCoordinatorType) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,25 +24,21 @@ enum RoomInfoListTarget: Equatable {
|
|||
case uploads
|
||||
case integrations
|
||||
case search
|
||||
|
||||
var tabIndex: UInt {
|
||||
let tabIndex: UInt
|
||||
|
||||
case notifications
|
||||
|
||||
var tabIndex: UInt? {
|
||||
switch self {
|
||||
case .members:
|
||||
tabIndex = 0
|
||||
return 0
|
||||
case .uploads:
|
||||
tabIndex = 1
|
||||
return 1
|
||||
case .settings:
|
||||
tabIndex = 2
|
||||
case .integrations:
|
||||
tabIndex = 3
|
||||
case .search:
|
||||
tabIndex = 4
|
||||
return 2
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
return tabIndex
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// RoomInfoListViewController view actions exposed to view model
|
||||
|
|
|
@ -150,6 +150,9 @@ final class RoomInfoListViewController: UIViewController {
|
|||
let rowSettings = Row(type: .default, icon: Asset.Images.settingsIcon.image, text: VectorL10n.roomDetailsSettings, accessoryType: .disclosureIndicator) {
|
||||
self.viewModel.process(viewAction: .navigate(target: .settings()))
|
||||
}
|
||||
let roomNotifications = Row(type: .default, icon: Asset.Images.notifications.image, text: VectorL10n.roomDetailsNotifs, accessoryType: .disclosureIndicator) {
|
||||
self.viewModel.process(viewAction: .navigate(target: .notifications))
|
||||
}
|
||||
let text = viewData.numberOfMembers == 1 ? VectorL10n.roomInfoListOneMember : VectorL10n.roomInfoListSeveralMembers(String(viewData.numberOfMembers))
|
||||
let rowMembers = Row(type: .default, icon: Asset.Images.userIcon.image, text: text, accessoryType: .disclosureIndicator) {
|
||||
self.viewModel.process(viewAction: .navigate(target: .members))
|
||||
|
@ -165,6 +168,10 @@ final class RoomInfoListViewController: UIViewController {
|
|||
}
|
||||
|
||||
var rows = [rowSettings]
|
||||
|
||||
if BuildSettings.roomSettingsScreenShowNotificationsV2 {
|
||||
rows.append(roomNotifications)
|
||||
}
|
||||
if RiotSettings.shared.roomInfoScreenShowIntegrations {
|
||||
rows.append(rowIntegrations)
|
||||
}
|
||||
|
|
|
@ -6056,7 +6056,8 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
|
|||
RoomInputToolbarView *roomInputToolbarView = [self inputToolbarViewAsRoomInputToolbarView];
|
||||
if (roomInputToolbarView)
|
||||
{
|
||||
[roomInputToolbarView sendSelectedVideo:url isPhotoLibraryAsset:NO];
|
||||
AVURLAsset *selectedVideo = [AVURLAsset assetWithURL:url];
|
||||
[roomInputToolbarView sendSelectedVideoAsset:selectedVideo isPhotoLibraryAsset:NO];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6080,7 +6081,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
|
|||
}
|
||||
}
|
||||
|
||||
- (void)mediaPickerCoordinatorBridgePresenter:(MediaPickerCoordinatorBridgePresenter *)coordinatorBridgePresenter didSelectVideoAt:(NSURL *)url
|
||||
- (void)mediaPickerCoordinatorBridgePresenter:(MediaPickerCoordinatorBridgePresenter *)coordinatorBridgePresenter didSelectVideo:(AVAsset *)videoAsset
|
||||
{
|
||||
[coordinatorBridgePresenter dismissWithAnimated:YES completion:nil];
|
||||
self.mediaPickerPresenter = nil;
|
||||
|
@ -6088,7 +6089,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
|
|||
RoomInputToolbarView *roomInputToolbarView = [self inputToolbarViewAsRoomInputToolbarView];
|
||||
if (roomInputToolbarView)
|
||||
{
|
||||
[roomInputToolbarView sendSelectedVideo:url isPhotoLibraryAsset:YES];
|
||||
[roomInputToolbarView sendSelectedVideoAsset:videoAsset isPhotoLibraryAsset:YES];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -528,7 +528,10 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti
|
|||
{
|
||||
[sectionMain addRowWithTag:ROOM_SETTINGS_MAIN_SECTION_ROW_DIRECT_CHAT];
|
||||
}
|
||||
[sectionMain addRowWithTag:ROOM_SETTINGS_MAIN_SECTION_ROW_MUTE_NOTIFICATIONS];
|
||||
if (!BuildSettings.roomSettingsScreenShowNotificationsV2)
|
||||
{
|
||||
[sectionMain addRowWithTag:ROOM_SETTINGS_MAIN_SECTION_ROW_MUTE_NOTIFICATIONS];
|
||||
}
|
||||
[sectionMain addRowWithTag:ROOM_SETTINGS_MAIN_SECTION_ROW_LEAVE];
|
||||
[tmpSections addObject:sectionMain];
|
||||
|
||||
|
|
|
@ -245,9 +245,9 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType {
|
|||
}
|
||||
|
||||
private func setupSideMenuGestures() {
|
||||
self.parameters.appNavigator.sideMenu.addScreenEdgePanGesturesToPresent(to: self.masterNavigationController.view)
|
||||
|
||||
self.parameters.appNavigator.sideMenu.addPanGestureToPresent(to: self.masterNavigationController.navigationBar)
|
||||
if let rootViewController = self.masterNavigationController.viewControllers.first {
|
||||
self.parameters.appNavigator.sideMenu.addScreenEdgePanGesturesToPresent(to: rootViewController.view)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Navigation
|
||||
|
|
|
@ -1165,8 +1165,8 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode)
|
|||
}
|
||||
|
||||
// Retrieve the video frame at 1 sec to define the video thumbnail
|
||||
AVURLAsset *urlAsset = [[AVURLAsset alloc] initWithURL:videoLocalUrl options:nil];
|
||||
AVAssetImageGenerator *assetImageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:urlAsset];
|
||||
AVURLAsset *videoAsset = [[AVURLAsset alloc] initWithURL:videoLocalUrl options:nil];
|
||||
AVAssetImageGenerator *assetImageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:videoAsset];
|
||||
assetImageGenerator.appliesPreferredTrackTransform = YES;
|
||||
CMTime time = CMTimeMake(1, 1);
|
||||
CGImageRef imageRef = [assetImageGenerator copyCGImageAtTime:time actualTime:NULL error:nil];
|
||||
|
@ -1174,7 +1174,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode)
|
|||
UIImage *videoThumbnail = [[UIImage alloc] initWithCGImage:imageRef];
|
||||
CFRelease(imageRef);
|
||||
|
||||
[room sendVideo:videoLocalUrl withThumbnail:videoThumbnail localEcho:nil success:^(NSString *eventId) {
|
||||
[room sendVideoAsset:videoAsset withThumbnail:videoThumbnail localEcho:nil success:^(NSString *eventId) {
|
||||
if (successBlock)
|
||||
{
|
||||
successBlock();
|
||||
|
|
146
RiotTests/RoomNotificationSettingsViewModelTests.swift
Normal file
146
RiotTests/RoomNotificationSettingsViewModelTests.swift
Normal file
|
@ -0,0 +1,146 @@
|
|||
//
|
||||
// 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 XCTest
|
||||
@testable import Riot
|
||||
|
||||
|
||||
class MockRoomNotificationSettingsService: RoomNotificationSettingsServiceType {
|
||||
|
||||
var listener: RoomNotificationStateCallback?
|
||||
var notificationState: RoomNotificationState
|
||||
|
||||
init(initialState: RoomNotificationState) {
|
||||
notificationState = initialState
|
||||
}
|
||||
|
||||
func observeNotificationState(listener: @escaping RoomNotificationStateCallback) {
|
||||
self.listener = listener
|
||||
}
|
||||
|
||||
func update(state: RoomNotificationState, completion: @escaping UpdateRoomNotificationStateCompletion) {
|
||||
self.notificationState = state
|
||||
completion()
|
||||
listener?(state)
|
||||
}
|
||||
}
|
||||
|
||||
class MockRoomNotificationSettingsView: RoomNotificationSettingsViewModelViewDelegate {
|
||||
|
||||
var viewState: RoomNotificationSettingsViewStateType?
|
||||
|
||||
func roomNotificationSettingsViewModel(_ viewModel: RoomNotificationSettingsViewModelType, didUpdateViewState viewState: RoomNotificationSettingsViewStateType) {
|
||||
self.viewState = viewState
|
||||
}
|
||||
}
|
||||
|
||||
class MockRoomNotificationSettingsCoordinator: RoomNotificationSettingsViewModelCoordinatorDelegate {
|
||||
|
||||
var didComplete = false
|
||||
var didCancel = false
|
||||
func roomNotificationSettingsViewModelDidComplete(_ viewModel: RoomNotificationSettingsViewModelType) {
|
||||
didComplete = true
|
||||
}
|
||||
|
||||
func roomNotificationSettingsViewModelDidCancel(_ viewModel: RoomNotificationSettingsViewModelType) {
|
||||
didCancel = true
|
||||
}
|
||||
}
|
||||
|
||||
class RoomNotificationSettingsViewModelTests: XCTestCase {
|
||||
|
||||
enum Constants{
|
||||
static let roomDisplayName: String = "Test Room Name"
|
||||
static let roomId: String = "1"
|
||||
static let avatarUrl: String = "http://test.url.com"
|
||||
static let avatarData = RoomAvatarViewData(roomId: "1", displayName: roomDisplayName, avatarUrl: avatarUrl, mediaManager: MXMediaManager())
|
||||
}
|
||||
|
||||
var coordinator: MockRoomNotificationSettingsCoordinator!
|
||||
var service: MockRoomNotificationSettingsService!
|
||||
var view: MockRoomNotificationSettingsView!
|
||||
var viewModel: RoomNotificationSettingsViewModel!
|
||||
|
||||
override func setUpWithError() throws {
|
||||
service = MockRoomNotificationSettingsService(initialState: .all)
|
||||
view = MockRoomNotificationSettingsView()
|
||||
coordinator = MockRoomNotificationSettingsCoordinator()
|
||||
}
|
||||
|
||||
func setupViewModel(roomEncrypted: Bool, showAvatar: Bool) {
|
||||
let avatarData = showAvatar ? Constants.avatarData : nil
|
||||
let viewModel = RoomNotificationSettingsViewModel(roomNotificationService: service, roomEncrypted: roomEncrypted, avatarViewData: avatarData)
|
||||
viewModel.viewDelegate = view
|
||||
viewModel.coordinatorDelegate = coordinator
|
||||
self.viewModel = viewModel
|
||||
}
|
||||
|
||||
func testUnloaded() throws {
|
||||
setupViewModel(roomEncrypted: true, showAvatar: false)
|
||||
XCTAssertNil(view.viewState)
|
||||
}
|
||||
|
||||
func testUnencryptedOptions() throws {
|
||||
setupViewModel(roomEncrypted: false, showAvatar: false)
|
||||
viewModel.process(viewAction: .load)
|
||||
XCTAssertNotNil(view.viewState)
|
||||
XCTAssertTrue(view.viewState!.notificationOptions.count == 3)
|
||||
}
|
||||
|
||||
func testEncryptedOptions() throws {
|
||||
setupViewModel(roomEncrypted: true, showAvatar: false)
|
||||
viewModel.process(viewAction: .load)
|
||||
XCTAssertNotNil(view.viewState)
|
||||
XCTAssertTrue(view.viewState!.notificationOptions.count == 2)
|
||||
}
|
||||
|
||||
func testAvatar() throws {
|
||||
setupViewModel(roomEncrypted: true, showAvatar: true)
|
||||
viewModel.process(viewAction: .load)
|
||||
XCTAssertNotNil(view.viewState?.avatarData)
|
||||
XCTAssertEqual(view.viewState!.avatarData!.avatarUrl, Constants.avatarUrl)
|
||||
}
|
||||
|
||||
func testSelectionUpdateAndSave() throws {
|
||||
setupViewModel(roomEncrypted: false, showAvatar: false)
|
||||
viewModel.process(viewAction: .load)
|
||||
XCTAssertNotNil(view.viewState)
|
||||
XCTAssertTrue(view.viewState!.notificationState == .all)
|
||||
viewModel.process(viewAction: .selectNotificationState(.mentionsAndKeywordsOnly))
|
||||
XCTAssertTrue(view.viewState!.notificationState == .mentionsAndKeywordsOnly)
|
||||
viewModel.process(viewAction: .save)
|
||||
XCTAssertTrue(service.notificationState == .mentionsAndKeywordsOnly)
|
||||
XCTAssertTrue(coordinator.didComplete)
|
||||
}
|
||||
|
||||
func testCancel() throws {
|
||||
setupViewModel(roomEncrypted: false, showAvatar: false)
|
||||
viewModel.process(viewAction: .load)
|
||||
XCTAssertNotNil(view.viewState)
|
||||
viewModel.process(viewAction: .cancel)
|
||||
XCTAssertTrue(coordinator.didCancel)
|
||||
}
|
||||
|
||||
func testMentionsOnlyNotAvaileOnEncryptedRoom() throws {
|
||||
service = MockRoomNotificationSettingsService(initialState: .mentionsAndKeywordsOnly)
|
||||
setupViewModel(roomEncrypted: true, showAvatar: false)
|
||||
|
||||
viewModel.process(viewAction: .load)
|
||||
XCTAssertNotNil(view.viewState)
|
||||
XCTAssertTrue(view.viewState!.notificationState == .mute)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2020 New Vector Ltd
|
||||
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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2020 New Vector Ltd
|
||||
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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2020 New Vector Ltd
|
||||
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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2020 New Vector Ltd
|
||||
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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2020 New Vector Ltd
|
||||
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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2020 New Vector Ltd
|
||||
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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2020 New Vector Ltd
|
||||
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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2020 New Vector Ltd
|
||||
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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2020 New Vector Ltd
|
||||
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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2020 New Vector Ltd
|
||||
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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2020 New Vector Ltd
|
||||
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.
|
||||
|
|
Loading…
Reference in a new issue