2 Commits

Author SHA1 Message Date
marcoschmickler cfb72bae67 Photos 4 years ago
marcoschmickler 36a21cef01 Photos 4 years ago
  1. 4
      kplayer.xcodeproj/project.pbxproj
  2. 2
      kplayer/core/LocalManager.swift
  3. 2
      kplayer/core/MediaItem.swift
  4. 20
      kplayer/photo/SPhotoModel.swift
  5. 89
      kplayer/photo/SPhotoScrubber.swift
  6. 125
      kplayer/photo/SPhotoView.swift
  7. 11
      kplayer/util/AsyncImage.swift

4
kplayer.xcodeproj/project.pbxproj

@ -39,6 +39,7 @@
1C736821D6DF2237A3EABCC1 /* ViewControllerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73648CEC974A2500172064 /* ViewControllerExtensions.swift */; }; 1C736821D6DF2237A3EABCC1 /* ViewControllerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73648CEC974A2500172064 /* ViewControllerExtensions.swift */; };
1C7368242038C0FF6C9631E7 /* VideoHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7364709899FF62774B0199 /* VideoHelper.swift */; }; 1C7368242038C0FF6C9631E7 /* VideoHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7364709899FF62774B0199 /* VideoHelper.swift */; };
1C73688D13E5A804880C8768 /* UIImageExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736DCCE3AA9993E15F7652 /* UIImageExtension.swift */; }; 1C73688D13E5A804880C8768 /* UIImageExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736DCCE3AA9993E15F7652 /* UIImageExtension.swift */; };
1C7368DBC47F0CC152504141 /* SPhotoScrubber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7368EFFBCD688E0D425117 /* SPhotoScrubber.swift */; };
1C73691A9C7174E0C6B57267 /* stringutil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736B794396F2E50387B8F2 /* stringutil.swift */; }; 1C73691A9C7174E0C6B57267 /* stringutil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736B794396F2E50387B8F2 /* stringutil.swift */; };
1C73693A1334A7792856FC58 /* MasterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73611D226B48C24DB37535 /* MasterViewController.swift */; }; 1C73693A1334A7792856FC58 /* MasterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73611D226B48C24DB37535 /* MasterViewController.swift */; };
1C736953BDBBAFC40884132A /* BrowserController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73602350ACE2436736F981 /* BrowserController.swift */; }; 1C736953BDBBAFC40884132A /* BrowserController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73602350ACE2436736F981 /* BrowserController.swift */; };
@ -132,6 +133,7 @@
1C7367ECBD369A2A0C94C499 /* FileHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileHelper.swift; sourceTree = "<group>"; }; 1C7367ECBD369A2A0C94C499 /* FileHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileHelper.swift; sourceTree = "<group>"; };
1C73685B4BBFDAFBF08C032C /* readme.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = readme.md; sourceTree = "<group>"; }; 1C73685B4BBFDAFBF08C032C /* readme.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = readme.md; sourceTree = "<group>"; };
1C73688DAB88F9360FB62A49 /* MediaItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaItem.swift; sourceTree = "<group>"; }; 1C73688DAB88F9360FB62A49 /* MediaItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaItem.swift; sourceTree = "<group>"; };
1C7368EFFBCD688E0D425117 /* SPhotoScrubber.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SPhotoScrubber.swift; sourceTree = "<group>"; };
1C7369BED02028D8564E82D5 /* pathfinder.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.scpt; path = pathfinder.scpt; sourceTree = "<group>"; }; 1C7369BED02028D8564E82D5 /* pathfinder.scpt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.scpt; path = pathfinder.scpt; sourceTree = "<group>"; };
1C7369EC16B19B32B515169E /* NetData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetData.swift; sourceTree = "<group>"; }; 1C7369EC16B19B32B515169E /* NetData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetData.swift; sourceTree = "<group>"; };
1C7369F53095B7A4D65679C2 /* DetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailViewController.swift; sourceTree = "<group>"; }; 1C7369F53095B7A4D65679C2 /* DetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailViewController.swift; sourceTree = "<group>"; };
@ -226,6 +228,7 @@
1C73673DC671535E3A049F54 /* PhotoController.swift */, 1C73673DC671535E3A049F54 /* PhotoController.swift */,
1C736AD9F0A39AD543FC1176 /* SPhotoView.swift */, 1C736AD9F0A39AD543FC1176 /* SPhotoView.swift */,
1C736F83C6190A3A0D5BD1F0 /* SPhotoModel.swift */, 1C736F83C6190A3A0D5BD1F0 /* SPhotoModel.swift */,
1C7368EFFBCD688E0D425117 /* SPhotoScrubber.swift */,
); );
path = photo; path = photo;
sourceTree = "<group>"; sourceTree = "<group>";
@ -630,6 +633,7 @@
1C736C00693A05747578DF87 /* grabber.js in Sources */, 1C736C00693A05747578DF87 /* grabber.js in Sources */,
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 */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };

2
kplayer/core/LocalManager.swift

@ -10,7 +10,7 @@ class LocalManager {
var settings = KSettings() var settings = KSettings()
var authenticated = false
var authenticated = true
var favorites = MediaItem(name: "fav", path: "", root: "", type: ItemType.FAVROOT) var favorites = MediaItem(name: "fav", path: "", root: "", type: ItemType.FAVROOT)

2
kplayer/core/MediaItem.swift

@ -207,7 +207,7 @@ class MediaItem: CustomDebugStringConvertible, ObservableObject, Identifiable {
} }
let enc = thumbUrl!.addingPercentEncoding(withAllowedCharacters: NSCharacterSet.urlQueryAllowed)! let enc = thumbUrl!.addingPercentEncoding(withAllowedCharacters: NSCharacterSet.urlQueryAllowed)!
return NetworkManager.sharedInstance.vidurl + enc.substringStartingFrom(10)
return NetworkManager.sharedInstance.baseurl + "/service/download/srv/samba" + enc.substringStartingFrom(10)
} }
var nameWithoutExtension: String { var nameWithoutExtension: String {

20
kplayer/photo/SPhotoModel.swift

@ -7,11 +7,31 @@ import Foundation
class SPhotoModel : ObservableObject { class SPhotoModel : ObservableObject {
@Published var allItems : [MediaItem] @Published var allItems : [MediaItem]
@Published var indexItems : [MediaItem]
@Published var selectedItem : MediaItem @Published var selectedItem : MediaItem
@Published var index = 0 @Published var index = 0
@Published var thumbIndex = 0
@Published var scrub = false
init(allItems: [MediaItem]) { init(allItems: [MediaItem]) {
self.allItems = allItems self.allItems = allItems
selectedItem = allItems[0] selectedItem = allItems[0]
let max = 17
if allItems.count < max {
indexItems = allItems
}
else {
indexItems = [MediaItem]()
var nj = -1
for i in 0...allItems.count-1 {
let j = Int((Double(i) / Double(allItems.count)) * 17.0)
if j > nj {
nj = j
indexItems.append(allItems[i])
}
}
}
} }
} }

89
kplayer/photo/SPhotoScrubber.swift

@ -0,0 +1,89 @@
//
// Created by Marco Schmickler on 25.06.22.
// Copyright (c) 2022 Marco Schmickler. All rights reserved.
//
import Foundation
import SwiftUI
import Haneke
struct SPhotoScrubber: View {
@ObservedObject var model: SPhotoModel
@State var proxy: GeometryProxy?
init(model: SPhotoModel) {
self.model = model
}
var body: some View {
GeometryReader { geometry in
HStack {
ForEach(model.indexItems) { item in
AsyncImage(item: item, placeholder: { Text("Loading ...") },
image: { Image(uiImage: $0).resizable() })
}
.frame(height: 50)
}
.onAppear() {
proxy = geometry
}
.gesture(
DragGesture()
.onChanged { gesture in
let dragged = gesture.translation
let xpos = Double(gesture.startLocation.x + dragged.width)
let width = Double(proxy!.size.width - 30)
let i = Int(Double(model.allItems.count - 1) * (xpos / width))
setIndex(i: i)
print("size \(width) xpos \(xpos) i \(i)")
model.scrub = true
}
.onEnded { gesture in
let xpos = gesture.predictedEndLocation.x
let width = Double(proxy!.size.width - 30)
let i = Int(Double(model.allItems.count - 1) * (xpos / width))
setIndex(i: i)
model.scrub = false
print("size \(width) xpos \(xpos) i \(i)")
}
)
}
}
private func setIndex(i: Int) {
if (i < model.allItems.count && i >= 0) {
model.index = i
let item = model.allItems[i]
if item === model.selectedItem {
return
}
model.selectedItem = item
if item.thumbUrl != nil && item.thumbImage == nil {
item.thumbImage = UIImage(systemName: "repeat")
let URL = Foundation.URL(string: item.thumbUrlAbsolute)!
// print("fetch \(item.thumbUrlAbsolute)")
Shared.imageCache.fetch(URL: URL).onSuccess {
i in
//newItem.image = i
item.thumbImage = i
}
}
}
}
}

125
kplayer/photo/SPhotoView.swift

@ -6,40 +6,108 @@
import Foundation import Foundation
import SwiftUI import SwiftUI
struct KAnimate: ViewModifier {
var dragOffset: CGSize
var spring: Bool
init(dragOffset: CGSize, spring: Bool) {
self.dragOffset = dragOffset
self.spring = spring
}
func body(content: Content) -> some View {
if (spring) {
content.animation(.spring(response: 10, dampingFraction: 0.05), value: dragOffset)
}
else {
content.animation(.easeOut(duration: 3), value: dragOffset)
}
}
}
struct SPhotoView: View { struct SPhotoView: View {
var completionHandler: ((Bool) -> Void)? var completionHandler: ((Bool) -> Void)?
@ObservedObject @ObservedObject
var model: SPhotoModel var model: SPhotoModel
@State
var update = false
@State var scale: CGFloat = 1.0 @State var scale: CGFloat = 1.0
@State var lastScaleValue: CGFloat = 1.0 @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
var body: some View { var body: some View {
VStack {
let v = VStack {
HStack { HStack {
Group { Group {
Button(action: { Button(action: {
cleanup()
completionHandler!(true) completionHandler!(true)
}, label: { }, label: {
Text("cancel")
}).foregroundColor(update ? Color.yellow : Color.blue)
//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()) .buttonStyle(BorderlessButtonStyle())
Spacer() Spacer()
SPhotoScrubber(model: model)
} }
}.frame(height: 50) }.frame(height: 50)
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.selectedItem.imageUrlAbsolute)){ image in
image //.resizable();//.scaledToFit()
SwiftUI.AsyncImage(url: URL(string: model.allItems[model.index].imageUrlAbsolute)) { image in
image.resizable().scaledToFill()
} }
placeholder: { placeholder: {
if let i = model.selectedItem.thumbImage { if let i = model.selectedItem.thumbImage {
Image(uiImage: i)
Image(uiImage: i).resizable().scaledToFill()
} else {
Text("...")
} }
else {
ProgressView()
} }
}.scaleEffect(scale)
.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) {
} else {
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() .gesture(MagnificationGesture()
.onChanged { val in .onChanged { val in
let delta = val / self.lastScaleValue let delta = val / self.lastScaleValue
@ -47,31 +115,36 @@ struct SPhotoView : View {
scale = self.scale * delta scale = self.scale * delta
//... anything else e.g. clamping the newScale //... anything else e.g. clamping the newScale
}.onEnded { val in
}
.onEnded { val in
// without this the next gesture will be broken // without this the next gesture will be broken
self.lastScaleValue = 1.0 self.lastScaleValue = 1.0
}) })
}
.contentShape(Rectangle()).clipped(); .contentShape(Rectangle()).clipped();
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(model.allItems) { item in
}
if more {
v.overlay(VStack {
Button(action: { Button(action: {
gotoSnapshot(item)
spring.toggle()
}, label: { }, label: {
AsyncImage(item: item, placeholder: { Text("Loading ...") },
image: { Image(uiImage: $0).resizable() })
}).buttonStyle(BorderlessButtonStyle())
}.frame(height: 70)
}
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 gotoSnapshot(_ item: MediaItem) {
model.selectedItem = item
update.toggle()
print("select \(model.selectedItem.imageUrlAbsolute)")
func cleanup() {
for i in model.allItems {
i.thumbImage = nil
i.image = nil
}
} }
} }

11
kplayer/util/AsyncImage.swift

@ -7,17 +7,14 @@ struct AsyncImage<Placeholder: View>: View {
@StateObject private var item: MediaItem @StateObject private var item: MediaItem
private let placeholder: Placeholder private let placeholder: Placeholder
private let image: (UIImage) -> Image private let image: (UIImage) -> Image
private let thumb: Bool
init( init(
item: MediaItem, item: MediaItem,
thumb: Bool = true,
@ViewBuilder placeholder: () -> Placeholder, @ViewBuilder placeholder: () -> Placeholder,
@ViewBuilder image: @escaping (UIImage) -> Image = Image.init(uiImage:) @ViewBuilder image: @escaping (UIImage) -> Image = Image.init(uiImage:)
) { ) {
self.placeholder = placeholder() self.placeholder = placeholder()
self.image = image self.image = image
self.thumb = thumb
_item = StateObject(wrappedValue: item) _item = StateObject(wrappedValue: item)
setImage(newItem: item) setImage(newItem: item)
@ -42,17 +39,15 @@ struct AsyncImage<Placeholder: View>: View {
} }
func setImage(newItem: MediaItem) { func setImage(newItem: MediaItem) {
if thumb && newItem.thumbImage != nil {
return
}
if newItem.image != nil { if newItem.image != nil {
newItem.thumbImage = newItem.image!.scaleToSize(66.0, height: 44.0) newItem.thumbImage = newItem.image!.scaleToSize(66.0, height: 44.0)
newItem.image = nil newItem.image = nil
} else {
} else if newItem.thumbImage == nil {
if newItem.thumbUrl != nil { if newItem.thumbUrl != nil {
let URL = Foundation.URL(string: newItem.thumbUrlAbsolute)! let URL = Foundation.URL(string: newItem.thumbUrlAbsolute)!
print("fetch \(newItem.thumbUrlAbsolute)")
Shared.imageCache.fetch(URL: URL).onSuccess { Shared.imageCache.fetch(URL: URL).onSuccess {
i in i in

Loading…
Cancel
Save