🎉 ShotScreen v1.0 - Initial Release

🚀 First official release of ShotScreen with complete feature set:

 Core Features:
- Advanced screenshot capture system
- Multi-monitor support
- Professional UI/UX design
- Automated update system with Sparkle
- Apple notarized & code signed

🛠 Technical Excellence:
- Native Swift macOS application
- Professional build & deployment pipeline
- Comprehensive error handling
- Memory optimized performance

📦 Distribution Ready:
- Professional DMG packaging
- Apple notarization complete
- No security warnings for users
- Ready for public distribution

This is the foundation release that establishes ShotScreen as a premium screenshot tool for macOS.
This commit is contained in:
2025-06-28 16:15:15 +02:00
commit 0dabed11d2
63 changed files with 25727 additions and 0 deletions

View File

@@ -0,0 +1,413 @@
import AppKit
import SwiftUI // Needed for CGSize, CaseIterable, Identifiable, Codable
// Define Notification Names
extension Notification.Name {
static let closeAfterDragSettingChanged = Notification.Name("closeAfterDragSettingChanged")
static let closeAfterSaveSettingChanged = Notification.Name("closeAfterSaveSettingChanged")
static let playSoundOnCaptureSettingChanged = Notification.Name("playSoundOnCaptureSettingChanged")
static let startAppOnLoginSettingChanged = Notification.Name("startAppOnLoginSettingChanged")
static let thumbnailSizeSettingChanged = Notification.Name("thumbnailSizeSettingChanged")
static let screenshotFolderChanged = Notification.Name("screenshotFolderChanged")
static let filenameSettingsChanged = Notification.Name("filenameSettingsChanged")
static let saveAfterEditSettingChanged = Notification.Name("saveAfterEditSettingChanged")
static let thumbnailTimerSettingChanged = Notification.Name("thumbnailTimerSettingChanged")
static let thumbnailPositionSettingChanged = Notification.Name("thumbnailPositionSettingChanged")
static let showFolderButtonSettingChanged = Notification.Name("showFolderButtonSettingChanged")
static let autoSaveScreenshotSettingChanged = Notification.Name("autoSaveScreenshotSettingChanged")
static let stashAlwaysOnTopSettingChanged = Notification.Name("stashAlwaysOnTopSettingChanged")
static let shortcutSettingChanged = Notification.Name("shortcutSettingChanged")
static let windowCaptureSettingChanged = Notification.Name("windowCaptureSettingChanged")
static let hideDesktopIconsSettingChanged = Notification.Name("hideDesktopIconsSettingChanged")
static let hideDesktopWidgetsSettingChanged = Notification.Name("hideDesktopWidgetsSettingChanged")
static let stashPreviewSizeChanged = Notification.Name("stashPreviewSizeChanged")
// 🔥💥 HYPERMODE GRID NOTIFICATIONS! 💥🔥
static let stashGridModeChanged = Notification.Name("stashGridModeChanged")
static let stashGridConfigChanged = Notification.Name("stashGridConfigChanged")
// 🔥 NIEUW: Persistent stash notification
static let persistentStashChanged = Notification.Name("persistentStashChanged")
// Add more here if needed for other live updates
}
// NEW: Enum for Screenshot Save Mode
enum ScreenshotSaveMode: Int, CaseIterable, Identifiable {
case automatic = 0
case manual = 1
var id: Int { self.rawValue }
var description: String {
switch self {
case .automatic: return "Always save screenshot to folder"
case .manual: return "Manually decide after screenshot"
}
}
}
// Settings keys
enum SettingsKey {
static let screenshotFolder = "screenshotFolder"
static let thumbnailTimer = "thumbnailTimer"
static let closeAfterDrag = "closeAfterDrag"
static let thumbnailPosition = "thumbnailPosition"
static let showFolderButton = "showFolderButton"
static let closeAfterSave = "closeAfterSave"
static let playSoundOnCapture = "playSoundOnCapture"
static let filenamePrefix = "filenamePrefix"
static let filenameFormatPreset = "filenameFormatPreset"
static let filenameCustomFormat = "filenameCustomFormat"
static let stashWindowBorderWidth = "stashWindowBorderWidth"
static let isRenameActionEnabled = "isRenameActionEnabled"
static let isStashActionEnabled = "isStashActionEnabled"
static let isOCRActionEnabled = "isOCRActionEnabled"
static let isClipboardActionEnabled = "isClipboardActionEnabled"
static let isBackgroundRemoveActionEnabled = "isBackgroundRemoveActionEnabled"
static let isCancelActionEnabled = "isCancelActionEnabled"
static let isRemoveActionEnabled = "isRemoveActionEnabled"
static let isAction3Enabled = "isAction3Enabled"
static let isAction4Enabled = "isAction4Enabled"
static let isDeleteActionEnabled = "isDeleteActionEnabled"
static let isCancelDragActionEnabled = "isCancelDragActionEnabled"
static let startAppOnLogin = "startAppOnLogin"
static let autoSaveScreenshot = "autoSaveScreenshot"
static let saveAfterEdit = "saveAfterEdit"
static let thumbnailDisplayScreen = "thumbnailDisplayScreen"
static let stashAlwaysOnTop = "stashAlwaysOnTop"
static let openOnStartup = "openOnStartup"
static let hasCompletedFirstLaunch = "hasCompletedFirstLaunch"
static let customShortcutModifiers = "customShortcutModifiers"
static let customShortcutKey = "customShortcutKey"
static let useCustomShortcut = "useCustomShortcut"
static let windowCaptureIncludeChildWindows = "windowCaptureIncludeChildWindows"
static let windowCaptureIncludeCursor = "windowCaptureIncludeCursor"
static let windowCaptureHighResolution = "windowCaptureHighResolution"
static let windowCaptureShowSelectionUI = "windowCaptureShowSelectionUI"
static let hideDesktopIconsDuringScreenshot = "hideDesktopIconsDuringScreenshot"
static let hideDesktopWidgetsDuringScreenshot = "hideDesktopWidgetsDuringScreenshot"
static let stashPreviewSize = "stashPreviewSize"
// 🔥💥 HYPERMODE STASH GRID KEYS! 💥🔥
static let stashGridMode = "stashGridMode"
static let stashMaxColumns = "stashMaxColumns"
static let stashMaxRows = "stashMaxRows"
// 🔥 NIEUW: Persistent stash setting
static let persistentStash = "persistentStash"
// 🔄 UPDATE SETTINGS
static let automaticUpdates = "automaticUpdates"
static let includePreReleases = "includePreReleases"
// 🔊 SOUND SETTINGS
static let screenshotSoundVolume = "screenshotSoundVolume"
static let screenshotSoundType = "screenshotSoundType"
// 🗂 CACHE MANAGEMENT SETTINGS
static let cacheRetentionTime = "cacheRetentionTime"
// 🎨 BACKGROUND REMOVAL METHOD PREFERENCE
static let preferredBackgroundRemovalMethod = "preferredBackgroundRemovalMethod"
}
enum ThumbnailPosition: Int, CaseIterable, Codable {
case rightBottom = 0
var description: String {
switch self {
case .rightBottom: return "Bottom Right"
}
}
}
enum ThumbnailDisplayScreen: String, CaseIterable, Codable {
case automatic = "automatic"
case screen1 = "screen1"
case screen2 = "screen2"
case screen3 = "screen3"
case screen4 = "screen4"
case screen5 = "screen5"
var description: String {
switch self {
case .automatic: return "Automatic (where mouse is)"
case .screen1: return "Screen 1"
case .screen2: return "Screen 2"
case .screen3: return "Screen 3"
case .screen4: return "Screen 4"
case .screen5: return "Screen 5"
}
}
func getDisplayName(for screenIndex: Int, screenName: String?) -> String {
switch self {
case .automatic: return "Automatic (where mouse is)"
case .screen1, .screen2, .screen3, .screen4, .screen5:
let screenNumber = screenIndex + 1
if let name = screenName, !name.isEmpty {
return "Screen \(screenNumber) (\(name))"
} else {
return "Screen \(screenNumber)"
}
}
}
}
// NEW: Enum for filename format presets
enum FilenameFormatPreset: Int, CaseIterable, Codable {
case macOSStyle = 0 // Screenshot {YYYY}-{MM}-{DD} at {hh}.{mm}.{ss}
case compactDateTime = 1 // {YYYY}-{MM}-{DD}_{hh}-{mm}-{ss}
case superCompactDateTime = 2 // {YYYYMMDD}_{hhmmss}
case timestamp = 3 // Unix timestamp
case prefixOnly = 4 // Prefix only
case custom = 5 // Custom format
var description: String {
switch self {
case .macOSStyle: return "macOS Style (Screenshot YYYY-MM-DD at hh.mm.ss)"
case .compactDateTime: return "Compact (YYYY-MM-DD_hh-mm-ss)"
case .superCompactDateTime: return "Super Compact (YYYYMMDD_hhmmss)"
case .timestamp: return "Unix Timestamp"
case .prefixOnly: return "Prefix Only"
case .custom: return "Custom..."
}
}
// Placeholder voor custom format string (alleen relevant voor .custom)
// De feitelijke custom string wordt apart opgeslagen.
}
// VOEG DEZE ENUM TOE (ergens bovenaan, of logisch gegroepeerd)
enum ThumbnailFixedSize: String, CaseIterable, Codable {
case small, medium, large, xLarge, xxLarge, xxxLarge
var dimensions: CGSize {
switch self {
case .small: return CGSize(width: 120, height: 90)
case .medium: return CGSize(width: 180, height: 135)
case .large: return CGSize(width: 240, height: 180)
case .xLarge: return CGSize(width: 300, height: 225)
case .xxLarge: return CGSize(width: 360, height: 270)
case .xxxLarge: return CGSize(width: 420, height: 315)
}
}
var displayName: String { // Voor de Picker
switch self {
case .small: return "Small"
case .medium: return "Medium"
case .large: return "Large"
case .xLarge: return "X-Large"
case .xxLarge: return "XX-Large"
case .xxxLarge: return "XXX-Large"
}
}
}
// 🔥💎 MEGA NIEUWE STASH PREVIEW SIZE ENUM! 💎🔥
enum StashPreviewSize: String, CaseIterable, Codable {
case xSmall = "xSmall" // 25% van origineel
case small = "small" // 40% van origineel
case medium = "medium" // 60% van origineel
case large = "large" // 80% van origineel
case xLarge = "xLarge" // 100% van origineel (full size!)
var percentage: CGFloat {
switch self {
case .xSmall: return 0.25
case .small: return 0.40
case .medium: return 0.60
case .large: return 0.80
case .xLarge: return 1.00
}
}
var displayName: String {
switch self {
case .xSmall: return "Extra Small"
case .small: return "Small"
case .medium: return "Medium"
case .large: return "Large"
case .xLarge: return "Extra Large"
}
}
}
// 🔥💥 HYPERMODE STASH GRID MODE ENUM! 💥🔥
enum StashGridMode: String, CaseIterable, Codable, Identifiable {
case fixedColumns = "fixedColumns" // Max kolommen, auto rijen (huidige systeem)
case fixedRows = "fixedRows" // Max rijen, auto kolommen (horizontale strip!)
var id: String { rawValue }
var displayName: String {
switch self {
case .fixedColumns: return "Fixed Columns, Auto Rows"
case .fixedRows: return "Fixed Rows, Auto Columns"
}
}
var description: String {
switch self {
case .fixedColumns: return "Grows vertically with more rows"
case .fixedRows: return "Grows horizontally with more columns"
}
}
var iconName: String {
switch self {
case .fixedColumns: return "rectangle.split.1x3" // Verticale layout (1 kolom, 3 rijen = verticaal groeien)
case .fixedRows: return "rectangle.split.3x1" // Horizontale layout (3 kolommen, 1 rij = horizontaal groeien)
}
}
}
// 🔊 SCREENSHOT SOUND TYPE ENUM
enum ScreenshotSoundType: String, CaseIterable, Codable, Identifiable {
case pop = "Pop"
case glass = "Glass"
case ping = "Ping"
case purr = "Purr"
case tink = "Tink"
case basso = "Basso"
case blow = "Blow"
case bottle = "Bottle"
case frog = "Frog"
case funk = "Funk"
case hero = "Hero"
case morse = "Morse"
case sosumi = "Sosumi"
case submarine = "Submarine"
var id: String { rawValue }
var displayName: String {
switch self {
case .pop: return "Pop"
case .glass: return "Glass"
case .ping: return "Ping"
case .purr: return "Purr"
case .tink: return "Tink"
case .basso: return "Basso"
case .blow: return "Blow"
case .bottle: return "Bottle"
case .frog: return "Frog"
case .funk: return "Funk"
case .hero: return "Hero"
case .morse: return "Morse"
case .sosumi: return "Sosumi"
case .submarine: return "Submarine"
}
}
var systemSoundName: String {
return rawValue
}
}
// 🗂 CACHE RETENTION TIME ENUM
enum CacheRetentionTime: String, CaseIterable, Codable, Identifiable {
case oneHour = "1h"
case sixHours = "6h"
case twelveHours = "12h"
case oneDay = "1d"
case threeDays = "3d"
case oneWeek = "1w"
case twoWeeks = "2w"
case oneMonth = "1m"
case forever = "forever"
var id: String { rawValue }
var displayName: String {
switch self {
case .oneHour: return "1 Hour"
case .sixHours: return "6 Hours"
case .twelveHours: return "12 Hours"
case .oneDay: return "1 Day"
case .threeDays: return "3 Days"
case .oneWeek: return "1 Week"
case .twoWeeks: return "2 Weeks"
case .oneMonth: return "1 Month"
case .forever: return "Forever (Manual delete)"
}
}
var timeInterval: TimeInterval? {
switch self {
case .oneHour: return 3600
case .sixHours: return 3600 * 6
case .twelveHours: return 3600 * 12
case .oneDay: return 86400
case .threeDays: return 86400 * 3
case .oneWeek: return 86400 * 7
case .twoWeeks: return 86400 * 14
case .oneMonth: return 86400 * 30
case .forever: return nil // Never delete
}
}
}
// Add after the existing SettingsKey enum
enum ActionType: String, CaseIterable, Identifiable {
case rename = "Rename"
case stash = "Stash"
case ocr = "OCR"
case clipboard = "Clipboard"
case backgroundRemove = "BackgroundRemove"
case cancel = "Cancel"
case remove = "Remove"
var id: String { self.rawValue }
var settingsKey: String {
switch self {
case .rename: return SettingsKey.isRenameActionEnabled
case .stash: return SettingsKey.isStashActionEnabled
case .ocr: return SettingsKey.isOCRActionEnabled
case .clipboard: return SettingsKey.isClipboardActionEnabled
case .backgroundRemove: return SettingsKey.isBackgroundRemoveActionEnabled
case .cancel: return SettingsKey.isCancelActionEnabled
case .remove: return SettingsKey.isRemoveActionEnabled
}
}
var displayName: String {
switch self {
case .rename: return "Rename"
case .stash: return "Stash"
case .ocr: return "Text Extract"
case .clipboard: return "Clipboard"
case .backgroundRemove: return "Remove Background"
case .cancel: return "Cancel"
case .remove: return "Remove"
}
}
}
// MARK: - Background Removal Method Preference
enum BackgroundRemovalMethod: String, CaseIterable, Identifiable {
case auto = "auto"
case rmbg = "rmbg"
case vision = "vision"
var id: String { self.rawValue }
var displayName: String {
switch self {
case .auto: return "Auto (RMBG → Vision fallback)"
case .rmbg: return "RMBG-1.4 only"
case .vision: return "Vision Framework only"
}
}
var description: String {
switch self {
case .auto: return "Tries RMBG-1.4 first, falls back to Vision if unavailable"
case .rmbg: return "Uses only RMBG-1.4 model (requires download)"
case .vision: return "Uses only Apple's Vision Framework (always available)"
}
}
}