Knock Requests banner display logic (#3550)

* added the join rule to the info

* update SDK and added logic to display the banner

in the room screen

* added the logic to display the banner
This commit is contained in:
Mauro 2024-11-25 13:14:02 +01:00 committed by GitHub
parent 9180bac13c
commit 26e07f2457
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 131 additions and 42 deletions

View File

@ -8195,7 +8195,7 @@
repositoryURL = "https://github.com/element-hq/matrix-rust-components-swift";
requirement = {
kind = exactVersion;
version = 1.0.73;
version = 1.0.74;
};
};
701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */ = {

View File

@ -149,8 +149,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/element-hq/matrix-rust-components-swift",
"state" : {
"revision" : "b3f97292695e8d63469c0d3ec608eb74423c6a2e",
"version" : "1.0.73"
"revision" : "66d32e79ae20dd31201cd16eced53cfcc0c3239d",
"version" : "1.0.74"
}
},
{

View File

@ -59,7 +59,8 @@ extension RoomInfo {
numUnreadMessages: 0,
numUnreadNotifications: 0,
numUnreadMentions: 0,
pinnedEventIds: [])
pinnedEventIds: [],
joinRule: .invite)
}
}

View File

@ -39,6 +39,7 @@ struct JoinedRoomProxyMockConfiguration {
var canUserPin = true
var shouldUseAutoUpdatingTimeline = false
var joinRule: JoinRule?
}
extension JoinedRoomProxyMock {
@ -166,6 +167,7 @@ extension RoomInfo {
numUnreadMessages: 0,
numUnreadNotifications: 0,
numUnreadMentions: 0,
pinnedEventIds: Array(configuration.pinnedEventIDs))
pinnedEventIds: Array(configuration.pinnedEventIDs),
joinRule: configuration.joinRule)
}
}

View File

@ -57,6 +57,7 @@ extension RoomInfo {
numUnreadMessages: 0,
numUnreadNotifications: 0,
numUnreadMentions: 0,
pinnedEventIds: [])
pinnedEventIds: [],
joinRule: .knock)
}
}

View File

@ -11,9 +11,17 @@ enum KnockRequestsListScreenViewModelAction { }
struct KnockRequestsListScreenViewState: BindableState {
var requests: [KnockRequestCellInfo] = []
var canAccept = false
var canDecline = false
var canBan = false
// If you are in this view one of these must have been true so by default we assume all of them to be true
var canAccept = true
var canDecline = true
var canBan = true
var isKnockableRoom = true
// If all the permissions are denied or the join rule changes while we are in the view
// we want to stop displaying any request
var shouldDisplayRequests: Bool {
!requests.isEmpty && isKnockableRoom && (canAccept || canDecline || canBan)
}
}
enum KnockRequestsListScreenViewAction {

View File

@ -22,6 +22,7 @@ class KnockRequestsListScreenViewModel: KnockRequestsListScreenViewModelType, Kn
self.roomProxy = roomProxy
super.init(initialViewState: KnockRequestsListScreenViewState(), mediaProvider: mediaProvider)
updateRoomInfo(roomInfo: roomProxy.infoPublisher.value)
Task {
await updatePermissions()
}
@ -48,13 +49,23 @@ class KnockRequestsListScreenViewModel: KnockRequestsListScreenViewModelType, Kn
private func setupSubscriptions() {
roomProxy.infoPublisher
.throttle(for: .milliseconds(200), scheduler: DispatchQueue.main, latest: true)
.sink { [weak self] _ in
.receive(on: DispatchQueue.main)
.sink { [weak self] roomInfo in
self?.updateRoomInfo(roomInfo: roomInfo)
Task { await self?.updatePermissions() }
}
.store(in: &cancellables)
}
private func updateRoomInfo(roomInfo: RoomInfoProxy) {
switch roomInfo.joinRule {
case .knock, .knockRestricted:
state.isKnockableRoom = true
default:
state.isKnockableRoom = false
}
}
private func updatePermissions() async {
state.canAccept = await (try? roomProxy.canUserInvite(userID: roomProxy.ownUserID).get()) == true
state.canDecline = await (try? roomProxy.canUserKick(userID: roomProxy.ownUserID).get()) == true

View File

@ -17,12 +17,12 @@ struct KnockRequestsListScreen: View {
.navigationTitle(L10n.screenKnockRequestsListTitle)
.background(.compound.bgCanvasDefault)
.overlay {
if context.viewState.requests.isEmpty {
if !context.viewState.shouldDisplayRequests {
KnockRequestsListEmptyStateView()
}
}
.safeAreaInset(edge: .bottom) {
if !context.viewState.requests.isEmpty {
if context.viewState.shouldDisplayRequests {
acceptAllButton
}
}
@ -32,14 +32,16 @@ struct KnockRequestsListScreen: View {
private var mainContent: some View {
ScrollView {
LazyVStack(spacing: 0) {
ForEach(context.viewState.requests) { requestInfo in
ListRow(kind: .custom {
KnockRequestCell(cellInfo: requestInfo,
mediaProvider: context.mediaProvider,
onAccept: context.viewState.canAccept ? onAccept : nil,
onDecline: context.viewState.canDecline ? onDecline : nil,
onDeclineAndBan: context.viewState.canBan ? onDeclineAndBan : nil)
})
if context.viewState.shouldDisplayRequests {
ForEach(context.viewState.requests) { requestInfo in
ListRow(kind: .custom {
KnockRequestCell(cellInfo: requestInfo,
mediaProvider: context.mediaProvider,
onAccept: context.viewState.canAccept ? onAccept : nil,
onDecline: context.viewState.canDecline ? onDecline : nil,
onDeclineAndBan: context.viewState.canBan ? onDeclineAndBan : nil)
})
}
}
}
.padding(.top, 40)
@ -79,10 +81,7 @@ struct KnockRequestsListScreen_Previews: PreviewProvider, TestablePreview {
// swiftlint:disable:next line_length
.init(id: "@bob:matrix.org", displayName: "Bob", avatarUrl: nil, timestamp: "Now", reason: "Hello this one is a very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very long reason"),
.init(id: "@charlie:matrix.org", displayName: "Charlie", avatarUrl: nil, timestamp: "Now", reason: nil),
.init(id: "@dan:matrix.org", displayName: "Dan", avatarUrl: nil, timestamp: "Now", reason: "Hello! It's a me! Dan!")],
canAccept: true,
canDecline: true,
canBan: true))
.init(id: "@dan:matrix.org", displayName: "Dan", avatarUrl: nil, timestamp: "Now", reason: "Hello! It's a me! Dan!")]))
static var previews: some View {
NavigationStack {

View File

@ -49,9 +49,10 @@ struct RoomDetailsScreenViewState: BindableState {
var canJoinCall = false
var pinnedEventsActionState = RoomDetailsScreenPinnedEventsActionState.loading
var knockingEnabled = false
var isKnockableRoom = false
var canSeeKnockingRequests: Bool {
knockingEnabled && dmRecipient == nil && (canInviteUsers || canKickUsers || canBanUsers)
knockingEnabled && dmRecipient == nil && isKnockableRoom && (canInviteUsers || canKickUsers || canBanUsers)
}
var canEdit: Bool {

View File

@ -189,6 +189,12 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
state.topicSummary = topic?.unattributedStringByReplacingNewlinesWithSpaces()
state.joinedMembersCount = roomInfo.joinedMembersCount
state.bindings.isFavourite = roomInfo.isFavourite
switch roomInfo.joinRule {
case .knock, .knockRestricted:
state.isKnockableRoom = true
default:
state.isKnockableRoom = false
}
}
private func fetchMembersIfNeeded() async {

View File

@ -323,7 +323,8 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview {
isDirect: false,
isEncrypted: true,
canonicalAlias: "#alias:domain.com",
members: members))
members: members,
joinRule: .knock))
var notificationSettingsProxyMockConfiguration = NotificationSettingsProxyMockConfiguration()
notificationSettingsProxyMockConfiguration.roomMode.isDefault = false
@ -378,7 +379,8 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview {
name: "Room A",
isDirect: false,
isEncrypted: false,
members: members))
members: members,
joinRule: .knock))
let notificationSettingsProxy = NotificationSettingsProxyMock(with: .init())
return RoomDetailsScreenViewModel(roomProxy: roomProxy,

View File

@ -39,6 +39,18 @@ struct RoomScreenViewState: BindableState {
var hasOngoingCall: Bool
var shouldShowCallButton = true
var isKnockingEnabled = false
var isKnockableRoom = false
var canAcceptKnocks = false
var canDeclineKnocks = false
var canBan = false
// TODO: We still don't know how to get these, but these will be the non already seen knock requests of the room, for now we are using this as a mock for testing purposes
var unseenKnockRequests: [KnockRequestInfo] = [.init(displayName: "Alice", avatarURL: nil, userID: "@alice:matrix.org", reason: "Helloooo")]
var shouldSeeKnockRequests: Bool {
isKnockingEnabled && isKnockableRoom && !unseenKnockRequests.isEmpty && (canAcceptKnocks || canDeclineKnocks || canBan)
}
var footerDetails: RoomScreenFooterViewDetails?
var bindings: RoomScreenViewStateBindings

View File

@ -117,6 +117,10 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
// MARK: - Private
private func setupSubscriptions(ongoingCallRoomIDPublisher: CurrentValuePublisher<String?, Never>) {
appSettings.$knockingEnabled
.weakAssign(to: \.state.isKnockingEnabled, on: self)
.store(in: &cancellables)
let roomInfoSubscription = roomProxy
.infoPublisher
@ -236,10 +240,18 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
state.pinnedEventsBannerState = .loading(numbersOfEvents: pinnedEventIDs.count)
}
let userID = roomProxy.ownUserID
if case let .success(permission) = await roomProxy.canUserJoinCall(userID: userID) {
state.canJoinCall = permission
switch (roomProxy.isEncryptedOneToOneRoom, roomInfo.joinRule) {
case (false, .knock), (false, .knockRestricted):
state.isKnockableRoom = true
default:
state.isKnockableRoom = false
}
let ownUserID = roomProxy.ownUserID
state.canJoinCall = await (try? roomProxy.canUserJoinCall(userID: ownUserID).get()) == true
state.canAcceptKnocks = await (try? roomProxy.canUserInvite(userID: ownUserID).get()) == true
state.canDeclineKnocks = await (try? roomProxy.canUserKick(userID: ownUserID).get()) == true
state.canBan = await (try? roomProxy.canUserBan(userID: ownUserID).get()) == true
}
private func setupPinnedEventsTimelineProviderIfNeeded() {

View File

@ -29,12 +29,11 @@ struct RoomScreen: View {
timeline
.background(Color.compound.bgCanvasDefault.ignoresSafeArea())
.overlay(alignment: .top) {
Group {
if roomContext.viewState.shouldShowPinnedEventsBanner {
pinnedItemsBanner
}
}
.animation(.elementDefault, value: roomContext.viewState.shouldShowPinnedEventsBanner)
pinnedItemsBanner
}
// This can overlay on top of the pinnedItemsBanner
.overlay(alignment: .top) {
knockRequestsBanner
}
.safeAreaInset(edge: .bottom, spacing: 0) {
VStack(spacing: 0) {
@ -119,11 +118,45 @@ struct RoomScreen: View {
}
}
@ViewBuilder
private var pinnedItemsBanner: some View {
PinnedItemsBannerView(state: roomContext.viewState.pinnedEventsBannerState,
onMainButtonTap: { roomContext.send(viewAction: .tappedPinnedEventsBanner) },
onViewAllButtonTap: { roomContext.send(viewAction: .viewAllPins) })
.transition(.move(edge: .top))
Group {
if roomContext.viewState.shouldShowPinnedEventsBanner {
PinnedItemsBannerView(state: roomContext.viewState.pinnedEventsBannerState,
onMainButtonTap: { roomContext.send(viewAction: .tappedPinnedEventsBanner) },
onViewAllButtonTap: { roomContext.send(viewAction: .viewAllPins) })
.transition(.move(edge: .top))
}
}
.animation(.elementDefault, value: roomContext.viewState.shouldShowPinnedEventsBanner)
}
@ViewBuilder
private var knockRequestsBanner: some View {
Group {
if roomContext.viewState.shouldSeeKnockRequests {
KnockRequestsBannerView(requests: roomContext.viewState.unseenKnockRequests,
onDismiss: dismissKnockRequestsBanner,
onAccept: roomContext.viewState.canAcceptKnocks ? acceptKnockRequest : nil,
onViewAll: onViewAllKnockRequests,
mediaProvider: roomContext.mediaProvider)
.padding(.top, 16)
.transition(.move(edge: .top))
}
}
.animation(.elementDefault, value: roomContext.viewState.shouldSeeKnockRequests)
}
private func dismissKnockRequestsBanner() {
// TODO: Implement
}
private func acceptKnockRequest(userID: String) {
// TODO: Implement
}
private func onViewAllKnockRequests() {
// TODO: Implement
}
private var scrollToBottomButton: some View {

View File

@ -65,6 +65,7 @@ struct RoomInfoProxy: BaseRoomInfoProxyProtocol {
var unreadNotificationsCount: UInt { UInt(roomInfo.numUnreadNotifications) }
var unreadMentionsCount: UInt { UInt(roomInfo.numUnreadMentions) }
var pinnedEventIDs: Set<String> { Set(roomInfo.pinnedEventIds) }
var joinRule: JoinRule? { roomInfo.joinRule }
}
struct RoomPreviewInfoProxy: BaseRoomInfoProxyProtocol {

View File

@ -61,7 +61,7 @@ packages:
# Element/Matrix dependencies
MatrixRustSDK:
url: https://github.com/element-hq/matrix-rust-components-swift
exactVersion: 1.0.73
exactVersion: 1.0.74
# path: ../matrix-rust-sdk
Compound:
url: https://github.com/element-hq/compound-ios