Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

ios: update chat previews, show filename in drafts #1847

Merged
merged 16 commits into from Jan 27, 2023
4 changes: 4 additions & 0 deletions apps/ios/Shared/AppDelegate.swift
Expand Up @@ -77,6 +77,10 @@ class AppDelegate: NSObject, UIApplicationDelegate {

func applicationWillTerminate(_ application: UIApplication) {
logger.debug("AppDelegate: applicationWillTerminate")
ChatModel.shared.filesToDelete.forEach {
removeFile($0)
}
ChatModel.shared.filesToDelete = []
terminateChat()
}

Expand Down
2 changes: 2 additions & 0 deletions apps/ios/Shared/Model/ChatModel.swift
Expand Up @@ -63,6 +63,8 @@ final class ChatModel: ObservableObject {

var messageDelivery: Dictionary<Int64, () -> Void> = [:]

var filesToDelete: [String] = []

static let shared = ChatModel()

static var ok: Bool { ChatModel.shared.chatDbStatus == .ok }
Expand Down
6 changes: 5 additions & 1 deletion apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift
Expand Up @@ -84,7 +84,7 @@ struct MsgContentView: View {
}
}

func messageText(_ text: String, _ formattedText: [FormattedText]?, _ sender: String?, preview: Bool = false) -> Text {
func messageText(_ text: String, _ formattedText: [FormattedText]?, _ sender: String?, icon: String? = nil, preview: Bool = false) -> Text {
let s = text
var res: Text
if let ft = formattedText, ft.count > 0 {
Expand All @@ -98,6 +98,10 @@ func messageText(_ text: String, _ formattedText: [FormattedText]?, _ sender: St
res = Text(s)
}

if let i = icon {
res = Text(Image(systemName: i)).foregroundColor(Color(uiColor: .tertiaryLabel)) + Text(" ") + res
}

if let s = sender {
let t = Text(s)
return (preview ? t : t.fontWeight(.medium)) + Text(": ") + res
Expand Down
6 changes: 1 addition & 5 deletions apps/ios/Shared/Views/Chat/ChatView.swift
Expand Up @@ -52,7 +52,7 @@ struct ChatView: View {
}

Spacer(minLength: 0)

ComposeView(
chat: chat,
composeState: $composeState,
Expand All @@ -79,10 +79,6 @@ struct ChatView: View {
.onDisappear {
if chatModel.chatId == cInfo.id {
chatModel.chatId = nil
if !composeState.empty {
chatModel.draft = composeState
chatModel.draftChatId = chat.id
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) {
if chatModel.chatId == nil {
chatModel.reversedChatItems = []
Expand Down
101 changes: 70 additions & 31 deletions apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift
Expand Up @@ -14,9 +14,9 @@ import PhotosUI
enum ComposePreview {
case noPreview
case linkPreview(linkPreview: LinkPreview?)
case imagePreviews(imagePreviews: [String])
case imagePreviews(imagePreviews: [(String, UploadContent?)])
case voicePreview(recordingFileName: String, duration: Int)
case filePreview(fileName: String)
case filePreview(fileName: String, file: URL)
}

enum ComposeContextItem {
Expand Down Expand Up @@ -163,7 +163,7 @@ struct ComposeState {
}

var empty: Bool {
message == "" && liveMessage == nil && noPreview
message == "" && noPreview
}
}

Expand All @@ -175,11 +175,12 @@ func chatItemPreview(chatItem: ChatItem) -> ComposePreview {
case let .link(_, preview: preview):
chatItemPreview = .linkPreview(linkPreview: preview)
case let .image(_, image):
chatItemPreview = .imagePreviews(imagePreviews: [image])
chatItemPreview = .imagePreviews(imagePreviews: [(image, nil)])
case let .voice(_, duration):
chatItemPreview = .voicePreview(recordingFileName: chatItem.file?.fileName ?? "", duration: duration)
case .file:
chatItemPreview = .filePreview(fileName: chatItem.file?.fileName ?? "")
let fileName = chatItem.file?.fileName ?? ""
chatItemPreview = .filePreview(fileName: fileName, file: getAppFilePath(fileName))
default:
chatItemPreview = .noPreview
}
Expand Down Expand Up @@ -233,7 +234,6 @@ struct ComposeView: View {
@State private var showTakePhoto = false
@State var chosenImages: [UploadContent] = []
@State private var showFileImporter = false
@State var chosenFile: URL? = nil

@State private var audioRecorder: AudioRecorder?
@State private var voiceMessageRecordingTime: TimeInterval?
Expand Down Expand Up @@ -341,10 +341,10 @@ struct ComposeView: View {
}
.onChange(of: chosenImages) { images in
Task {
var imgs: [String] = []
var imgs: [(String, UploadContent)] = []
for image in images {
if let img = resizeImageToStrSize(image.uiImage, maxDataSize: 14000) {
imgs.append(img)
imgs.append((img, image))
await MainActor.run {
composeState = composeState.copy(preview: .imagePreviews(imagePreviews: imgs))
}
Expand All @@ -371,9 +371,8 @@ struct ComposeView: View {
}
fileURL.stopAccessingSecurityScopedResource()
if let fileSize = fileSize,
fileSize <= MAX_FILE_SIZE {
chosenFile = fileURL
composeState = composeState.copy(preview: .filePreview(fileName: fileURL.lastPathComponent))
fileSize <= MAX_FILE_SIZE {
composeState = composeState.copy(preview: .filePreview(fileName: fileURL.lastPathComponent, file: fileURL))
} else {
let prettyMaxFileSize = ByteCountFormatter().string(fromByteCount: MAX_FILE_SIZE)
AlertManager.shared.showAlertMsg(
Expand All @@ -387,12 +386,18 @@ struct ComposeView: View {
}
}
.onDisappear {
if let fileName = composeState.voiceMessageRecordingFileName {
cancelVoiceMessageRecording(fileName)
}
if composeState.liveMessage != nil && (!composeState.message.isEmpty || composeState.liveMessage?.sentMsg != nil) {
if composeState.liveMessage != nil
&& (!composeState.message.isEmpty || composeState.liveMessage?.sentMsg != nil) {
cancelCurrentVoiceRecording()
clearCurrentDraft()
sendMessage()
resetLinkPreview()
} else if !composeState.empty {
saveCurrentDraft()
} else {
cancelCurrentVoiceRecording()
clearCurrentDraft()
clearState()
}
chatModel.removeLiveDummy(animated: false)
}
Expand All @@ -409,6 +414,12 @@ struct ComposeView: View {
if !vmAllowed && composeState.voicePreview,
let fileName = composeState.voiceMessageRecordingFileName {
cancelVoiceMessageRecording(fileName)
clearState()
}
}
.onAppear {
if case let .voicePreview(_, duration) = composeState.preview {
voiceMessageRecordingTime = TimeInterval(duration)
}
}
}
Expand Down Expand Up @@ -475,7 +486,7 @@ struct ComposeView: View {
ComposeLinkView(linkPreview: preview, cancelPreview: cancelLinkPreview)
case let .imagePreviews(imagePreviews: images):
ComposeImageView(
images: images,
images: images.map { (img, _) in img },
cancelImage: {
composeState = composeState.copy(preview: .noPreview)
chosenImages = []
Expand All @@ -486,16 +497,18 @@ struct ComposeView: View {
recordingFileName: recordingFileName,
recordingTime: $voiceMessageRecordingTime,
recordingState: $composeState.voiceMessageRecordingState,
cancelVoiceMessage: { cancelVoiceMessageRecording($0) },
cancelVoiceMessage: {
cancelVoiceMessageRecording($0)
clearState()
},
cancelEnabled: !composeState.editing,
stopPlayback: $stopPlayback
)
case let .filePreview(fileName: fileName):
case let .filePreview(fileName, _):
ComposeFileView(
fileName: fileName,
cancelFile: {
composeState = composeState.copy(preview: .noPreview)
chosenFile = nil
},
cancelEnabled: !composeState.editing)
}
Expand Down Expand Up @@ -552,27 +565,23 @@ struct ComposeView: View {
case .linkPreview:
sent = await send(checkLinkPreview(), quoted: quoted, live: live)
case let .imagePreviews(imagePreviews: images):
let last = min(chosenImages.count, images.count) - 1
let last = images.count - 1
if last >= 0 {
for i in 0..<last {
if let savedFile = saveAnyImage(chosenImages[i]) {
_ = await send(.image(text: "", image: images[i]), quoted: nil, file: savedFile)
}
sent = await sendImage(images[i])
_ = try? await Task.sleep(nanoseconds: 100_000000)
}
if let savedFile = saveAnyImage(chosenImages[last]) {
sent = await send(.image(text: msgText, image: images[last]), quoted: quoted, file: savedFile, live: live)
}
sent = await sendImage(images[last], text: msgText, quoted: quoted, live: live)
}
if sent == nil {
sent = await send(.text(msgText), quoted: quoted, live: live)
}
case let .voicePreview(recordingFileName, duration):
stopPlayback.toggle()
chatModel.filesToDelete.removeAll { $0 == recordingFileName }
sent = await send(.voice(text: msgText, duration: duration), quoted: quoted, file: recordingFileName)
case .filePreview:
if let fileURL = chosenFile,
let savedFile = saveFileFromURL(fileURL) {
case let .filePreview(_, file):
if let savedFile = saveFileFromURL(file) {
sent = await send(.file(msgText), quoted: quoted, file: savedFile, live: live)
}
}
Expand Down Expand Up @@ -627,6 +636,14 @@ struct ComposeView: View {
}
}

func sendImage(_ imageData: (String, UploadContent?), text: String = "", quoted: Int64? = nil, live: Bool = false) async -> ChatItem? {
let (image, data) = imageData
if let data = data, let savedFile = saveAnyImage(data) {
return await send(.image(text: text, image: image), quoted: quoted, file: savedFile, live: live)
}
return nil
}

func send(_ mc: MsgContent, quoted: Int64?, file: String? = nil, live: Bool = false) async -> ChatItem? {
if let chatItem = await apiSendMessage(
type: chat.chatInfo.chatType,
Expand Down Expand Up @@ -737,11 +754,16 @@ struct ComposeView: View {
)
}

private func cancelCurrentVoiceRecording() {
if let fileName = composeState.voiceMessageRecordingFileName {
cancelVoiceMessageRecording(fileName)
}
}

private func cancelVoiceMessageRecording(_ fileName: String) {
stopPlayback.toggle()
audioRecorder?.stop()
removeFile(fileName)
clearState()
}

private func clearState(live: Bool = false) {
Expand All @@ -753,12 +775,29 @@ struct ComposeView: View {
resetLinkPreview()
}
chosenImages = []
chosenFile = nil
audioRecorder = nil
voiceMessageRecordingTime = nil
startingRecording = false
}

private func saveCurrentDraft() {
if case .recording = composeState.voiceMessageRecordingState {
finishVoiceMessageRecording()
if let fileName = composeState.voiceMessageRecordingFileName {
chatModel.filesToDelete.append(fileName)
}
}
chatModel.draft = composeState
chatModel.draftChatId = chat.id
}

private func clearCurrentDraft() {
if chatModel.draftChatId == chat.id {
chatModel.draft = nil
chatModel.draftChatId = nil
}
}

private func showLinkPreview(_ s: String) {
prevLinkUrl = linkUrl
linkUrl = parseMessage(s)
Expand Down
27 changes: 20 additions & 7 deletions apps/ios/Shared/Views/ChatList/ChatPreviewView.swift
Expand Up @@ -40,7 +40,7 @@ struct ChatPreviewView: View {
.padding(.horizontal, 8)

ZStack(alignment: .topTrailing) {
chatPreviewText(cItem)
chatMessagePreview(cItem)
if case .direct = chat.chatInfo {
chatStatusImage()
.padding(.top, 24)
Expand Down Expand Up @@ -142,21 +142,34 @@ struct ChatPreviewView: View {

func attachment() -> Text {
switch draft.preview {
case .filePreview: return image("doc.fill")
case let .filePreview(fileName, _): return image("doc.fill") + Text(fileName) + Text(" ")
case .imagePreviews: return image("photo")
case let .voicePreview(_, duration): return image("play.fill") + Text(voiceMessageTime(TimeInterval(duration)))
case let .voicePreview(_, duration): return image("play.fill") + Text(durationText(duration))
default: return Text("")
}
}
}

@ViewBuilder private func chatPreviewText(_ cItem: ChatItem?) -> some View {
func chatItemPreview(_ cItem: ChatItem) -> Text {
let itemText = !cItem.meta.itemDeleted ? cItem.text : NSLocalizedString("marked deleted", comment: "marked deleted chat item preview text")
let itemFormattedText = !cItem.meta.itemDeleted ? cItem.formattedText : nil
return messageText(itemText, itemFormattedText, cItem.memberDisplayName, icon: attachment(), preview: true)

func attachment() -> String? {
switch cItem.content.msgContent {
case .file: return "doc.fill"
case .image: return "photo"
case .voice: return "play.fill"
default: return nil
}
}
}

@ViewBuilder private func chatMessagePreview(_ cItem: ChatItem?) -> some View {
if chatModel.draftChatId == chat.id, let draft = chatModel.draft {
chatPreviewLayout(messageDraft(draft))
} else if let cItem = cItem {
let itemText = !cItem.meta.itemDeleted ? cItem.text : NSLocalizedString("marked deleted", comment: "marked deleted chat item preview text")
let itemFormattedText = !cItem.meta.itemDeleted ? cItem.formattedText : nil
chatPreviewLayout(itemStatusMark(cItem) + messageText(itemText, itemFormattedText, cItem.memberDisplayName, preview: true))
chatPreviewLayout(itemStatusMark(cItem) + chatItemPreview(cItem))
} else {
switch (chat.chatInfo) {
case let .direct(contact):
Expand Down