Files
simplex-chat/apps/ios/SimpleX SE/ShareView.swift
T
Evgeny Poberezkin 6865515f43 ios: share extension (#4466)
* ios: share extension (#4414)

* ios: add share extension target

* ios: Add UI

* ios: send file from share-sheet

* image utils

* ShareError

* error handling; ui-cleanup

* progress bar; completion for direct chat

* cleanup

* cleanup

* ios: unify filter and sort between forward and share sheets

* ios: match share sheet styling with the main app

* ios: fix text input stroke width

* ios: align compose views

* more of the same...

* ShareAPI

* remove combine

* minor

* Better error descriptions

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>

* ios: enable file sending workers in share extension (#4474)

* ios: align compose background, row height and fallback images for share-sheet (#4467)

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>

* ios: coordinate database access between share extension, the app and notifications extension (#4472)

* ios: database management proposal

* Add SEState

* Global event loop

* minor

* reset state

* use apiCreateItem for local chats

* simplify waiting for suspension

* loading bar

* Dismiss share sheet with error

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>

* send image message (#4481)

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>

* ios: improve share extension completion handling (#4486)

* improve completion handling

* minor

* show only spinner for group send

* rework event loop, errorAlert

* group chat timeout loading bar

* state machine WIP

* event loop actor

* alert

* errors text

* default

* file error

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>

* ios: add remaining share types; process attachment in background on launch (#4510)

* add remaining share types; process attachment in background on launch

* cleanup diff

* revert `makeVideoQualityLower`

* reduce diff

* reduce diff

* iOS15 support

* process events when sharing link and text

* cleanup

* remove video file on failure

* cleanup CompletionHandler

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>

* ios: share extension - additional alerts and media previews (#4521)

* add remaining share types; process attachment in background on launch

* cleanup diff

* revert `makeVideoQualityLower`

* reduce diff

* reduce diff

* iOS15 support

* process events when sharing link and text

* cleanup

* remove video file on failure

* cleanup CompletionHandler

* media previews

* network timeout alert

* revert framework compiler optimisation flag

* suspend chat after sheet dismiss

* activate chat

* update

* fix search

* sendMessageColor, file preview, chat deselect, simplify error action

* cleanup

* interupt database closing when sheet is reopened quickly

* cleanup redundant alert check

* restore package

* refactor previews, remove link preview

* show link preview when becomes available

* comment

* dont fail on invalid image

* suspend

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>

* ios: descriptive database errors (#4527)

* ios: set share extension as inactive when suspending chat

---------

Co-authored-by: Arturs Krumins <auth@levitatingpineapple.com>
2024-07-28 17:54:58 +01:00

181 lines
6.1 KiB
Swift

//
// ShareView.swift
// SimpleX SE
//
// Created by Levitating Pineapple on 09/07/2024.
// Copyright © 2024 SimpleX Chat. All rights reserved.
//
import SwiftUI
import SimpleXChat
struct ShareView: View {
@ObservedObject var model: ShareModel
@Environment(\.colorScheme) var colorScheme
var body: some View {
NavigationView {
ZStack(alignment: .bottom) {
if model.isLoaded {
List(model.filteredChats) { chat in
HStack {
profileImage(
chatInfoId: chat.chatInfo.id,
systemFallback: chatIconName(chat.chatInfo),
size: 30
)
Text(chat.chatInfo.displayName)
Spacer()
radioButton(selected: chat == model.selected)
}
.contentShape(Rectangle())
.onTapGesture { model.selected = model.selected == chat ? nil : chat }
.tag(chat)
}
} else {
ProgressView().frame(maxHeight: .infinity)
}
}
.navigationTitle("Share")
.safeAreaInset(edge: .bottom) {
switch model.bottomBar {
case .sendButton:
compose(isLoading: false)
case .loadingSpinner:
compose(isLoading: true)
case .loadingBar(let progress):
loadingBar(progress: progress)
}
}
}
.searchable(
text: $model.search,
placement: .navigationBarDrawer(displayMode: .always)
)
.alert($model.errorAlert) { alert in
Button("Ok") { model.completion() }
}
}
private func compose(isLoading: Bool) -> some View {
VStack(spacing: .zero) {
Divider()
if let content = model.sharedContent {
itemPreview(content)
}
HStack {
Group {
if #available(iOSApplicationExtension 16.0, *) {
TextField("Comment", text: $model.comment, axis: .vertical)
} else {
TextField("Comment", text: $model.comment)
}
}
.contentShape(Rectangle())
.disabled(isLoading)
.padding(.horizontal, 12)
.padding(.vertical, 4)
Group {
if isLoading {
ProgressView()
} else {
Button(action: model.send) {
Image(systemName: "arrow.up.circle.fill")
.resizable()
}
.disabled(model.isSendDisbled)
}
}
.frame(width: 28, height: 28)
.padding(6)
}
.background(Color(.systemBackground))
.clipShape(RoundedRectangle(cornerRadius: 20))
.overlay(
RoundedRectangle(cornerRadius: 20)
.strokeBorder(.secondary, lineWidth: 0.5).opacity(0.7)
)
.padding(8)
}
.background(.thinMaterial)
}
@ViewBuilder private func itemPreview(_ content: SharedContent) -> some View {
switch content {
case let .image(preview, _): imagePreview(preview)
case let .movie(preview, _, _): imagePreview(preview)
case let .url(linkPreview): imagePreview(linkPreview.image)
case let .data(cryptoFile):
previewArea {
Image(systemName: "doc.fill")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 30, height: 30)
.foregroundColor(Color(uiColor: .tertiaryLabel))
.padding(.leading, 4)
Text(cryptoFile.filePath)
}
case .text: EmptyView()
}
}
@ViewBuilder private func imagePreview(_ img: String) -> some View {
if let img = UIImage(base64Encoded: img) {
previewArea {
Image(uiImage: img)
.resizable()
.scaledToFit()
.frame(minHeight: 40, maxHeight: 60)
}
} else {
EmptyView()
}
}
@ViewBuilder private func previewArea<V: View>(@ViewBuilder content: @escaping () -> V) -> some View {
HStack(alignment: .center, spacing: 8) {
content()
Spacer()
}
.padding(.vertical, 1)
.frame(minHeight: 54)
.background {
switch colorScheme {
case .light: LightColorPaletteApp.sentMessage
case .dark: DarkColorPaletteApp.sentMessage
@unknown default: Color(.tertiarySystemBackground)
}
}
Divider()
}
private func loadingBar(progress: Double) -> some View {
VStack {
Text("Sending File")
ProgressView(value: progress)
}
.padding()
.background(Material.ultraThin)
}
private func profileImage(chatInfoId: ChatInfo.ID, systemFallback: String, size: Double) -> some View {
Group {
if let uiImage = model.profileImages[chatInfoId] {
Image(uiImage: uiImage).resizable()
} else {
Image(systemName: systemFallback).resizable()
}
}
.foregroundStyle(Color(.tertiaryLabel))
.frame(width: size, height: size)
.clipShape(RoundedRectangle(cornerRadius: size * 0.225, style: .continuous))
}
private func radioButton(selected: Bool) -> some View {
Image(systemName: selected ? "checkmark.circle.fill" : "circle")
.imageScale(.large)
.foregroundStyle(selected ? Color.accentColor : Color(.tertiaryLabel))
}
}