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