diff --git a/Podfile b/Podfile index c4a249575..7452458e8 100644 --- a/Podfile +++ b/Podfile @@ -43,7 +43,7 @@ when String # specific MatrixSDK released version $matrixSDKVersionSpec = $matrixSDKVersion end -$matrixSDKVersionSpec = { :git => 'https://dl-gitlab.example.com/bwmessenger/bundesmessenger/bundesmessenger-ios-sdk', :tag => 'v2.12.0' } +$matrixSDKVersionSpec = { :git => 'https://dl-gitlab.example.com/bwmessenger/bundesmessenger/bundesmessenger-ios-sdk', :tag => 'v2.13.0-RC01' } # Method to import the MatrixSDK def import_MatrixSDK diff --git a/Riot/Assets/de.lproj/Bwi.strings b/Riot/Assets/de.lproj/Bwi.strings index 023d04dd6..1e9c47ae7 100644 --- a/Riot/Assets/de.lproj/Bwi.strings +++ b/Riot/Assets/de.lproj/Bwi.strings @@ -418,6 +418,12 @@ "user_avatar_view_accessibility_label" = "Profilbild"; "secrets_recovery_with_key_information_unlock_secure_backup_with_phrase" = "Zum Forfahren gib deinen Wiederherstellungsschlüssel ein."; +"create_room_type_federated" = "Für eine Föderation zulassen"; +"create_room_type_federated_subtext" = "(Raum kann von externen Organisationen mitgenutzt werden)"; +"create_room_deactivate_federation_for_room_error_title" = "Föderation aktiv"; +"create_room_deactivate_federation_for_room_error_text" = "Die Föderation konnte nicht deaktiviert werden, bitte versuche es später erneut."; +"create_room_deactivate_federation_alert_dismiss_button" = "Ok"; + // Mark: - Voice Messages "voice_message_release_to_send" = "Zur Aufnahme halten, zum Senden loslassen"; diff --git a/Riot/Assets/en.lproj/Bwi.strings b/Riot/Assets/en.lproj/Bwi.strings index 918ed64dd..5a6dc53b3 100644 --- a/Riot/Assets/en.lproj/Bwi.strings +++ b/Riot/Assets/en.lproj/Bwi.strings @@ -330,6 +330,12 @@ // Mark: - Room Creation "room_intro_cell_information_dm_sentence1_part1" = "This is the beginning of your direct message history with "; +"create_room_type_federated" = "Allow federation"; +"create_room_type_federated_subtext" = "(External organizations can join the room)"; +"create_room_deactivate_federation_for_room_error_title" = "Federation active"; +"create_room_deactivate_federation_for_room_error_text" = "The federation could not be disabled, please try again later."; +"create_room_deactivate_federation_alert_dismiss_button" = "Ok"; + // MARK: - Notification Times diff --git a/Riot/Generated/BWIStrings.swift b/Riot/Generated/BWIStrings.swift index c4f9e5672..c354012f1 100644 --- a/Riot/Generated/BWIStrings.swift +++ b/Riot/Generated/BWIStrings.swift @@ -723,10 +723,30 @@ public class BWIL10n: NSObject { public static var create: String { return BWIL10n.tr("Bwi", "create") } + /// Ok + public static var createRoomDeactivateFederationAlertDismissButton: String { + return BWIL10n.tr("Bwi", "create_room_deactivate_federation_alert_dismiss_button") + } + /// Die Föderation konnte nicht deaktiviert werden, bitte versuche es später erneut. + public static var createRoomDeactivateFederationForRoomErrorText: String { + return BWIL10n.tr("Bwi", "create_room_deactivate_federation_for_room_error_text") + } + /// Föderation aktiv + public static var createRoomDeactivateFederationForRoomErrorTitle: String { + return BWIL10n.tr("Bwi", "create_room_deactivate_federation_for_room_error_title") + } /// Thema public static var createRoomPlaceholderTopic: String { return BWIL10n.tr("Bwi", "create_room_placeholder_topic") } + /// Für eine Föderation zulassen + public static var createRoomTypeFederated: String { + return BWIL10n.tr("Bwi", "create_room_type_federated") + } + /// (Raum kann von externen Organisationen mitgenutzt werden) + public static var createRoomTypeFederatedSubtext: String { + return BWIL10n.tr("Bwi", "create_room_type_federated_subtext") + } /// Verifizierung abgebrochen. Du kannst sie erneut starten. public static var deviceVerificationCancelled: String { return BWIL10n.tr("Bwi", "device_verification_cancelled") diff --git a/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewAction.swift b/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewAction.swift index 8833b80b9..86f8aca50 100644 --- a/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewAction.swift +++ b/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewAction.swift @@ -22,6 +22,7 @@ import UIKit enum EnterNewRoomDetailsViewAction { case loadData case chooseAvatar(sourceView: UIView) + case updateServerACLRule // bwi: #4846 case removeAvatar case cancel case create diff --git a/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewController.swift b/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewController.swift index 7b545b1df..300a96c75 100644 --- a/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewController.swift +++ b/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewController.swift @@ -35,6 +35,7 @@ final class EnterNewRoomDetailsViewController: UIViewController { static let roomTopicMaximumNumberOfChars = 250 static let chooseAvatarTableViewCellHeight: CGFloat = 140 static let textViewTableViewCellHeight: CGFloat = 150 + static let bwiToggleWithLabelAndSubLabelCellReuseIdentifier = "bwiToggleWithLabelAndSubLabelCellReuseIdentifier" // bwi: #4846 } // MARK: - Properties @@ -74,11 +75,13 @@ final class EnterNewRoomDetailsViewController: UIViewController { case textField(tag: Int, placeholder: String?, delegate: UITextFieldDelegate?) case textView(tag: Int, placeholder: String?, delegate: UITextViewDelegate?) case withSwitch(isOn: Bool, onValueChanged: ((UISwitch) -> Void)?) + case withSwitchWithSubText(onValueChanged: ((Bool) -> Void)?) // bwi: #4846 } private struct Row { var type: RowType var text: String? + var subText: String? // bwi: #4846 var accessoryType: UITableViewCell.AccessoryType = .none var action: (() -> Void)? } @@ -169,12 +172,24 @@ final class EnterNewRoomDetailsViewController: UIViewController { } } } + // bwi: #4846 add serverACLRule on roomcreation + let row_4_3 = Row(type: .withSwitchWithSubText(onValueChanged: { [weak self] newToggleValue in + guard let self = self else { + return + } + viewModel.process(viewAction: .updateServerACLRule) + }), text: BWIL10n.createRoomTypeFederated, subText: BWIL10n.createRoomTypeFederatedSubtext) let rows: [Row] switch viewModel.actionType { case .createAndAddToSpace: rows = [row_4_0, row_4_1, row_4_2] case .createOnly: - rows = [row_4_0, row_4_2] + // bwi: #4846 add serverACLRule on roomcreation + if BWIBuildSettings.shared.isFederationEnabled { + rows = [row_4_0, row_4_2, row_4_3] + } else { + rows = [row_4_0, row_4_2] + } } var footer: String switch viewModel.roomCreationParameters.joinRule { @@ -348,6 +363,8 @@ final class EnterNewRoomDetailsViewController: UIViewController { mainTableView.register(cellType: TextFieldTableViewCell.self) mainTableView.register(cellType: TextViewTableViewCell.self) mainTableView.register(headerFooterViewType: TextViewTableViewHeaderFooterView.self) + // bwi: #4846 add serverACLRule on roomcreation + mainTableView.register(TableViewCellWithLabelSubLabelAndSwitch.self, forCellReuseIdentifier: Constants.bwiToggleWithLabelAndSubLabelCellReuseIdentifier) mainTableView.sectionHeaderHeight = UITableView.automaticDimension mainTableView.estimatedSectionHeaderHeight = 50 mainTableView.sectionFooterHeight = UITableView.automaticDimension @@ -490,6 +507,22 @@ extension EnterNewRoomDetailsViewController: UITableViewDataSource { cell.mxkSwitchTrailingConstraint.constant = 15 cell.update(theme: theme) + return cell + + // bwi: #4846 add toggle with subtext for federation + case .withSwitchWithSubText(let onValueChanged): + guard let cell = tableView.dequeueReusableCell(withIdentifier: Constants.bwiToggleWithLabelAndSubLabelCellReuseIdentifier, for: indexPath) as? TableViewCellWithLabelSubLabelAndSwitch else { + return UITableViewCell() + } + cell.selectionStyle = .none + if let viewModel = viewModel as? EnterNewRoomDetailsViewModel { + cell.setupView(parent: self, rootView: TableViewCellWithLabelSubLabelAndSwitchView(viewModel: viewModel, toggleText: row.text ?? "", subText: row.subText ?? "", onValueChanged: { newToggleValue in + onValueChanged?(newToggleValue) + })) + } else { + return UITableViewCell() + } + return cell } } @@ -600,6 +633,20 @@ extension EnterNewRoomDetailsViewController: EnterNewRoomDetailsViewModelViewDel func enterNewRoomDetailsViewModel(_ viewModel: EnterNewRoomDetailsViewModelType, didUpdateViewState viewSate: EnterNewRoomDetailsViewState) { self.render(viewState: viewSate) } + + // bwi: #4846 add serverACLRule on roomcreation + func showDeactivateFederationForRoomErrorAlert() { + let alert = UIAlertController(title: BWIL10n.createRoomDeactivateFederationForRoomErrorTitle, + message: BWIL10n.createRoomDeactivateFederationForRoomErrorText, + preferredStyle: .alert) + + let closeAction = UIAlertAction(title: BWIL10n.createRoomDeactivateFederationAlertDismissButton, style: .default, handler: { _ in + }) + + alert.addAction(closeAction) + + present(alert, animated: true, completion: nil) + } } // MARK: - UITextFieldDelegate diff --git a/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewModel.swift b/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewModel.swift index da12bf564..45244ec71 100644 --- a/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewModel.swift +++ b/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewModel.swift @@ -18,7 +18,7 @@ import Foundation -final class EnterNewRoomDetailsViewModel: EnterNewRoomDetailsViewModelType { +final class EnterNewRoomDetailsViewModel: EnterNewRoomDetailsViewModelType, ObservableObject { // MARK: - Properties @@ -36,6 +36,9 @@ final class EnterNewRoomDetailsViewModel: EnterNewRoomDetailsViewModelType { weak var coordinatorDelegate: EnterNewRoomDetailsViewModelCoordinatorDelegate? var roomCreationParameters: RoomCreationParameters = RoomCreationParameters() + // bwi: #4846 add serverACLRule on roomcreation + @Published var serverACLToggleValue: Bool = false + private(set) var viewState: EnterNewRoomDetailsViewState { didSet { self.viewDelegate?.enterNewRoomDetailsViewModel(self, didUpdateViewState: viewState) @@ -54,6 +57,11 @@ final class EnterNewRoomDetailsViewModel: EnterNewRoomDetailsViewModelType { roomCreationParameters.isEncrypted = session.vc_homeserverConfiguration().encryption.isE2EEByDefaultEnabled && RiotSettings.shared.roomCreationScreenRoomIsEncrypted roomCreationParameters.joinRule = RiotSettings.shared.roomCreationScreenRoomIsPublic ? .public : .private viewState = .loaded + + // bwi: #4846 add serverACLRule on roomcreation + if BWIBuildSettings.shared.isFederationEnabled { + updateServerACLRule() + } } deinit { @@ -75,6 +83,9 @@ final class EnterNewRoomDetailsViewModel: EnterNewRoomDetailsViewModelType { self.coordinatorDelegate?.enterNewRoomDetailsViewModelDidCancel(self) case .create: self.createRoom() + // bwi: #4846 add serverACLRule on roomcreation + case .updateServerACLRule: + self.updateServerACLRule() } } @@ -129,6 +140,7 @@ final class EnterNewRoomDetailsViewModel: EnterNewRoomDetailsViewModelType { parentRoomId: parentSpace?.spaceId, aliasLocalPart: fixRoomAlias(alias: roomCreationParameters.address), isEncrypted: roomCreationParameters.isEncrypted, + serverACLRule: roomCreationParameters.serverACLRule, completion: { response in switch response { case .success(let room): @@ -203,6 +215,25 @@ final class EnterNewRoomDetailsViewModel: EnterNewRoomDetailsViewModelType { } } } + + // bwi: #4846 add serverACLRule on roomcreation + private func updateServerACLRule() { + var allowedServers = [String]() + if serverACLToggleValue { + // Room is federated, allow other user from other servers to join the room + allowedServers.append("*") + } else { + if let host = URLComponents(string: session.homeserverWellknown.homeServer.baseUrl)?.host { + // Room is not federated, only user from the same homeserver can join the room + allowedServers.append(host) + } else { + serverACLToggleValue = true + self.viewDelegate?.showDeactivateFederationForRoomErrorAlert() + } + } + + roomCreationParameters.serverACLRule = MXRoomServerACLRule(allow: allowedServers) + } private func cancelOperations() { self.currentOperation?.cancel() diff --git a/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewModelType.swift b/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewModelType.swift index 95ccbcaa5..7ccd7f4e2 100644 --- a/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewModelType.swift +++ b/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewModelType.swift @@ -20,6 +20,7 @@ import Foundation protocol EnterNewRoomDetailsViewModelViewDelegate: AnyObject { func enterNewRoomDetailsViewModel(_ viewModel: EnterNewRoomDetailsViewModelType, didUpdateViewState viewSate: EnterNewRoomDetailsViewState) + func showDeactivateFederationForRoomErrorAlert() // bwi: #4846 } protocol EnterNewRoomDetailsViewModelCoordinatorDelegate: AnyObject { diff --git a/Riot/Modules/CreateRoom/Models/RoomCreationParameters.swift b/Riot/Modules/CreateRoom/Models/RoomCreationParameters.swift index 311234c27..f8ec01dbf 100644 --- a/Riot/Modules/CreateRoom/Models/RoomCreationParameters.swift +++ b/Riot/Modules/CreateRoom/Models/RoomCreationParameters.swift @@ -38,6 +38,7 @@ struct RoomCreationParameters { } } } + var serverACLRule: MXRoomServerACLRule? // bwi: #4846 var showInDirectory: Bool = false var isRoomSuggested: Bool = false diff --git a/bwi/CommonUI/TableViewCellWithLabelSubLabelAndSwitch.swift b/bwi/CommonUI/TableViewCellWithLabelSubLabelAndSwitch.swift new file mode 100644 index 000000000..8638dc1a2 --- /dev/null +++ b/bwi/CommonUI/TableViewCellWithLabelSubLabelAndSwitch.swift @@ -0,0 +1,113 @@ +// +/* + * Copyright (c) 2023 BWI GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import SwiftUI +import Foundation + +/// Helper class for making our SwiftUI view available to ObjectiveC +@objcMembers class TableViewCellWithLabelSubLabelAndSwitch: MXKTableViewCell { + private var parentViewController: UIViewController? + private let hostingController = UIHostingController(rootView: nil) + + private var theme: Theme! + + override init!(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public func setupView(parent: UIViewController, rootView: Content) { + self.parentViewController = parent + hostingController.rootView = rootView + + let shouldControllerMove = hostingController.parent != parent + if shouldControllerMove { + removeController() + parent.addChild(hostingController) + } + + if !self.contentView.subviews.contains(hostingController.view) { + self.contentView.addSubview(hostingController.view) + self.contentView.vc_addSubViewMatchingParentSafeArea(hostingController.view) + } + + if shouldControllerMove { + hostingController.didMove(toParent: parent) + } + + registerThemeServiceDidChangeThemeNotification() + update(theme: ThemeService.shared().theme) + } + + 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 update(theme: Theme) { + self.theme = theme + + hostingController.view.backgroundColor = theme.headerBackgroundColor + } + + deinit { + removeController() + } + + private func removeController() { + hostingController.willMove(toParent: nil) + hostingController.view.removeFromSuperview() + hostingController.removeFromParent() + parentViewController = nil + } +} + +struct TableViewCellWithLabelSubLabelAndSwitchView: View { + @ObservedObject var viewModel: EnterNewRoomDetailsViewModel + var toggleText: String + var subText: String + @Environment(\.theme) private var theme + + var onValueChanged: ((_: Bool) -> Void)? + + var body: some View { + VStack(alignment: .leading) { + HStack { + Toggle(isOn: $viewModel.serverACLToggleValue) { + Text(toggleText) + .lineLimit(nil) + } + .toggleStyle(SwitchToggleStyle(tint: Color(ThemeService.shared().theme.tintColor))) + .onChange(of: viewModel.serverACLToggleValue) { newToggleValue in + onValueChanged?(newToggleValue) + } + } + Text(subText) + .font(.system(size: 15)) + .foregroundColor(theme.colors.secondaryContent) + .lineLimit(nil) + } + .padding([.leading, .trailing], 16) + .padding([.top, .bottom], 8) + } +}