- Updated the application namespace and ID from "net.ypchat.app" to "de.ypchat.android" for better alignment with branding. - Increased Gradle heap size settings to optimize build performance. - Disabled dependency constraints to simplify dependency management. - Removed obsolete files related to the previous application structure, including MainActivity, YpChatApp, and various core components, streamlining the codebase. These changes collectively enhance the application's configuration and structure, improving maintainability and performance.
596 lines
24 KiB
Swift
596 lines
24 KiB
Swift
import PhotosUI
|
|
import SwiftUI
|
|
|
|
// MARK: - Inbox / History
|
|
|
|
struct InboxTabView: View {
|
|
@EnvironmentObject private var services: AppServices
|
|
private var repo: ChatRepository { services.repository }
|
|
private var chat: ChatState { repo.state }
|
|
|
|
var body: some View {
|
|
ScrollView {
|
|
LazyVStack(alignment: .leading, spacing: 10) {
|
|
Text(L10n.tr("tab_inbox"))
|
|
.font(.title2.weight(.bold))
|
|
.padding(.horizontal, 16)
|
|
if chat.inboxResults.isEmpty {
|
|
Text(L10n.tr("inbox_empty"))
|
|
.foregroundStyle(YpChatTheme.textMuted)
|
|
.padding(.horizontal, 16)
|
|
}
|
|
ForEach(chat.inboxResults, id: \.userName) { item in
|
|
Button {
|
|
repo.openConversation(userName: item.userName)
|
|
} label: {
|
|
HStack {
|
|
Text(item.userName).fontWeight(.semibold)
|
|
Spacer()
|
|
Text(L10n.inboxNew(item.unreadCount))
|
|
.font(.subheadline)
|
|
.foregroundStyle(YpChatTheme.textMuted)
|
|
}
|
|
.padding(16)
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.background(YpChatTheme.surface, in: RoundedRectangle(cornerRadius: 12))
|
|
}
|
|
.buttonStyle(.plain)
|
|
.padding(.horizontal, 16)
|
|
}
|
|
}
|
|
.padding(.vertical, 16)
|
|
}
|
|
}
|
|
}
|
|
|
|
struct HistoryTabView: View {
|
|
@EnvironmentObject private var services: AppServices
|
|
private var repo: ChatRepository { services.repository }
|
|
private var chat: ChatState { repo.state }
|
|
|
|
var body: some View {
|
|
ScrollView {
|
|
LazyVStack(alignment: .leading, spacing: 10) {
|
|
Text(L10n.tr("tab_history"))
|
|
.font(.title2.weight(.bold))
|
|
.padding(.horizontal, 16)
|
|
if chat.historyResults.isEmpty {
|
|
Text(L10n.tr("history_empty"))
|
|
.foregroundStyle(YpChatTheme.textMuted)
|
|
.padding(.horizontal, 16)
|
|
}
|
|
ForEach(chat.historyResults, id: \.userName) { item in
|
|
Button {
|
|
repo.openConversation(userName: item.userName)
|
|
} label: {
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
Text(item.userName).fontWeight(.semibold)
|
|
Text(item.lastMessage?.message ?? L10n.tr("no_message"))
|
|
.font(.subheadline)
|
|
.foregroundStyle(YpChatTheme.textMuted)
|
|
}
|
|
.padding(16)
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.background(YpChatTheme.surface, in: RoundedRectangle(cornerRadius: 12))
|
|
}
|
|
.buttonStyle(.plain)
|
|
.padding(.horizontal, 16)
|
|
}
|
|
}
|
|
.padding(.vertical, 16)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Console
|
|
|
|
struct ConsoleTabView: View {
|
|
@EnvironmentObject private var services: AppServices
|
|
private var repo: ChatRepository { services.repository }
|
|
private var chat: ChatState { repo.state }
|
|
|
|
@State private var input = ""
|
|
|
|
private var placeholder: String {
|
|
if chat.awaitingLoginUsername { return L10n.tr("feedback_admin_user") }
|
|
if chat.awaitingLoginPassword { return L10n.tr("feedback_admin_password") }
|
|
return L10n.tr("console_placeholder")
|
|
}
|
|
|
|
var body: some View {
|
|
ScrollView {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
Text(L10n.tr("console_title"))
|
|
.font(.title2.weight(.bold))
|
|
|
|
TextField(placeholder, text: $input)
|
|
.textFieldStyle(.roundedBorder)
|
|
|
|
Button(L10n.tr("console_send")) {
|
|
repo.sendMessage(text: input)
|
|
input = ""
|
|
}
|
|
.buttonStyle(.borderedProminent)
|
|
.tint(YpChatTheme.primary600)
|
|
.disabled(input.isEmpty)
|
|
|
|
if chat.commandLines.isEmpty, chat.commandTable == nil {
|
|
Text(L10n.tr("console_empty"))
|
|
.foregroundStyle(YpChatTheme.textMuted)
|
|
}
|
|
|
|
ForEach(Array(chat.commandLines.enumerated()), id: \.offset) { _, line in
|
|
consoleLineCard(line: localizeRuntimeMessage(line), kind: chat.commandKind)
|
|
}
|
|
|
|
if let table = chat.commandTable {
|
|
commandTableCard(table)
|
|
}
|
|
}
|
|
.padding(16)
|
|
}
|
|
}
|
|
|
|
private func consoleLineCard(line: String, kind: String?) -> some View {
|
|
let k = kind ?? ""
|
|
let bg: Color = {
|
|
if k.hasPrefix("login") { return YpChatTheme.surfaceSoftBlue }
|
|
if k == "info" { return YpChatTheme.surfaceSoftGreen }
|
|
if k == "error" { return YpChatTheme.surfaceSoftRed }
|
|
return YpChatTheme.surfaceSubtle
|
|
}()
|
|
let border: Color = {
|
|
if k.hasPrefix("login") { return Color(red: 0.79, green: 0.86, blue: 0.93) }
|
|
if k == "info" { return Color(red: 0.81, green: 0.89, blue: 0.83) }
|
|
if k == "error" { return Color(red: 0.94, green: 0.79, blue: 0.79) }
|
|
return YpChatTheme.border
|
|
}()
|
|
let fg = k == "error" ? YpChatTheme.danger : YpChatTheme.textStrong
|
|
return Text(line)
|
|
.font(.body)
|
|
.foregroundStyle(fg)
|
|
.padding(14)
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.background(bg, in: RoundedRectangle(cornerRadius: 12))
|
|
.overlay(RoundedRectangle(cornerRadius: 12).stroke(border, lineWidth: 1))
|
|
}
|
|
|
|
private func commandTableCard(_ table: CommandTableState) -> some View {
|
|
VStack(alignment: .leading, spacing: 10) {
|
|
Text(table.title).fontWeight(.bold).foregroundStyle(YpChatTheme.textStrong)
|
|
if !table.columns.isEmpty {
|
|
HStack {
|
|
ForEach(table.columns, id: \.self) { col in
|
|
Text(col).fontWeight(.bold).foregroundStyle(YpChatTheme.primary700)
|
|
Spacer(minLength: 8)
|
|
}
|
|
}
|
|
.padding(.horizontal, 10)
|
|
.padding(.vertical, 8)
|
|
.background(YpChatTheme.primary100, in: RoundedRectangle(cornerRadius: 12))
|
|
}
|
|
ForEach(Array(table.rows.enumerated()), id: \.offset) { _, row in
|
|
HStack {
|
|
ForEach(Array(row.enumerated()), id: \.offset) { _, cell in
|
|
Text(cell).foregroundStyle(YpChatTheme.textMuted)
|
|
Spacer(minLength: 8)
|
|
}
|
|
}
|
|
.padding(.horizontal, 10)
|
|
.padding(.vertical, 8)
|
|
.background(YpChatTheme.surfaceSubtle, in: RoundedRectangle(cornerRadius: 12))
|
|
}
|
|
}
|
|
.padding(16)
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.background(YpChatTheme.surface, in: RoundedRectangle(cornerRadius: 12))
|
|
.overlay(RoundedRectangle(cornerRadius: 12).stroke(YpChatTheme.border, lineWidth: 1))
|
|
}
|
|
}
|
|
|
|
// MARK: - More
|
|
|
|
struct MoreTabView: View {
|
|
@Binding var section: MoreSection
|
|
|
|
var body: some View {
|
|
Group {
|
|
switch section {
|
|
case .overview:
|
|
moreOverview
|
|
case .feedback:
|
|
FeedbackDetailView { section = .overview }
|
|
case .partners:
|
|
PartnersDetailView { section = .overview }
|
|
case .faq:
|
|
staticContent(title: L10n.tr("faq_title"), body: L10n.tr("faq_body")) { section = .overview }
|
|
case .rules:
|
|
staticContent(title: L10n.tr("rules_title"), body: L10n.tr("rules_body")) { section = .overview }
|
|
case .safety:
|
|
staticContent(title: L10n.tr("safety_title"), body: L10n.tr("safety_body")) { section = .overview }
|
|
case .imprint:
|
|
staticContent(title: L10n.tr("imprint_title"), body: L10n.tr("imprint_body")) { section = .overview }
|
|
}
|
|
}
|
|
}
|
|
|
|
private var moreOverview: some View {
|
|
ScrollView {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
Text(L10n.tr("more_title"))
|
|
.font(.title2.weight(.bold))
|
|
moreLink(L10n.tr("more_feedback"), L10n.tr("feedback_comment")) { section = .feedback }
|
|
moreLink(L10n.tr("more_partners"), L10n.tr("partners_intro")) { section = .partners }
|
|
moreLink(L10n.tr("more_faq"), L10n.tr("faq_intro")) { section = .faq }
|
|
moreLink(L10n.tr("more_rules"), L10n.tr("rules_intro")) { section = .rules }
|
|
moreLink(L10n.tr("more_safety"), L10n.tr("safety_intro")) { section = .safety }
|
|
moreLink(L10n.tr("more_imprint"), L10n.tr("imprint_intro")) { section = .imprint }
|
|
}
|
|
.padding(16)
|
|
}
|
|
}
|
|
|
|
private func moreLink(_ title: String, _ subtitle: String, action: @escaping () -> Void) -> some View {
|
|
Button(action: action) {
|
|
VStack(alignment: .leading, spacing: 6) {
|
|
Text(title).fontWeight(.semibold).foregroundStyle(YpChatTheme.textStrong)
|
|
Text(subtitle).font(.subheadline).foregroundStyle(YpChatTheme.textMuted)
|
|
}
|
|
.padding(16)
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.background(YpChatTheme.surface, in: RoundedRectangle(cornerRadius: 12))
|
|
}
|
|
.buttonStyle(.plain)
|
|
}
|
|
|
|
private func staticContent(title: String, body: String, onBack: @escaping () -> Void) -> some View {
|
|
ScrollView {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
HStack {
|
|
Button(L10n.tr("more_back"), action: onBack)
|
|
.foregroundStyle(YpChatTheme.primary700)
|
|
Text(title)
|
|
.font(.title2.weight(.bold))
|
|
}
|
|
Text(body).foregroundStyle(YpChatTheme.textStrong)
|
|
}
|
|
.padding(16)
|
|
}
|
|
}
|
|
}
|
|
|
|
struct FeedbackDetailView: View {
|
|
@EnvironmentObject private var services: AppServices
|
|
var onBack: () -> Void
|
|
|
|
private var repo: ChatRepository { services.repository }
|
|
private var chat: ChatState { repo.state }
|
|
|
|
@State private var comment = ""
|
|
@State private var adminUser = ""
|
|
@State private var adminPassword = ""
|
|
|
|
var body: some View {
|
|
ScrollView {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
HStack {
|
|
Button(L10n.tr("more_back"), action: onBack)
|
|
.foregroundStyle(YpChatTheme.primary700)
|
|
Text(L10n.tr("feedback_title"))
|
|
.font(.title2.weight(.bold))
|
|
}
|
|
|
|
TextField(L10n.tr("feedback_comment"), text: $comment, axis: .vertical)
|
|
.lineLimit(4 ... 8)
|
|
.textFieldStyle(.roundedBorder)
|
|
|
|
Button(L10n.tr("feedback_send")) {
|
|
Task {
|
|
await repo.submitFeedback(comment: comment)
|
|
comment = ""
|
|
}
|
|
}
|
|
.buttonStyle(.borderedProminent)
|
|
.tint(YpChatTheme.primary600)
|
|
.disabled(comment.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
|
|
|
|
if let fm = chat.feedbackMessage {
|
|
Text(localizeRuntimeMessage(fm))
|
|
.font(.footnote)
|
|
.foregroundStyle(YpChatTheme.textMuted)
|
|
}
|
|
|
|
VStack(alignment: .leading, spacing: 8) {
|
|
if chat.feedbackAdminAuthenticated {
|
|
Text("\(L10n.tr("feedback_admin_user")): \(chat.feedbackAdminUserName ?? "")")
|
|
.fontWeight(.semibold)
|
|
Button(L10n.tr("feedback_admin_logout")) {
|
|
Task { await repo.logoutFeedbackAdmin() }
|
|
}
|
|
.buttonStyle(.bordered)
|
|
.tint(YpChatTheme.primary500)
|
|
} else {
|
|
TextField(L10n.tr("feedback_admin_user"), text: $adminUser)
|
|
.textFieldStyle(.roundedBorder)
|
|
SecureField(L10n.tr("feedback_admin_password"), text: $adminPassword)
|
|
.textFieldStyle(.roundedBorder)
|
|
Button(L10n.tr("feedback_admin_login")) {
|
|
Task {
|
|
await repo.loginFeedbackAdmin(username: adminUser, password: adminPassword)
|
|
}
|
|
}
|
|
.buttonStyle(.bordered)
|
|
.tint(YpChatTheme.primary500)
|
|
}
|
|
if let err = chat.feedbackAdminError {
|
|
Text(localizeRuntimeMessage(err))
|
|
.foregroundStyle(YpChatTheme.danger)
|
|
.font(.footnote)
|
|
}
|
|
}
|
|
.padding(16)
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.background(YpChatTheme.surfaceSubtle, in: RoundedRectangle(cornerRadius: 12))
|
|
|
|
if chat.feedbackItems.isEmpty {
|
|
Text(L10n.tr("feedback_empty"))
|
|
.foregroundStyle(YpChatTheme.textMuted)
|
|
}
|
|
ForEach(Array(chat.feedbackItems.enumerated()), id: \.offset) { _, item in
|
|
feedbackRow(item)
|
|
}
|
|
}
|
|
.padding(16)
|
|
}
|
|
}
|
|
|
|
private func feedbackRow(_ item: FeedbackItemDto) -> some View {
|
|
VStack(alignment: .leading, spacing: 6) {
|
|
HStack {
|
|
Text(item.name.flatMap { $0.isEmpty ? nil : $0 } ?? L10n.tr("anonymous"))
|
|
.fontWeight(.semibold)
|
|
Spacer()
|
|
if chat.feedbackAdminAuthenticated {
|
|
Button(L10n.tr("feedback_delete"), role: .destructive) {
|
|
Task { await repo.deleteFeedback(id: item.id) }
|
|
}
|
|
.font(.caption)
|
|
}
|
|
}
|
|
Text(item.comment).foregroundStyle(YpChatTheme.textStrong)
|
|
let meta = [item.country, item.gender, item.age.map { String($0) }]
|
|
.compactMap { $0 }
|
|
.filter { !$0.isEmpty }
|
|
if !meta.isEmpty {
|
|
Text(meta.joined(separator: L10n.tr("feedback_meta_separator")))
|
|
.font(.caption)
|
|
.foregroundStyle(YpChatTheme.textMuted)
|
|
}
|
|
if let ts = formatFeedbackTimestamp(item.createdAt) {
|
|
Text(L10n.feedbackCreatedAt(ts))
|
|
.font(.caption2)
|
|
.foregroundStyle(YpChatTheme.textMuted)
|
|
}
|
|
}
|
|
.padding(16)
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.background(YpChatTheme.surface, in: RoundedRectangle(cornerRadius: 12))
|
|
}
|
|
}
|
|
|
|
struct PartnersDetailView: View {
|
|
@EnvironmentObject private var services: AppServices
|
|
var onBack: () -> Void
|
|
@Environment(\.openURL) private var openURL
|
|
|
|
private var chat: ChatState { services.repository.state }
|
|
|
|
var body: some View {
|
|
ScrollView {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
HStack {
|
|
Button(L10n.tr("more_back"), action: onBack)
|
|
.foregroundStyle(YpChatTheme.primary700)
|
|
Text(L10n.tr("partners_title"))
|
|
.font(.title2.weight(.bold))
|
|
}
|
|
Text(L10n.tr("partners_intro"))
|
|
.foregroundStyle(YpChatTheme.textMuted)
|
|
if let err = chat.partnersError {
|
|
Text(localizeRuntimeMessage(err))
|
|
.foregroundStyle(YpChatTheme.danger)
|
|
}
|
|
ForEach(Array(chat.partnerLinks.enumerated()), id: \.offset) { _, link in
|
|
Button {
|
|
if let u = URL(string: link.url) { openURL(u) }
|
|
} label: {
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
Text(link.pageName).fontWeight(.semibold)
|
|
Text(link.url).foregroundStyle(YpChatTheme.primary700)
|
|
Text(L10n.tr("external_link"))
|
|
.font(.caption)
|
|
.foregroundStyle(YpChatTheme.textMuted)
|
|
}
|
|
.padding(16)
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.background(YpChatTheme.surface, in: RoundedRectangle(cornerRadius: 12))
|
|
}
|
|
.buttonStyle(.plain)
|
|
}
|
|
}
|
|
.padding(16)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Chat
|
|
|
|
struct YpChatConversationView: View {
|
|
@EnvironmentObject private var services: AppServices
|
|
private var repo: ChatRepository { services.repository }
|
|
private var chat: ChatState { repo.state }
|
|
|
|
@State private var draft = ""
|
|
@State private var showSmileys = false
|
|
@State private var photoItem: PhotosPickerItem?
|
|
|
|
private static let maxImageBytes = 5 * 1024 * 1024
|
|
|
|
var body: some View {
|
|
VStack(spacing: 0) {
|
|
HStack {
|
|
Button(L10n.tr("back")) { repo.closeConversation() }
|
|
Text(chat.currentConversation ?? "")
|
|
.fontWeight(.bold)
|
|
.frame(maxWidth: .infinity)
|
|
Button(L10n.tr("block")) {
|
|
if let u = chat.currentConversation { repo.blockUser(userName: u) }
|
|
}
|
|
Button(L10n.tr("unblock")) {
|
|
if let u = chat.currentConversation { repo.unblockUser(userName: u) }
|
|
}
|
|
}
|
|
.padding(12)
|
|
Divider()
|
|
ScrollView {
|
|
LazyVStack(alignment: .leading, spacing: 8) {
|
|
ForEach(Array(chat.messages.enumerated()), id: \.offset) { _, message in
|
|
messageBubble(message)
|
|
}
|
|
}
|
|
.padding(12)
|
|
}
|
|
if chat.isUploadingImage || chat.imageUploadMessage != nil {
|
|
uploadBanner
|
|
}
|
|
HStack(alignment: .bottom) {
|
|
TextField(L10n.tr("message_placeholder"), text: $draft, axis: .vertical)
|
|
.lineLimit(1 ... 4)
|
|
.textFieldStyle(.roundedBorder)
|
|
.disabled(chat.isUploadingImage)
|
|
Button {
|
|
showSmileys.toggle()
|
|
} label: {
|
|
Image(systemName: "face.smiling")
|
|
}
|
|
.disabled(chat.isUploadingImage)
|
|
PhotosPicker(selection: $photoItem, matching: .images) {
|
|
Image(systemName: "photo")
|
|
}
|
|
.disabled(chat.isUploadingImage)
|
|
Button(L10n.tr("button_send")) {
|
|
repo.sendMessage(text: draft)
|
|
draft = ""
|
|
}
|
|
.buttonStyle(.borderedProminent)
|
|
.tint(YpChatTheme.primary600)
|
|
.disabled(draft.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || chat.isUploadingImage)
|
|
}
|
|
.padding(12)
|
|
if showSmileys {
|
|
ScrollView(.horizontal, showsIndicators: false) {
|
|
HStack(spacing: 8) {
|
|
ForEach(ypChatSmileys) { sm in
|
|
Button {
|
|
draft += sm.token
|
|
showSmileys = false
|
|
} label: {
|
|
Text(smileyEmoji(hexCode: sm.hexCode))
|
|
.padding(.horizontal, 12)
|
|
.padding(.vertical, 8)
|
|
.background(YpChatTheme.primary100, in: Capsule())
|
|
.foregroundStyle(YpChatTheme.primary700)
|
|
.fontWeight(.bold)
|
|
}
|
|
}
|
|
}
|
|
.padding(.horizontal, 12)
|
|
}
|
|
}
|
|
if let err = chat.errorMessage {
|
|
Text(localizeRuntimeMessage(err))
|
|
.font(.footnote)
|
|
.foregroundStyle(YpChatTheme.danger)
|
|
.padding(.horizontal, 12)
|
|
}
|
|
}
|
|
.onChange(of: photoItem) { _, new in
|
|
Task { await handlePhoto(new) }
|
|
}
|
|
}
|
|
|
|
private var uploadBanner: some View {
|
|
let msg: String = {
|
|
if chat.isUploadingImage { return L10n.tr("image_upload_in_progress") }
|
|
return localizeRuntimeMessage(chat.imageUploadMessage ?? "")
|
|
}()
|
|
let ok = chat.imageUploadMessage == "Image uploaded"
|
|
let bg: Color = chat.isUploadingImage ? YpChatTheme.surfaceSoftBlue : (ok ? YpChatTheme.surfaceSoftGreen : YpChatTheme.surfaceSoftRed)
|
|
let fg: Color = chat.isUploadingImage || ok ? YpChatTheme.primary700 : YpChatTheme.danger
|
|
return Text(msg)
|
|
.font(.subheadline)
|
|
.foregroundStyle(fg)
|
|
.padding(.horizontal, 14)
|
|
.padding(.vertical, 10)
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.background(bg)
|
|
.padding(.horizontal, 12)
|
|
}
|
|
|
|
@ViewBuilder
|
|
private func messageBubble(_ message: ChatMessageDto) -> some View {
|
|
let selfMsg = message.from == (chat.currentUser?.userName ?? "")
|
|
HStack {
|
|
if selfMsg { Spacer(minLength: 40) }
|
|
VStack(alignment: selfMsg ? .trailing : .leading) {
|
|
if message.isImage, let urlStr = message.imageUrl, let url = URL(string: urlStr) {
|
|
AsyncImage(url: url) { phase in
|
|
switch phase {
|
|
case .success(let img):
|
|
img.resizable().scaledToFit().frame(maxHeight: 220)
|
|
case .failure:
|
|
Text(L10n.tr("image_message")).foregroundStyle(YpChatTheme.textMuted)
|
|
case .empty:
|
|
ProgressView()
|
|
@unknown default:
|
|
EmptyView()
|
|
}
|
|
}
|
|
} else {
|
|
Text(message.message)
|
|
}
|
|
}
|
|
.padding(12)
|
|
.background(selfMsg ? YpChatTheme.bubbleSelf : YpChatTheme.surfaceSubtle, in: RoundedRectangle(cornerRadius: 18))
|
|
if !selfMsg { Spacer(minLength: 40) }
|
|
}
|
|
}
|
|
|
|
private func handlePhoto(_ item: PhotosPickerItem?) async {
|
|
guard let item, let partner = chat.currentConversation else { return }
|
|
repo.setImageUploadState(inProgress: true, message: nil)
|
|
guard let data = try? await item.loadTransferable(type: Data.self) else {
|
|
repo.setImageUploadState(inProgress: false, message: "Image could not be opened")
|
|
photoItem = nil
|
|
return
|
|
}
|
|
guard data.count <= Self.maxImageBytes else {
|
|
repo.setImageUploadState(inProgress: false, message: "Image exceeds 5 MB")
|
|
photoItem = nil
|
|
return
|
|
}
|
|
do {
|
|
let response = try await repo.uploadImage(data: data, fileName: "ypchat-image.jpg", mimeType: "image/jpeg")
|
|
if response.success, let code = response.code, let url = response.url, !code.isEmpty {
|
|
repo.sendImage(toUserName: partner, imageCode: code, imageUrl: url)
|
|
repo.setImageUploadState(inProgress: false, message: "Image uploaded")
|
|
} else {
|
|
repo.setImageUploadState(inProgress: false, message: response.error ?? "Image upload failed")
|
|
}
|
|
} catch {
|
|
repo.setImageUploadState(inProgress: false, message: error.localizedDescription)
|
|
}
|
|
photoItem = nil
|
|
}
|
|
}
|