SimpleX-Chat/apps/ios/Shared/Views/Chat/ChatView.swift

202 lines
8.6 KiB
Swift
Raw Normal View History

//
// ChatView.swift
// SimpleX
//
// Created by Evgeny Poberezkin on 27/01/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
import SwiftUI
struct ChatView: View {
@EnvironmentObject var chatModel: ChatModel
@Environment(\.colorScheme) var colorScheme
@ObservedObject var chat: Chat
@State var message: String = ""
@State var quotedItem: ChatItem? = nil
@State var editingItem: ChatItem? = nil
@State private var inProgress: Bool = false
@FocusState private var keyboardVisible: Bool
@State private var showChatInfo = false
var body: some View {
let cInfo = chat.chatInfo
return VStack {
GeometryReader { g in
let maxWidth = g.size.width * 0.78
ScrollViewReader { proxy in
ScrollView {
LazyVStack(spacing: 5) {
ForEach(chatModel.chatItems) { ci in
let alignment: Alignment = ci.chatDir.sent ? .trailing : .leading
ChatItemView(chatItem: ci)
.contextMenu {
Button {
withAnimation {
editingItem = nil
quotedItem = ci
}
} label: { Label("Reply", systemImage: "arrowshape.turn.up.left") }
Button {
showShareSheet(items: [ci.content.text])
} label: { Label("Share", systemImage: "square.and.arrow.up") }
Button {
UIPasteboard.general.string = ci.content.text
} label: { Label("Copy", systemImage: "doc.on.doc") }
2022-03-26 15:08:42 +04:00
// if (ci.chatDir.sent && ci.meta.editable) {
// Button {
// withAnimation {
// quotedItem = nil
// editingItem = ci
// message = ci.content.text
// }
// } label: { Label("Edit", systemImage: "square.and.pencil") }
// }
}
.padding(.horizontal)
.frame(maxWidth: maxWidth, maxHeight: .infinity, alignment: alignment)
.frame(minWidth: 0, maxWidth: .infinity, alignment: alignment)
}
.onAppear {
DispatchQueue.main.async {
scrollToFirstUnread(proxy)
}
markAllRead()
}
.onChange(of: chatModel.chatItems.count) { _ in
scrollToBottom(proxy)
}
.onChange(of: keyboardVisible) { _ in
if keyboardVisible {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
scrollToBottom(proxy, animation: .easeInOut(duration: 1))
}
}
}
}
}
.onTapGesture {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
}
Spacer(minLength: 0)
ComposeView(
message: $message,
quotedItem: $quotedItem,
editingItem: $editingItem,
sendMessage: sendMessage,
resetMessage: { message = "" },
inProgress: inProgress,
keyboardVisible: $keyboardVisible
)
}
.navigationTitle(cInfo.chatViewName)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button { chatModel.chatId = nil } label: {
2022-02-04 16:31:08 +00:00
HStack(spacing: 4) {
Image(systemName: "chevron.backward")
Text("Chats")
}
}
}
ToolbarItem(placement: .principal) {
Button {
showChatInfo = true
} label: {
ChatInfoToolbar(chat: chat)
}
.sheet(isPresented: $showChatInfo) {
ChatInfoView(chat: chat, showChatInfo: $showChatInfo)
}
}
}
.navigationBarBackButtonHidden(true)
}
func scrollToBottom(_ proxy: ScrollViewProxy, animation: Animation = .default) {
withAnimation(animation) { scrollToBottom_(proxy) }
}
func scrollToBottom_(_ proxy: ScrollViewProxy) {
if let id = chatModel.chatItems.last?.id {
proxy.scrollTo(id, anchor: .bottom)
}
}
// align first unread with the top or the last unread with bottom
func scrollToFirstUnread(_ proxy: ScrollViewProxy) {
if let cItem = chatModel.chatItems.first(where: { $0.isRcvNew() }) {
proxy.scrollTo(cItem.id)
} else {
scrollToBottom_(proxy)
}
}
func markAllRead() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
if chatModel.chatId == chat.id {
Task { await markChatRead(chat) }
}
}
}
func sendMessage(_ msg: String) {
profile images (restore #423) (#466) * core: configurable smp servers (#366) * core: update simplexmq hash * core: update simplexmq hash (fix SMPServer json encoding) * core: fix crashing on supplying duplicate SMP servers * core: update simplexmq hash (remove SMPServer FromJSON) * core: update simplexmq hash (merged master) * core: profile images (#384) * adding initial RFC * adding migration SQL * update RFC * linting * Apply suggestions from code review Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> * refine RFC * add avatars db migration to Store.hs * initial chages to have images in users/groups * fix protocol tests * update SQL & MobileTests * minor bug fixes * add missing comma * fix query error * refactor and update functions * bug fixes + testing * update to parse base64 web format images * fix parsing and use valid padded base64 encoded image * fix typos * respose to and suggestions from review * fix: typo * refactor: avatars -> profile_images * fix: typo * swap updateProfile parameters * remove TODO Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> * initial changes to show profile images * simple set up complete * add initial shape of image getting (needs work) * redesign * ios, android: configurable smp servers (only model and api for android) (#392) * example image picker placed in edit profile screen * tidy up and allow encoding * more tidying * update bottom modal bar * v0.1 UI for upload ready * add api calls * refactor edit profile screen * complete the refactor with connection back to api * linting * update encoding for hs compat * no line wrapping and resize image * refactor and tidy up for cleanest compatability with haskell * ios: UI for editing images * crop image to square * update profile edit layout * fixing image preview orientation etc * allow expandable image in profile view * handle case where user exits camera rather than take image * housekeeping on when to call apiUpdateProfileImage * improve scaling of large image * linting * spacing * fix padding * revert whitespace change * tidy up, one remaining issue * refactor to get parsing working * add missed change * use custom modal in user profile * fix image size after scaling * scale image iteratively * add filter * update profile editing view * ios: edit profile image (TODO aspect ratio) * ios: UI to manage profile images * ios: use new profile api * android: use new api to update profile * android: scroll profile view up when editing * revert change * reduce profile image resolution to 104px to fit in 12.5kb Co-authored-by: IanRDavies <ian_davies_@hotmail.co.uk> Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-03-25 22:13:01 +04:00
logger.debug("ChatView sendMessage")
Task {
profile images (restore #423) (#466) * core: configurable smp servers (#366) * core: update simplexmq hash * core: update simplexmq hash (fix SMPServer json encoding) * core: fix crashing on supplying duplicate SMP servers * core: update simplexmq hash (remove SMPServer FromJSON) * core: update simplexmq hash (merged master) * core: profile images (#384) * adding initial RFC * adding migration SQL * update RFC * linting * Apply suggestions from code review Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> * refine RFC * add avatars db migration to Store.hs * initial chages to have images in users/groups * fix protocol tests * update SQL & MobileTests * minor bug fixes * add missing comma * fix query error * refactor and update functions * bug fixes + testing * update to parse base64 web format images * fix parsing and use valid padded base64 encoded image * fix typos * respose to and suggestions from review * fix: typo * refactor: avatars -> profile_images * fix: typo * swap updateProfile parameters * remove TODO Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> * initial changes to show profile images * simple set up complete * add initial shape of image getting (needs work) * redesign * ios, android: configurable smp servers (only model and api for android) (#392) * example image picker placed in edit profile screen * tidy up and allow encoding * more tidying * update bottom modal bar * v0.1 UI for upload ready * add api calls * refactor edit profile screen * complete the refactor with connection back to api * linting * update encoding for hs compat * no line wrapping and resize image * refactor and tidy up for cleanest compatability with haskell * ios: UI for editing images * crop image to square * update profile edit layout * fixing image preview orientation etc * allow expandable image in profile view * handle case where user exits camera rather than take image * housekeeping on when to call apiUpdateProfileImage * improve scaling of large image * linting * spacing * fix padding * revert whitespace change * tidy up, one remaining issue * refactor to get parsing working * add missed change * use custom modal in user profile * fix image size after scaling * scale image iteratively * add filter * update profile editing view * ios: edit profile image (TODO aspect ratio) * ios: UI to manage profile images * ios: use new profile api * android: use new api to update profile * android: scroll profile view up when editing * revert change * reduce profile image resolution to 104px to fit in 12.5kb Co-authored-by: IanRDavies <ian_davies_@hotmail.co.uk> Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2022-03-25 22:13:01 +04:00
logger.debug("ChatView sendMessage: in Task")
do {
if let ei = editingItem {
let chatItem = try await apiUpdateMessage(
type: chat.chatInfo.chatType,
id: chat.chatInfo.apiId,
itemId: ei.id,
msg: .text(msg)
)
DispatchQueue.main.async {
editingItem = nil
let _ = chatModel.upsertChatItem(chat.chatInfo, chatItem)
}
} else {
let chatItem = try await apiSendMessage(
type: chat.chatInfo.chatType,
id: chat.chatInfo.apiId,
quotedItemId: quotedItem?.meta.itemId,
msg: .text(msg)
)
DispatchQueue.main.async {
quotedItem = nil
chatModel.addChatItem(chat.chatInfo, chatItem)
}
}
} catch {
logger.error("ChatView.sendMessage error: \(error.localizedDescription)")
}
}
}
}
struct ChatView_Previews: PreviewProvider {
static var previews: some View {
let chatModel = ChatModel()
chatModel.chatId = "@1"
chatModel.chatItems = [
ChatItem.getSample(1, .directSnd, .now, "hello"),
ChatItem.getSample(2, .directRcv, .now, "hi"),
ChatItem.getSample(3, .directRcv, .now, "hi there"),
ChatItem.getSample(4, .directRcv, .now, "hello again"),
ChatItem.getSample(5, .directSnd, .now, "hi there!!!"),
ChatItem.getSample(6, .directSnd, .now, "how are you?"),
ChatItem.getSample(7, .directSnd, .now, "👍👍👍👍"),
ChatItem.getSample(8, .directSnd, .now, "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
]
return ChatView(chat: Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: []))
.environmentObject(chatModel)
}
}