mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-06 12:28:29 +00:00
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:
@@ -8195,7 +8195,7 @@
|
|||||||
repositoryURL = "https://github.com/element-hq/matrix-rust-components-swift";
|
repositoryURL = "https://github.com/element-hq/matrix-rust-components-swift";
|
||||||
requirement = {
|
requirement = {
|
||||||
kind = exactVersion;
|
kind = exactVersion;
|
||||||
version = 1.0.73;
|
version = 1.0.74;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */ = {
|
701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */ = {
|
||||||
|
|||||||
@@ -149,8 +149,8 @@
|
|||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/element-hq/matrix-rust-components-swift",
|
"location" : "https://github.com/element-hq/matrix-rust-components-swift",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "b3f97292695e8d63469c0d3ec608eb74423c6a2e",
|
"revision" : "66d32e79ae20dd31201cd16eced53cfcc0c3239d",
|
||||||
"version" : "1.0.73"
|
"version" : "1.0.74"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -59,7 +59,8 @@ extension RoomInfo {
|
|||||||
numUnreadMessages: 0,
|
numUnreadMessages: 0,
|
||||||
numUnreadNotifications: 0,
|
numUnreadNotifications: 0,
|
||||||
numUnreadMentions: 0,
|
numUnreadMentions: 0,
|
||||||
pinnedEventIds: [])
|
pinnedEventIds: [],
|
||||||
|
joinRule: .invite)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ struct JoinedRoomProxyMockConfiguration {
|
|||||||
var canUserPin = true
|
var canUserPin = true
|
||||||
|
|
||||||
var shouldUseAutoUpdatingTimeline = false
|
var shouldUseAutoUpdatingTimeline = false
|
||||||
|
var joinRule: JoinRule?
|
||||||
}
|
}
|
||||||
|
|
||||||
extension JoinedRoomProxyMock {
|
extension JoinedRoomProxyMock {
|
||||||
@@ -166,6 +167,7 @@ extension RoomInfo {
|
|||||||
numUnreadMessages: 0,
|
numUnreadMessages: 0,
|
||||||
numUnreadNotifications: 0,
|
numUnreadNotifications: 0,
|
||||||
numUnreadMentions: 0,
|
numUnreadMentions: 0,
|
||||||
pinnedEventIds: Array(configuration.pinnedEventIDs))
|
pinnedEventIds: Array(configuration.pinnedEventIDs),
|
||||||
|
joinRule: configuration.joinRule)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ extension RoomInfo {
|
|||||||
numUnreadMessages: 0,
|
numUnreadMessages: 0,
|
||||||
numUnreadNotifications: 0,
|
numUnreadNotifications: 0,
|
||||||
numUnreadMentions: 0,
|
numUnreadMentions: 0,
|
||||||
pinnedEventIds: [])
|
pinnedEventIds: [],
|
||||||
|
joinRule: .knock)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-3
@@ -11,9 +11,17 @@ enum KnockRequestsListScreenViewModelAction { }
|
|||||||
|
|
||||||
struct KnockRequestsListScreenViewState: BindableState {
|
struct KnockRequestsListScreenViewState: BindableState {
|
||||||
var requests: [KnockRequestCellInfo] = []
|
var requests: [KnockRequestCellInfo] = []
|
||||||
var canAccept = 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 canDecline = false
|
var canAccept = true
|
||||||
var canBan = false
|
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 {
|
enum KnockRequestsListScreenViewAction {
|
||||||
|
|||||||
+13
-2
@@ -22,6 +22,7 @@ class KnockRequestsListScreenViewModel: KnockRequestsListScreenViewModelType, Kn
|
|||||||
self.roomProxy = roomProxy
|
self.roomProxy = roomProxy
|
||||||
super.init(initialViewState: KnockRequestsListScreenViewState(), mediaProvider: mediaProvider)
|
super.init(initialViewState: KnockRequestsListScreenViewState(), mediaProvider: mediaProvider)
|
||||||
|
|
||||||
|
updateRoomInfo(roomInfo: roomProxy.infoPublisher.value)
|
||||||
Task {
|
Task {
|
||||||
await updatePermissions()
|
await updatePermissions()
|
||||||
}
|
}
|
||||||
@@ -48,13 +49,23 @@ class KnockRequestsListScreenViewModel: KnockRequestsListScreenViewModelType, Kn
|
|||||||
|
|
||||||
private func setupSubscriptions() {
|
private func setupSubscriptions() {
|
||||||
roomProxy.infoPublisher
|
roomProxy.infoPublisher
|
||||||
.throttle(for: .milliseconds(200), scheduler: DispatchQueue.main, latest: true)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] _ in
|
.sink { [weak self] roomInfo in
|
||||||
|
self?.updateRoomInfo(roomInfo: roomInfo)
|
||||||
Task { await self?.updatePermissions() }
|
Task { await self?.updatePermissions() }
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.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 {
|
private func updatePermissions() async {
|
||||||
state.canAccept = await (try? roomProxy.canUserInvite(userID: roomProxy.ownUserID).get()) == true
|
state.canAccept = await (try? roomProxy.canUserInvite(userID: roomProxy.ownUserID).get()) == true
|
||||||
state.canDecline = await (try? roomProxy.canUserKick(userID: roomProxy.ownUserID).get()) == true
|
state.canDecline = await (try? roomProxy.canUserKick(userID: roomProxy.ownUserID).get()) == true
|
||||||
|
|||||||
+13
-14
@@ -17,12 +17,12 @@ struct KnockRequestsListScreen: View {
|
|||||||
.navigationTitle(L10n.screenKnockRequestsListTitle)
|
.navigationTitle(L10n.screenKnockRequestsListTitle)
|
||||||
.background(.compound.bgCanvasDefault)
|
.background(.compound.bgCanvasDefault)
|
||||||
.overlay {
|
.overlay {
|
||||||
if context.viewState.requests.isEmpty {
|
if !context.viewState.shouldDisplayRequests {
|
||||||
KnockRequestsListEmptyStateView()
|
KnockRequestsListEmptyStateView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.safeAreaInset(edge: .bottom) {
|
.safeAreaInset(edge: .bottom) {
|
||||||
if !context.viewState.requests.isEmpty {
|
if context.viewState.shouldDisplayRequests {
|
||||||
acceptAllButton
|
acceptAllButton
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,14 +32,16 @@ struct KnockRequestsListScreen: View {
|
|||||||
private var mainContent: some View {
|
private var mainContent: some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
LazyVStack(spacing: 0) {
|
LazyVStack(spacing: 0) {
|
||||||
ForEach(context.viewState.requests) { requestInfo in
|
if context.viewState.shouldDisplayRequests {
|
||||||
ListRow(kind: .custom {
|
ForEach(context.viewState.requests) { requestInfo in
|
||||||
KnockRequestCell(cellInfo: requestInfo,
|
ListRow(kind: .custom {
|
||||||
mediaProvider: context.mediaProvider,
|
KnockRequestCell(cellInfo: requestInfo,
|
||||||
onAccept: context.viewState.canAccept ? onAccept : nil,
|
mediaProvider: context.mediaProvider,
|
||||||
onDecline: context.viewState.canDecline ? onDecline : nil,
|
onAccept: context.viewState.canAccept ? onAccept : nil,
|
||||||
onDeclineAndBan: context.viewState.canBan ? onDeclineAndBan : nil)
|
onDecline: context.viewState.canDecline ? onDecline : nil,
|
||||||
})
|
onDeclineAndBan: context.viewState.canBan ? onDeclineAndBan : nil)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.top, 40)
|
.padding(.top, 40)
|
||||||
@@ -79,10 +81,7 @@ struct KnockRequestsListScreen_Previews: PreviewProvider, TestablePreview {
|
|||||||
// swiftlint:disable:next line_length
|
// 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: "@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: "@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!")],
|
.init(id: "@dan:matrix.org", displayName: "Dan", avatarUrl: nil, timestamp: "Now", reason: "Hello! It's a me! Dan!")]))
|
||||||
canAccept: true,
|
|
||||||
canDecline: true,
|
|
||||||
canBan: true))
|
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
|
|||||||
@@ -49,9 +49,10 @@ struct RoomDetailsScreenViewState: BindableState {
|
|||||||
var canJoinCall = false
|
var canJoinCall = false
|
||||||
var pinnedEventsActionState = RoomDetailsScreenPinnedEventsActionState.loading
|
var pinnedEventsActionState = RoomDetailsScreenPinnedEventsActionState.loading
|
||||||
var knockingEnabled = false
|
var knockingEnabled = false
|
||||||
|
var isKnockableRoom = false
|
||||||
|
|
||||||
var canSeeKnockingRequests: Bool {
|
var canSeeKnockingRequests: Bool {
|
||||||
knockingEnabled && dmRecipient == nil && (canInviteUsers || canKickUsers || canBanUsers)
|
knockingEnabled && dmRecipient == nil && isKnockableRoom && (canInviteUsers || canKickUsers || canBanUsers)
|
||||||
}
|
}
|
||||||
|
|
||||||
var canEdit: Bool {
|
var canEdit: Bool {
|
||||||
|
|||||||
@@ -189,6 +189,12 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
|
|||||||
state.topicSummary = topic?.unattributedStringByReplacingNewlinesWithSpaces()
|
state.topicSummary = topic?.unattributedStringByReplacingNewlinesWithSpaces()
|
||||||
state.joinedMembersCount = roomInfo.joinedMembersCount
|
state.joinedMembersCount = roomInfo.joinedMembersCount
|
||||||
state.bindings.isFavourite = roomInfo.isFavourite
|
state.bindings.isFavourite = roomInfo.isFavourite
|
||||||
|
switch roomInfo.joinRule {
|
||||||
|
case .knock, .knockRestricted:
|
||||||
|
state.isKnockableRoom = true
|
||||||
|
default:
|
||||||
|
state.isKnockableRoom = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func fetchMembersIfNeeded() async {
|
private func fetchMembersIfNeeded() async {
|
||||||
|
|||||||
@@ -323,7 +323,8 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview {
|
|||||||
isDirect: false,
|
isDirect: false,
|
||||||
isEncrypted: true,
|
isEncrypted: true,
|
||||||
canonicalAlias: "#alias:domain.com",
|
canonicalAlias: "#alias:domain.com",
|
||||||
members: members))
|
members: members,
|
||||||
|
joinRule: .knock))
|
||||||
|
|
||||||
var notificationSettingsProxyMockConfiguration = NotificationSettingsProxyMockConfiguration()
|
var notificationSettingsProxyMockConfiguration = NotificationSettingsProxyMockConfiguration()
|
||||||
notificationSettingsProxyMockConfiguration.roomMode.isDefault = false
|
notificationSettingsProxyMockConfiguration.roomMode.isDefault = false
|
||||||
@@ -378,7 +379,8 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview {
|
|||||||
name: "Room A",
|
name: "Room A",
|
||||||
isDirect: false,
|
isDirect: false,
|
||||||
isEncrypted: false,
|
isEncrypted: false,
|
||||||
members: members))
|
members: members,
|
||||||
|
joinRule: .knock))
|
||||||
let notificationSettingsProxy = NotificationSettingsProxyMock(with: .init())
|
let notificationSettingsProxy = NotificationSettingsProxyMock(with: .init())
|
||||||
|
|
||||||
return RoomDetailsScreenViewModel(roomProxy: roomProxy,
|
return RoomDetailsScreenViewModel(roomProxy: roomProxy,
|
||||||
|
|||||||
@@ -39,6 +39,18 @@ struct RoomScreenViewState: BindableState {
|
|||||||
var hasOngoingCall: Bool
|
var hasOngoingCall: Bool
|
||||||
var shouldShowCallButton = true
|
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 footerDetails: RoomScreenFooterViewDetails?
|
||||||
|
|
||||||
var bindings: RoomScreenViewStateBindings
|
var bindings: RoomScreenViewStateBindings
|
||||||
|
|||||||
@@ -117,6 +117,10 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
private func setupSubscriptions(ongoingCallRoomIDPublisher: CurrentValuePublisher<String?, Never>) {
|
private func setupSubscriptions(ongoingCallRoomIDPublisher: CurrentValuePublisher<String?, Never>) {
|
||||||
|
appSettings.$knockingEnabled
|
||||||
|
.weakAssign(to: \.state.isKnockingEnabled, on: self)
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
let roomInfoSubscription = roomProxy
|
let roomInfoSubscription = roomProxy
|
||||||
.infoPublisher
|
.infoPublisher
|
||||||
|
|
||||||
@@ -236,10 +240,18 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
state.pinnedEventsBannerState = .loading(numbersOfEvents: pinnedEventIDs.count)
|
state.pinnedEventsBannerState = .loading(numbersOfEvents: pinnedEventIDs.count)
|
||||||
}
|
}
|
||||||
|
|
||||||
let userID = roomProxy.ownUserID
|
switch (roomProxy.isEncryptedOneToOneRoom, roomInfo.joinRule) {
|
||||||
if case let .success(permission) = await roomProxy.canUserJoinCall(userID: userID) {
|
case (false, .knock), (false, .knockRestricted):
|
||||||
state.canJoinCall = permission
|
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() {
|
private func setupPinnedEventsTimelineProviderIfNeeded() {
|
||||||
|
|||||||
@@ -29,12 +29,11 @@ struct RoomScreen: View {
|
|||||||
timeline
|
timeline
|
||||||
.background(Color.compound.bgCanvasDefault.ignoresSafeArea())
|
.background(Color.compound.bgCanvasDefault.ignoresSafeArea())
|
||||||
.overlay(alignment: .top) {
|
.overlay(alignment: .top) {
|
||||||
Group {
|
pinnedItemsBanner
|
||||||
if roomContext.viewState.shouldShowPinnedEventsBanner {
|
}
|
||||||
pinnedItemsBanner
|
// This can overlay on top of the pinnedItemsBanner
|
||||||
}
|
.overlay(alignment: .top) {
|
||||||
}
|
knockRequestsBanner
|
||||||
.animation(.elementDefault, value: roomContext.viewState.shouldShowPinnedEventsBanner)
|
|
||||||
}
|
}
|
||||||
.safeAreaInset(edge: .bottom, spacing: 0) {
|
.safeAreaInset(edge: .bottom, spacing: 0) {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
@@ -119,11 +118,45 @@ struct RoomScreen: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
private var pinnedItemsBanner: some View {
|
private var pinnedItemsBanner: some View {
|
||||||
PinnedItemsBannerView(state: roomContext.viewState.pinnedEventsBannerState,
|
Group {
|
||||||
onMainButtonTap: { roomContext.send(viewAction: .tappedPinnedEventsBanner) },
|
if roomContext.viewState.shouldShowPinnedEventsBanner {
|
||||||
onViewAllButtonTap: { roomContext.send(viewAction: .viewAllPins) })
|
PinnedItemsBannerView(state: roomContext.viewState.pinnedEventsBannerState,
|
||||||
.transition(.move(edge: .top))
|
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 {
|
private var scrollToBottomButton: some View {
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ struct RoomInfoProxy: BaseRoomInfoProxyProtocol {
|
|||||||
var unreadNotificationsCount: UInt { UInt(roomInfo.numUnreadNotifications) }
|
var unreadNotificationsCount: UInt { UInt(roomInfo.numUnreadNotifications) }
|
||||||
var unreadMentionsCount: UInt { UInt(roomInfo.numUnreadMentions) }
|
var unreadMentionsCount: UInt { UInt(roomInfo.numUnreadMentions) }
|
||||||
var pinnedEventIDs: Set<String> { Set(roomInfo.pinnedEventIds) }
|
var pinnedEventIDs: Set<String> { Set(roomInfo.pinnedEventIds) }
|
||||||
|
var joinRule: JoinRule? { roomInfo.joinRule }
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RoomPreviewInfoProxy: BaseRoomInfoProxyProtocol {
|
struct RoomPreviewInfoProxy: BaseRoomInfoProxyProtocol {
|
||||||
|
|||||||
+1
-1
@@ -61,7 +61,7 @@ packages:
|
|||||||
# Element/Matrix dependencies
|
# Element/Matrix dependencies
|
||||||
MatrixRustSDK:
|
MatrixRustSDK:
|
||||||
url: https://github.com/element-hq/matrix-rust-components-swift
|
url: https://github.com/element-hq/matrix-rust-components-swift
|
||||||
exactVersion: 1.0.73
|
exactVersion: 1.0.74
|
||||||
# path: ../matrix-rust-sdk
|
# path: ../matrix-rust-sdk
|
||||||
Compound:
|
Compound:
|
||||||
url: https://github.com/element-hq/compound-ios
|
url: https://github.com/element-hq/compound-ios
|
||||||
|
|||||||
Reference in New Issue
Block a user