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

Implement creating new group

parent 172ddcad
No related branches found
No related tags found
2 merge requests!153Release 1.1.0,!149[Messenger example] create new group
...@@ -8,31 +8,43 @@ public struct NewGroupComponent: ReducerProtocol { ...@@ -8,31 +8,43 @@ public struct NewGroupComponent: ReducerProtocol {
public struct State: Equatable { public struct State: Equatable {
public enum Field: String, Hashable { public enum Field: String, Hashable {
case name case name
case message
} }
public init( public init(
contacts: IdentifiedArrayOf<XXModels.Contact> = [], contacts: IdentifiedArrayOf<XXModels.Contact> = [],
members: IdentifiedArrayOf<XXModels.Contact> = [], members: IdentifiedArrayOf<XXModels.Contact> = [],
name: String = "", name: String = "",
focusedField: Field? = nil message: String = "",
focusedField: Field? = nil,
isCreating: Bool = false,
failure: String? = nil
) { ) {
self.contacts = contacts self.contacts = contacts
self.members = members self.members = members
self.name = name self.name = name
self.message = message
self.focusedField = focusedField self.focusedField = focusedField
self.isCreating = isCreating
self.failure = failure
} }
public var contacts: IdentifiedArrayOf<XXModels.Contact> public var contacts: IdentifiedArrayOf<XXModels.Contact>
public var members: IdentifiedArrayOf<XXModels.Contact> public var members: IdentifiedArrayOf<XXModels.Contact>
@BindableState public var name: String @BindableState public var name: String
@BindableState public var message: String
@BindableState public var focusedField: Field? @BindableState public var focusedField: Field?
public var isCreating: Bool
public var failure: String?
} }
public enum Action: Equatable, BindableAction { public enum Action: Equatable, BindableAction {
case start case start
case didFetchContacts([XXModels.Contact]) case didFetchContacts([XXModels.Contact])
case didSelectContact(XXModels.Contact) case didSelectContact(XXModels.Contact)
case createButtonTapped
case didFinish case didFinish
case didFail(String)
case binding(BindingAction<State>) case binding(BindingAction<State>)
} }
...@@ -42,6 +54,7 @@ public struct NewGroupComponent: ReducerProtocol { ...@@ -42,6 +54,7 @@ public struct NewGroupComponent: ReducerProtocol {
@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>
@Dependency(\.date) var date: DateGenerator
public var body: some ReducerProtocol<State, Action> { public var body: some ReducerProtocol<State, Action> {
BindingReducer() BindingReducer()
...@@ -71,7 +84,61 @@ public struct NewGroupComponent: ReducerProtocol { ...@@ -71,7 +84,61 @@ public struct NewGroupComponent: ReducerProtocol {
} }
return .none return .none
case .createButtonTapped:
state.focusedField = nil
state.isCreating = true
state.failure = nil
return Effect.result { [state] in
do {
let groupChat = try messenger.groupChat.tryGet()
let report = try groupChat.makeGroup(
membership: state.members.map(\.id),
message: state.message.data(using: .utf8)!,
name: state.name.data(using: .utf8)!
)
let myContactId = try messenger.e2e.tryGet().getContact().getId()
let group = XXModels.Group(
id: report.id,
name: state.name,
leaderId: myContactId,
createdAt: date(),
authStatus: .participating,
serialized: try report.encode()
)
try db().saveGroup(group)
if state.message.isEmpty == false {
try db().saveMessage(.init(
senderId: myContactId,
recipientId: nil,
groupId: group.id,
date: group.createdAt,
status: .sent,
isUnread: false,
text: state.message
))
}
try state.members.map {
GroupMember(groupId: group.id, contactId: $0.id)
}.forEach {
try db().saveGroupMember($0)
}
return .success(.didFinish)
} catch {
return .success(.didFail(error.localizedDescription))
}
}
.subscribe(on: bgQueue)
.receive(on: mainQueue)
.eraseToEffect()
case .didFinish: case .didFinish:
state.isCreating = false
state.failure = nil
return .none
case .didFail(let failure):
state.isCreating = false
state.failure = failure
return .none return .none
case .binding(_): case .binding(_):
......
...@@ -19,13 +19,19 @@ public struct NewGroupView: View { ...@@ -19,13 +19,19 @@ public struct NewGroupView: View {
contacts = state.contacts contacts = state.contacts
members = state.members members = state.members
name = state.name name = state.name
message = state.message
focusedField = state.focusedField focusedField = state.focusedField
isCreating = state.isCreating
failure = state.failure
} }
var contacts: IdentifiedArrayOf<XXModels.Contact> var contacts: IdentifiedArrayOf<XXModels.Contact>
var members: IdentifiedArrayOf<XXModels.Contact> var members: IdentifiedArrayOf<XXModels.Contact>
var name: String var name: String
var message: String
var focusedField: Component.State.Field? var focusedField: Component.State.Field?
var isCreating: Bool
var failure: String?
} }
public var body: some View { public var body: some View {
...@@ -34,6 +40,13 @@ public struct NewGroupView: View { ...@@ -34,6 +40,13 @@ public struct NewGroupView: View {
Section { Section {
membersView(viewStore) membersView(viewStore)
nameView(viewStore) nameView(viewStore)
messageView(viewStore)
}
Section {
createButton(viewStore)
if let failure = viewStore.failure {
Text(failure)
}
} }
} }
.navigationTitle("New Group") .navigationTitle("New Group")
...@@ -61,6 +74,7 @@ public struct NewGroupView: View { ...@@ -61,6 +74,7 @@ public struct NewGroupView: View {
} }
} }
} }
.disabled(viewStore.isCreating)
} }
func nameView(_ viewStore: ViewStore) -> some View { func nameView(_ viewStore: ViewStore) -> some View {
...@@ -69,6 +83,33 @@ public struct NewGroupView: View { ...@@ -69,6 +83,33 @@ public struct NewGroupView: View {
send: { .set(\.$name, $0) } send: { .set(\.$name, $0) }
)) ))
.focused($focusedField, equals: .name) .focused($focusedField, equals: .name)
.disabled(viewStore.isCreating)
}
func messageView(_ viewStore: ViewStore) -> some View {
TextField("Initial message", text: viewStore.binding(
get: \.message,
send: { .set(\.$message, $0) }
))
.focused($focusedField, equals: .message)
.disabled(viewStore.isCreating)
}
func createButton(_ viewStore: ViewStore) -> some View {
Button {
viewStore.send(.createButtonTapped)
} label: {
HStack {
Text("Create group")
Spacer()
if viewStore.isCreating {
ProgressView()
} else {
Image(systemName: "play.fill")
}
}
}
.disabled(viewStore.isCreating)
} }
} }
......
...@@ -10,6 +10,10 @@ import XXModels ...@@ -10,6 +10,10 @@ import XXModels
final class NewGroupComponentTests: XCTestCase { final class NewGroupComponentTests: XCTestCase {
enum Action: Equatable { enum Action: Equatable {
case didFetchContacts(XXModels.Contact.Query) case didFetchContacts(XXModels.Contact.Query)
case didMakeGroup(membership: [Data], message: Data?, name: Data?)
case didSaveGroup(XXModels.Group)
case didSaveMessage(XXModels.Message)
case didSaveGroupMember(XXModels.GroupMember)
} }
var actions: [Action]! var actions: [Action]!
...@@ -116,6 +120,165 @@ final class NewGroupComponentTests: XCTestCase { ...@@ -116,6 +120,165 @@ final class NewGroupComponentTests: XCTestCase {
} }
} }
func testEnterInitialMessage() {
let store = TestStore(
initialState: NewGroupComponent.State(),
reducer: NewGroupComponent()
)
store.send(.binding(.set(\.$focusedField, .message))) {
$0.focusedField = .message
}
store.send(.binding(.set(\.$message, "Welcome message"))) {
$0.message = "Welcome message"
}
store.send(.binding(.set(\.$focusedField, nil))) {
$0.focusedField = nil
}
}
func testCreateGroup() {
let members: [XXModels.Contact] = [
.init(id: "member-contact-1".data(using: .utf8)!),
.init(id: "member-contact-2".data(using: .utf8)!),
.init(id: "member-contact-3".data(using: .utf8)!),
]
let name = "New group"
let message = "Welcome message"
let groupReport = GroupReport(
id: "new-group-id".data(using: .utf8)!,
rounds: [],
roundURL: "",
status: 0
)
let myContactId = "my-contact-id".data(using: .utf8)!
let currentDate = Date(timeIntervalSince1970: 123)
let store = TestStore(
initialState: NewGroupComponent.State(
members: IdentifiedArray(uniqueElements: members),
name: name,
message: message
),
reducer: NewGroupComponent()
)
store.dependencies.app.mainQueue = .immediate
store.dependencies.app.bgQueue = .immediate
store.dependencies.app.messenger.groupChat.get = {
var groupChat: GroupChat = .unimplemented
groupChat.makeGroup.run = { membership, message, name in
self.actions.append(.didMakeGroup(
membership: membership,
message: message,
name: name
))
return groupReport
}
return groupChat
}
store.dependencies.app.messenger.e2e.get = {
var e2e: E2E = .unimplemented
e2e.getContact.run = {
var contact = XXClient.Contact.unimplemented("my-contact-data".data(using: .utf8)!)
contact.getIdFromContact.run = { _ in myContactId }
return contact
}
return e2e
}
store.dependencies.date = .constant(currentDate)
store.dependencies.app.dbManager.getDB.run = {
var db: Database = .unimplemented
db.saveGroup.run = { group in
self.actions.append(.didSaveGroup(group))
return group
}
db.saveMessage.run = { message in
self.actions.append(.didSaveMessage(message))
return message
}
db.saveGroupMember.run = { groupMember in
self.actions.append(.didSaveGroupMember(groupMember))
return groupMember
}
return db
}
store.send(.createButtonTapped) {
$0.isCreating = true
}
XCTAssertNoDifference(actions, [
.didMakeGroup(
membership: members.map(\.id),
message: message.data(using: .utf8)!,
name: name.data(using: .utf8)!
),
.didSaveGroup(.init(
id: groupReport.id,
name: name,
leaderId: myContactId,
createdAt: currentDate,
authStatus: .participating,
serialized: try! groupReport.encode()
)),
.didSaveMessage(.init(
senderId: myContactId,
recipientId: nil,
groupId: groupReport.id,
date: currentDate,
status: .sent,
isUnread: false,
text: message
)),
.didSaveGroupMember(.init(
groupId: groupReport.id,
contactId: members[0].id
)),
.didSaveGroupMember(.init(
groupId: groupReport.id,
contactId: members[1].id
)),
.didSaveGroupMember(.init(
groupId: groupReport.id,
contactId: members[2].id
)),
])
store.receive(.didFinish) {
$0.isCreating = false
}
}
func testCreateGroupFailure() {
struct Failure: Error, Equatable {}
let failure = Failure()
let store = TestStore(
initialState: NewGroupComponent.State(),
reducer: NewGroupComponent()
)
store.dependencies.app.mainQueue = .immediate
store.dependencies.app.bgQueue = .immediate
store.dependencies.app.messenger.groupChat.get = {
var groupChat: GroupChat = .unimplemented
groupChat.makeGroup.run = { _, _, _ in throw failure }
return groupChat
}
store.send(.createButtonTapped) {
$0.isCreating = true
}
store.receive(.didFail(failure.localizedDescription)) {
$0.isCreating = false
$0.failure = failure.localizedDescription
}
}
func testFinish() { func testFinish() {
let store = TestStore( let store = TestStore(
initialState: NewGroupComponent.State(), initialState: NewGroupComponent.State(),
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment