Skip to content

Commit

Permalink
New Post Composer, uploading avatar/banner (#818)
Browse files Browse the repository at this point in the history
  • Loading branch information
Sjmarf authored Dec 21, 2023
1 parent 93a1001 commit 789ac13
Show file tree
Hide file tree
Showing 19 changed files with 821 additions and 520 deletions.
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

0 comments on commit 789ac13

Please sign in to comment.