├── 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 | 
6 |
7 | ## 👀 预览
8 |
9 | 
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 | [](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 | 
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 |
33 |
--------------------------------------------------------------------------------
/Resources/Download_on_the_App_Store_Badge_US-UK_RGB_blk_092917.svg:
--------------------------------------------------------------------------------
1 |
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 | }
--------------------------------------------------------------------------------