diff --git a/kplayer/core/DatabaseManager.swift b/kplayer/core/DatabaseManager.swift index 79be26c..2089eb7 100644 --- a/kplayer/core/DatabaseManager.swift +++ b/kplayer/core/DatabaseManager.swift @@ -24,6 +24,24 @@ class DatabaseManager { 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) { let fetchRequest = KItem.fetchRequest() fetchRequest.predicate = NSPredicate(format: "name == %@ AND path == %@ AND root == %@", item.name, item.path, item.root) @@ -329,6 +347,7 @@ rollback() var res = [MediaItem]() let fetchRequest = KTag.fetchRequest() + fetchRequest.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)] let results = try! managedObjectContext.fetch(fetchRequest) diff --git a/kplayer/core/KSettings.swift b/kplayer/core/KSettings.swift index f2c4bff..c795166 100644 --- a/kplayer/core/KSettings.swift +++ b/kplayer/core/KSettings.swift @@ -24,6 +24,9 @@ class KSettings: ObservableObject { @Published var edit = false + @Published + var confirm = true + @Published var automaticallyWaitsToMinimizeStalling = true @@ -37,10 +40,11 @@ class KSettings: ObservableObject { zoomed = model.zoomed jump = model.jump slow = model.slow + confirm = model.confirm } 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 { diff --git a/kplayer/core/KSettingsModel.swift b/kplayer/core/KSettingsModel.swift index 6cecdc5..90155ac 100644 --- a/kplayer/core/KSettingsModel.swift +++ b/kplayer/core/KSettingsModel.swift @@ -11,4 +11,5 @@ struct KSettingsModel: Codable { var zoomed = false var jump = false var slow = false + var confirm = false } diff --git a/kplayer/core/MediaItem.swift b/kplayer/core/MediaItem.swift index 97726ab..6a851d5 100644 --- a/kplayer/core/MediaItem.swift +++ b/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 { var c = [MediaModel]() diff --git a/kplayer/detail/DetailView.swift b/kplayer/detail/DetailView.swift index 7397c4b..fa14139 100644 --- a/kplayer/detail/DetailView.swift +++ b/kplayer/detail/DetailView.swift @@ -6,6 +6,20 @@ import Foundation 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 { @ObservedObject var model: MasterModel @State var hiddenTrigger = false @@ -21,10 +35,24 @@ struct DetailView : View { var body: some View { ScrollView(.vertical, showsIndicators: false) { 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 - 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 + }) } } } diff --git a/kplayer/detail/EditItemView.swift b/kplayer/detail/EditItemView.swift index 7a1ca68..eca6676 100644 --- a/kplayer/detail/EditItemView.swift +++ b/kplayer/detail/EditItemView.swift @@ -19,15 +19,17 @@ struct TagEditor: View { @ObservedObject var item: MediaItem var completionHandler: ((MediaItem) -> Void)? + var data = DatabaseManager.sharedInstance.allTags[""]! init(item: MediaItem, completionHandler: ((MediaItem) -> Void)? = nil) { self.item = item self.completionHandler = completionHandler + data.sort() } var body: some View { FlexibleView( - data: DatabaseManager.sharedInstance.allTags[""]!, + data: data, spacing: 15, alignment: .leading ) { tag in @@ -49,7 +51,7 @@ struct TagEditor: View { RoundedRectangle(cornerRadius: 8) .fill( item.tags.contains(tag) ? Color.yellow.opacity(0.4) : - Color.gray.opacity(0.2) + Color.gray.opacity(0.4) ) ) }).buttonStyle(BorderlessButtonStyle()) @@ -144,7 +146,7 @@ struct EditItemView: View { .onDisappear { UITableView.appearance().backgroundColor = .systemBackground } - .frame(height: 350, alignment: .top) + .frame(height: 800, alignment: .top) //Spacer() } diff --git a/kplayer/detail/ItemView.swift b/kplayer/detail/ItemView.swift index 1dfca93..876f2b1 100644 --- a/kplayer/detail/ItemView.swift +++ b/kplayer/detail/ItemView.swift @@ -16,7 +16,13 @@ struct ItemView : View { print("click") model.showDetails(sectionItem: category, selectedItem: item, categoryItem: category) }, 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)) + } }) } } diff --git a/kplayer/master/KSettingsView.swift b/kplayer/master/KSettingsView.swift index 8b7bf2e..74965db 100644 --- a/kplayer/master/KSettingsView.swift +++ b/kplayer/master/KSettingsView.swift @@ -10,56 +10,85 @@ struct KSettingsView: View { @ObservedObject var kSettings: KSettings + @State var newTag = "" + var completionHandler: (() -> Void)? var body: some View { Form { 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 { - 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 { } diff --git a/kplayer/master/MasterModel.swift b/kplayer/master/MasterModel.swift index a11b769..d0cab86 100644 --- a/kplayer/master/MasterModel.swift +++ b/kplayer/master/MasterModel.swift @@ -21,6 +21,8 @@ class MasterModel : ObservableObject { var delegate: MasterDelegate 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 photoModel = SPhotoModel(allItems: [MediaItem(name: "", path: "", root: "", type: .PICS)]) @@ -29,10 +31,15 @@ class MasterModel : ObservableObject { @Published var showPhoto = false @Published var showFilePicker = false @Published var showSettings = false + @Published var isTagging = false @Published var selectedItemId : String? { didSet { + if isTagging { + return + } + if let selected = selectedItemId { if !LocalManager.sharedInstance.authenticated { // doAuthenticate() @@ -88,15 +95,36 @@ class MasterModel : ObservableObject { @Published var detailItems: [MediaItem] func back() { - if !parentItem.isEmpty { - parentItem.removeLast() + if !isTagging { + if !parentItem.isEmpty { + parentItem.removeLast() + } + } + else { + isTagging = false } - items = parentItem.last?.children ?? itemModel.items selectedItemId = 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) { DispatchQueue.main.async { self.selectedItemId = nil @@ -104,7 +132,12 @@ class MasterModel : ObservableObject { self.parentItem.append(neu) + // ItemModel().sortItems(selectedItem: neu, children: neu.children) + if (neu.type != ItemType.TAGROOT) { + neu.sort() + } self.items = neu.children + // items.sort() neu.printTree() } @@ -176,6 +209,7 @@ class MasterModel : ObservableObject { model.zoomed = delegate.settings().zoomed model.jump = delegate.settings().jump model.loop = delegate.settings().autoloop + model.tagging = isTagging videoModel = model showVideo = true @@ -183,7 +217,10 @@ class MasterModel : ObservableObject { func saveVideo(saved: Bool) { 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 delegate.saveItem(selectedItem: baseItem) } @@ -191,7 +228,39 @@ class MasterModel : ObservableObject { else { //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) { @@ -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() { let authenticationContext = LAContext() var error: NSError? diff --git a/kplayer/master/MasterSplitView.swift b/kplayer/master/MasterSplitView.swift index 35dec77..30d646c 100644 --- a/kplayer/master/MasterSplitView.swift +++ b/kplayer/master/MasterSplitView.swift @@ -13,21 +13,42 @@ struct MasterSplitView: View { var body: some View { ZStack { 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) { Button(action: model.back, label: { Image(systemName: "arrowshape.backward") }) } + ToolbarItem(placement: .navigationBarTrailing) { + Button(action: model.tagIt, label: { + Image(systemName: "tag") + }) + } ToolbarItem(placement: .navigationBarTrailing) { Button(action: { model.showSettings = true }, label: { Image(systemName: "slider.horizontal.3") }) } - ToolbarItem(placement: .navigationBarTrailing) { + ToolbarItem(placement: .navigationBarTrailing) { Button(action: completionHandler, label: { Image(systemName: "xmark.square") }) @@ -38,6 +59,11 @@ struct MasterSplitView: View { HStack { Text(model.selectedItem?.longName() ?? "").font(.title3) Spacer() + if model.selectedItem != nil { + Button(action: model.overview, label: { + Text("Overview") + }) + } }.background(Color(UIColor.systemGray6)) DetailView(model: model) } diff --git a/kplayer/master/MasterViewController.swift b/kplayer/master/MasterViewController.swift index 30f3e2e..bcdb6eb 100644 --- a/kplayer/master/MasterViewController.swift +++ b/kplayer/master/MasterViewController.swift @@ -177,6 +177,13 @@ class MasterViewController: UITableViewController, UISearchResultsUpdating, UITa let submitAction = UIAlertAction(title: "Submit", style: .default) { [unowned ac] _ in 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") { DatabaseManager.sharedInstance.createTag(answer) let m = MediaItem(name: answer, path: answer, root: "tags", type: ItemType.TAG) diff --git a/kplayer/master/NetworkDelegate.swift b/kplayer/master/NetworkDelegate.swift index d4ad9d8..764bd95 100644 --- a/kplayer/master/NetworkDelegate.swift +++ b/kplayer/master/NetworkDelegate.swift @@ -115,7 +115,14 @@ class NetworkDelegate: MasterDelegate, DetailDelegate { func saveItem(selectedItem: MediaItem) { 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 { item = selectedItem.parent! } diff --git a/kplayer/photo/SPhotoView.swift b/kplayer/photo/SPhotoView.swift index 04eda4b..fb46d5e 100644 --- a/kplayer/photo/SPhotoView.swift +++ b/kplayer/photo/SPhotoView.swift @@ -74,6 +74,7 @@ struct SPhotoView: View { print("3 tapped!") if model.scale == 1 { model.scale = lastScale + model.dragOffset = CGSize(width: 600, height: 0) } else { lastScale = model.scale model.scale = 1 diff --git a/kplayer/util/AsyncImage.swift b/kplayer/util/AsyncImage.swift index ee35a10..f15bf39 100644 --- a/kplayer/util/AsyncImage.swift +++ b/kplayer/util/AsyncImage.swift @@ -33,7 +33,7 @@ struct AsyncImage: View { image(item.thumbImage!.scaleToSize(66.0, height: 44.0)) } else { - image(item.thumbImage!.scaleToSize(15 * 16, height: 15 * 9)) + image(item.thumbImage!.scaleToHeight(15 * 16)) } } else { placeholder diff --git a/kplayer/util/UIImageExtension.swift b/kplayer/util/UIImageExtension.swift index bc7cacf..cc89935 100644 --- a/kplayer/util/UIImageExtension.swift +++ b/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? { guard let newImage = self.cgImage else { return nil } diff --git a/kplayer/video/SVideoModel.swift b/kplayer/video/SVideoModel.swift index 1a8dd14..a0f9be9 100644 --- a/kplayer/video/SVideoModel.swift +++ b/kplayer/video/SVideoModel.swift @@ -25,6 +25,7 @@ class SVideoModel : ObservableObject { @Published var edit = false @Published var dirty = false @Published var loop = false + @Published var tagging = false @Published var slow = false @Published var zoomed = false @Published var jump = false diff --git a/kplayer/video/SVideoPlayer.swift b/kplayer/video/SVideoPlayer.swift index 691762b..e0b837f 100644 --- a/kplayer/video/SVideoPlayer.swift +++ b/kplayer/video/SVideoPlayer.swift @@ -25,6 +25,7 @@ struct SVideoPlayer: View, EditItemDelegate { @State var confirmationShown = false @State var dirtyShown = false @State var blackShown = false + @State var showSettings = false @State var truncateShown = false @State var seekSmoothly = false @State var upsidedown = false @@ -42,13 +43,18 @@ struct SVideoPlayer: View, EditItemDelegate { @State var xoffs = 0.0 @State var rotazero = -1000.0 + @State var loop5 = false + @State var loop5start = 0.0 + @State var cutFlag = false @State var lores = false @State var second = false + @State var tap3 = false @State var secondScale = 0.75 @State var secondOffset = CGSize(width: -70, height: 50) @State var orientation = UIDevice.current.orientation + @State private var scrollTarget: Int? let orientationChanged = NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification) .makeConnectable() @@ -65,6 +71,10 @@ struct SVideoPlayer: View, EditItemDelegate { self.playerLooper = AVPlayerLooper(player: player, templateItem: model.currentPlayerItem()) } + func isEnd() -> Bool { + model.paused && model.currentSnapshot.time > 0 + } + func cleanup() { if model.observer != nil && model.observed != nil && model.observed! === player { 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 { VStack { 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) + 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) { - 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) { HStack { ForEach(model.allItems) { item in Button(action: { gotoSnapshot(item) + scrollvalue.scrollTo(item.id) }) { AsyncImage(item: item, placeholder: { Text("Loading ...") }, 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 VStack { @@ -214,25 +269,14 @@ struct SVideoPlayer: View, EditItemDelegate { .scaleEffect(small ? 1 : model.scale) .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) - .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( DragGesture() .onChanged { gesture in @@ -323,12 +367,7 @@ struct SVideoPlayer: View, EditItemDelegate { .frame(height: 30) .buttonStyle(BorderlessButtonStyle()) Button(action: { - if model.paused { - player.play() - } else { - player.pause() - } - model.paused = !model.paused + togglePause() }, label: { Text("pause") }) @@ -366,7 +405,8 @@ struct SVideoPlayer: View, EditItemDelegate { } Button(action: { cut() }) { Text("cut") - }.foregroundColor(cutFlag ? Color.yellow : Color.blue) + } + .foregroundColor(cutFlag ? Color.yellow : Color.blue) .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) + } 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 { v.overlay(VStack { 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() { model.observed = player 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) + }.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) -> () { { time in if timeCounter >= 1 { @@ -503,6 +590,11 @@ struct SVideoPlayer: View, EditItemDelegate { if timeSlomoCounter >= 1 { timeSlomoCounter -= 1 } + if loop5 { + if time.seconds > loop5start + 5.0 { + seekTime(loop5start) + } + } if model.loop && !model.paused && model.currentSnapshot.length > 0 { if time.seconds > model.currentSnapshot.time + model.currentSnapshot.length { seekTime(model.currentSnapshot.time) @@ -596,6 +688,7 @@ struct SVideoPlayer: View, EditItemDelegate { newItem.external = currentItem.external model.allItems.append(newItem) model.currentSnapshot = newItem + scrollTarget = model.allItems.count } private func move(_ dragged: CGSize, start: CGPoint) -> Bool { @@ -606,7 +699,7 @@ struct SVideoPlayer: View, EditItemDelegate { } if (start.y < 170) { - let delta = (dragged.width + dragged.height) / 200.0 + let delta = (dragged.width + dragged.height) / 100.0 seekTimeSmoothly(smoothTime + delta) return false @@ -614,7 +707,7 @@ struct SVideoPlayer: View, EditItemDelegate { if second || small { return true } else { - let delta = (dragged.width + dragged.height) / 200.0 + let delta = (dragged.width + dragged.height) / 30.0 seekTimeSmoothly(smoothTime + delta) return false @@ -646,7 +739,7 @@ struct SVideoPlayer: View, EditItemDelegate { if i - 1 >= 0 { gotoSnapshot(model.allItems[i - 1]) } else { - gotoSnapshot(model.allItems[model.allItems.count-1]) + gotoSnapshot(model.allItems[model.allItems.count - 1]) } timeCounter = 50 print("jump") @@ -797,7 +890,7 @@ struct SVideoPlayer: View, EditItemDelegate { } model.height = Int(height) - lores = (height <= 1080) && (height*1.5 < heightSpace) + lores = (height <= 1080) && (height * 1.5 < heightSpace) var f = height / heightSpace print("h \(height) \(heightSpace) \(f)") @@ -815,8 +908,7 @@ struct SVideoPlayer: View, EditItemDelegate { model.scale = f model.dragOffset.width = 0 model.dragOffset.height = 0 - } - else { + } else { if (model.scale != 1) { lastScale = model.scale } @@ -824,8 +916,7 @@ struct SVideoPlayer: View, EditItemDelegate { if (lores) { model.scale = secondScale model.dragOffset = secondOffset - } - else { + } else { model.scale = 1 model.dragOffset = CGSize.zero }