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
Loading
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