├── Resources ├── KillNotchDrop.command ├── Privacy.md ├── 截屏2024-07-08 03.14.34.png ├── i18n │ └── zh-Hans │ │ ├── README.md │ │ └── Download_on_the_Mac_App_Store_Badge_CNSC_RGB_blk_092917.svg └── Download_on_the_App_Store_Badge_US-UK_RGB_blk_092917.svg ├── NotchDrop ├── Assets.xcassets │ ├── Contents.json │ ├── AppIcon.appiconset │ │ ├── icon-128.png │ │ ├── icon-16.png │ │ ├── icon-256.png │ │ ├── icon-32.png │ │ ├── icon-512.png │ │ ├── icon-128@2x.png │ │ ├── icon-16@2x.png │ │ ├── icon-256@2x.png │ │ ├── icon-32@2x.png │ │ ├── icon-512@2x.png │ │ └── Contents.json │ ├── GitHub.imageset │ │ ├── Contents.json │ │ └── github-mark-white.svg │ └── AccentColor.colorset │ │ └── Contents.json ├── Info.plist ├── NotchViewController.swift ├── Ext+NSImage.swift ├── NotchDrop.entitlements ├── Ext+URL.swift ├── NotchHeaderView.swift ├── EventMonitor.swift ├── Ext+NSScreen.swift ├── NotchWindow.swift ├── Ext+NSAlert.swift ├── NotchContentView.swift ├── Share.swift ├── EventMonitors.swift ├── Ext+FileProvider.swift ├── TrayDrop+DropItemView.swift ├── NotchWindowController.swift ├── main.swift ├── NotchSettingsView.swift ├── TrayDrop+DropItem.swift ├── AppDelegate.swift ├── InfoPlist.xcstrings ├── NotchViewModel.swift ├── NotchMenuView.swift ├── PublishedPersist.swift ├── TrayDrop+View.swift ├── Language.swift ├── Share+View.swift ├── NotchViewModel+Events.swift ├── TrayDrop.swift ├── NotchView.swift └── Localizable.xcstrings ├── NotchDrop.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved ├── xcshareddata │ └── xcschemes │ │ └── NotchDrop.xcscheme └── project.pbxproj ├── LICENSE ├── README.md ├── AGENTS.md └── .gitignore /Resources/KillNotchDrop.command: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | pkill NotchDrop 4 | 5 | -------------------------------------------------------------------------------- /Resources/Privacy.md: -------------------------------------------------------------------------------- 1 | # Privacy Policy 2 | 3 | We do not collect any data from this app. 4 | -------------------------------------------------------------------------------- /Resources/截屏2024-07-08 03.14.34.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lakr233/NotchDrop/HEAD/Resources/截屏2024-07-08 03.14.34.png -------------------------------------------------------------------------------- /NotchDrop/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /NotchDrop/Assets.xcassets/AppIcon.appiconset/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lakr233/NotchDrop/HEAD/NotchDrop/Assets.xcassets/AppIcon.appiconset/icon-128.png -------------------------------------------------------------------------------- /NotchDrop/Assets.xcassets/AppIcon.appiconset/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lakr233/NotchDrop/HEAD/NotchDrop/Assets.xcassets/AppIcon.appiconset/icon-16.png -------------------------------------------------------------------------------- /NotchDrop/Assets.xcassets/AppIcon.appiconset/icon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lakr233/NotchDrop/HEAD/NotchDrop/Assets.xcassets/AppIcon.appiconset/icon-256.png -------------------------------------------------------------------------------- /NotchDrop/Assets.xcassets/AppIcon.appiconset/icon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lakr233/NotchDrop/HEAD/NotchDrop/Assets.xcassets/AppIcon.appiconset/icon-32.png -------------------------------------------------------------------------------- /NotchDrop/Assets.xcassets/AppIcon.appiconset/icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lakr233/NotchDrop/HEAD/NotchDrop/Assets.xcassets/AppIcon.appiconset/icon-512.png -------------------------------------------------------------------------------- /NotchDrop/Assets.xcassets/AppIcon.appiconset/icon-128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lakr233/NotchDrop/HEAD/NotchDrop/Assets.xcassets/AppIcon.appiconset/icon-128@2x.png -------------------------------------------------------------------------------- /NotchDrop/Assets.xcassets/AppIcon.appiconset/icon-16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lakr233/NotchDrop/HEAD/NotchDrop/Assets.xcassets/AppIcon.appiconset/icon-16@2x.png -------------------------------------------------------------------------------- /NotchDrop/Assets.xcassets/AppIcon.appiconset/icon-256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lakr233/NotchDrop/HEAD/NotchDrop/Assets.xcassets/AppIcon.appiconset/icon-256@2x.png -------------------------------------------------------------------------------- /NotchDrop/Assets.xcassets/AppIcon.appiconset/icon-32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lakr233/NotchDrop/HEAD/NotchDrop/Assets.xcassets/AppIcon.appiconset/icon-32@2x.png -------------------------------------------------------------------------------- /NotchDrop/Assets.xcassets/AppIcon.appiconset/icon-512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lakr233/NotchDrop/HEAD/NotchDrop/Assets.xcassets/AppIcon.appiconset/icon-512@2x.png -------------------------------------------------------------------------------- /NotchDrop.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /NotchDrop/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ITSAppUsesNonExemptEncryption 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /NotchDrop.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /NotchDrop/Assets.xcassets/GitHub.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "github-mark-white.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /NotchDrop/NotchViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotchViewController.swift 3 | // NotchDrop 4 | // 5 | // Created by 秋星桥 on 2024/7/7. 6 | // 7 | 8 | import AppKit 9 | import Cocoa 10 | import SwiftUI 11 | 12 | class NotchViewController: NSHostingController { 13 | init(_ vm: NotchViewModel) { 14 | super.init(rootView: .init(vm: vm)) 15 | } 16 | 17 | @available(*, unavailable) 18 | required init?(coder _: NSCoder) { 19 | fatalError() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /NotchDrop/Ext+NSImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Ext+NSImage.swift 3 | // NotchDrop 4 | // 5 | // Created by 秋星桥 on 2024/7/8. 6 | // 7 | 8 | import Cocoa 9 | 10 | extension NSImage { 11 | var pngRepresentation: Data { 12 | guard let cgImage = cgImage(forProposedRect: nil, context: nil, hints: nil) else { 13 | return .init() 14 | } 15 | let imageRep = NSBitmapImageRep(cgImage: cgImage) 16 | imageRep.size = size 17 | return imageRep.representation(using: .png, properties: [:]) ?? .init() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /NotchDrop/NotchDrop.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.hardened-process 6 | 7 | com.apple.security.hardened-process.dyld-ro 8 | 9 | com.apple.security.hardened-process.enhanced-security-version 10 | 1 11 | com.apple.security.hardened-process.platform-restrictions 12 | 2 13 | 14 | 15 | -------------------------------------------------------------------------------- /NotchDrop/Ext+URL.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Ext+URL.swift 3 | // NotchDrop 4 | // 5 | // Created by 秋星桥 on 2024/7/8. 6 | // 7 | 8 | import Cocoa 9 | import Foundation 10 | import QuickLook 11 | 12 | extension URL { 13 | func snapshotPreview() -> NSImage { 14 | if let preview = QLThumbnailImageCreate( 15 | kCFAllocatorDefault, 16 | self as CFURL, 17 | CGSize(width: 128, height: 128), 18 | nil 19 | )?.takeRetainedValue() { 20 | return NSImage(cgImage: preview, size: .zero) 21 | } 22 | return NSWorkspace.shared.icon(forFile: path) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Resources/i18n/zh-Hans/README.md: -------------------------------------------------------------------------------- 1 | # 挪翅丢 (NotchDrop) 2 | 3 | 将你的 MacBook 刘海变成一个方便的文件传输区域。 4 | 5 | ![App Store Icon](./Download_on_the_Mac_App_Store_Badge_CNSC_RGB_blk_092917.svg) 6 | 7 | ## 👀 预览 8 | 9 | ![屏幕截图](../../截屏2024-07-08%2003.14.34.png) 10 | 11 | ## 🌟 主要功能 12 | 13 | - [x] 应该可以和你喜爱的菜单栏管理器同时使用 14 | - [x] 将文件拖放到挪翅丢便可暂存一天,也可以配置时长 15 | - [x] 将文件拖放到挪翅丢左侧的 AirDrop 便可直接发送 16 | - [x] 点击即可打开文件 17 | - [x] 按住 Option 键并点击 X 按钮删除文件 18 | - [x] 完全开源并注重隐私 19 | - [x] 如果你自己动手编译,那就完全免费 20 | 21 | ## 🚀 使用方法 22 | 23 | 请从 [Releases](https://github.com/Lakr233/NotchDrop/releases) 页面下载最新版本。 24 | 25 | ## 🧑‍⚖️ 许可证 26 | 27 | [MIT License](./LICENSE) 28 | 29 | ## 🥰 致谢 30 | 31 | 特别感谢 [NotchNook](https://lo.cafe/notchnook) 提供初步的灵感。这个开源项目更专注于我自己的需求,简化了各种配置,并改进了和我喜爱的软件的兼容性问题。 32 | 33 | --- 34 | 35 | 版权所有 © 2024 砍砍@标准件厂长。保留所有权利。 36 | -------------------------------------------------------------------------------- /NotchDrop/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.575", 9 | "green" : "0.329", 10 | "red" : "0.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "1.000", 27 | "green" : "0.838", 28 | "red" : "0.462" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /NotchDrop/NotchHeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotchHeaderView.swift 3 | // NotchDrop 4 | // 5 | // Created by 秋星桥 on 2024/7/7. 6 | // 7 | 8 | import ColorfulX 9 | import SwiftUI 10 | 11 | struct NotchHeaderView: View { 12 | @StateObject var vm: NotchViewModel 13 | 14 | var body: some View { 15 | HStack { 16 | Text( 17 | vm.contentType == .settings 18 | ? "Version: \(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown") (Build: \(Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "Unknown"))" 19 | : "Notch Drop" 20 | ) 21 | .contentTransition(.numericText()) 22 | Spacer() 23 | Image(systemName: "ellipsis") 24 | } 25 | .animation(vm.animation, value: vm.contentType) 26 | .font(.system(.headline, design: .rounded)) 27 | } 28 | } 29 | 30 | #Preview { 31 | NotchHeaderView(vm: .init()) 32 | } 33 | -------------------------------------------------------------------------------- /NotchDrop/Assets.xcassets/GitHub.imageset/github-mark-white.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /NotchDrop/EventMonitor.swift: -------------------------------------------------------------------------------- 1 | import AppKit 2 | 3 | public class EventMonitor { 4 | private var globalMonitor: AnyObject? 5 | private var localMonitor: AnyObject? 6 | private let mask: NSEvent.EventTypeMask 7 | private let handler: (NSEvent?) -> Void 8 | 9 | public init(mask: NSEvent.EventTypeMask, handler: @escaping (NSEvent?) -> Void) { 10 | self.mask = mask 11 | self.handler = handler 12 | } 13 | 14 | deinit { 15 | stop() 16 | } 17 | 18 | public func start() { 19 | globalMonitor = NSEvent.addGlobalMonitorForEvents(matching: mask, handler: handler) as AnyObject? 20 | localMonitor = NSEvent.addLocalMonitorForEvents(matching: mask) { [weak self] event in 21 | self?.handler(event) 22 | return event 23 | } as AnyObject? 24 | } 25 | 26 | public func stop() { 27 | if let globalMonitor { NSEvent.removeMonitor(globalMonitor) } 28 | globalMonitor = nil 29 | if let localMonitor { NSEvent.removeMonitor(localMonitor) } 30 | localMonitor = nil 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Lakr Aream 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NotchDrop/Ext+NSScreen.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Ext+NSScreen.swift 3 | // NotchDrop 4 | // 5 | // Created by 秋星桥 on 2024/7/7. 6 | // 7 | 8 | import Cocoa 9 | 10 | extension NSScreen { 11 | var notchSize: CGSize { 12 | guard safeAreaInsets.top > 0 else { return .zero } 13 | let notchHeight = safeAreaInsets.top 14 | let fullWidth = frame.width 15 | let leftPadding = auxiliaryTopLeftArea?.width ?? 0 16 | let rightPadding = auxiliaryTopRightArea?.width ?? 0 17 | guard leftPadding > 0, rightPadding > 0 else { return .zero } 18 | let notchWidth = fullWidth - leftPadding - rightPadding 19 | return CGSize(width: notchWidth, height: notchHeight) 20 | } 21 | 22 | var isBuildinDisplay: Bool { 23 | let screenNumberKey = NSDeviceDescriptionKey(rawValue: "NSScreenNumber") 24 | guard let id = deviceDescription[screenNumberKey], 25 | let rid = (id as? NSNumber)?.uint32Value, 26 | CGDisplayIsBuiltin(rid) == 1 27 | else { return false } 28 | return true 29 | } 30 | 31 | static var buildin: NSScreen? { 32 | screens.first { $0.isBuildinDisplay } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /NotchDrop/NotchWindow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotchWindow.swift 3 | // NotchDrop 4 | // 5 | // Created by 秋星桥 on 2024/7/7. 6 | // 7 | 8 | import Cocoa 9 | 10 | class NotchWindow: NSWindow { 11 | override init( 12 | contentRect: NSRect, 13 | styleMask: NSWindow.StyleMask, 14 | backing: NSWindow.BackingStoreType, 15 | defer flag: Bool 16 | ) { 17 | super.init( 18 | contentRect: contentRect, 19 | styleMask: styleMask, 20 | backing: backing, 21 | defer: flag 22 | ) 23 | 24 | isOpaque = false 25 | alphaValue = 1 26 | titleVisibility = .hidden 27 | titlebarAppearsTransparent = true 28 | backgroundColor = NSColor.clear 29 | isMovable = false 30 | collectionBehavior = [ 31 | .fullScreenAuxiliary, 32 | .stationary, 33 | .canJoinAllSpaces, 34 | .ignoresCycle, 35 | ] 36 | level = .statusBar + 8 // kills ibar lol 37 | hasShadow = false 38 | } 39 | 40 | override var canBecomeKey: Bool { 41 | true 42 | } 43 | 44 | override var canBecomeMain: Bool { 45 | true 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /NotchDrop/Ext+NSAlert.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Ext+NSAlert.swift 3 | // NotchDrop 4 | // 5 | // Created by 秋星桥 on 2024/7/9. 6 | // 7 | 8 | import Cocoa 9 | 10 | extension NSAlert { 11 | static func popError(_ error: String) { 12 | let alert = NSAlert() 13 | alert.messageText = NSLocalizedString("Error", comment: "") 14 | alert.alertStyle = .critical 15 | alert.informativeText = error 16 | alert.addButton(withTitle: NSLocalizedString("OK", comment: "")) 17 | alert.runModal() 18 | } 19 | 20 | static func popRestart(_ error: String, completion: @escaping () -> Void) { 21 | let alert = NSAlert() 22 | alert.messageText = NSLocalizedString("Need Restart", comment: "") 23 | alert.alertStyle = .critical 24 | alert.informativeText = error 25 | alert.addButton(withTitle: NSLocalizedString("Exit", comment: "")) 26 | alert.addButton(withTitle: NSLocalizedString("Later", comment: "")) 27 | let response = alert.runModal() 28 | if response == .alertFirstButtonReturn { 29 | completion() 30 | } 31 | } 32 | 33 | static func popError(_ error: Error) { 34 | popError(error.localizedDescription) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /NotchDrop/NotchContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotchContentView.swift 3 | // NotchDrop 4 | // 5 | // Created by 秋星桥 on 2024/7/7. 6 | // Last Modified by 冷月 on 2025/5/5. 7 | // 8 | 9 | import ColorfulX 10 | import SwiftUI 11 | import UniformTypeIdentifiers 12 | 13 | struct NotchContentView: View { 14 | @StateObject var vm: NotchViewModel 15 | 16 | var body: some View { 17 | ZStack { 18 | switch vm.contentType { 19 | case .normal: 20 | HStack(spacing: vm.spacing) { 21 | ShareView(vm: vm, type: .airdrop) 22 | TrayView(vm: vm) 23 | } 24 | .transition(.scale(scale: 0.8).combined(with: .opacity)) 25 | case .menu: 26 | NotchMenuView(vm: vm) 27 | .transition(.scale(scale: 0.8).combined(with: .opacity)) 28 | case .settings: 29 | NotchSettingsView(vm: vm) 30 | .transition(.scale(scale: 0.8).combined(with: .opacity)) 31 | } 32 | } 33 | .animation(vm.animation, value: vm.contentType) 34 | } 35 | } 36 | 37 | #Preview { 38 | NotchContentView(vm: .init()) 39 | .padding() 40 | .frame(width: 600, height: 150, alignment: .center) 41 | .background(.black) 42 | .preferredColorScheme(.dark) 43 | } 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NotchDrop 2 | 3 | Transform your MacBook's notch into a convenient file drop zone. 4 | 5 | [简体中文 🇨🇳](./Resources/i18n/zh-Hans/README.md) 6 | 7 | [![App Store Icon](./Resources/Download_on_the_App_Store_Badge_US-UK_RGB_blk_092917.svg)](https://apps.apple.com/app/notchdrop/id6529528324) 8 | 9 | For Developers: You can use [NotchNotification](https://github.com/Lakr233/NotchNotification) in your app, powered by NotchDrop. 10 | 11 | ## 👀 Preview 12 | 13 | ![Screenshot](./Resources/截屏2024-07-08%2003.14.34.png) 14 | 15 | ## 🌟 Key Features 16 | 17 | - [x] Should work with your menu bar managers 18 | - [x] Drag and drop files to the notch 19 | - [x] Open AirDrop directly from the notch 20 | - [x] Automatically save files for 1 day, can be configured 21 | - [x] Open files with a simple click 22 | - [x] Delete files by holding the option key and clicking the x mark 23 | - [x] Fully open source and privacy-focused 24 | - [x] Free of charge if you do it yourself 25 | 26 | ## 🚀 Usage 27 | 28 | Download the latest version from [Releases](https://github.com/Lakr233/NotchDrop/releases). 29 | 30 | ## 🧑‍⚖️ License 31 | 32 | [MIT License](./LICENSE) 33 | 34 | ## 🥰 Acknowledgements 35 | 36 | Special thanks to [NotchNook](https://lo.cafe/notchnook) for providing the initial inspiration. This open-source project focuses more on my own needs, simplifies various configurations, and improves compatibility with the software I prefer. 37 | 38 | --- 39 | 40 | Copyright © 2024 Lakr Aream. All Rights Reserved. 41 | -------------------------------------------------------------------------------- /NotchDrop/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icon-16.png", 5 | "idiom" : "mac", 6 | "scale" : "1x", 7 | "size" : "16x16" 8 | }, 9 | { 10 | "filename" : "icon-16@2x.png", 11 | "idiom" : "mac", 12 | "scale" : "2x", 13 | "size" : "16x16" 14 | }, 15 | { 16 | "filename" : "icon-32.png", 17 | "idiom" : "mac", 18 | "scale" : "1x", 19 | "size" : "32x32" 20 | }, 21 | { 22 | "filename" : "icon-32@2x.png", 23 | "idiom" : "mac", 24 | "scale" : "2x", 25 | "size" : "32x32" 26 | }, 27 | { 28 | "filename" : "icon-128.png", 29 | "idiom" : "mac", 30 | "scale" : "1x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "filename" : "icon-128@2x.png", 35 | "idiom" : "mac", 36 | "scale" : "2x", 37 | "size" : "128x128" 38 | }, 39 | { 40 | "filename" : "icon-256.png", 41 | "idiom" : "mac", 42 | "scale" : "1x", 43 | "size" : "256x256" 44 | }, 45 | { 46 | "filename" : "icon-256@2x.png", 47 | "idiom" : "mac", 48 | "scale" : "2x", 49 | "size" : "256x256" 50 | }, 51 | { 52 | "filename" : "icon-512.png", 53 | "idiom" : "mac", 54 | "scale" : "1x", 55 | "size" : "512x512" 56 | }, 57 | { 58 | "filename" : "icon-512@2x.png", 59 | "idiom" : "mac", 60 | "scale" : "2x", 61 | "size" : "512x512" 62 | } 63 | ], 64 | "info" : { 65 | "author" : "xcode", 66 | "version" : 1 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /AGENTS.md: -------------------------------------------------------------------------------- 1 | # Swift Code Style Guidelines 2 | 3 | ## Core Style 4 | - **Indentation**: 4 spaces 5 | - **Braces**: Opening brace on same line 6 | - **Spacing**: Single space around operators and commas 7 | - **Naming**: PascalCase for types, camelCase for properties/methods 8 | 9 | ## File Organization 10 | - Logical directory grouping 11 | - PascalCase files for types, `+` for extensions 12 | - Modular design with extensions 13 | 14 | ## Modern Swift Features 15 | - **@Observable macro**: Replace `ObservableObject`/`@Published` 16 | - **Swift concurrency**: `async/await`, `Task`, `actor`, `@MainActor` 17 | - **Result builders**: Declarative APIs 18 | - **Property wrappers**: Use line breaks for long declarations 19 | - **Opaque types**: `some` for protocol returns 20 | 21 | ## Code Structure 22 | - Early returns to reduce nesting 23 | - Guard statements for optional unwrapping 24 | - Single responsibility per type/extension 25 | - Value types over reference types 26 | 27 | ## Error Handling 28 | - `Result` enum for typed errors 29 | - `throws`/`try` for propagation 30 | - Optional chaining with `guard let`/`if let` 31 | - Typed error definitions 32 | 33 | ## Architecture 34 | - Avoid using protocol-oriented design unless necessary 35 | - Dependency injection over singletons 36 | - Composition over inheritance 37 | - Factory/Repository patterns 38 | 39 | ## Debug Assertions 40 | - Use `assert()` for development-time invariant checking 41 | - Use `assertionFailure()` for unreachable code paths 42 | - Assertions removed in release builds for performance 43 | - Precondition checking with `precondition()` for fatal errors 44 | 45 | ## Memory Management 46 | - `weak` references for cycles 47 | - `unowned` when guaranteed non-nil 48 | - Capture lists in closures 49 | - `deinit` for cleanup 50 | -------------------------------------------------------------------------------- /NotchDrop/Share.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Share.swift 3 | // NotchDrop 4 | // 5 | // Created by 秋星桥 on 2024/7/8. 6 | // Last Modified by 冷月 on 2025/5/5. 7 | // 8 | 9 | import Cocoa 10 | 11 | class Share: NSObject, NSSharingServiceDelegate { 12 | let files: [URL] 13 | let serviceName: NSSharingService.Name? 14 | 15 | init(files: [URL], serviceName: NSSharingService.Name? = nil) { 16 | self.files = files 17 | self.serviceName = serviceName 18 | super.init() 19 | } 20 | 21 | func begin() { 22 | do { 23 | try sendEx(files) 24 | } catch { 25 | NSAlert.popError(error) 26 | } 27 | } 28 | 29 | private func sendEx(_ files: [URL]) throws { 30 | if let serviceName { 31 | guard let service = NSSharingService(named: serviceName) else { 32 | throw NSError(domain: "ShareService", code: 1, userInfo: [ 33 | NSLocalizedDescriptionKey: NSLocalizedString("Selected sharing service not available", comment: ""), 34 | ]) 35 | } 36 | 37 | guard service.canPerform(withItems: files) else { 38 | throw NSError(domain: "ShareService", code: 2, userInfo: [ 39 | NSLocalizedDescriptionKey: NSLocalizedString("Sharing service cannot perform with given files", comment: ""), 40 | ]) 41 | } 42 | 43 | service.delegate = self 44 | service.perform(withItems: files) 45 | } else { 46 | // 弹出分享面板 47 | let picker = NSSharingServicePicker(items: files) 48 | if let view = NSApp.keyWindow?.contentView { 49 | picker.show(relativeTo: view.bounds, of: view, preferredEdge: .minY) 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /NotchDrop/EventMonitors.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EventMonitors.swift 3 | // NotchDrop 4 | // 5 | // Created by 秋星桥 on 2024/7/7. 6 | // 7 | 8 | import Cocoa 9 | import Combine 10 | 11 | class EventMonitors { 12 | static let shared = EventMonitors() 13 | 14 | private var mouseMoveEvent: EventMonitor! 15 | private var mouseDownEvent: EventMonitor! 16 | private var mouseDraggingFileEvent: EventMonitor! 17 | private var optionKeyPressEvent: EventMonitor! 18 | 19 | let mouseLocation: CurrentValueSubject = .init(.zero) 20 | let mouseDown: PassthroughSubject = .init() 21 | let mouseDraggingFile: PassthroughSubject = .init() 22 | let optionKeyPress: CurrentValueSubject = .init(false) 23 | 24 | private init() { 25 | mouseMoveEvent = EventMonitor(mask: .mouseMoved) { [weak self] _ in 26 | guard let self else { return } 27 | let mouseLocation = NSEvent.mouseLocation 28 | self.mouseLocation.send(mouseLocation) 29 | } 30 | mouseMoveEvent.start() 31 | 32 | mouseDownEvent = EventMonitor(mask: .leftMouseDown) { [weak self] _ in 33 | guard let self else { return } 34 | mouseDown.send() 35 | } 36 | mouseDownEvent.start() 37 | 38 | mouseDraggingFileEvent = EventMonitor(mask: .leftMouseDragged) { [weak self] _ in 39 | guard let self else { return } 40 | mouseDraggingFile.send() 41 | } 42 | mouseDraggingFileEvent.start() 43 | 44 | optionKeyPressEvent = EventMonitor(mask: .flagsChanged) { [weak self] event in 45 | guard let self else { return } 46 | if event?.modifierFlags.contains(.option) == true { 47 | optionKeyPress.send(true) 48 | } else { 49 | optionKeyPress.send(false) 50 | } 51 | } 52 | optionKeyPressEvent.start() 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /NotchDrop/Ext+FileProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Ext+FileProvider.swift 3 | // NotchDrop 4 | // 5 | // Created by 秋星桥 on 2024/7/8. 6 | // 7 | 8 | import Cocoa 9 | import Foundation 10 | import UniformTypeIdentifiers 11 | 12 | extension NSItemProvider { 13 | private func duplicateToOurStorage(_ url: URL?) throws -> URL { 14 | guard let url else { throw NSError() } 15 | let temp = temporaryDirectory 16 | .appendingPathComponent("TemporaryDrop") 17 | .appendingPathComponent(UUID().uuidString) 18 | .appendingPathComponent(url.lastPathComponent) 19 | try? FileManager.default.createDirectory( 20 | at: temp.deletingLastPathComponent(), 21 | withIntermediateDirectories: true 22 | ) 23 | try FileManager.default.copyItem(at: url, to: temp) 24 | return temp 25 | } 26 | 27 | func convertToFilePathThatIsWhatWeThinkItWillWorkWithNotchDrop() -> URL? { 28 | var url: URL? 29 | let sem = DispatchSemaphore(value: 0) 30 | _ = loadObject(ofClass: URL.self) { item, _ in 31 | url = try? self.duplicateToOurStorage(item) 32 | sem.signal() 33 | } 34 | sem.wait() 35 | if url == nil { 36 | loadInPlaceFileRepresentation( 37 | forTypeIdentifier: UTType.data.identifier 38 | ) { input, _, _ in 39 | defer { sem.signal() } 40 | url = try? self.duplicateToOurStorage(input) 41 | } 42 | sem.wait() 43 | } 44 | return url 45 | } 46 | } 47 | 48 | extension [NSItemProvider] { 49 | func interfaceConvert() -> [URL]? { 50 | let urls = compactMap { provider -> URL? in 51 | provider.convertToFilePathThatIsWhatWeThinkItWillWorkWithNotchDrop() 52 | } 53 | guard urls.count == count else { 54 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { 55 | NSAlert.popError(NSLocalizedString("One or more files failed to load", comment: "")) 56 | } 57 | return nil 58 | } 59 | return urls 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /NotchDrop/TrayDrop+DropItemView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrayDrop+DropItemView.swift 3 | // NotchDrop 4 | // 5 | // Created by 秋星桥 on 2024/7/8. 6 | // 7 | 8 | import Foundation 9 | import Pow 10 | import SwiftUI 11 | import UniformTypeIdentifiers 12 | 13 | struct DropItemView: View { 14 | let item: TrayDrop.DropItem 15 | @StateObject var vm: NotchViewModel 16 | @StateObject var tvm = TrayDrop.shared 17 | 18 | @State var hover = false 19 | 20 | var body: some View { 21 | VStack { 22 | Image(nsImage: item.workspacePreviewImage) 23 | .resizable() 24 | .aspectRatio(contentMode: .fit) 25 | .frame(maxWidth: 64) 26 | Text(item.fileName) 27 | .multilineTextAlignment(.center) 28 | .font(.system(.footnote, design: .rounded)) 29 | .frame(maxWidth: 64) 30 | } 31 | .contentShape(Rectangle()) 32 | .transition(.asymmetric( 33 | insertion: .opacity.combined(with: .scale), 34 | removal: .movingParts.poof 35 | )) 36 | .contentShape(Rectangle()) 37 | .onHover { hover = $0 } 38 | .scaleEffect(hover ? 1.05 : 1.0) 39 | .animation(vm.animation, value: hover) 40 | .draggable(item) 41 | .onTapGesture { 42 | guard !vm.optionKeyPressed else { return } 43 | vm.notchClose() 44 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { 45 | NSWorkspace.shared.open(item.storageURL) 46 | } 47 | } 48 | .overlay { 49 | Image(systemName: "xmark.circle.fill") 50 | .resizable() 51 | .aspectRatio(contentMode: .fit) 52 | .foregroundStyle(.red) 53 | .background(Color.white.clipShape(Circle()).padding(1)) 54 | .frame(width: vm.spacing, height: vm.spacing) 55 | .opacity(vm.optionKeyPressed ? 1 : 0) 56 | .scaleEffect(vm.optionKeyPressed ? 1 : 0.5) 57 | .animation(vm.animation, value: vm.optionKeyPressed) 58 | .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing) 59 | .offset(x: vm.spacing / 2, y: -vm.spacing / 2) 60 | .onTapGesture { tvm.delete(item.id) } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /NotchDrop.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "f0d4037b316e691fefb0ff41167e80ee122ba1766331e69b36ecf83a7408b0fc", 3 | "pins" : [ 4 | { 5 | "identity" : "colorfulx", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/Lakr233/ColorfulX", 8 | "state" : { 9 | "revision" : "4b48da803e2e02bdd29c031802d995bf28eea486", 10 | "version" : "5.7.0" 11 | } 12 | }, 13 | { 14 | "identity" : "colorvector", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/Lakr233/ColorVector.git", 17 | "state" : { 18 | "revision" : "6da8726bf38d68eb943d0f2139ac2a1fac70e65b", 19 | "version" : "1.0.4" 20 | } 21 | }, 22 | { 23 | "identity" : "launchatlogin-modern", 24 | "kind" : "remoteSourceControl", 25 | "location" : "https://github.com/sindresorhus/LaunchAtLogin-Modern", 26 | "state" : { 27 | "revision" : "a04ec1c363be3627734f6dad757d82f5d4fa8fcc", 28 | "version" : "1.1.0" 29 | } 30 | }, 31 | { 32 | "identity" : "msdisplaylink", 33 | "kind" : "remoteSourceControl", 34 | "location" : "https://github.com/Lakr233/MSDisplayLink.git", 35 | "state" : { 36 | "revision" : "ebf5823cb5fc1326639d9a05bc06d16bbe82989f", 37 | "version" : "2.0.8" 38 | } 39 | }, 40 | { 41 | "identity" : "pow", 42 | "kind" : "remoteSourceControl", 43 | "location" : "https://github.com/EmergeTools/Pow", 44 | "state" : { 45 | "revision" : "a504eb6d144bcf49f4f33029a2795345cb39e6b4", 46 | "version" : "1.0.5" 47 | } 48 | }, 49 | { 50 | "identity" : "springinterpolation", 51 | "kind" : "remoteSourceControl", 52 | "location" : "https://github.com/Lakr233/SpringInterpolation.git", 53 | "state" : { 54 | "revision" : "f9ae95ece5d6b7cdceafd4381f1d5f0f9494e5d2", 55 | "version" : "1.3.1" 56 | } 57 | }, 58 | { 59 | "identity" : "swift-collections", 60 | "kind" : "remoteSourceControl", 61 | "location" : "https://github.com/apple/swift-collections.git", 62 | "state" : { 63 | "revision" : "7b847a3b7008b2dc2f47ca3110d8c782fb2e5c7e", 64 | "version" : "1.3.0" 65 | } 66 | } 67 | ], 68 | "version" : 3 69 | } 70 | -------------------------------------------------------------------------------- /NotchDrop/NotchWindowController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotchWindowController.swift 3 | // NotchDrop 4 | // 5 | // Created by 秋星桥 on 2024/7/7. 6 | // 7 | 8 | import Cocoa 9 | 10 | private let notchHeight: CGFloat = 200 11 | 12 | class NotchWindowController: NSWindowController { 13 | var vm: NotchViewModel? 14 | weak var screen: NSScreen? 15 | 16 | var openAfterCreate: Bool = false 17 | 18 | init(window: NSWindow, screen: NSScreen) { 19 | self.screen = screen 20 | 21 | super.init(window: window) 22 | 23 | var notchSize = screen.notchSize 24 | 25 | let vm = NotchViewModel(inset: notchSize == .zero ? 0 : -4) 26 | self.vm = vm 27 | contentViewController = NotchViewController(vm) 28 | 29 | if notchSize == .zero { 30 | notchSize = .init(width: 150, height: 28) 31 | } 32 | vm.deviceNotchRect = CGRect( 33 | x: screen.frame.origin.x + (screen.frame.width - notchSize.width) / 2, 34 | y: screen.frame.origin.y + screen.frame.height - notchSize.height, 35 | width: notchSize.width, 36 | height: notchSize.height 37 | ) 38 | window.makeKeyAndOrderFront(nil) 39 | 40 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak vm] in 41 | vm?.screenRect = screen.frame 42 | if self.openAfterCreate { vm?.notchOpen(.boot) } 43 | } 44 | } 45 | 46 | @available(*, unavailable) 47 | required init?(coder _: NSCoder) { fatalError() } 48 | 49 | convenience init(screen: NSScreen) { 50 | let window = NotchWindow( 51 | contentRect: screen.frame, 52 | styleMask: [.borderless, .fullSizeContentView], 53 | backing: .buffered, 54 | defer: false, 55 | screen: screen 56 | ) 57 | self.init(window: window, screen: screen) 58 | 59 | let topRect = CGRect( 60 | x: screen.frame.origin.x, 61 | y: screen.frame.origin.y + screen.frame.height - notchHeight, 62 | width: screen.frame.width, 63 | height: notchHeight 64 | ) 65 | window.setFrameOrigin(topRect.origin) 66 | window.setContentSize(topRect.size) 67 | } 68 | 69 | deinit { 70 | destroy() 71 | } 72 | 73 | func destroy() { 74 | vm?.destroy() 75 | vm = nil 76 | window?.close() 77 | contentViewController = nil 78 | window = nil 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | .DS_Store 92 | -------------------------------------------------------------------------------- /NotchDrop/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // NotchDrop 4 | // 5 | // Created by 秋星桥 on 2024/7/7. 6 | // 7 | 8 | import AppKit 9 | 10 | let productPage = URL(string: "https://github.com/Lakr233/NotchDrop")! 11 | let sponsorPage = URL(string: "https://github.com/sponsors/Lakr233")! 12 | 13 | let bundleIdentifier = Bundle.main.bundleIdentifier! 14 | let appVersion = "\(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "") (\(Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? ""))" 15 | 16 | private let availableDirectories = FileManager 17 | .default 18 | .urls(for: .documentDirectory, in: .userDomainMask) 19 | let documentsDirectory = availableDirectories[0] 20 | .appendingPathComponent("NotchDrop") 21 | let temporaryDirectory = URL(fileURLWithPath: NSTemporaryDirectory()) 22 | .appendingPathComponent(bundleIdentifier) 23 | try? FileManager.default.removeItem(at: temporaryDirectory) 24 | try? FileManager.default.createDirectory( 25 | at: documentsDirectory, 26 | withIntermediateDirectories: true, 27 | attributes: nil 28 | ) 29 | try? FileManager.default.createDirectory( 30 | at: temporaryDirectory, 31 | withIntermediateDirectories: true, 32 | attributes: nil 33 | ) 34 | 35 | let pidFile = documentsDirectory.appendingPathComponent("ProcessIdentifier") 36 | 37 | do { 38 | let prevIdentifier = try String(contentsOf: pidFile, encoding: .utf8) 39 | if let prev = Int(prevIdentifier) { 40 | if let app = NSRunningApplication(processIdentifier: pid_t(prev)) { 41 | app.terminate() 42 | } 43 | } 44 | } catch {} 45 | try? FileManager.default.removeItem(at: pidFile) 46 | 47 | do { 48 | let pid = String(NSRunningApplication.current.processIdentifier) 49 | try pid.write(to: pidFile, atomically: true, encoding: .utf8) 50 | } catch { 51 | NSAlert.popError(error) 52 | exit(1) 53 | } 54 | 55 | _ = TrayDrop.shared 56 | TrayDrop.shared.cleanExpiredFiles() 57 | 58 | repeat { 59 | let executablePath = ProcessInfo.processInfo.arguments.first! 60 | let selfHandle = open(executablePath, O_EVTONLY) 61 | guard selfHandle > 0 else { break } 62 | 63 | let monitorSource = DispatchSource.makeFileSystemObjectSource( 64 | fileDescriptor: selfHandle, 65 | eventMask: .delete 66 | ) 67 | monitorSource.setEventHandler { 68 | guard monitorSource.data == .delete else { return } 69 | monitorSource.cancel() 70 | exit(0) 71 | } 72 | monitorSource.resume() 73 | } while false 74 | 75 | private let delegate = AppDelegate() 76 | NSApplication.shared.delegate = delegate 77 | _ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv) 78 | -------------------------------------------------------------------------------- /NotchDrop/NotchSettingsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotchSettingsView.swift 3 | // NotchDrop 4 | // 5 | // Created by 曹丁杰 on 2024/7/29. 6 | // 7 | 8 | import LaunchAtLogin 9 | import SwiftUI 10 | 11 | struct NotchSettingsView: View { 12 | @StateObject var vm: NotchViewModel 13 | @StateObject var tvm: TrayDrop = .shared 14 | 15 | var body: some View { 16 | VStack(spacing: vm.spacing) { 17 | HStack { 18 | Picker("Language: ", selection: $vm.selectedLanguage) { 19 | ForEach(Language.allCases) { language in 20 | Text(language.localized).tag(language) 21 | } 22 | } 23 | .pickerStyle(MenuPickerStyle()) 24 | .frame(width: vm.selectedLanguage == .simplifiedChinese || vm.selectedLanguage == .traditionalChinese ? 220 : 160) 25 | 26 | Spacer() 27 | LaunchAtLogin.Toggle { 28 | Text(NSLocalizedString("Launch at Login", comment: "")) 29 | } 30 | 31 | Spacer() 32 | Toggle("Haptic Feedback ", isOn: $vm.hapticFeedback) 33 | 34 | Spacer() 35 | } 36 | 37 | HStack { 38 | Text("File Storage Time: ") 39 | Picker(String(), selection: $tvm.selectedFileStorageTime) { 40 | ForEach(TrayDrop.FileStorageTime.allCases) { time in 41 | Text(time.localized).tag(time) 42 | } 43 | } 44 | .pickerStyle(MenuPickerStyle()) 45 | .frame(width: 100) 46 | if tvm.selectedFileStorageTime == .custom { 47 | TextField("Days", value: $tvm.customStorageTime, formatter: NumberFormatter()) 48 | .textFieldStyle(RoundedBorderTextFieldStyle()) 49 | .frame(width: 50) 50 | .padding(.leading, 10) 51 | Picker("Time Unit", selection: $tvm.customStorageTimeUnit) { 52 | ForEach(TrayDrop.CustomstorageTimeUnit.allCases) { unit in 53 | Text(unit.localized).tag(unit) 54 | } 55 | } 56 | .pickerStyle(MenuPickerStyle()) 57 | .frame(width: 200) 58 | } 59 | Spacer() 60 | } 61 | } 62 | .padding() 63 | .transition(.scale(scale: 0.8).combined(with: .opacity)) 64 | } 65 | } 66 | 67 | #Preview { 68 | NotchSettingsView(vm: .init()) 69 | .padding() 70 | .frame(width: 600, height: 150, alignment: .center) 71 | .background(.black) 72 | .preferredColorScheme(.dark) 73 | } 74 | -------------------------------------------------------------------------------- /NotchDrop/TrayDrop+DropItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrayDrop+DropItem.swift 3 | // TrayDrop 4 | // 5 | // Created by 秋星桥 on 2024/7/8. 6 | // 7 | 8 | import Cocoa 9 | import CoreTransferable 10 | import Foundation 11 | import QuickLook 12 | import UniformTypeIdentifiers 13 | 14 | extension TrayDrop { 15 | struct DropItem: Identifiable, Codable, Equatable, Hashable { 16 | let id: UUID 17 | 18 | let fileName: String 19 | let size: Int 20 | 21 | let copiedDate: Date 22 | let workspacePreviewImageData: Data 23 | 24 | init(url: URL) throws { 25 | assert(!Thread.isMainThread) 26 | 27 | id = UUID() 28 | fileName = url.lastPathComponent 29 | 30 | size = (try? url.resourceValues(forKeys: [.fileSizeKey]))?.fileSize ?? 0 31 | copiedDate = Date() 32 | workspacePreviewImageData = url.snapshotPreview().pngRepresentation 33 | 34 | try FileManager.default.createDirectory( 35 | at: storageURL.deletingLastPathComponent(), 36 | withIntermediateDirectories: true 37 | ) 38 | try FileManager.default.copyItem(at: url, to: storageURL) 39 | } 40 | } 41 | } 42 | 43 | extension TrayDrop.DropItem: Transferable { 44 | static var transferRepresentation: some TransferRepresentation { 45 | let exportingBehavior: @Sendable (TrayDrop.DropItem) async throws -> SentTransferredFile = { input in 46 | let tempDir = temporaryDirectory.appendingPathComponent(UUID().uuidString) 47 | try? FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true) 48 | let newPath = tempDir.appendingPathComponent(input.fileName) 49 | try FileManager.default.copyItem( 50 | at: input.storageURL, 51 | to: newPath 52 | ) 53 | return .init(newPath, allowAccessingOriginalFile: true) 54 | } 55 | let importingBehavior: @Sendable (ReceivedTransferredFile) async throws -> TrayDrop.DropItem = { _ in 56 | fatalError() 57 | } 58 | return FileRepresentation( 59 | contentType: .data, 60 | shouldAttemptToOpenInPlace: true, 61 | exporting: exportingBehavior, 62 | importing: importingBehavior 63 | ) 64 | } 65 | } 66 | 67 | extension TrayDrop.DropItem { 68 | static let mainDir = "CopiedItems" 69 | 70 | var storageURL: URL { 71 | documentsDirectory 72 | .appendingPathComponent(Self.mainDir) 73 | .appendingPathComponent(id.uuidString) 74 | .appendingPathComponent(fileName) 75 | } 76 | 77 | var workspacePreviewImage: NSImage { 78 | .init(data: workspacePreviewImageData) ?? .init() 79 | } 80 | 81 | var shouldClean: Bool { 82 | if !FileManager.default.fileExists(atPath: storageURL.path) { return true } 83 | let keepInterval = TrayDrop.shared.keepInterval 84 | guard keepInterval > 0 else { return true } // avoid non-reasonable value deleting user's files 85 | if Date().timeIntervalSince(copiedDate) > TrayDrop.shared.keepInterval { return true } 86 | return false 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /NotchDrop/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // NotchDrop 4 | // 5 | // Created by 秋星桥 on 2024/7/7. 6 | // 7 | 8 | import AppKit 9 | import Cocoa 10 | import LaunchAtLogin 11 | 12 | class AppDelegate: NSObject, NSApplicationDelegate { 13 | var isFirstOpen = true 14 | var isLaunchedAtLogin = false 15 | var mainWindowController: NotchWindowController? 16 | 17 | var timer: Timer? 18 | 19 | func applicationDidFinishLaunching(_: Notification) { 20 | NotificationCenter.default.addObserver( 21 | self, 22 | selector: #selector(rebuildApplicationWindows), 23 | name: NSApplication.didChangeScreenParametersNotification, 24 | object: nil 25 | ) 26 | NSApp.setActivationPolicy(.accessory) 27 | 28 | isLaunchedAtLogin = LaunchAtLogin.wasLaunchedAtLogin 29 | 30 | _ = EventMonitors.shared 31 | let timer = Timer.scheduledTimer( 32 | withTimeInterval: 1, 33 | repeats: true 34 | ) { [weak self] _ in 35 | self?.determineIfProcessIdentifierMatches() 36 | self?.makeKeyAndVisibleIfNeeded() 37 | } 38 | self.timer = timer 39 | 40 | rebuildApplicationWindows() 41 | } 42 | 43 | func applicationWillTerminate(_: Notification) { 44 | try? FileManager.default.removeItem(at: temporaryDirectory) 45 | try? FileManager.default.removeItem(at: pidFile) 46 | } 47 | 48 | func findScreenFitsOurNeeds() -> NSScreen? { 49 | if let screen = NSScreen.buildin, screen.notchSize != .zero { return screen } 50 | return .main 51 | } 52 | 53 | @objc func rebuildApplicationWindows() { 54 | defer { isFirstOpen = false } 55 | if let mainWindowController { 56 | mainWindowController.destroy() 57 | } 58 | mainWindowController = nil 59 | guard let mainScreen = findScreenFitsOurNeeds() else { return } 60 | mainWindowController = .init(screen: mainScreen) 61 | if isFirstOpen, !isLaunchedAtLogin { 62 | mainWindowController?.openAfterCreate = true 63 | } 64 | } 65 | 66 | func determineIfProcessIdentifierMatches() { 67 | let pid = String(NSRunningApplication.current.processIdentifier) 68 | let content = (try? String(contentsOf: pidFile)) ?? "" 69 | guard pid.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() 70 | == content.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() 71 | else { 72 | NSApp.terminate(nil) 73 | return 74 | } 75 | } 76 | 77 | func makeKeyAndVisibleIfNeeded() { 78 | guard let controller = mainWindowController, 79 | let window = controller.window, 80 | let vm = controller.vm, 81 | vm.status == .opened 82 | else { return } 83 | window.makeKeyAndOrderFront(nil) 84 | } 85 | 86 | func applicationShouldHandleReopen(_: NSApplication, hasVisibleWindows _: Bool) -> Bool { 87 | guard let controller = mainWindowController, 88 | let vm = controller.vm 89 | else { return true } 90 | vm.notchOpen(.click) 91 | return true 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /NotchDrop.xcodeproj/xcshareddata/xcschemes/NotchDrop.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 9 | 10 | 16 | 22 | 23 | 24 | 25 | 26 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /NotchDrop/InfoPlist.xcstrings: -------------------------------------------------------------------------------- 1 | { 2 | "sourceLanguage" : "en", 3 | "strings" : { 4 | "CFBundleDisplayName" : { 5 | "comment" : "Bundle display name", 6 | "localizations" : { 7 | "en" : { 8 | "stringUnit" : { 9 | "state" : "translated", 10 | "value" : "NotchDrop" 11 | } 12 | }, 13 | "fr" : { 14 | "stringUnit" : { 15 | "state" : "translated", 16 | "value" : "NotchDrop" 17 | } 18 | }, 19 | "ja" : { 20 | "stringUnit" : { 21 | "state" : "translated", 22 | "value" : "Notch Drop" 23 | } 24 | }, 25 | "zh-Hans" : { 26 | "stringUnit" : { 27 | "state" : "translated", 28 | "value" : "简放岛" 29 | } 30 | }, 31 | "zh-Hant" : { 32 | "stringUnit" : { 33 | "state" : "translated", 34 | "value" : "簡放島" 35 | } 36 | } 37 | } 38 | }, 39 | "CFBundleName" : { 40 | "comment" : "Bundle name", 41 | "extractionState" : "extracted_with_value", 42 | "localizations" : { 43 | "en" : { 44 | "stringUnit" : { 45 | "state" : "new", 46 | "value" : "NotchDrop" 47 | } 48 | }, 49 | "fr" : { 50 | "stringUnit" : { 51 | "state" : "translated", 52 | "value" : "NotchDrop" 53 | } 54 | }, 55 | "ja" : { 56 | "stringUnit" : { 57 | "state" : "translated", 58 | "value" : "Notch Drop" 59 | } 60 | }, 61 | "zh-Hans" : { 62 | "stringUnit" : { 63 | "state" : "translated", 64 | "value" : "简放岛" 65 | } 66 | }, 67 | "zh-Hant" : { 68 | "stringUnit" : { 69 | "state" : "translated", 70 | "value" : "簡放島" 71 | } 72 | } 73 | } 74 | }, 75 | "NSHumanReadableCopyright" : { 76 | "comment" : "Copyright (human-readable)", 77 | "extractionState" : "extracted_with_value", 78 | "localizations" : { 79 | "en" : { 80 | "stringUnit" : { 81 | "state" : "new", 82 | "value" : "Copyright © 2024 Lakr Aream. All rights reserved." 83 | } 84 | }, 85 | "fr" : { 86 | "stringUnit" : { 87 | "state" : "translated", 88 | "value" : "Copyright © 2024 Lakr Aream. All rights reserved." 89 | } 90 | }, 91 | "ja" : { 92 | "stringUnit" : { 93 | "state" : "translated", 94 | "value" : "Copyright © 2024 Lakr Aream. All rights reserved." 95 | } 96 | }, 97 | "zh-Hans" : { 98 | "stringUnit" : { 99 | "state" : "translated", 100 | "value" : "© 2024 砍砍@标准件厂长 版权所有" 101 | } 102 | }, 103 | "zh-Hant" : { 104 | "stringUnit" : { 105 | "state" : "translated", 106 | "value" : "© 2024 砍砍@標準件廠長 版權所有" 107 | } 108 | } 109 | } 110 | } 111 | }, 112 | "version" : "1.0" 113 | } -------------------------------------------------------------------------------- /NotchDrop/NotchViewModel.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import Combine 3 | import Foundation 4 | import LaunchAtLogin 5 | import SwiftUI 6 | 7 | class NotchViewModel: NSObject, ObservableObject { 8 | var cancellables: Set = [] 9 | let inset: CGFloat 10 | 11 | init(inset: CGFloat = -4) { 12 | self.inset = inset 13 | super.init() 14 | setupCancellables() 15 | } 16 | 17 | deinit { 18 | destroy() 19 | } 20 | 21 | let animation: Animation = .interactiveSpring( 22 | duration: 0.5, 23 | extraBounce: 0.25, 24 | blendDuration: 0.125 25 | ) 26 | let notchOpenedSize: CGSize = .init(width: 600, height: 160) 27 | let dropDetectorRange: CGFloat = 32 28 | 29 | enum Status: String, Codable, Hashable, Equatable { 30 | case closed 31 | case opened 32 | case popping 33 | } 34 | 35 | enum OpenReason: String, Codable, Hashable, Equatable { 36 | case click 37 | case drag 38 | case boot 39 | case unknown 40 | } 41 | 42 | enum ContentType: Int, Codable, Hashable, Equatable { 43 | case normal 44 | case menu 45 | case settings 46 | } 47 | 48 | var notchOpenedRect: CGRect { 49 | .init( 50 | x: screenRect.origin.x + (screenRect.width - notchOpenedSize.width) / 2, 51 | y: screenRect.origin.y + screenRect.height - notchOpenedSize.height, 52 | width: notchOpenedSize.width, 53 | height: notchOpenedSize.height 54 | ) 55 | } 56 | 57 | var headlineOpenedRect: CGRect { 58 | .init( 59 | x: screenRect.origin.x + (screenRect.width - notchOpenedSize.width) / 2, 60 | y: screenRect.origin.y + screenRect.height - deviceNotchRect.height, 61 | width: notchOpenedSize.width, 62 | height: deviceNotchRect.height 63 | ) 64 | } 65 | 66 | @Published private(set) var status: Status = .closed 67 | @Published var openReason: OpenReason = .unknown 68 | @Published var contentType: ContentType = .normal 69 | 70 | @Published var spacing: CGFloat = 16 71 | @Published var cornerRadius: CGFloat = 16 72 | @Published var deviceNotchRect: CGRect = .zero 73 | @Published var screenRect: CGRect = .zero 74 | @Published var optionKeyPressed: Bool = false 75 | @Published var notchVisible: Bool = true 76 | 77 | @PublishedPersist(key: "selectedLanguage", defaultValue: .system) 78 | var selectedLanguage: Language 79 | 80 | @PublishedPersist(key: "hapticFeedback", defaultValue: true) 81 | var hapticFeedback: Bool 82 | 83 | let hapticSender = PassthroughSubject() 84 | 85 | func notchOpen(_ reason: OpenReason) { 86 | openReason = reason 87 | status = .opened 88 | contentType = .normal 89 | NSApp.activate(ignoringOtherApps: true) 90 | } 91 | 92 | func notchClose() { 93 | openReason = .unknown 94 | status = .closed 95 | contentType = .normal 96 | } 97 | 98 | func showSettings() { 99 | contentType = .settings 100 | } 101 | 102 | func notchPop() { 103 | openReason = .unknown 104 | status = .popping 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /NotchDrop/NotchMenuView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotchMenuView.swift 3 | // NotchDrop 4 | // 5 | // Created by 秋星桥 on 2024/7/11. 6 | // 7 | 8 | import ColorfulX 9 | import SwiftUI 10 | 11 | struct NotchMenuView: View { 12 | @StateObject var vm: NotchViewModel 13 | @StateObject var tvm = TrayDrop.shared 14 | 15 | var body: some View { 16 | HStack(spacing: vm.spacing) { 17 | close 18 | settings 19 | clear 20 | } 21 | } 22 | 23 | var close: some View { 24 | ColorButton( 25 | color: [.red], 26 | image: Image(systemName: "xmark"), 27 | title: "Exit" 28 | ) 29 | .onTapGesture { 30 | vm.notchClose() 31 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { 32 | NSApp.terminate(nil) 33 | } 34 | } 35 | .clipShape(RoundedRectangle(cornerRadius: vm.cornerRadius)) 36 | } 37 | 38 | var clear: some View { 39 | ColorButton( 40 | color: [.red], 41 | image: Image(systemName: "trash"), 42 | title: "Clear" 43 | ) 44 | .onTapGesture { 45 | tvm.removeAll() 46 | vm.notchClose() 47 | } 48 | .clipShape(RoundedRectangle(cornerRadius: vm.cornerRadius)) 49 | } 50 | 51 | var settings: some View { 52 | ColorButton( 53 | color: ColorfulPreset.colorful.colors.map { .init($0) }, 54 | image: Image(systemName: "gear"), 55 | title: LocalizedStringKey("Settings") 56 | ) 57 | .onTapGesture { 58 | vm.showSettings() 59 | } 60 | .clipShape(RoundedRectangle(cornerRadius: vm.cornerRadius)) 61 | } 62 | } 63 | 64 | private struct ColorButton: View { 65 | let color: [Color] 66 | let image: Image 67 | let title: LocalizedStringKey 68 | 69 | @State var hover: Bool = false 70 | 71 | var body: some View { 72 | Color.white 73 | .opacity(0.1) 74 | .overlay( 75 | ColorfulView( 76 | color: .constant(color), 77 | speed: .constant(0) 78 | ) 79 | .mask { 80 | VStack(spacing: 8) { 81 | Text("888888") 82 | .hidden() 83 | .overlay { 84 | image 85 | .resizable() 86 | .aspectRatio(contentMode: .fit) 87 | } 88 | Text(title) 89 | } 90 | .font(.system(.headline, design: .rounded)) 91 | } 92 | .contentShape(Rectangle()) 93 | .scaleEffect(hover ? 1.05 : 1) 94 | .animation(.spring, value: hover) 95 | .onHover { hover = $0 } 96 | ) 97 | .aspectRatio(1, contentMode: .fit) 98 | .contentShape(Rectangle()) 99 | } 100 | } 101 | 102 | #Preview { 103 | NotchMenuView(vm: .init()) 104 | .padding() 105 | .frame(width: 600, height: 150, alignment: .center) 106 | .background(.black) 107 | .preferredColorScheme(.dark) 108 | } 109 | -------------------------------------------------------------------------------- /NotchDrop/PublishedPersist.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PublishedPersist.swift 3 | // NotchDrop 4 | // 5 | // Created by 秋星桥 on 2024/7/8. 6 | // 7 | 8 | import Combine 9 | import Foundation 10 | 11 | protocol PersistProvider { 12 | func data(forKey: String) -> Data? 13 | func set(_ data: Data?, forKey: String) 14 | } 15 | 16 | private let valueEncoder = JSONEncoder() 17 | private let valueDecoder = JSONDecoder() 18 | private let configDir = documentsDirectory 19 | .appendingPathComponent("Config") 20 | 21 | class FileStorage: PersistProvider { 22 | func pathForKey(_ key: String) -> URL { 23 | try? FileManager.default.createDirectory(at: configDir, withIntermediateDirectories: true) 24 | return configDir.appendingPathComponent(key) 25 | } 26 | 27 | func data(forKey key: String) -> Data? { 28 | try? Data(contentsOf: pathForKey(key)) 29 | } 30 | 31 | func set(_ data: Data?, forKey key: String) { 32 | try? data?.write(to: pathForKey(key)) 33 | } 34 | } 35 | 36 | @propertyWrapper 37 | struct Persist { 38 | private let subject: CurrentValueSubject 39 | private let cancellables: Set 40 | 41 | var projectedValue: AnyPublisher { 42 | subject.eraseToAnyPublisher() 43 | } 44 | 45 | init(key: String, defaultValue: Value, engine: PersistProvider) { 46 | if let data = engine.data(forKey: key), 47 | let object = try? valueDecoder.decode(Value.self, from: data) 48 | { 49 | subject = CurrentValueSubject(object) 50 | } else { 51 | subject = CurrentValueSubject(defaultValue) 52 | } 53 | 54 | var cancellables: Set = .init() 55 | subject 56 | .receive(on: DispatchQueue.global()) 57 | .map { try? valueEncoder.encode($0) } 58 | .removeDuplicates() 59 | .sink { engine.set($0, forKey: key) } 60 | .store(in: &cancellables) 61 | self.cancellables = cancellables 62 | } 63 | 64 | var wrappedValue: Value { 65 | get { subject.value } 66 | set { subject.send(newValue) } 67 | } 68 | } 69 | 70 | @propertyWrapper 71 | struct PublishedPersist { 72 | @Persist private var value: Value 73 | 74 | var projectedValue: AnyPublisher { $value } 75 | 76 | @available(*, unavailable, message: "accessing wrappedValue will result undefined behavior") 77 | var wrappedValue: Value { 78 | get { value } 79 | set { value = newValue } 80 | } 81 | 82 | static subscript( 83 | _enclosingInstance object: EnclosingSelf, 84 | wrapped _: ReferenceWritableKeyPath, 85 | storage storageKeyPath: ReferenceWritableKeyPath> 86 | ) -> Value { 87 | get { object[keyPath: storageKeyPath].value } 88 | set { 89 | (object.objectWillChange as? ObservableObjectPublisher)?.send() 90 | object[keyPath: storageKeyPath].value = newValue 91 | } 92 | } 93 | 94 | init(key: String, defaultValue: Value, engine: PersistProvider) { 95 | _value = .init(key: key, defaultValue: defaultValue, engine: engine) 96 | } 97 | } 98 | 99 | extension Persist { 100 | init(key: String, defaultValue: Value) { 101 | self.init(key: key, defaultValue: defaultValue, engine: FileStorage()) 102 | } 103 | } 104 | 105 | extension PublishedPersist { 106 | init(key: String, defaultValue: Value) { 107 | self.init(key: key, defaultValue: defaultValue, engine: FileStorage()) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /NotchDrop/TrayDrop+View.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrayDrop+View.swift 3 | // NotchDrop 4 | // 5 | // Created by 秋星桥 on 2024/7/8. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct TrayView: View { 11 | @StateObject var vm: NotchViewModel 12 | @StateObject var tvm = TrayDrop.shared 13 | 14 | @State private var targeting = false 15 | 16 | var storageTime: String { 17 | switch tvm.selectedFileStorageTime { 18 | case .oneHour: 19 | return NSLocalizedString("an hour", comment: "") 20 | case .oneDay: 21 | return NSLocalizedString("a day", comment: "") 22 | case .twoDays: 23 | return NSLocalizedString("two days", comment: "") 24 | case .threeDays: 25 | return NSLocalizedString("three days", comment: "") 26 | case .oneWeek: 27 | return NSLocalizedString("a week", comment: "") 28 | case .never: 29 | return NSLocalizedString("forever", comment: "") 30 | case .custom: 31 | let localizedTimeUnit = NSLocalizedString(tvm.customStorageTimeUnit.localized.lowercased(), comment: "") 32 | return "\(tvm.customStorageTime) \(localizedTimeUnit)" 33 | } 34 | } 35 | 36 | var body: some View { 37 | panel 38 | .onDrop(of: [.data], isTargeted: $targeting) { providers in 39 | DispatchQueue.global().async { tvm.load(providers) } 40 | return true 41 | } 42 | } 43 | 44 | var panel: some View { 45 | RoundedRectangle(cornerRadius: vm.cornerRadius) 46 | .strokeBorder(style: StrokeStyle(lineWidth: 4, dash: [10])) 47 | .foregroundStyle(.white.opacity(0.1)) 48 | .background(loading) 49 | .overlay { 50 | content 51 | .padding() 52 | } 53 | .animation(vm.animation, value: tvm.items) 54 | .animation(vm.animation, value: tvm.isLoading) 55 | } 56 | 57 | var loading: some View { 58 | RoundedRectangle(cornerRadius: vm.cornerRadius) 59 | .foregroundStyle(.white.opacity(0.1)) 60 | .conditionalEffect( 61 | .repeat( 62 | .glow(color: .blue, radius: 50), 63 | every: 1.5 64 | ), 65 | condition: tvm.isLoading > 0 66 | ) 67 | } 68 | 69 | var text: String { 70 | [ 71 | String( 72 | format: NSLocalizedString("Drag files here to keep them for %@", comment: ""), 73 | storageTime 74 | ), 75 | "&", 76 | NSLocalizedString("Press Option to delete", comment: ""), 77 | ].joined(separator: " ") 78 | } 79 | 80 | var content: some View { 81 | Group { 82 | if tvm.isEmpty { 83 | VStack(spacing: 8) { 84 | Image(systemName: "tray.and.arrow.down.fill") 85 | Text(text) 86 | .multilineTextAlignment(.center) 87 | .font(.system(.headline, design: .rounded)) 88 | } 89 | } else { 90 | ScrollView(.horizontal) { 91 | HStack(spacing: vm.spacing) { 92 | ForEach(tvm.items) { item in 93 | DropItemView(item: item, vm: vm, tvm: tvm) 94 | } 95 | } 96 | .padding(vm.spacing) 97 | } 98 | .padding(-vm.spacing) 99 | .scrollIndicators(.never) 100 | } 101 | } 102 | } 103 | } 104 | 105 | #Preview { 106 | NotchContentView(vm: .init()) 107 | .padding() 108 | .frame(width: 550, height: 150, alignment: .center) 109 | .background(.black) 110 | .preferredColorScheme(.dark) 111 | } 112 | -------------------------------------------------------------------------------- /NotchDrop/Language.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Language.swift 3 | // NotchDrop 4 | // 5 | // Created by 秋星桥 on 2024/7/31. 6 | // 7 | 8 | import Cocoa 9 | 10 | enum Language: String, CaseIterable, Identifiable, Codable { 11 | case system = "Follow System" 12 | case english = "English" 13 | case german = "German" 14 | case simplifiedChinese = "Simplified Chinese" 15 | case traditionalChinese = "Traditional Chinese" 16 | case japanese = "Japanese" 17 | case french = "French" 18 | 19 | var id: String { rawValue } 20 | 21 | var localized: String { 22 | NSLocalizedString(rawValue, comment: "") 23 | } 24 | 25 | func apply() { 26 | let languageCode: String? 27 | let local = Calendar.autoupdatingCurrent.locale?.identifier 28 | let region = local?.split(separator: "@").last?.split(separator: "_").last 29 | 30 | switch self { 31 | case .system: 32 | if region == "rg=hkzzzz" || region == "rg=twzzzz" || region == "rg=mozzzz" 33 | || region == "TW" || region == "HK" || region == "MO" 34 | { 35 | languageCode = "zh-Hant" 36 | } else if region == "CN" { 37 | languageCode = "zh-Hans" 38 | } else if region == "FR" { 39 | languageCode = "fr" 40 | } else { 41 | languageCode = "en" 42 | } 43 | case .english: 44 | languageCode = "en" 45 | case .german: 46 | languageCode = "de" 47 | case .simplifiedChinese: 48 | languageCode = "zh-Hans" 49 | case .traditionalChinese: 50 | languageCode = "zh-Hant" 51 | case .japanese: 52 | languageCode = "ja" 53 | case .french: 54 | languageCode = "fr" 55 | } 56 | 57 | let currentLanguages = UserDefaults.standard.array(forKey: "AppleLanguages") as? [String] 58 | let currentLanguageCode = currentLanguages?.first 59 | 60 | if currentLanguageCode == languageCode { 61 | return 62 | } 63 | 64 | Bundle.setLanguage(languageCode) 65 | 66 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { 67 | NSAlert.popRestart( 68 | NSLocalizedString("The language has been changed. The app will restart for the changes to take effect.", comment: ""), 69 | completion: relaunchApp 70 | ) 71 | } 72 | } 73 | } 74 | 75 | private func relaunchApp() { 76 | let path = Bundle.main.bundlePath 77 | let task = Process() 78 | task.launchPath = "/usr/bin/open" 79 | task.arguments = ["-n", path] 80 | task.launch() 81 | exit(0) 82 | } 83 | 84 | private extension Bundle { 85 | private static var onLanguageDispatchOnce: () -> Void = { 86 | object_setClass(Bundle.main, PrivateBundle.self) 87 | } 88 | 89 | static func setLanguage(_ language: String?) { 90 | onLanguageDispatchOnce() 91 | 92 | if let language { 93 | UserDefaults.standard.set([language], forKey: "AppleLanguages") 94 | } else { 95 | UserDefaults.standard.removeObject(forKey: "AppleLanguages") 96 | } 97 | UserDefaults.standard.synchronize() 98 | } 99 | } 100 | 101 | private class PrivateBundle: Bundle, @unchecked Sendable { 102 | override func localizedString(forKey key: String, value: String?, table tableName: String?) -> String { 103 | guard let languages = UserDefaults.standard.array(forKey: "AppleLanguages") as? [String], 104 | let languageCode = languages.first, 105 | let bundlePath = Bundle.main.path(forResource: languageCode, ofType: "lproj"), 106 | let bundle = Bundle(path: bundlePath) 107 | else { 108 | return super.localizedString(forKey: key, value: value, table: tableName) 109 | } 110 | return bundle.localizedString(forKey: key, value: value, table: tableName) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /NotchDrop/Share+View.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Share+View.swift 3 | // NotchDrop 4 | // 5 | // Created by 秋星桥 on 2024/7/8. 6 | // Last Modified by 冷月 on 2025/5/5. 7 | // 8 | 9 | import ColorfulX 10 | import Pow 11 | import SwiftUI 12 | import UniformTypeIdentifiers 13 | 14 | struct ShareView: View { 15 | enum ShareType { 16 | case airdrop 17 | case generic 18 | 19 | var imageName: String { 20 | switch self { 21 | case .airdrop: "airplayaudio" 22 | case .generic: "arrow.up.circle" 23 | } 24 | } 25 | 26 | var title: String { 27 | switch self { 28 | case .airdrop: NSLocalizedString("AirDrop", comment: "AirDrop sharing title") 29 | case .generic: NSLocalizedString("Share", comment: "Generic sharing title") 30 | } 31 | } 32 | 33 | var service: ([URL]) -> Share { 34 | switch self { 35 | case .airdrop: 36 | { urls in Share(files: urls, serviceName: .sendViaAirDrop) } 37 | case .generic: 38 | { urls in Share(files: urls) } 39 | } 40 | } 41 | 42 | var colorfulPresetTargeting: ColorfulPreset { 43 | switch self { 44 | case .airdrop: .neon 45 | case .generic: .sunset 46 | } 47 | } 48 | 49 | var colorfulPresetNormal: ColorfulPreset { 50 | switch self { 51 | case .airdrop: .aurora 52 | case .generic: .sunrise 53 | } 54 | } 55 | } 56 | 57 | @StateObject var vm: NotchViewModel 58 | let type: ShareType 59 | 60 | @State var trigger: UUID = .init() 61 | @State var targeting = false 62 | 63 | var body: some View { 64 | dropArea 65 | .onDrop(of: [.data], isTargeted: $targeting) { providers in 66 | trigger = .init() 67 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { 68 | vm.notchClose() 69 | } 70 | DispatchQueue.global().async { beginDrop(providers) } 71 | return true 72 | } 73 | } 74 | 75 | var dropAreaColors: [NSColor] { 76 | if targeting { 77 | type.colorfulPresetTargeting.colors 78 | } else { 79 | type.colorfulPresetNormal.colors 80 | } 81 | } 82 | 83 | var dropArea: some View { 84 | ColorfulView( 85 | color: .init(get: { dropAreaColors.map { Color($0) } }, set: { _ in }), 86 | speed: .init(get: { targeting ? 1.5 : 0 }, set: { _ in }), 87 | transitionSpeed: .constant(25) 88 | ) 89 | .opacity(0.5) 90 | .clipShape(RoundedRectangle(cornerRadius: vm.cornerRadius)) 91 | .overlay { dropLabel } 92 | .aspectRatio(1, contentMode: .fit) 93 | .contentShape(Rectangle()) 94 | .changeEffect( 95 | .spray(origin: UnitPoint(x: 0.5, y: 0.5)) { 96 | Image(systemName: "paperplane") 97 | .foregroundStyle(.white) 98 | }, 99 | value: trigger 100 | ) 101 | } 102 | 103 | var dropLabel: some View { 104 | VStack(spacing: 8) { 105 | Image(systemName: type.imageName) 106 | Text(type.title) 107 | } 108 | .font(.system(.headline, design: .rounded)) 109 | .contentShape(Rectangle()) 110 | .onTapGesture { 111 | trigger = .init() 112 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { 113 | vm.notchClose() 114 | } 115 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { 116 | let picker = NSOpenPanel() 117 | picker.allowsMultipleSelection = true 118 | picker.canChooseDirectories = true 119 | picker.canChooseFiles = true 120 | picker.begin { response in 121 | if response == .OK { 122 | let drop = type.service(picker.urls) 123 | drop.begin() 124 | } 125 | } 126 | } 127 | } 128 | } 129 | 130 | func beginDrop(_ providers: [NSItemProvider]) { 131 | assert(!Thread.isMainThread) 132 | guard let urls = providers.interfaceConvert() else { return } 133 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { 134 | let drop = type.service(urls) 135 | drop.begin() 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /NotchDrop/NotchViewModel+Events.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotchViewModel+Events.swift 3 | // NotchDrop 4 | // 5 | // Created by 秋星桥 on 2024/7/8. 6 | // 7 | 8 | import Cocoa 9 | import Combine 10 | import Foundation 11 | import SwiftUI 12 | 13 | extension NotchViewModel { 14 | func setupCancellables() { 15 | let events = EventMonitors.shared 16 | events.mouseDown 17 | .receive(on: DispatchQueue.main) 18 | .sink { [weak self] _ in 19 | guard let self else { return } 20 | let mouseLocation: NSPoint = NSEvent.mouseLocation 21 | switch status { 22 | case .opened: 23 | // touch outside, close 24 | if !notchOpenedRect.contains(mouseLocation) { 25 | notchClose() 26 | // click where user open the panel 27 | } else if deviceNotchRect.insetBy(dx: inset, dy: inset).contains(mouseLocation) { 28 | notchClose() 29 | // for the same height as device notch, open the url of project 30 | } else if headlineOpenedRect.contains(mouseLocation) { 31 | // for clicking headline which mouse event may handled by another app 32 | // open the menu 33 | if let nextValue = ContentType(rawValue: contentType.rawValue + 1) { 34 | contentType = nextValue 35 | } else { 36 | contentType = ContentType(rawValue: 0)! 37 | } 38 | } 39 | case .closed, .popping: 40 | // touch inside, open 41 | if deviceNotchRect.insetBy(dx: inset, dy: inset).contains(mouseLocation) { 42 | notchOpen(.click) 43 | } 44 | } 45 | } 46 | .store(in: &cancellables) 47 | 48 | events.optionKeyPress 49 | .receive(on: DispatchQueue.main) 50 | .sink { [weak self] input in 51 | guard let self else { return } 52 | optionKeyPressed = input 53 | } 54 | .store(in: &cancellables) 55 | 56 | events.mouseLocation 57 | .receive(on: DispatchQueue.main) 58 | .sink { [weak self] mouseLocation in 59 | guard let self else { return } 60 | let mouseLocation: NSPoint = NSEvent.mouseLocation 61 | let aboutToOpen = deviceNotchRect.insetBy(dx: inset, dy: inset).contains(mouseLocation) 62 | if status == .closed, aboutToOpen { notchPop() } 63 | if status == .popping, !aboutToOpen { notchClose() } 64 | } 65 | .store(in: &cancellables) 66 | 67 | $status 68 | .filter { $0 != .closed } 69 | .receive(on: DispatchQueue.main) 70 | .sink { [weak self] _ in 71 | withAnimation { self?.notchVisible = true } 72 | } 73 | .store(in: &cancellables) 74 | 75 | $status 76 | .filter { $0 == .popping } 77 | .throttle(for: .seconds(0.5), scheduler: DispatchQueue.main, latest: false) 78 | .sink { [weak self] _ in 79 | guard NSEvent.pressedMouseButtons == 0 else { return } 80 | self?.hapticSender.send() 81 | } 82 | .store(in: &cancellables) 83 | 84 | hapticSender 85 | .throttle(for: .seconds(0.5), scheduler: DispatchQueue.main, latest: false) 86 | .sink { [weak self] _ in 87 | guard self?.hapticFeedback ?? false else { return } 88 | NSHapticFeedbackManager.defaultPerformer.perform( 89 | .levelChange, 90 | performanceTime: .now 91 | ) 92 | } 93 | .store(in: &cancellables) 94 | 95 | $status 96 | .debounce(for: 0.5, scheduler: DispatchQueue.global()) 97 | .filter { $0 == .closed } 98 | .receive(on: DispatchQueue.main) 99 | .sink { [weak self] _ in 100 | withAnimation { 101 | self?.notchVisible = false 102 | } 103 | } 104 | .store(in: &cancellables) 105 | 106 | $selectedLanguage 107 | .dropFirst() 108 | .removeDuplicates() 109 | .receive(on: DispatchQueue.main) 110 | .sink { [weak self] output in 111 | self?.notchClose() 112 | output.apply() 113 | } 114 | .store(in: &cancellables) 115 | } 116 | 117 | func destroy() { 118 | cancellables.forEach { $0.cancel() } 119 | cancellables.removeAll() 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /NotchDrop/TrayDrop.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import Combine 3 | import Foundation 4 | import OrderedCollections 5 | 6 | class TrayDrop: ObservableObject { 7 | static let shared = TrayDrop() 8 | 9 | var cancellables = Set() 10 | 11 | @Persist(key: "keepInterval", defaultValue: 3600 * 24) 12 | var keepInterval: TimeInterval 13 | 14 | private init() { 15 | Publishers.CombineLatest3( 16 | $selectedFileStorageTime.removeDuplicates(), 17 | $customStorageTime.removeDuplicates(), 18 | $customStorageTimeUnit.removeDuplicates() 19 | ) 20 | .map { selectedFileStorageTime, customStorageTime, customStorageTimeUnit in 21 | let customTime = switch customStorageTimeUnit { 22 | case .hours: 23 | TimeInterval(customStorageTime) * 60 * 60 24 | case .days: 25 | TimeInterval(customStorageTime) * 60 * 60 * 24 26 | case .weeks: 27 | TimeInterval(customStorageTime) * 60 * 60 * 24 * 7 28 | case .months: 29 | TimeInterval(customStorageTime) * 60 * 60 * 24 * 30 30 | case .years: 31 | TimeInterval(customStorageTime) * 60 * 60 * 24 * 365 32 | } 33 | let ans = selectedFileStorageTime.toTimeInterval(customTime: customTime) 34 | print("[*] using interval \(ans) to keep files") 35 | return ans 36 | } 37 | .receive(on: DispatchQueue.main) 38 | .sink { [weak self] output in 39 | self?.keepInterval = output 40 | } 41 | .store(in: &cancellables) 42 | } 43 | 44 | var isEmpty: Bool { items.isEmpty } 45 | 46 | @PublishedPersist(key: "TrayDropItems", defaultValue: .init()) 47 | var items: OrderedSet 48 | 49 | @PublishedPersist(key: "selectedFileStorageTime", defaultValue: .oneDay) 50 | var selectedFileStorageTime: FileStorageTime 51 | 52 | @PublishedPersist(key: "customStorageTime", defaultValue: 1) 53 | var customStorageTime: Int 54 | 55 | @PublishedPersist(key: "customStorageTimeUnit", defaultValue: .days) 56 | var customStorageTimeUnit: CustomstorageTimeUnit 57 | 58 | @Published var isLoading: Int = 0 59 | 60 | func load(_ providers: [NSItemProvider]) { 61 | assert(!Thread.isMainThread) 62 | DispatchQueue.main.asyncAndWait { isLoading += 1 } 63 | guard let urls = providers.interfaceConvert() else { 64 | DispatchQueue.main.asyncAndWait { isLoading -= 1 } 65 | return 66 | } 67 | do { 68 | let items = try urls.map { try DropItem(url: $0) } 69 | DispatchQueue.main.async { 70 | items.forEach { self.items.updateOrInsert($0, at: 0) } 71 | self.isLoading -= 1 72 | } 73 | } catch { 74 | DispatchQueue.main.async { 75 | self.isLoading -= 1 76 | NSAlert.popError(error) 77 | } 78 | } 79 | } 80 | 81 | func cleanExpiredFiles() { 82 | var inEdit = items 83 | let shouldCleanItems = items.filter(\.shouldClean) 84 | for item in shouldCleanItems { 85 | inEdit.remove(item) 86 | } 87 | items = inEdit 88 | } 89 | 90 | func delete(_ item: DropItem.ID) { 91 | guard let item = items.first(where: { $0.id == item }) else { return } 92 | delete(item: item) 93 | } 94 | 95 | private func delete(item: DropItem) { 96 | var inEdit = items 97 | 98 | var url = item.storageURL 99 | try? FileManager.default.removeItem(at: url) 100 | 101 | do { 102 | // loops up to the main directory 103 | url = url.deletingLastPathComponent() 104 | while url.lastPathComponent != DropItem.mainDir, url != documentsDirectory { 105 | let contents = try FileManager.default.contentsOfDirectory(atPath: url.path) 106 | guard contents.isEmpty else { break } 107 | try FileManager.default.removeItem(at: url) 108 | url = url.deletingLastPathComponent() 109 | } 110 | } catch {} 111 | 112 | inEdit.remove(item) 113 | items = inEdit 114 | } 115 | 116 | func removeAll() { 117 | items.forEach { delete(item: $0) } 118 | } 119 | } 120 | 121 | extension TrayDrop { 122 | enum FileStorageTime: String, CaseIterable, Identifiable, Codable { 123 | case oneHour = "1 Hour" 124 | case oneDay = "1 Day" 125 | case twoDays = "2 Days" 126 | case threeDays = "3 Days" 127 | case oneWeek = "1 Week" 128 | case never = "Forever" 129 | case custom = "Custom" 130 | 131 | var id: String { rawValue } 132 | 133 | var localized: String { 134 | NSLocalizedString(rawValue, comment: "") 135 | } 136 | 137 | func toTimeInterval(customTime: TimeInterval) -> TimeInterval { 138 | switch self { 139 | case .oneHour: 140 | 60 * 60 141 | case .oneDay: 142 | 60 * 60 * 24 143 | case .twoDays: 144 | 60 * 60 * 24 * 2 145 | case .threeDays: 146 | 60 * 60 * 24 * 3 147 | case .oneWeek: 148 | 60 * 60 * 24 * 7 149 | case .never: 150 | TimeInterval.infinity 151 | case .custom: 152 | customTime 153 | } 154 | } 155 | } 156 | 157 | enum CustomstorageTimeUnit: String, CaseIterable, Identifiable, Codable { 158 | case hours = "Hours" 159 | case days = "Days" 160 | case weeks = "Weeks" 161 | case months = "Months" 162 | case years = "Years" 163 | 164 | var id: String { rawValue } 165 | 166 | var localized: String { 167 | NSLocalizedString(rawValue, comment: "") 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /NotchDrop/NotchView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotchView.swift 3 | // NotchDrop 4 | // 5 | // Created by 秋星桥 on 2024/7/7. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct NotchView: View { 11 | @StateObject var vm: NotchViewModel 12 | 13 | @State var dropTargeting: Bool = false 14 | 15 | var notchSize: CGSize { 16 | switch vm.status { 17 | case .closed: 18 | var ans = CGSize( 19 | width: vm.deviceNotchRect.width - 4, 20 | height: vm.deviceNotchRect.height - 4 21 | ) 22 | if ans.width < 0 { ans.width = 0 } 23 | if ans.height < 0 { ans.height = 0 } 24 | return ans 25 | case .opened: 26 | return vm.notchOpenedSize 27 | case .popping: 28 | return .init( 29 | width: vm.deviceNotchRect.width, 30 | height: vm.deviceNotchRect.height + 4 31 | ) 32 | } 33 | } 34 | 35 | var notchCornerRadius: CGFloat { 36 | switch vm.status { 37 | case .closed: 8 38 | case .opened: 32 39 | case .popping: 10 40 | } 41 | } 42 | 43 | var body: some View { 44 | ZStack(alignment: .top) { 45 | notch 46 | .zIndex(0) 47 | .disabled(true) 48 | .opacity(vm.notchVisible ? 1 : 0.3) 49 | Group { 50 | if vm.status == .opened { 51 | VStack(spacing: vm.spacing) { 52 | NotchHeaderView(vm: vm) 53 | NotchContentView(vm: vm) 54 | .frame(maxWidth: .infinity, maxHeight: .infinity) 55 | } 56 | .padding(vm.spacing) 57 | .frame(maxWidth: vm.notchOpenedSize.width, maxHeight: vm.notchOpenedSize.height) 58 | .zIndex(1) 59 | } 60 | } 61 | .transition( 62 | .scale.combined( 63 | with: .opacity 64 | ).combined( 65 | with: .offset(y: -vm.notchOpenedSize.height / 2) 66 | ).animation(vm.animation) 67 | ) 68 | } 69 | .background(dragDetector) 70 | .animation(vm.animation, value: vm.status) 71 | .preferredColorScheme(.dark) 72 | .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) 73 | } 74 | 75 | var notch: some View { 76 | Rectangle() 77 | .foregroundStyle(.black) 78 | .mask(notchBackgroundMaskGroup) 79 | .frame( 80 | width: notchSize.width + notchCornerRadius * 2, 81 | height: notchSize.height 82 | ) 83 | .shadow( 84 | color: .black.opacity(([.opened, .popping].contains(vm.status)) ? 1 : 0), 85 | radius: 16 86 | ) 87 | } 88 | 89 | var notchBackgroundMaskGroup: some View { 90 | Rectangle() 91 | .foregroundStyle(.black) 92 | .frame( 93 | width: notchSize.width, 94 | height: notchSize.height 95 | ) 96 | .clipShape(.rect( 97 | bottomLeadingRadius: notchCornerRadius, 98 | bottomTrailingRadius: notchCornerRadius 99 | )) 100 | .overlay { 101 | ZStack(alignment: .topTrailing) { 102 | Rectangle() 103 | .frame(width: notchCornerRadius, height: notchCornerRadius) 104 | .foregroundStyle(.black) 105 | Rectangle() 106 | .clipShape(.rect(topTrailingRadius: notchCornerRadius)) 107 | .foregroundStyle(.white) 108 | .frame( 109 | width: notchCornerRadius + vm.spacing, 110 | height: notchCornerRadius + vm.spacing 111 | ) 112 | .blendMode(.destinationOut) 113 | } 114 | .compositingGroup() 115 | .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) 116 | .offset(x: -notchCornerRadius - vm.spacing + 0.5, y: -0.5) 117 | } 118 | .overlay { 119 | ZStack(alignment: .topLeading) { 120 | Rectangle() 121 | .frame(width: notchCornerRadius, height: notchCornerRadius) 122 | .foregroundStyle(.black) 123 | Rectangle() 124 | .clipShape(.rect(topLeadingRadius: notchCornerRadius)) 125 | .foregroundStyle(.white) 126 | .frame( 127 | width: notchCornerRadius + vm.spacing, 128 | height: notchCornerRadius + vm.spacing 129 | ) 130 | .blendMode(.destinationOut) 131 | } 132 | .compositingGroup() 133 | .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing) 134 | .offset(x: notchCornerRadius + vm.spacing - 0.5, y: -0.5) 135 | } 136 | } 137 | 138 | @ViewBuilder 139 | var dragDetector: some View { 140 | RoundedRectangle(cornerRadius: notchCornerRadius) 141 | .foregroundStyle(Color.black.opacity(0.001)) // fuck you apple and 0.001 is the smallest we can have 142 | .contentShape(Rectangle()) 143 | .frame(width: notchSize.width + vm.dropDetectorRange, height: notchSize.height + vm.dropDetectorRange) 144 | .onDrop(of: [.data], isTargeted: $dropTargeting) { _ in true } 145 | .onChange(of: dropTargeting) { isTargeted in 146 | if isTargeted, vm.status == .closed { 147 | // Open the notch when a file is dragged over it 148 | vm.notchOpen(.drag) 149 | vm.hapticSender.send() 150 | } else if !isTargeted { 151 | // Close the notch when the dragged item leaves the area 152 | let mouseLocation: NSPoint = NSEvent.mouseLocation 153 | if !vm.notchOpenedRect.insetBy(dx: vm.inset, dy: vm.inset).contains(mouseLocation) { 154 | vm.notchClose() 155 | } 156 | } 157 | } 158 | .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /Resources/i18n/zh-Hans/Download_on_the_Mac_App_Store_Badge_CNSC_RGB_blk_092917.svg: -------------------------------------------------------------------------------- 1 | 2 | Download_on_the_Mac_App_Store_Badge_CNSC_RGB_blk_092917 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Resources/Download_on_the_App_Store_Badge_US-UK_RGB_blk_092917.svg: -------------------------------------------------------------------------------- 1 | 2 | Download_on_the_App_Store_Badge_US-UK_RGB_blk_4SVG_092917 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /NotchDrop.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 148901532DC9149900F2E06C /* Share.swift in Sources */ = {isa = PBXBuildFile; fileRef = 148901512DC9149900F2E06C /* Share.swift */; }; 11 | 148901542DC9149900F2E06C /* Share+View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 148901522DC9149900F2E06C /* Share+View.swift */; }; 12 | 2700D8EE2C3CFD37005ACDDF /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 2700D8ED2C3CFD37005ACDDF /* Localizable.xcstrings */; }; 13 | 501F0E8C2C59E6BD00F0113C /* Language.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501F0E8B2C59E6BD00F0113C /* Language.swift */; }; 14 | 502E41D62C3CEC990060660E /* Ext+NSAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 502E41D52C3CEC990060660E /* Ext+NSAlert.swift */; }; 15 | 503D8EBA2C3F838200E173FF /* NotchMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 503D8EB92C3F838200E173FF /* NotchMenuView.swift */; }; 16 | 504381882C3A86EC000ED325 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 504381872C3A86EC000ED325 /* Assets.xcassets */; }; 17 | 504381932C3A8755000ED325 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504381922C3A8755000ED325 /* main.swift */; }; 18 | 504381992C3A8893000ED325 /* NotchWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504381982C3A8893000ED325 /* NotchWindow.swift */; }; 19 | 5043819B2C3A8A06000ED325 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5043819A2C3A8A06000ED325 /* AppDelegate.swift */; }; 20 | 5043819E2C3A8ACA000ED325 /* ColorfulX in Frameworks */ = {isa = PBXBuildFile; productRef = 5043819D2C3A8ACA000ED325 /* ColorfulX */; }; 21 | 504381A02C3A8BA1000ED325 /* NotchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5043819F2C3A8BA1000ED325 /* NotchView.swift */; }; 22 | 504381A22C3A8BE0000ED325 /* Ext+NSScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504381A12C3A8BE0000ED325 /* Ext+NSScreen.swift */; }; 23 | 504381A42C3A8C7E000ED325 /* NotchWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504381A32C3A8C7E000ED325 /* NotchWindowController.swift */; }; 24 | 504381A62C3A8CFD000ED325 /* NotchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504381A52C3A8CFD000ED325 /* NotchViewController.swift */; }; 25 | 504381A82C3A926E000ED325 /* NotchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504381A72C3A926E000ED325 /* NotchViewModel.swift */; }; 26 | 504381AA2C3A9514000ED325 /* EventMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504381A92C3A9514000ED325 /* EventMonitor.swift */; }; 27 | 504381AC2C3A95BD000ED325 /* EventMonitors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504381AB2C3A95BD000ED325 /* EventMonitors.swift */; }; 28 | 504381AF2C3ADFF2000ED325 /* NotchContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504381AE2C3ADFF2000ED325 /* NotchContentView.swift */; }; 29 | 504381B12C3AE0B6000ED325 /* NotchHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504381B02C3AE0B6000ED325 /* NotchHeaderView.swift */; }; 30 | 5044EE992C3B069200071C5C /* TrayDrop+View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5044EE982C3B069200071C5C /* TrayDrop+View.swift */; }; 31 | 5044EE9C2C3B082300071C5C /* Pow in Frameworks */ = {isa = PBXBuildFile; productRef = 5044EE9B2C3B082300071C5C /* Pow */; }; 32 | 5044EE9E2C3B0F3D00071C5C /* Ext+FileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5044EE9D2C3B0F3D00071C5C /* Ext+FileProvider.swift */; }; 33 | 5044EEA02C3B116000071C5C /* PublishedPersist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5044EE9F2C3B116000071C5C /* PublishedPersist.swift */; }; 34 | 5044EEA22C3B126F00071C5C /* TrayDrop+DropItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5044EEA12C3B126F00071C5C /* TrayDrop+DropItemView.swift */; }; 35 | 5044EEA42C3B140F00071C5C /* Ext+URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5044EEA32C3B140F00071C5C /* Ext+URL.swift */; }; 36 | 5044EEA72C3B174100071C5C /* BitCollections in Frameworks */ = {isa = PBXBuildFile; productRef = 5044EEA62C3B174100071C5C /* BitCollections */; }; 37 | 5044EEA92C3B174100071C5C /* Collections in Frameworks */ = {isa = PBXBuildFile; productRef = 5044EEA82C3B174100071C5C /* Collections */; }; 38 | 5044EEAB2C3B174100071C5C /* DequeModule in Frameworks */ = {isa = PBXBuildFile; productRef = 5044EEAA2C3B174100071C5C /* DequeModule */; }; 39 | 5044EEAD2C3B174100071C5C /* HashTreeCollections in Frameworks */ = {isa = PBXBuildFile; productRef = 5044EEAC2C3B174100071C5C /* HashTreeCollections */; }; 40 | 5044EEAF2C3B174100071C5C /* HeapModule in Frameworks */ = {isa = PBXBuildFile; productRef = 5044EEAE2C3B174100071C5C /* HeapModule */; }; 41 | 504A5D802C59EC2D004E8297 /* LaunchAtLogin in Frameworks */ = {isa = PBXBuildFile; productRef = 504A5D7F2C59EC2D004E8297 /* LaunchAtLogin */; }; 42 | 508C4DF22C631EF700642ADC /* InfoPlist.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 508C4DF12C631EF700642ADC /* InfoPlist.xcstrings */; }; 43 | 50CB767E2C3BC8880070D5E6 /* NotchViewModel+Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50CB767D2C3BC8880070D5E6 /* NotchViewModel+Events.swift */; }; 44 | 50E26E272C3AF5E9007CF247 /* TrayDrop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E26E262C3AF5E9007CF247 /* TrayDrop.swift */; }; 45 | 50E26E292C3AF67B007CF247 /* TrayDrop+DropItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E26E282C3AF67B007CF247 /* TrayDrop+DropItem.swift */; }; 46 | 50EFDB522C3BF7B4002D3E0F /* Ext+NSImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50EFDB512C3BF7B4002D3E0F /* Ext+NSImage.swift */; }; 47 | 60AD62872C579F4400299297 /* NotchSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60AD62862C579F4400299297 /* NotchSettingsView.swift */; }; 48 | /* End PBXBuildFile section */ 49 | 50 | /* Begin PBXFileReference section */ 51 | 148901512DC9149900F2E06C /* Share.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Share.swift; sourceTree = ""; }; 52 | 148901522DC9149900F2E06C /* Share+View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Share+View.swift"; sourceTree = ""; }; 53 | 2700D8ED2C3CFD37005ACDDF /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; 54 | 501F0E8B2C59E6BD00F0113C /* Language.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Language.swift; sourceTree = ""; }; 55 | 502E41D52C3CEC990060660E /* Ext+NSAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Ext+NSAlert.swift"; sourceTree = ""; }; 56 | 503D8EB92C3F838200E173FF /* NotchMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotchMenuView.swift; sourceTree = ""; }; 57 | 504381802C3A86EA000ED325 /* NotchDrop.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NotchDrop.app; sourceTree = BUILT_PRODUCTS_DIR; }; 58 | 504381872C3A86EC000ED325 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 59 | 5043818C2C3A86EC000ED325 /* NotchDrop.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NotchDrop.entitlements; sourceTree = ""; }; 60 | 504381922C3A8755000ED325 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; 61 | 504381982C3A8893000ED325 /* NotchWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotchWindow.swift; sourceTree = ""; }; 62 | 5043819A2C3A8A06000ED325 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 63 | 5043819F2C3A8BA1000ED325 /* NotchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotchView.swift; sourceTree = ""; }; 64 | 504381A12C3A8BE0000ED325 /* Ext+NSScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Ext+NSScreen.swift"; sourceTree = ""; }; 65 | 504381A32C3A8C7E000ED325 /* NotchWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotchWindowController.swift; sourceTree = ""; }; 66 | 504381A52C3A8CFD000ED325 /* NotchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotchViewController.swift; sourceTree = ""; }; 67 | 504381A72C3A926E000ED325 /* NotchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotchViewModel.swift; sourceTree = ""; }; 68 | 504381A92C3A9514000ED325 /* EventMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventMonitor.swift; sourceTree = ""; }; 69 | 504381AB2C3A95BD000ED325 /* EventMonitors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventMonitors.swift; sourceTree = ""; }; 70 | 504381AE2C3ADFF2000ED325 /* NotchContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotchContentView.swift; sourceTree = ""; }; 71 | 504381B02C3AE0B6000ED325 /* NotchHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotchHeaderView.swift; sourceTree = ""; }; 72 | 5044EE982C3B069200071C5C /* TrayDrop+View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TrayDrop+View.swift"; sourceTree = ""; }; 73 | 5044EE9D2C3B0F3D00071C5C /* Ext+FileProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Ext+FileProvider.swift"; sourceTree = ""; }; 74 | 5044EE9F2C3B116000071C5C /* PublishedPersist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublishedPersist.swift; sourceTree = ""; }; 75 | 5044EEA12C3B126F00071C5C /* TrayDrop+DropItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TrayDrop+DropItemView.swift"; sourceTree = ""; }; 76 | 5044EEA32C3B140F00071C5C /* Ext+URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Ext+URL.swift"; sourceTree = ""; }; 77 | 508C4DF12C631EF700642ADC /* InfoPlist.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = InfoPlist.xcstrings; sourceTree = ""; }; 78 | 50CB767D2C3BC8880070D5E6 /* NotchViewModel+Events.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NotchViewModel+Events.swift"; sourceTree = ""; }; 79 | 50D7EFF22C3E484C005AC04E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 80 | 50E26E262C3AF5E9007CF247 /* TrayDrop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrayDrop.swift; sourceTree = ""; }; 81 | 50E26E282C3AF67B007CF247 /* TrayDrop+DropItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TrayDrop+DropItem.swift"; sourceTree = ""; }; 82 | 50EFDB512C3BF7B4002D3E0F /* Ext+NSImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Ext+NSImage.swift"; sourceTree = ""; }; 83 | 60AD62862C579F4400299297 /* NotchSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotchSettingsView.swift; sourceTree = ""; }; 84 | /* End PBXFileReference section */ 85 | 86 | /* Begin PBXFrameworksBuildPhase section */ 87 | 5043817D2C3A86EA000ED325 /* Frameworks */ = { 88 | isa = PBXFrameworksBuildPhase; 89 | buildActionMask = 2147483647; 90 | files = ( 91 | 5044EEAD2C3B174100071C5C /* HashTreeCollections in Frameworks */, 92 | 5043819E2C3A8ACA000ED325 /* ColorfulX in Frameworks */, 93 | 5044EE9C2C3B082300071C5C /* Pow in Frameworks */, 94 | 5044EEAF2C3B174100071C5C /* HeapModule in Frameworks */, 95 | 5044EEAB2C3B174100071C5C /* DequeModule in Frameworks */, 96 | 5044EEA92C3B174100071C5C /* Collections in Frameworks */, 97 | 504A5D802C59EC2D004E8297 /* LaunchAtLogin in Frameworks */, 98 | 5044EEA72C3B174100071C5C /* BitCollections in Frameworks */, 99 | ); 100 | runOnlyForDeploymentPostprocessing = 0; 101 | }; 102 | /* End PBXFrameworksBuildPhase section */ 103 | 104 | /* Begin PBXGroup section */ 105 | 504381772C3A86EA000ED325 = { 106 | isa = PBXGroup; 107 | children = ( 108 | 504381822C3A86EA000ED325 /* NotchDrop */, 109 | 504381812C3A86EA000ED325 /* Products */, 110 | 504381AD2C3ADDAB000ED325 /* Frameworks */, 111 | ); 112 | sourceTree = ""; 113 | }; 114 | 504381812C3A86EA000ED325 /* Products */ = { 115 | isa = PBXGroup; 116 | children = ( 117 | 504381802C3A86EA000ED325 /* NotchDrop.app */, 118 | ); 119 | name = Products; 120 | sourceTree = ""; 121 | }; 122 | 504381822C3A86EA000ED325 /* NotchDrop */ = { 123 | isa = PBXGroup; 124 | children = ( 125 | 148901512DC9149900F2E06C /* Share.swift */, 126 | 148901522DC9149900F2E06C /* Share+View.swift */, 127 | 50D7EFF22C3E484C005AC04E /* Info.plist */, 128 | 5043818C2C3A86EC000ED325 /* NotchDrop.entitlements */, 129 | 2700D8ED2C3CFD37005ACDDF /* Localizable.xcstrings */, 130 | 508C4DF12C631EF700642ADC /* InfoPlist.xcstrings */, 131 | 504381872C3A86EC000ED325 /* Assets.xcassets */, 132 | 504381922C3A8755000ED325 /* main.swift */, 133 | 5043819A2C3A8A06000ED325 /* AppDelegate.swift */, 134 | 501F0E8B2C59E6BD00F0113C /* Language.swift */, 135 | 504381982C3A8893000ED325 /* NotchWindow.swift */, 136 | 504381A32C3A8C7E000ED325 /* NotchWindowController.swift */, 137 | 5043819F2C3A8BA1000ED325 /* NotchView.swift */, 138 | 504381B02C3AE0B6000ED325 /* NotchHeaderView.swift */, 139 | 503D8EB92C3F838200E173FF /* NotchMenuView.swift */, 140 | 504381AE2C3ADFF2000ED325 /* NotchContentView.swift */, 141 | 60AD62862C579F4400299297 /* NotchSettingsView.swift */, 142 | 504381A52C3A8CFD000ED325 /* NotchViewController.swift */, 143 | 504381A72C3A926E000ED325 /* NotchViewModel.swift */, 144 | 50CB767D2C3BC8880070D5E6 /* NotchViewModel+Events.swift */, 145 | 504381A92C3A9514000ED325 /* EventMonitor.swift */, 146 | 504381AB2C3A95BD000ED325 /* EventMonitors.swift */, 147 | 50E26E262C3AF5E9007CF247 /* TrayDrop.swift */, 148 | 50E26E282C3AF67B007CF247 /* TrayDrop+DropItem.swift */, 149 | 5044EE982C3B069200071C5C /* TrayDrop+View.swift */, 150 | 5044EEA12C3B126F00071C5C /* TrayDrop+DropItemView.swift */, 151 | 5044EEA32C3B140F00071C5C /* Ext+URL.swift */, 152 | 50EFDB512C3BF7B4002D3E0F /* Ext+NSImage.swift */, 153 | 504381A12C3A8BE0000ED325 /* Ext+NSScreen.swift */, 154 | 5044EE9D2C3B0F3D00071C5C /* Ext+FileProvider.swift */, 155 | 502E41D52C3CEC990060660E /* Ext+NSAlert.swift */, 156 | 5044EE9F2C3B116000071C5C /* PublishedPersist.swift */, 157 | ); 158 | path = NotchDrop; 159 | sourceTree = ""; 160 | }; 161 | 504381AD2C3ADDAB000ED325 /* Frameworks */ = { 162 | isa = PBXGroup; 163 | children = ( 164 | ); 165 | name = Frameworks; 166 | sourceTree = ""; 167 | }; 168 | /* End PBXGroup section */ 169 | 170 | /* Begin PBXNativeTarget section */ 171 | 5043817F2C3A86EA000ED325 /* NotchDrop */ = { 172 | isa = PBXNativeTarget; 173 | buildConfigurationList = 5043818F2C3A86EC000ED325 /* Build configuration list for PBXNativeTarget "NotchDrop" */; 174 | buildPhases = ( 175 | 5043817C2C3A86EA000ED325 /* Sources */, 176 | 5043817D2C3A86EA000ED325 /* Frameworks */, 177 | 5043817E2C3A86EA000ED325 /* Resources */, 178 | ); 179 | buildRules = ( 180 | ); 181 | dependencies = ( 182 | ); 183 | name = NotchDrop; 184 | packageProductDependencies = ( 185 | 5043819D2C3A8ACA000ED325 /* ColorfulX */, 186 | 5044EE9B2C3B082300071C5C /* Pow */, 187 | 5044EEA62C3B174100071C5C /* BitCollections */, 188 | 5044EEA82C3B174100071C5C /* Collections */, 189 | 5044EEAA2C3B174100071C5C /* DequeModule */, 190 | 5044EEAC2C3B174100071C5C /* HashTreeCollections */, 191 | 5044EEAE2C3B174100071C5C /* HeapModule */, 192 | 504A5D7F2C59EC2D004E8297 /* LaunchAtLogin */, 193 | ); 194 | productName = NotchDrop; 195 | productReference = 504381802C3A86EA000ED325 /* NotchDrop.app */; 196 | productType = "com.apple.product-type.application"; 197 | }; 198 | /* End PBXNativeTarget section */ 199 | 200 | /* Begin PBXProject section */ 201 | 504381782C3A86EA000ED325 /* Project object */ = { 202 | isa = PBXProject; 203 | attributes = { 204 | BuildIndependentTargetsInParallel = 1; 205 | LastSwiftUpdateCheck = 1540; 206 | LastUpgradeCheck = 2600; 207 | TargetAttributes = { 208 | 5043817F2C3A86EA000ED325 = { 209 | CreatedOnToolsVersion = 15.4; 210 | }; 211 | }; 212 | }; 213 | buildConfigurationList = 5043817B2C3A86EA000ED325 /* Build configuration list for PBXProject "NotchDrop" */; 214 | compatibilityVersion = "Xcode 14.0"; 215 | developmentRegion = en; 216 | hasScannedForEncodings = 0; 217 | knownRegions = ( 218 | en, 219 | Base, 220 | "zh-Hans", 221 | "zh-Hant", 222 | ja, 223 | fr, 224 | ); 225 | mainGroup = 504381772C3A86EA000ED325; 226 | packageReferences = ( 227 | 5043819C2C3A8ACA000ED325 /* XCRemoteSwiftPackageReference "ColorfulX" */, 228 | 5044EE9A2C3B082300071C5C /* XCRemoteSwiftPackageReference "Pow" */, 229 | 5044EEA52C3B174100071C5C /* XCRemoteSwiftPackageReference "swift-collections" */, 230 | 504A5D7E2C59EC2D004E8297 /* XCRemoteSwiftPackageReference "LaunchAtLogin-Modern" */, 231 | ); 232 | productRefGroup = 504381812C3A86EA000ED325 /* Products */; 233 | projectDirPath = ""; 234 | projectRoot = ""; 235 | targets = ( 236 | 5043817F2C3A86EA000ED325 /* NotchDrop */, 237 | ); 238 | }; 239 | /* End PBXProject section */ 240 | 241 | /* Begin PBXResourcesBuildPhase section */ 242 | 5043817E2C3A86EA000ED325 /* Resources */ = { 243 | isa = PBXResourcesBuildPhase; 244 | buildActionMask = 2147483647; 245 | files = ( 246 | 508C4DF22C631EF700642ADC /* InfoPlist.xcstrings in Resources */, 247 | 504381882C3A86EC000ED325 /* Assets.xcassets in Resources */, 248 | 2700D8EE2C3CFD37005ACDDF /* Localizable.xcstrings in Resources */, 249 | ); 250 | runOnlyForDeploymentPostprocessing = 0; 251 | }; 252 | /* End PBXResourcesBuildPhase section */ 253 | 254 | /* Begin PBXSourcesBuildPhase section */ 255 | 5043817C2C3A86EA000ED325 /* Sources */ = { 256 | isa = PBXSourcesBuildPhase; 257 | buildActionMask = 2147483647; 258 | files = ( 259 | 504381992C3A8893000ED325 /* NotchWindow.swift in Sources */, 260 | 5044EEA22C3B126F00071C5C /* TrayDrop+DropItemView.swift in Sources */, 261 | 504381AF2C3ADFF2000ED325 /* NotchContentView.swift in Sources */, 262 | 504381A02C3A8BA1000ED325 /* NotchView.swift in Sources */, 263 | 503D8EBA2C3F838200E173FF /* NotchMenuView.swift in Sources */, 264 | 50EFDB522C3BF7B4002D3E0F /* Ext+NSImage.swift in Sources */, 265 | 50E26E272C3AF5E9007CF247 /* TrayDrop.swift in Sources */, 266 | 5044EE9E2C3B0F3D00071C5C /* Ext+FileProvider.swift in Sources */, 267 | 504381A42C3A8C7E000ED325 /* NotchWindowController.swift in Sources */, 268 | 504381B12C3AE0B6000ED325 /* NotchHeaderView.swift in Sources */, 269 | 50E26E292C3AF67B007CF247 /* TrayDrop+DropItem.swift in Sources */, 270 | 504381A22C3A8BE0000ED325 /* Ext+NSScreen.swift in Sources */, 271 | 5044EEA42C3B140F00071C5C /* Ext+URL.swift in Sources */, 272 | 5044EE992C3B069200071C5C /* TrayDrop+View.swift in Sources */, 273 | 50CB767E2C3BC8880070D5E6 /* NotchViewModel+Events.swift in Sources */, 274 | 504381AC2C3A95BD000ED325 /* EventMonitors.swift in Sources */, 275 | 502E41D62C3CEC990060660E /* Ext+NSAlert.swift in Sources */, 276 | 504381A62C3A8CFD000ED325 /* NotchViewController.swift in Sources */, 277 | 504381AA2C3A9514000ED325 /* EventMonitor.swift in Sources */, 278 | 501F0E8C2C59E6BD00F0113C /* Language.swift in Sources */, 279 | 5043819B2C3A8A06000ED325 /* AppDelegate.swift in Sources */, 280 | 148901532DC9149900F2E06C /* Share.swift in Sources */, 281 | 148901542DC9149900F2E06C /* Share+View.swift in Sources */, 282 | 504381932C3A8755000ED325 /* main.swift in Sources */, 283 | 60AD62872C579F4400299297 /* NotchSettingsView.swift in Sources */, 284 | 504381A82C3A926E000ED325 /* NotchViewModel.swift in Sources */, 285 | 5044EEA02C3B116000071C5C /* PublishedPersist.swift in Sources */, 286 | ); 287 | runOnlyForDeploymentPostprocessing = 0; 288 | }; 289 | /* End PBXSourcesBuildPhase section */ 290 | 291 | /* Begin XCBuildConfiguration section */ 292 | 5043818D2C3A86EC000ED325 /* Debug */ = { 293 | isa = XCBuildConfiguration; 294 | buildSettings = { 295 | ALWAYS_SEARCH_USER_PATHS = NO; 296 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 297 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 298 | CLANG_ANALYZER_NONNULL = YES; 299 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 300 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 301 | CLANG_ENABLE_MODULES = YES; 302 | CLANG_ENABLE_OBJC_ARC = YES; 303 | CLANG_ENABLE_OBJC_WEAK = YES; 304 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 305 | CLANG_WARN_BOOL_CONVERSION = YES; 306 | CLANG_WARN_COMMA = YES; 307 | CLANG_WARN_CONSTANT_CONVERSION = YES; 308 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 309 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 310 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 311 | CLANG_WARN_EMPTY_BODY = YES; 312 | CLANG_WARN_ENUM_CONVERSION = YES; 313 | CLANG_WARN_INFINITE_RECURSION = YES; 314 | CLANG_WARN_INT_CONVERSION = YES; 315 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 316 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 317 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 318 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 319 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 320 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 321 | CLANG_WARN_STRICT_PROTOTYPES = YES; 322 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 323 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 324 | CLANG_WARN_UNREACHABLE_CODE = YES; 325 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 326 | COPY_PHASE_STRIP = NO; 327 | DEAD_CODE_STRIPPING = YES; 328 | DEBUG_INFORMATION_FORMAT = dwarf; 329 | DEVELOPMENT_TEAM = 964G86XT2P; 330 | ENABLE_STRICT_OBJC_MSGSEND = YES; 331 | ENABLE_TESTABILITY = YES; 332 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 333 | GCC_C_LANGUAGE_STANDARD = gnu17; 334 | GCC_DYNAMIC_NO_PIC = NO; 335 | GCC_NO_COMMON_BLOCKS = YES; 336 | GCC_OPTIMIZATION_LEVEL = 0; 337 | GCC_PREPROCESSOR_DEFINITIONS = ( 338 | "DEBUG=1", 339 | "$(inherited)", 340 | ); 341 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 342 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 343 | GCC_WARN_UNDECLARED_SELECTOR = YES; 344 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 345 | GCC_WARN_UNUSED_FUNCTION = YES; 346 | GCC_WARN_UNUSED_VARIABLE = YES; 347 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 348 | MACOSX_DEPLOYMENT_TARGET = 14.5; 349 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 350 | MTL_FAST_MATH = YES; 351 | ONLY_ACTIVE_ARCH = YES; 352 | SDKROOT = macosx; 353 | STRING_CATALOG_GENERATE_SYMBOLS = YES; 354 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; 355 | SWIFT_EMIT_LOC_STRINGS = YES; 356 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 357 | }; 358 | name = Debug; 359 | }; 360 | 5043818E2C3A86EC000ED325 /* Release */ = { 361 | isa = XCBuildConfiguration; 362 | buildSettings = { 363 | ALWAYS_SEARCH_USER_PATHS = NO; 364 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 365 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 366 | CLANG_ANALYZER_NONNULL = YES; 367 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 368 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 369 | CLANG_ENABLE_MODULES = YES; 370 | CLANG_ENABLE_OBJC_ARC = YES; 371 | CLANG_ENABLE_OBJC_WEAK = YES; 372 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 373 | CLANG_WARN_BOOL_CONVERSION = YES; 374 | CLANG_WARN_COMMA = YES; 375 | CLANG_WARN_CONSTANT_CONVERSION = YES; 376 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 377 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 378 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 379 | CLANG_WARN_EMPTY_BODY = YES; 380 | CLANG_WARN_ENUM_CONVERSION = YES; 381 | CLANG_WARN_INFINITE_RECURSION = YES; 382 | CLANG_WARN_INT_CONVERSION = YES; 383 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 384 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 385 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 386 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 387 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 388 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 389 | CLANG_WARN_STRICT_PROTOTYPES = YES; 390 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 391 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 392 | CLANG_WARN_UNREACHABLE_CODE = YES; 393 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 394 | COPY_PHASE_STRIP = NO; 395 | DEAD_CODE_STRIPPING = YES; 396 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 397 | DEVELOPMENT_TEAM = 964G86XT2P; 398 | ENABLE_NS_ASSERTIONS = NO; 399 | ENABLE_STRICT_OBJC_MSGSEND = YES; 400 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 401 | GCC_C_LANGUAGE_STANDARD = gnu17; 402 | GCC_NO_COMMON_BLOCKS = YES; 403 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 404 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 405 | GCC_WARN_UNDECLARED_SELECTOR = YES; 406 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 407 | GCC_WARN_UNUSED_FUNCTION = YES; 408 | GCC_WARN_UNUSED_VARIABLE = YES; 409 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 410 | MACOSX_DEPLOYMENT_TARGET = 14.5; 411 | MTL_ENABLE_DEBUG_INFO = NO; 412 | MTL_FAST_MATH = YES; 413 | SDKROOT = macosx; 414 | STRING_CATALOG_GENERATE_SYMBOLS = YES; 415 | SWIFT_COMPILATION_MODE = wholemodule; 416 | SWIFT_EMIT_LOC_STRINGS = YES; 417 | }; 418 | name = Release; 419 | }; 420 | 504381902C3A86EC000ED325 /* Debug */ = { 421 | isa = XCBuildConfiguration; 422 | buildSettings = { 423 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 424 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 425 | CODE_SIGN_ENTITLEMENTS = NotchDrop/NotchDrop.entitlements; 426 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; 427 | CODE_SIGN_STYLE = Automatic; 428 | COMBINE_HIDPI_IMAGES = YES; 429 | CURRENT_PROJECT_VERSION = 42; 430 | DEAD_CODE_STRIPPING = YES; 431 | ENABLE_APP_SANDBOX = YES; 432 | ENABLE_ENHANCED_SECURITY = YES; 433 | ENABLE_HARDENED_RUNTIME = YES; 434 | ENABLE_POINTER_AUTHENTICATION = NO; 435 | ENABLE_PREVIEWS = YES; 436 | ENABLE_USER_SELECTED_FILES = readwrite; 437 | GENERATE_INFOPLIST_FILE = YES; 438 | INFOPLIST_FILE = NotchDrop/Info.plist; 439 | INFOPLIST_KEY_CFBundleDisplayName = NotchDrop; 440 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; 441 | INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 Lakr Aream. All rights reserved."; 442 | LD_RUNPATH_SEARCH_PATHS = ( 443 | "$(inherited)", 444 | "@executable_path/../Frameworks", 445 | ); 446 | MACOSX_DEPLOYMENT_TARGET = 13.0; 447 | MARKETING_VERSION = 2.16; 448 | PRODUCT_BUNDLE_IDENTIFIER = wiki.qaq.NotchDrop; 449 | PRODUCT_NAME = "$(TARGET_NAME)"; 450 | SWIFT_EMIT_LOC_STRINGS = YES; 451 | SWIFT_VERSION = 5.0; 452 | }; 453 | name = Debug; 454 | }; 455 | 504381912C3A86EC000ED325 /* Release */ = { 456 | isa = XCBuildConfiguration; 457 | buildSettings = { 458 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 459 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 460 | CODE_SIGN_ENTITLEMENTS = NotchDrop/NotchDrop.entitlements; 461 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; 462 | CODE_SIGN_STYLE = Automatic; 463 | COMBINE_HIDPI_IMAGES = YES; 464 | CURRENT_PROJECT_VERSION = 42; 465 | DEAD_CODE_STRIPPING = YES; 466 | ENABLE_APP_SANDBOX = YES; 467 | ENABLE_ENHANCED_SECURITY = YES; 468 | ENABLE_HARDENED_RUNTIME = YES; 469 | ENABLE_POINTER_AUTHENTICATION = NO; 470 | ENABLE_PREVIEWS = YES; 471 | ENABLE_USER_SELECTED_FILES = readwrite; 472 | GENERATE_INFOPLIST_FILE = YES; 473 | INFOPLIST_FILE = NotchDrop/Info.plist; 474 | INFOPLIST_KEY_CFBundleDisplayName = NotchDrop; 475 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; 476 | INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 Lakr Aream. All rights reserved."; 477 | LD_RUNPATH_SEARCH_PATHS = ( 478 | "$(inherited)", 479 | "@executable_path/../Frameworks", 480 | ); 481 | MACOSX_DEPLOYMENT_TARGET = 13.0; 482 | MARKETING_VERSION = 2.16; 483 | PRODUCT_BUNDLE_IDENTIFIER = wiki.qaq.NotchDrop; 484 | PRODUCT_NAME = "$(TARGET_NAME)"; 485 | SWIFT_EMIT_LOC_STRINGS = YES; 486 | SWIFT_VERSION = 5.0; 487 | }; 488 | name = Release; 489 | }; 490 | /* End XCBuildConfiguration section */ 491 | 492 | /* Begin XCConfigurationList section */ 493 | 5043817B2C3A86EA000ED325 /* Build configuration list for PBXProject "NotchDrop" */ = { 494 | isa = XCConfigurationList; 495 | buildConfigurations = ( 496 | 5043818D2C3A86EC000ED325 /* Debug */, 497 | 5043818E2C3A86EC000ED325 /* Release */, 498 | ); 499 | defaultConfigurationIsVisible = 0; 500 | defaultConfigurationName = Release; 501 | }; 502 | 5043818F2C3A86EC000ED325 /* Build configuration list for PBXNativeTarget "NotchDrop" */ = { 503 | isa = XCConfigurationList; 504 | buildConfigurations = ( 505 | 504381902C3A86EC000ED325 /* Debug */, 506 | 504381912C3A86EC000ED325 /* Release */, 507 | ); 508 | defaultConfigurationIsVisible = 0; 509 | defaultConfigurationName = Release; 510 | }; 511 | /* End XCConfigurationList section */ 512 | 513 | /* Begin XCRemoteSwiftPackageReference section */ 514 | 5043819C2C3A8ACA000ED325 /* XCRemoteSwiftPackageReference "ColorfulX" */ = { 515 | isa = XCRemoteSwiftPackageReference; 516 | repositoryURL = "https://github.com/Lakr233/ColorfulX"; 517 | requirement = { 518 | kind = upToNextMajorVersion; 519 | minimumVersion = 5.7.0; 520 | }; 521 | }; 522 | 5044EE9A2C3B082300071C5C /* XCRemoteSwiftPackageReference "Pow" */ = { 523 | isa = XCRemoteSwiftPackageReference; 524 | repositoryURL = "https://github.com/EmergeTools/Pow"; 525 | requirement = { 526 | kind = upToNextMajorVersion; 527 | minimumVersion = 1.0.4; 528 | }; 529 | }; 530 | 5044EEA52C3B174100071C5C /* XCRemoteSwiftPackageReference "swift-collections" */ = { 531 | isa = XCRemoteSwiftPackageReference; 532 | repositoryURL = "https://github.com/apple/swift-collections.git"; 533 | requirement = { 534 | kind = upToNextMajorVersion; 535 | minimumVersion = 1.3.0; 536 | }; 537 | }; 538 | 504A5D7E2C59EC2D004E8297 /* XCRemoteSwiftPackageReference "LaunchAtLogin-Modern" */ = { 539 | isa = XCRemoteSwiftPackageReference; 540 | repositoryURL = "https://github.com/sindresorhus/LaunchAtLogin-Modern"; 541 | requirement = { 542 | kind = upToNextMajorVersion; 543 | minimumVersion = 1.1.0; 544 | }; 545 | }; 546 | /* End XCRemoteSwiftPackageReference section */ 547 | 548 | /* Begin XCSwiftPackageProductDependency section */ 549 | 5043819D2C3A8ACA000ED325 /* ColorfulX */ = { 550 | isa = XCSwiftPackageProductDependency; 551 | package = 5043819C2C3A8ACA000ED325 /* XCRemoteSwiftPackageReference "ColorfulX" */; 552 | productName = ColorfulX; 553 | }; 554 | 5044EE9B2C3B082300071C5C /* Pow */ = { 555 | isa = XCSwiftPackageProductDependency; 556 | package = 5044EE9A2C3B082300071C5C /* XCRemoteSwiftPackageReference "Pow" */; 557 | productName = Pow; 558 | }; 559 | 5044EEA62C3B174100071C5C /* BitCollections */ = { 560 | isa = XCSwiftPackageProductDependency; 561 | package = 5044EEA52C3B174100071C5C /* XCRemoteSwiftPackageReference "swift-collections" */; 562 | productName = BitCollections; 563 | }; 564 | 5044EEA82C3B174100071C5C /* Collections */ = { 565 | isa = XCSwiftPackageProductDependency; 566 | package = 5044EEA52C3B174100071C5C /* XCRemoteSwiftPackageReference "swift-collections" */; 567 | productName = Collections; 568 | }; 569 | 5044EEAA2C3B174100071C5C /* DequeModule */ = { 570 | isa = XCSwiftPackageProductDependency; 571 | package = 5044EEA52C3B174100071C5C /* XCRemoteSwiftPackageReference "swift-collections" */; 572 | productName = DequeModule; 573 | }; 574 | 5044EEAC2C3B174100071C5C /* HashTreeCollections */ = { 575 | isa = XCSwiftPackageProductDependency; 576 | package = 5044EEA52C3B174100071C5C /* XCRemoteSwiftPackageReference "swift-collections" */; 577 | productName = HashTreeCollections; 578 | }; 579 | 5044EEAE2C3B174100071C5C /* HeapModule */ = { 580 | isa = XCSwiftPackageProductDependency; 581 | package = 5044EEA52C3B174100071C5C /* XCRemoteSwiftPackageReference "swift-collections" */; 582 | productName = HeapModule; 583 | }; 584 | 504A5D7F2C59EC2D004E8297 /* LaunchAtLogin */ = { 585 | isa = XCSwiftPackageProductDependency; 586 | package = 504A5D7E2C59EC2D004E8297 /* XCRemoteSwiftPackageReference "LaunchAtLogin-Modern" */; 587 | productName = LaunchAtLogin; 588 | }; 589 | /* End XCSwiftPackageProductDependency section */ 590 | }; 591 | rootObject = 504381782C3A86EA000ED325 /* Project object */; 592 | } 593 | -------------------------------------------------------------------------------- /NotchDrop/Localizable.xcstrings: -------------------------------------------------------------------------------- 1 | { 2 | "sourceLanguage" : "en", 3 | "strings" : { 4 | "& Press Option to delete" : { 5 | "extractionState" : "manual", 6 | "localizations" : { 7 | "de" : { 8 | "stringUnit" : { 9 | "state" : "translated", 10 | "value" : "& drücke ⌥ zum Löschen" 11 | } 12 | }, 13 | "en" : { 14 | "stringUnit" : { 15 | "state" : "translated", 16 | "value" : "& Press ⌥ to delete" 17 | } 18 | }, 19 | "fr" : { 20 | "stringUnit" : { 21 | "state" : "needs_review", 22 | "value" : "& appuyer sur ••• puis \"Vider\" pour les supprimer" 23 | } 24 | }, 25 | "ja" : { 26 | "stringUnit" : { 27 | "state" : "translated", 28 | "value" : "& ⌥を押して削除" 29 | } 30 | }, 31 | "zh-Hans" : { 32 | "stringUnit" : { 33 | "state" : "translated", 34 | "value" : "& 按⌥键删除" 35 | } 36 | }, 37 | "zh-Hant" : { 38 | "stringUnit" : { 39 | "state" : "translated", 40 | "value" : "& 按下⌥以刪除" 41 | } 42 | } 43 | } 44 | }, 45 | "1 Day" : { 46 | "extractionState" : "manual", 47 | "localizations" : { 48 | "de" : { 49 | "stringUnit" : { 50 | "state" : "translated", 51 | "value" : "1 Tag" 52 | } 53 | }, 54 | "en" : { 55 | "stringUnit" : { 56 | "state" : "translated", 57 | "value" : "1 Day" 58 | } 59 | }, 60 | "fr" : { 61 | "stringUnit" : { 62 | "state" : "translated", 63 | "value" : "1 Jour" 64 | } 65 | }, 66 | "ja" : { 67 | "stringUnit" : { 68 | "state" : "translated", 69 | "value" : "1日間" 70 | } 71 | }, 72 | "zh-Hans" : { 73 | "stringUnit" : { 74 | "state" : "translated", 75 | "value" : "1 天" 76 | } 77 | }, 78 | "zh-Hant" : { 79 | "stringUnit" : { 80 | "state" : "translated", 81 | "value" : "1 天" 82 | } 83 | } 84 | } 85 | }, 86 | "1 Hour" : { 87 | "extractionState" : "manual", 88 | "localizations" : { 89 | "de" : { 90 | "stringUnit" : { 91 | "state" : "translated", 92 | "value" : "1 Stunde" 93 | } 94 | }, 95 | "en" : { 96 | "stringUnit" : { 97 | "state" : "translated", 98 | "value" : "1 Hour" 99 | } 100 | }, 101 | "fr" : { 102 | "stringUnit" : { 103 | "state" : "translated", 104 | "value" : "1 Heure" 105 | } 106 | }, 107 | "ja" : { 108 | "stringUnit" : { 109 | "state" : "translated", 110 | "value" : "1時間" 111 | } 112 | }, 113 | "zh-Hans" : { 114 | "stringUnit" : { 115 | "state" : "translated", 116 | "value" : "1 小时" 117 | } 118 | }, 119 | "zh-Hant" : { 120 | "stringUnit" : { 121 | "state" : "translated", 122 | "value" : "1 小時" 123 | } 124 | } 125 | } 126 | }, 127 | "1 Week" : { 128 | "extractionState" : "manual", 129 | "localizations" : { 130 | "de" : { 131 | "stringUnit" : { 132 | "state" : "translated", 133 | "value" : "1 Woche" 134 | } 135 | }, 136 | "en" : { 137 | "stringUnit" : { 138 | "state" : "translated", 139 | "value" : "1 Week" 140 | } 141 | }, 142 | "fr" : { 143 | "stringUnit" : { 144 | "state" : "translated", 145 | "value" : "1 Semaine" 146 | } 147 | }, 148 | "ja" : { 149 | "stringUnit" : { 150 | "state" : "translated", 151 | "value" : "1週間" 152 | } 153 | }, 154 | "zh-Hans" : { 155 | "stringUnit" : { 156 | "state" : "translated", 157 | "value" : "1 周" 158 | } 159 | }, 160 | "zh-Hant" : { 161 | "stringUnit" : { 162 | "state" : "translated", 163 | "value" : "1 星期" 164 | } 165 | } 166 | } 167 | }, 168 | "2 Days" : { 169 | "extractionState" : "manual", 170 | "localizations" : { 171 | "de" : { 172 | "stringUnit" : { 173 | "state" : "translated", 174 | "value" : "2 Tage" 175 | } 176 | }, 177 | "en" : { 178 | "stringUnit" : { 179 | "state" : "translated", 180 | "value" : "2 Days" 181 | } 182 | }, 183 | "fr" : { 184 | "stringUnit" : { 185 | "state" : "translated", 186 | "value" : "2 Jours" 187 | } 188 | }, 189 | "ja" : { 190 | "stringUnit" : { 191 | "state" : "translated", 192 | "value" : "2日間" 193 | } 194 | }, 195 | "zh-Hans" : { 196 | "stringUnit" : { 197 | "state" : "translated", 198 | "value" : "2天" 199 | } 200 | }, 201 | "zh-Hant" : { 202 | "stringUnit" : { 203 | "state" : "translated", 204 | "value" : "2 天" 205 | } 206 | } 207 | } 208 | }, 209 | "3 Days" : { 210 | "extractionState" : "manual", 211 | "localizations" : { 212 | "de" : { 213 | "stringUnit" : { 214 | "state" : "translated", 215 | "value" : "3 Tage" 216 | } 217 | }, 218 | "en" : { 219 | "stringUnit" : { 220 | "state" : "translated", 221 | "value" : "3 Days" 222 | } 223 | }, 224 | "fr" : { 225 | "stringUnit" : { 226 | "state" : "translated", 227 | "value" : "3 Jours" 228 | } 229 | }, 230 | "ja" : { 231 | "stringUnit" : { 232 | "state" : "translated", 233 | "value" : "3日間" 234 | } 235 | }, 236 | "zh-Hans" : { 237 | "stringUnit" : { 238 | "state" : "translated", 239 | "value" : "3天" 240 | } 241 | }, 242 | "zh-Hant" : { 243 | "stringUnit" : { 244 | "state" : "translated", 245 | "value" : "3 天" 246 | } 247 | } 248 | } 249 | }, 250 | "888888" : { 251 | "localizations" : { 252 | "de" : { 253 | "stringUnit" : { 254 | "state" : "translated", 255 | "value" : "888888" 256 | } 257 | }, 258 | "en" : { 259 | "stringUnit" : { 260 | "state" : "translated", 261 | "value" : "888888" 262 | } 263 | }, 264 | "fr" : { 265 | "stringUnit" : { 266 | "state" : "translated", 267 | "value" : "888888" 268 | } 269 | }, 270 | "ja" : { 271 | "stringUnit" : { 272 | "state" : "translated", 273 | "value" : "888888" 274 | } 275 | }, 276 | "zh-Hans" : { 277 | "stringUnit" : { 278 | "state" : "translated", 279 | "value" : "88888" 280 | } 281 | }, 282 | "zh-Hant" : { 283 | "stringUnit" : { 284 | "state" : "translated", 285 | "value" : "888888" 286 | } 287 | } 288 | } 289 | }, 290 | "a day" : { 291 | "localizations" : { 292 | "de" : { 293 | "stringUnit" : { 294 | "state" : "translated", 295 | "value" : "einen Tag" 296 | } 297 | }, 298 | "en" : { 299 | "stringUnit" : { 300 | "state" : "translated", 301 | "value" : "a day" 302 | } 303 | }, 304 | "fr" : { 305 | "stringUnit" : { 306 | "state" : "translated", 307 | "value" : "un jour" 308 | } 309 | }, 310 | "ja" : { 311 | "stringUnit" : { 312 | "state" : "translated", 313 | "value" : "一日間" 314 | } 315 | }, 316 | "zh-Hans" : { 317 | "stringUnit" : { 318 | "state" : "translated", 319 | "value" : "一天" 320 | } 321 | }, 322 | "zh-Hant" : { 323 | "stringUnit" : { 324 | "state" : "translated", 325 | "value" : "一天" 326 | } 327 | } 328 | } 329 | }, 330 | "a week" : { 331 | "localizations" : { 332 | "de" : { 333 | "stringUnit" : { 334 | "state" : "translated", 335 | "value" : "eine Woche" 336 | } 337 | }, 338 | "en" : { 339 | "stringUnit" : { 340 | "state" : "translated", 341 | "value" : "a week" 342 | } 343 | }, 344 | "fr" : { 345 | "stringUnit" : { 346 | "state" : "translated", 347 | "value" : "une semaine" 348 | } 349 | }, 350 | "ja" : { 351 | "stringUnit" : { 352 | "state" : "translated", 353 | "value" : "一週間" 354 | } 355 | }, 356 | "zh-Hans" : { 357 | "stringUnit" : { 358 | "state" : "translated", 359 | "value" : "一周" 360 | } 361 | }, 362 | "zh-Hant" : { 363 | "stringUnit" : { 364 | "state" : "translated", 365 | "value" : "一星期" 366 | } 367 | } 368 | } 369 | }, 370 | "AirDrop" : { 371 | "extractionState" : "manual", 372 | "localizations" : { 373 | "de" : { 374 | "stringUnit" : { 375 | "state" : "translated", 376 | "value" : "AirDrop" 377 | } 378 | }, 379 | "en" : { 380 | "stringUnit" : { 381 | "state" : "translated", 382 | "value" : "AirDrop" 383 | } 384 | }, 385 | "fr" : { 386 | "stringUnit" : { 387 | "state" : "translated", 388 | "value" : "AirDrop" 389 | } 390 | }, 391 | "ja" : { 392 | "stringUnit" : { 393 | "state" : "translated", 394 | "value" : "AirDrop" 395 | } 396 | }, 397 | "zh-Hans" : { 398 | "stringUnit" : { 399 | "state" : "translated", 400 | "value" : "隔空投送" 401 | } 402 | }, 403 | "zh-Hant" : { 404 | "stringUnit" : { 405 | "state" : "translated", 406 | "value" : "AirDrop" 407 | } 408 | } 409 | } 410 | }, 411 | "AirDrop service not available" : { 412 | "extractionState" : "manual", 413 | "localizations" : { 414 | "de" : { 415 | "stringUnit" : { 416 | "state" : "translated", 417 | "value" : "AirDrop nicht verfügbar" 418 | } 419 | }, 420 | "en" : { 421 | "stringUnit" : { 422 | "state" : "translated", 423 | "value" : "AirDrop service not available" 424 | } 425 | }, 426 | "fr" : { 427 | "stringUnit" : { 428 | "state" : "translated", 429 | "value" : "Le service Airdrop n'est pas disponible" 430 | } 431 | }, 432 | "ja" : { 433 | "stringUnit" : { 434 | "state" : "translated", 435 | "value" : "AirDropサービスは利用できません。" 436 | } 437 | }, 438 | "zh-Hans" : { 439 | "stringUnit" : { 440 | "state" : "translated", 441 | "value" : "隔空投送服务不可用,请稍后重试" 442 | } 443 | }, 444 | "zh-Hant" : { 445 | "stringUnit" : { 446 | "state" : "translated", 447 | "value" : "無法取用 AirDrop 服務,請稍後重試" 448 | } 449 | } 450 | } 451 | }, 452 | "an hour" : { 453 | "localizations" : { 454 | "de" : { 455 | "stringUnit" : { 456 | "state" : "translated", 457 | "value" : "eine Stunde" 458 | } 459 | }, 460 | "en" : { 461 | "stringUnit" : { 462 | "state" : "translated", 463 | "value" : "an hour" 464 | } 465 | }, 466 | "fr" : { 467 | "stringUnit" : { 468 | "state" : "translated", 469 | "value" : "une heure" 470 | } 471 | }, 472 | "ja" : { 473 | "stringUnit" : { 474 | "state" : "translated", 475 | "value" : "1時間" 476 | } 477 | }, 478 | "zh-Hans" : { 479 | "stringUnit" : { 480 | "state" : "translated", 481 | "value" : "一小时" 482 | } 483 | }, 484 | "zh-Hant" : { 485 | "stringUnit" : { 486 | "state" : "translated", 487 | "value" : "一小時" 488 | } 489 | } 490 | } 491 | }, 492 | "Clear" : { 493 | "localizations" : { 494 | "de" : { 495 | "stringUnit" : { 496 | "state" : "translated", 497 | "value" : "Löschen" 498 | } 499 | }, 500 | "en" : { 501 | "stringUnit" : { 502 | "state" : "translated", 503 | "value" : "Clear" 504 | } 505 | }, 506 | "fr" : { 507 | "stringUnit" : { 508 | "state" : "translated", 509 | "value" : "Vider" 510 | } 511 | }, 512 | "ja" : { 513 | "stringUnit" : { 514 | "state" : "translated", 515 | "value" : "消去" 516 | } 517 | }, 518 | "zh-Hans" : { 519 | "stringUnit" : { 520 | "state" : "translated", 521 | "value" : "清空" 522 | } 523 | }, 524 | "zh-Hant" : { 525 | "stringUnit" : { 526 | "state" : "translated", 527 | "value" : "清空" 528 | } 529 | } 530 | } 531 | }, 532 | "Custom" : { 533 | "extractionState" : "manual", 534 | "localizations" : { 535 | "de" : { 536 | "stringUnit" : { 537 | "state" : "translated", 538 | "value" : "Eigene" 539 | } 540 | }, 541 | "en" : { 542 | "stringUnit" : { 543 | "state" : "translated", 544 | "value" : "Custom" 545 | } 546 | }, 547 | "fr" : { 548 | "stringUnit" : { 549 | "state" : "translated", 550 | "value" : "Personnalisé" 551 | } 552 | }, 553 | "ja" : { 554 | "stringUnit" : { 555 | "state" : "translated", 556 | "value" : "カスタム" 557 | } 558 | }, 559 | "zh-Hans" : { 560 | "stringUnit" : { 561 | "state" : "translated", 562 | "value" : "自定义" 563 | } 564 | }, 565 | "zh-Hant" : { 566 | "stringUnit" : { 567 | "state" : "translated", 568 | "value" : "自訂" 569 | } 570 | } 571 | } 572 | }, 573 | "Days" : { 574 | "localizations" : { 575 | "de" : { 576 | "stringUnit" : { 577 | "state" : "translated", 578 | "value" : "Tage" 579 | } 580 | }, 581 | "en" : { 582 | "stringUnit" : { 583 | "state" : "translated", 584 | "value" : "Days" 585 | } 586 | }, 587 | "fr" : { 588 | "stringUnit" : { 589 | "state" : "translated", 590 | "value" : "Jours" 591 | } 592 | }, 593 | "ja" : { 594 | "stringUnit" : { 595 | "state" : "translated", 596 | "value" : "日間" 597 | } 598 | }, 599 | "zh-Hans" : { 600 | "stringUnit" : { 601 | "state" : "translated", 602 | "value" : "天" 603 | } 604 | }, 605 | "zh-Hant" : { 606 | "stringUnit" : { 607 | "state" : "translated", 608 | "value" : "天" 609 | } 610 | } 611 | } 612 | }, 613 | "Drag files here to keep them for" : { 614 | "extractionState" : "manual", 615 | "localizations" : { 616 | "de" : { 617 | "stringUnit" : { 618 | "state" : "translated", 619 | "value" : "Ziehe Dateien hier her, um sie zu behalten für" 620 | } 621 | }, 622 | "en" : { 623 | "stringUnit" : { 624 | "state" : "translated", 625 | "value" : "Drag files here to keep them for" 626 | } 627 | }, 628 | "fr" : { 629 | "stringUnit" : { 630 | "state" : "translated", 631 | "value" : "Glisser vos fichier ici pour les garder pendant" 632 | } 633 | }, 634 | "ja" : { 635 | "stringUnit" : { 636 | "state" : "translated", 637 | "value" : "ファイルをここにドラッグして保存します" 638 | } 639 | }, 640 | "zh-Hans" : { 641 | "stringUnit" : { 642 | "state" : "translated", 643 | "value" : "拖动文件到此处以保留" 644 | } 645 | }, 646 | "zh-Hant" : { 647 | "stringUnit" : { 648 | "state" : "translated", 649 | "value" : "將檔案拖到這裡以保存" 650 | } 651 | } 652 | } 653 | }, 654 | "Drag files here to keep them for %@" : { 655 | "localizations" : { 656 | "de" : { 657 | "stringUnit" : { 658 | "state" : "translated", 659 | "value" : "Ziehe Dateien hier her, um sie zu behalten für %@" 660 | } 661 | }, 662 | "en" : { 663 | "stringUnit" : { 664 | "state" : "translated", 665 | "value" : "Drag files here to keep them for %@" 666 | } 667 | }, 668 | "fr" : { 669 | "stringUnit" : { 670 | "state" : "translated", 671 | "value" : "Glisser vos fichier ici pour les garder pendant %@" 672 | } 673 | }, 674 | "ja" : { 675 | "stringUnit" : { 676 | "state" : "translated", 677 | "value" : "ファイルをここにドラッグすると、%@ 保存されます" 678 | } 679 | }, 680 | "zh-Hans" : { 681 | "stringUnit" : { 682 | "state" : "translated", 683 | "value" : "拖动文件到此处以保留 %@" 684 | } 685 | }, 686 | "zh-Hant" : { 687 | "stringUnit" : { 688 | "state" : "translated", 689 | "value" : "將檔案拖到這裡以保存 %@" 690 | } 691 | } 692 | } 693 | }, 694 | "English" : { 695 | "extractionState" : "manual", 696 | "localizations" : { 697 | "de" : { 698 | "stringUnit" : { 699 | "state" : "translated", 700 | "value" : "English" 701 | } 702 | }, 703 | "en" : { 704 | "stringUnit" : { 705 | "state" : "translated", 706 | "value" : "English" 707 | } 708 | }, 709 | "fr" : { 710 | "stringUnit" : { 711 | "state" : "translated", 712 | "value" : "Anglais" 713 | } 714 | }, 715 | "ja" : { 716 | "stringUnit" : { 717 | "state" : "translated", 718 | "value" : "英語" 719 | } 720 | }, 721 | "zh-Hans" : { 722 | "stringUnit" : { 723 | "state" : "translated", 724 | "value" : "英文" 725 | } 726 | }, 727 | "zh-Hant" : { 728 | "stringUnit" : { 729 | "state" : "translated", 730 | "value" : "英文" 731 | } 732 | } 733 | } 734 | }, 735 | "Error" : { 736 | "localizations" : { 737 | "de" : { 738 | "stringUnit" : { 739 | "state" : "translated", 740 | "value" : "Error" 741 | } 742 | }, 743 | "en" : { 744 | "stringUnit" : { 745 | "state" : "translated", 746 | "value" : "Error" 747 | } 748 | }, 749 | "fr" : { 750 | "stringUnit" : { 751 | "state" : "translated", 752 | "value" : "Erreur" 753 | } 754 | }, 755 | "ja" : { 756 | "stringUnit" : { 757 | "state" : "translated", 758 | "value" : "エラー" 759 | } 760 | }, 761 | "zh-Hans" : { 762 | "stringUnit" : { 763 | "state" : "translated", 764 | "value" : "错误" 765 | } 766 | }, 767 | "zh-Hant" : { 768 | "stringUnit" : { 769 | "state" : "translated", 770 | "value" : "錯誤" 771 | } 772 | } 773 | } 774 | }, 775 | "Exit" : { 776 | "localizations" : { 777 | "de" : { 778 | "stringUnit" : { 779 | "state" : "translated", 780 | "value" : "Beenden" 781 | } 782 | }, 783 | "en" : { 784 | "stringUnit" : { 785 | "state" : "translated", 786 | "value" : "Exit" 787 | } 788 | }, 789 | "fr" : { 790 | "stringUnit" : { 791 | "state" : "translated", 792 | "value" : "Quitter" 793 | } 794 | }, 795 | "ja" : { 796 | "stringUnit" : { 797 | "state" : "translated", 798 | "value" : "終了" 799 | } 800 | }, 801 | "zh-Hans" : { 802 | "stringUnit" : { 803 | "state" : "translated", 804 | "value" : "退出" 805 | } 806 | }, 807 | "zh-Hant" : { 808 | "stringUnit" : { 809 | "state" : "translated", 810 | "value" : "退出" 811 | } 812 | } 813 | } 814 | }, 815 | "File Storage Time: " : { 816 | "localizations" : { 817 | "de" : { 818 | "stringUnit" : { 819 | "state" : "translated", 820 | "value" : "Dateispeicherdauer:" 821 | } 822 | }, 823 | "en" : { 824 | "stringUnit" : { 825 | "state" : "translated", 826 | "value" : "File Storage Time: " 827 | } 828 | }, 829 | "fr" : { 830 | "stringUnit" : { 831 | "state" : "translated", 832 | "value" : "Durée de stockage de fichier :" 833 | } 834 | }, 835 | "ja" : { 836 | "stringUnit" : { 837 | "state" : "translated", 838 | "value" : "ファイル保存時間:" 839 | } 840 | }, 841 | "zh-Hans" : { 842 | "stringUnit" : { 843 | "state" : "translated", 844 | "value" : "文件保存时间:" 845 | } 846 | }, 847 | "zh-Hant" : { 848 | "stringUnit" : { 849 | "state" : "translated", 850 | "value" : "檔案儲存時間:" 851 | } 852 | } 853 | } 854 | }, 855 | "Follow System" : { 856 | "extractionState" : "manual", 857 | "localizations" : { 858 | "de" : { 859 | "stringUnit" : { 860 | "state" : "translated", 861 | "value" : "Systemeinstellung" 862 | } 863 | }, 864 | "en" : { 865 | "stringUnit" : { 866 | "state" : "translated", 867 | "value" : "Follow System" 868 | } 869 | }, 870 | "fr" : { 871 | "stringUnit" : { 872 | "state" : "translated", 873 | "value" : "Système" 874 | } 875 | }, 876 | "ja" : { 877 | "stringUnit" : { 878 | "state" : "translated", 879 | "value" : "フォローシステム" 880 | } 881 | }, 882 | "zh-Hans" : { 883 | "stringUnit" : { 884 | "state" : "translated", 885 | "value" : "跟随系统" 886 | } 887 | }, 888 | "zh-Hant" : { 889 | "stringUnit" : { 890 | "state" : "translated", 891 | "value" : "跟隨系統" 892 | } 893 | } 894 | } 895 | }, 896 | "forever" : { 897 | "localizations" : { 898 | "de" : { 899 | "stringUnit" : { 900 | "state" : "translated", 901 | "value" : "für immer" 902 | } 903 | }, 904 | "en" : { 905 | "stringUnit" : { 906 | "state" : "translated", 907 | "value" : "forever" 908 | } 909 | }, 910 | "fr" : { 911 | "stringUnit" : { 912 | "state" : "translated", 913 | "value" : "pour toujours" 914 | } 915 | }, 916 | "ja" : { 917 | "stringUnit" : { 918 | "state" : "translated", 919 | "value" : "永遠に" 920 | } 921 | }, 922 | "zh-Hans" : { 923 | "stringUnit" : { 924 | "state" : "translated", 925 | "value" : "至永远" 926 | } 927 | }, 928 | "zh-Hant" : { 929 | "stringUnit" : { 930 | "state" : "translated", 931 | "value" : "永遠" 932 | } 933 | } 934 | } 935 | }, 936 | "Forever" : { 937 | "extractionState" : "manual", 938 | "localizations" : { 939 | "de" : { 940 | "stringUnit" : { 941 | "state" : "translated", 942 | "value" : "Für immer" 943 | } 944 | }, 945 | "en" : { 946 | "stringUnit" : { 947 | "state" : "translated", 948 | "value" : "Forever" 949 | } 950 | }, 951 | "fr" : { 952 | "stringUnit" : { 953 | "state" : "translated", 954 | "value" : "Pour toujours" 955 | } 956 | }, 957 | "ja" : { 958 | "stringUnit" : { 959 | "state" : "translated", 960 | "value" : "永遠に" 961 | } 962 | }, 963 | "zh-Hans" : { 964 | "stringUnit" : { 965 | "state" : "translated", 966 | "value" : "永远" 967 | } 968 | }, 969 | "zh-Hant" : { 970 | "stringUnit" : { 971 | "state" : "translated", 972 | "value" : "永遠" 973 | } 974 | } 975 | } 976 | }, 977 | "French" : { 978 | "extractionState" : "manual", 979 | "localizations" : { 980 | "de" : { 981 | "stringUnit" : { 982 | "state" : "translated", 983 | "value" : "Französisch" 984 | } 985 | }, 986 | "en" : { 987 | "stringUnit" : { 988 | "state" : "translated", 989 | "value" : "French" 990 | } 991 | }, 992 | "fr" : { 993 | "stringUnit" : { 994 | "state" : "translated", 995 | "value" : "Français" 996 | } 997 | }, 998 | "ja" : { 999 | "stringUnit" : { 1000 | "state" : "translated", 1001 | "value" : "フランス語" 1002 | } 1003 | }, 1004 | "zh-Hans" : { 1005 | "stringUnit" : { 1006 | "state" : "translated", 1007 | "value" : "法语" 1008 | } 1009 | }, 1010 | "zh-Hant" : { 1011 | "stringUnit" : { 1012 | "state" : "translated", 1013 | "value" : "法語" 1014 | } 1015 | } 1016 | } 1017 | }, 1018 | "German" : { 1019 | "extractionState" : "manual", 1020 | "localizations" : { 1021 | "de" : { 1022 | "stringUnit" : { 1023 | "state" : "translated", 1024 | "value" : "Deutsch" 1025 | } 1026 | }, 1027 | "en" : { 1028 | "stringUnit" : { 1029 | "state" : "translated", 1030 | "value" : "German" 1031 | } 1032 | }, 1033 | "fr" : { 1034 | "stringUnit" : { 1035 | "state" : "translated", 1036 | "value" : "Allemand" 1037 | } 1038 | }, 1039 | "ja" : { 1040 | "stringUnit" : { 1041 | "state" : "translated", 1042 | "value" : "ドイツ語" 1043 | } 1044 | }, 1045 | "zh-Hans" : { 1046 | "stringUnit" : { 1047 | "state" : "translated", 1048 | "value" : "德语" 1049 | } 1050 | }, 1051 | "zh-Hant" : { 1052 | "stringUnit" : { 1053 | "state" : "translated", 1054 | "value" : "德文" 1055 | } 1056 | } 1057 | } 1058 | }, 1059 | "GitHub" : { 1060 | "extractionState" : "stale", 1061 | "localizations" : { 1062 | "de" : { 1063 | "stringUnit" : { 1064 | "state" : "translated", 1065 | "value" : "GitHub" 1066 | } 1067 | }, 1068 | "en" : { 1069 | "stringUnit" : { 1070 | "state" : "translated", 1071 | "value" : "GitHub" 1072 | } 1073 | }, 1074 | "fr" : { 1075 | "stringUnit" : { 1076 | "state" : "translated", 1077 | "value" : "Github" 1078 | } 1079 | }, 1080 | "ja" : { 1081 | "stringUnit" : { 1082 | "state" : "translated", 1083 | "value" : "GitHub" 1084 | } 1085 | }, 1086 | "zh-Hans" : { 1087 | "stringUnit" : { 1088 | "state" : "translated", 1089 | "value" : "GitHub" 1090 | } 1091 | }, 1092 | "zh-Hant" : { 1093 | "stringUnit" : { 1094 | "state" : "translated", 1095 | "value" : "GitHub" 1096 | } 1097 | } 1098 | } 1099 | }, 1100 | "Haptic Feedback " : { 1101 | "localizations" : { 1102 | "de" : { 1103 | "stringUnit" : { 1104 | "state" : "translated", 1105 | "value" : "Haptisches Feedback" 1106 | } 1107 | }, 1108 | "en" : { 1109 | "stringUnit" : { 1110 | "state" : "translated", 1111 | "value" : "Haptic Feedback" 1112 | } 1113 | }, 1114 | "fr" : { 1115 | "stringUnit" : { 1116 | "state" : "translated", 1117 | "value" : "Retour haptique" 1118 | } 1119 | }, 1120 | "ja" : { 1121 | "stringUnit" : { 1122 | "state" : "translated", 1123 | "value" : "振動フィードバック" 1124 | } 1125 | }, 1126 | "zh-Hans" : { 1127 | "stringUnit" : { 1128 | "state" : "translated", 1129 | "value" : "触觉反馈" 1130 | } 1131 | }, 1132 | "zh-Hant" : { 1133 | "stringUnit" : { 1134 | "state" : "translated", 1135 | "value" : "觸覺回饋" 1136 | } 1137 | } 1138 | } 1139 | }, 1140 | "Hours" : { 1141 | "extractionState" : "manual", 1142 | "localizations" : { 1143 | "de" : { 1144 | "stringUnit" : { 1145 | "state" : "translated", 1146 | "value" : "Stunden" 1147 | } 1148 | }, 1149 | "en" : { 1150 | "stringUnit" : { 1151 | "state" : "translated", 1152 | "value" : "Hours" 1153 | } 1154 | }, 1155 | "fr" : { 1156 | "stringUnit" : { 1157 | "state" : "translated", 1158 | "value" : "Heures" 1159 | } 1160 | }, 1161 | "ja" : { 1162 | "stringUnit" : { 1163 | "state" : "translated", 1164 | "value" : "時間" 1165 | } 1166 | }, 1167 | "zh-Hans" : { 1168 | "stringUnit" : { 1169 | "state" : "translated", 1170 | "value" : "小时" 1171 | } 1172 | }, 1173 | "zh-Hant" : { 1174 | "stringUnit" : { 1175 | "state" : "translated", 1176 | "value" : "小時" 1177 | } 1178 | } 1179 | } 1180 | }, 1181 | "Japanese" : { 1182 | "extractionState" : "manual", 1183 | "localizations" : { 1184 | "de" : { 1185 | "stringUnit" : { 1186 | "state" : "translated", 1187 | "value" : "Japanisch" 1188 | } 1189 | }, 1190 | "en" : { 1191 | "stringUnit" : { 1192 | "state" : "translated", 1193 | "value" : "Japanese" 1194 | } 1195 | }, 1196 | "fr" : { 1197 | "stringUnit" : { 1198 | "state" : "translated", 1199 | "value" : "Japonais" 1200 | } 1201 | }, 1202 | "ja" : { 1203 | "stringUnit" : { 1204 | "state" : "translated", 1205 | "value" : "日本語" 1206 | } 1207 | }, 1208 | "zh-Hans" : { 1209 | "stringUnit" : { 1210 | "state" : "translated", 1211 | "value" : "日语" 1212 | } 1213 | }, 1214 | "zh-Hant" : { 1215 | "stringUnit" : { 1216 | "state" : "translated", 1217 | "value" : "日文" 1218 | } 1219 | } 1220 | } 1221 | }, 1222 | "Language: " : { 1223 | "localizations" : { 1224 | "de" : { 1225 | "stringUnit" : { 1226 | "state" : "translated", 1227 | "value" : "Sprache" 1228 | } 1229 | }, 1230 | "en" : { 1231 | "stringUnit" : { 1232 | "state" : "translated", 1233 | "value" : "Language: " 1234 | } 1235 | }, 1236 | "fr" : { 1237 | "stringUnit" : { 1238 | "state" : "translated", 1239 | "value" : "Langue :" 1240 | } 1241 | }, 1242 | "ja" : { 1243 | "stringUnit" : { 1244 | "state" : "translated", 1245 | "value" : "言語:" 1246 | } 1247 | }, 1248 | "zh-Hans" : { 1249 | "stringUnit" : { 1250 | "state" : "translated", 1251 | "value" : "语言:" 1252 | } 1253 | }, 1254 | "zh-Hant" : { 1255 | "stringUnit" : { 1256 | "state" : "translated", 1257 | "value" : "語言:" 1258 | } 1259 | } 1260 | } 1261 | }, 1262 | "Later" : { 1263 | "extractionState" : "manual", 1264 | "localizations" : { 1265 | "de" : { 1266 | "stringUnit" : { 1267 | "state" : "translated", 1268 | "value" : "Später" 1269 | } 1270 | }, 1271 | "en" : { 1272 | "stringUnit" : { 1273 | "state" : "translated", 1274 | "value" : "Later" 1275 | } 1276 | }, 1277 | "fr" : { 1278 | "stringUnit" : { 1279 | "state" : "translated", 1280 | "value" : "Plus tard" 1281 | } 1282 | }, 1283 | "ja" : { 1284 | "stringUnit" : { 1285 | "state" : "translated", 1286 | "value" : "後で" 1287 | } 1288 | }, 1289 | "zh-Hans" : { 1290 | "stringUnit" : { 1291 | "state" : "translated", 1292 | "value" : "稍后" 1293 | } 1294 | }, 1295 | "zh-Hant" : { 1296 | "stringUnit" : { 1297 | "state" : "translated", 1298 | "value" : "稍後" 1299 | } 1300 | } 1301 | } 1302 | }, 1303 | "Launch at Login" : { 1304 | "extractionState" : "manual", 1305 | "localizations" : { 1306 | "de" : { 1307 | "stringUnit" : { 1308 | "state" : "translated", 1309 | "value" : "Beim Anmelden starten" 1310 | } 1311 | }, 1312 | "en" : { 1313 | "stringUnit" : { 1314 | "state" : "translated", 1315 | "value" : "Launch at Login" 1316 | } 1317 | }, 1318 | "fr" : { 1319 | "stringUnit" : { 1320 | "state" : "translated", 1321 | "value" : "Lancer au démarrage" 1322 | } 1323 | }, 1324 | "ja" : { 1325 | "stringUnit" : { 1326 | "state" : "translated", 1327 | "value" : "ログイン時に起動" 1328 | } 1329 | }, 1330 | "zh-Hans" : { 1331 | "stringUnit" : { 1332 | "state" : "translated", 1333 | "value" : "登录时启动" 1334 | } 1335 | }, 1336 | "zh-Hant" : { 1337 | "stringUnit" : { 1338 | "state" : "translated", 1339 | "value" : "登入時啟動" 1340 | } 1341 | } 1342 | } 1343 | }, 1344 | "Love Drop" : { 1345 | "extractionState" : "manual", 1346 | "localizations" : { 1347 | "de" : { 1348 | "stringUnit" : { 1349 | "state" : "translated", 1350 | "value" : "Love Drop" 1351 | } 1352 | }, 1353 | "en" : { 1354 | "stringUnit" : { 1355 | "state" : "translated", 1356 | "value" : "Love Drop" 1357 | } 1358 | }, 1359 | "fr" : { 1360 | "stringUnit" : { 1361 | "state" : "translated", 1362 | "value" : "Soutenir" 1363 | } 1364 | }, 1365 | "ja" : { 1366 | "stringUnit" : { 1367 | "state" : "translated", 1368 | "value" : "Love Drop" 1369 | } 1370 | }, 1371 | "zh-Hans" : { 1372 | "stringUnit" : { 1373 | "state" : "translated", 1374 | "value" : "喜爱" 1375 | } 1376 | }, 1377 | "zh-Hant" : { 1378 | "stringUnit" : { 1379 | "state" : "translated", 1380 | "value" : "喜愛" 1381 | } 1382 | } 1383 | } 1384 | }, 1385 | "Months" : { 1386 | "extractionState" : "manual", 1387 | "localizations" : { 1388 | "de" : { 1389 | "stringUnit" : { 1390 | "state" : "translated", 1391 | "value" : "Monate" 1392 | } 1393 | }, 1394 | "en" : { 1395 | "stringUnit" : { 1396 | "state" : "translated", 1397 | "value" : "Months" 1398 | } 1399 | }, 1400 | "fr" : { 1401 | "stringUnit" : { 1402 | "state" : "translated", 1403 | "value" : "Mois" 1404 | } 1405 | }, 1406 | "ja" : { 1407 | "stringUnit" : { 1408 | "state" : "translated", 1409 | "value" : "月間" 1410 | } 1411 | }, 1412 | "zh-Hans" : { 1413 | "stringUnit" : { 1414 | "state" : "translated", 1415 | "value" : "月" 1416 | } 1417 | }, 1418 | "zh-Hant" : { 1419 | "stringUnit" : { 1420 | "state" : "translated", 1421 | "value" : "月" 1422 | } 1423 | } 1424 | } 1425 | }, 1426 | "Need Restart" : { 1427 | "extractionState" : "manual", 1428 | "localizations" : { 1429 | "de" : { 1430 | "stringUnit" : { 1431 | "state" : "translated", 1432 | "value" : "Benötigt einen Neustart" 1433 | } 1434 | }, 1435 | "en" : { 1436 | "stringUnit" : { 1437 | "state" : "translated", 1438 | "value" : "Need Restart" 1439 | } 1440 | }, 1441 | "fr" : { 1442 | "stringUnit" : { 1443 | "state" : "translated", 1444 | "value" : "Besoin de redémarrer" 1445 | } 1446 | }, 1447 | "ja" : { 1448 | "stringUnit" : { 1449 | "state" : "translated", 1450 | "value" : "再起動が必要です" 1451 | } 1452 | }, 1453 | "zh-Hans" : { 1454 | "stringUnit" : { 1455 | "state" : "translated", 1456 | "value" : "需要重新启动" 1457 | } 1458 | }, 1459 | "zh-Hant" : { 1460 | "stringUnit" : { 1461 | "state" : "translated", 1462 | "value" : "需要重新啟動" 1463 | } 1464 | } 1465 | } 1466 | }, 1467 | "Notch Drop" : { 1468 | "localizations" : { 1469 | "de" : { 1470 | "stringUnit" : { 1471 | "state" : "translated", 1472 | "value" : "Notch Drop" 1473 | } 1474 | }, 1475 | "en" : { 1476 | "stringUnit" : { 1477 | "state" : "translated", 1478 | "value" : "Notch Drop" 1479 | } 1480 | }, 1481 | "fr" : { 1482 | "stringUnit" : { 1483 | "state" : "translated", 1484 | "value" : "Notch Drop" 1485 | } 1486 | }, 1487 | "ja" : { 1488 | "stringUnit" : { 1489 | "state" : "translated", 1490 | "value" : "Notch Drop" 1491 | } 1492 | }, 1493 | "zh-Hans" : { 1494 | "stringUnit" : { 1495 | "state" : "translated", 1496 | "value" : "简放岛" 1497 | } 1498 | }, 1499 | "zh-Hant" : { 1500 | "stringUnit" : { 1501 | "state" : "translated", 1502 | "value" : "簡放島" 1503 | } 1504 | } 1505 | } 1506 | }, 1507 | "OK" : { 1508 | "localizations" : { 1509 | "de" : { 1510 | "stringUnit" : { 1511 | "state" : "translated", 1512 | "value" : "Okay" 1513 | } 1514 | }, 1515 | "en" : { 1516 | "stringUnit" : { 1517 | "state" : "translated", 1518 | "value" : "OK" 1519 | } 1520 | }, 1521 | "fr" : { 1522 | "stringUnit" : { 1523 | "state" : "translated", 1524 | "value" : "OK" 1525 | } 1526 | }, 1527 | "ja" : { 1528 | "stringUnit" : { 1529 | "state" : "translated", 1530 | "value" : "はい" 1531 | } 1532 | }, 1533 | "zh-Hans" : { 1534 | "stringUnit" : { 1535 | "state" : "translated", 1536 | "value" : "好" 1537 | } 1538 | }, 1539 | "zh-Hant" : { 1540 | "stringUnit" : { 1541 | "state" : "translated", 1542 | "value" : "好" 1543 | } 1544 | } 1545 | } 1546 | }, 1547 | "One or more files failed to load" : { 1548 | "localizations" : { 1549 | "de" : { 1550 | "stringUnit" : { 1551 | "state" : "translated", 1552 | "value" : "Das Laden einer oder mehrerer Dateien ist fehlgeschlagen" 1553 | } 1554 | }, 1555 | "en" : { 1556 | "stringUnit" : { 1557 | "state" : "translated", 1558 | "value" : "One or more files failed to load" 1559 | } 1560 | }, 1561 | "fr" : { 1562 | "stringUnit" : { 1563 | "state" : "translated", 1564 | "value" : "Un ou plusieurs fichier n'ont pas été chargés" 1565 | } 1566 | }, 1567 | "ja" : { 1568 | "stringUnit" : { 1569 | "state" : "translated", 1570 | "value" : "1つ以上のファイルの読み込みに失敗しました。" 1571 | } 1572 | }, 1573 | "zh-Hans" : { 1574 | "stringUnit" : { 1575 | "state" : "translated", 1576 | "value" : "一个或多个文件加载失败" 1577 | } 1578 | }, 1579 | "zh-Hant" : { 1580 | "stringUnit" : { 1581 | "state" : "translated", 1582 | "value" : "一個或多個檔案無法載入" 1583 | } 1584 | } 1585 | } 1586 | }, 1587 | "Press Option to delete" : { 1588 | "localizations" : { 1589 | "de" : { 1590 | "stringUnit" : { 1591 | "state" : "translated", 1592 | "value" : "Drücke ⌥ zum Löschen" 1593 | } 1594 | }, 1595 | "en" : { 1596 | "stringUnit" : { 1597 | "state" : "translated", 1598 | "value" : "Press ⌥ to delete" 1599 | } 1600 | }, 1601 | "fr" : { 1602 | "stringUnit" : { 1603 | "state" : "needs_review", 1604 | "value" : "Appuyer sur ••• puis \"Vider\" pour les supprimer" 1605 | } 1606 | }, 1607 | "ja" : { 1608 | "stringUnit" : { 1609 | "state" : "translated", 1610 | "value" : "⌥を押すと削除できます" 1611 | } 1612 | }, 1613 | "zh-Hans" : { 1614 | "stringUnit" : { 1615 | "state" : "translated", 1616 | "value" : "按住⌥键删除" 1617 | } 1618 | }, 1619 | "zh-Hant" : { 1620 | "stringUnit" : { 1621 | "state" : "translated", 1622 | "value" : "按住⌥鍵刪除" 1623 | } 1624 | } 1625 | } 1626 | }, 1627 | "Selected sharing service not available" : { 1628 | "localizations" : { 1629 | "ja" : { 1630 | "stringUnit" : { 1631 | "state" : "translated", 1632 | "value" : "選択した共有サービスは利用できません" 1633 | } 1634 | }, 1635 | "zh-Hans" : { 1636 | "stringUnit" : { 1637 | "state" : "translated", 1638 | "value" : "选择的分享服务不可用" 1639 | } 1640 | }, 1641 | "zh-Hant" : { 1642 | "stringUnit" : { 1643 | "state" : "translated", 1644 | "value" : "所選共享服務不可用" 1645 | } 1646 | } 1647 | } 1648 | }, 1649 | "Settings" : { 1650 | "localizations" : { 1651 | "de" : { 1652 | "stringUnit" : { 1653 | "state" : "translated", 1654 | "value" : "Einstellungen" 1655 | } 1656 | }, 1657 | "en" : { 1658 | "stringUnit" : { 1659 | "state" : "translated", 1660 | "value" : "Settings" 1661 | } 1662 | }, 1663 | "fr" : { 1664 | "stringUnit" : { 1665 | "state" : "translated", 1666 | "value" : "Paramètres" 1667 | } 1668 | }, 1669 | "ja" : { 1670 | "stringUnit" : { 1671 | "state" : "translated", 1672 | "value" : "設定" 1673 | } 1674 | }, 1675 | "zh-Hans" : { 1676 | "stringUnit" : { 1677 | "state" : "translated", 1678 | "value" : "设置" 1679 | } 1680 | }, 1681 | "zh-Hant" : { 1682 | "stringUnit" : { 1683 | "state" : "translated", 1684 | "value" : "設定" 1685 | } 1686 | } 1687 | } 1688 | }, 1689 | "Share" : { 1690 | "extractionState" : "manual", 1691 | "localizations" : { 1692 | "de" : { 1693 | "stringUnit" : { 1694 | "state" : "translated", 1695 | "value" : "Teilen" 1696 | } 1697 | }, 1698 | "fr" : { 1699 | "stringUnit" : { 1700 | "state" : "translated", 1701 | "value" : "Partager" 1702 | } 1703 | }, 1704 | "ja" : { 1705 | "stringUnit" : { 1706 | "state" : "translated", 1707 | "value" : "共有" 1708 | } 1709 | }, 1710 | "zh-Hans" : { 1711 | "stringUnit" : { 1712 | "state" : "translated", 1713 | "value" : "共享" 1714 | } 1715 | }, 1716 | "zh-Hant" : { 1717 | "stringUnit" : { 1718 | "state" : "translated", 1719 | "value" : "分享" 1720 | } 1721 | } 1722 | } 1723 | }, 1724 | "Sharing service cannot perform with given files" : { 1725 | "localizations" : { 1726 | "ja" : { 1727 | "stringUnit" : { 1728 | "state" : "translated", 1729 | "value" : "指定されたファイルでは共有サービスを実行できません" 1730 | } 1731 | }, 1732 | "zh-Hans" : { 1733 | "stringUnit" : { 1734 | "state" : "translated", 1735 | "value" : "分享服务无法处理指定的文件" 1736 | } 1737 | }, 1738 | "zh-Hant" : { 1739 | "stringUnit" : { 1740 | "state" : "translated", 1741 | "value" : "共享服務無法執行給定的檔案" 1742 | } 1743 | } 1744 | } 1745 | }, 1746 | "Simplified Chinese" : { 1747 | "extractionState" : "manual", 1748 | "localizations" : { 1749 | "de" : { 1750 | "stringUnit" : { 1751 | "state" : "translated", 1752 | "value" : "Vereinfachtes Chinesisch" 1753 | } 1754 | }, 1755 | "en" : { 1756 | "stringUnit" : { 1757 | "state" : "translated", 1758 | "value" : "Simplified Chinese" 1759 | } 1760 | }, 1761 | "fr" : { 1762 | "stringUnit" : { 1763 | "state" : "translated", 1764 | "value" : "Chinois Simplifié" 1765 | } 1766 | }, 1767 | "ja" : { 1768 | "stringUnit" : { 1769 | "state" : "translated", 1770 | "value" : "中国語(簡体字)" 1771 | } 1772 | }, 1773 | "zh-Hans" : { 1774 | "stringUnit" : { 1775 | "state" : "translated", 1776 | "value" : "简体中文" 1777 | } 1778 | }, 1779 | "zh-Hant" : { 1780 | "stringUnit" : { 1781 | "state" : "translated", 1782 | "value" : "簡體中文" 1783 | } 1784 | } 1785 | } 1786 | }, 1787 | "The language has been changed. The app will restart for the changes to take effect." : { 1788 | "extractionState" : "manual", 1789 | "localizations" : { 1790 | "de" : { 1791 | "stringUnit" : { 1792 | "state" : "translated", 1793 | "value" : "Die Sprache wurde geändert. Die App muss neu gestartet werden, um die Änderung zu übernehmen." 1794 | } 1795 | }, 1796 | "en" : { 1797 | "stringUnit" : { 1798 | "state" : "translated", 1799 | "value" : "The language has been changed. The app will restart for the changes to take effect." 1800 | } 1801 | }, 1802 | "fr" : { 1803 | "stringUnit" : { 1804 | "state" : "translated", 1805 | "value" : "La langue a été changé. L'application va redémarrer pour que les changements prennent effet." 1806 | } 1807 | }, 1808 | "ja" : { 1809 | "stringUnit" : { 1810 | "state" : "translated", 1811 | "value" : "言語が変更されました。変更を適用するため、アプリが終了して再起動します。" 1812 | } 1813 | }, 1814 | "zh-Hans" : { 1815 | "stringUnit" : { 1816 | "state" : "translated", 1817 | "value" : "语言设置已应用,请重新启动应用程序来完成更改。" 1818 | } 1819 | }, 1820 | "zh-Hant" : { 1821 | "stringUnit" : { 1822 | "state" : "translated", 1823 | "value" : "語言已更改。應用程式將重新啟動以使更改生效。" 1824 | } 1825 | } 1826 | } 1827 | }, 1828 | "three days" : { 1829 | "localizations" : { 1830 | "de" : { 1831 | "stringUnit" : { 1832 | "state" : "translated", 1833 | "value" : "drei Tage" 1834 | } 1835 | }, 1836 | "en" : { 1837 | "stringUnit" : { 1838 | "state" : "translated", 1839 | "value" : "three days" 1840 | } 1841 | }, 1842 | "fr" : { 1843 | "stringUnit" : { 1844 | "state" : "translated", 1845 | "value" : "trois jours" 1846 | } 1847 | }, 1848 | "ja" : { 1849 | "stringUnit" : { 1850 | "state" : "translated", 1851 | "value" : "三日間" 1852 | } 1853 | }, 1854 | "zh-Hans" : { 1855 | "stringUnit" : { 1856 | "state" : "translated", 1857 | "value" : "三天" 1858 | } 1859 | }, 1860 | "zh-Hant" : { 1861 | "stringUnit" : { 1862 | "state" : "translated", 1863 | "value" : "三天" 1864 | } 1865 | } 1866 | } 1867 | }, 1868 | "Time Unit" : { 1869 | "localizations" : { 1870 | "de" : { 1871 | "stringUnit" : { 1872 | "state" : "translated", 1873 | "value" : "Zeiteinheit" 1874 | } 1875 | }, 1876 | "en" : { 1877 | "stringUnit" : { 1878 | "state" : "translated", 1879 | "value" : "Time Unit" 1880 | } 1881 | }, 1882 | "fr" : { 1883 | "stringUnit" : { 1884 | "state" : "translated", 1885 | "value" : "Unité de temps" 1886 | } 1887 | }, 1888 | "ja" : { 1889 | "stringUnit" : { 1890 | "state" : "translated", 1891 | "value" : "時間単位" 1892 | } 1893 | }, 1894 | "zh-Hans" : { 1895 | "stringUnit" : { 1896 | "state" : "translated", 1897 | "value" : "时间单位" 1898 | } 1899 | }, 1900 | "zh-Hant" : { 1901 | "stringUnit" : { 1902 | "state" : "translated", 1903 | "value" : "時間單位" 1904 | } 1905 | } 1906 | } 1907 | }, 1908 | "Traditional Chinese" : { 1909 | "extractionState" : "manual", 1910 | "localizations" : { 1911 | "de" : { 1912 | "stringUnit" : { 1913 | "state" : "translated", 1914 | "value" : "Traditionelles Chinesisch" 1915 | } 1916 | }, 1917 | "en" : { 1918 | "stringUnit" : { 1919 | "state" : "translated", 1920 | "value" : "Traditional Chinese" 1921 | } 1922 | }, 1923 | "fr" : { 1924 | "stringUnit" : { 1925 | "state" : "translated", 1926 | "value" : "Chinois Traditionnel" 1927 | } 1928 | }, 1929 | "ja" : { 1930 | "stringUnit" : { 1931 | "state" : "translated", 1932 | "value" : "中国語(繁体字)" 1933 | } 1934 | }, 1935 | "zh-Hans" : { 1936 | "stringUnit" : { 1937 | "state" : "translated", 1938 | "value" : "繁体中文" 1939 | } 1940 | }, 1941 | "zh-Hant" : { 1942 | "stringUnit" : { 1943 | "state" : "translated", 1944 | "value" : "繁體中文" 1945 | } 1946 | } 1947 | } 1948 | }, 1949 | "two days" : { 1950 | "localizations" : { 1951 | "de" : { 1952 | "stringUnit" : { 1953 | "state" : "translated", 1954 | "value" : "zwei Tage" 1955 | } 1956 | }, 1957 | "en" : { 1958 | "stringUnit" : { 1959 | "state" : "translated", 1960 | "value" : "two days" 1961 | } 1962 | }, 1963 | "fr" : { 1964 | "stringUnit" : { 1965 | "state" : "translated", 1966 | "value" : "deux jours" 1967 | } 1968 | }, 1969 | "ja" : { 1970 | "stringUnit" : { 1971 | "state" : "translated", 1972 | "value" : "二日間" 1973 | } 1974 | }, 1975 | "zh-Hans" : { 1976 | "stringUnit" : { 1977 | "state" : "translated", 1978 | "value" : "两天" 1979 | } 1980 | }, 1981 | "zh-Hant" : { 1982 | "stringUnit" : { 1983 | "state" : "translated", 1984 | "value" : "兩天" 1985 | } 1986 | } 1987 | } 1988 | }, 1989 | "Version: %@ (Build: %@)" : { 1990 | "localizations" : { 1991 | "de" : { 1992 | "stringUnit" : { 1993 | "state" : "translated", 1994 | "value" : "Version: %1$@ (Build: %2$@)" 1995 | } 1996 | }, 1997 | "en" : { 1998 | "stringUnit" : { 1999 | "state" : "new", 2000 | "value" : "Version: %1$@ (Build: %2$@)" 2001 | } 2002 | }, 2003 | "fr" : { 2004 | "stringUnit" : { 2005 | "state" : "translated", 2006 | "value" : "Version: %1$@ (Build: %2$@)" 2007 | } 2008 | }, 2009 | "ja" : { 2010 | "stringUnit" : { 2011 | "state" : "translated", 2012 | "value" : "バージョン: %1$@ (ビルド: %2$@)" 2013 | } 2014 | }, 2015 | "zh-Hans" : { 2016 | "stringUnit" : { 2017 | "state" : "translated", 2018 | "value" : "版本:%1$@ (构建:%2$@)" 2019 | } 2020 | }, 2021 | "zh-Hant" : { 2022 | "stringUnit" : { 2023 | "state" : "translated", 2024 | "value" : "版本:%1$@ (建置:%2$@)" 2025 | } 2026 | } 2027 | } 2028 | }, 2029 | "Weeks" : { 2030 | "extractionState" : "manual", 2031 | "localizations" : { 2032 | "de" : { 2033 | "stringUnit" : { 2034 | "state" : "translated", 2035 | "value" : "Wochen" 2036 | } 2037 | }, 2038 | "en" : { 2039 | "stringUnit" : { 2040 | "state" : "translated", 2041 | "value" : "Weeks" 2042 | } 2043 | }, 2044 | "fr" : { 2045 | "stringUnit" : { 2046 | "state" : "translated", 2047 | "value" : "Semaines" 2048 | } 2049 | }, 2050 | "ja" : { 2051 | "stringUnit" : { 2052 | "state" : "translated", 2053 | "value" : "週間" 2054 | } 2055 | }, 2056 | "zh-Hans" : { 2057 | "stringUnit" : { 2058 | "state" : "translated", 2059 | "value" : "周" 2060 | } 2061 | }, 2062 | "zh-Hant" : { 2063 | "stringUnit" : { 2064 | "state" : "translated", 2065 | "value" : "星期" 2066 | } 2067 | } 2068 | } 2069 | }, 2070 | "Years" : { 2071 | "extractionState" : "manual", 2072 | "localizations" : { 2073 | "de" : { 2074 | "stringUnit" : { 2075 | "state" : "translated", 2076 | "value" : "Jahre" 2077 | } 2078 | }, 2079 | "en" : { 2080 | "stringUnit" : { 2081 | "state" : "translated", 2082 | "value" : "Years" 2083 | } 2084 | }, 2085 | "fr" : { 2086 | "stringUnit" : { 2087 | "state" : "translated", 2088 | "value" : "Années" 2089 | } 2090 | }, 2091 | "ja" : { 2092 | "stringUnit" : { 2093 | "state" : "translated", 2094 | "value" : "年間" 2095 | } 2096 | }, 2097 | "zh-Hans" : { 2098 | "stringUnit" : { 2099 | "state" : "translated", 2100 | "value" : "年" 2101 | } 2102 | }, 2103 | "zh-Hant" : { 2104 | "stringUnit" : { 2105 | "state" : "translated", 2106 | "value" : "年" 2107 | } 2108 | } 2109 | } 2110 | } 2111 | }, 2112 | "version" : "1.0" 2113 | } --------------------------------------------------------------------------------