mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-12-26 00:50:38 +00:00
Add support for GIPHY + rework loading of the media in the editor
This commit is contained in:
parent
4985e69200
commit
1fa54afc3a
7 changed files with 174 additions and 54 deletions
|
@ -88,9 +88,10 @@
|
|||
9FBFE64E292A72BD00C250E9 /* Network in Frameworks */ = {isa = PBXBuildFile; productRef = 9FBFE64D292A72BD00C250E9 /* Network */; };
|
||||
9FD34823293D06E800DB0EE9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9FD34822293D06E800DB0EE9 /* Assets.xcassets */; };
|
||||
9FD542E72962D2FF0045321A /* Lists in Frameworks */ = {isa = PBXBuildFile; productRef = 9FD542E62962D2FF0045321A /* Lists */; };
|
||||
9FE0346C2ADD5C2100529EA8 /* MediaUI in Frameworks */ = {isa = PBXBuildFile; productRef = 9FE0346B2ADD5C2100529EA8 /* MediaUI */; };
|
||||
9FE151A6293C90F900E9683D /* IconSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FE151A5293C90F900E9683D /* IconSelectorView.swift */; };
|
||||
9FE3DB57296FEFCA00628CB0 /* AppAccount in Frameworks */ = {isa = PBXBuildFile; productRef = 9FE3DB56296FEFCA00628CB0 /* AppAccount */; };
|
||||
9FF614472B2EDCE500F7B0E6 /* GiphyUISDK in Frameworks */ = {isa = PBXBuildFile; productRef = 9FF614462B2EDCE500F7B0E6 /* GiphyUISDK */; };
|
||||
9FF614492B2EDCEC00F7B0E6 /* GiphyUISDK in Frameworks */ = {isa = PBXBuildFile; productRef = 9FF614482B2EDCEC00F7B0E6 /* GiphyUISDK */; };
|
||||
9FFF677C299B7B2C00FE700A /* Notifications in Frameworks */ = {isa = PBXBuildFile; productRef = 9FFF677B299B7B2C00FE700A /* Notifications */; };
|
||||
9FFF677E299B7D2800FE700A /* Status in Frameworks */ = {isa = PBXBuildFile; productRef = 9FFF677D299B7D2800FE700A /* Status */; };
|
||||
9FFF6780299B7D2B00FE700A /* DesignSystem in Frameworks */ = {isa = PBXBuildFile; productRef = 9FFF677F299B7D2B00FE700A /* DesignSystem */; };
|
||||
|
@ -228,7 +229,6 @@
|
|||
9FCBB3D429859615009B77EE /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
9FD34822293D06E800DB0EE9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
9FD542E52962D2CE0045321A /* Lists */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Lists; path = Packages/Lists; sourceTree = "<group>"; };
|
||||
9FE034692ADD597100529EA8 /* MediaUI */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = MediaUI; path = Packages/MediaUI; sourceTree = "<group>"; };
|
||||
9FE0346A2ADD59AC00529EA8 /* MediaUI */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = MediaUI; path = Packages/MediaUI; sourceTree = "<group>"; };
|
||||
9FE151A5293C90F900E9683D /* IconSelectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconSelectorView.swift; sourceTree = "<group>"; };
|
||||
9FE3DB55296FEF5800628CB0 /* AppAccount */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = AppAccount; path = Packages/AppAccount; sourceTree = "<group>"; };
|
||||
|
@ -275,6 +275,7 @@
|
|||
9FAD85A2297456A400496AB1 /* Env in Frameworks */,
|
||||
9FAD85A0297456A100496AB1 /* Models in Frameworks */,
|
||||
9FAD85A4297456A800496AB1 /* DesignSystem in Frameworks */,
|
||||
9FF614492B2EDCEC00F7B0E6 /* GiphyUISDK in Frameworks */,
|
||||
9FAD859E2974569B00496AB1 /* Account in Frameworks */,
|
||||
9FAD859C2974422700496AB1 /* AppAccount in Frameworks */,
|
||||
9FAD859A297440CB00496AB1 /* KeychainSwift in Frameworks */,
|
||||
|
@ -296,13 +297,13 @@
|
|||
9FAE4ACE29379A5A00772766 /* KeychainSwift in Frameworks */,
|
||||
9F7335EA2966B3F800AFF0BA /* Conversations in Frameworks */,
|
||||
9FE3DB57296FEFCA00628CB0 /* AppAccount in Frameworks */,
|
||||
9FE0346C2ADD5C2100529EA8 /* MediaUI in Frameworks */,
|
||||
9F398AA92935FFDB00A889F2 /* Account in Frameworks */,
|
||||
9FBFE64E292A72BD00C250E9 /* Network in Frameworks */,
|
||||
9FD542E72962D2FF0045321A /* Lists in Frameworks */,
|
||||
9F398AAB2935FFDB00A889F2 /* Models in Frameworks */,
|
||||
9F5E581929545BE700A53960 /* Env in Frameworks */,
|
||||
9F2A540A29699705009B2D7C /* ReceiptParser in Frameworks */,
|
||||
9FF614472B2EDCE500F7B0E6 /* GiphyUISDK in Frameworks */,
|
||||
9F35DB44294F9A7D00B3281A /* Status in Frameworks */,
|
||||
DA0B24FB2A6876D50045BDD7 /* SFSafeSymbols in Frameworks */,
|
||||
9F295540292B6C3400E0E81B /* Timeline in Frameworks */,
|
||||
|
@ -493,7 +494,6 @@
|
|||
9FBFE64C292A72BD00C250E9 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9FE034692ADD597100529EA8 /* MediaUI */,
|
||||
9F2A540D2969A0B0009B2D7C /* StoreKit.framework */,
|
||||
9F2A5404296995FB009B2D7C /* QuickLookUI.framework */,
|
||||
9F7335EE29674F7100AFF0BA /* QuickLook.framework */,
|
||||
|
@ -597,6 +597,7 @@
|
|||
9FAD85A1297456A400496AB1 /* Env */,
|
||||
9FAD85A3297456A800496AB1 /* DesignSystem */,
|
||||
065FA209298675BA00012EA0 /* LRUCache */,
|
||||
9FF614482B2EDCEC00F7B0E6 /* GiphyUISDK */,
|
||||
);
|
||||
productName = IceCubesShareExtension;
|
||||
productReference = 9FAD858829743F7400496AB1 /* IceCubesShareExtension.appex */;
|
||||
|
@ -636,7 +637,7 @@
|
|||
9FE3DB56296FEFCA00628CB0 /* AppAccount */,
|
||||
065FA1FD29866CD600012EA0 /* LRUCache */,
|
||||
DA0B24FA2A6876D50045BDD7 /* SFSafeSymbols */,
|
||||
9FE0346B2ADD5C2100529EA8 /* MediaUI */,
|
||||
9FF614462B2EDCE500F7B0E6 /* GiphyUISDK */,
|
||||
);
|
||||
productName = IceCubesApp;
|
||||
productReference = 9FBFE639292A715500C250E9 /* Ice Cubes.app */;
|
||||
|
@ -719,6 +720,7 @@
|
|||
9F2A540829699705009B2D7C /* XCRemoteSwiftPackageReference "purchases-ios" */,
|
||||
065FA1FC29866CD600012EA0 /* XCRemoteSwiftPackageReference "LRUCache" */,
|
||||
DA0B24F92A6876D40045BDD7 /* XCRemoteSwiftPackageReference "SFSafeSymbols" */,
|
||||
9FF614452B2EDCE500F7B0E6 /* XCRemoteSwiftPackageReference "giphy-ios-sdk" */,
|
||||
);
|
||||
productRefGroup = 9FBFE63A292A715500C250E9 /* Products */;
|
||||
projectDirPath = "";
|
||||
|
@ -1410,6 +1412,14 @@
|
|||
kind = branch;
|
||||
};
|
||||
};
|
||||
9FF614452B2EDCE500F7B0E6 /* XCRemoteSwiftPackageReference "giphy-ios-sdk" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/Giphy/giphy-ios-sdk";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 2.2.7;
|
||||
};
|
||||
};
|
||||
DA0B24F92A6876D40045BDD7 /* XCRemoteSwiftPackageReference "SFSafeSymbols" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/SFSafeSymbols/SFSafeSymbols";
|
||||
|
@ -1532,14 +1542,20 @@
|
|||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Lists;
|
||||
};
|
||||
9FE0346B2ADD5C2100529EA8 /* MediaUI */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = MediaUI;
|
||||
};
|
||||
9FE3DB56296FEFCA00628CB0 /* AppAccount */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = AppAccount;
|
||||
};
|
||||
9FF614462B2EDCE500F7B0E6 /* GiphyUISDK */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 9FF614452B2EDCE500F7B0E6 /* XCRemoteSwiftPackageReference "giphy-ios-sdk" */;
|
||||
productName = GiphyUISDK;
|
||||
};
|
||||
9FF614482B2EDCEC00F7B0E6 /* GiphyUISDK */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 9FF614452B2EDCE500F7B0E6 /* XCRemoteSwiftPackageReference "giphy-ios-sdk" */;
|
||||
productName = GiphyUISDK;
|
||||
};
|
||||
9FFF677B299B7B2C00FE700A /* Notifications */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Notifications;
|
||||
|
|
|
@ -18,6 +18,15 @@
|
|||
"version" : "3.2.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "giphy-ios-sdk",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/Giphy/giphy-ios-sdk",
|
||||
"state" : {
|
||||
"revision" : "95c32b862185e76107841b49bfff8ff7230f3b68",
|
||||
"version" : "2.2.7"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "keychain-swift",
|
||||
"kind" : "remoteSourceControl",
|
||||
|
@ -27,6 +36,15 @@
|
|||
"revision" : "f38cb0ada97847ac5068b915b8d2793b35435668"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "libwebp-xcode",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/SDWebImage/libwebp-Xcode",
|
||||
"state" : {
|
||||
"revision" : "b2b1d20a90b14d11f6ef4241da6b81c1d3f171e4",
|
||||
"version" : "1.3.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "lrucache",
|
||||
"kind" : "remoteSourceControl",
|
||||
|
|
|
@ -2,7 +2,7 @@ import Foundation
|
|||
|
||||
public enum Media: Endpoint {
|
||||
case medias
|
||||
case media(id: String, json: MediaDescriptionData)
|
||||
case media(id: String, json: MediaDescriptionData?)
|
||||
|
||||
public func path() -> String {
|
||||
switch self {
|
||||
|
@ -20,9 +20,12 @@ public enum Media: Endpoint {
|
|||
public var jsonValue: Encodable? {
|
||||
switch self {
|
||||
case let .media(_, json):
|
||||
json
|
||||
if let json {
|
||||
return json
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
nil
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
import SwiftUI
|
||||
import GiphyUISDK
|
||||
import UIKit
|
||||
import DesignSystem
|
||||
|
||||
struct GifPickerView: UIViewControllerRepresentable {
|
||||
@Environment(Theme.self) private var theme
|
||||
|
||||
var completion: ((String) -> Void)
|
||||
var onShouldDismissGifPicker: () -> Void
|
||||
|
||||
func makeUIViewController(context: Context) -> GiphyViewController {
|
||||
Giphy.configure(apiKey: "MIylJkNX57vcUNZxmSODKU9dQKBgXCkV")
|
||||
|
||||
let controller = GiphyViewController()
|
||||
controller.swiftUIEnabled = true
|
||||
controller.mediaTypeConfig = [.gifs, .stickers, .recents]
|
||||
controller.delegate = context.coordinator
|
||||
controller.navigationController?.isNavigationBarHidden = true
|
||||
controller.navigationController?.setNavigationBarHidden(true, animated: false)
|
||||
|
||||
GiphyViewController.trayHeightMultiplier = 1.0
|
||||
|
||||
controller.theme = GPHTheme(type: theme.selectedScheme == .dark ? .darkBlur : .lightBlur)
|
||||
|
||||
return controller
|
||||
}
|
||||
|
||||
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { }
|
||||
|
||||
func makeCoordinator() -> Coordinator {
|
||||
GifPickerView.Coordinator(parent: self)
|
||||
}
|
||||
|
||||
class Coordinator: NSObject, GiphyDelegate {
|
||||
var parent: GifPickerView
|
||||
|
||||
init(parent: GifPickerView) {
|
||||
self.parent = parent
|
||||
}
|
||||
|
||||
func didDismiss(controller: GiphyViewController?) {
|
||||
self.parent.onShouldDismissGifPicker()
|
||||
}
|
||||
|
||||
func didSelectMedia(giphyViewController: GiphyViewController, media: GPHMedia) {
|
||||
let url = media.url(rendition: .fixedWidth, fileType: .gif)
|
||||
parent.completion(url ?? "")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ import Models
|
|||
import NukeUI
|
||||
import PhotosUI
|
||||
import SwiftUI
|
||||
import GiphyUISDK
|
||||
|
||||
@MainActor
|
||||
struct StatusEditorAccessoryView: View {
|
||||
|
@ -24,6 +25,7 @@ struct StatusEditorAccessoryView: View {
|
|||
@State private var isPhotosPickerPresented: Bool = false
|
||||
@State private var isFileImporterPresented: Bool = false
|
||||
@State private var isCameraPickerPresented: Bool = false
|
||||
@State private var isGIFPickerPresented: Bool = false
|
||||
|
||||
var body: some View {
|
||||
@Bindable var viewModel = focusedSEVM
|
||||
|
@ -51,6 +53,12 @@ struct StatusEditorAccessoryView: View {
|
|||
} label: {
|
||||
Label("status.editor.browse-file", systemImage: "folder")
|
||||
}
|
||||
|
||||
Button {
|
||||
isGIFPickerPresented = true
|
||||
} label: {
|
||||
Label("GIPHY", systemImage: "party.popper")
|
||||
}
|
||||
} label: {
|
||||
if viewModel.isMediasLoading {
|
||||
ProgressView()
|
||||
|
@ -82,6 +90,18 @@ struct StatusEditorAccessoryView: View {
|
|||
}))
|
||||
.background(.black)
|
||||
})
|
||||
.sheet(isPresented: $isGIFPickerPresented, content: {
|
||||
GifPickerView { url in
|
||||
GPHCache.shared.downloadAssetData(url) { data, error in
|
||||
guard let data else { return }
|
||||
viewModel.processGIFData(data: data)
|
||||
}
|
||||
isGIFPickerPresented = false
|
||||
} onShouldDismissGifPicker: {
|
||||
isGIFPickerPresented = false
|
||||
}
|
||||
.presentationDetents([.medium, .large])
|
||||
})
|
||||
.accessibilityLabel("accessibility.editor.button.attach-photo")
|
||||
.disabled(viewModel.showPoll)
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import Env
|
|||
import Models
|
||||
import NukeUI
|
||||
import SwiftUI
|
||||
import MediaUI
|
||||
|
||||
@MainActor
|
||||
struct StatusEditorMediaView: View {
|
||||
|
@ -93,13 +94,13 @@ struct StatusEditorMediaView: View {
|
|||
RoundedRectangle(cornerRadius: 8).fill(.clear)
|
||||
.overlay {
|
||||
if let attachement = container.mediaAttachment {
|
||||
makeLazyImage(mediaAttachement: attachement)
|
||||
makeRemoteMediaView(mediaAttachement: attachement)
|
||||
} else if container.image != nil {
|
||||
makeLocalImage(container: container)
|
||||
} else if container.movieTransferable != nil || container.gifTransferable != nil {
|
||||
makeVideoAttachement(container: container)
|
||||
makeLocalImageView(container: container)
|
||||
} else if let error = container.error as? ServerError {
|
||||
makeErrorView(error: error)
|
||||
} else {
|
||||
placeholderView
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -116,17 +117,7 @@ struct StatusEditorMediaView: View {
|
|||
.matchedGeometryEffect(id: index, in: mediaSpace, anchor: .leading)
|
||||
}
|
||||
|
||||
private func makeVideoAttachement(container: StatusEditorMediaContainer) -> some View {
|
||||
ZStack(alignment: .center) {
|
||||
placeholderView
|
||||
if container.mediaAttachment == nil {
|
||||
ProgressView()
|
||||
}
|
||||
}
|
||||
.cornerRadius(8)
|
||||
}
|
||||
|
||||
private func makeLocalImage(container: StatusEditorMediaContainer) -> some View {
|
||||
private func makeLocalImageView(container: StatusEditorMediaContainer) -> some View {
|
||||
ZStack(alignment: .center) {
|
||||
Image(uiImage: container.image!)
|
||||
.resizable()
|
||||
|
@ -141,30 +132,29 @@ struct StatusEditorMediaView: View {
|
|||
}
|
||||
}
|
||||
|
||||
private func makeLazyImage(mediaAttachement: MediaAttachment) -> some View {
|
||||
private func makeRemoteMediaView(mediaAttachement: MediaAttachment) -> some View {
|
||||
ZStack(alignment: .center) {
|
||||
if let url = mediaAttachement.url ?? mediaAttachement.previewUrl {
|
||||
LazyImage(url: url) { state in
|
||||
if let image = state.image {
|
||||
image
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
} else {
|
||||
placeholderView
|
||||
switch mediaAttachement.supportedType {
|
||||
case .gifv, .video, .audio:
|
||||
if let url = mediaAttachement.url {
|
||||
MediaUIAttachmentVideoView(viewModel: .init(url: url, forceAutoPlay: true))
|
||||
} else {
|
||||
placeholderView
|
||||
}
|
||||
case .image:
|
||||
if let url = mediaAttachement.url ?? mediaAttachement.previewUrl {
|
||||
LazyImage(url: url) { state in
|
||||
if let image = state.image {
|
||||
image
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
} else {
|
||||
placeholderView
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
placeholderView
|
||||
}
|
||||
if mediaAttachement.url == nil {
|
||||
ProgressView()
|
||||
}
|
||||
if mediaAttachement.url != nil,
|
||||
mediaAttachement.supportedType == .video || mediaAttachement.supportedType == .gifv
|
||||
{
|
||||
Image(systemName: "play.fill")
|
||||
.font(.headline)
|
||||
.tint(.white)
|
||||
case .none:
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
.cornerRadius(8)
|
||||
|
@ -242,11 +232,18 @@ struct StatusEditorMediaView: View {
|
|||
}
|
||||
return false
|
||||
})
|
||||
viewModel.mediaContainers.removeAll {
|
||||
$0.id == container.id
|
||||
}
|
||||
}
|
||||
|
||||
private var placeholderView: some View {
|
||||
Rectangle()
|
||||
.foregroundColor(theme.secondaryBackgroundColor)
|
||||
.accessibilityHidden(true)
|
||||
ZStack(alignment: .center) {
|
||||
Rectangle()
|
||||
.foregroundColor(theme.secondaryBackgroundColor)
|
||||
.accessibilityHidden(true)
|
||||
ProgressView()
|
||||
}
|
||||
.cornerRadius(8)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -109,7 +109,7 @@ import SwiftUI
|
|||
|
||||
var isMediasLoading: Bool = false
|
||||
|
||||
private(set) var mediaContainers: [StatusEditorMediaContainer] = []
|
||||
var mediaContainers: [StatusEditorMediaContainer] = []
|
||||
var replyToStatus: Status?
|
||||
var embeddedStatus: Status?
|
||||
|
||||
|
@ -384,6 +384,19 @@ import SwiftUI
|
|||
.compactMap { NSItemProvider(contentsOf: $0) }
|
||||
processItemsProvider(items: items)
|
||||
}
|
||||
|
||||
func processGIFData(data: Data) {
|
||||
isMediasLoading = true
|
||||
let url = URL.temporaryDirectory.appending(path: "\(UUID().uuidString).gif")
|
||||
try? data.write(to: url)
|
||||
let container = StatusEditorMediaContainer(id: UUID().uuidString,
|
||||
image: nil,
|
||||
movieTransferable: nil,
|
||||
gifTransferable: .init(url: url),
|
||||
mediaAttachment: nil,
|
||||
error: nil)
|
||||
prepareToPost(for: container)
|
||||
}
|
||||
|
||||
func processCameraPhoto(image: UIImage) {
|
||||
let container = StatusEditorMediaContainer(
|
||||
|
@ -725,7 +738,7 @@ import SwiftUI
|
|||
}
|
||||
do {
|
||||
let newAttachement: MediaAttachment = try await client.get(endpoint: Media.media(id: mediaAttachement.id,
|
||||
json: .init(description: nil)))
|
||||
json: nil))
|
||||
if newAttachement.url != nil {
|
||||
let oldContainer = mediaContainers[index]
|
||||
mediaContainers[index] = StatusEditorMediaContainer(
|
||||
|
@ -736,7 +749,9 @@ import SwiftUI
|
|||
mediaAttachment: newAttachement,
|
||||
error: nil)
|
||||
}
|
||||
} catch {}
|
||||
} catch {
|
||||
print(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
try? await Task.sleep(for: .seconds(5))
|
||||
} while !Task.isCancelled
|
||||
|
|
Loading…
Reference in a new issue