Implements side menu screen.

This commit is contained in:
SBiOSoftWhare 2021-06-07 00:33:12 +02:00
parent 2d2e26682c
commit b32bffed78
7 changed files with 595 additions and 0 deletions

View file

@ -224,6 +224,11 @@ internal enum StoryboardScene {
internal static let searchableDirectoryViewController = SceneType<Riot.ShowDirectoryViewController>(storyboard: ShowDirectoryViewController.self, identifier: "SearchableDirectoryViewController") internal static let searchableDirectoryViewController = SceneType<Riot.ShowDirectoryViewController>(storyboard: ShowDirectoryViewController.self, identifier: "SearchableDirectoryViewController")
} }
internal enum SideMenuViewController: StoryboardType {
internal static let storyboardName = "SideMenuViewController"
internal static let initialScene = InitialSceneType<Riot.SideMenuViewController>(storyboard: SideMenuViewController.self)
}
internal enum SimpleScreenTemplateViewController: StoryboardType { internal enum SimpleScreenTemplateViewController: StoryboardType {
internal static let storyboardName = "SimpleScreenTemplateViewController" internal static let storyboardName = "SimpleScreenTemplateViewController"

View file

@ -0,0 +1,25 @@
// File created from ScreenTemplate
// $ createScreen.sh SideMenu SideMenu
/*
Copyright 2020 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
/// SideMenuViewController view actions exposed to view model
enum SideMenuViewAction {
case loadData
case tap(menuItem: SideMenuItem, sourceView: UIView)
}

View file

@ -0,0 +1,149 @@
<?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">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Side Menu View Controller-->
<scene sceneID="mt5-wz-YKA">
<objects>
<viewController extendedLayoutIncludesOpaqueBars="YES" automaticallyAdjustsScrollViewInsets="NO" id="V8j-Lb-PgC" customClass="SideMenuViewController" 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>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="9U2-KL-ZVA">
<rect key="frame" x="0.0" y="44" width="414" height="852"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="e7g-um-WO4">
<rect key="frame" x="0.0" y="0.0" width="414" height="222"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="voD-3Q-ryt">
<rect key="frame" x="0.0" y="0.0" width="414" height="222"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="uTs-MO-piF">
<rect key="frame" x="0.0" y="0.0" width="414" height="158"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="NuF-pw-IzO" customClass="UserAvatarView" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="20" y="30" width="52" height="52"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="width" secondItem="NuF-pw-IzO" secondAttribute="height" multiplier="1:1" id="aPR-9H-XC7"/>
<constraint firstAttribute="width" constant="52" id="v7Z-9B-ROI"/>
</constraints>
</view>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="pj0-XK-IJ2">
<rect key="frame" x="20" y="92" width="374" height="46"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bbo-IX-VUb">
<rect key="frame" x="0.0" y="0.0" width="374" height="24"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle3"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="VWw-Gn-nd0">
<rect key="frame" x="0.0" y="28" width="374" height="18"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</stackView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="pj0-XK-IJ2" secondAttribute="bottom" constant="20" id="GyA-NG-zuK"/>
<constraint firstAttribute="trailing" secondItem="pj0-XK-IJ2" secondAttribute="trailing" constant="20" id="Y1J-eh-n41"/>
<constraint firstItem="pj0-XK-IJ2" firstAttribute="leading" secondItem="uTs-MO-piF" secondAttribute="leading" constant="20" id="oqk-zx-vwa"/>
<constraint firstItem="NuF-pw-IzO" firstAttribute="leading" secondItem="uTs-MO-piF" secondAttribute="leading" constant="20" id="rSh-ot-aqo"/>
<constraint firstItem="pj0-XK-IJ2" firstAttribute="top" secondItem="NuF-pw-IzO" secondAttribute="bottom" constant="10" id="wMq-kI-hIR"/>
<constraint firstItem="NuF-pw-IzO" firstAttribute="top" secondItem="uTs-MO-piF" secondAttribute="top" constant="30" id="woS-eb-vCr"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="M9u-6y-ybq">
<rect key="frame" x="0.0" y="158" width="414" height="64"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="jh2-Rr-gGK">
<rect key="frame" x="20" y="0.0" width="374" height="44"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="d2f-GW-Q3t" customClass="SideMenuActionView" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="374" height="44"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="44" id="RaC-Fc-LVI"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstItem="d2f-GW-Q3t" firstAttribute="width" secondItem="jh2-Rr-gGK" secondAttribute="width" id="OtD-wt-AZr"/>
</constraints>
</stackView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="jh2-Rr-gGK" firstAttribute="leading" secondItem="M9u-6y-ybq" secondAttribute="leading" constant="20" id="4H1-E7-NKg"/>
<constraint firstAttribute="bottom" secondItem="jh2-Rr-gGK" secondAttribute="bottom" constant="20" id="Ml9-0O-ZAG"/>
<constraint firstItem="jh2-Rr-gGK" firstAttribute="top" secondItem="M9u-6y-ybq" secondAttribute="top" id="dTl-ZO-glj"/>
<constraint firstAttribute="trailing" secondItem="jh2-Rr-gGK" secondAttribute="trailing" constant="20" id="uW2-nD-nhl"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="M9u-6y-ybq" firstAttribute="leading" secondItem="voD-3Q-ryt" secondAttribute="leading" id="BY4-9k-kWW"/>
<constraint firstAttribute="bottom" secondItem="M9u-6y-ybq" secondAttribute="bottom" id="JgN-P3-Gr2"/>
<constraint firstItem="M9u-6y-ybq" firstAttribute="top" secondItem="uTs-MO-piF" secondAttribute="bottom" id="MuD-JP-iy9"/>
<constraint firstAttribute="width" priority="750" constant="500" id="glD-Sz-73O"/>
<constraint firstItem="uTs-MO-piF" firstAttribute="leading" secondItem="voD-3Q-ryt" secondAttribute="leading" id="kQ7-oa-oSs"/>
<constraint firstItem="uTs-MO-piF" firstAttribute="top" secondItem="voD-3Q-ryt" secondAttribute="top" id="m0n-kQ-UAA"/>
<constraint firstAttribute="trailing" secondItem="uTs-MO-piF" secondAttribute="trailing" id="oWE-b2-UKq"/>
<constraint firstAttribute="trailing" secondItem="M9u-6y-ybq" secondAttribute="trailing" id="wLj-aM-UyK"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="voD-3Q-ryt" secondAttribute="bottom" id="63a-5e-ptU"/>
<constraint firstItem="voD-3Q-ryt" firstAttribute="centerX" secondItem="e7g-um-WO4" secondAttribute="centerX" id="P2G-mq-gQW"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="voD-3Q-ryt" secondAttribute="trailing" id="QgV-SO-5yf"/>
<constraint firstItem="voD-3Q-ryt" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="e7g-um-WO4" secondAttribute="leading" id="YPo-u1-PtT"/>
<constraint firstItem="voD-3Q-ryt" firstAttribute="top" secondItem="e7g-um-WO4" secondAttribute="top" id="rhQ-96-szL"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="e7g-um-WO4" secondAttribute="trailing" id="GyG-Fh-PME"/>
<constraint firstItem="e7g-um-WO4" firstAttribute="width" secondItem="9U2-KL-ZVA" secondAttribute="width" id="Ok2-WQ-Zgc"/>
<constraint firstAttribute="bottom" secondItem="e7g-um-WO4" secondAttribute="bottom" id="Y46-NP-zAc"/>
<constraint firstItem="e7g-um-WO4" firstAttribute="leading" secondItem="9U2-KL-ZVA" secondAttribute="leading" id="aoV-Yh-AcD"/>
<constraint firstItem="e7g-um-WO4" firstAttribute="top" secondItem="9U2-KL-ZVA" secondAttribute="top" id="pFN-bA-SHw"/>
</constraints>
</scrollView>
</subviews>
<viewLayoutGuide key="safeArea" id="bFg-jh-JZB"/>
<color key="backgroundColor" red="0.94509803921568625" green="0.96078431372549022" blue="0.97254901960784312" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="9U2-KL-ZVA" secondAttribute="bottom" id="7Cb-nY-CsO"/>
<constraint firstItem="9U2-KL-ZVA" firstAttribute="leading" secondItem="bFg-jh-JZB" secondAttribute="leading" id="GdQ-hK-muG"/>
<constraint firstItem="bFg-jh-JZB" firstAttribute="trailing" secondItem="9U2-KL-ZVA" secondAttribute="trailing" id="sbD-ek-vGJ"/>
<constraint firstItem="bFg-jh-JZB" firstAttribute="top" secondItem="9U2-KL-ZVA" secondAttribute="top" id="wTB-V6-IHV"/>
</constraints>
</view>
<connections>
<outlet property="menuItemsStackView" destination="jh2-Rr-gGK" id="mTS-AO-avQ"/>
<outlet property="scrollView" destination="9U2-KL-ZVA" id="ojG-2y-X7b"/>
<outlet property="userAvatarView" destination="NuF-pw-IzO" id="Xyh-Rl-hW4"/>
<outlet property="userDisplayNameLabel" destination="bbo-IX-VUb" id="8vG-CB-Fgo"/>
<outlet property="userIdLabel" destination="VWw-Gn-nd0" id="4gK-yt-uR9"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="zK0-v6-7Wt" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-3198" y="-647"/>
</scene>
</scenes>
</document>

View file

@ -0,0 +1,218 @@
// File created from ScreenTemplate
// $ createScreen.sh SideMenu SideMenu
/*
Copyright 2020 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import UIKit
final class SideMenuViewController: UIViewController {
// MARK: - Constants
private enum Constants {
static let sideMenuActionViewHeight: CGFloat = 44.0
}
// MARK: - Properties
// MARK: Outlets
@IBOutlet private weak var scrollView: UIScrollView!
// User info
@IBOutlet private weak var userAvatarView: UserAvatarView!
@IBOutlet private weak var userDisplayNameLabel: UILabel!
@IBOutlet private weak var userIdLabel: UILabel!
// Bottom menu items
@IBOutlet private weak var menuItemsStackView: UIStackView!
// MARK: Private
private var viewModel: SideMenuViewModelType!
private var theme: Theme!
private var keyboardAvoider: KeyboardAvoider?
private var errorPresenter: MXKErrorPresentation!
private var activityPresenter: ActivityIndicatorPresenter!
private var sideMenuActionViews: [SideMenuActionView] = []
private weak var sideMenuVersionView: SideMenuVersionView?
// MARK: - Setup
class func instantiate(with viewModel: SideMenuViewModelType) -> SideMenuViewController {
let viewController = StoryboardScene.SideMenuViewController.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.
self.setupViews()
self.activityPresenter = ActivityIndicatorPresenter()
self.errorPresenter = MXKErrorAlertPresentation()
self.registerThemeServiceDidChangeThemeNotification()
self.update(theme: self.theme)
self.viewModel.viewDelegate = self
self.viewModel.process(viewAction: .loadData)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.setNavigationBarHidden(true, animated: animated)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return self.theme.statusBarStyle
}
// MARK: - Private
private func update(theme: Theme) {
self.theme = theme
if let navigationBar = self.navigationController?.navigationBar {
theme.applyStyle(onNavigationBar: navigationBar)
}
self.view.backgroundColor = theme.headerBackgroundColor
self.userAvatarView.update(theme: theme)
self.userDisplayNameLabel.textColor = theme.textPrimaryColor
self.userIdLabel.textColor = theme.textSecondaryColor
for sideMenuActionView in self.sideMenuActionViews {
sideMenuActionView.update(theme: theme)
}
self.sideMenuVersionView?.update(theme: theme)
}
private func registerThemeServiceDidChangeThemeNotification() {
NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil)
}
@objc private func themeDidChange() {
self.update(theme: ThemeService.shared().theme)
}
private func setupViews() {
}
private func render(viewState: SideMenuViewState) {
switch viewState {
case .loading:
self.renderLoading()
case .loaded(let viewData):
self.renderLoaded(viewData: viewData)
case .error(let error):
self.render(error: error)
}
}
private func renderLoading() {
self.activityPresenter.presentActivityIndicator(on: self.view, animated: true)
}
private func renderLoaded(viewData: SideMenuViewData) {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
self.updateUserInformation(with: viewData.userAvatarViewData)
self.updateBottomMenuItems(with: viewData)
}
private func updateUserInformation(with userAvatarViewData: UserAvatarViewData) {
self.userIdLabel.text = userAvatarViewData.userId
self.userDisplayNameLabel.text = userAvatarViewData.displayName
self.userDisplayNameLabel.isHidden = userAvatarViewData.displayName.isEmptyOrNil
self.userAvatarView.fill(with: userAvatarViewData)
}
private func updateBottomMenuItems(with viewData: SideMenuViewData) {
self.menuItemsStackView.vc_removeAllSubviews()
self.sideMenuActionViews = []
for sideMenuItem in viewData.sideMenuItems {
let sideMenuActionView = SideMenuActionView.instantiate()
sideMenuActionView.translatesAutoresizingMaskIntoConstraints = false
sideMenuActionView.heightAnchor.constraint(equalToConstant: Constants.sideMenuActionViewHeight).isActive = true
sideMenuActionView.update(theme: self.theme)
sideMenuActionView.fill(with: sideMenuItem)
sideMenuActionView.delegate = self
self.menuItemsStackView.addArrangedSubview(sideMenuActionView)
sideMenuActionView.widthAnchor.constraint(equalTo: menuItemsStackView.widthAnchor).isActive = true
self.sideMenuActionViews.append(sideMenuActionView)
}
if let appVersion = viewData.appVersion {
let sideMenuVersionView = SideMenuVersionView.instantiate()
sideMenuVersionView.translatesAutoresizingMaskIntoConstraints = false
sideMenuVersionView.update(theme: self.theme)
sideMenuVersionView.fill(with: appVersion)
self.menuItemsStackView.addArrangedSubview(sideMenuVersionView)
sideMenuVersionView.widthAnchor.constraint(equalTo: menuItemsStackView.widthAnchor).isActive = true
self.sideMenuVersionView = sideMenuVersionView
}
}
private func render(error: Error) {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil)
}
// MARK: - Actions
}
// MARK: - SideMenuViewModelViewDelegate
extension SideMenuViewController: SideMenuViewModelViewDelegate {
func sideMenuViewModel(_ viewModel: SideMenuViewModelType, didUpdateViewState viewSate: SideMenuViewState) {
self.render(viewState: viewSate)
}
}
// MARK: - SideMenuActionViewDelegate
extension SideMenuViewController: SideMenuActionViewDelegate {
func sideMenuActionView(_ actionView: SideMenuActionView, didTapMenuItem sideMenuItem: SideMenuItem?) {
guard let sideMenuItem = sideMenuItem else {
return
}
self.viewModel.process(viewAction: .tap(menuItem: sideMenuItem, sourceView: actionView))
}
}

View file

@ -0,0 +1,130 @@
// File created from ScreenTemplate
// $ createScreen.sh SideMenu SideMenu
/*
Copyright 2020 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
final class SideMenuViewModel: SideMenuViewModelType {
// MARK: - Properties
// MARK: Private
private let userSessionsService: UserSessionsService
private let appInfo: AppInfo
private var currentOperation: MXHTTPOperation?
// MARK: Public
weak var viewDelegate: SideMenuViewModelViewDelegate?
weak var coordinatorDelegate: SideMenuViewModelCoordinatorDelegate?
// MARK: - Setup
init(userSessionsService: UserSessionsService, appInfo: AppInfo) {
self.userSessionsService = userSessionsService
self.appInfo = appInfo
}
deinit {
self.cancelOperations()
}
// MARK: - Public
func process(viewAction: SideMenuViewAction) {
switch viewAction {
case .loadData:
self.loadData()
case .tap(menuItem: let menuItem, sourceView: let sourceView):
self.coordinatorDelegate?.sideMenuViewModel(self, didTapMenuItem: menuItem, fromSourceView: sourceView)
}
}
// MARK: - Private
private func loadData() {
self.update(viewState: .loading)
guard let mainUserSession = self.userSessionsService.mainUserSession else {
return
}
self.updateView(with: mainUserSession)
self.registerUserSessionsServiceNotifications()
}
private func userAvatarViewData(from mxSession: MXSession) -> UserAvatarViewData? {
guard let userId = mxSession.myUserId, let mediaManager = mxSession.mediaManager else {
return nil
}
let userDisplayName = mxSession.myUser.displayname
let avatarUrl = mxSession.myUser.avatarUrl
return UserAvatarViewData(userId: userId,
displayName: userDisplayName,
avatarUrl: avatarUrl,
mediaManager: mediaManager)
}
private func update(viewState: SideMenuViewState) {
self.viewDelegate?.sideMenuViewModel(self, didUpdateViewState: viewState)
}
private func cancelOperations() {
self.currentOperation?.cancel()
}
private func updateView(with userSession: UserSession) {
guard let userAvatarViewData = self.userAvatarViewData(from: userSession.matrixSession) else {
return
}
let sideMenuItems: [SideMenuItem] = [
.inviteFriends,
.settings,
.help,
.feedback
]
let appVersion = self.appInfo.appVersion?.bundleShortVersion
let viewData = SideMenuViewData(userAvatarViewData: userAvatarViewData, sideMenuItems: sideMenuItems, appVersion: appVersion)
self.update(viewState: .loaded(viewData))
}
private func registerUserSessionsServiceNotifications() {
// Listen only notifications from the current UserSessionsService instance
NotificationCenter.default.addObserver(self, selector: #selector(userSessionDidChange(_:)), name: UserSessionsService.userSessionDidChange, object: self.userSessionsService)
}
@objc private func userSessionDidChange(_ notification: Notification) {
guard let userSession = notification.userInfo?[UserSessionsService.NotificationUserInfoKey.userSession] as? UserSession else {
return
}
// Main user session did change (maybe avatar or display name changed)
if let mainUserSession = self.userSessionsService.mainUserSession, mainUserSession.userId == userSession.userId {
self.updateView(with: mainUserSession)
}
}
}

View file

@ -0,0 +1,36 @@
// File created from ScreenTemplate
// $ createScreen.sh SideMenu SideMenu
/*
Copyright 2020 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
protocol SideMenuViewModelViewDelegate: class {
func sideMenuViewModel(_ viewModel: SideMenuViewModelType, didUpdateViewState viewSate: SideMenuViewState)
}
protocol SideMenuViewModelCoordinatorDelegate: class {
func sideMenuViewModel(_ viewModel: SideMenuViewModelType, didTapMenuItem menuItem: SideMenuItem, fromSourceView sourceView: UIView)
}
/// Protocol describing the view model used by `SideMenuViewController`
protocol SideMenuViewModelType {
var viewDelegate: SideMenuViewModelViewDelegate? { get set }
var coordinatorDelegate: SideMenuViewModelCoordinatorDelegate? { get set }
func process(viewAction: SideMenuViewAction)
}

View file

@ -0,0 +1,32 @@
// File created from ScreenTemplate
// $ createScreen.sh SideMenu SideMenu
/*
Copyright 2020 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
struct SideMenuViewData {
let userAvatarViewData: UserAvatarViewData
let sideMenuItems: [SideMenuItem]
let appVersion: String?
}
/// SideMenuViewController view state
enum SideMenuViewState {
case loading
case loaded(_ viewData: SideMenuViewData)
case error(Error)
}