Browse Source

Search Tags

master
marcoschmickler 4 years ago
parent
commit
92e559d638
  1. 8
      kplayer.xcodeproj/project.pbxproj
  2. 144
      kplayer/core/DatabaseManager.swift
  3. 2
      kplayer/core/MediaItem.swift
  4. 26
      kplayer/detail/EditItemView.swift
  5. 19
      kplayer/master/MasterViewController.swift
  6. 50
      kplayer/master/SearchItemView.swift
  7. 98
      kplayer/util/FlexibleView.swift

8
kplayer.xcodeproj/project.pbxproj

@ -15,6 +15,7 @@
1C73631EACF56BABD3B2BCFB /* LayoutTools.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736BC4450890C45F8FBC63 /* LayoutTools.swift */; };
1C73633C00C18FDA2E9F0A2F /* KNetworkProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736DCD945ABAE984FF43EF /* KNetworkProtocol.swift */; };
1C73635138BBD2BB480A308F /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1C736777456388CA571DA17B /* MediaPlayer.framework */; };
1C7363C2E744C2318879127D /* FlexibleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736D50A22FC4553165199D /* FlexibleView.swift */; };
1C7363D4C34EBBD5C7AAD0A8 /* scratch.txt in Resources */ = {isa = PBXBuildFile; fileRef = 1C7363E0DDA5854D55F8836E /* scratch.txt */; };
1C73640D928DE56D35175D39 /* UploadOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736260E748CF136FF37EA7 /* UploadOperation.swift */; };
1C73646F87B495A47D7943C7 /* NetData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7369EC16B19B32B515169E /* NetData.swift */; };
@ -44,6 +45,7 @@
1C736A622876405F3EE2D043 /* EditItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7366C09381DC0052B52B69 /* EditItemView.swift */; };
1C736A7B6221A1D50FB3904C /* ItemType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73631C96E6C860833052CA /* ItemType.swift */; };
1C736B4B0889BD35DC566124 /* nspersistentcontainer-defaults-swift.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7361F01841F546FA7AFD58 /* nspersistentcontainer-defaults-swift.swift */; };
1C736C8DAD6C2FBB9A2EA625 /* SearchItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73654AB95A2D629833BEC5 /* SearchItemView.swift */; };
1C736D16E81BA1FB325200E0 /* HanekeFetchOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7360744ABACC3557D05760 /* HanekeFetchOperation.swift */; };
1C736D24891597F2728230EE /* ImageLoadOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7360A94DBECA685ED8602F /* ImageLoadOperation.swift */; };
1C736D24B49451141CD4B64D /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7369F53095B7A4D65679C2 /* DetailViewController.swift */; };
@ -106,6 +108,7 @@
1C7364709899FF62774B0199 /* VideoHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoHelper.swift; sourceTree = "<group>"; };
1C73648CEC974A2500172064 /* ViewControllerExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewControllerExtensions.swift; sourceTree = "<group>"; };
1C7364F10BED5DA0F1C0423C /* NetworkDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkDelegate.swift; sourceTree = "<group>"; };
1C73654AB95A2D629833BEC5 /* SearchItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchItemView.swift; sourceTree = "<group>"; };
1C736595533B56039C417E0D /* ServerDownloadDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerDownloadDelegate.swift; sourceTree = "<group>"; };
1C73659CC9B523B957E58DC6 /* LocalManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalManager.swift; sourceTree = "<group>"; };
1C7365B06FA66294E99AC2D3 /* NetworkManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkManager.swift; sourceTree = "<group>"; };
@ -127,6 +130,7 @@
1C736BC4450890C45F8FBC63 /* LayoutTools.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LayoutTools.swift; sourceTree = "<group>"; };
1C736C94157754DE1C808173 /* KSettingsModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KSettingsModel.swift; sourceTree = "<group>"; };
1C736CF935C2A6AB916BE494 /* scratch.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = scratch.txt; sourceTree = "<group>"; };
1C736D50A22FC4553165199D /* FlexibleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlexibleView.swift; sourceTree = "<group>"; };
1C736D9BB5498E7E8F11C754 /* HeaderCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeaderCell.swift; sourceTree = "<group>"; };
1C736DBB6986A8B62963FBB3 /* HtmlParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HtmlParser.swift; sourceTree = "<group>"; };
1C736DCCE3AA9993E15F7652 /* UIImageExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIImageExtension.swift; sourceTree = "<group>"; };
@ -214,6 +218,7 @@
1C7364F10BED5DA0F1C0423C /* NetworkDelegate.swift */,
1C736A6E8396EE306B1AD3A8 /* KSettingsView.swift */,
1C736595533B56039C417E0D /* ServerDownloadDelegate.swift */,
1C73654AB95A2D629833BEC5 /* SearchItemView.swift */,
);
path = master;
sourceTree = "<group>";
@ -239,6 +244,7 @@
1C7361F01841F546FA7AFD58 /* nspersistentcontainer-defaults-swift.swift */,
1C7362DE1D6BE634D7C2ACBF /* KPersistentContainer.swift */,
1C7360295486647982CFEACF /* UIViewController+Alert.swift */,
1C736D50A22FC4553165199D /* FlexibleView.swift */,
);
path = util;
sourceTree = "<group>";
@ -582,6 +588,8 @@
1C736DFA8544C773E3C22F10 /* VideoPlayerView.swift in Sources */,
1C736D5A7C7CB9B14AF0ECA6 /* DetailViewController+Show.swift in Sources */,
1C7367084839D2E8B180DB74 /* UIViewController+Alert.swift in Sources */,
1C7363C2E744C2318879127D /* FlexibleView.swift in Sources */,
1C736C8DAD6C2FBB9A2EA625 /* SearchItemView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

144
kplayer/core/DatabaseManager.swift

@ -13,9 +13,13 @@ class DatabaseManager {
let managedObjectContext : NSManagedObjectContext
let persistentContainer : NSPersistentContainer
var allTags = [String]()
init() {
self.persistentContainer = KPersistentContainer(defaultContainerWithName: "kplayer")
self.managedObjectContext = self.persistentContainer.viewContext
loadAllTags()
}
func enrichItem(_ item: MediaItem) {
@ -42,6 +46,10 @@ class DatabaseManager {
c.offset.y = CGFloat(s.offy)
c.scale = s.scale
c.rating = Int(s.rating)
for t in s.tags as! Set<KTag> {
c.tags.append(t.name!)
}
}
}
print(s)
@ -133,6 +141,23 @@ class DatabaseManager {
snap.rating = Int16(c.rating)
snap.thumb = c.thumbUrl
var ct = [String](c.tags)
for t in snap.tags as! Set<KTag> {
if !c.tags.contains(t.name!) {
snap.removeFromTags(t)
print("remove \(t.name!)")
}
else {
ct.removeAll(where: { e in e == t.name!})
}
}
for a in ct {
addTag(a, snapshot: snap)
print("add \(a)")
}
print("DB -- Update snapshot at \(c.indexId)")
}
}
@ -148,6 +173,58 @@ class DatabaseManager {
tag.name = answer
save()
loadAllTags()
}
func loadAllTags() {
let fetchRequest = KTag.fetchRequest()
let results = try! managedObjectContext.fetch(fetchRequest)
allTags.removeAll()
for t in results {
allTags.append(t.name!)
}
}
func searchSnapshots(item: MediaItem) -> Void {
let fetch = KSnapshot.fetchRequest()
if item.tags.isEmpty {
return
}
let firstTag = item.tags[0]
item.children.removeAll()
fetch.predicate = NSPredicate(format: "ANY tags.name == %@", firstTag)
let results = try! managedObjectContext.fetch(fetch)
for s in results {
var accept = true
for t in item.tags {
var hasTag = false
for has in s.tags as! Set<KTag> {
if has.name == t {
hasTag = true
break
}
}
if !hasTag {
accept = false
}
}
if accept {
let n = loadSnapshot(s: s)
n.loaded = true
item.children.append(n)
}
}
}
func loadTags(completionHandler: @escaping Weiter) -> Void {
@ -163,26 +240,8 @@ class DatabaseManager {
let snapshots = t.tagged as! Set<KSnapshot>
for s in snapshots {
let i = s.item!
let sitem = MediaItem(name: i.name!, path: i.path!, root: i.root!, type: ItemType.VIDEO)
sitem.loaded = true
let sitem = loadSnapshot(s: s)
sitem.parent = tag
let c = MediaItem(name: i.name!, path: i.path!, root: i.root!, type: ItemType.SNAPSHOT)
c.time = s.time
c.length = s.length
c.loop = s.loop
c.size.width = CGFloat(s.sizex)
c.size.height = CGFloat(s.sizey)
c.offset.x = CGFloat(s.offx)
c.offset.y = CGFloat(s.offy)
c.scale = s.scale
c.rating = Int(s.rating)
c.thumbUrl = s.thumb
c.indexId = Int(s.index)
c.parent = sitem
sitem.children.append(c)
tag.children.append(sitem)
}
@ -191,12 +250,57 @@ class DatabaseManager {
let m = MediaItem(name: "new", path: "new", root: "tags", type: ItemType.TAG)
m.local = true
res.append(m)
let c = MediaItem(name: "combine", path: "combine", root: "tags", type: ItemType.TAG)
c.local = true
res.append(c)
completionHandler(res)
}
private func loadSnapshot(s: KSnapshot) -> MediaItem {
let i = s.item!
let sitem = MediaItem(name: i.name!, path: i.path!, root: i.root!, type: ItemType.VIDEO)
sitem.loaded = true
let c = MediaItem(name: i.name!, path: i.path!, root: i.root!, type: ItemType.SNAPSHOT)
c.time = s.time
c.length = s.length
c.loop = s.loop
c.size.width = CGFloat(s.sizex)
c.size.height = CGFloat(s.sizey)
c.offset.x = CGFloat(s.offx)
c.offset.y = CGFloat(s.offy)
c.scale = s.scale
c.rating = Int(s.rating)
c.thumbUrl = s.thumb
c.indexId = Int(s.index)
c.parent = sitem
for t in s.tags as! Set<KTag> {
c.tags.append(t.name!)
}
sitem.children.append(c)
return sitem
}
func addTag(_ name: String, snapshot: KSnapshot) {
let kFetch = KTag.fetchRequest()
kFetch.predicate = NSPredicate(format: "name == %@", name)
let tags = try! managedObjectContext.fetch(kFetch)
if tags.isEmpty {
return
}
let tag = tags[0]
snapshot.addToTags(tag)
}
func addTag(_ name: String, _ item: MediaItem) {
let kFetch = KTag.fetchRequest()
kFetch.predicate = NSPredicate(format: "name == %@", name)

2
kplayer/core/MediaItem.swift

@ -84,6 +84,8 @@ class MediaItem: CustomDebugStringConvertible, ObservableObject, Identifiable {
var size = CGSize()
var tags = [String]()
convenience init(model: MediaModel) {
self.init(name: model.name, path: model.path, root: model.root, type: model.type)

26
kplayer/detail/EditItemView.swift

@ -89,6 +89,32 @@ struct EditItemView: View {
Text("cancel")
}).padding(10).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())
}
}.background(Color.clear)
}.background(Color.clear)
}

19
kplayer/master/MasterViewController.swift

@ -9,6 +9,7 @@
import UIKit
import CoreData
import LocalAuthentication
import SwiftUI
typealias Weiter = ([MediaItem]) -> Void
@ -80,6 +81,11 @@ class MasterViewController: UITableViewController, UISearchResultsUpdating, UITa
let selectedItem = model.items[indexPath.row]
currentSelection = selectedItem
if (selectedItem.local && selectedItem.name == "combine") {
searchView(item: selectedItem)
// gotoDetails(selectedItem)
}
if (selectedItem.local && selectedItem.name == "new") {
createFolder(selectedItem)
return
@ -118,6 +124,19 @@ class MasterViewController: UITableViewController, UISearchResultsUpdating, UITa
}
}
func searchView(item: MediaItem) {
let kv = SearchItemView(item: item, completionHandler: {
self.dismiss(animated: true, completion: nil);
DatabaseManager.sharedInstance.searchSnapshots(item: item)
self.gotoDetails(item)
} )
let pc = UIHostingController(rootView: kv)
let navController = UINavigationController(rootViewController: pc) // Creating a navigation controller with pc at the root of the navigation stack.
navController.modalPresentationStyle = .automatic
present(navController, animated: false, completion: nil)
}
func gotoNextFolder(_ selectedItem: MediaItem) {
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc = mainStoryboard.instantiateViewController(withIdentifier: "mastertable") as! MasterViewController

50
kplayer/master/SearchItemView.swift

@ -0,0 +1,50 @@
//
// Created by Marco Schmickler on 26.01.22.
// Copyright (c) 2022 Marco Schmickler. All rights reserved.
//
import Foundation
import SwiftUI
struct SearchItemView: View {
@ObservedObject
var item: MediaItem
var completionHandler: (() -> Void)?
var body: some View {
VStack {
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)
}
item.objectWillChange.send()
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())
}
Button(action: {
self.completionHandler?()
}, label: {
Text("ok")
})
}
}
}

98
kplayer/util/FlexibleView.swift

@ -0,0 +1,98 @@
//
// Created by Marco Schmickler on 26.01.22.
// Copyright (c) 2022 Marco Schmickler. All rights reserved.
//
import Foundation
import SwiftUI
struct _FlexibleView<Data: Collection, Content: View>: View where Data.Element: Hashable {
let availableWidth: CGFloat
let data: Data
let spacing: CGFloat
let alignment: HorizontalAlignment
let content: (Data.Element) -> Content
@State var elementsSize: [Data.Element: CGSize] = [:]
var body : some View {
VStack(alignment: alignment, spacing: spacing) {
ForEach(computeRows(), id: \.self) { rowElements in
HStack(spacing: spacing) {
ForEach(rowElements, id: \.self) { element in
content(element)
.fixedSize()
.readSize { size in
elementsSize[element] = size
}
}
}
}
}
}
func computeRows() -> [[Data.Element]] {
var rows: [[Data.Element]] = [[]]
var currentRow = 0
var remainingWidth = availableWidth
for element in data {
let elementSize = elementsSize[element, default: CGSize(width: availableWidth, height: 1)]
if remainingWidth - (elementSize.width + spacing) >= 0 {
rows[currentRow].append(element)
} else {
currentRow = currentRow + 1
rows.append([element])
remainingWidth = availableWidth
}
remainingWidth = remainingWidth - (elementSize.width + spacing)
}
return rows
}
}
struct FlexibleView<Data: Collection, Content: View>: View where Data.Element: Hashable {
let data: Data
let spacing: CGFloat
let alignment: HorizontalAlignment
let content: (Data.Element) -> Content
@State private var availableWidth: CGFloat = 0
var body: some View {
ZStack(alignment: Alignment(horizontal: alignment, vertical: .center)) {
Color.clear
.frame(height: 1)
.readSize { size in
availableWidth = size.width
}
_FlexibleView(
availableWidth: availableWidth,
data: data,
spacing: spacing,
alignment: alignment,
content: content
)
}
}
}
extension View {
func readSize(onChange: @escaping (CGSize) -> Void) -> some View {
background(
GeometryReader { geometryProxy in
Color.clear
.preference(key: SizePreferenceKey.self, value: geometryProxy.size)
}
)
.onPreferenceChange(SizePreferenceKey.self, perform: onChange)
}
}
private struct SizePreferenceKey: PreferenceKey {
static var defaultValue: CGSize = .zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {}
}
Loading…
Cancel
Save