Browse Source

Photos

master
marcoschmickler 4 years ago
parent
commit
8002223afa
  1. 4
      kplayer.xcodeproj/project.pbxproj
  2. 1
      kplayer/core/MediaItem.swift
  3. 25
      kplayer/detail/DetailViewController+Show.swift
  4. 72
      kplayer/detail/EditItemView.swift
  5. 70
      kplayer/photo/SPhotoAlbumView.swift
  6. 5
      kplayer/photo/SPhotoModel.swift
  7. 92
      kplayer/photo/SPhotoView.swift

4
kplayer.xcodeproj/project.pbxproj

@ -54,6 +54,7 @@
1C736B4B0889BD35DC566124 /* nspersistentcontainer-defaults-swift.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7361F01841F546FA7AFD58 /* nspersistentcontainer-defaults-swift.swift */; }; 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 */; }; 1C736BEC4C4263EF6A89E9E3 /* SPhotoModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736F83C6190A3A0D5BD1F0 /* SPhotoModel.swift */; };
1C736C00693A05747578DF87 /* grabber.js in Sources */ = {isa = PBXBuildFile; fileRef = 1C73619BBFA9295A11C9ACBA /* grabber.js */; }; 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 */; }; 1C736C8DAD6C2FBB9A2EA625 /* SearchItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73654AB95A2D629833BEC5 /* SearchItemView.swift */; };
1C736C9821DA743C2E3F3B07 /* kplayer.txt in Resources */ = {isa = PBXBuildFile; fileRef = 1C7360F9649E40B7C2EAB581 /* kplayer.txt */; }; 1C736C9821DA743C2E3F3B07 /* kplayer.txt in Resources */ = {isa = PBXBuildFile; fileRef = 1C7360F9649E40B7C2EAB581 /* kplayer.txt */; };
1C736D16E81BA1FB325200E0 /* HanekeFetchOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7360744ABACC3557D05760 /* HanekeFetchOperation.swift */; }; 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 = "<group>"; }; 1C736059262A57AADE6AB761 /* Kirschkeks-256x256.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Kirschkeks-256x256.png"; path = "kplayer/Kirschkeks-256x256.png"; sourceTree = "<group>"; };
1C736069C214E9522BB1BD97 /* ItemCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemCell.swift; sourceTree = "<group>"; }; 1C736069C214E9522BB1BD97 /* ItemCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemCell.swift; sourceTree = "<group>"; };
1C7360744ABACC3557D05760 /* HanekeFetchOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HanekeFetchOperation.swift; sourceTree = "<group>"; }; 1C7360744ABACC3557D05760 /* HanekeFetchOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HanekeFetchOperation.swift; sourceTree = "<group>"; };
1C7360A759DFDE11F631E0B6 /* SPhotoAlbumView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SPhotoAlbumView.swift; sourceTree = "<group>"; };
1C7360A94DBECA685ED8602F /* ImageLoadOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageLoadOperation.swift; sourceTree = "<group>"; }; 1C7360A94DBECA685ED8602F /* ImageLoadOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageLoadOperation.swift; sourceTree = "<group>"; };
1C7360B6D0757D4FB6433E7B /* AsyncImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncImage.swift; sourceTree = "<group>"; }; 1C7360B6D0757D4FB6433E7B /* AsyncImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncImage.swift; sourceTree = "<group>"; };
1C7360F9649E40B7C2EAB581 /* kplayer.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = kplayer.txt; sourceTree = "<group>"; }; 1C7360F9649E40B7C2EAB581 /* kplayer.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = kplayer.txt; sourceTree = "<group>"; };
@ -229,6 +231,7 @@
1C736AD9F0A39AD543FC1176 /* SPhotoView.swift */, 1C736AD9F0A39AD543FC1176 /* SPhotoView.swift */,
1C736F83C6190A3A0D5BD1F0 /* SPhotoModel.swift */, 1C736F83C6190A3A0D5BD1F0 /* SPhotoModel.swift */,
1C7368EFFBCD688E0D425117 /* SPhotoScrubber.swift */, 1C7368EFFBCD688E0D425117 /* SPhotoScrubber.swift */,
1C7360A759DFDE11F631E0B6 /* SPhotoAlbumView.swift */,
); );
path = photo; path = photo;
sourceTree = "<group>"; sourceTree = "<group>";
@ -634,6 +637,7 @@
1C73620BC7F5A1F35FFFF9FC /* SPhotoView.swift in Sources */, 1C73620BC7F5A1F35FFFF9FC /* SPhotoView.swift in Sources */,
1C736BEC4C4263EF6A89E9E3 /* SPhotoModel.swift in Sources */, 1C736BEC4C4263EF6A89E9E3 /* SPhotoModel.swift in Sources */,
1C7368DBC47F0CC152504141 /* SPhotoScrubber.swift in Sources */, 1C7368DBC47F0CC152504141 /* SPhotoScrubber.swift in Sources */,
1C736C76C1D80B649474F0A5 /* SPhotoAlbumView.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };

1
kplayer/core/MediaItem.swift

@ -89,6 +89,7 @@ class MediaItem: CustomDebugStringConvertible, ObservableObject, Identifiable {
var size = CGSize() var size = CGSize()
@Published
var tags = [String]() var tags = [String]()
var cookies = "" var cookies = ""

25
kplayer/detail/DetailViewController+Show.swift

@ -7,6 +7,15 @@ import Foundation
import UIKit import UIKit
import SwiftUI import SwiftUI
class SelfSizingHostingController<Content>: UIHostingController<Content> where Content: View {
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let s = UIScreen.main.bounds.size
self.view.frame.size = s
}
}
extension DetailViewController { extension DetailViewController {
func showDetails(sectionItem: MediaItem, selectedItem: MediaItem) { func showDetails(sectionItem: MediaItem, selectedItem: MediaItem) {
@ -22,18 +31,20 @@ extension DetailViewController {
} }
func showPhotos(_ im: [MediaItem]) { 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); self.dismiss(animated: true, completion: nil);
}, model: model) }, model: model)
let pc = UIHostingController(rootView: view)
let pc = SelfSizingHostingController(rootView: view)
pc.view.backgroundColor = .black pc.view.backgroundColor = .black
getWindow().rootViewController!.definesPresentationContext = true getWindow().rootViewController!.definesPresentationContext = true
@ -68,7 +79,7 @@ extension DetailViewController {
self.dismiss(animated: true, completion: nil); self.dismiss(animated: true, completion: nil);
} }
let pc = UIHostingController(rootView: webView)
let pc = SelfSizingHostingController(rootView: webView)
pc.view.backgroundColor = .black pc.view.backgroundColor = .black
getWindow().rootViewController!.definesPresentationContext = true getWindow().rootViewController!.definesPresentationContext = true
@ -140,7 +151,7 @@ extension DetailViewController {
}, model: model) }, model: model)
player player
let pc = UIHostingController(rootView: player)
let pc = SelfSizingHostingController(rootView: player)
pc.view.backgroundColor = .black pc.view.backgroundColor = .black
getWindow().rootViewController!.definesPresentationContext = true getWindow().rootViewController!.definesPresentationContext = true

72
kplayer/detail/EditItemView.swift

@ -15,6 +15,43 @@ protocol EditItemDelegate {
func seek(_ : Double) 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 { struct EditItemView: View {
@ObservedObject @ObservedObject
var item: MediaItem var item: MediaItem
@ -66,9 +103,6 @@ struct EditItemView: View {
Text("End") Text("End")
}).buttonStyle(BorderlessButtonStyle()); }).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") ") Text("Zoom \(item.scale, specifier: "%.1f") X \(item.offset.x, specifier: "%.1f") Y \(item.offset.y, specifier: "%.1f") ")
HStack { HStack {
Button(action: delegate.captureZoom, label: { Button(action: delegate.captureZoom, label: {
@ -94,31 +128,7 @@ struct EditItemView: View {
Text("cancel") Text("cancel")
}).padding(5).buttonStyle(BorderlessButtonStyle()); }).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)
}.background(Color.clear) }.background(Color.clear)
@ -142,10 +152,4 @@ struct EditItemView: View {
return String(format: "%02d:%.1f", min, sec) 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, )))
// }
//}

70
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
}
}
}

5
kplayer/photo/SPhotoModel.swift

@ -4,6 +4,7 @@
// //
import Foundation import Foundation
import SwiftUI
class SPhotoModel : ObservableObject { class SPhotoModel : ObservableObject {
@Published var allItems : [MediaItem] @Published var allItems : [MediaItem]
@ -13,6 +14,10 @@ class SPhotoModel : ObservableObject {
@Published var thumbIndex = 0 @Published var thumbIndex = 0
@Published var scrub = false @Published var scrub = false
@Published var scale: CGFloat = 1.0
@Published var dragOffset: CGSize = CGSize.zero
@Published var spring = false
init(allItems: [MediaItem]) { init(allItems: [MediaItem]) {
self.allItems = allItems self.allItems = allItems
selectedItem = allItems[0] selectedItem = allItems[0]

92
kplayer/photo/SPhotoView.swift

@ -18,70 +18,59 @@ struct KAnimate: ViewModifier {
func body(content: Content) -> some View { func body(content: Content) -> some View {
if (spring) { if (spring) {
content.animation(.spring(response: 10, dampingFraction: 0.05), value: dragOffset) content.animation(.spring(response: 10, dampingFraction: 0.05), value: dragOffset)
}
else {
} else {
content.animation(.easeOut(duration: 3), value: dragOffset) content.animation(.easeOut(duration: 3), value: dragOffset)
} }
} }
} }
struct SPhotoView: View { struct SPhotoView: View {
var completionHandler: ((Bool) -> Void)?
@ObservedObject @ObservedObject
var model: SPhotoModel var model: SPhotoModel
@State var scale: CGFloat = 1.0
@State var lastScale: CGFloat = 1.75
@State var lastScaleValue: CGFloat = 1.0 @State var lastScaleValue: CGFloat = 1.0
@State var lastDragOffset: CGSize = CGSize.zero @State var lastDragOffset: CGSize = CGSize.zero
@State var dragOffset: CGSize = CGSize.zero
@State var lastDrag: CGSize = CGSize.zero @State var lastDrag: CGSize = CGSize.zero
@State var skip = false @State var skip = false
@State var more = false
@State var spring = false
@State var animateX = 0 @State var animateX = 0
@State var dampen = 0.05 @State var dampen = 0.05
init(model: SPhotoModel) {
self.model = model
}
var body: some View { 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 GeometryReader { geo in
// AsyncImage(item: model.selectedItem, thumb: false, placeholder: { Text("Loading ...") }, image: { Image(uiImage: $0).resizable() }) // 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 SwiftUI.AsyncImage(url: URL(string: model.allItems[model.index].imageUrlAbsolute)) { image in
image.resizable().scaledToFill()
image.resizable().scaledToFit()
} }
placeholder: { placeholder: {
if let i = model.selectedItem.thumbImage { if let i = model.selectedItem.thumbImage {
Image(uiImage: i).resizable().scaledToFill()
Image(uiImage: i).resizable().scaledToFit()
} else { } else {
Text("...") Text("...")
} }
} }
.frame(height: geo.size.height - 50)
.scaleEffect(scale).offset(dragOffset).modifier(KAnimate(dragOffset: dragOffset, spring: spring))
.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
}
}
.gesture( .gesture(
DragGesture() DragGesture()
.onChanged { gesture in .onChanged { gesture in
let dragged = gesture.translation let dragged = gesture.translation
let multi = (spring) ? 2.0 : 3.0
let multi = (model.spring) ? 2.0 : 3.0
if gesture.startLocation.y < 100 && gesture.startLocation.x < 400 { if gesture.startLocation.y < 100 && gesture.startLocation.x < 400 {
if dragged.width > 50 && model.index < model.allItems.count - 1 && !skip { if dragged.width > 50 && model.index < model.allItems.count - 1 && !skip {
@ -93,17 +82,15 @@ struct SPhotoView: View {
skip = true skip = true
} }
} else { } else {
if (abs(dragged.width - lastDrag.width) > 20) {
} else {
dragOffset = CGSize(width: (dragged.width * multi) + lastDragOffset.width, height: (dragged.height * multi) + lastDragOffset.height)
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 dampen = 0.05
lastDrag = dragged lastDrag = dragged
} }
.onEnded { gesture in .onEnded { gesture in
lastDragOffset = dragOffset
lastDragOffset = model.dragOffset
dampen = 0.05 dampen = 0.05
skip = false skip = false
} }
@ -112,9 +99,7 @@ struct SPhotoView: View {
.onChanged { val in .onChanged { val in
let delta = val / self.lastScaleValue let delta = val / self.lastScaleValue
self.lastScaleValue = val self.lastScaleValue = val
scale = self.scale * delta
//... anything else e.g. clamping the newScale
model.scale = model.scale * delta
} }
.onEnded { val in .onEnded { val in
// without this the next gesture will be broken // without this the next gesture will be broken
@ -123,28 +108,5 @@ struct SPhotoView: View {
} }
.contentShape(Rectangle()).clipped(); .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
}
}
func cleanup() {
for i in model.allItems {
i.thumbImage = nil
i.image = nil
}
} }
} }
Loading…
Cancel
Save