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

Implement creating new group

parent 172ddcad
Branches
Tags
2 merge requests!153Release 1.1.0,!149[Messenger example] create new group
This commit is part of merge request !149. Comments created here will be created in the context of that merge request.
......@@ -8,31 +8,43 @@ public struct NewGroupComponent: ReducerProtocol {
public struct State: Equatable {
public enum Field: String, Hashable {
case name
case message
}
public init(
contacts: IdentifiedArrayOf<XXModels.Contact> = [],
members: IdentifiedArrayOf<XXModels.Contact> = [],
name: String = "",
focusedField: Field? = nil
message: String = "",
focusedField: Field? = nil,
isCreating: Bool = false,
failure: String? = nil
) {
self.contacts = contacts
self.members = members
self.name = name
self.message = message
self.focusedField = focusedField
self.isCreating = isCreating
self.failure = failure
}
public var contacts: IdentifiedArrayOf<XXModels.Contact>
public var members: IdentifiedArrayOf<XXModels.Contact>
@BindableState public var name: String
@BindableState public var message: String
@BindableState public var focusedField: Field?
public var isCreating: Bool
public var failure: String?
}
public enum Action: Equatable, BindableAction {
case start
case didFetchContacts([XXModels.Contact])
case didSelectContact(XXModels.Contact)
case createButtonTapped
case didFinish
case didFail(String)
case binding(BindingAction<State>)
}
......@@ -42,6 +54,7 @@ public struct NewGroupComponent: ReducerProtocol {
@Dependency(\.app.dbManager.getDB) var db: DBManagerGetDB
@Dependency(\.app.mainQueue) var mainQueue: AnySchedulerOf<DispatchQueue>
@Dependency(\.app.bgQueue) var bgQueue: AnySchedulerOf<DispatchQueue>
@Dependency(\.date) var date: DateGenerator
public var body: some ReducerProtocol<State, Action> {
BindingReducer()
......@@ -71,7 +84,61 @@ public struct NewGroupComponent: ReducerProtocol {
}
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:
state.isCreating = false
state.failure = nil
return .none
case .didFail(let failure):
state.isCreating = false
state.failure = failure
return .none
case .binding(_):
......
......@@ -19,13 +19,19 @@ public struct NewGroupView: View {
contacts = state.contacts
members = state.members
name = state.name
message = state.message
focusedField = state.focusedField
isCreating = state.isCreating
failure = state.failure
}
var contacts: IdentifiedArrayOf<XXModels.Contact>
var members: IdentifiedArrayOf<XXModels.Contact>
var name: String
var message: String
var focusedField: Component.State.Field?
var isCreating: Bool
var failure: String?
}
public var body: some View {
......@@ -34,6 +40,13 @@ public struct NewGroupView: View {
Section {
membersView(viewStore)
nameView(viewStore)
messageView(viewStore)
}
Section {
createButton(viewStore)
if let failure = viewStore.failure {
Text(failure)
}
}
}
.navigationTitle("New Group")
......@@ -61,6 +74,7 @@ public struct NewGroupView: View {
}
}
}
.disabled(viewStore.isCreating)
}
func nameView(_ viewStore: ViewStore) -> some View {
......@@ -69,6 +83,33 @@ public struct NewGroupView: View {
send: { .set(\.$name, $0) }
))
.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
final class NewGroupComponentTests: XCTestCase {
enum Action: Equatable {
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]!
......@@ -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() {
let store = TestStore(
initialState: NewGroupComponent.State(),
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment