Skip to content

Commit

Permalink
Merge branch 'dev' into bosco/improve-navigation-routing-pattern
Browse files Browse the repository at this point in the history
# Conflicts:
#	Mlem.xcodeproj/project.pbxproj
#	Mlem/Views/Tabs/Search/Search View.swift
#	Mlem/Views/Tabs/Settings/Components/Views/About/LicensesView.swift
  • Loading branch information
boscojwho committed Oct 4, 2023
2 parents 78ca37c + 05ef83c commit d9e57a9
Show file tree
Hide file tree
Showing 85 changed files with 3,187 additions and 330 deletions.
165 changes: 157 additions & 8 deletions Mlem.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Mlem/API/APIClient/APIClient+Community.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ extension APIClient {
return try await perform(request: request)
}

@discardableResult
func followCommunity(id: Int, shouldFollow: Bool) async throws -> CommunityResponse {
let request = try FollowCommunityRequest(session: session, communityId: id, follow: shouldFollow)
return try await perform(request: request)
Expand Down
107 changes: 107 additions & 0 deletions Mlem/API/APIClient/APIClient+Pictrs.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
//
// APIClient+Pictrs.swift
// Mlem
//
// Created by Sjmarf on 24/09/2023.
//

import Foundation

extension APIClient {
@discardableResult
func deleteImage(file: PictrsFile) async throws -> ImageDeleteResponse {
let request = try ImageDeleteRequest(session: session, file: file.file, deleteToken: file.deleteToken)
return try await perform(request: request)
}

func uploadImage(
_ imageData: Data,
onProgress progressCallback: @escaping (_ progress: Double) -> Void,
onCompletion completionCallback: @escaping(_ response: ImageUploadResponse?) -> Void,
`catch`: @escaping (Error) -> Void
) async throws -> Task<(), any Error> {

let delegate = ImageUploadDelegate(callback: progressCallback)
// Modify the instance URL to remove "api/v3" and add "pictrs/image".
var components = URLComponents()
components.scheme = try self.session.instanceUrl.scheme
components.host = try self.session.instanceUrl.host
components.path = "/pictrs/image"

guard let url = components.url else {
throw APIClientError.response(.init(error: "Failed to modify instance URL to add pictrs."), nil)
}
var request = URLRequest(url: url)
request.httpMethod = "POST"

let boundary = UUID().uuidString

request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
request.setValue("jwt=\(try session.token)", forHTTPHeaderField: "Cookie")

let multiPartForm: MultiPartForm = try .init(
mimeType: "image/png",
fileName: "image.png",
imageData: imageData,
auth: session.token
)

return Task { [request] in
do {
let (data, _) = try await self.urlSession.upload(
for: request,
from: multiPartForm.createField(boundary: boundary),
delegate: delegate)
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let response = try decoder.decode(ImageUploadResponse.self, from: data)
completionCallback(response)
} catch DecodingError.dataCorrupted {
throw APIClientError.decoding(data)
}
} catch {
if !Task.isCancelled {
`catch`(error)
}
}
}
}
}

private struct MultiPartForm: Codable {
var mimeType: String
var fileName: String
var imageData: Data
var auth: String

func createField(boundary: String) -> Data {
var data = Data()
data.append(Data("--\(boundary)\r\n".utf8))
data.append(Data("Content-Disposition: form-data; name=\"images[]\"; filename=\"\(fileName)\"\r\n".utf8))
data.append(Data("Content-Type: \(mimeType)\r\n".utf8))
data.append(Data("\r\n".utf8))
data.append(imageData)
data.append(Data("\r\n".utf8))
data.append(Data("--\(boundary)--\r\n".utf8))
return data
}
}

private class ImageUploadDelegate: NSObject, URLSessionTaskDelegate {
public let callback: (Double) -> Void

public init(callback: @escaping (Double) -> Void) {
self.callback = callback
}

public func urlSession(
_ session: URLSession,
task: URLSessionTask,
didSendBodyData bytesSent: Int64,
totalBytesSent: Int64,
totalBytesExpectedToSend: Int64
) {
callback(Double(totalBytesSent) / Double(totalBytesExpectedToSend))
}
}
8 changes: 8 additions & 0 deletions Mlem/API/Models/APIErrorResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,16 @@ private let possible2FAErrors = [
"incorrect_totp_token"
]

private let registrationErrors = [
"registration_application_pending",
"email_not_verified"
]

extension APIErrorResponse {
var isIncorrectLogin: Bool { possibleCredentialErrors.contains(error) }
var requires2FA: Bool { possible2FAErrors.contains(error) }
var isNotLoggedIn: Bool { error == "not_logged_in" }
var userRegistrationPending: Bool { registrationErrors.contains(error) }
var emailNotVerified: Bool { registrationErrors.contains(error) }
var instanceIsPrivate: Bool { error == "instance_is_private" }
}
36 changes: 36 additions & 0 deletions Mlem/API/Requests/Post/DeletePictrsFile.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// DeletePictrsFile.swift
// Mlem
//
// Created by Sjmarf on 29/09/2023.
//

import Foundation

struct ImageDeleteRequest: APIRequest {
var path: String
var instanceURL: URL

var endpoint: URL {
instanceURL
.appending(path: path)
}

typealias Response = ImageDeleteResponse

init(session: APISession, file: String, deleteToken: String) throws {
var components = URLComponents()
components.scheme = try session.instanceUrl.scheme
components.host = try session.instanceUrl.host
components.path = "/pictrs/image"

guard let url = components.url else {
throw APIClientError.response(.init(error: "Failed to modify instance URL to delete from pictrs."), nil)
}
self.instanceURL = url

self.path = "/delete/\(deleteToken)/\(file)"
}
}

struct ImageDeleteResponse: Decodable { }
2 changes: 1 addition & 1 deletion Mlem/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ struct ContentView: View {
}
}

SearchView()
SearchRoot()
.fancyTabItem(tag: TabSelection.search) {
FancyTabBarLabel(
tag: TabSelection.search,
Expand Down
24 changes: 23 additions & 1 deletion Mlem/Data/Licenses.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
""")

let swiftMarkdownUILIcense = Document(body: """
let swiftMarkdownUILicense = Document(body: """
The MIT License (MIT)
Copyright (c) 2020 Guillermo Gonzalez
Expand All @@ -104,6 +104,28 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
""")

let swiftUIXLicense = Document(body: """
Copyright © 2020 Vatsal Manot
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the “Software”), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
""")

let awesomeLemmyInstancesLicense = Document(body: """
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Expand Down
20 changes: 20 additions & 0 deletions Mlem/Dependency/Repositories/PictrsRepository+Dependency.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// PictrsRepository+Dependency.swift
// Mlem
//
// Created by Sjmarf on 29/09/2023.
//

import Dependencies
import Foundation

extension PictrsRespository: DependencyKey {
static let liveValue = PictrsRespository()
}

extension DependencyValues {
var pictrsRepository: PictrsRespository {
get { self[PictrsRespository.self] }
set { self[PictrsRespository.self] = newValue }
}
}
33 changes: 33 additions & 0 deletions Mlem/Enums/AvatarType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// AvatarType.swift
// Mlem
//
// Created by Eric Andrews on 2023-10-02.
//

import Foundation

/// Enum of things that can have avatars
enum AvatarType {
case user, community
}

extension AvatarType: AssociatedIcon {
var iconName: String {
switch self {
case .user:
return Icons.user
case .community:
return Icons.community
}
}

var iconNameFill: String {
switch self {
case .user:
return Icons.userFill
case .community:
return Icons.communityFill
}
}
}
4 changes: 2 additions & 2 deletions Mlem/Enums/Content Type.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@

import Foundation

enum ContentType: Int {
case post
enum ContentType: Int, Codable {
case post, community, user
}
17 changes: 11 additions & 6 deletions Mlem/Extensions/Swipey Actions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public struct SwipeAction {

let symbol: Symbol
let color: Color
let action: () async -> Void
let action: () -> Void
}

// MARK: -
Expand All @@ -29,7 +29,7 @@ public struct SwipeConfiguration {
/// In ascending order of appearance.
let trailingActions: [SwipeAction]

init(leadingActions: [SwipeAction?], trailingActions: [SwipeAction?]) {
init(leadingActions: [SwipeAction?] = [], trailingActions: [SwipeAction?] = []) {
assert(
leadingActions.count <= 3 && trailingActions.count <= 3,
"Getting a little swipey aren't we? Ask your fellow Mlem'ers if you really need more than 3 swipe actions =)"
Expand Down Expand Up @@ -185,6 +185,7 @@ struct SwipeyView: ViewModifier {
.padding(.horizontal, 20)
}
.accessibilityHidden(true) // prevent these from popping up in VO
.opacity(dragState == .zero ? 0 : 1) // prevent this view from appearing in animations on parent view(s).
}
}
// prevents various animation glitches
Expand All @@ -200,9 +201,7 @@ struct SwipeyView: ViewModifier {

reset()

Task(priority: .userInitiated) {
await swipeAction(at: finalDragPosition)?.action()
}
swipeAction(at: finalDragPosition)?.action()
}

private func reset() {
Expand Down Expand Up @@ -347,8 +346,14 @@ struct SwipeyView: ViewModifier {
// swiftlint:enable function_body_length

extension View {
/// Adds swipey actions to a view.
///
/// NOTE: if the view you are attaching this to also has a context menu, add the context menu view modifier AFTER the swipey actions modifier! This will prevent the swipey action from triggering and appearing bugged on an aborted context menu pop if the context menu animation initiates.
/// - Parameters:
/// - leading: leading edge swipey actions, ordered by ascending swipe distance from leading edge
/// - trailing: trailing edge swipey actions, ordered by ascending swipe distance from leading edge
@ViewBuilder
func addSwipeyActions(leading: [SwipeAction?], trailing: [SwipeAction?]) -> some View {
func addSwipeyActions(leading: [SwipeAction?] = [], trailing: [SwipeAction?] = []) -> some View {
modifier(
SwipeyView(
configuration: .init(
Expand Down
17 changes: 9 additions & 8 deletions Mlem/Extensions/View - Handle Lemmy Links.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,16 @@ struct HandleLemmyLinksDisplay: ViewModifier {
.environmentObject(CommunitySearchResultsTracker())
case .apiPostView(let post):
let postModel = PostModel(from: post)
let postTracker = PostTracker(
shouldPerformMergeSorting: false,
internetSpeed: internetSpeed,
initialItems: [postModel],
upvoteOnSave: upvoteOnSave
)
// swiftlint:disable:next redundant_discardable_let
let _ = postTracker.add([postModel])
ExpandedPost(post: postModel)
.environmentObject(
PostTracker(
shouldPerformMergeSorting: false,
internetSpeed: internetSpeed,
initialItems: [postModel],
upvoteOnSave: upvoteOnSave
)
)
.environmentObject(postTracker)
.environmentObject(appState)
case .apiPost(let post):
LazyLoadExpandedPost(post: post)
Expand Down
Loading

0 comments on commit d9e57a9

Please sign in to comment.