Skip to content
Snippets Groups Projects
Commit a5ab6975 authored by Dariusz Rybicki's avatar Dariusz Rybicki
Browse files

Implement joining a group

parent dd822557
No related branches found
No related tags found
2 merge requests!153Release 1.1.0,!151[Messenger example] join group
This commit is part of merge request !151. Comments created here will be created in the context of that merge request.
import AppCore import AppCore
import ComposableArchitecture import ComposableArchitecture
import Foundation import Foundation
import XXMessengerClient
import XXModels import XXModels
public struct GroupComponent: ReducerProtocol { public struct GroupComponent: ReducerProtocol {
public struct State: Equatable { public struct State: Equatable {
public init( public init(
groupId: XXModels.Group.ID, groupId: XXModels.Group.ID,
groupInfo: XXModels.GroupInfo? = nil groupInfo: XXModels.GroupInfo? = nil,
isJoining: Bool = false,
joinFailure: String? = nil
) { ) {
self.groupId = groupId self.groupId = groupId
self.groupInfo = groupInfo self.groupInfo = groupInfo
self.isJoining = isJoining
self.joinFailure = joinFailure
} }
public var groupId: XXModels.Group.ID public var groupId: XXModels.Group.ID
public var groupInfo: XXModels.GroupInfo? public var groupInfo: XXModels.GroupInfo?
public var isJoining: Bool
public var joinFailure: String?
} }
public enum Action: Equatable { public enum Action: Equatable {
case start case start
case didFetchGroupInfo(XXModels.GroupInfo?) case didFetchGroupInfo(XXModels.GroupInfo?)
case joinButtonTapped
case didJoin
case didFailToJoin(String)
} }
public init() {} public init() {}
@Dependency(\.app.messenger) var messenger: Messenger
@Dependency(\.app.dbManager.getDB) var db: DBManagerGetDB @Dependency(\.app.dbManager.getDB) var db: DBManagerGetDB
@Dependency(\.app.mainQueue) var mainQueue: AnySchedulerOf<DispatchQueue> @Dependency(\.app.mainQueue) var mainQueue: AnySchedulerOf<DispatchQueue>
@Dependency(\.app.bgQueue) var bgQueue: AnySchedulerOf<DispatchQueue> @Dependency(\.app.bgQueue) var bgQueue: AnySchedulerOf<DispatchQueue>
...@@ -47,6 +58,36 @@ public struct GroupComponent: ReducerProtocol { ...@@ -47,6 +58,36 @@ public struct GroupComponent: ReducerProtocol {
case .didFetchGroupInfo(let groupInfo): case .didFetchGroupInfo(let groupInfo):
state.groupInfo = groupInfo state.groupInfo = groupInfo
return .none return .none
case .joinButtonTapped:
guard let info = state.groupInfo else { return .none }
state.isJoining = true
state.joinFailure = nil
return Effect.result {
do {
let groupChat = try messenger.groupChat.tryGet()
try groupChat.joinGroup(serializedGroupData: info.group.serialized)
var group = info.group
group.authStatus = .participating
try db().saveGroup(group)
return .success(.didJoin)
} catch {
return .success(.didFailToJoin(error.localizedDescription))
}
}
.subscribe(on: bgQueue)
.receive(on: mainQueue)
.eraseToEffect()
case .didJoin:
state.isJoining = false
state.joinFailure = nil
return .none
case .didFailToJoin(let failure):
state.isJoining = false
state.joinFailure = failure
return .none
} }
} }
} }
......
...@@ -16,9 +16,13 @@ public struct GroupView: View { ...@@ -16,9 +16,13 @@ public struct GroupView: View {
struct ViewState: Equatable { struct ViewState: Equatable {
init(state: Component.State) { init(state: Component.State) {
info = state.groupInfo info = state.groupInfo
isJoining = state.isJoining
joinFailure = state.joinFailure
} }
var info: XXModels.GroupInfo? var info: XXModels.GroupInfo?
var isJoining: Bool
var joinFailure: String?
} }
public var body: some View { public var body: some View {
...@@ -41,6 +45,27 @@ public struct GroupView: View { ...@@ -41,6 +45,27 @@ public struct GroupView: View {
Section("Status") { Section("Status") {
GroupAuthStatusView(info.group.authStatus) GroupAuthStatusView(info.group.authStatus)
if case .pending = info.group.authStatus {
Button {
viewStore.send(.joinButtonTapped)
} label: {
HStack {
Text("Join")
Spacer()
if viewStore.isJoining {
ProgressView()
} else {
Image(systemName: "play.fill")
}
}
}
.disabled(viewStore.isJoining)
}
if let failure = viewStore.joinFailure {
Text(failure)
}
} }
} }
} }
......
...@@ -2,12 +2,16 @@ import Combine ...@@ -2,12 +2,16 @@ import Combine
import ComposableArchitecture import ComposableArchitecture
import CustomDump import CustomDump
import XCTest import XCTest
import XXClient
import XXMessengerClient
import XXModels import XXModels
@testable import GroupFeature @testable import GroupFeature
final class GroupComponentTests: XCTestCase { final class GroupComponentTests: XCTestCase {
enum Action: Equatable { enum Action: Equatable {
case didFetchGroupInfos(GroupInfo.Query) case didFetchGroupInfos(GroupInfo.Query)
case didJoinGroup(Data)
case didSaveGroup(XXModels.Group)
} }
var actions: [Action]! var actions: [Action]!
...@@ -57,6 +61,85 @@ final class GroupComponentTests: XCTestCase { ...@@ -57,6 +61,85 @@ final class GroupComponentTests: XCTestCase {
groupInfosSubject.send(completion: .finished) groupInfosSubject.send(completion: .finished)
} }
func testJoinGroup() {
var groupInfo = GroupInfo.stub()
groupInfo.group.authStatus = .pending
let store = TestStore(
initialState: GroupComponent.State(
groupId: groupInfo.group.id,
groupInfo: groupInfo
),
reducer: GroupComponent()
)
store.dependencies.app.mainQueue = .immediate
store.dependencies.app.bgQueue = .immediate
store.dependencies.app.messenger.groupChat.get = {
var groupChat: GroupChat = .unimplemented
groupChat.joinGroup.run = { serializedGroupData in
self.actions.append(.didJoinGroup(serializedGroupData))
}
return groupChat
}
store.dependencies.app.dbManager.getDB.run = {
var db: Database = .unimplemented
db.saveGroup.run = { group in
self.actions.append(.didSaveGroup(group))
return group
}
return db
}
store.send(.joinButtonTapped) {
$0.isJoining = true
}
XCTAssertNoDifference(actions, [
.didJoinGroup(groupInfo.group.serialized),
.didSaveGroup({
var group = groupInfo.group
group.authStatus = .participating
return group
}())
])
store.receive(.didJoin) {
$0.isJoining = false
}
}
func testJoinGroupFailure() {
let groupInfo = GroupInfo.stub()
struct Failure: Error {}
let failure = Failure()
let store = TestStore(
initialState: GroupComponent.State(
groupId: groupInfo.group.id,
groupInfo: groupInfo
),
reducer: GroupComponent()
)
store.dependencies.app.mainQueue = .immediate
store.dependencies.app.bgQueue = .immediate
store.dependencies.app.messenger.groupChat.get = {
var groupChat: GroupChat = .unimplemented
groupChat.joinGroup.run = { _ in throw failure }
return groupChat
}
store.send(.joinButtonTapped) {
$0.isJoining = true
}
store.receive(.didFailToJoin(failure.localizedDescription)) {
$0.isJoining = false
$0.joinFailure = failure.localizedDescription
}
}
} }
private extension XXModels.GroupInfo { private extension XXModels.GroupInfo {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment