From 8002223afacad1d6d708b2402af0f620dedd1990 Mon Sep 17 00:00:00 2001 From: marcoschmickler Date: Sun, 26 Jun 2022 09:43:03 +0200 Subject: [PATCH] Photos --- kplayer.xcodeproj/project.pbxproj | 4 + kplayer/core/MediaItem.swift | 1 + .../detail/DetailViewController+Show.swift | 25 ++- kplayer/detail/EditItemView.swift | 72 ++++---- kplayer/photo/SPhotoAlbumView.swift | 70 +++++++ kplayer/photo/SPhotoModel.swift | 5 + kplayer/photo/SPhotoView.swift | 172 +++++++----------- 7 files changed, 203 insertions(+), 146 deletions(-) create mode 100644 kplayer/photo/SPhotoAlbumView.swift diff --git a/kplayer.xcodeproj/project.pbxproj b/kplayer.xcodeproj/project.pbxproj index 6f37fb0..33cd621 100644 --- a/kplayer.xcodeproj/project.pbxproj +++ b/kplayer.xcodeproj/project.pbxproj @@ -54,6 +54,7 @@ 1C736B4B0889BD35DC566124 /* nspersistentcontainer-defaults-swift.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7361F01841F546FA7AFD58 /* nspersistentcontainer-defaults-swift.swift */; }; 1C736BEC4C4263EF6A89E9E3 /* SPhotoModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736F83C6190A3A0D5BD1F0 /* SPhotoModel.swift */; }; 1C736C00693A05747578DF87 /* grabber.js in Sources */ = {isa = PBXBuildFile; fileRef = 1C73619BBFA9295A11C9ACBA /* grabber.js */; }; + 1C736C76C1D80B649474F0A5 /* SPhotoAlbumView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7360A759DFDE11F631E0B6 /* SPhotoAlbumView.swift */; }; 1C736C8DAD6C2FBB9A2EA625 /* SearchItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73654AB95A2D629833BEC5 /* SearchItemView.swift */; }; 1C736C9821DA743C2E3F3B07 /* kplayer.txt in Resources */ = {isa = PBXBuildFile; fileRef = 1C7360F9649E40B7C2EAB581 /* kplayer.txt */; }; 1C736D16E81BA1FB325200E0 /* HanekeFetchOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7360744ABACC3557D05760 /* HanekeFetchOperation.swift */; }; @@ -100,6 +101,7 @@ 1C736059262A57AADE6AB761 /* Kirschkeks-256x256.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Kirschkeks-256x256.png"; path = "kplayer/Kirschkeks-256x256.png"; sourceTree = ""; }; 1C736069C214E9522BB1BD97 /* ItemCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemCell.swift; sourceTree = ""; }; 1C7360744ABACC3557D05760 /* HanekeFetchOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HanekeFetchOperation.swift; sourceTree = ""; }; + 1C7360A759DFDE11F631E0B6 /* SPhotoAlbumView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SPhotoAlbumView.swift; sourceTree = ""; }; 1C7360A94DBECA685ED8602F /* ImageLoadOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageLoadOperation.swift; sourceTree = ""; }; 1C7360B6D0757D4FB6433E7B /* AsyncImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncImage.swift; sourceTree = ""; }; 1C7360F9649E40B7C2EAB581 /* kplayer.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = kplayer.txt; sourceTree = ""; }; @@ -229,6 +231,7 @@ 1C736AD9F0A39AD543FC1176 /* SPhotoView.swift */, 1C736F83C6190A3A0D5BD1F0 /* SPhotoModel.swift */, 1C7368EFFBCD688E0D425117 /* SPhotoScrubber.swift */, + 1C7360A759DFDE11F631E0B6 /* SPhotoAlbumView.swift */, ); path = photo; sourceTree = ""; @@ -634,6 +637,7 @@ 1C73620BC7F5A1F35FFFF9FC /* SPhotoView.swift in Sources */, 1C736BEC4C4263EF6A89E9E3 /* SPhotoModel.swift in Sources */, 1C7368DBC47F0CC152504141 /* SPhotoScrubber.swift in Sources */, + 1C736C76C1D80B649474F0A5 /* SPhotoAlbumView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/kplayer/core/MediaItem.swift b/kplayer/core/MediaItem.swift index cf66f27..c56db2f 100644 --- a/kplayer/core/MediaItem.swift +++ b/kplayer/core/MediaItem.swift @@ -89,6 +89,7 @@ class MediaItem: CustomDebugStringConvertible, ObservableObject, Identifiable { var size = CGSize() + @Published var tags = [String]() var cookies = "" diff --git a/kplayer/detail/DetailViewController+Show.swift b/kplayer/detail/DetailViewController+Show.swift index 425f24c..4de0cb0 100644 --- a/kplayer/detail/DetailViewController+Show.swift +++ b/kplayer/detail/DetailViewController+Show.swift @@ -7,6 +7,15 @@ import Foundation import UIKit import SwiftUI +class SelfSizingHostingController: UIHostingController where Content: View { + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + let s = UIScreen.main.bounds.size + self.view.frame.size = s + } +} + extension DetailViewController { func showDetails(sectionItem: MediaItem, selectedItem: MediaItem) { @@ -22,18 +31,20 @@ extension DetailViewController { } func showPhotos(_ im: [MediaItem]) { - let model = SPhotoModel(allItems: im) + let base = MediaItem(name: "", path: "", root: "", type: ItemType.PICFOLDER) + base.children = im + let model = SPhotoModel(allItems: base.clone().children) - let view = SPhotoView(completionHandler: { saved in - self.collectionView.reloadData() - self.collectionView.collectionViewLayout.invalidateLayout() + let view = SPhotoAlbumView(completionHandler: { saved in + // self.collectionView.reloadData() + // self.collectionView.collectionViewLayout.invalidateLayout() self.dismiss(animated: true, completion: nil); }, model: model) - let pc = UIHostingController(rootView: view) + let pc = SelfSizingHostingController(rootView: view) pc.view.backgroundColor = .black getWindow().rootViewController!.definesPresentationContext = true @@ -68,7 +79,7 @@ extension DetailViewController { self.dismiss(animated: true, completion: nil); } - let pc = UIHostingController(rootView: webView) + let pc = SelfSizingHostingController(rootView: webView) pc.view.backgroundColor = .black getWindow().rootViewController!.definesPresentationContext = true @@ -140,7 +151,7 @@ extension DetailViewController { }, model: model) player - let pc = UIHostingController(rootView: player) + let pc = SelfSizingHostingController(rootView: player) pc.view.backgroundColor = .black getWindow().rootViewController!.definesPresentationContext = true diff --git a/kplayer/detail/EditItemView.swift b/kplayer/detail/EditItemView.swift index 3c07d18..ab6134c 100644 --- a/kplayer/detail/EditItemView.swift +++ b/kplayer/detail/EditItemView.swift @@ -15,6 +15,43 @@ protocol EditItemDelegate { func seek(_ : Double) } +struct TagEditor: View { + @ObservedObject + var item: MediaItem + + init(item: MediaItem) { + self.item = item + } + + var body: some View { + FlexibleView( + data: DatabaseManager.sharedInstance.allTags, + spacing: 15, + alignment: .leading + ) { tag in + Button(action: { + if item.tags.contains(tag) { + item.tags.removeAll(where: { toRemove in toRemove == tag }) + } + else { + item.tags.append(tag) + } + print(tag) + }, label: { + Text(verbatim: tag) + .padding(8) + .background( + RoundedRectangle(cornerRadius: 8) + .fill( item.tags.contains(tag) ? + Color.yellow.opacity(0.4) : + Color.gray.opacity(0.2) + ) + ) + }).buttonStyle(BorderlessButtonStyle()) + } + } +} + struct EditItemView: View { @ObservedObject var item: MediaItem @@ -66,9 +103,6 @@ struct EditItemView: View { Text("End") }).buttonStyle(BorderlessButtonStyle()); } -// Toggle(isOn: $item.loop, label: { -// Text("Loop") -// }) Text("Zoom \(item.scale, specifier: "%.1f") X \(item.offset.x, specifier: "%.1f") Y \(item.offset.y, specifier: "%.1f") ") HStack { Button(action: delegate.captureZoom, label: { @@ -94,31 +128,7 @@ struct EditItemView: View { Text("cancel") }).padding(5).buttonStyle(BorderlessButtonStyle()); } - FlexibleView( - data: DatabaseManager.sharedInstance.allTags, - spacing: 15, - alignment: .leading - ) { tag in - Button(action: { - if item.tags.contains(tag) { - item.tags.removeAll(where: { toRemove in toRemove == tag }) - } - else { - item.tags.append(tag) - } - print(tag) - }, label: { - Text(verbatim: tag) - .padding(8) - .background( - RoundedRectangle(cornerRadius: 8) - .fill( item.tags.contains(tag) ? - Color.yellow.opacity(0.4) : - Color.gray.opacity(0.2) - ) - ) - }).buttonStyle(BorderlessButtonStyle()) - } + TagEditor(item: item) }.background(Color.clear) }.background(Color.clear) @@ -142,10 +152,4 @@ struct EditItemView: View { return String(format: "%02d:%.1f", min, sec) } } -// -//struct EditItemView_Previews: PreviewProvider { -// static var previews: some View { -// EditItemView(item: MediaItem(name: "extern", path: "", root: "", type: ItemType.FAVROOT), len: 1000, delegate: SVideoPlayer(completionHandler: nil, model: SVideoModel(allItems: [](),currentSnapshot: nil, ))) -// } -//} diff --git a/kplayer/photo/SPhotoAlbumView.swift b/kplayer/photo/SPhotoAlbumView.swift new file mode 100644 index 0000000..06df843 --- /dev/null +++ b/kplayer/photo/SPhotoAlbumView.swift @@ -0,0 +1,70 @@ +// +// Created by Marco Schmickler on 26.06.22. +// Copyright (c) 2022 Marco Schmickler. All rights reserved. +// + +import Foundation +import SwiftUI + +struct SPhotoAlbumView: View { + var completionHandler: ((Bool) -> Void)? + @ObservedObject + var model: SPhotoModel + + @State var more = false + @State var edit = false + + init(completionHandler: ((Bool) -> ())?, model: SPhotoModel) { + self.completionHandler = completionHandler + self.model = model + } + + var body: some View { + let v = VStack { + HStack { + Group { + Button(action: { + cleanup() + completionHandler!(true) + }, label: { + //Text("cancel") + Text("X").frame(width: 30) + })//.foregroundColor(update ? Color.yellow : Color.blue) + .buttonStyle(BorderlessButtonStyle()) + Button(action: { + more.toggle() + }, label: { + //Text("cancel") + Text("\(model.index)").frame(width: 70) + })//.foregroundColor(update ? Color.yellow : Color.blue) + .buttonStyle(BorderlessButtonStyle()) + Spacer() + SPhotoScrubber(model: model) + } + } + .frame(height: 50) + SPhotoView(model: model) + } + if more { + v.overlay(VStack { + KToggleButton(text: "spring", binding: $model.spring).frame(height: 30) + KToggleButton(text: "edit", binding: $edit).frame(height: 30) + } + .frame(width: 60, alignment: .top).offset(x: 0, y: 70), alignment: .topLeading).overlay(TagEditor(item: model.allItems[model.index]) + .frame(width: 60, alignment: .top).offset(x: 0, y: 70), + alignment: .topTrailing) + } + else { + v + } + + } + + func cleanup() { + for i in model.allItems { + i.thumbImage = nil + i.image = nil + } + } +} + diff --git a/kplayer/photo/SPhotoModel.swift b/kplayer/photo/SPhotoModel.swift index eebd2a1..71532ce 100644 --- a/kplayer/photo/SPhotoModel.swift +++ b/kplayer/photo/SPhotoModel.swift @@ -4,6 +4,7 @@ // import Foundation +import SwiftUI class SPhotoModel : ObservableObject { @Published var allItems : [MediaItem] @@ -13,6 +14,10 @@ class SPhotoModel : ObservableObject { @Published var thumbIndex = 0 @Published var scrub = false + @Published var scale: CGFloat = 1.0 + @Published var dragOffset: CGSize = CGSize.zero + @Published var spring = false + init(allItems: [MediaItem]) { self.allItems = allItems selectedItem = allItems[0] diff --git a/kplayer/photo/SPhotoView.swift b/kplayer/photo/SPhotoView.swift index ccfc2d3..b602cea 100644 --- a/kplayer/photo/SPhotoView.swift +++ b/kplayer/photo/SPhotoView.swift @@ -18,133 +18,95 @@ struct KAnimate: ViewModifier { func body(content: Content) -> some View { if (spring) { content.animation(.spring(response: 10, dampingFraction: 0.05), value: dragOffset) - } - else { + } else { content.animation(.easeOut(duration: 3), value: dragOffset) } } } struct SPhotoView: View { - var completionHandler: ((Bool) -> Void)? @ObservedObject var model: SPhotoModel - @State var scale: CGFloat = 1.0 + + @State var lastScale: CGFloat = 1.75 @State var lastScaleValue: CGFloat = 1.0 @State var lastDragOffset: CGSize = CGSize.zero - @State var dragOffset: CGSize = CGSize.zero @State var lastDrag: CGSize = CGSize.zero @State var skip = false - @State var more = false - @State var spring = false @State var animateX = 0 @State var dampen = 0.05 + init(model: SPhotoModel) { + self.model = model + } + var body: some View { - let v = VStack { - HStack { - Group { - Button(action: { - cleanup() - completionHandler!(true) - }, label: { - //Text("cancel") - Text("X").frame(width: 30) - })//.foregroundColor(update ? Color.yellow : Color.blue) - .buttonStyle(BorderlessButtonStyle()) - Button(action: { - more.toggle() - }, label: { - //Text("cancel") - Text("\(model.index)").frame(width: 70) - })//.foregroundColor(update ? Color.yellow : Color.blue) - .buttonStyle(BorderlessButtonStyle()) - Spacer() - SPhotoScrubber(model: model) - } - }.frame(height: 50) - GeometryReader { geo in - // AsyncImage(item: model.selectedItem, thumb: false, placeholder: { Text("Loading ...") }, image: { Image(uiImage: $0).resizable() }) - SwiftUI.AsyncImage(url: URL(string: model.allItems[model.index].imageUrlAbsolute)) { image in - image.resizable().scaledToFill() + + GeometryReader { geo in + // AsyncImage(item: model.selectedItem, thumb: false, placeholder: { Text("Loading ...") }, image: { Image(uiImage: $0).resizable() }) + SwiftUI.AsyncImage(url: URL(string: model.allItems[model.index].imageUrlAbsolute)) { image in + image.resizable().scaledToFit() + } + placeholder: { + if let i = model.selectedItem.thumbImage { + Image(uiImage: i).resizable().scaledToFit() + } else { + Text("...") } - placeholder: { - if let i = model.selectedItem.thumbImage { - Image(uiImage: i).resizable().scaledToFill() - } else { - Text("...") - } + } + .frame(height: geo.size.height).frame(width: geo.size.width) + .scaleEffect(model.scale).offset(model.dragOffset).modifier(KAnimate(dragOffset: model.dragOffset, spring: model.spring)) + .onTapGesture(count: 2) { + print("3 tapped!") + if model.scale == 1 { + model.scale = lastScale + } else { + lastScale = model.scale + model.scale = 1 + model.dragOffset = CGSize.zero } - .frame(height: geo.size.height - 50) - .scaleEffect(scale).offset(dragOffset).modifier(KAnimate(dragOffset: dragOffset, spring: spring)) - .gesture( - DragGesture() - .onChanged { gesture in - let dragged = gesture.translation - let multi = (spring) ? 2.0 : 3.0 - - if gesture.startLocation.y < 100 && gesture.startLocation.x < 400 { - if dragged.width > 50 && model.index < model.allItems.count - 1 && !skip { - model.index += 1 - skip = true - } - if dragged.width < -50 && model.index > 0 && !skip { - model.index -= 1 - skip = true - } - } else { - if (abs(dragged.width - lastDrag.width) > 20) { + } + .gesture( + DragGesture() + .onChanged { gesture in + let dragged = gesture.translation + let multi = (model.spring) ? 2.0 : 3.0 - } else { - dragOffset = CGSize(width: (dragged.width * multi) + lastDragOffset.width, height: (dragged.height * multi) + lastDragOffset.height) - } + if gesture.startLocation.y < 100 && gesture.startLocation.x < 400 { + if dragged.width > 50 && model.index < model.allItems.count - 1 && !skip { + model.index += 1 + skip = true + } + if dragged.width < -50 && model.index > 0 && !skip { + model.index -= 1 + skip = true + } + } else { + if (dragged.height * multi) + lastDragOffset.height > -1000 { + model.dragOffset = CGSize(width: (dragged.width * multi) + lastDragOffset.width, height: (dragged.height * multi) + lastDragOffset.height) } - dampen = 0.05 - lastDrag = dragged - } - .onEnded { gesture in - lastDragOffset = dragOffset - dampen = 0.05 - skip = false } - ) - .gesture(MagnificationGesture() - .onChanged { val in - let delta = val / self.lastScaleValue - self.lastScaleValue = val - scale = self.scale * delta - -//... anything else e.g. clamping the newScale - } - .onEnded { val in - // without this the next gesture will be broken - self.lastScaleValue = 1.0 - }) - - } - .contentShape(Rectangle()).clipped(); - - } - - if more { - v.overlay(VStack { - Button(action: { - spring.toggle() - }, label: { - Text("spring") - }).foregroundColor(spring ? Color.yellow : Color.blue) - .buttonStyle(BorderlessButtonStyle()) - }.frame(width: 50, alignment: .top).offset(x: 0, y: 70), alignment: .topLeading) - } - else { - v - } - } + dampen = 0.05 + lastDrag = dragged + } + .onEnded { gesture in + lastDragOffset = model.dragOffset + dampen = 0.05 + skip = false + } + ) + .gesture(MagnificationGesture() + .onChanged { val in + let delta = val / self.lastScaleValue + self.lastScaleValue = val + model.scale = model.scale * delta + } + .onEnded { val in + // without this the next gesture will be broken + self.lastScaleValue = 1.0 + }) - func cleanup() { - for i in model.allItems { - i.thumbImage = nil - i.image = nil } + .contentShape(Rectangle()).clipped(); } }