Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//
// Wire
// Copyright (C) 2026 Wire Swiss GmbH
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/.
//

import Foundation

/// Tracks, per conversation, whether the user has closed the `ConversationViewerAccessBanner`.
///
/// This state is kept in memory only, for the lifetime of the app process, and is intentionally
/// never persisted (no `UserDefaults`, disk, or keychain), so the banner reappears again on next launch.
@MainActor
public final class ConversationViewerAccessBannerDismissalStore {
public static let shared = ConversationViewerAccessBannerDismissalStore()
typealias CellName = String

private var dismissedCellNames: Set<String> = []
Comment on lines +28 to +30

private init() {}

public func isDismissed(forCellName cellName: String) -> Bool {
dismissedCellNames.contains(cellName)
}

public func markDismissed(forCellName cellName: String) {
dismissedCellNames.insert(cellName)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ package struct FilesContentView<Toolbar: ToolbarContent, Sheet: View>: View {
if viewModel.showReadOnlyBanner {
ConversationViewerAccessBanner(backgroundColor: ColorTheme.Buttons.Secondary
.disabledOutline) {
viewModel.showReadOnlyBanner = false
viewModel.dismissReadOnlyBanner()
}.padding(.bottom, viewModel.isOffline ? 0 : 15)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,19 @@ package final class FilesViewModel: ObservableObject {
selfUser = conversations.flatMap(\.participants).first(where: \.isSelfUser)

if let selfUser {
showReadOnlyBanner = !isBrowsing && selfUser.role == .viewer && isDrivePermissionsFlagEnabled
let isDismissed = cellName
.map(ConversationViewerAccessBannerDismissalStore.shared.isDismissed(forCellName:)) ?? false
let isViewer = selfUser.role == .viewer
showReadOnlyBanner = isDrivePermissionsFlagEnabled && !isBrowsing && isViewer && !isDismissed &&
!isRecycleBin
Comment on lines +276 to +280
}
}

func dismissReadOnlyBanner() {
showReadOnlyBanner = false

if let cellName {
ConversationViewerAccessBannerDismissalStore.shared.markDismissed(forCellName: cellName)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,13 +178,23 @@ final class ConversationInputBarViewController: UIViewController,
.isGuest(in: conversation) && DeveloperFlag.enableDrivePermissions.isOn
}

private var shouldShowDriveViewerBanner: Bool {
showDriveViewerBanner &&
!ConversationViewerAccessBannerDismissalStore.shared
.isDismissed(forCellName: conversation.wireDriveCellName)
}

// MARK: subviews

lazy var inputBar: InputBar = {
let driveConfiguration: InputBar.DriveConfiguration? = if conversation.isWireDriveEnabled {
.init(cellName: conversation.wireDriveCellName, showBanner: showDriveViewerBanner)
} else {
nil
}
Comment on lines 189 to +194
let inputBar = InputBar(
buttons: inputBarButtons,
isWireDriveEnabled: conversation.isWireDriveEnabled,
showDriveViewerBanner: showDriveViewerBanner
driveConfiguration: driveConfiguration
)
if !mediaShareRestrictionManager.canUseSpellChecking {
inputBar.textView.spellCheckingType = .no
Expand Down Expand Up @@ -504,6 +514,7 @@ final class ConversationInputBarViewController: UIViewController,

override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
inputBar.hideDriveViewerBannerIfDismissed()
updateButtonStates()
inputBar.updateReturnKey()
inputBar.updateEphemeralState()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,13 @@ final class InputBar: UIView {

typealias ConversationInputBar = L10n.Localizable.Conversation.InputBar

struct DriveConfiguration {
let cellName: String
let showBanner: Bool
}

private let inputBarVerticalInset: CGFloat = 34
private let isWireDriveEnabled: Bool
private let showDriveViewerBanner: Bool
private let driveConfiguration: DriveConfiguration?
static let rightIconSize: CGFloat = 32
private let textViewFont = FontSpec.normalRegularFont.font!

Expand Down Expand Up @@ -239,14 +243,16 @@ final class InputBar: UIView {
textView.isScrollEnabled = true
}

required init(buttons: [UIButton], isWireDriveEnabled: Bool, showDriveViewerBanner: Bool) {
required init(
buttons: [UIButton],
driveConfiguration: DriveConfiguration?
) {
self.buttonsView = InputBarButtonsView(buttons: buttons)
self.secondaryButtonsView = InputBarSecondaryButtonsView(
editBarView: editingView,
markdownBarView: markdownView
)
self.isWireDriveEnabled = isWireDriveEnabled
self.showDriveViewerBanner = showDriveViewerBanner
self.driveConfiguration = driveConfiguration

super.init(frame: CGRect.zero)

Expand All @@ -259,7 +265,7 @@ final class InputBar: UIView {
addSubview(inputContainer)

// Viewer access banner
if showDriveViewerBanner {
if let driveConfiguration, driveConfiguration.showBanner {
inputContainer.addArrangedSubview(driveViewerAccessBanner)
[driveViewerAccessBanner, self].forEach {
$0.layer.cornerRadius = 12
Expand All @@ -272,7 +278,7 @@ final class InputBar: UIView {
inputContainer.addArrangedSubview(upperContainer)
[leftAccessoryView, textView, rightAccessoryStackView].forEach { upperContainer.addSubview($0) }

if isWireDriveEnabled {
if driveConfiguration != nil {
inputContainer.addArrangedSubview(attachmentsContainer)
}

Expand Down Expand Up @@ -366,7 +372,7 @@ final class InputBar: UIView {
textView.backgroundColor = .clear

markdownView.delegate = textView
if !showDriveViewerBanner {
if let driveConfiguration, !driveConfiguration.showBanner {
addBorder(for: .top)
}
Comment on lines +375 to 377
updateReturnKey()
Expand All @@ -390,7 +396,7 @@ final class InputBar: UIView {
buttonInnerContainer
].forEach { $0.translatesAutoresizingMaskIntoConstraints = false }

if isWireDriveEnabled {
if driveConfiguration != nil {
NSLayoutConstraint.activate([
attachmentsContainer.widthAnchor.constraint(equalTo: inputContainer.widthAnchor),
attachmentsContainer.heightAnchor.constraint(equalToConstant: 82)
Expand Down Expand Up @@ -457,7 +463,7 @@ final class InputBar: UIView {
rowTopInsetConstraint
])

if showDriveViewerBanner {
if let driveConfiguration, driveConfiguration.showBanner {
driveViewerAccessBanner.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
driveViewerAccessBanner.leadingAnchor.constraint(equalTo: inputContainer.leadingAnchor),
Expand Down Expand Up @@ -710,13 +716,25 @@ final class InputBar: UIView {
}

private func onDriveViewerAccessBannerClosed() {
guard let driveConfiguration else { return }
ConversationViewerAccessBannerDismissalStore.shared.markDismissed(forCellName: driveConfiguration.cellName)
inputContainer.removeArrangedSubview(driveViewerAccessBanner)
driveViewerAccessBanner.removeFromSuperview()
addBorder(for: .top)
layer.cornerRadius = 0
layer.maskedCorners = []
clipsToBounds = false
}

/// Hides the drive viewer access banner if it was dismissed elsewhere (e.g. from the Shared
/// Drive screen) while this `InputBar` instance was already alive and showing it.
func hideDriveViewerBannerIfDismissed() {
guard driveViewerAccessBanner.superview != nil,
let driveConfiguration,
ConversationViewerAccessBannerDismissalStore.shared.isDismissed(forCellName: driveConfiguration.cellName)
else { return }
onDriveViewerAccessBannerClosed()
}
}

extension InputBar {
Expand Down
Loading