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
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.
Finish editing this message first!
Please register or to comment