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

Implement joining a group

parent dd822557
Branches
Tags
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 ComposableArchitecture
import Foundation
import XXMessengerClient
import XXModels
public struct GroupComponent: ReducerProtocol {
public struct State: Equatable {
public init(
groupId: XXModels.Group.ID,
groupInfo: XXModels.GroupInfo? = nil
groupInfo: XXModels.GroupInfo? = nil,
isJoining: Bool = false,
joinFailure: String? = nil
) {
self.groupId = groupId
self.groupInfo = groupInfo
self.isJoining = isJoining
self.joinFailure = joinFailure
}
public var groupId: XXModels.Group.ID
public var groupInfo: XXModels.GroupInfo?
public var isJoining: Bool
public var joinFailure: String?
}
public enum Action: Equatable {
case start
case didFetchGroupInfo(XXModels.GroupInfo?)
case joinButtonTapped
case didJoin
case didFailToJoin(String)
}
public init() {}
@Dependency(\.app.messenger) var messenger: Messenger
@Dependency(\.app.dbManager.getDB) var db: DBManagerGetDB
@Dependency(\.app.mainQueue) var mainQueue: AnySchedulerOf<DispatchQueue>
@Dependency(\.app.bgQueue) var bgQueue: AnySchedulerOf<DispatchQueue>
......@@ -47,6 +58,36 @@ public struct GroupComponent: ReducerProtocol {
case .didFetchGroupInfo(let groupInfo):
state.groupInfo = groupInfo
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 {
struct ViewState: Equatable {
init(state: Component.State) {
info = state.groupInfo
isJoining = state.isJoining
joinFailure = state.joinFailure
}
var info: XXModels.GroupInfo?
var isJoining: Bool
var joinFailure: String?
}
public var body: some View {
......@@ -41,6 +45,27 @@ public struct GroupView: View {
Section("Status") {
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
import ComposableArchitecture
import CustomDump
import XCTest
import XXClient
import XXMessengerClient
import XXModels
@testable import GroupFeature
final class GroupComponentTests: XCTestCase {
enum Action: Equatable {
case didFetchGroupInfos(GroupInfo.Query)
case didJoinGroup(Data)
case didSaveGroup(XXModels.Group)
}
var actions: [Action]!
......@@ -57,6 +61,85 @@ final class GroupComponentTests: XCTestCase {
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 {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment