mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-10-31 16:24:11 +00:00
Add invites badges (#819)
This commit is contained in:
@@ -21,6 +21,7 @@ import SwiftUI
|
|||||||
final class AppSettings: ObservableObject {
|
final class AppSettings: ObservableObject {
|
||||||
private enum UserDefaultsKeys: String {
|
private enum UserDefaultsKeys: String {
|
||||||
case lastVersionLaunched
|
case lastVersionLaunched
|
||||||
|
case seenInvites
|
||||||
case timelineStyle
|
case timelineStyle
|
||||||
case enableAnalytics
|
case enableAnalytics
|
||||||
case enableInAppNotifications
|
case enableInAppNotifications
|
||||||
@@ -69,6 +70,11 @@ final class AppSettings: ObservableObject {
|
|||||||
@UserPreference(key: UserDefaultsKeys.lastVersionLaunched, storageType: .userDefaults(store))
|
@UserPreference(key: UserDefaultsKeys.lastVersionLaunched, storageType: .userDefaults(store))
|
||||||
var lastVersionLaunched: String?
|
var lastVersionLaunched: String?
|
||||||
|
|
||||||
|
/// The Set of room identifiers of invites that the user already saw in the invites list.
|
||||||
|
/// This Set is being used to implement badges for unread invites.
|
||||||
|
@UserPreference(key: UserDefaultsKeys.seenInvites, defaultValue: [], storageType: .userDefaults(store))
|
||||||
|
var seenInvites: Set<String>
|
||||||
|
|
||||||
/// The default homeserver address used. This is intentionally a string without a scheme
|
/// The default homeserver address used. This is intentionally a string without a scheme
|
||||||
/// so that it can be passed to Rust as a ServerName for well-known discovery.
|
/// so that it can be passed to Rust as a ServerName for well-known discovery.
|
||||||
let defaultHomeserverAddress = "matrix.org"
|
let defaultHomeserverAddress = "matrix.org"
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ struct HomeScreenViewState: BindableState {
|
|||||||
let invitePermalink: URL?
|
let invitePermalink: URL?
|
||||||
|
|
||||||
var hasPendingInvitations = false
|
var hasPendingInvitations = false
|
||||||
|
var hasUnreadPendingInvitations = false
|
||||||
|
|
||||||
var startChatFlowEnabled: Bool {
|
var startChatFlowEnabled: Bool {
|
||||||
ServiceLocator.shared.settings.startChatFlowEnabled
|
ServiceLocator.shared.settings.startChatFlowEnabled
|
||||||
|
|||||||
@@ -134,10 +134,18 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
invitesSummaryProvider.countPublisher
|
invitesSummaryProvider.roomListPublisher
|
||||||
.map { $0 > 0 }
|
.combineLatest(ServiceLocator.shared.settings.$seenInvites)
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.weakAssign(to: \.state.hasPendingInvitations, on: self)
|
.sink { [weak self] summaries, readInvites in
|
||||||
|
self?.state.hasPendingInvitations = !summaries.isEmpty
|
||||||
|
self?.state.hasUnreadPendingInvitations = summaries.contains(where: {
|
||||||
|
guard let roomId = $0.id else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return !readInvites.contains(roomId)
|
||||||
|
})
|
||||||
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
updateRooms()
|
updateRooms()
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ struct HomeScreen: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if context.viewState.hasPendingInvitations, ServiceLocator.shared.settings.invitesFlowEnabled {
|
if context.viewState.hasPendingInvitations, ServiceLocator.shared.settings.invitesFlowEnabled {
|
||||||
HomeScreenInvitesButton(title: L10n.actionInvitesList, hasBadge: true) {
|
HomeScreenInvitesButton(title: L10n.actionInvitesList, hasBadge: context.viewState.hasUnreadPendingInvitations) {
|
||||||
context.send(viewAction: .selectInvites)
|
context.send(viewAction: .selectInvites)
|
||||||
}
|
}
|
||||||
.padding(.trailing, 16)
|
.padding(.trailing, 16)
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ struct InvitesViewStateBindings {
|
|||||||
struct InvitesRoomDetails {
|
struct InvitesRoomDetails {
|
||||||
let roomDetails: RoomSummaryDetails
|
let roomDetails: RoomSummaryDetails
|
||||||
var inviter: RoomMemberProxyProtocol?
|
var inviter: RoomMemberProxyProtocol?
|
||||||
|
var isUnread: Bool
|
||||||
|
|
||||||
var isDirect: Bool {
|
var isDirect: Bool {
|
||||||
roomDetails.isDirect
|
roomDetails.isDirect
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ typealias InvitesViewModelType = StateStoreViewModel<InvitesViewState, InvitesVi
|
|||||||
class InvitesViewModel: InvitesViewModelType, InvitesViewModelProtocol {
|
class InvitesViewModel: InvitesViewModelType, InvitesViewModelProtocol {
|
||||||
private var actionsSubject: PassthroughSubject<InvitesViewModelAction, Never> = .init()
|
private var actionsSubject: PassthroughSubject<InvitesViewModelAction, Never> = .init()
|
||||||
private let userSession: UserSessionProtocol
|
private let userSession: UserSessionProtocol
|
||||||
|
private let previouslySeenInvites: Set<String>
|
||||||
|
|
||||||
var actions: AnyPublisher<InvitesViewModelAction, Never> {
|
var actions: AnyPublisher<InvitesViewModelAction, Never> {
|
||||||
actionsSubject.eraseToAnyPublisher()
|
actionsSubject.eraseToAnyPublisher()
|
||||||
@@ -29,6 +30,7 @@ class InvitesViewModel: InvitesViewModelType, InvitesViewModelProtocol {
|
|||||||
|
|
||||||
init(userSession: UserSessionProtocol) {
|
init(userSession: UserSessionProtocol) {
|
||||||
self.userSession = userSession
|
self.userSession = userSession
|
||||||
|
previouslySeenInvites = ServiceLocator.shared.settings.seenInvites
|
||||||
super.init(initialViewState: InvitesViewState(), imageProvider: userSession.mediaProvider)
|
super.init(initialViewState: InvitesViewState(), imageProvider: userSession.mediaProvider)
|
||||||
setupSubscriptions()
|
setupSubscriptions()
|
||||||
}
|
}
|
||||||
@@ -65,7 +67,8 @@ class InvitesViewModel: InvitesViewModelType, InvitesViewModelProtocol {
|
|||||||
.sink { [weak self] roomSummaries in
|
.sink { [weak self] roomSummaries in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
|
|
||||||
let invites = roomSummaries.invites
|
let invites = self.buildInvites(from: roomSummaries)
|
||||||
|
ServiceLocator.shared.settings.seenInvites = Set(invites.map(\.roomDetails.id))
|
||||||
self.state.invites = invites
|
self.state.invites = invites
|
||||||
|
|
||||||
for invite in invites {
|
for invite in invites {
|
||||||
@@ -75,6 +78,15 @@ class InvitesViewModel: InvitesViewModelType, InvitesViewModelProtocol {
|
|||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func buildInvites(from summaries: [RoomSummary]) -> [InvitesRoomDetails] {
|
||||||
|
summaries.compactMap { summary in
|
||||||
|
guard case .filled(let details) = summary else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return InvitesRoomDetails(roomDetails: details, isUnread: !previouslySeenInvites.contains(details.id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func fetchInviter(for roomID: String) {
|
private func fetchInviter(for roomID: String) {
|
||||||
Task {
|
Task {
|
||||||
guard let room: RoomProxyProtocol = await self.clientProxy.roomForIdentifier(roomID) else {
|
guard let room: RoomProxyProtocol = await self.clientProxy.roomForIdentifier(roomID) else {
|
||||||
@@ -154,14 +166,3 @@ class InvitesViewModel: InvitesViewModelType, InvitesViewModelProtocol {
|
|||||||
message: L10n.errorUnknown)
|
message: L10n.errorUnknown)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension Array where Element == RoomSummary {
|
|
||||||
var invites: [InvitesRoomDetails] {
|
|
||||||
compactMap { summary in
|
|
||||||
guard case .filled(let details) = summary else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return .init(roomDetails: details)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ struct InvitesScreenCell: View {
|
|||||||
let declineAction: () -> Void
|
let declineAction: () -> Void
|
||||||
|
|
||||||
private let verticalInsets = 16.0
|
private let verticalInsets = 16.0
|
||||||
|
@ScaledMetric private var badgeSize = 12.0
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(alignment: .top, spacing: 16) {
|
HStack(alignment: .top, spacing: 16) {
|
||||||
@@ -42,27 +43,26 @@ struct InvitesScreenCell: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.top, verticalInsets)
|
.padding(.top, verticalInsets)
|
||||||
.padding(.horizontal, 12)
|
.padding(.horizontal, 16)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
private var mainContent: some View {
|
private var mainContent: some View {
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 6) {
|
||||||
Text(title)
|
HStack(alignment: .firstTextBaseline, spacing: 12) {
|
||||||
.font(.element.headline)
|
textualContent
|
||||||
.foregroundColor(.element.primaryContent)
|
|
||||||
|
if invite.isUnread {
|
||||||
if let subtitle {
|
badge
|
||||||
Text(subtitle)
|
}
|
||||||
.font(.compound.bodyMD)
|
|
||||||
.foregroundColor(.compound.textPlaceholder)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inviterView
|
inviterView
|
||||||
|
|
||||||
buttons
|
buttons
|
||||||
.padding(.top, 10)
|
.padding(.trailing, 26)
|
||||||
|
.padding(.top, 8)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,6 +82,22 @@ struct InvitesScreenCell: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var textualContent: some View {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Text(title)
|
||||||
|
.font(.element.headline)
|
||||||
|
.foregroundColor(.element.primaryContent)
|
||||||
|
|
||||||
|
if let subtitle {
|
||||||
|
Text(subtitle)
|
||||||
|
.font(.compound.bodyMD)
|
||||||
|
.foregroundColor(.compound.textPlaceholder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
}
|
||||||
|
|
||||||
private var buttons: some View {
|
private var buttons: some View {
|
||||||
HStack(spacing: 12) {
|
HStack(spacing: 12) {
|
||||||
Button(L10n.actionDecline, action: declineAction)
|
Button(L10n.actionDecline, action: declineAction)
|
||||||
@@ -127,6 +143,12 @@ struct InvitesScreenCell: View {
|
|||||||
}
|
}
|
||||||
return attributedString
|
return attributedString
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var badge: some View {
|
||||||
|
Circle()
|
||||||
|
.frame(width: badgeSize, height: badgeSize)
|
||||||
|
.foregroundColor(.element.brand)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct InvitesScreenCell_Previews: PreviewProvider {
|
struct InvitesScreenCell_Previews: PreviewProvider {
|
||||||
@@ -139,6 +161,10 @@ struct InvitesScreenCell_Previews: PreviewProvider {
|
|||||||
|
|
||||||
InvitesScreenCell(invite: .room(alias: "#footest:somewhere.org"), imageProvider: MockMediaProvider(), acceptAction: { }, declineAction: { })
|
InvitesScreenCell(invite: .room(alias: "#footest:somewhere.org"), imageProvider: MockMediaProvider(), acceptAction: { }, declineAction: { })
|
||||||
.previewDisplayName("Aliased room")
|
.previewDisplayName("Aliased room")
|
||||||
|
|
||||||
|
InvitesScreenCell(invite: .room(alias: "#footest:somewhere.org"), imageProvider: MockMediaProvider(), acceptAction: { }, declineAction: { })
|
||||||
|
.dynamicTypeSize(.accessibility1)
|
||||||
|
.previewDisplayName("Aliased room (AX1)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +183,7 @@ private extension InvitesRoomDetails {
|
|||||||
inviter.displayName = "Jack"
|
inviter.displayName = "Jack"
|
||||||
inviter.userID = "@jack:somewhere.com"
|
inviter.userID = "@jack:somewhere.com"
|
||||||
|
|
||||||
return .init(roomDetails: dmRoom, inviter: inviter)
|
return .init(roomDetails: dmRoom, inviter: inviter, isUnread: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func room(alias: String?) -> InvitesRoomDetails {
|
static func room(alias: String?) -> InvitesRoomDetails {
|
||||||
@@ -174,6 +200,6 @@ private extension InvitesRoomDetails {
|
|||||||
inviter.userID = "@jack:somewhere.com"
|
inviter.userID = "@jack:somewhere.com"
|
||||||
inviter.avatarURL = nil
|
inviter.avatarURL = nil
|
||||||
|
|
||||||
return .init(roomDetails: dmRoom, inviter: inviter)
|
return .init(roomDetails: dmRoom, inviter: inviter, isUnread: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ class UserSessionFlowCoordinator: CoordinatorProtocol {
|
|||||||
self.presentStartChat(animated: animated)
|
self.presentStartChat(animated: animated)
|
||||||
case (.startChatScreen, .dismissedStartChatScreen, .roomList):
|
case (.startChatScreen, .dismissedStartChatScreen, .roomList):
|
||||||
break
|
break
|
||||||
|
|
||||||
case (.roomList, .showInvitesScreen, .invitesScreen):
|
case (.roomList, .showInvitesScreen, .invitesScreen):
|
||||||
self.presentInvitesList(animated: animated)
|
self.presentInvitesList(animated: animated)
|
||||||
case (.invitesScreen, .closedInvitesScreen, .roomList):
|
case (.invitesScreen, .closedInvitesScreen, .roomList):
|
||||||
|
|||||||
@@ -364,7 +364,20 @@ class MockScreen: Identifiable {
|
|||||||
mediaProvider: MockMediaProvider()))
|
mediaProvider: MockMediaProvider()))
|
||||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||||
return navigationStackCoordinator
|
return navigationStackCoordinator
|
||||||
|
case .invitesWithBadges:
|
||||||
|
ServiceLocator.shared.settings.seenInvites = Set([RoomSummary].mockInvites.dropFirst(1).compactMap(\.id))
|
||||||
|
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||||
|
let clientProxy = MockClientProxy(userID: "@mock:client.com")
|
||||||
|
clientProxy.roomInviter = RoomMemberProxyMock.mockCharlie
|
||||||
|
let summaryProvider = MockRoomSummaryProvider(state: .loaded(.mockInvites))
|
||||||
|
clientProxy.visibleRoomsSummaryProvider = summaryProvider
|
||||||
|
clientProxy.invitesSummaryProvider = summaryProvider
|
||||||
|
|
||||||
|
let coordinator = InvitesCoordinator(parameters: .init(userSession: MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider())))
|
||||||
|
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||||
|
return navigationStackCoordinator
|
||||||
case .invites:
|
case .invites:
|
||||||
|
ServiceLocator.shared.settings.seenInvites = Set([RoomSummary].mockInvites.compactMap(\.id))
|
||||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||||
let clientProxy = MockClientProxy(userID: "@mock:client.com")
|
let clientProxy = MockClientProxy(userID: "@mock:client.com")
|
||||||
clientProxy.roomInviter = RoomMemberProxyMock.mockCharlie
|
clientProxy.roomInviter = RoomMemberProxyMock.mockCharlie
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ enum UITestsScreenIdentifier: String {
|
|||||||
case startChat
|
case startChat
|
||||||
case startChatWithSearchResults
|
case startChatWithSearchResults
|
||||||
case invites
|
case invites
|
||||||
|
case invitesWithBadges
|
||||||
case invitesNoInvites
|
case invitesNoInvites
|
||||||
case inviteUsers
|
case inviteUsers
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,11 +18,16 @@ import ElementX
|
|||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
class InvitesScreenUITests: XCTestCase {
|
class InvitesScreenUITests: XCTestCase {
|
||||||
func testMixedInvites() {
|
func testInvitesWithNoBadges() {
|
||||||
let app = Application.launch(.invites)
|
let app = Application.launch(.invites)
|
||||||
app.assertScreenshot(.invites)
|
app.assertScreenshot(.invites)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testInvitesWithBadges() {
|
||||||
|
let app = Application.launch(.invitesWithBadges)
|
||||||
|
app.assertScreenshot(.invitesWithBadges)
|
||||||
|
}
|
||||||
|
|
||||||
func testNoInvites() {
|
func testNoInvites() {
|
||||||
let app = Application.launch(.invitesNoInvites)
|
let app = Application.launch(.invitesNoInvites)
|
||||||
XCTAssertTrue(app.staticTexts[A11yIdentifiers.invitesScreen.noInvites].exists)
|
XCTAssertTrue(app.staticTexts[A11yIdentifiers.invitesScreen.noInvites].exists)
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:227793880d0425d98c0dbb1d8a5f6439432383d9a9506030b9f4838bf9c0ce4d
|
oid sha256:eceabc10203dde514204abb2e9f842ec10f18ca0216c5932bda372ec8ee727a5
|
||||||
size 188175
|
size 188129
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:8b7e4017ecafaca01e672701182dd6695573b021bc684c285e76214f311b35f6
|
oid sha256:11505b76e0455ca673ccd716893e2fe771e34666dcedacbf998bc5c3d0a550ec
|
||||||
size 98707
|
size 98629
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:0f23e94a8e39bc7adaa06136aea223b6112626f77425ff3db021c5496bff3f28
|
||||||
|
size 98983
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:da9cae78d8cf7592e20aedd014801c912e0feb90a1a9365b6ed99bea5b11bc1f
|
oid sha256:85e5dfea417d1130ed453e1c483034b2e1f717a1418b164a4f9856bfe5a175fc
|
||||||
size 356615
|
size 353862
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:947fbd514af15087b850aae85371920b9f829fe92c0bbb4ca62ac97e95b6a92d
|
oid sha256:7cebc6d0ae1b65debda82b586a4033d83a5c21e9d125855c8544ac70631aaee3
|
||||||
size 116523
|
size 112839
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:a0cdb8a004a3ca661aae6a649fdbdd0c8990f6bcc8760cf222b05490a7785fa7
|
||||||
|
size 114708
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:3221977873adc6942d96a283f8625a835277201db4897a72fd6942a8191ed221
|
oid sha256:2b33ca3e65d765ae24b1a0223198e7b42ed0686a825629f5b0f8bc93b357d6b3
|
||||||
size 233543
|
size 233512
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:5e4ce0956d2c9499c1d4f6e84041377263ed74a959196e4f60574c8a7f694855
|
oid sha256:1ecf3a404f38c4c4fa376c236e50a1160f4d6eadbcd77145f156a81cec245e12
|
||||||
size 104489
|
size 104268
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:161305b820d23a1e29a5d80f1a4a306c68a65af933d039dcd50f49d888a1f552
|
||||||
|
size 104655
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:0fe7a52291eb74567927f6da56a533f1dfc616a899d1174e65d36198ca65ca11
|
oid sha256:ef8a8fe4a9ae1bdd437a2618574962d1c57175e1893131293d6fa13f9a9dd1c1
|
||||||
size 461856
|
size 522278
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:dd3942891d03d9e67861fefd90406d51bb3189782307fc0335a1fba1c4754c0b
|
oid sha256:dc43c5d83a75af17a28421ffed097f67d1ffc026b8d0d36c5cae1f4a1c012336
|
||||||
size 121889
|
size 137190
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:7de304c4383db02fde81ede2169907a2394c27dd49a1238fb91626f110d44b3f
|
||||||
|
size 137714
|
||||||
@@ -53,7 +53,7 @@ class InvitesViewModelTests: XCTestCase {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
setupViewModel(roomSummaries: invites)
|
setupViewModel(roomSummaries: invites)
|
||||||
context.send(viewAction: .accept(.init(roomDetails: details)))
|
context.send(viewAction: .accept(.init(roomDetails: details, isUnread: false)))
|
||||||
let action: InvitesViewModelAction? = await viewModel.actions.values.first()
|
let action: InvitesViewModelAction? = await viewModel.actions.values.first()
|
||||||
guard case .openRoom(let roomID) = action else {
|
guard case .openRoom(let roomID) = action else {
|
||||||
XCTFail("Wrong view model action")
|
XCTFail("Wrong view model action")
|
||||||
@@ -69,7 +69,7 @@ class InvitesViewModelTests: XCTestCase {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
setupViewModel(roomSummaries: invites)
|
setupViewModel(roomSummaries: invites)
|
||||||
context.send(viewAction: .decline(.init(roomDetails: details)))
|
context.send(viewAction: .decline(.init(roomDetails: details, isUnread: false)))
|
||||||
XCTAssertNotNil(context.alertInfo)
|
XCTAssertNotNil(context.alertInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
Added unread badges in the invites list.
|
||||||
Reference in New Issue
Block a user