7 changed files with 327 additions and 20 deletions
-
8kplayer.xcodeproj/project.pbxproj
-
144kplayer/core/DatabaseManager.swift
-
2kplayer/core/MediaItem.swift
-
26kplayer/detail/EditItemView.swift
-
19kplayer/master/MasterViewController.swift
-
50kplayer/master/SearchItemView.swift
-
98kplayer/util/FlexibleView.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") |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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) {} |
||||
|
} |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue