Fix apps issues and bump to v1.1
Fixes: - Fix interface text and glitches when switching themes. - Redesign settings on application. - Optimize code and compilation warnings.
This commit is contained in:
parent
32d4dd3501
commit
957a4ab7e7
@ -267,7 +267,7 @@
|
||||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 001;
|
||||
CURRENT_PROJECT_VERSION = 002;
|
||||
DEVELOPMENT_TEAM = 7CQPG22234;
|
||||
ENABLE_APP_SANDBOX = NO;
|
||||
ENABLE_FILE_ACCESS_MUSIC_FOLDER = readwrite;
|
||||
@ -291,7 +291,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
MARKETING_VERSION = 1.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.jesuschapman.Valentine;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
REGISTER_APP_GROUPS = YES;
|
||||
@ -312,7 +312,7 @@
|
||||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 001;
|
||||
CURRENT_PROJECT_VERSION = 002;
|
||||
DEVELOPMENT_TEAM = 7CQPG22234;
|
||||
ENABLE_APP_SANDBOX = NO;
|
||||
ENABLE_FILE_ACCESS_MUSIC_FOLDER = readwrite;
|
||||
@ -336,7 +336,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
MARKETING_VERSION = 1.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.jesuschapman.Valentine;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
REGISTER_APP_GROUPS = YES;
|
||||
|
||||
@ -14,55 +14,48 @@ struct ContentView: View {
|
||||
@State private var isTargeted = false
|
||||
@State private var isPlaylistVisible = true
|
||||
@State private var wasWide = true
|
||||
@State private var windowSize: CGSize? = nil
|
||||
|
||||
@AppStorage("lastNormalWidth") private var lastNormalWidth: Double = 900
|
||||
@AppStorage("lastNormalHeight") private var lastNormalHeight: Double = 600
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
let isWide = geometry.size.width > 600
|
||||
let currentWidth = geometry.size.width
|
||||
let isWide = currentWidth > 600
|
||||
|
||||
Group {
|
||||
if engine.queue.isEmpty {
|
||||
emptyStateView
|
||||
} else {
|
||||
HStack(spacing: 0) {
|
||||
if isWide {
|
||||
if isPlaylistVisible {
|
||||
PlaylistView(engine: engine)
|
||||
.frame(width: 280)
|
||||
.background(Color.black.opacity(0.2))
|
||||
.transition(.move(edge: .leading))
|
||||
}
|
||||
|
||||
if isPlaylistVisible {
|
||||
PlaylistView(engine: engine)
|
||||
.frame(minWidth: isWide ? 280 : nil,
|
||||
idealWidth: isWide ? 280 : nil,
|
||||
maxWidth: isWide ? 280 : .infinity,
|
||||
maxHeight: .infinity)
|
||||
.background(Color.black.opacity(0.2))
|
||||
.transition(.asymmetric(insertion: .move(edge: .leading), removal: .move(edge: .leading)))
|
||||
}
|
||||
|
||||
if isWide || !isPlaylistVisible {
|
||||
PlayerView(
|
||||
engine: engine,
|
||||
togglePlaylist: {},
|
||||
togglePlaylist: {
|
||||
withAnimation(.spring()) {
|
||||
isPlaylistVisible.toggle()
|
||||
}
|
||||
},
|
||||
isPlaylistVisible: isPlaylistVisible,
|
||||
showToggle: false
|
||||
showToggle: !isWide
|
||||
)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
} else {
|
||||
if isPlaylistVisible {
|
||||
PlaylistView(engine: engine)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.background(Color.black.opacity(0.2))
|
||||
.transition(.asymmetric(insertion: .move(edge: .leading), removal: .move(edge: .leading)))
|
||||
} else {
|
||||
PlayerView(
|
||||
engine: engine,
|
||||
togglePlaylist: {},
|
||||
isPlaylistVisible: isPlaylistVisible,
|
||||
showToggle: false
|
||||
)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.transition(.asymmetric(insertion: .move(edge: .trailing), removal: .move(edge: .trailing)))
|
||||
}
|
||||
.transition(.asymmetric(insertion: .move(edge: .trailing), removal: .move(edge: .trailing)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(width: geometry.size.width, height: geometry.size.height)
|
||||
.background(backgroundLayer)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigation) {
|
||||
@ -76,7 +69,11 @@ struct ContentView: View {
|
||||
}
|
||||
}
|
||||
.toolbarBackground(.hidden, for: .windowToolbar)
|
||||
.onChange(of: geometry.size) { newSize in
|
||||
.onDrop(of: ["public.file-url"], isTargeted: $isTargeted) { providers in
|
||||
handleDrop(providers: providers)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.onChange(of: geometry.size) { _, newSize in
|
||||
lastNormalWidth = Double(newSize.width)
|
||||
lastNormalHeight = Double(newSize.height)
|
||||
|
||||
@ -91,10 +88,7 @@ struct ContentView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.onDrop(of: ["public.file-url"], isTargeted: $isTargeted) { providers in
|
||||
handleDrop(providers: providers)
|
||||
}
|
||||
.frame(minWidth: 400, maxWidth: .infinity, minHeight: 540, maxHeight: .infinity)
|
||||
.frame(minWidth: 400, minHeight: 540)
|
||||
}
|
||||
|
||||
private var backgroundLayer: some View {
|
||||
@ -105,17 +99,24 @@ struct ContentView: View {
|
||||
art
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
|
||||
.clipped()
|
||||
.blur(radius: 80)
|
||||
.opacity(0.7)
|
||||
.ignoresSafeArea()
|
||||
.animation(.easeInOut(duration: 1.5), value: engine.currentTrack?.id)
|
||||
} else {
|
||||
LinearGradient(
|
||||
colors: [Color(red: 0.2, green: 0.1, blue: 0.15), Color(red: 0.1, green: 0.1, blue: 0.1)],
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
)
|
||||
.ignoresSafeArea()
|
||||
if colorScheme == .dark {
|
||||
LinearGradient(
|
||||
colors: [Color(red: 0.2, green: 0.1, blue: 0.15), Color(red: 0.1, green: 0.1, blue: 0.1)],
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
)
|
||||
.ignoresSafeArea()
|
||||
} else {
|
||||
Color(NSColor.windowBackgroundColor)
|
||||
.ignoresSafeArea()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -232,3 +233,11 @@ struct HoverZoomButton: View {
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct WindowSizePreferenceKey: PreferenceKey {
|
||||
static var defaultValue: CGSize = .zero
|
||||
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
|
||||
value = nextValue()
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,14 +7,21 @@
|
||||
|
||||
import SwiftUI
|
||||
|
||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||
NSWindow.allowsAutomaticWindowTabbing = false
|
||||
}
|
||||
}
|
||||
|
||||
@main
|
||||
struct ValentineApp: App {
|
||||
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
RootView()
|
||||
}
|
||||
.windowStyle(.hiddenTitleBar)
|
||||
.windowResizability(.contentSize)
|
||||
.windowResizability(.contentMinSize)
|
||||
.commands {
|
||||
ValentineCommands()
|
||||
}
|
||||
@ -26,12 +33,11 @@ struct ValentineApp: App {
|
||||
.windowResizability(.contentSize)
|
||||
.defaultSize(width: 650, height: 480)
|
||||
|
||||
Window("Modify Appearance", id: "lyricsAppearance") {
|
||||
LyricsAppearanceView()
|
||||
Window("Settings", id: "settings") {
|
||||
SettingsView()
|
||||
}
|
||||
.windowStyle(.hiddenTitleBar)
|
||||
.windowResizability(.contentSize)
|
||||
.defaultSize(width: 400, height: 600)
|
||||
.windowResizability(.contentMinSize)
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,12 +58,15 @@ struct RootView: View {
|
||||
.environmentObject(engine)
|
||||
}
|
||||
}
|
||||
.preferredColorScheme(appTheme == 1 ? .light : (appTheme == 2 ? .dark : nil))
|
||||
.animation(.easeInOut, value: isMiniPlayerMode)
|
||||
.onAppear {
|
||||
updateTheme(theme: appTheme)
|
||||
configureWindow(forMiniPlayer: isMiniPlayerMode)
|
||||
}
|
||||
.onChange(of: isMiniPlayerMode) { newValue in
|
||||
.onChange(of: appTheme) { _, newTheme in
|
||||
updateTheme(theme: newTheme)
|
||||
}
|
||||
.onChange(of: isMiniPlayerMode) { _, newValue in
|
||||
configureWindow(forMiniPlayer: newValue)
|
||||
}
|
||||
.sheet(isPresented: $engine.showLyricsEditor) {
|
||||
@ -76,11 +85,28 @@ struct RootView: View {
|
||||
.onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("ReinstallMutagen"))) { _ in engine.showMutagenInstaller = true }
|
||||
}
|
||||
|
||||
private func updateTheme(theme: Int) {
|
||||
#if os(macOS)
|
||||
DispatchQueue.main.async {
|
||||
let appearance: NSAppearance?
|
||||
switch theme {
|
||||
case 1: appearance = NSAppearance(named: .aqua)
|
||||
case 2: appearance = NSAppearance(named: .darkAqua)
|
||||
default: appearance = nil
|
||||
}
|
||||
NSApplication.shared.appearance = appearance
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private func configureWindow(forMiniPlayer: Bool) {
|
||||
#if os(macOS)
|
||||
DispatchQueue.main.async {
|
||||
for window in NSApplication.shared.windows {
|
||||
if window.className == "NSWindow" || window.className.contains("SwiftUI") {
|
||||
let id = window.identifier?.rawValue ?? ""
|
||||
if id.contains("settings") || id.contains("about") { continue }
|
||||
|
||||
window.level = forMiniPlayer ? .floating : .normal
|
||||
window.standardWindowButton(.closeButton)?.isHidden = forMiniPlayer
|
||||
window.standardWindowButton(.miniaturizeButton)?.isHidden = forMiniPlayer
|
||||
@ -119,11 +145,7 @@ struct RootView: View {
|
||||
}
|
||||
|
||||
struct ValentineCommands: Commands {
|
||||
@AppStorage("isGlowEffectEnabled") private var isGlowEffectEnabled = false
|
||||
@AppStorage("isNeonEffectEnabled") private var isNeonEffectEnabled = false
|
||||
@AppStorage("isMiniPlayerMode") private var isMiniPlayerMode = false
|
||||
@AppStorage("miniPlayerGlassMode") private var miniPlayerGlassMode = 0
|
||||
@AppStorage("appTheme") private var appTheme = 0
|
||||
@Environment(\.openWindow) var openWindow
|
||||
|
||||
var body: some Commands {
|
||||
@ -135,6 +157,15 @@ struct ValentineCommands: Commands {
|
||||
}
|
||||
}
|
||||
|
||||
CommandGroup(replacing: .appSettings) {
|
||||
Button(action: { openWindow(id: "settings") }) {
|
||||
Label("Settings...", systemImage: "gearshape")
|
||||
}
|
||||
.keyboardShortcut(",", modifiers: [.command])
|
||||
}
|
||||
|
||||
|
||||
|
||||
CommandGroup(replacing: .newItem) {
|
||||
Button(action: { NotificationCenter.default.post(name: NSNotification.Name("AddFile"), object: nil) }) {
|
||||
Label("Add File...", systemImage: "doc.badge.plus")
|
||||
@ -160,45 +191,15 @@ struct ValentineCommands: Commands {
|
||||
Label("Edit Lyrics", systemImage: "music.note.list")
|
||||
}
|
||||
.keyboardShortcut("e", modifiers: [.command])
|
||||
|
||||
Button(action: { openWindow(id: "lyricsAppearance") }) {
|
||||
Label("Modify Appearance", systemImage: "textformat.alt")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
CommandGroup(replacing: .help) {
|
||||
Button(action: { NotificationCenter.default.post(name: NSNotification.Name("ReinstallMutagen"), object: nil) }) {
|
||||
Label("Reinstall Mutagen", systemImage: "arrow.triangle.2.circlepath")
|
||||
}
|
||||
}
|
||||
|
||||
CommandGroup(after: .toolbar) {
|
||||
Divider()
|
||||
Menu {
|
||||
Toggle("Glow Effect", isOn: $isGlowEffectEnabled)
|
||||
Toggle("Neon Effect", isOn: $isNeonEffectEnabled)
|
||||
} label: {
|
||||
Label("Synced Lyrics Settings", systemImage: "sparkles")
|
||||
}
|
||||
Menu {
|
||||
Picker("Mode", selection: $miniPlayerGlassMode) {
|
||||
Text("Tinted (System Theme)").tag(0)
|
||||
Text("Transparent (No Tint)").tag(1)
|
||||
}
|
||||
.pickerStyle(InlinePickerStyle())
|
||||
} label: {
|
||||
Label("Mini Player Background", systemImage: "macwindow")
|
||||
}
|
||||
Menu {
|
||||
Picker("Theme", selection: $appTheme) {
|
||||
Text("Follow System").tag(0)
|
||||
Text("Light").tag(1)
|
||||
Text("Dark").tag(2)
|
||||
}
|
||||
.pickerStyle(InlinePickerStyle())
|
||||
} label: {
|
||||
Label("App Theme", systemImage: "paintpalette")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
CommandGroup(after: .windowList) {
|
||||
Button(action: { isMiniPlayerMode.toggle() }) {
|
||||
Label(isMiniPlayerMode ? "Switch to Full Player" : "Switch to Mini-Player", systemImage: isMiniPlayerMode ? "arrow.up.left.and.arrow.down.right" : "pip.enter")
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -32,7 +32,7 @@ struct Track: Identifiable, Hashable {
|
||||
}
|
||||
|
||||
mutating func loadMetadata() async {
|
||||
let asset = AVAsset(url: url)
|
||||
let asset = AVURLAsset(url: url)
|
||||
|
||||
do {
|
||||
self.duration = try await asset.load(.duration).seconds
|
||||
|
||||
@ -56,11 +56,13 @@ class AudioEngine: ObservableObject {
|
||||
setupRemoteCommandCenter()
|
||||
|
||||
NotificationCenter.default.addObserver(forName: UserDefaults.didChangeNotification, object: nil, queue: .main) { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
let newGlow = UserDefaults.standard.bool(forKey: "isGlowEffectEnabled")
|
||||
let newNeon = UserDefaults.standard.bool(forKey: "isNeonEffectEnabled")
|
||||
if self.isGlowEffectEnabled != newGlow { self.isGlowEffectEnabled = newGlow }
|
||||
if self.isNeonEffectEnabled != newNeon { self.isNeonEffectEnabled = newNeon }
|
||||
Task { @MainActor in
|
||||
guard let self = self else { return }
|
||||
let newGlow = UserDefaults.standard.bool(forKey: "isGlowEffectEnabled")
|
||||
let newNeon = UserDefaults.standard.bool(forKey: "isNeonEffectEnabled")
|
||||
if self.isGlowEffectEnabled != newGlow { self.isGlowEffectEnabled = newGlow }
|
||||
if self.isNeonEffectEnabled != newNeon { self.isNeonEffectEnabled = newNeon }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -170,7 +172,7 @@ class AudioEngine: ObservableObject {
|
||||
if fileManager.fileExists(atPath: url.path, isDirectory: &isDirectory) {
|
||||
if isDirectory.boolValue {
|
||||
if let enumerator = fileManager.enumerator(at: url, includingPropertiesForKeys: [.isRegularFileKey], options: [.skipsHiddenFiles, .skipsPackageDescendants]) {
|
||||
for case let fileURL as URL in enumerator {
|
||||
while let fileURL = enumerator.nextObject() as? URL {
|
||||
let ext = fileURL.pathExtension.lowercased()
|
||||
if supportedExtensions.contains(ext) {
|
||||
audioURLs.append(fileURL)
|
||||
@ -212,11 +214,13 @@ class AudioEngine: ObservableObject {
|
||||
|
||||
let interval = CMTime(seconds: 0.1, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
|
||||
timeObserver = player?.addPeriodicTimeObserver(forInterval: interval, queue: .main) { [weak self] time in
|
||||
guard let self = self else { return }
|
||||
self.currentTime = time.seconds
|
||||
|
||||
if self.duration > 0 && self.currentTime >= self.duration - 0.1 {
|
||||
self.nextTrack(isAutomatic: true)
|
||||
Task { @MainActor in
|
||||
guard let self = self else { return }
|
||||
self.currentTime = time.seconds
|
||||
|
||||
if self.duration > 0 && self.currentTime >= self.duration - 0.1 {
|
||||
self.nextTrack(isAutomatic: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,142 +0,0 @@
|
||||
import SwiftUI
|
||||
|
||||
struct LyricsAppearanceView: View {
|
||||
@ObservedObject var settings = LyricsAppearanceManager.shared
|
||||
|
||||
@State private var previewIsDark = true
|
||||
@State private var previewNeon = false
|
||||
@State private var previewGlow = false
|
||||
@State private var applyMode = 0 // 0: Both Themes, 1: Specific Theme
|
||||
|
||||
// Bindings for the currently selected theme to edit
|
||||
private var isEditingDark: Bool {
|
||||
if applyMode == 0 { return previewIsDark }
|
||||
return previewIsDark // Wait, if specific theme, it edits the preview's current theme.
|
||||
}
|
||||
|
||||
private var fontDesignBinding: Binding<Int> {
|
||||
Binding(
|
||||
get: { isEditingDark ? settings.fontDesignDark : settings.fontDesignLight },
|
||||
set: { newValue in
|
||||
if applyMode == 0 {
|
||||
settings.fontDesignDark = newValue
|
||||
settings.fontDesignLight = newValue
|
||||
} else {
|
||||
if isEditingDark { settings.fontDesignDark = newValue }
|
||||
else { settings.fontDesignLight = newValue }
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private func colorBinding(for stringBindingLight: Binding<String>, stringBindingDark: Binding<String>, defaultColor: Color) -> Binding<Color> {
|
||||
Binding<Color>(
|
||||
get: {
|
||||
let hex = isEditingDark ? stringBindingDark.wrappedValue : stringBindingLight.wrappedValue
|
||||
return hex.isEmpty ? defaultColor : Color(hex: hex)
|
||||
},
|
||||
set: { newValue in
|
||||
let hex = newValue.toHex()
|
||||
if applyMode == 0 {
|
||||
stringBindingDark.wrappedValue = hex
|
||||
stringBindingLight.wrappedValue = hex
|
||||
} else {
|
||||
if isEditingDark { stringBindingDark.wrappedValue = hex }
|
||||
else { stringBindingLight.wrappedValue = hex }
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
// PREVIEW SECTION
|
||||
VStack {
|
||||
HStack {
|
||||
Text("Preview")
|
||||
.font(.headline)
|
||||
.foregroundColor(.primary)
|
||||
Spacer()
|
||||
Toggle("Dark Mode", isOn: $previewIsDark)
|
||||
.toggleStyle(.switch)
|
||||
.controlSize(.mini)
|
||||
}
|
||||
.padding()
|
||||
|
||||
ZStack {
|
||||
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
||||
.fill(previewIsDark ? Color(white: 0.1) : Color(white: 0.95))
|
||||
|
||||
VStack(spacing: 16) {
|
||||
previewText("Lorem ipsum dolor sit amet", isActive: false)
|
||||
previewText("Consectetur adipiscing elit", isActive: true)
|
||||
previewText("Sed do eiusmod tempor", isActive: false)
|
||||
}
|
||||
}
|
||||
.frame(height: 180)
|
||||
.padding(.horizontal)
|
||||
.padding(.bottom)
|
||||
}
|
||||
.background(Color(NSColor.windowBackgroundColor))
|
||||
|
||||
Divider()
|
||||
|
||||
// SETTINGS SECTION
|
||||
ScrollView {
|
||||
VStack(spacing: 24) {
|
||||
Picker("Apply to:", selection: $applyMode) {
|
||||
Text("Both Themes").tag(0)
|
||||
Text(previewIsDark ? "Dark Theme Only" : "Light Theme Only").tag(1)
|
||||
}
|
||||
.pickerStyle(SegmentedPickerStyle())
|
||||
|
||||
HStack {
|
||||
Toggle("Neon Effect", isOn: $previewNeon)
|
||||
Spacer()
|
||||
Toggle("Glow Effect", isOn: $previewGlow)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text("Typography")
|
||||
.font(.subheadline)
|
||||
.fontWeight(.semibold)
|
||||
|
||||
Picker("Font Design", selection: fontDesignBinding) {
|
||||
Text("Rounded").tag(1)
|
||||
Text("Default").tag(0)
|
||||
Text("Monospaced").tag(2)
|
||||
Text("Serif").tag(3)
|
||||
}
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text("Colors")
|
||||
.font(.subheadline)
|
||||
.fontWeight(.semibold)
|
||||
|
||||
ColorPicker("Font Color", selection: colorBinding(for: $settings.fontColorLight, stringBindingDark: $settings.fontColorDark, defaultColor: .primary))
|
||||
ColorPicker("Neon Color", selection: colorBinding(for: $settings.neonColorLight, stringBindingDark: $settings.neonColorDark, defaultColor: .white))
|
||||
ColorPicker("Glow Color", selection: colorBinding(for: $settings.glowColorLight, stringBindingDark: $settings.glowColorDark, defaultColor: .accentColor))
|
||||
}
|
||||
|
||||
Button("Reset to Defaults") {
|
||||
settings.resetToDefaults()
|
||||
}
|
||||
.padding(.top, 10)
|
||||
}
|
||||
.padding(24)
|
||||
}
|
||||
}
|
||||
.frame(width: 400, height: 600)
|
||||
}
|
||||
|
||||
private func previewText(_ text: String, isActive: Bool) -> some View {
|
||||
Text(text)
|
||||
.font(.system(size: isActive ? 24 : 18, weight: isActive ? .bold : .medium, design: settings.getFontDesign(isDark: previewIsDark)))
|
||||
.foregroundColor((previewNeon && isActive) ? settings.getNeonColor(isDark: previewIsDark) : settings.getFontColor(isDark: previewIsDark, isActive: isActive))
|
||||
.shadow(color: (previewNeon && isActive) ? settings.getNeonColor(isDark: previewIsDark).opacity(0.8) : .clear, radius: 10, x: 0, y: 0)
|
||||
.shadow(color: (previewNeon && isActive) ? settings.getNeonColor(isDark: previewIsDark).opacity(0.4) : .clear, radius: 20, x: 0, y: 0)
|
||||
.shadow(color: (previewGlow && isActive) ? settings.getGlowColor(isDark: previewIsDark).opacity(0.8) : .clear, radius: 15, x: 0, y: 0)
|
||||
.shadow(color: (previewGlow && isActive) ? settings.getGlowColor(isDark: previewIsDark).opacity(0.5) : .clear, radius: 5, x: 0, y: 0)
|
||||
}
|
||||
}
|
||||
@ -59,14 +59,14 @@ struct LyricsView: View {
|
||||
endPoint: .bottom
|
||||
)
|
||||
)
|
||||
.onChange(of: activeLineIndex) { newIndex in
|
||||
.onChange(of: activeLineIndex) { _, newIndex in
|
||||
if let index = newIndex {
|
||||
withAnimation(.spring(response: 0.6, dampingFraction: 0.8, blendDuration: 0)) {
|
||||
proxy.scrollTo(index, anchor: .center)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: geo.size) { _ in
|
||||
.onChange(of: geo.size) { _, _ in
|
||||
if let index = activeLineIndex {
|
||||
proxy.scrollTo(index, anchor: .center)
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ struct PlayerView: View {
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 8) {
|
||||
Spacer(minLength: 8)
|
||||
Spacer(minLength: 0)
|
||||
|
||||
if engine.showLyrics {
|
||||
LyricsView(engine: engine)
|
||||
@ -23,6 +23,8 @@ struct PlayerView: View {
|
||||
art
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.clipped()
|
||||
)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 16))
|
||||
.shadow(color: .black.opacity(0.4), radius: 15, x: 0, y: 10)
|
||||
@ -40,8 +42,7 @@ struct PlayerView: View {
|
||||
.layoutPriority(1)
|
||||
}
|
||||
|
||||
Spacer(minLength: 16)
|
||||
|
||||
Spacer(minLength: 0)
|
||||
WaveformView(engine: engine)
|
||||
.frame(height: 50)
|
||||
.padding(.horizontal, 32)
|
||||
@ -70,19 +71,19 @@ struct PlayerView: View {
|
||||
.padding(.horizontal, 16)
|
||||
.layoutPriority(1)
|
||||
|
||||
Spacer(minLength: 8)
|
||||
Spacer(minLength: 0)
|
||||
|
||||
PlaybackControlsView(engine: engine)
|
||||
.layoutPriority(2)
|
||||
|
||||
Spacer(minLength: 16)
|
||||
Spacer(minLength: 0)
|
||||
|
||||
VolumeControlView(engine: engine)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.horizontal, 32)
|
||||
.layoutPriority(2)
|
||||
|
||||
Spacer(minLength: 16)
|
||||
Spacer(minLength: 0)
|
||||
|
||||
HStack(spacing: 24) {
|
||||
Button(action: {
|
||||
@ -153,7 +154,7 @@ struct PlayerView: View {
|
||||
.padding(.bottom, 12)
|
||||
.layoutPriority(2)
|
||||
|
||||
Spacer(minLength: 12)
|
||||
Spacer(minLength: 0)
|
||||
}
|
||||
.safeAreaPadding(.top, 24)
|
||||
.safeAreaPadding(.bottom, 16)
|
||||
|
||||
@ -199,6 +199,7 @@ struct CreditSection: View {
|
||||
ForEach(0..<items.count, id: \.self) { index in
|
||||
Text(items[index])
|
||||
.font(.system(size: 13))
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
449
Valentine/Views/Settings/SettingsView.swift
Normal file
449
Valentine/Views/Settings/SettingsView.swift
Normal file
@ -0,0 +1,449 @@
|
||||
import SwiftUI
|
||||
|
||||
enum SettingsTab: String, CaseIterable, Identifiable {
|
||||
case general = "General"
|
||||
case lyrics = "Lyrics"
|
||||
|
||||
var id: String { self.rawValue }
|
||||
|
||||
var icon: String {
|
||||
switch self {
|
||||
case .general: return "gear"
|
||||
case .lyrics: return "textformat.alt"
|
||||
}
|
||||
}
|
||||
|
||||
var color: Color {
|
||||
switch self {
|
||||
case .general: return .gray
|
||||
case .lyrics: return .blue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SettingsView: View {
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@State private var selectedTab: SettingsTab? = .general
|
||||
@AppStorage("appTheme") private var appTheme = 0
|
||||
|
||||
var body: some View {
|
||||
NavigationSplitView {
|
||||
List(selection: $selectedTab) {
|
||||
ForEach(SettingsTab.allCases) { tab in
|
||||
NavigationLink(value: tab) {
|
||||
Label {
|
||||
Text(LocalizedStringKey(tab.rawValue))
|
||||
} icon: {
|
||||
Image(systemName: tab.icon)
|
||||
.foregroundColor(.white)
|
||||
.frame(width: 20, height: 20)
|
||||
.background(tab.color, in: RoundedRectangle(cornerRadius: 6))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Settings")
|
||||
.listStyle(.sidebar)
|
||||
} detail: {
|
||||
switch selectedTab {
|
||||
case .general:
|
||||
GeneralSettingsView()
|
||||
.navigationTitle(LocalizedStringKey(SettingsTab.general.rawValue))
|
||||
case .lyrics:
|
||||
LyricsAppearanceView()
|
||||
.navigationTitle(LocalizedStringKey(SettingsTab.lyrics.rawValue))
|
||||
case .none:
|
||||
Text("Select a setting")
|
||||
}
|
||||
}
|
||||
.frame(minWidth: 600, idealWidth: 700, minHeight: 400, idealHeight: 500)
|
||||
.background(WindowButtonsHider())
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigation) {
|
||||
Button(action: { dismiss() }) {
|
||||
Image(systemName: "xmark")
|
||||
}
|
||||
.help("Close Settings")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct GeneralSettingsView: View {
|
||||
@AppStorage("isGlowEffectEnabled") private var isGlowEffectEnabled = false
|
||||
@AppStorage("isNeonEffectEnabled") private var isNeonEffectEnabled = false
|
||||
@AppStorage("miniPlayerGlassMode") private var miniPlayerGlassMode = 0
|
||||
@AppStorage("appTheme") private var appTheme = 0
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section(header: Text("Appearance")) {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text("App Theme")
|
||||
ThemeSelectionView(selection: $appTheme)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text("Mini Player Appearance")
|
||||
.font(.body)
|
||||
Text("Choose an appearance for the mini player.")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
LiquidGlassSelectionView(selection: $miniPlayerGlassMode)
|
||||
.padding(.top, 4)
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
|
||||
Section(header: Text("Synced Lyrics Effects")) {
|
||||
Toggle("Glow Effect", isOn: $isGlowEffectEnabled)
|
||||
Toggle("Neon Effect", isOn: $isNeonEffectEnabled)
|
||||
}
|
||||
}
|
||||
.formStyle(.grouped)
|
||||
}
|
||||
}
|
||||
|
||||
struct LyricsAppearanceView: View {
|
||||
@ObservedObject var settings = LyricsAppearanceManager.shared
|
||||
|
||||
@State private var previewIsDark = true
|
||||
@State private var previewNeon = false
|
||||
@State private var previewGlow = false
|
||||
@State private var applyMode = 0 // 0: Both Themes, 1: Specific Theme
|
||||
|
||||
// Bindings for the currently selected theme to edit
|
||||
private var isEditingDark: Bool {
|
||||
if applyMode == 0 { return previewIsDark }
|
||||
return previewIsDark // Wait, if specific theme, it edits the preview's current theme.
|
||||
}
|
||||
|
||||
private var fontDesignBinding: Binding<Int> {
|
||||
Binding(
|
||||
get: { isEditingDark ? settings.fontDesignDark : settings.fontDesignLight },
|
||||
set: { newValue in
|
||||
if applyMode == 0 {
|
||||
settings.fontDesignDark = newValue
|
||||
settings.fontDesignLight = newValue
|
||||
} else {
|
||||
if isEditingDark { settings.fontDesignDark = newValue }
|
||||
else { settings.fontDesignLight = newValue }
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private func colorBinding(for stringBindingLight: Binding<String>, stringBindingDark: Binding<String>, defaultColor: Color) -> Binding<Color> {
|
||||
Binding<Color>(
|
||||
get: {
|
||||
let hex = isEditingDark ? stringBindingDark.wrappedValue : stringBindingLight.wrappedValue
|
||||
return hex.isEmpty ? defaultColor : Color(hex: hex)
|
||||
},
|
||||
set: { newValue in
|
||||
let hex = newValue.toHex()
|
||||
if applyMode == 0 {
|
||||
stringBindingDark.wrappedValue = hex
|
||||
stringBindingLight.wrappedValue = hex
|
||||
} else {
|
||||
if isEditingDark { stringBindingDark.wrappedValue = hex }
|
||||
else { stringBindingLight.wrappedValue = hex }
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
// PREVIEW SECTION
|
||||
VStack {
|
||||
HStack {
|
||||
Text("Preview")
|
||||
.font(.headline)
|
||||
.foregroundColor(.primary)
|
||||
Spacer()
|
||||
Toggle("Dark Mode", isOn: $previewIsDark)
|
||||
.toggleStyle(.switch)
|
||||
.controlSize(.mini)
|
||||
}
|
||||
.padding()
|
||||
|
||||
ZStack {
|
||||
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
||||
.fill(previewIsDark ? Color(white: 0.1) : Color(white: 0.95))
|
||||
|
||||
VStack(spacing: 16) {
|
||||
previewText("Lorem ipsum dolor sit amet", isActive: false)
|
||||
previewText("Consectetur adipiscing elit", isActive: true)
|
||||
previewText("Sed do eiusmod tempor", isActive: false)
|
||||
}
|
||||
}
|
||||
.frame(height: 180)
|
||||
.padding(.horizontal)
|
||||
.padding(.bottom)
|
||||
}
|
||||
.background(Color(NSColor.windowBackgroundColor))
|
||||
|
||||
Divider()
|
||||
|
||||
// SETTINGS SECTION
|
||||
ScrollView {
|
||||
VStack(spacing: 24) {
|
||||
Picker("Apply to:", selection: $applyMode) {
|
||||
Text("Both Themes").tag(0)
|
||||
Text(previewIsDark ? "Dark Theme Only" : "Light Theme Only").tag(1)
|
||||
}
|
||||
.pickerStyle(SegmentedPickerStyle())
|
||||
|
||||
HStack {
|
||||
Toggle("Neon Effect", isOn: $previewNeon)
|
||||
Spacer()
|
||||
Toggle("Glow Effect", isOn: $previewGlow)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text("Typography")
|
||||
.font(.subheadline)
|
||||
.fontWeight(.semibold)
|
||||
|
||||
Picker("Font Design", selection: fontDesignBinding) {
|
||||
Text("Rounded").tag(1)
|
||||
Text("Default").tag(0)
|
||||
Text("Monospaced").tag(2)
|
||||
Text("Serif").tag(3)
|
||||
}
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text("Colors")
|
||||
.font(.subheadline)
|
||||
.fontWeight(.semibold)
|
||||
|
||||
ColorPicker("Font Color", selection: colorBinding(for: $settings.fontColorLight, stringBindingDark: $settings.fontColorDark, defaultColor: .primary))
|
||||
ColorPicker("Neon Color", selection: colorBinding(for: $settings.neonColorLight, stringBindingDark: $settings.neonColorDark, defaultColor: .white))
|
||||
ColorPicker("Glow Color", selection: colorBinding(for: $settings.glowColorLight, stringBindingDark: $settings.glowColorDark, defaultColor: .accentColor))
|
||||
}
|
||||
|
||||
Button("Reset to Defaults") {
|
||||
settings.resetToDefaults()
|
||||
}
|
||||
.padding(.top, 10)
|
||||
}
|
||||
.padding(24)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func previewText(_ text: String, isActive: Bool) -> some View {
|
||||
Text(text)
|
||||
.font(.system(size: isActive ? 24 : 18, weight: isActive ? .bold : .medium, design: settings.getFontDesign(isDark: previewIsDark)))
|
||||
.foregroundColor((previewNeon && isActive) ? settings.getNeonColor(isDark: previewIsDark) : settings.getFontColor(isDark: previewIsDark, isActive: isActive))
|
||||
.shadow(color: (previewNeon && isActive) ? settings.getNeonColor(isDark: previewIsDark).opacity(0.8) : .clear, radius: 10, x: 0, y: 0)
|
||||
.shadow(color: (previewNeon && isActive) ? settings.getNeonColor(isDark: previewIsDark).opacity(0.4) : .clear, radius: 20, x: 0, y: 0)
|
||||
.shadow(color: (previewGlow && isActive) ? settings.getGlowColor(isDark: previewIsDark).opacity(0.8) : .clear, radius: 15, x: 0, y: 0)
|
||||
.shadow(color: (previewGlow && isActive) ? settings.getGlowColor(isDark: previewIsDark).opacity(0.5) : .clear, radius: 5, x: 0, y: 0)
|
||||
}
|
||||
}
|
||||
|
||||
enum ThemeStyle {
|
||||
case system, light, dark
|
||||
}
|
||||
|
||||
struct ThemeSelectionView: View {
|
||||
@Binding var selection: Int
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 20) {
|
||||
ThemeThumbnail(title: "Follow System", isSelected: selection == 0, style: .system) {
|
||||
selection = 0
|
||||
}
|
||||
ThemeThumbnail(title: "Light", isSelected: selection == 1, style: .light) {
|
||||
selection = 1
|
||||
}
|
||||
ThemeThumbnail(title: "Dark", isSelected: selection == 2, style: .dark) {
|
||||
selection = 2
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
}
|
||||
|
||||
struct ThemeThumbnail: View {
|
||||
let title: LocalizedStringKey
|
||||
let isSelected: Bool
|
||||
let style: ThemeStyle
|
||||
let action: () -> Void
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 8) {
|
||||
Button(action: action) {
|
||||
ZStack {
|
||||
// Background
|
||||
RoundedRectangle(cornerRadius: 8)
|
||||
.fill(Color.clear)
|
||||
.frame(width: 76, height: 52)
|
||||
|
||||
if style == .system {
|
||||
HStack(spacing: 0) {
|
||||
ZStack {
|
||||
LinearGradient(colors: [.cyan.opacity(0.6), .white], startPoint: .topLeading, endPoint: .bottomTrailing)
|
||||
WindowGraphic(isDark: false)
|
||||
}
|
||||
ZStack {
|
||||
LinearGradient(colors: [.blue.opacity(0.8), .black], startPoint: .topLeading, endPoint: .bottomTrailing)
|
||||
WindowGraphic(isDark: true)
|
||||
}
|
||||
}
|
||||
.frame(width: 72, height: 48)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 6))
|
||||
} else {
|
||||
let isDark = style == .dark
|
||||
ZStack {
|
||||
if isDark {
|
||||
LinearGradient(colors: [.blue.opacity(0.8), .black], startPoint: .topLeading, endPoint: .bottomTrailing)
|
||||
} else {
|
||||
LinearGradient(colors: [.cyan.opacity(0.6), .white], startPoint: .topLeading, endPoint: .bottomTrailing)
|
||||
}
|
||||
WindowGraphic(isDark: isDark)
|
||||
}
|
||||
.frame(width: 72, height: 48)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 6))
|
||||
}
|
||||
|
||||
if isSelected {
|
||||
RoundedRectangle(cornerRadius: 8)
|
||||
.stroke(Color.accentColor, lineWidth: 3)
|
||||
.frame(width: 76, height: 52)
|
||||
}
|
||||
}
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
|
||||
Text(title)
|
||||
.font(.system(size: 11, weight: isSelected ? .bold : .regular))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct WindowGraphic: View {
|
||||
let isDark: Bool
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
Spacer().frame(height: 12)
|
||||
ZStack(alignment: .topLeading) {
|
||||
RoundedRectangle(cornerRadius: 4)
|
||||
.fill(isDark ? Color(white: 0.1) : .white)
|
||||
.shadow(color: .black.opacity(0.2), radius: 2, y: 1)
|
||||
|
||||
// Content bar
|
||||
RoundedRectangle(cornerRadius: 2)
|
||||
.fill(Color.accentColor)
|
||||
.frame(width: 40, height: 8)
|
||||
.padding(.leading, 8)
|
||||
.padding(.top, 8)
|
||||
|
||||
// Traffic lights
|
||||
HStack(spacing: 2) {
|
||||
Circle().fill(Color.red).frame(width: 4, height: 4)
|
||||
Circle().fill(Color.yellow).frame(width: 4, height: 4)
|
||||
Circle().fill(Color.green).frame(width: 4, height: 4)
|
||||
}
|
||||
.padding(.leading, 8)
|
||||
.padding(.top, 24)
|
||||
}
|
||||
.frame(width: 56, height: 40)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct LiquidGlassSelectionView: View {
|
||||
@Binding var selection: Int
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 20) {
|
||||
LiquidGlassThumbnail(title: "Transparent", isSelected: selection == 1, isTinted: false) {
|
||||
selection = 1
|
||||
}
|
||||
LiquidGlassThumbnail(title: "Tinted", isSelected: selection == 0, isTinted: true) {
|
||||
selection = 0
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
}
|
||||
|
||||
struct LiquidGlassThumbnail: View {
|
||||
let title: LocalizedStringKey
|
||||
let isSelected: Bool
|
||||
let isTinted: Bool
|
||||
let action: () -> Void
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 8) {
|
||||
Button(action: action) {
|
||||
ZStack {
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.fill(Color.clear)
|
||||
.frame(width: 86, height: 60)
|
||||
|
||||
let isDark = colorScheme == .dark
|
||||
|
||||
ZStack {
|
||||
if isDark {
|
||||
LinearGradient(colors: [.blue.opacity(0.8), .black], startPoint: .topLeading, endPoint: .bottomTrailing)
|
||||
} else {
|
||||
LinearGradient(colors: [.cyan.opacity(0.6), .white], startPoint: .topLeading, endPoint: .bottomTrailing)
|
||||
}
|
||||
|
||||
if isTinted {
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.fill(isDark ? Color.accentColor.opacity(0.4) : Color(white: 1.0).opacity(0.7))
|
||||
.background(.regularMaterial)
|
||||
.shadow(color: .black.opacity(0.2), radius: 2, y: 1)
|
||||
.frame(width: 50, height: 26)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 12))
|
||||
} else {
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.fill(Color.clear)
|
||||
.background(.ultraThinMaterial)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.stroke(Color(white: 1.0).opacity(isDark ? 0.2 : 0.6), lineWidth: 0.5)
|
||||
)
|
||||
.shadow(color: .black.opacity(0.2), radius: 2, y: 1)
|
||||
.frame(width: 50, height: 26)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 12))
|
||||
}
|
||||
}
|
||||
.frame(width: 80, height: 54)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||||
|
||||
if isSelected {
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.stroke(Color.accentColor, lineWidth: 3)
|
||||
.frame(width: 86, height: 60)
|
||||
}
|
||||
}
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
|
||||
Text(title)
|
||||
.font(.system(size: 11, weight: isSelected ? .bold : .regular))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct WindowButtonsHider: NSViewRepresentable {
|
||||
func makeNSView(context: Context) -> NSView {
|
||||
let view = NSView()
|
||||
DispatchQueue.main.async {
|
||||
if let window = view.window {
|
||||
window.standardWindowButton(.closeButton)?.isHidden = true
|
||||
window.standardWindowButton(.miniaturizeButton)?.isHidden = true
|
||||
window.standardWindowButton(.zoomButton)?.isHidden = true
|
||||
}
|
||||
}
|
||||
return view
|
||||
}
|
||||
func updateNSView(_ nsView: NSView, context: Context) {}
|
||||
}
|
||||
@ -10,7 +10,7 @@
|
||||
"fill" : "automatic",
|
||||
"glass" : true,
|
||||
"hidden" : false,
|
||||
"image-name" : "IMG_0959.PNG",
|
||||
"image-name" : "IMG_0959.png",
|
||||
"name" : "IMG_0959",
|
||||
"opacity" : 1,
|
||||
"position" : {
|
||||
|
||||
@ -13,5 +13,5 @@
|
||||
<choice id="dev.jesuschapman.Valentine" visible="false">
|
||||
<pkg-ref id="dev.jesuschapman.Valentine"/>
|
||||
</choice>
|
||||
<pkg-ref id="dev.jesuschapman.Valentine" version="1.0 - 001" onConclusion="none">Valentine-component.pkg</pkg-ref>
|
||||
<pkg-ref id="dev.jesuschapman.Valentine" version="1.1 - 002" onConclusion="none">Valentine-component.pkg</pkg-ref>
|
||||
</installer-gui-script>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user