5720: Update from develop

This commit is contained in:
MaximeE
2022-03-22 09:41:25 +01:00
104 changed files with 2599 additions and 272 deletions
+7 -2
View File
@@ -121,8 +121,13 @@ import AnalyticsEvents
MXLog.debug("[Analytics] Started.")
// Catch and log crashes
MXLogger.logCrashes(true)
if Bundle.main.isShareExtension {
// Don't log crashes in the share extension
} else {
// Catch and log crashes
MXLogger.logCrashes(true)
}
MXLogger.setBuildVersion(AppInfo.current.buildInfo.readableBuildVersion)
}
@@ -251,7 +251,8 @@ extension AppCoordinator: LegacyAppDelegateDelegate {
func legacyAppDelegate(_ legacyAppDelegate: LegacyAppDelegate!, didAddMatrixSession session: MXSession!) {
}
func legacyAppDelegate(_ legacyAppDelegate: LegacyAppDelegate!, didRemoveMatrixSession session: MXSession!) {
func legacyAppDelegate(_ legacyAppDelegate: LegacyAppDelegate!, didRemoveMatrixSession session: MXSession?) {
guard let session = session else { return }
// Handle user session removal on clear cache. On clear cache the account has his session closed but the account is not removed.
self.userSessionsService.removeUserSession(relatedToMatrixSession: session)
}
+5 -1
View File
@@ -2048,7 +2048,11 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
[[NSNotificationCenter defaultCenter] addObserverForName:kMXKAccountManagerDidSoftlogoutAccountNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
MXKAccount *account = notif.object;
[self removeMatrixSession:account.mxSession];
if (account.mxSession)
{
[self removeMatrixSession:account.mxSession];
}
// Return to authentication screen
[self.masterTabBarController showSoftLogoutOnboardingFlowWithCredentials:account.mxCredentials];
+3 -9
View File
@@ -19,7 +19,7 @@ import UIKit
import AVFoundation
@objc protocol CameraPresenterDelegate: AnyObject {
func cameraPresenter(_ presenter: CameraPresenter, didSelectImageData imageData: Data, withUTI uti: MXKUTI?)
func cameraPresenter(_ presenter: CameraPresenter, didSelectImage image: UIImage)
func cameraPresenter(_ presenter: CameraPresenter, didSelectVideoAt url: URL)
func cameraPresenterDidCancel(_ cameraPresenter: CameraPresenter)
}
@@ -27,12 +27,6 @@ import AVFoundation
/// CameraPresenter enables to present native camera
@objc final class CameraPresenter: NSObject {
// MARK: - Constants
private enum Constants {
static let jpegCompressionQuality: CGFloat = 1.0
}
// MARK: - Properties
// MARK: Private
@@ -131,8 +125,8 @@ extension CameraPresenter: UIImagePickerControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
if let videoURL = info[.mediaURL] as? URL {
self.delegate?.cameraPresenter(self, didSelectVideoAt: videoURL)
} else if let image = (info[.editedImage] ?? info[.originalImage]) as? UIImage, let imageData = image.jpegData(compressionQuality: Constants.jpegCompressionQuality) {
self.delegate?.cameraPresenter(self, didSelectImageData: imageData, withUTI: MXKUTI.jpeg)
} else if let image = (info[.editedImage] ?? info[.originalImage]) as? UIImage {
self.delegate?.cameraPresenter(self, didSelectImage: image)
}
}
@@ -130,9 +130,13 @@
[self.tableViewPaginationThrottler throttle:^{
NSInteger section = indexPath.section;
if (tableView.numberOfSections <= section)
{
return;
}
NSInteger numberOfRowsInSection = [tableView numberOfRowsInSection:section];
if (tableView.numberOfSections > section
&& indexPath.row == numberOfRowsInSection - 1)
if (indexPath.row == numberOfRowsInSection - 1)
{
[self->recentsDataSource paginateInSection:section];
}
+6 -2
View File
@@ -600,9 +600,13 @@
{
[self.collectionViewPaginationThrottler throttle:^{
NSInteger collectionViewSection = indexPath.section;
if (collectionView.numberOfSections <= collectionViewSection)
{
return;
}
NSInteger numberOfItemsInSection = [collectionView numberOfItemsInSection:collectionViewSection];
if (collectionView.numberOfSections > collectionViewSection
&& indexPath.item == numberOfItemsInSection - 1)
if (indexPath.item == numberOfItemsInSection - 1)
{
NSInteger tableViewSection = collectionView.tag;
[self->recentsDataSource paginateInSection:tableViewSection];
@@ -2073,6 +2073,11 @@ static NSArray<NSNumber*> *initialSyncSilentErrorsHTTPStatusCodes;
mxSession.syncFilterId, syncFilter.JSONDictionary);
completion(NO);
}
else if (!mxSession.store.allFilterIds.count)
{
MXLogDebug(@"[MXKAccount] There are no filters stored in this session, proceed as if no /sync was done before");
completion(YES);
}
else
{
// Check the filter is the one previously set
@@ -27,6 +27,12 @@ import AVFoundation
@objcMembers
final class SingleImagePickerPresenter: NSObject {
// MARK: - Constants
private enum Constants {
static let jpegCompressionQuality: CGFloat = 1.0
}
// MARK: - Properties
// MARK: Private
@@ -117,8 +123,10 @@ final class SingleImagePickerPresenter: NSObject {
// MARK: - CameraPresenterDelegate
extension SingleImagePickerPresenter: CameraPresenterDelegate {
func cameraPresenter(_ cameraPresenter: CameraPresenter, didSelectImageData imageData: Data, withUTI uti: MXKUTI?) {
self.delegate?.singleImagePickerPresenter(self, didSelectImageData: imageData, withUTI: uti)
func cameraPresenter(_ cameraPresenter: CameraPresenter, didSelectImage image: UIImage) {
if let imageData = image.jpegData(compressionQuality: Constants.jpegCompressionQuality) {
self.delegate?.singleImagePickerPresenter(self, didSelectImageData: imageData, withUTI: MXKUTI.jpeg)
}
}
func cameraPresenterDidCancel(_ cameraPresenter: CameraPresenter) {
@@ -0,0 +1,110 @@
//
// Copyright 2022 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 PhotosUI
import CommonKit
@available(iOS 14.0, *)
protocol MediaPickerPresenterDelegate: AnyObject {
func mediaPickerPresenter(_ presenter: MediaPickerPresenter, didPickImage image: UIImage)
func mediaPickerPresenterDidCancel(_ presenter: MediaPickerPresenter)
}
/// A picker for photos and videos from the user's photo library on iOS 14+ using the
/// new `PHPickerViewController` that doesn't require permission to be granted.
///
/// **Note:** If you need to support iOS 12 & 13, then you will need to use the older
/// `MediaPickerCoordinator`/`MediaPickerViewController` instead.
@available(iOS 14.0, *)
final class MediaPickerPresenter: NSObject {
// MARK: - Properties
// MARK: Private
private weak var pickerViewController: UIViewController?
private var indicatorPresenter: UserIndicatorTypePresenterProtocol?
private var loadingIndicator: UserIndicator?
// MARK: Public
weak var delegate: MediaPickerPresenterDelegate?
// MARK: - Public
// TODO: Support videos and multi-selection
func presentPicker(from presentingViewController: UIViewController, with filter: PHPickerFilter?, animated: Bool) {
var configuration = PHPickerConfiguration(photoLibrary: .shared())
configuration.selectionLimit = 1
configuration.filter = filter
let pickerViewController = PHPickerViewController(configuration: configuration)
pickerViewController.delegate = self
self.pickerViewController = pickerViewController
indicatorPresenter = UserIndicatorTypePresenter(presentingViewController: pickerViewController)
presentingViewController.present(pickerViewController, animated: true, completion: nil)
}
func dismiss(animated: Bool, completion: (() -> Void)?) {
guard let pickerViewController = pickerViewController else { return }
pickerViewController.dismiss(animated: animated, completion: completion)
}
// MARK: - Private
func showLoadingIndicator() {
loadingIndicator = indicatorPresenter?.present(.loading(label: VectorL10n.loading, isInteractionBlocking: true))
}
func hideLoadingIndicator() {
loadingIndicator = nil
}
}
// MARK: - PHPickerViewControllerDelegate
@available(iOS 14, *)
extension MediaPickerPresenter: PHPickerViewControllerDelegate {
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
// TODO: Handle videos and multi-selection
guard let provider = results.first?.itemProvider, provider.canLoadObject(ofClass: UIImage.self) else {
self.delegate?.mediaPickerPresenterDidCancel(self)
return
}
showLoadingIndicator()
provider.loadObject(ofClass: UIImage.self) { [weak self] image, error in
guard let self = self else { return }
guard let image = image as? UIImage else {
DispatchQueue.main.async {
self.hideLoadingIndicator()
self.delegate?.mediaPickerPresenterDidCancel(self)
}
return
}
DispatchQueue.main.async {
self.hideLoadingIndicator()
self.delegate?.mediaPickerPresenter(self, didPickImage: image)
}
}
}
}
@@ -65,6 +65,9 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
private var authenticationType: MXKAuthenticationType?
private var session: MXSession?
private var shouldShowDisplayNameScreen = false
private var shouldShowAvatarScreen = false
/// Whether all of the onboarding steps have been completed or not. `false` if there are more screens to be shown.
private var onboardingFinished = false
/// Whether authentication is complete. `true` once authenticated, verified and the app is ready to be shown.
@@ -182,6 +185,7 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
}
/// Displays the next view in the flow after the use case screen.
@available(iOS 14.0, *)
private func useCaseSelectionCoordinator(_ coordinator: OnboardingUseCaseSelectionCoordinator, didCompleteWith result: OnboardingUseCaseViewModelResult) {
useCaseResult = result
showAuthenticationScreen()
@@ -247,12 +251,13 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
self.session = session
self.authenticationType = authenticationType
// May need to move the spinner and key verification up to here in order to coordinate properly.
// Check whether another screen should be shown.
if #available(iOS 14.0, *) {
if authenticationType == .register, let userId = session.credentials.userId, BuildSettings.onboardingShowAccountPersonalisation {
showCongratulationsScreen(userId: userId)
if authenticationType == .register,
let userId = session.credentials.userId,
let userSession = UserSessionsService.shared.userSession(withUserId: userId),
BuildSettings.onboardingShowAccountPersonalization {
checkHomeserverCapabilities(for: userSession)
return
} else if Analytics.shared.shouldShowAnalyticsPrompt {
showAnalyticsPrompt(for: session)
@@ -265,6 +270,24 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
completeIfReady()
}
/// Checks the capabilities of the user's homeserver in order to determine
/// whether or not the display name and avatar can be updated.
///
/// Once complete this method will start the post authentication flow automatically.
@available(iOS 14.0, *)
private func checkHomeserverCapabilities(for userSession: UserSession) {
userSession.matrixSession.matrixRestClient.capabilities { [weak self] capabilities in
guard let self = self else { return }
self.shouldShowDisplayNameScreen = capabilities?.setDisplayName?.isEnabled == true
self.shouldShowAvatarScreen = capabilities?.setAvatarUrl?.isEnabled == true
self.beginPostAuthentication(for: userSession)
} failure: { [weak self] _ in
MXLog.warning("[OnboardingCoordinator] Homeserver capabilities not returned. Skipping personalisation")
self?.beginPostAuthentication(for: userSession)
}
}
/// Displays the next view in the flow after the authentication screen.
private func authenticationCoordinatorDidComplete(_ coordinator: AuthenticationCoordinatorProtocol) {
isShowingAuthentication = false
@@ -287,11 +310,19 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
// MARK: - Post-Authentication
/// Starts the part of the flow that comes after authentication for new users.
@available(iOS 14.0, *)
private func showCongratulationsScreen(userId: String) {
private func beginPostAuthentication(for userSession: UserSession) {
showCongratulationsScreen(for: userSession)
}
/// Show the congratulations screen for new users. The screen will be configured based on the homeserver's capabilities.
@available(iOS 14.0, *)
private func showCongratulationsScreen(for userSession: UserSession) {
MXLog.debug("[OnboardingCoordinator] showCongratulationsScreen")
let parameters = OnboardingCongratulationsCoordinatorParameters(userId: userId)
let parameters = OnboardingCongratulationsCoordinatorParameters(userSession: userSession,
personalizationDisabled: !shouldShowDisplayNameScreen && !shouldShowAvatarScreen)
let coordinator = OnboardingCongratulationsCoordinator(parameters: parameters)
coordinator.completion = { [weak self, weak coordinator] result in
@@ -308,21 +339,25 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
}
}
/// Displays the next view in the flow after the congratulations screen.
@available(iOS 14.0, *)
private func congratulationsCoordinator(_ coordinator: OnboardingCongratulationsCoordinator, didCompleteWith result: OnboardingCongratulationsViewModelResult) {
if let session = session {
switch result {
case .personaliseProfile:
// TODO: Profile screens here instead.
if Analytics.shared.shouldShowAnalyticsPrompt {
showAnalyticsPrompt(for: session)
return
}
case .takeMeHome:
if Analytics.shared.shouldShowAnalyticsPrompt {
showAnalyticsPrompt(for: session)
return
}
private func congratulationsCoordinator(_ coordinator: OnboardingCongratulationsCoordinator, didCompleteWith result: OnboardingCongratulationsCoordinatorResult) {
switch result {
case .personalizeProfile(let userSession):
if shouldShowDisplayNameScreen {
showDisplayNameScreen(for: userSession)
return
} else if shouldShowAvatarScreen {
showAvatarScreen(for: userSession)
return
} else if Analytics.shared.shouldShowAnalyticsPrompt {
showAnalyticsPrompt(for: userSession.matrixSession)
return
}
case .takeMeHome(let userSession):
if Analytics.shared.shouldShowAnalyticsPrompt {
showAnalyticsPrompt(for: userSession.matrixSession)
return
}
}
@@ -330,6 +365,84 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
completeIfReady()
}
/// Show the display name personalization screen for new users using the supplied user session.
@available(iOS 14.0, *)
private func showDisplayNameScreen(for userSession: UserSession) {
MXLog.debug("[OnboardingCoordinator]: showDisplayNameScreen")
let parameters = OnboardingDisplayNameCoordinatorParameters(userSession: userSession)
let coordinator = OnboardingDisplayNameCoordinator(parameters: parameters)
coordinator.completion = { [weak self, weak coordinator] session in
guard let self = self, let coordinator = coordinator else { return }
self.displayNameCoordinator(coordinator, didCompleteWith: session)
}
add(childCoordinator: coordinator)
coordinator.start()
navigationRouter.setRootModule(coordinator, hideNavigationBar: false, animated: true) { [weak self] in
self?.remove(childCoordinator: coordinator)
}
}
/// Displays the next view in the flow after the display name screen.
@available(iOS 14.0, *)
private func displayNameCoordinator(_ coordinator: OnboardingDisplayNameCoordinator, didCompleteWith userSession: UserSession) {
if shouldShowAvatarScreen {
showAvatarScreen(for: userSession)
return
} else if Analytics.shared.shouldShowAnalyticsPrompt {
showAnalyticsPrompt(for: userSession.matrixSession)
return
}
onboardingFinished = true
completeIfReady()
}
/// Show the avatar personalization screen for new users using the supplied user session.
@available(iOS 14.0, *)
private func showAvatarScreen(for userSession: UserSession) {
MXLog.debug("[OnboardingCoordinator]: showAvatarScreen")
let parameters = OnboardingAvatarCoordinatorParameters(userSession: userSession)
let coordinator = OnboardingAvatarCoordinator(parameters: parameters)
coordinator.completion = { [weak self, weak coordinator] session in
guard let self = self, let coordinator = coordinator else { return }
self.avatarCoordinator(coordinator, didCompleteWith: session)
}
add(childCoordinator: coordinator)
coordinator.start()
if navigationRouter.modules.isEmpty || !shouldShowDisplayNameScreen {
navigationRouter.setRootModule(coordinator, hideNavigationBar: false, animated: true) { [weak self] in
self?.remove(childCoordinator: coordinator)
}
} else {
navigationRouter.push(coordinator, animated: true) { [weak self] in
self?.remove(childCoordinator: coordinator)
}
}
}
/// Displays the next view in the flow after the avatar screen.
@available(iOS 14.0, *)
private func avatarCoordinator(_ coordinator: OnboardingAvatarCoordinator, didCompleteWith userSession: UserSession) {
if Analytics.shared.shouldShowAnalyticsPrompt {
showAnalyticsPrompt(for: userSession.matrixSession)
return
}
onboardingFinished = true
completeIfReady()
}
/// Shows the analytics prompt for the supplied session.
///
/// Check `Analytics.shared.shouldShowAnalyticsPrompt` before calling this method.
@available(iOS 14.0, *)
private func showAnalyticsPrompt(for session: MXSession) {
MXLog.debug("[OnboardingCoordinator]: Invite the user to send analytics")
@@ -351,6 +464,7 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
}
}
/// Displays the next view in the flow after the analytics screen.
private func analyticsPromptCoordinatorDidComplete(_ coordinator: AnalyticsPromptCoordinator) {
onboardingFinished = true
completeIfReady()
@@ -358,6 +472,8 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
// MARK: - Finished
/// Calls the coordinator's completion handler if both `onboardingFinished` and `authenticationFinished`
/// are true. Otherwise displays any pending screens and waits to be called again.
private func completeIfReady() {
guard onboardingFinished else {
MXLog.debug("[OnboardingCoordinator] Delaying onboarding completion until all screens have been shown.")
+6 -2
View File
@@ -105,9 +105,13 @@
[self.tableViewPaginationThrottler throttle:^{
NSInteger section = indexPath.section;
if (tableView.numberOfSections <= section)
{
return;
}
NSInteger numberOfRowsInSection = [tableView numberOfRowsInSection:section];
if (tableView.numberOfSections > section
&& indexPath.row == numberOfRowsInSection - 1)
if (indexPath.row == numberOfRowsInSection - 1)
{
[self->recentsDataSource paginateInSection:section];
}
@@ -0,0 +1,96 @@
//
// Copyright 2022 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
import UIKit
@objcMembers
final class LiveLocationSharingBannerView: UIView, NibLoadable, Themable {
// MARK: - Properties
// MARK: Outlets
@IBOutlet private weak var iconImageView: UIImageView!
@IBOutlet private weak var titleLabel: UILabel!
@IBOutlet private weak var stopButton: UIButton!
// MARK: Private
private var theme: Theme!
// MARK: Public
var didTapBackground: (() -> Void)?
var didTapStopButton: (() -> Void)?
// MARK: - Setup
static func instantiate() -> LiveLocationSharingBannerView {
let view = LiveLocationSharingBannerView.loadFromNib()
view.update(theme: ThemeService.shared().theme)
return view
}
// MARK: - Life cycle
override func awakeFromNib() {
super.awakeFromNib()
self.setupBackgroundTapGestureRecognizer()
self.titleLabel.text = VectorL10n.liveLocationSharingBannerTitle
self.stopButton.setTitle(VectorL10n.liveLocationSharingBannerStop, for: .normal)
}
// MARK: - Public
func update(theme: Theme) {
self.theme = theme
let tintColor = theme.colors.background
self.backgroundColor = theme.tintColor
self.iconImageView.tintColor = tintColor
self.titleLabel.textColor = tintColor
self.titleLabel.font = theme.fonts.footnote
self.stopButton.vc_setTitleFont(theme.fonts.footnote)
self.stopButton.tintColor = tintColor
self.stopButton.setTitleColor(tintColor, for: .normal)
self.stopButton.setTitleColor(tintColor.withAlphaComponent(0.5), for: .highlighted)
}
// MARK: - Private
private func setupBackgroundTapGestureRecognizer() {
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleBackgroundViewTap(_:)))
self.addGestureRecognizer(tapGestureRecognizer)
}
// MARK: - Actions
@objc private func handleBackgroundViewTap(_ gestureRecognizer: UITapGestureRecognizer) {
self.didTapBackground?()
}
@IBAction private func stopButtonAction(_ sender: Any) {
self.didTapStopButton?()
}
}
@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="19529" 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="19519"/>
<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="LiveLocationSharingBannerView" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="414" height="104"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="live_location_icon" translatesAutoresizingMaskIntoConstraints="NO" id="hDt-s2-HU0">
<rect key="frame" x="15" y="62" width="24" height="24"/>
<constraints>
<constraint firstAttribute="width" secondItem="hDt-s2-HU0" secondAttribute="height" multiplier="1:1" id="LUH-tx-Vu9"/>
<constraint firstAttribute="width" constant="24" id="jBe-4U-Yb7"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Live location enabled" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7UG-mn-oZ3">
<rect key="frame" x="49" y="49" width="128.5" height="50"/>
<fontDescription key="fontDescription" type="system" pointSize="13"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="H2w-fv-3ba">
<rect key="frame" x="355" y="60.5" width="44" height="27"/>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="44" id="WZJ-7H-OFh"/>
</constraints>
<fontDescription key="fontDescription" style="UICTFontTextStyleFootnote"/>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
<state key="normal" title="Stop"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="vc_adjustsFontForContentSizeCategory" value="YES"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="stopButtonAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="rVA-YA-4J6"/>
</connections>
</button>
</subviews>
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
<color key="backgroundColor" red="0.050980392156862744" green="0.74117647058823533" blue="0.54509803921568623" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="H2w-fv-3ba" secondAttribute="trailing" constant="15" id="29I-X9-nEy"/>
<constraint firstAttribute="bottomMargin" secondItem="7UG-mn-oZ3" secondAttribute="bottom" constant="5" id="5TD-eA-uqi"/>
<constraint firstItem="7UG-mn-oZ3" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="topMargin" constant="5" id="919-NN-C1l"/>
<constraint firstItem="hDt-s2-HU0" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="15" id="FcV-b9-J6v"/>
<constraint firstItem="7UG-mn-oZ3" firstAttribute="leading" secondItem="hDt-s2-HU0" secondAttribute="trailing" constant="10" id="Lsz-9Z-xLO"/>
<constraint firstItem="H2w-fv-3ba" firstAttribute="centerY" secondItem="vUN-kp-3ea" secondAttribute="centerY" id="RTT-lt-BtI"/>
<constraint firstItem="hDt-s2-HU0" firstAttribute="centerY" secondItem="vUN-kp-3ea" secondAttribute="centerY" id="XgN-M3-jLz"/>
<constraint firstItem="H2w-fv-3ba" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="7UG-mn-oZ3" secondAttribute="trailing" constant="10" id="zWz-Me-wx1"/>
</constraints>
<nil key="simulatedTopBarMetrics"/>
<nil key="simulatedBottomBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<connections>
<outlet property="iconImageView" destination="hDt-s2-HU0" id="1HC-Kc-Rxp"/>
<outlet property="stopButton" destination="H2w-fv-3ba" id="g7S-JF-xw5"/>
<outlet property="titleLabel" destination="7UG-mn-oZ3" id="bxZ-zI-Q7J"/>
</connections>
<point key="canvasLocation" x="137.68115942028987" y="-122.54464285714285"/>
</view>
</objects>
<resources>
<image name="live_location_icon" width="24" height="24"/>
</resources>
</document>
@@ -630,7 +630,7 @@
}
// Check whether the option Ignore may be presented
if (RiotSettings.shared.roomMemberScreenShowIgnore && self.mxRoomMember.membership == MXMembershipJoin)
if (RiotSettings.shared.roomMemberScreenShowIgnore)
{
// is he already ignored ?
if (![self.mainSession isUserIgnored:self.mxRoomMember.userId])
+8
View File
@@ -459,4 +459,12 @@ extension RoomCoordinator: RoomViewControllerDelegate {
func roomViewControllerDidStopLoading(_ roomViewController: RoomViewController) {
stopLoading()
}
func roomViewControllerDidTapLiveLocationSharingBanner(_ roomViewController: RoomViewController) {
// TODO:
}
func roomViewControllerDidStopLiveLocationSharing(_ roomViewController: RoomViewController) {
// TODO:
}
}
+6
View File
@@ -277,6 +277,12 @@ didRequestEditForPollWithStartEvent:(MXEvent *)startEvent;
*/
- (void)roomViewControllerDidStopLoading:(RoomViewController *)roomViewController;
/// User tap live location sharing stop action
- (void)roomViewControllerDidStopLiveLocationSharing:(RoomViewController *)roomViewController;
/// User tap live location sharing banner
- (void)roomViewControllerDidTapLiveLocationSharingBanner:(RoomViewController *)roomViewController;
@end
NS_ASSUME_NONNULL_END
+133 -33
View File
@@ -89,6 +89,10 @@
NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNotification";
NSNotificationName const RoomGroupCallTileTappedNotification = @"RoomGroupCallTileTappedNotification";
const NSTimeInterval kResizeComposerAnimationDuration = .05;
static const int kThreadListBarButtonItemTag = 99;
static UIEdgeInsets kThreadListBarButtonItemContentInsetsNoDot;
static UIEdgeInsets kThreadListBarButtonItemContentInsetsDot;
static CGSize kThreadListBarButtonItemImageSize;
@interface RoomViewController () <UISearchBarDelegate, UIGestureRecognizerDelegate, UIScrollViewAccessibilityDelegate, RoomTitleViewTapGestureDelegate, MXKRoomMemberDetailsViewControllerDelegate, ContactsTableViewControllerDelegate, MXServerNoticesDelegate, RoomContextualMenuViewControllerDelegate,
ReactionsMenuViewModelCoordinatorDelegate, EditHistoryCoordinatorBridgePresenterDelegate, MXKDocumentPickerPresenterDelegate, EmojiPickerCoordinatorBridgePresenterDelegate,
@@ -178,9 +182,6 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
// Time to display notification content in the timeline
MXTaskProfile *notificationTaskProfile;
// Reference to thread list bar button item, to update it easily later
BadgedBarButtonItem *threadListBarButtonItem;
}
@property (nonatomic, weak) IBOutlet UIView *overlayContainerView;
@@ -223,6 +224,13 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
// scroll state just before the layout change, and restore it after the layout.
@property (nonatomic) BOOL shouldScrollToBottomAfterLayout;
/// Handles all banners that should be displayed at the top of the timeline but that should not scroll with the timeline
@property (weak, nonatomic, nullable) IBOutlet UIStackView *topBannersStackView;
@property (nonatomic) BOOL shouldShowLiveLocationSharingBannerView;
@property (nonatomic, weak) LiveLocationSharingBannerView *liveLocationSharingBannerView;
@end
@implementation RoomViewController
@@ -230,6 +238,13 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
#pragma mark - Class methods
+ (void)initialize
{
kThreadListBarButtonItemContentInsetsNoDot = UIEdgeInsetsMake(0, 8, 0, 8);
kThreadListBarButtonItemContentInsetsDot = UIEdgeInsetsMake(0, 8, 6, 8);
kThreadListBarButtonItemImageSize = CGSizeMake(21, 21);
}
+ (UINib *)nib
{
return [UINib nibWithNibName:NSStringFromClass(self.class)
@@ -385,6 +400,8 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
[self registerURLPreviewNotifications];
[self setupActions];
[self setupUserSuggestionViewIfNeeded];
}
- (void)userInterfaceThemeDidChange
@@ -461,7 +478,8 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
self.scrollToBottomBadgeLabel.badgeColor = ThemeService.shared.theme.tintColor;
[self updateThreadListBarButtonBadgeWith:self.mainSession.threadingService];
[threadListBarButtonItem updateWithTheme:ThemeService.shared.theme];
[self.liveLocationSharingBannerView updateWithTheme:ThemeService.shared.theme];
[self setNeedsStatusBarAppearanceUpdate];
}
@@ -1029,7 +1047,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
room:dataSource.room];
_userSuggestionCoordinator.delegate = self;
[self setupUserSuggestionView];
[self setupUserSuggestionViewIfNeeded];
}
- (void)onRoomDataSourceReady
@@ -1520,20 +1538,21 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
return item;
}
- (BadgedBarButtonItem *)threadListBarButtonItem
- (UIBarButtonItem *)threadListBarButtonItem
{
UIButton *button = [UIButton new];
UIImage *icon = [AssetImages.threadsIcon.image vc_resizedWith:CGSizeMake(21, 21)];
button.contentEdgeInsets = UIEdgeInsetsMake(4, 8, 4, 8);
[button setImage:icon
button.contentEdgeInsets = kThreadListBarButtonItemContentInsetsNoDot;
button.imageView.contentMode = UIViewContentModeScaleAspectFit;
[button setImage:[AssetImages.threadsIcon.image vc_resizedWith:kThreadListBarButtonItemImageSize]
forState:UIControlStateNormal];
[button addTarget:self
action:@selector(onThreadListTapped:)
forControlEvents:UIControlEventTouchUpInside];
button.accessibilityLabel = [VectorL10n roomAccessibilityThreads];
return [[BadgedBarButtonItem alloc] initWithBaseButton:button
theme:ThemeService.shared.theme];
UIBarButtonItem *result = [[UIBarButtonItem alloc] initWithCustomView:button];
result.tag = kThreadListBarButtonItemTag;
return result;
}
- (void)setupRemoveJitsiWidgetRemoveView
@@ -1783,10 +1802,10 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
else
{
// in a regular timeline
BadgedBarButtonItem *itemThreadList = [self threadListBarButtonItem];
UIBarButtonItem *itemThreadList = [self threadListBarButtonItem];
[self updateThreadListBarButtonItem:itemThreadList
with:self.mainSession.threadingService];
[rightBarButtonItems insertObject:itemThreadList atIndex:0];
threadListBarButtonItem = itemThreadList;
[self updateThreadListBarButtonBadgeWith:self.mainSession.threadingService];
}
}
}
@@ -2353,10 +2372,9 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
}
}
- (void)setupUserSuggestionView
- (void)setupUserSuggestionViewIfNeeded
{
if(!self.isViewLoaded) {
MXLogError(@"Failed setting up user suggestions. View not loaded.");
return;
}
@@ -2380,6 +2398,18 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
[suggestionsViewController didMoveToParentViewController:self];
}
- (void)updateTopBanners
{
[self.view bringSubviewToFront:self.topBannersStackView];
[self.topBannersStackView vc_removeAllSubviews];
if (self.shouldShowLiveLocationSharingBannerView)
{
[self showLiveLocationBannerView];
}
}
#pragma mark - Jitsi
- (void)showJitsiCallWithWidget:(Widget*)widget
@@ -6831,41 +6861,82 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
- (void)updateThreadListBarButtonBadgeWith:(MXThreadingService *)service
{
if (!threadListBarButtonItem || !service)
[self updateThreadListBarButtonItem:nil with:service];
}
- (void)updateThreadListBarButtonItem:(UIBarButtonItem *)barButtonItem with:(MXThreadingService *)service
{
if (!service)
{
// there is no thread list bar button, ignore
return;
}
__block NSInteger replaceIndex = NSNotFound;
[self.navigationItem.rightBarButtonItems enumerateObjectsUsingBlock:^(UIBarButtonItem * _Nonnull item, NSUInteger index, BOOL * _Nonnull stop)
{
if (item.tag == kThreadListBarButtonItemTag)
{
replaceIndex = index;
*stop = YES;
}
}];
if (!barButtonItem && replaceIndex == NSNotFound)
{
// there is no thread list bar button item, and not provided another to update
// ignore
return;
}
UIBarButtonItem *threadListBarButtonItem = barButtonItem ?: [self threadListBarButtonItem];
UIButton *button = (UIButton *)threadListBarButtonItem.customView;
MXThreadNotificationsCount *notificationsCount = [service notificationsCountForRoom:self.roomDataSource.roomId];
if (notificationsCount.numberOfHighlightedThreads > 0)
{
threadListBarButtonItem.badgeText = [self threadListBadgeTextFor:notificationsCount.numberOfHighlightedThreads];
threadListBarButtonItem.badgeBackgroundColor = ThemeService.shared.theme.colors.alert;
[button setImage:AssetImages.threadsIconRedDot.image
forState:UIControlStateNormal];
button.contentEdgeInsets = kThreadListBarButtonItemContentInsetsDot;
}
else if (notificationsCount.numberOfNotifiedThreads > 0)
{
threadListBarButtonItem.badgeText = [self threadListBadgeTextFor:notificationsCount.numberOfNotifiedThreads];
threadListBarButtonItem.badgeBackgroundColor = ThemeService.shared.theme.noticeSecondaryColor;
if (ThemeService.shared.isCurrentThemeDark)
{
[button setImage:AssetImages.threadsIconGrayDotDark.image
forState:UIControlStateNormal];
}
else
{
[button setImage:AssetImages.threadsIconGrayDotLight.image
forState:UIControlStateNormal];
}
button.contentEdgeInsets = kThreadListBarButtonItemContentInsetsDot;
}
else
{
// remove badge
threadListBarButtonItem.badgeText = nil;
[button setImage:[AssetImages.threadsIcon.image vc_resizedWith:kThreadListBarButtonItemImageSize]
forState:UIControlStateNormal];
button.contentEdgeInsets = kThreadListBarButtonItemContentInsetsNoDot;
}
}
- (NSString *)threadListBadgeTextFor:(NSUInteger)numberOfThreads
{
if (numberOfThreads < 100)
if (replaceIndex == NSNotFound)
{
return [NSString stringWithFormat:@"%tu", numberOfThreads];
// there is no thread list bar button item, this was only an update
return;
}
else
UIBarButtonItem *originalItem = self.navigationItem.rightBarButtonItems[replaceIndex];
UIButton *originalButton = (UIButton *)originalItem.customView;
if ([originalButton imageForState:UIControlStateNormal] == [button imageForState:UIControlStateNormal]
&& UIEdgeInsetsEqualToEdgeInsets(originalButton.contentEdgeInsets, button.contentEdgeInsets))
{
return @"···";
// no need to replace, it's the same
return;
}
NSMutableArray<UIBarButtonItem*> *items = [self.navigationItem.rightBarButtonItems mutableCopy];
items[replaceIndex] = threadListBarButtonItem;
self.navigationItem.rightBarButtonItems = items;
}
#pragma mark - RoomContextualMenuViewControllerDelegate
@@ -7323,5 +7394,34 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
[self stopActivityIndicator];
}
@end
#pragma mark - Live location sharing
- (void)showLiveLocationBannerView
{
if (self.liveLocationSharingBannerView)
{
return;
}
LiveLocationSharingBannerView *bannerView = [LiveLocationSharingBannerView instantiate];
[bannerView updateWithTheme:ThemeService.shared.theme];
MXWeakify(self);
bannerView.didTapBackground = ^{
MXStrongifyAndReturnIfNil(self);
[self.delegate roomViewControllerDidTapLiveLocationSharingBanner:self];
};
bannerView.didTapStopButton = ^{
MXStrongifyAndReturnIfNil(self);
[self.delegate roomViewControllerDidStopLiveLocationSharing:self];
};
[self.topBannersStackView addArrangedSubview:bannerView];
self.liveLocationSharingBannerView = bannerView;
}
@end
+24 -4
View File
@@ -13,7 +13,6 @@
<connections>
<outlet property="bubblesTableView" destination="BGD-sd-SQR" id="OG4-Tw-Ovt"/>
<outlet property="bubblesTableViewBottomConstraint" destination="1SD-y2-oTg" id="n8D-hT-eqt"/>
<outlet property="bubblesTableViewTopConstraint" destination="bFw-dg-qEr" id="KxM-H0-h2y"/>
<outlet property="inputBackgroundView" destination="Xt7-83-dQh" id="xoG-eb-zFB"/>
<outlet property="jumpToLastUnreadBanner" destination="S6r-bo-jxw" id="FSS-Be-E15"/>
<outlet property="jumpToLastUnreadBannerContainer" destination="S6H-Az-RCM" id="YlI-fu-OpT"/>
@@ -32,6 +31,7 @@
<outlet property="roomInputToolbarContainerHeightConstraint" destination="5eD-Fm-RDb" id="6ny-5w-1UA"/>
<outlet property="scrollToBottomBadgeLabel" destination="QHs-rM-UU8" id="wk7-PQ-9Jm"/>
<outlet property="scrollToBottomButton" destination="Ih9-EU-BOU" id="Wwg-gS-Sfp"/>
<outlet property="topBannersStackView" destination="3z2-8P-wlg" id="uf5-gw-zWi"/>
<outlet property="userSuggestionContainerView" destination="oni-F4-X1U" id="0js-Ji-8Mm"/>
<outlet property="view" destination="iN0-l3-epB" id="ieV-u7-rXU"/>
</connections>
@@ -41,8 +41,25 @@
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView contentMode="scaleToFill" alwaysBounceVertical="YES" keyboardDismissMode="interactive" style="plain" separatorStyle="none" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="BGD-sd-SQR">
<rect key="frame" x="0.0" y="0.0" width="375" height="626"/>
<stackView opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="3z2-8P-wlg">
<rect key="frame" x="0.0" y="0.0" width="375" height="20.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hIS-uE-jlE">
<rect key="frame" x="0.0" y="0.0" width="375" height="20.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<variation key="default">
<mask key="subviews">
<exclude reference="hIS-uE-jlE"/>
</mask>
</variation>
</stackView>
<tableView contentMode="scaleToFill" ambiguous="YES" alwaysBounceVertical="YES" keyboardDismissMode="interactive" style="plain" separatorStyle="none" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="BGD-sd-SQR">
<rect key="frame" x="0.0" y="20.5" width="375" height="605.5"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="accessibilityIdentifier" value="RoomVCBubblesTableView"/>
@@ -192,6 +209,7 @@
<constraint firstItem="54r-18-K1g" firstAttribute="top" secondItem="QpJ-1u-4ii" secondAttribute="top" id="7Ft-EV-Br0"/>
<constraint firstItem="gt1-EO-UVY" firstAttribute="leading" secondItem="QpJ-1u-4ii" secondAttribute="leading" id="8Ff-Ot-h3F"/>
<constraint firstItem="Xt7-83-dQh" firstAttribute="bottom" secondItem="iN0-l3-epB" secondAttribute="bottom" id="9g2-wm-4M9"/>
<constraint firstItem="3z2-8P-wlg" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="AN6-Eq-Po9"/>
<constraint firstItem="fmF-ad-erE" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="DtG-PR-F24"/>
<constraint firstItem="BGD-sd-SQR" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="ECb-mP-EOG"/>
<constraint firstAttribute="trailing" secondItem="BGD-sd-SQR" secondAttribute="trailing" id="EGD-cX-OGq"/>
@@ -200,17 +218,19 @@
<constraint firstItem="QHs-rM-UU8" firstAttribute="centerX" secondItem="Ih9-EU-BOU" secondAttribute="centerX" id="K8X-wJ-hkh"/>
<constraint firstItem="QHs-rM-UU8" firstAttribute="centerY" secondItem="Ih9-EU-BOU" secondAttribute="top" id="KL4-OU-cP6"/>
<constraint firstItem="gt1-EO-UVY" firstAttribute="trailing" secondItem="QpJ-1u-4ii" secondAttribute="trailing" id="L9A-P5-xeT"/>
<constraint firstItem="3z2-8P-wlg" firstAttribute="top" secondItem="QpJ-1u-4ii" secondAttribute="top" id="Nyp-Yf-EfD"/>
<constraint firstItem="XX4-n6-hCm" firstAttribute="leading" secondItem="QpJ-1u-4ii" secondAttribute="leading" id="Os4-cU-eQb"/>
<constraint firstItem="XX4-n6-hCm" firstAttribute="bottom" secondItem="nLd-BP-JAE" secondAttribute="top" id="QO8-nF-xys"/>
<constraint firstAttribute="trailing" secondItem="3z2-8P-wlg" secondAttribute="trailing" id="QPT-D1-rPy"/>
<constraint firstItem="nLd-BP-JAE" firstAttribute="leading" secondItem="QpJ-1u-4ii" secondAttribute="leading" id="T1Y-r9-bYV"/>
<constraint firstItem="XX4-n6-hCm" firstAttribute="trailing" secondItem="QpJ-1u-4ii" secondAttribute="trailing" id="Tij-mR-KZp"/>
<constraint firstItem="gt1-EO-UVY" firstAttribute="top" secondItem="QpJ-1u-4ii" secondAttribute="top" id="YfN-0Z-0bc"/>
<constraint firstItem="54r-18-K1g" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="aR5-rp-1Cp"/>
<constraint firstItem="gt1-EO-UVY" firstAttribute="bottom" secondItem="nLd-BP-JAE" secondAttribute="bottom" id="acJ-g8-R7x"/>
<constraint firstItem="nLd-BP-JAE" firstAttribute="top" secondItem="oni-F4-X1U" secondAttribute="bottom" id="ave-fu-X1D"/>
<constraint firstItem="BGD-sd-SQR" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="bFw-dg-qEr"/>
<constraint firstItem="oni-F4-X1U" firstAttribute="top" relation="greaterThanOrEqual" secondItem="QpJ-1u-4ii" secondAttribute="top" id="hzP-Ee-xzI"/>
<constraint firstItem="oni-F4-X1U" firstAttribute="leading" secondItem="QpJ-1u-4ii" secondAttribute="leading" id="k83-dd-hdL"/>
<constraint firstItem="BGD-sd-SQR" firstAttribute="top" secondItem="3z2-8P-wlg" secondAttribute="bottom" id="lqZ-Cq-40f"/>
<constraint firstAttribute="bottom" secondItem="nLd-BP-JAE" secondAttribute="bottom" id="omU-sm-3bK"/>
<constraint firstItem="nLd-BP-JAE" firstAttribute="trailing" secondItem="QpJ-1u-4ii" secondAttribute="trailing" id="pRw-S0-6WL"/>
<constraint firstItem="fmF-ad-erE" firstAttribute="top" secondItem="QpJ-1u-4ii" secondAttribute="top" id="qYq-nb-cu5"/>
+6 -2
View File
@@ -110,9 +110,13 @@
[self.tableViewPaginationThrottler throttle:^{
NSInteger section = indexPath.section;
if (tableView.numberOfSections <= section)
{
return;
}
NSInteger numberOfRowsInSection = [tableView numberOfRowsInSection:section];
if (tableView.numberOfSections > section
&& indexPath.row == numberOfRowsInSection - 1)
if (indexPath.row == numberOfRowsInSection - 1)
{
[self->recentsDataSource paginateInSection:section];
}
@@ -22,4 +22,5 @@ import Foundation
enum SideMenuViewAction {
case loadData
case tap(menuItem: SideMenuItem, sourceView: UIView)
case tapHeader(sourceView: UIView)
}
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="V8j-Lb-PgC">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="20037" 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="17703"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
@@ -28,29 +28,42 @@
</constraints>
</view>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="pj0-XK-IJ2">
<rect key="frame" x="87" y="23" width="307" height="46"/>
<rect key="frame" x="87" y="26.5" width="307" height="39"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bbo-IX-VUb">
<rect key="frame" x="0.0" y="0.0" width="307" height="24"/>
<rect key="frame" x="0.0" y="0.0" width="307" height="20.5"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle3"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="VWw-Gn-nd0">
<rect key="frame" x="0.0" y="28" width="307" height="18"/>
<rect key="frame" x="0.0" y="24.5" width="307" height="14.5"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</stackView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="WR7-DB-ZZt">
<rect key="frame" x="20" y="20" width="374" height="52"/>
<color key="tintColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<state key="normal" title="Button"/>
<buttonConfiguration key="configuration" style="plain"/>
<connections>
<action selector="headerTapActionWithSender:" destination="V8j-Lb-PgC" eventType="touchUpInside" id="1Zl-nV-Owp"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="WR7-DB-ZZt" firstAttribute="leading" secondItem="NuF-pw-IzO" secondAttribute="leading" id="5h9-2q-GNI"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="pj0-XK-IJ2" secondAttribute="bottom" constant="20" id="GyA-NG-zuK"/>
<constraint firstItem="WR7-DB-ZZt" firstAttribute="top" secondItem="NuF-pw-IzO" secondAttribute="top" id="MXY-hN-UAR"/>
<constraint firstItem="pj0-XK-IJ2" firstAttribute="centerY" secondItem="NuF-pw-IzO" secondAttribute="centerY" id="Ryy-Un-b4P"/>
<constraint firstAttribute="trailing" secondItem="pj0-XK-IJ2" secondAttribute="trailing" constant="20" id="Y1J-eh-n41"/>
<constraint firstItem="WR7-DB-ZZt" firstAttribute="trailing" secondItem="pj0-XK-IJ2" secondAttribute="trailing" id="bgn-k8-OzN"/>
<constraint firstItem="pj0-XK-IJ2" firstAttribute="leading" secondItem="NuF-pw-IzO" secondAttribute="trailing" constant="15" id="dY6-O4-aq7"/>
<constraint firstItem="WR7-DB-ZZt" firstAttribute="bottom" secondItem="NuF-pw-IzO" secondAttribute="bottom" id="kVm-8h-9TN"/>
<constraint firstItem="NuF-pw-IzO" firstAttribute="leading" secondItem="uTs-MO-piF" secondAttribute="leading" constant="20" id="rSh-ot-aqo"/>
<constraint firstItem="NuF-pw-IzO" firstAttribute="top" secondItem="uTs-MO-piF" secondAttribute="top" constant="20" id="woS-eb-vCr"/>
</constraints>
@@ -114,7 +127,7 @@
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="zK0-v6-7Wt" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-3198" y="-647"/>
<point key="canvasLocation" x="-3198.5507246376815" y="-647.54464285714278"/>
</scene>
</scenes>
</document>
@@ -198,6 +198,10 @@ final class SideMenuViewController: UIViewController {
// MARK: - Actions
@IBAction func headerTapAction(sender: UIView) {
self.viewModel.process(viewAction: .tapHeader(sourceView: sender))
}
}
// MARK: - SideMenuViewModelViewDelegate
@@ -53,6 +53,8 @@ final class SideMenuViewModel: SideMenuViewModelType {
self.loadData()
case .tap(menuItem: let menuItem, sourceView: let sourceView):
self.coordinatorDelegate?.sideMenuViewModel(self, didTapMenuItem: menuItem, fromSourceView: sourceView)
case .tapHeader(sourceView: let sourceView):
self.coordinatorDelegate?.sideMenuViewModel(self, didTapMenuItem: .settings, fromSourceView: sourceView)
}
}
@@ -474,6 +474,13 @@ extension ExploreRoomCoordinator: RoomViewControllerDelegate {
}
func roomViewControllerDidTapLiveLocationSharingBanner(_ roomViewController: RoomViewController) {
// TODO:
}
func roomViewControllerDidStopLiveLocationSharing(_ roomViewController: RoomViewController) {
// TODO:
}
}
// MARK: - ContactsPickerCoordinatorDelegate