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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Post Composer, uploading avatar/banner #818

Merged
merged 2 commits into from
Dec 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 30 additions & 18 deletions Mlem.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions Mlem/API/Models/Person/APIPerson.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ struct APIPerson: Decodable, Identifiable, Hashable {
let id: Int
let name: String
var displayName: String?
let avatar: String?
var avatar: String?
let banned: Bool
let published: Date
let updated: Date?
let actorId: URL
var bio: String?
let local: Bool
let banner: String?
var banner: String?
let deleted: Bool
let sharedInboxUrl: String?
var matrixUserId: String?
Expand Down
1 change: 1 addition & 0 deletions Mlem/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ struct ContentView: View {
PostComposerView(editModel: editing)
}
.presentationDetents([.medium, .large], selection: .constant(.large))
.presentationDragIndicator(.hidden)
._presentationBackgroundInteraction(enabledUpThrough: .medium)
}
.sheet(item: $quickLookState.url) { url in
Expand Down
2 changes: 1 addition & 1 deletion Mlem/Enums/Settings/PostSortType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ enum PostSortType: String, Codable, CaseIterable, Identifiable {
}
self = value
} else if let intValue = try? container.decode(Int.self) {
guard 0 ... 10 ~= intValue else {
guard 0...10 ~= intValue else {
throw DecodingError.dataCorruptedError(
in: container,
debugDescription: "Must be an integer in range 0...10."
Expand Down
1 change: 1 addition & 0 deletions Mlem/Models/PictrsImageModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

import SwiftUI
import PhotosUI

struct ImageUploadResponse: Codable {
public let msg: String?
Expand Down
84 changes: 0 additions & 84 deletions Mlem/Views/Shared/Components/Image Upload/ImageUploadView.swift

This file was deleted.

186 changes: 186 additions & 0 deletions Mlem/Views/Shared/Components/Image Upload/LinkAttachmentModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
//
// LinkAttachmentModel.swift
// Mlem
//
// Created by Sjmarf on 17/12/2023.
//

import SwiftUI
import Dependencies
import PhotosUI

class LinkAttachmentModel: ObservableObject {
@Dependency(\.pictrsRepository) private var pictrsRepository: PictrsRespository
@Dependency(\.apiClient) private var apiClient: APIClient
@Dependency(\.errorHandler) private var errorHandler: ErrorHandler

var uploadTask: Task<(), any Error>?

@AppStorage("promptUser.permission.privacy.allowImageUploads") var askedForPermissionToUploadImages: Bool = false
@AppStorage("confirmImageUploads") var confirmImageUploads: Bool = false

@Published var url: String = ""
@Published var imageModel: PictrsImageModel?

init(url: String) {
self.url = url
}

@Published var photosPickerItem: PhotosPickerItem?
@Published var showingUploadConfirmation: Bool = false
@Published var showingPhotosPicker: Bool = false
@Published var showingFilePicker: Bool = false

func attachImageAction() {
showingPhotosPicker = true
}

func attachFileAction() {
showingFilePicker = true
}

func pasteFromClipboardAction() {
pasteFromClipboard()
}

func removeLinkAction() {
url = ""
deletePictrs()
}

func prepareToUpload(photo: PhotosPickerItem) async {
do {
if let data = try await photo.loadTransferable(type: Data.self) {
DispatchQueue.main.async {
self.prepareToUpload(data: data)
}
} else {
imageModel = .init(state: .failed("Invalid image format"))
}
} catch {
imageModel = .init(state: .failed(String(describing: error)))
}
}

func prepareToUpload(result: Result<URL, Error>) {
switch result {
case .success(let url):
do {
guard url.startAccessingSecurityScopedResource() else {
imageModel = .init(state: .failed("Invalid permissions"))
return
}
let data = try Data(contentsOf: url)
url.stopAccessingSecurityScopedResource()
prepareToUpload(data: data)
} catch {
url.stopAccessingSecurityScopedResource()
imageModel = .init(state: .failed(String(describing: error)))
}
case .failure(let error):
imageModel = .init(state: .failed(String(describing: error)))
}
}

func prepareToUpload(data: Data) {
imageModel = .init()
self.imageModel?.state = .readyToUpload(data: data)
if let uiImage = UIImage(data: data) {
self.imageModel?.image = Image(uiImage: uiImage)
}
if self.askedForPermissionToUploadImages == false || self.confirmImageUploads {
self.showingUploadConfirmation = true
} else {
self.uploadImage()
}
}

func pasteFromClipboard() {
Task {
if UIPasteboard.general.hasImages, let content = UIPasteboard.general.image {
if let data = content.pngData() {
DispatchQueue.main.async {
self.prepareToUpload(data: data)
}
}
} else if UIPasteboard.general.hasURLs, let content = UIPasteboard.general.url {
DispatchQueue.main.async {
self.url = content.absoluteString
}
}
}
}

func uploadImage() {
guard let imageModel = imageModel else { return }
Task(priority: .userInitiated) {
self.uploadTask = try await pictrsRepository.uploadImage(
imageModel: imageModel,
onUpdate: { newValue in
DispatchQueue.main.async {
self.imageModel = newValue
switch newValue.state {
case .uploaded(let file):
if let file = file {
do {
var components = URLComponents()
components.scheme = try self.apiClient.session.instanceUrl.scheme
components.host = try self.apiClient.session.instanceUrl.host
components.path = "/pictrs/image/\(file.file)"
self.url = components.url?.absoluteString ?? ""
} catch {
self.imageModel?.state = .failed(nil)
}
}
default:
self.url = ""
}
}
}
)
}
}

func deletePictrs(compareUrl: String? = nil) {
if let task = self.uploadTask {
task.cancel()
}
switch self.imageModel?.state {
case .uploaded(file: let file):
if let file = file {
self.photosPickerItem = nil
Task {
do {
if let compareUrl {
var components = URLComponents()
components.scheme = try self.apiClient.session.instanceUrl.scheme
components.host = try self.apiClient.session.instanceUrl.host
components.path = "/pictrs/image/\(file.file)"
if let imageModelUrl = components.url?.absoluteString, compareUrl != imageModelUrl {
try await pictrsRepository.deleteImage(file: file)
print("Deleted from pictrs")
DispatchQueue.main.async {
self.imageModel = nil
}
}
} else {
try await pictrsRepository.deleteImage(file: file)
print("Deleted from pictrs")
DispatchQueue.main.async {
self.imageModel = nil
}
}
} catch {
print("ERROR", error)
errorHandler.handle(error)
}
}
}
default:
self.imageModel = nil
}
if url == "" && self.imageModel != nil {
self.imageModel = nil
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// LinkAttachmentProxy.swift
// Mlem
//
// Created by Sjmarf on 17/12/2023.
//

import Foundation

struct LinkAttachmentProxy {
private let model: LinkAttachmentModel

var imageModel: PictrsImageModel? {
return model.imageModel
}

func attachImageAction() {
model.showingPhotosPicker = true
}

func attachFileAction() {
model.showingFilePicker = true
}

func pasteFromClipboardAction() {
model.pasteFromClipboard()
}

func removeLinkAction() {
model.url = ""
}

init(model: LinkAttachmentModel) {
self.model = model
}
}
Loading