Browse Source

MasterDetail

master
marcoschmickler 2 years ago
parent
commit
408f446229
  1. 19
      kplayer/core/DatabaseManager.swift
  2. 6
      kplayer/core/KSettings.swift
  3. 1
      kplayer/core/KSettingsModel.swift
  4. 9
      kplayer/core/MediaItem.swift
  5. 34
      kplayer/detail/DetailView.swift
  6. 8
      kplayer/detail/EditItemView.swift
  7. 8
      kplayer/detail/ItemView.swift
  8. 109
      kplayer/master/KSettingsView.swift
  9. 103
      kplayer/master/MasterModel.swift
  10. 36
      kplayer/master/MasterSplitView.swift
  11. 7
      kplayer/master/MasterViewController.swift
  12. 9
      kplayer/master/NetworkDelegate.swift
  13. 1
      kplayer/photo/SPhotoView.swift
  14. 2
      kplayer/util/AsyncImage.swift
  15. 12
      kplayer/util/UIImageExtension.swift
  16. 1
      kplayer/video/SVideoModel.swift
  17. 341
      kplayer/video/SVideoPlayer.swift

19
kplayer/core/DatabaseManager.swift

@ -24,6 +24,24 @@ class DatabaseManager {
loadAllTags() loadAllTags()
} }
func renameCurrentTag(newName: String) {
let kFetch = KTag.fetchRequest()
kFetch.predicate = NSPredicate(format: "name == %@ and path == %@", currentTag, "")
let tags = try! managedObjectContext.fetch(kFetch)
if tags.isEmpty {
return
}
let tag = tags[0]
tag.name = newName
save()
print(newName)
}
func enrichItem(_ item: MediaItem) { func enrichItem(_ item: MediaItem) {
let fetchRequest = KItem.fetchRequest() let fetchRequest = KItem.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "name == %@ AND path == %@ AND root == %@", item.name, item.path, item.root) fetchRequest.predicate = NSPredicate(format: "name == %@ AND path == %@ AND root == %@", item.name, item.path, item.root)
@ -329,6 +347,7 @@ rollback()
var res = [MediaItem]() var res = [MediaItem]()
let fetchRequest = KTag.fetchRequest() let fetchRequest = KTag.fetchRequest()
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
let results = try! managedObjectContext.fetch(fetchRequest) let results = try! managedObjectContext.fetch(fetchRequest)

6
kplayer/core/KSettings.swift

@ -24,6 +24,9 @@ class KSettings: ObservableObject {
@Published @Published
var edit = false var edit = false
@Published
var confirm = true
@Published @Published
var automaticallyWaitsToMinimizeStalling = true var automaticallyWaitsToMinimizeStalling = true
@ -37,10 +40,11 @@ class KSettings: ObservableObject {
zoomed = model.zoomed zoomed = model.zoomed
jump = model.jump jump = model.jump
slow = model.slow slow = model.slow
confirm = model.confirm
} }
func toModel() -> KSettingsModel { func toModel() -> KSettingsModel {
KSettingsModel(scale: scale, autoloop: autoloop, zoomed: zoomed, jump: jump, slow: slow )
KSettingsModel(scale: scale, autoloop: autoloop, zoomed: zoomed, jump: jump, slow: slow, confirm: confirm)
} }
func toJSON() -> String { func toJSON() -> String {

1
kplayer/core/KSettingsModel.swift

@ -11,4 +11,5 @@ struct KSettingsModel: Codable {
var zoomed = false var zoomed = false
var jump = false var jump = false
var slow = false var slow = false
var confirm = false
} }

9
kplayer/core/MediaItem.swift

@ -144,6 +144,15 @@ class MediaItem: CustomDebugStringConvertible, ObservableObject, Identifiable, H
} }
} }
func tagList() -> String {
var list = ""
for t in tags {
list.append(t)
list.append(" ")
}
return list
}
func toMediaModel() -> MediaModel { func toMediaModel() -> MediaModel {
var c = [MediaModel]() var c = [MediaModel]()

34
kplayer/detail/DetailView.swift

@ -6,6 +6,20 @@
import Foundation import Foundation
import SwiftUI import SwiftUI
struct SectionView : View {
let category : MediaItem
var body: some View {
HStack {
if (category.isPic()) {
Image(systemName: "photo")
}
Text(category.name)
Spacer()
}
}
}
struct DetailView : View { struct DetailView : View {
@ObservedObject var model: MasterModel @ObservedObject var model: MasterModel
@State var hiddenTrigger = false @State var hiddenTrigger = false
@ -21,10 +35,24 @@ struct DetailView : View {
var body: some View { var body: some View {
ScrollView(.vertical, showsIndicators: false) { ScrollView(.vertical, showsIndicators: false) {
ForEach(model.detailItems, id: \.self) { category in ForEach(model.detailItems, id: \.self) { category in
LazyVGrid(columns: columns, spacing: 10, pinnedViews: [.sectionHeaders]) {
Section(header: Text(category.name)) {
LazyVGrid(columns: columns, spacing: 10) {
Section(header: SectionView(category: category)) {
ForEach(category.children.count == 0 ? [category] : category.children, id: \.self) { item in ForEach(category.children.count == 0 ? [category] : category.children, id: \.self) { item in
ItemView(category: category, item: item, model: model)
ItemView(category: category, item: item, model: model).dropDestination(for: String.self, action: {x, _ in
var doit = false
for t in x {
print(t)
if !item.tags.contains(t) {
item.tags.append(t)
doit = true
}
}
if doit {
model.delegate.saveItem(selectedItem: item)
}
return doit
})
} }
} }
} }

8
kplayer/detail/EditItemView.swift

@ -19,15 +19,17 @@ struct TagEditor: View {
@ObservedObject @ObservedObject
var item: MediaItem var item: MediaItem
var completionHandler: ((MediaItem) -> Void)? var completionHandler: ((MediaItem) -> Void)?
var data = DatabaseManager.sharedInstance.allTags[""]!
init(item: MediaItem, completionHandler: ((MediaItem) -> Void)? = nil) { init(item: MediaItem, completionHandler: ((MediaItem) -> Void)? = nil) {
self.item = item self.item = item
self.completionHandler = completionHandler self.completionHandler = completionHandler
data.sort()
} }
var body: some View { var body: some View {
FlexibleView( FlexibleView(
data: DatabaseManager.sharedInstance.allTags[""]!,
data: data,
spacing: 15, spacing: 15,
alignment: .leading alignment: .leading
) { tag in ) { tag in
@ -49,7 +51,7 @@ struct TagEditor: View {
RoundedRectangle(cornerRadius: 8) RoundedRectangle(cornerRadius: 8)
.fill( item.tags.contains(tag) ? .fill( item.tags.contains(tag) ?
Color.yellow.opacity(0.4) : Color.yellow.opacity(0.4) :
Color.gray.opacity(0.2)
Color.gray.opacity(0.4)
) )
) )
}).buttonStyle(BorderlessButtonStyle()) }).buttonStyle(BorderlessButtonStyle())
@ -144,7 +146,7 @@ struct EditItemView: View {
.onDisappear { .onDisappear {
UITableView.appearance().backgroundColor = .systemBackground UITableView.appearance().backgroundColor = .systemBackground
} }
.frame(height: 350, alignment: .top)
.frame(height: 800, alignment: .top)
//Spacer() //Spacer()
} }

8
kplayer/detail/ItemView.swift

@ -16,7 +16,13 @@ struct ItemView : View {
print("click") print("click")
model.showDetails(sectionItem: category, selectedItem: item, categoryItem: category) model.showDetails(sectionItem: category, selectedItem: item, categoryItem: category)
}, label: { }, label: {
AsyncImage(item: item, small: false, placeholder: { Text("Loading ...") })
VStack {
AsyncImage(item: item, small: false, placeholder: { Text("Loading ...").frame(width: 15*16, height: 15*9).background(
RoundedRectangle(cornerRadius: 8)
.fill(Color.gray.opacity(0.2))
) })
Text(item.tagList()).font(.system(size: 10))
}
}) })
} }
} }

109
kplayer/master/KSettingsView.swift

@ -10,56 +10,85 @@ struct KSettingsView: View {
@ObservedObject @ObservedObject
var kSettings: KSettings var kSettings: KSettings
@State var newTag = ""
var completionHandler: (() -> Void)? var completionHandler: (() -> Void)?
var body: some View { var body: some View {
Form { Form {
Section(header: Text("K Settings")) { Section(header: Text("K Settings")) {
HStack {
VStack {
Text("Size")
Slider(value: $kSettings.scale, in: 1...2)
Toggle(isOn: $kSettings.autoloop, label: {
Text("Autoloop")
})
Toggle(isOn: $kSettings.zoomed, label: {
Text("Zoomed")
})
Toggle(isOn: $kSettings.jump, label: {
Text("Jump")
})
}.padding(30)
VStack {
Toggle(isOn: $kSettings.edit, label: {
Text("Edit")
})
Toggle(isOn: $kSettings.slow, label: {
Text("Slow")
})
Toggle(isOn: $kSettings.confirm, label: {
Text("Confirm")
})
Toggle(isOn: $kSettings.automaticallyWaitsToMinimizeStalling, label: {
Text("Stalling")
})
}.padding(30)
}
}
Section(header: Text("K Settings 3")) {
HStack { HStack {
Text("Size")
Slider(value: $kSettings.scale, in: 1...2)
Toggle(isOn: $kSettings.autoloop, label: {
Text("Autoloop")
})
Toggle(isOn: $kSettings.zoomed, label: {
Text("Zoomed")
})
Toggle(isOn: $kSettings.jump, label: {
Text("Jump")
})
Toggle(isOn: $kSettings.edit, label: {
Text("Edit")
})
Toggle(isOn: $kSettings.slow, label: {
Text("Slow")
Button(action: {
NetworkManager.sharedInstance.suspendLinkstation()
}, label: {
Text("suspend")
});
Spacer()
Button(action: {
NetworkManager.sharedInstance.wakeLinkstation()
}, label: {
Text("wake")
});
}
HStack {
Button(action: {
LocalManager.sharedInstance.saveSettings()
self.completionHandler?()
}, label: {
Text("ok")
}) })
Toggle(isOn: $kSettings.automaticallyWaitsToMinimizeStalling, label: {
Text("Stalling")
.buttonStyle(BorderlessButtonStyle());
Spacer()
Button(action: {
LocalManager.sharedInstance.loadSettings()
self.completionHandler?()
}, label: {
Text("cancel")
}) })
.buttonStyle(BorderlessButtonStyle())
} }
// HStack {
// TextField("Rename " + DatabaseManager.sharedInstance.currentTag, text: $newTag)
// Button(action: {
// DatabaseManager.sharedInstance.renameCurrentTag(newName: newTag)
// }, label: {
// Text("rename")
// }).buttonStyle(BorderlessButtonStyle())
// }
} }
Button(action: {
NetworkManager.sharedInstance.suspendLinkstation()
}, label: {
Text("suspend")
});
Button(action: {
NetworkManager.sharedInstance.wakeLinkstation()
}, label: {
Text("wake")
});
Button(action: {
LocalManager.sharedInstance.saveSettings()
self.completionHandler?()
}, label: {
Text("ok")
});
Button(action: {
LocalManager.sharedInstance.loadSettings()
self.completionHandler?()
}, label: {
Text("cancel")
})
} }
.onAppear { .onAppear {
} }

103
kplayer/master/MasterModel.swift

@ -21,6 +21,8 @@ class MasterModel : ObservableObject {
var delegate: MasterDelegate var delegate: MasterDelegate
var detailDelegate: DetailDelegate var detailDelegate: DetailDelegate
var tagModes = ["", "models", "compilations"]
var tagMode = 0
@Published var videoModel: SVideoModel = SVideoModel(allItems: [], currentSnapshot: MediaItem(name: "", path: "", root: "", type: .VIDEO), baseItem: MediaItem(name: "", path: "", root: "", type: .VIDEO)) @Published var videoModel: SVideoModel = SVideoModel(allItems: [], currentSnapshot: MediaItem(name: "", path: "", root: "", type: .VIDEO), baseItem: MediaItem(name: "", path: "", root: "", type: .VIDEO))
@Published var photoModel = SPhotoModel(allItems: [MediaItem(name: "", path: "", root: "", type: .PICS)]) @Published var photoModel = SPhotoModel(allItems: [MediaItem(name: "", path: "", root: "", type: .PICS)])
@ -29,10 +31,15 @@ class MasterModel : ObservableObject {
@Published var showPhoto = false @Published var showPhoto = false
@Published var showFilePicker = false @Published var showFilePicker = false
@Published var showSettings = false @Published var showSettings = false
@Published var isTagging = false
@Published var selectedItemId : String? @Published var selectedItemId : String?
{ {
didSet { didSet {
if isTagging {
return
}
if let selected = selectedItemId { if let selected = selectedItemId {
if !LocalManager.sharedInstance.authenticated { if !LocalManager.sharedInstance.authenticated {
// doAuthenticate() // doAuthenticate()
@ -88,15 +95,36 @@ class MasterModel : ObservableObject {
@Published var detailItems: [MediaItem] @Published var detailItems: [MediaItem]
func back() { func back() {
if !parentItem.isEmpty {
parentItem.removeLast()
if !isTagging {
if !parentItem.isEmpty {
parentItem.removeLast()
}
}
else {
isTagging = false
} }
items = parentItem.last?.children ?? itemModel.items items = parentItem.last?.children ?? itemModel.items
selectedItemId = nil selectedItemId = nil
selectedItem = nil selectedItem = nil
} }
func tagIt() {
isTagging = true
let tagCategory = tagModes[tagMode]
tagMode+=1
if tagMode >= tagModes.count {
tagMode = 0
}
var newItems = [MediaItem]()
var tags = DatabaseManager.sharedInstance.allTags[tagCategory]!
tags.sort()
for t in tags {
newItems.append(MediaItem(name: t, path: t, root: "tags", type: ItemType.TAG))
}
items = newItems
}
private func gotoNextFolder(_ neu: MediaItem) { private func gotoNextFolder(_ neu: MediaItem) {
DispatchQueue.main.async { DispatchQueue.main.async {
self.selectedItemId = nil self.selectedItemId = nil
@ -104,7 +132,12 @@ class MasterModel : ObservableObject {
self.parentItem.append(neu) self.parentItem.append(neu)
// ItemModel().sortItems(selectedItem: neu, children: neu.children)
if (neu.type != ItemType.TAGROOT) {
neu.sort()
}
self.items = neu.children self.items = neu.children
// items.sort()
neu.printTree() neu.printTree()
} }
@ -176,6 +209,7 @@ class MasterModel : ObservableObject {
model.zoomed = delegate.settings().zoomed model.zoomed = delegate.settings().zoomed
model.jump = delegate.settings().jump model.jump = delegate.settings().jump
model.loop = delegate.settings().autoloop model.loop = delegate.settings().autoloop
model.tagging = isTagging
videoModel = model videoModel = model
showVideo = true showVideo = true
@ -183,7 +217,10 @@ class MasterModel : ObservableObject {
func saveVideo(saved: Bool) { func saveVideo(saved: Bool) {
if saved { if saved {
if let baseItem=selectedDetail {
if var baseItem=selectedDetail {
if baseItem.type == ItemType.SNAPSHOT && baseItem.parent != nil {
baseItem = baseItem.parent!
}
baseItem.children = videoModel.allItems baseItem.children = videoModel.allItems
delegate.saveItem(selectedItem: baseItem) delegate.saveItem(selectedItem: baseItem)
} }
@ -191,7 +228,39 @@ class MasterModel : ObservableObject {
else { else {
//baseItem.children = clonedChildren //baseItem.children = clonedChildren
} }
showVideo = false
var next = false
var nextChild = false
if LocalManager.sharedInstance.settings.confirm == false {
for c in detailItems {
if next {
showVideo = false
Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { timer in
self.showVideo(selectedItem: c)
}
break
}
if selectedDetail == c {
next = true
continue
}
for cc in c.children {
if selectedDetail == cc {
nextChild = true
continue
}
}
if nextChild {
next = true
continue
}
}
}
else {
showVideo = false
}
} }
func showPhotos(_ im: [MediaItem], selectedItem: MediaItem, categoryItem: MediaItem, compilation: Bool = false) { func showPhotos(_ im: [MediaItem], selectedItem: MediaItem, categoryItem: MediaItem, compilation: Bool = false) {
@ -241,6 +310,30 @@ class MasterModel : ObservableObject {
} }
} }
func overview() {
if let item = selectedItem {
let composition = MediaItem(name: item.name, path: item.path, root: item.root, type: ItemType.VIDEO)
composition.compilation = true
for d in detailItems {
if d.children.isEmpty {
let clone = d.clone()
clone.parent = composition
clone.type = ItemType.SNAPSHOT
composition.children.append(clone)
} else {
for c in d.children {
let clone = c.clone()
clone.parent = composition
composition.children.append(clone)
}
}
}
showVideo(selectedItem: composition.children[0])
}
}
func doAuthenticate() { func doAuthenticate() {
let authenticationContext = LAContext() let authenticationContext = LAContext()
var error: NSError? var error: NSError?

36
kplayer/master/MasterSplitView.swift

@ -13,21 +13,42 @@ struct MasterSplitView: View {
var body: some View { var body: some View {
ZStack { ZStack {
NavigationSplitView { NavigationSplitView {
List(model.items, selection: $model.selectedItemId) { item in
Text(item.name).font(.title3).bold()
}.id(UUID()).toolbar {
ZStack {
if model.isTagging {
ScrollView {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 80))]) {
ForEach(model.items) { item in
Text(item.name).font(.system(size: 16)).bold().padding(EdgeInsets(top: 10, leading: 6, bottom: 10, trailing: 6))
.background(
RoundedRectangle(cornerRadius: 8)
.fill(Color.gray.opacity(0.2))
).draggable(item.name)
}
}
}
}
else {
List(model.items, selection: $model.selectedItemId) { item in
Text(item.name).font(.title3).bold()
}.id(UUID())
}
}.toolbar {
ToolbarItem(placement: .navigationBarLeading) { ToolbarItem(placement: .navigationBarLeading) {
Button(action: model.back, label: { Button(action: model.back, label: {
Image(systemName: "arrowshape.backward") Image(systemName: "arrowshape.backward")
}) })
} }
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: model.tagIt, label: {
Image(systemName: "tag")
})
}
ToolbarItem(placement: .navigationBarTrailing) { ToolbarItem(placement: .navigationBarTrailing) {
Button(action: { model.showSettings = true }, label: { Button(action: { model.showSettings = true }, label: {
Image(systemName: "slider.horizontal.3") Image(systemName: "slider.horizontal.3")
}) })
} }
ToolbarItem(placement: .navigationBarTrailing) {
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: completionHandler, label: { Button(action: completionHandler, label: {
Image(systemName: "xmark.square") Image(systemName: "xmark.square")
}) })
@ -38,6 +59,11 @@ struct MasterSplitView: View {
HStack { HStack {
Text(model.selectedItem?.longName() ?? "").font(.title3) Text(model.selectedItem?.longName() ?? "").font(.title3)
Spacer() Spacer()
if model.selectedItem != nil {
Button(action: model.overview, label: {
Text("Overview")
})
}
}.background(Color(UIColor.systemGray6)) }.background(Color(UIColor.systemGray6))
DetailView(model: model) DetailView(model: model)
} }

7
kplayer/master/MasterViewController.swift

@ -177,6 +177,13 @@ class MasterViewController: UITableViewController, UISearchResultsUpdating, UITa
let submitAction = UIAlertAction(title: "Submit", style: .default) { [unowned ac] _ in let submitAction = UIAlertAction(title: "Submit", style: .default) { [unowned ac] _ in
let answer = ac.textFields![0].text! let answer = ac.textFields![0].text!
if (item.root == "/models") {
DatabaseManager.sharedInstance.createTag(answer, path: "models")
let m = MediaItem(name: answer, path: answer, root: "models", type: ItemType.TAG)
m.local = true
item.children.append(m)
m.parent = item
} else
if (item.root == "/tags") { if (item.root == "/tags") {
DatabaseManager.sharedInstance.createTag(answer) DatabaseManager.sharedInstance.createTag(answer)
let m = MediaItem(name: answer, path: answer, root: "tags", type: ItemType.TAG) let m = MediaItem(name: answer, path: answer, root: "tags", type: ItemType.TAG)

9
kplayer/master/NetworkDelegate.swift

@ -115,7 +115,14 @@ class NetworkDelegate: MasterDelegate, DetailDelegate {
func saveItem(selectedItem: MediaItem) { func saveItem(selectedItem: MediaItem) {
var item = selectedItem var item = selectedItem
if (selectedItem.local || (selectedItem.parent?.local != nil )) {
var isLocal = selectedItem.local
if (!isLocal) {
if let p = selectedItem.parent {
isLocal = p.local
}
}
if (isLocal) {
if item.type == ItemType.SNAPSHOT { if item.type == ItemType.SNAPSHOT {
item = selectedItem.parent! item = selectedItem.parent!
} }

1
kplayer/photo/SPhotoView.swift

@ -74,6 +74,7 @@ struct SPhotoView: View {
print("3 tapped!") print("3 tapped!")
if model.scale == 1 { if model.scale == 1 {
model.scale = lastScale model.scale = lastScale
model.dragOffset = CGSize(width: 600, height: 0)
} else { } else {
lastScale = model.scale lastScale = model.scale
model.scale = 1 model.scale = 1

2
kplayer/util/AsyncImage.swift

@ -33,7 +33,7 @@ struct AsyncImage<Placeholder: View>: View {
image(item.thumbImage!.scaleToSize(66.0, height: 44.0)) image(item.thumbImage!.scaleToSize(66.0, height: 44.0))
} }
else { else {
image(item.thumbImage!.scaleToSize(15 * 16, height: 15 * 9))
image(item.thumbImage!.scaleToHeight(15 * 16))
} }
} else { } else {
placeholder placeholder

12
kplayer/util/UIImageExtension.swift

@ -39,6 +39,18 @@ extension UIImage {
} }
} }
func scaleToHeight(_ max: CGFloat) -> UIImage {
let targetSize = CGSize(width: max, height: max)
// Compute the scaling ratio for the width and height separately
let widthScaleRatio = targetSize.width / size.width
let heightScaleRatio = targetSize.height / size.height
// To keep the aspect ratio, scale by the smaller scaling ratio
let scaleFactor = min(widthScaleRatio, heightScaleRatio)
return scaleToSize(size.width * scaleFactor, height: size.height * scaleFactor)
}
func decodeImage() -> UIImage? { func decodeImage() -> UIImage? {
guard let newImage = self.cgImage else { return nil } guard let newImage = self.cgImage else { return nil }

1
kplayer/video/SVideoModel.swift

@ -25,6 +25,7 @@ class SVideoModel : ObservableObject {
@Published var edit = false @Published var edit = false
@Published var dirty = false @Published var dirty = false
@Published var loop = false @Published var loop = false
@Published var tagging = false
@Published var slow = false @Published var slow = false
@Published var zoomed = false @Published var zoomed = false
@Published var jump = false @Published var jump = false

341
kplayer/video/SVideoPlayer.swift

@ -25,6 +25,7 @@ struct SVideoPlayer: View, EditItemDelegate {
@State var confirmationShown = false @State var confirmationShown = false
@State var dirtyShown = false @State var dirtyShown = false
@State var blackShown = false @State var blackShown = false
@State var showSettings = false
@State var truncateShown = false @State var truncateShown = false
@State var seekSmoothly = false @State var seekSmoothly = false
@State var upsidedown = false @State var upsidedown = false
@ -42,13 +43,18 @@ struct SVideoPlayer: View, EditItemDelegate {
@State var xoffs = 0.0 @State var xoffs = 0.0
@State var rotazero = -1000.0 @State var rotazero = -1000.0
@State var loop5 = false
@State var loop5start = 0.0
@State var cutFlag = false @State var cutFlag = false
@State var lores = false @State var lores = false
@State var second = false @State var second = false
@State var tap3 = false
@State var secondScale = 0.75 @State var secondScale = 0.75
@State var secondOffset = CGSize(width: -70, height: 50) @State var secondOffset = CGSize(width: -70, height: 50)
@State var orientation = UIDevice.current.orientation @State var orientation = UIDevice.current.orientation
@State private var scrollTarget: Int?
let orientationChanged = NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification) let orientationChanged = NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)
.makeConnectable() .makeConnectable()
@ -65,6 +71,10 @@ struct SVideoPlayer: View, EditItemDelegate {
self.playerLooper = AVPlayerLooper(player: player, templateItem: model.currentPlayerItem()) self.playerLooper = AVPlayerLooper(player: player, templateItem: model.currentPlayerItem())
} }
func isEnd() -> Bool {
model.paused && model.currentSnapshot.time > 0
}
func cleanup() { func cleanup() {
if model.observer != nil && model.observed != nil && model.observed! === player { if model.observer != nil && model.observed != nil && model.observed! === player {
do { do {
@ -76,101 +86,133 @@ struct SVideoPlayer: View, EditItemDelegate {
} }
} }
var multiTapGesture: some Gesture {
SimultaneousGesture(TapGesture(count: 2), TapGesture(count: 3))
.onEnded { gestureValue in
if gestureValue.second != nil {
if !model.baseItem.compilation {
doSnapshot()
}
print("triple tap!")
} else if gestureValue.first != nil {
doubleTapZoom()
}
}
}
var body: some View { var body: some View {
VStack { VStack {
HStack { HStack {
Group {
Button(action: {
if model.dirty {
dirtyShown = true
} else {
let withSave = model.edit
closePlayer(withSave: withSave)
}
}, label: {
Text("cancel")
})
.buttonStyle(BorderlessButtonStyle()).confirmationDialog("Save?", isPresented: $dirtyShown) {
Button("save") {
VStack {
HStack {
Button(action: {
if model.dirty {
if LocalManager.sharedInstance.settings.confirm {
dirtyShown = true
} else {
cleanup()
closePlayer(withSave: true) closePlayer(withSave: true)
cleanup()
} }
Button("cancel", role: .cancel) {
closePlayer(withSave: false)
}
} else {
let withSave = model.edit
cleanup()
closePlayer(withSave: withSave)
} }
if !model.baseItem.compilation {
Button(action: { blackShown = true }) {
Text("black")
}
.foregroundColor(NetworkManager.sharedInstance.isBlack(model.baseItem) ? Color.yellow : Color.blue)
.frame(height: 30).buttonStyle(BorderlessButtonStyle()).confirmationDialog("Delete?", isPresented: $blackShown) {
Button("delete") {
black();
closePlayer(withSave: false)
blackShown = false
}, label: {
Text(LocalManager.sharedInstance.settings.confirm ? "cancel" : "next")
})
.buttonStyle(BorderlessButtonStyle()).confirmationDialog("Save?", isPresented: $dirtyShown) {
Button("save") {
closePlayer(withSave: true)
} }
Button("cancel", role: .cancel) { Button("cancel", role: .cancel) {
blackShown = false
closePlayer(withSave: false)
} }
} }
}
KToggleButton(text: "\(relative())", binding: $more)
Button(action: {
let tag = DatabaseManager.sharedInstance.currentTag
if tag != "" {
let item = model.currentSnapshot
if item.tags.contains(tag) {
item.tags.removeAll(where: { toRemove in toRemove == tag })
} else {
item.tags.append(tag)
if !model.baseItem.compilation {
Button(action: {
if isEnd() {
setEnd()
} else {
blackShown = true
}
}) {
Text(isEnd() ? "end" : "black")
} }
model.dirty = true
.foregroundColor(NetworkManager.sharedInstance.isBlack(model.baseItem) ? Color.yellow : Color.blue)
.frame(height: 30).buttonStyle(BorderlessButtonStyle()).confirmationDialog("Delete?", isPresented: $blackShown) {
Button("delete") {
black();
closePlayer(withSave: false)
blackShown = false
}
Button("cancel", role: .cancel) {
blackShown = false
}
}
} }
}, label: {
Text(DatabaseManager.sharedInstance.currentTag)
}).foregroundColor(model.currentSnapshot.tags.contains(DatabaseManager.sharedInstance.currentTag) ? Color.yellow : Color.blue)
Button(action: {
model.favorite.toggle()
model.dirty = true
}, label: {
Image(systemName: "heart.fill")
})
.foregroundColor(model.favorite ? Color.yellow : Color.blue).buttonStyle(BorderlessButtonStyle())
KToggleButton(text: "\(relative())", binding: $more)
Button(action: {
let tag = DatabaseManager.sharedInstance.currentTag
if tag != "" {
let item = model.currentSnapshot
if item.tags.contains(tag) {
item.tags.removeAll(where: { toRemove in toRemove == tag })
} else {
item.tags.append(tag)
}
model.dirty = true
}
}, label: {
Text(DatabaseManager.sharedInstance.currentTag)
})
.foregroundColor(model.currentSnapshot.tags.contains(DatabaseManager.sharedInstance.currentTag) ? Color.yellow : Color.blue)
Button(action: {
model.favorite.toggle()
model.dirty = true
}, label: {
Image(systemName: "heart.fill")
})
.foregroundColor(model.favorite ? Color.yellow : Color.blue).buttonStyle(BorderlessButtonStyle())
Button(action: { confirmationShown = true }, label: {
Text(savetext)
})
.buttonStyle(BorderlessButtonStyle())
.confirmationDialog("Save to folder", isPresented: $confirmationShown) {
Button("1") {
save(currentSnapshot: model.currentSnapshot, name: "1")
}
Button("2") {
save(currentSnapshot: model.currentSnapshot, name: "2")
}
Button("3") {
save(currentSnapshot: model.currentSnapshot, name: "3")
}
KToggleButton(text: "loop5", binding: $loop5)
KToggleButton(text: "cfg", binding: $showSettings).frame(height: 30)
Button(action: { confirmationShown = true }, label: {
Text(savetext)
})
.buttonStyle(BorderlessButtonStyle())
.confirmationDialog("Save to folder", isPresented: $confirmationShown) {
Button("1") {
save(currentSnapshot: model.currentSnapshot, name: "1")
}
Button("2") {
save(currentSnapshot: model.currentSnapshot, name: "2")
}
Button("3") {
save(currentSnapshot: model.currentSnapshot, name: "3")
}
Button("cancel", role: .cancel) {
Button("cancel", role: .cancel) {
}
} }
}
KToggleButton(text: "embd", binding: $embedded).frame(height: 30)
Group {
Text(model.currentSnapshot.name).foregroundColor(Color.blue)
Text("""
(\(model.codec) \(model.height), \(model.nominalFrameRate), \(model.bitRate)m)\n\(model.scale, specifier: "%.2f")x (\(model.dragOffset.width, specifier: "%.0f"),\(model.dragOffset.height, specifier: "%.0f"))
""").foregroundColor(Color.blue)
KToggleButton(text: "embd", binding: $embedded).frame(height: 30)
}
HStack {
Text(model.currentSnapshot.name).foregroundColor(Color.blue).fontWeight(Font.Weight.light)
}
}
HStack {
ScrollViewReader { scrollvalue in
ScrollView(.horizontal, showsIndicators: false) { ScrollView(.horizontal, showsIndicators: false) {
HStack { HStack {
ForEach(model.allItems) { item in ForEach(model.allItems) { item in
Button(action: { Button(action: {
gotoSnapshot(item) gotoSnapshot(item)
scrollvalue.scrollTo(item.id)
}) { }) {
AsyncImage(item: item, placeholder: { Text("Loading ...") }, AsyncImage(item: item, placeholder: { Text("Loading ...") },
image: { Image(uiImage: $0).resizable() }).border(.yellow, width: (item === model.currentSnapshot) ? 1 : 0) image: { Image(uiImage: $0).resizable() }).border(.yellow, width: (item === model.currentSnapshot) ? 1 : 0)
@ -179,33 +221,46 @@ struct SVideoPlayer: View, EditItemDelegate {
} }
} }
} }
// .onChange(of: scrollTarget { target in
// if let tg = target {
// scrollTarget = nil
// withAnimation {
// scrollvalue.scrollTo(tg)
// }
// }
// })
}
Spacer()
Spacer()
VStack {
HStack {
Button(action: { model.edit.toggle() }, label: {
Text("edit")
})
.buttonStyle(BorderlessButtonStyle());
Button(action: { model.edit.toggle() }, label: {
Text("edit")
})
.buttonStyle(BorderlessButtonStyle());
if !model.baseItem.compilation {
if model.currentSnapshot != nil {
Button(action: addFrame, label: {
Text("frame")
})
.buttonStyle(BorderlessButtonStyle())
}
if !model.baseItem.compilation {
if model.currentSnapshot != nil {
Button(action: addFrame, label: {
Text("frame")
Button(action: doSnapshot, label: {
Text("snap")
}) })
.buttonStyle(BorderlessButtonStyle())
}
Button(action: doSnapshot, label: {
Text("snap")
})
.buttonStyle(BorderlessButtonStyle());
.buttonStyle(BorderlessButtonStyle());
}
} }
Text("""
(\(model.codec) \(model.height), \(model.nominalFrameRate), \(model.bitRate)m)\n\(model.scale, specifier: "%.2f")x (\(model.dragOffset.width, specifier: "%.0f"),\(model.dragOffset.height, specifier: "%.0f"))
""").foregroundColor(Color.blue).fontWeight(Font.Weight.light)
} }
} }
.frame(height: 50)
} }
.frame(height: 50)
GeometryReader { geometry in GeometryReader { geometry in
VStack { VStack {
@ -214,25 +269,14 @@ struct SVideoPlayer: View, EditItemDelegate {
.scaleEffect(small ? 1 : model.scale) .scaleEffect(small ? 1 : model.scale)
.rotation3DEffect(.degrees(upsidedown ? 180 : 0), axis: (x: 1, y: 0, z: 0)) .rotation3DEffect(.degrees(upsidedown ? 180 : 0), axis: (x: 1, y: 0, z: 0))
.offset(small ? CGSize.zero : model.dragOffset).modifier(KAnimate(dragOffset: model.dragOffset, spring: false)).offset(x: xoffs, y: 0) .offset(small ? CGSize.zero : model.dragOffset).modifier(KAnimate(dragOffset: model.dragOffset, spring: false)).offset(x: xoffs, y: 0)
.onTapGesture(count: 2) {
print("3 tapped!")
second = !second
if second {
model.scale = lastScale
} else {
lastScale = model.scale
if (lores) {
model.scale = secondScale
model.dragOffset = secondOffset
}
else {
model.scale = 1
model.dragOffset = CGSize.zero
}
}
}
.gesture(multiTapGesture)
.simultaneousGesture(
LongPressGesture()
.onEnded { _ in
print("Loooong")
togglePause()
}
)
.gesture( .gesture(
DragGesture() DragGesture()
.onChanged { gesture in .onChanged { gesture in
@ -323,12 +367,7 @@ struct SVideoPlayer: View, EditItemDelegate {
.frame(height: 30) .frame(height: 30)
.buttonStyle(BorderlessButtonStyle()) .buttonStyle(BorderlessButtonStyle())
Button(action: { Button(action: {
if model.paused {
player.play()
} else {
player.pause()
}
model.paused = !model.paused
togglePause()
}, label: { }, label: {
Text("pause") Text("pause")
}) })
@ -366,7 +405,8 @@ struct SVideoPlayer: View, EditItemDelegate {
} }
Button(action: { cut() }) { Button(action: { cut() }) {
Text("cut") Text("cut")
}.foregroundColor(cutFlag ? Color.yellow : Color.blue)
}
.foregroundColor(cutFlag ? Color.yellow : Color.blue)
.frame(height: 30).buttonStyle(BorderlessButtonStyle()) .frame(height: 30).buttonStyle(BorderlessButtonStyle())
} }
@ -401,6 +441,12 @@ struct SVideoPlayer: View, EditItemDelegate {
}) })
} }
.frame(width: 50, alignment: .top).offset(x: 0, y: 0), alignment: .topLeading) .frame(width: 50, alignment: .top).offset(x: 0, y: 0), alignment: .topLeading)
} else if model.tagging {
v.overlay(ScrollView() {
TagEditor(item: model.currentSnapshot, completionHandler: NetworkManager.sharedInstance.saveItem)
}
.frame(width: 170, alignment: .top).offset(x: 0, y: 0),
alignment: .topLeading)
} else if frames && model.zoomed { } else if frames && model.zoomed {
v.overlay(VStack { v.overlay(VStack {
ForEach(model.frames) { f in ForEach(model.frames) { f in
@ -450,6 +496,11 @@ struct SVideoPlayer: View, EditItemDelegate {
} }
} }
.sheet(isPresented: $showSettings, onDismiss: { showSettings = false }) {
KSettingsView(kSettings: LocalManager.sharedInstance.settings, completionHandler: {
showSettings = false
})
}
.onAppear() { .onAppear() {
model.observed = player model.observed = player
model.observer = player.addPeriodicTimeObserver(forInterval: CMTime(seconds: 0.02, preferredTimescale: 600), queue: nil, using: timeObserver()) model.observer = player.addPeriodicTimeObserver(forInterval: CMTime(seconds: 0.02, preferredTimescale: 600), queue: nil, using: timeObserver())
@ -491,10 +542,46 @@ struct SVideoPlayer: View, EditItemDelegate {
} }
model.bitRate = Int(lastEvent.indicatedBitrate / 1024 / 1024) model.bitRate = Int(lastEvent.indicatedBitrate / 1024 / 1024)
}.onChange(of: loop5) { new in
if new {
if model.paused {
model.paused = false
player.play()
player.rate = 0.8
}
loop5start = player.currentTime().seconds
}
} }
} }
private func togglePause() {
if model.paused {
player.play()
} else {
player.pause()
}
model.paused = !model.paused
}
private func doubleTapZoom() {
second = !second
if second {
model.scale = lastScale
} else {
lastScale = model.scale
if (lores) {
model.scale = secondScale
model.dragOffset = secondOffset
} else {
model.scale = 1
model.dragOffset = CGSize.zero
}
}
}
private func timeObserver() -> (CMTime) -> () { private func timeObserver() -> (CMTime) -> () {
{ time in { time in
if timeCounter >= 1 { if timeCounter >= 1 {
@ -503,6 +590,11 @@ struct SVideoPlayer: View, EditItemDelegate {
if timeSlomoCounter >= 1 { if timeSlomoCounter >= 1 {
timeSlomoCounter -= 1 timeSlomoCounter -= 1
} }
if loop5 {
if time.seconds > loop5start + 5.0 {
seekTime(loop5start)
}
}
if model.loop && !model.paused && model.currentSnapshot.length > 0 { if model.loop && !model.paused && model.currentSnapshot.length > 0 {
if time.seconds > model.currentSnapshot.time + model.currentSnapshot.length { if time.seconds > model.currentSnapshot.time + model.currentSnapshot.length {
seekTime(model.currentSnapshot.time) seekTime(model.currentSnapshot.time)
@ -596,6 +688,7 @@ struct SVideoPlayer: View, EditItemDelegate {
newItem.external = currentItem.external newItem.external = currentItem.external
model.allItems.append(newItem) model.allItems.append(newItem)
model.currentSnapshot = newItem model.currentSnapshot = newItem
scrollTarget = model.allItems.count
} }
private func move(_ dragged: CGSize, start: CGPoint) -> Bool { private func move(_ dragged: CGSize, start: CGPoint) -> Bool {
@ -606,7 +699,7 @@ struct SVideoPlayer: View, EditItemDelegate {
} }
if (start.y < 170) { if (start.y < 170) {
let delta = (dragged.width + dragged.height) / 200.0
let delta = (dragged.width + dragged.height) / 100.0
seekTimeSmoothly(smoothTime + delta) seekTimeSmoothly(smoothTime + delta)
return false return false
@ -614,7 +707,7 @@ struct SVideoPlayer: View, EditItemDelegate {
if second || small { if second || small {
return true return true
} else { } else {
let delta = (dragged.width + dragged.height) / 200.0
let delta = (dragged.width + dragged.height) / 30.0
seekTimeSmoothly(smoothTime + delta) seekTimeSmoothly(smoothTime + delta)
return false return false
@ -646,7 +739,7 @@ struct SVideoPlayer: View, EditItemDelegate {
if i - 1 >= 0 { if i - 1 >= 0 {
gotoSnapshot(model.allItems[i - 1]) gotoSnapshot(model.allItems[i - 1])
} else { } else {
gotoSnapshot(model.allItems[model.allItems.count-1])
gotoSnapshot(model.allItems[model.allItems.count - 1])
} }
timeCounter = 50 timeCounter = 50
print("jump") print("jump")
@ -797,7 +890,7 @@ struct SVideoPlayer: View, EditItemDelegate {
} }
model.height = Int(height) model.height = Int(height)
lores = (height <= 1080) && (height*1.5 < heightSpace)
lores = (height <= 1080) && (height * 1.5 < heightSpace)
var f = height / heightSpace var f = height / heightSpace
print("h \(height) \(heightSpace) \(f)") print("h \(height) \(heightSpace) \(f)")
@ -815,8 +908,7 @@ struct SVideoPlayer: View, EditItemDelegate {
model.scale = f model.scale = f
model.dragOffset.width = 0 model.dragOffset.width = 0
model.dragOffset.height = 0 model.dragOffset.height = 0
}
else {
} else {
if (model.scale != 1) { if (model.scale != 1) {
lastScale = model.scale lastScale = model.scale
} }
@ -824,8 +916,7 @@ struct SVideoPlayer: View, EditItemDelegate {
if (lores) { if (lores) {
model.scale = secondScale model.scale = secondScale
model.dragOffset = secondOffset model.dragOffset = secondOffset
}
else {
} else {
model.scale = 1 model.scale = 1
model.dragOffset = CGSize.zero model.dragOffset = CGSize.zero
} }

Loading…
Cancel
Save