├── docs ├── CNAME ├── Support │ └── appcast.xml └── index.html ├── .github ├── releaser │ ├── requirements.txt │ ├── sign_update │ ├── generate_appcast │ ├── export_options.plist │ ├── generate_html_for_sparkle_release.py │ ├── generate_latest_changes.py │ └── remove_last_item_appcast.py └── workflows │ └── release.yml ├── icon.png ├── Matchumbeop ├── AppIcon.icns ├── Matchumbeop │ ├── Assets.xcassets │ │ ├── Contents.json │ │ └── AccentColor.colorset │ │ │ └── Contents.json │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ ├── SpellCheckers │ │ ├── SpellChecker.swift │ │ └── NaverSpellChecker.swift │ ├── Constants.swift │ ├── Analytics │ │ ├── AnalyticsEvent.swift │ │ ├── Events │ │ │ └── AnalyticsEvents.swift │ │ ├── Analytics.swift │ │ └── AnalyticsEngine.swift │ ├── Matchumbeop.entitlements │ ├── SharedUIComponents │ │ ├── ToastView.swift │ │ ├── HintView.swift │ │ ├── SettingsDivider.swift │ │ ├── ProgressBar.swift │ │ └── DraggableTextView.swift │ ├── MatchumbeopApp.swift │ ├── Defaults.swift │ ├── GoogleService-Info.plist │ ├── Extensions │ │ ├── StringExtension.swift │ │ └── SpellCheckerExtension.swift │ ├── ServicesProvider.swift │ ├── UpdateChecker │ │ └── UpdateChecker.swift │ ├── Utils.swift │ ├── AppState.swift │ ├── AppDelegate.swift │ ├── SettingsView.swift │ └── ContentView.swift ├── Matchumbeop.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── swiftpm │ │ │ └── Package.resolved │ ├── xcshareddata │ │ └── xcschemes │ │ │ └── Matchumbeop.xcscheme │ └── project.pbxproj ├── Matchumbeop.xcworkspace │ ├── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ │ └── Package.resolved │ └── contents.xcworkspacedata ├── matchumbeop.xcworkspace │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── contents.xcworkspacedata ├── Podfile ├── MatchumbeopUITests │ ├── MachumbubUITestsLaunchTests.swift │ └── MachumbubUITests.swift ├── Info.plist ├── MatchumbeopTests │ └── machumbubTests.swift └── Podfile.lock ├── CHANGELOG.md ├── README.md ├── .gitignore └── LICENSE /docs/CNAME: -------------------------------------------------------------------------------- 1 | matchumbeop.ssut.me -------------------------------------------------------------------------------- /.github/releaser/requirements.txt: -------------------------------------------------------------------------------- 1 | markdown 2 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ssut/Matchumbeop/HEAD/icon.png -------------------------------------------------------------------------------- /Matchumbeop/AppIcon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ssut/Matchumbeop/HEAD/Matchumbeop/AppIcon.icns -------------------------------------------------------------------------------- /.github/releaser/sign_update: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ssut/Matchumbeop/HEAD/.github/releaser/sign_update -------------------------------------------------------------------------------- /.github/releaser/generate_appcast: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ssut/Matchumbeop/HEAD/.github/releaser/generate_appcast -------------------------------------------------------------------------------- /Matchumbeop/Matchumbeop/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Matchumbeop/Matchumbeop/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Matchumbeop/Matchumbeop/SpellCheckers/SpellChecker.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Defaults 3 | 4 | protocol SpellChecker { 5 | func correctText(_ text: String, eol: String) async throws -> SpellerApiResponse 6 | } 7 | 8 | -------------------------------------------------------------------------------- /Matchumbeop/Matchumbeop.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Matchumbeop/Matchumbeop/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Matchumbeop/Matchumbeop/Constants.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | let Green = Color(red: 0.13, green: 0.85, blue: 0.13) 4 | let Red = Color(red: 1.0, green: 0.45, blue: 0.45) 5 | let Violet = Color(red: 0.75, green: 0.33, blue: 0.99) 6 | let Blue = Color(red: 0.30, green: 0.70, blue: 1.0) 7 | -------------------------------------------------------------------------------- /Matchumbeop/Matchumbeop.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Matchumbeop/matchumbeop.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Matchumbeop/Matchumbeop.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Matchumbeop/Matchumbeop.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Matchumbeop/matchumbeop.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Matchumbeop/Podfile: -------------------------------------------------------------------------------- 1 | target 'Matchumbeop' do 2 | use_frameworks! 3 | 4 | pod 'Alamofire', '~> 5.4' 5 | pod 'SwiftSoup', '~> 2.3' 6 | pod 'FirebaseCore' 7 | pod 'FirebaseAnalytics' 8 | pod 'FirebaseCrashlytics' 9 | 10 | target 'MatchumbeopTests' do 11 | inherit! :search_paths 12 | end 13 | 14 | target 'MatchumbeopUITests' do 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /Matchumbeop/Matchumbeop/Analytics/AnalyticsEvent.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | class AnalyticsEvent { 4 | let name: String 5 | let parameters: [String: Any]? 6 | 7 | init(name: String, parameters: [String: Any]? = nil) { 8 | self.name = name 9 | self.parameters = parameters 10 | } 11 | } 12 | 13 | final class MatchumbeopAnalyticsEvent: AnalyticsEvent {} 14 | -------------------------------------------------------------------------------- /Matchumbeop/Matchumbeop/Matchumbeop.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.files.user-selected.read-only 6 | 7 | com.apple.security.network.client 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Matchumbeop/Matchumbeop/SharedUIComponents/ToastView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ToastView: View { 4 | let message: String 5 | 6 | var body: some View { 7 | Text(message) 8 | .foregroundColor(.white) 9 | .padding(.horizontal, 14) 10 | .padding(.vertical, 8) 11 | .background(Color.gray.opacity(0.9)) 12 | .cornerRadius(8) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.github/releaser/export_options.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | method 6 | developer-id 7 | signingCertificate 8 | Apple Development 9 | signingStyle 10 | automatic 11 | teamID 12 | N7V29V6Q33 13 | 14 | 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v1.1.0 - macOS Ventura (13.0+) 지원, 사용성 개선, macOS 서비스 메뉴 추가 2 | 3 | - macOS Ventura (13.0+)를 지원합니다. 4 | - 앱 실행 시 아무 동작이 없는 것처럼 보인다는 피드백을 반영하여 실행 직후 맞춤법 검사기 화면을 표시하도록 변경했습니다. 5 | - Dock에 앱을 두고 사용하는 경우를 고려해 Dock에 있는 앱을 클릭했을 때 맞춤법 검사기 화면이 표시되도록 변경했습니다. 6 | - 검사 진행 중 확인이 가능하도록 진행률을 표시하도록 추가했습니다. 7 | - 맥 내 서비스(Services) 메뉴에서도 맞춤법 검사를 할 수 있도록 추가했습니다. 단축키를 할당하면 아무 곳에서나 선택한 텍스트를 바로 검사할 수 있습니다. 방법: https://github.com/ssut/Matchumbeop/wiki/macOS-서비스-등록-방법 8 | 9 | ## v1.0 - 첫 릴리즈 10 | 11 | - 첫 릴리즈 12 | -------------------------------------------------------------------------------- /Matchumbeop/Matchumbeop/MatchumbeopApp.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | class TheUpdateCheckerDelegate: UpdateCheckerDelegate { 4 | func prepareForRelaunch(finish: @escaping () -> Void) { 5 | Task { 6 | finish() 7 | } 8 | } 9 | } 10 | 11 | let updateCheckerDelegate = TheUpdateCheckerDelegate() 12 | 13 | @main 14 | struct MatchumbeopApp: App { 15 | @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate 16 | 17 | var body: some Scene { 18 | Settings { 19 | SettingsView() 20 | .environmentObject(AppState.shared) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Matchumbeop/Matchumbeop/Defaults.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Defaults 3 | 4 | enum SpellCheckerEngine: String, Defaults.Serializable, CaseIterable, Identifiable, CustomStringConvertible { 5 | case naver 6 | 7 | var id: Self { self } 8 | 9 | var description: String { 10 | switch self { 11 | case .naver: 12 | return "네이버 (NAVER)" 13 | // case .kakao: 14 | // return "카카오 (Daum)" 15 | } 16 | } 17 | } 18 | 19 | extension Defaults.Keys { 20 | static let hasLaunchedOnce = Key("hasLaunchedOnce", default: false) 21 | static let spellCheckerEngine = Key("spellCheckerEngine", default: .naver) 22 | } 23 | -------------------------------------------------------------------------------- /.github/releaser/generate_html_for_sparkle_release.py: -------------------------------------------------------------------------------- 1 | # Takes as input two files: 2 | # 1. File "title" contains the title of the release 3 | # 2. File "latest_changes" contains the latest changes in the release in markdown format 4 | 5 | # The output is a file "Release/latest_changes.html" which contains the latest changes in html format 6 | 7 | import markdown 8 | 9 | def generate_html_for_latest_changes(): 10 | with open('title', 'r') as f: 11 | title = f.read() 12 | with open('latest_changes', 'r') as f: 13 | changes = f.read() 14 | 15 | text = '# ' + title + '\n\n' + changes 16 | html = markdown.markdown(text) 17 | 18 | with open('Release/latest_changes.html', 'w') as f: 19 | f.write(html) 20 | 21 | if __name__ == '__main__': 22 | generate_html_for_latest_changes() 23 | -------------------------------------------------------------------------------- /Matchumbeop/Matchumbeop/SharedUIComponents/HintView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct HintView: View { 4 | var body: some View { 5 | HStack(spacing: 10) { 6 | HintItem(color: Red, text: "맞춤법") 7 | HintItem(color: Green, text: "띄어쓰기") 8 | HintItem(color: Violet, text: "표준어 의심") 9 | HintItem(color: Blue, text: "통계적 교정") 10 | } 11 | } 12 | } 13 | 14 | struct HintItem: View { 15 | var color: Color 16 | var text: String 17 | 18 | var body: some View { 19 | HStack(spacing: 5) { 20 | Circle() 21 | .fill(color) 22 | .frame(width: 10, height: 10) 23 | 24 | Text(text) 25 | .foregroundColor(.gray) 26 | .font(.system(size: 12, weight: .thin)) 27 | } 28 | } 29 | } 30 | 31 | struct HintView_Previews: PreviewProvider { 32 | static var previews: some View { 33 | HintView() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Matchumbeop/MatchumbeopUITests/MachumbubUITestsLaunchTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // hanspellUITestsLaunchTests.swift 3 | // hanspellUITests 4 | // 5 | // Created by suhun han on 8/13/24. 6 | // 7 | 8 | import XCTest 9 | 10 | final class hanspellUITestsLaunchTests: XCTestCase { 11 | 12 | override class var runsForEachTargetApplicationUIConfiguration: Bool { 13 | true 14 | } 15 | 16 | override func setUpWithError() throws { 17 | continueAfterFailure = false 18 | } 19 | 20 | func testLaunch() throws { 21 | let app = XCUIApplication() 22 | app.launch() 23 | 24 | // Insert steps here to perform after app launch but before taking a screenshot, 25 | // such as logging into a test account or navigating somewhere in the app 26 | 27 | let attachment = XCTAttachment(screenshot: app.screenshot()) 28 | attachment.name = "Launch Screen" 29 | attachment.lifetime = .keepAlways 30 | add(attachment) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Matchumbeop/Matchumbeop/Analytics/Events/AnalyticsEvents.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | enum ApplicationActivationMethod: String { 4 | case menuBar, keyboardShortcut 5 | } 6 | 7 | enum SpellCheckMethod: String { 8 | case inApp, service, clipboard 9 | } 10 | 11 | extension AnalyticsEvent { 12 | static let settingsOpened = MatchumbeopAnalyticsEvent(name: "settings_opened") 13 | static let quit = MatchumbeopAnalyticsEvent(name: "quit") 14 | static let textCopied = MatchumbeopAnalyticsEvent(name: "text_copied") 15 | 16 | static func applicationDidBecomeActive(method: ApplicationActivationMethod) -> MatchumbeopAnalyticsEvent { 17 | MatchumbeopAnalyticsEvent(name: "app_opened", parameters: ["method": method.rawValue]) 18 | } 19 | 20 | static func spellChecked(method: SpellCheckMethod, length: Int) -> MatchumbeopAnalyticsEvent { 21 | MatchumbeopAnalyticsEvent(name: "spell_checked", parameters: ["method": method.rawValue, "length": length]) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Matchumbeop/Matchumbeop/GoogleService-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | API_KEY 6 | AIzaSyBsS-f4K7eidEOiEYzEpsZgcwT_y6LYPLg 7 | GCM_SENDER_ID 8 | 751934985218 9 | PLIST_VERSION 10 | 1 11 | BUNDLE_ID 12 | me.ssut.matchumbeop 13 | PROJECT_ID 14 | matchumbeop-2024 15 | STORAGE_BUCKET 16 | matchumbeop-2024.appspot.com 17 | IS_ADS_ENABLED 18 | 19 | IS_ANALYTICS_ENABLED 20 | 21 | IS_APPINVITE_ENABLED 22 | 23 | IS_GCM_ENABLED 24 | 25 | IS_SIGNIN_ENABLED 26 | 27 | GOOGLE_APP_ID 28 | 1:751934985218:ios:5b09f8d3d96efed1bc831b 29 | 30 | 31 | -------------------------------------------------------------------------------- /Matchumbeop/Matchumbeop/Analytics/Analytics.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | protocol Analytics { 4 | func send(_ event: AnalyticsEvent, forceSend: Bool) 5 | } 6 | 7 | extension Analytics { 8 | func send(_ event: AnalyticsEvent) { 9 | self.send(event, forceSend: false) 10 | } 11 | 12 | func send(_ events: AnalyticsEvent..., forceSend: Bool = false) { 13 | events.forEach { event in 14 | self.send(event, forceSend: forceSend) 15 | } 16 | } 17 | } 18 | 19 | final class MatchumbeopAnalytics: Analytics { 20 | static let shared = MatchumbeopAnalytics() 21 | 22 | private let firebaseAnalyticsEngine: AnalyticsEngine 23 | 24 | private init() { 25 | self.firebaseAnalyticsEngine = FirebaseAnalyticsEngine() 26 | } 27 | 28 | func send(_ event: AnalyticsEvent, forceSend: Bool) { 29 | if event is MatchumbeopAnalyticsEvent { 30 | self.firebaseAnalyticsEngine.sendAnalyticsEvent(named: event.name, parameters: event.parameters, forceSend: forceSend) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Matchumbeop/Matchumbeop/Extensions/StringExtension.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension String { 4 | var containsWhitespace: Bool { 5 | self.rangeOfCharacter(from: .whitespacesAndNewlines) != nil 6 | } 7 | 8 | func trimmed() -> String { 9 | self.trimmingCharacters(in: .whitespacesAndNewlines) 10 | } 11 | 12 | func condenseWhitespace() -> String { 13 | let components = self.components(separatedBy: .whitespacesAndNewlines) 14 | return components.filter { !$0.isEmpty }.joined(separator: " ") 15 | } 16 | 17 | func chunked(into size: Int) -> [String] { 18 | var chunks: [String] = [] 19 | var startIndex = self.startIndex 20 | 21 | while startIndex < self.endIndex { 22 | let endIndex = self.index(startIndex, offsetBy: size, limitedBy: self.endIndex) ?? self.endIndex 23 | let chunk = String(self[startIndex..: View { 4 | let title: Title? 5 | 6 | public init(_ title: Title) { 7 | self.title = title 8 | } 9 | 10 | public var body: some View { 11 | if let title { 12 | HStack { 13 | VStack { 14 | Divider() 15 | } 16 | title 17 | .foregroundStyle(.secondary) 18 | .font(.subheadline) 19 | .zIndex(2) 20 | VStack { 21 | Divider() 22 | } 23 | } 24 | .padding(.vertical, 8) 25 | } else { 26 | Divider() 27 | .padding(.vertical, 8) 28 | } 29 | } 30 | } 31 | 32 | extension SettingsDivider where Title == Text { 33 | public init(_ title: String) { 34 | self.title = Text(title) 35 | } 36 | } 37 | 38 | extension SettingsDivider where Title == EmptyView { 39 | public init() { 40 | self.title = nil 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /.github/releaser/generate_latest_changes.py: -------------------------------------------------------------------------------- 1 | # look at file Release_Notes.md, take all bullet points under the top header. Also return the value of the first and second headers 2 | # the format in the header: # version - title 3 | 4 | def get_latest_release_notes(): 5 | with open('CHANGELOG.md', 'r') as f: 6 | lines = f.readlines() 7 | first_header = lines[0].strip() 8 | version, title = first_header.replace('## ', '').split(' - ', 1) 9 | lines = lines[1:] 10 | latest_changes = "" 11 | 12 | for line in lines: 13 | if line.strip().startswith('-'): 14 | latest_changes += line.strip() + "\n" 15 | elif line.strip().startswith('#'): 16 | break 17 | 18 | return title, version, latest_changes 19 | 20 | if __name__ == '__main__': 21 | title, new_version, latest_changes = get_latest_release_notes() 22 | print(title, new_version, latest_changes, sep='\n') 23 | # write each value to a file 24 | with open('new_version', 'w') as f: 25 | f.write(new_version) 26 | with open('latest_changes', 'w') as f: 27 | f.write(latest_changes) 28 | with open('title', 'w') as f: 29 | f.write(title) 30 | -------------------------------------------------------------------------------- /Matchumbeop/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIconFile 6 | AppIcon.icns 7 | NSServices 8 | 9 | 10 | NSMenuItem 11 | 12 | default 13 | 한글 맞춤법 검사 14 | 15 | NSMessage 16 | handlePasteAndCheck 17 | NSPortName 18 | MatchumbeopService 19 | NSSendTypes 20 | 21 | NSStringPboardType 22 | NSRTFPboardType 23 | NSRTFDPboardType 24 | 25 | NSServiceDescription 26 | 선택한 텍스트의 한글 맞춤법을 검사합니다. 27 | NSRestricted 28 | 29 | NSRequiredContext 30 | 31 | 32 | 33 | SUFeedURL 34 | https://matchumbeop.ssut.me/Support/appcast.xml 35 | SUPublicEDKey 36 | oXSo82AWzS6y7odVowozqI8jg7p2Ub6T4WxopBk7ysQ= 37 | 38 | 39 | -------------------------------------------------------------------------------- /Matchumbeop/Matchumbeop/ServicesProvider.swift: -------------------------------------------------------------------------------- 1 | import AppKit 2 | 3 | @MainActor final class ServicesProvider: NSObject { 4 | private var appDelegate: AppDelegate 5 | 6 | init(appDelegate: AppDelegate) { 7 | self.appDelegate = appDelegate 8 | } 9 | 10 | 11 | @objc func handlePasteAndCheck(_ pboard: NSPasteboard, userData: String, error: AutoreleasingUnsafeMutablePointer) { 12 | if let data = pboard.data(forType: .rtf) { 13 | if let text = NSAttributedString(rtf: data, documentAttributes: nil)?.string { 14 | appDelegate.pasteAndCheck(input: text) 15 | } else { 16 | error.pointee = "텍스트를 가져오는 데 실패했습니다." as NSString 17 | } 18 | } else if let data = pboard.data(forType: .rtfd) { 19 | if let text = NSAttributedString(rtf: data, documentAttributes: nil)?.string { 20 | appDelegate.pasteAndCheck(input: text) 21 | } else { 22 | error.pointee = "텍스트를 가져오는 데 실패했습니다." as NSString 23 | } 24 | } else if let text = pboard.string(forType: .string) { 25 | appDelegate.pasteAndCheck(input: text) 26 | } else { 27 | error.pointee = "텍스트를 가져오는 데 실패했습니다." as NSString 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /Matchumbeop/MatchumbeopTests/machumbubTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // hanspellTests.swift 3 | // hanspellTests 4 | // 5 | // Created by suhun han on 8/13/24. 6 | // 7 | 8 | import XCTest 9 | @testable import hanspell 10 | 11 | final class hanspellTests: XCTestCase { 12 | 13 | override func setUpWithError() throws { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | } 16 | 17 | override func tearDownWithError() throws { 18 | // Put teardown code here. This method is called after the invocation of each test method in the class. 19 | } 20 | 21 | func testExample() throws { 22 | // This is an example of a functional test case. 23 | // Use XCTAssert and related functions to verify your tests produce the correct results. 24 | // Any test you write for XCTest can be annotated as throws and async. 25 | // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. 26 | // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. 27 | } 28 | 29 | func testPerformanceExample() throws { 30 | // This is an example of a performance test case. 31 | self.measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Matchumbeop/Matchumbeop/SharedUIComponents/ProgressBar.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ProgressBar: View { 4 | @Binding var progress: Double 5 | @Binding var isError: Bool 6 | @State private var isVisible = true 7 | 8 | var body: some View { 9 | ZStack(alignment: .leading) { 10 | GeometryReader { geometry in 11 | Rectangle() 12 | .fill(isError ? Color.red.opacity(0.75) : Color.blue.opacity(0.8)) 13 | .frame(width: CGFloat(self.progress / 100) * geometry.size.width) 14 | .animation(.easeOut(duration: 0.2), value: progress) 15 | .animation(.easeInOut(duration: 0.2), value: isError) 16 | } 17 | .frame(height: 4) 18 | .background(.clear) 19 | .cornerRadius(2) 20 | .opacity(isVisible ? 1 : 0) 21 | .animation(.easeInOut(duration: 0.5), value: isVisible) 22 | } 23 | .onChange(of: progress) { newValue in 24 | if newValue >= 100 { 25 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { 26 | withAnimation { 27 | isVisible = false 28 | } 29 | } 30 | } else { 31 | withAnimation { 32 | isVisible = true 33 | } 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Matchumbeop/Matchumbeop/Analytics/AnalyticsEngine.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import FirebaseAnalytics 3 | 4 | protocol AnalyticsEngine: AnyObject { 5 | func sendAnalyticsEvent(named name: String, parameters: [String: Any]?, forceSend: Bool) 6 | } 7 | 8 | extension AnalyticsEngine { 9 | func sendAnalyticsEvent(named name: String) { 10 | self.sendAnalyticsEvent(named: name, parameters: nil, forceSend: false) 11 | } 12 | } 13 | 14 | final class FirebaseAnalyticsEngine: AnalyticsEngine { 15 | init() {} 16 | 17 | func sendAnalyticsEvent(named name: String, parameters: [String: Any]?, forceSend: Bool) { 18 | let processedName = self.processString(name) 19 | let processedParameters: [String: Any]? = { 20 | guard let parameters = parameters else { 21 | return nil 22 | } 23 | 24 | return Dictionary(uniqueKeysWithValues: parameters.map { (self.processString($0), $1) }) 25 | }() 26 | 27 | FirebaseAnalytics.Analytics.logEvent(processedName, parameters: processedParameters) 28 | print("event logged: \(processedName) with parameters: \(String(describing: processedParameters))") 29 | } 30 | 31 | private func processString(_ string: String) -> String { 32 | guard string.containsWhitespace else { 33 | return string 34 | } 35 | 36 | return string 37 | .replacingOccurrences(of: " ", with: "_") 38 | .lowercased() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Matchumbeop/MatchumbeopUITests/MachumbubUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // hanspellUITests.swift 3 | // hanspellUITests 4 | // 5 | // Created by suhun han on 8/13/24. 6 | // 7 | 8 | import XCTest 9 | 10 | final class hanspellUITests: XCTestCase { 11 | 12 | override func setUpWithError() throws { 13 | // Put setup code here. This method is called before the invocation of each test method in the class. 14 | 15 | // In UI tests it is usually best to stop immediately when a failure occurs. 16 | continueAfterFailure = false 17 | 18 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 19 | } 20 | 21 | override func tearDownWithError() throws { 22 | // Put teardown code here. This method is called after the invocation of each test method in the class. 23 | } 24 | 25 | func testExample() throws { 26 | // UI tests must launch the application that they test. 27 | let app = XCUIApplication() 28 | app.launch() 29 | 30 | // Use XCTAssert and related functions to verify your tests produce the correct results. 31 | } 32 | 33 | func testLaunchPerformance() throws { 34 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { 35 | // This measures how long it takes to launch your application. 36 | measure(metrics: [XCTApplicationLaunchMetric()]) { 37 | XCUIApplication().launch() 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Icon 3 |

4 |

Matchumbeop

5 |

6 | 한글 맞춤법 검사기 for macOS 7 |

8 | 9 | 웹 기반의 여러 맞춤법 검사기를 macOS에서 편하게 사용할 수 있도록 만든 앱입니다. 10 | 11 | 현재 지원 또는 지원 예정인 맞춤법 검사 엔진은 다음과 같습니다. 상황에 따라 일부는 구현되지 않거나 목록에서 제외될 수 있습니다. 12 | 13 | - [x] [네이버(NAVER) 맞춤법 검사기](https://m.search.naver.com/search.naver?query=맞춤법+검사기) 14 | - [ ] [다음(Kakao) 맞춤법 검사기](https://search.daum.net/search?w=tot&DA=YZR&t__nil_searchbox=btn&q=맞춤법+검사기) 15 | - [ ] [부산대학교 한국어 맞춤법/문법 검사기](http://speller.cs.pusan.ac.kr) 16 | - [ ] [인크루트(Incruit) 맞춤법 검사기](https://lab.incruit.com/editor/spell/) 17 | 18 | ## 미리 보기 19 | 20 | 21 | 22 | ## 사용하기 23 | 24 | - **cmd + return (커맨드 + 엔터):** 맞춤법 검사 진행 25 | 26 | ### 기본 단축키 27 | 28 | ![Screenshot 2024-08-24 at 13 40 18](https://github.com/user-attachments/assets/1f3ea704-8eb9-426d-8059-24c47f958295) 29 | 30 | - **cmd + option + k:** 앱 실행 31 | - **cmd + shift + v:** 클립보드에 복사된 텍스트를 자동으로 붙여 넣어 맞춤법 검사 진행 32 | - [macOS 서비스를 등록하여 다른 앱 사용 중 선택한 텍스트를 바로 검사할 수 있습니다.](https://github.com/ssut/Matchumbeop/wiki/macOS-%EC%84%9C%EB%B9%84%EC%8A%A4-%EB%93%B1%EB%A1%9D-%EB%B0%A9%EB%B2%95) 33 | 34 | 메뉴 바에 있는 아이콘을 우클릭하여 기본 단축키를 포함한 여러 설정을 변경할 수 있습니다. 35 | 36 | ## Disclaimer 37 | 38 | - 본 앱은 각 맞춤법 검사 기능 사이트를 운영하는 회사에서 개발한 공식 앱이 아닌 개인 취미 프로젝트로 맞춤법 검사 기능을 제공하는 회사와는 아무런 관련이 없습니다. 39 | - 각 사이트에서 기능 제공을 중단하거나 변경(또는 차단) 할 경우 앱이 정상 동작하지 않을 수 있습니다. 40 | -------------------------------------------------------------------------------- /.github/releaser/remove_last_item_appcast.py: -------------------------------------------------------------------------------- 1 | def appcast_contains_version(version): 2 | with open('docs/Support/appcast.xml', 'r') as f: 3 | return version in f.read() 4 | 5 | def get_first(str, lines): 6 | for line in lines: 7 | if str in line: 8 | return lines.index(line) 9 | 10 | def remove_last_item_in_appcast(): 11 | lines = [] 12 | with open('docs/Support/appcast.xml', 'r') as f: 13 | lines = f.readlines() 14 | start_index = get_first("", lines) 15 | end_index = get_first("", lines) 16 | lines = lines[:start_index] + lines[end_index+1:] 17 | with open('docs/Support/appcast.xml', 'w') as f: 18 | f.writelines(lines) 19 | 20 | def get_last_item_in_appcast(): 21 | with open('docs/Support/appcast.xml', 'r') as f: 22 | lines = f.readlines() 23 | start_index = get_first("", lines) 24 | end_index = get_first("", lines) 25 | return ' '.join(lines[start_index:end_index+1]) 26 | 27 | def item_channel_is_beta(item): 28 | return "beta" in item 29 | 30 | if __name__ == '__main__': 31 | with open('new_version', 'r') as new_version_file: 32 | new_version = new_version_file.read() 33 | try: 34 | last_item = get_last_item_in_appcast() 35 | if new_version in last_item: 36 | print("Last item in appcast.xml contains new_version") 37 | if item_channel_is_beta(last_item): 38 | remove_last_item_in_appcast() 39 | print("removed last item in appcast.xml") 40 | else: 41 | print("last item is not new_version") 42 | except Exception as e: 43 | print(e) 44 | -------------------------------------------------------------------------------- /Matchumbeop/Matchumbeop/SharedUIComponents/DraggableTextView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import AppKit 3 | 4 | struct DraggableTextView: NSViewRepresentable { 5 | let attributedText: NSAttributedString 6 | var font: NSFont 7 | 8 | init(attributedText: NSAttributedString, font: NSFont = .systemFont(ofSize: 14)) { 9 | self.attributedText = attributedText 10 | self.font = font 11 | } 12 | 13 | func makeNSView(context: Context) -> NSScrollView { 14 | let textView = NSTextView() 15 | textView.isEditable = false 16 | textView.isSelectable = true 17 | textView.drawsBackground = false 18 | textView.textContainerInset = NSSize(width: 0, height: 0) 19 | textView.backgroundColor = .clear 20 | textView.registerForDraggedTypes([.string]) 21 | 22 | if let textContainer = textView.textContainer { 23 | textContainer.lineFragmentPadding = 0 24 | textContainer.heightTracksTextView = false 25 | textContainer.widthTracksTextView = true 26 | textContainer.containerSize = NSSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude) 27 | } 28 | 29 | textView.isVerticallyResizable = true 30 | textView.isHorizontallyResizable = false 31 | textView.autoresizingMask = [.width] 32 | 33 | let scrollView = NSScrollView() 34 | scrollView.hasVerticalScroller = true 35 | scrollView.documentView = textView 36 | scrollView.drawsBackground = false 37 | scrollView.borderType = .noBorder 38 | 39 | return scrollView 40 | } 41 | 42 | func updateNSView(_ nsView: NSScrollView, context: Context) { 43 | guard let textView = nsView.documentView as? NSTextView else { return } 44 | textView.textStorage?.setAttributedString(attributedText) 45 | textView.font = font 46 | 47 | nsView.invalidateIntrinsicContentSize() 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Matchumbeop/Matchumbeop/Extensions/SpellCheckerExtension.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | let punctuationCharacters: CharacterSet = [".", ",", "!", "?", ":", ";", ")", "]", ">"] 4 | 5 | extension SpellChecker { 6 | func correctLongText(_ text: String, eol: String) async throws -> [SpellerApiResponse] { 7 | let chunks = splitTextIntoChunks(text) 8 | var spellerResponses: [SpellerApiResponse] = [] 9 | 10 | for chunk in chunks { 11 | let spellerResponse = try await correctText(chunk, eol: eol) 12 | spellerResponses.append(spellerResponse) 13 | } 14 | 15 | return spellerResponses 16 | } 17 | 18 | private func splitTextIntoChunks(_ text: String) -> [String] { 19 | var chunks: [String] = [] 20 | var currentChunk = "" 21 | 22 | let words = text.split(separator: " ", omittingEmptySubsequences: false) 23 | for word in words { 24 | let wordString = String(word) 25 | if currentChunk.count + wordString.count + 1 > 300 { 26 | chunks.append(currentChunk) 27 | currentChunk = wordString 28 | } else { 29 | if !currentChunk.isEmpty { 30 | currentChunk.append(" ") 31 | } 32 | currentChunk.append(wordString) 33 | } 34 | 35 | if currentChunk.count >= 300 { 36 | chunks.append(currentChunk) 37 | currentChunk = "" 38 | } 39 | } 40 | 41 | if !currentChunk.isEmpty { 42 | chunks.append(currentChunk) 43 | } 44 | 45 | for (index, chunk) in chunks.enumerated() { 46 | if chunk.count > 300 { 47 | let hardCutChunks = chunk.chunked(into: 300) 48 | chunks.remove(at: index) 49 | chunks.insert(contentsOf: hardCutChunks, at: index) 50 | } 51 | } 52 | 53 | return chunks 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /docs/Support/appcast.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Matchumbeop - 한글 맞춤법 검사기 5 | https://matchumbeop.ssut.me/Support/appcast.xml 6 | Most recent changes with links to updates. 7 | ko 8 | 9 | 1.1.0 10 | Tue, 27 Aug 2024 12:00:27 +0000 11 | https://github.com/ssut/Matchumbeop/releases 12 | 1 13 | 1.1.0 14 | 13.0 15 | macOS Ventura (13.0+) 지원, 사용성 개선, macOS 서비스 메뉴 추가 16 |
    17 |
  • macOS Ventura (13.0+)를 지원합니다.
  • 18 |
  • 앱 실행 시 아무 동작이 없는 것처럼 보인다는 피드백을 반영하여 실행 직후 맞춤법 검사기 화면을 표시하도록 변경했습니다.
  • 19 |
  • Dock에 앱을 두고 사용하는 경우를 고려해 Dock에 있는 앱을 클릭했을 때 맞춤법 검사기 화면이 표시되도록 변경했습니다.
  • 20 |
  • 검사 진행 중 확인이 가능하도록 진행률을 표시하도록 추가했습니다.
  • 21 |
  • 맥 내 서비스(Services) 메뉴에서도 맞춤법 검사를 할 수 있도록 추가했습니다. 단축키를 할당하면 아무 곳에서나 선택한 텍스트를 바로 검사할 수 있습니다. 방법: https://github.com/ssut/Matchumbeop/wiki/macOS-서비스-등록-방법
  • 22 |
]]>
23 | 24 |
25 | 26 | 1.0 27 | Sun, 25 Aug 2024 12:47:42 +0000 28 | https://github.com/ssut/Matchumbeop/releases 29 | 0 30 | 1.0 31 | 14.0 32 | 첫 릴리즈 33 |
    34 |
  • 첫 릴리즈
  • 35 |
]]>
36 | 37 |
38 |
39 |
-------------------------------------------------------------------------------- /Matchumbeop/Matchumbeop/UpdateChecker/UpdateChecker.swift: -------------------------------------------------------------------------------- 1 | import Sparkle 2 | 3 | public final class UpdateChecker { 4 | let updater: SPUUpdater 5 | let hostBundleFound: Bool 6 | let delegate: UpdaterDelegate 7 | 8 | public weak var updateCheckerDelegate: UpdateCheckerDelegate? { 9 | get { delegate.updateCheckerDelegate } 10 | set { delegate.updateCheckerDelegate = newValue } 11 | } 12 | 13 | public init( 14 | hostBundle: Bundle?, 15 | shouldAutomaticallyCheckForUpdate: Bool 16 | ) { 17 | if hostBundle == nil { 18 | hostBundleFound = false 19 | } else { 20 | hostBundleFound = true 21 | } 22 | delegate = .init( 23 | shouldAutomaticallyCheckForUpdate: shouldAutomaticallyCheckForUpdate 24 | ) 25 | updater = SPUUpdater( 26 | hostBundle: hostBundle ?? Bundle.main, 27 | applicationBundle: Bundle.main, 28 | userDriver: SPUStandardUserDriver(hostBundle: hostBundle ?? Bundle.main, delegate: nil), 29 | delegate: delegate 30 | ) 31 | do { 32 | try updater.start() 33 | } catch { 34 | } 35 | } 36 | 37 | public func checkForUpdates() { 38 | updater.checkForUpdates() 39 | } 40 | 41 | public func resetUpdateCycle() { 42 | updater.resetUpdateCycleAfterShortDelay() 43 | } 44 | 45 | public var automaticallyChecksForUpdates: Bool { 46 | get { updater.automaticallyChecksForUpdates } 47 | set { updater.automaticallyChecksForUpdates = newValue } 48 | } 49 | } 50 | 51 | public protocol UpdateCheckerDelegate: AnyObject { 52 | func prepareForRelaunch(finish: @escaping () -> Void) 53 | } 54 | 55 | class UpdaterDelegate: NSObject, SPUUpdaterDelegate { 56 | let shouldAutomaticallyCheckForUpdate: Bool 57 | weak var updateCheckerDelegate: UpdateCheckerDelegate? 58 | 59 | init(shouldAutomaticallyCheckForUpdate: Bool) { 60 | self.shouldAutomaticallyCheckForUpdate = shouldAutomaticallyCheckForUpdate 61 | } 62 | 63 | func updater(_ updater: SPUUpdater, mayPerform updateCheck: SPUUpdateCheck) throws { 64 | } 65 | 66 | func updater( 67 | _ updater: SPUUpdater, 68 | shouldPostponeRelaunchForUpdate item: SUAppcastItem, 69 | untilInvokingBlock installHandler: @escaping () -> Void 70 | ) -> Bool { 71 | if let updateCheckerDelegate { 72 | updateCheckerDelegate.prepareForRelaunch(finish: installHandler) 73 | return true 74 | } 75 | return false 76 | } 77 | 78 | func updater(_ updater: SPUUpdater, willScheduleUpdateCheckAfterDelay delay: TimeInterval) { 79 | } 80 | 81 | func updaterWillNotScheduleUpdateCheck(_ updater: SPUUpdater) { 82 | } 83 | 84 | func allowedChannels(for updater: SPUUpdater) -> Set { 85 | [] 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Matchumbeop/Matchumbeop/SpellCheckers/NaverSpellChecker.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Alamofire 3 | import SwiftSoup 4 | 5 | class NaverSpellChecker : SpellChecker { 6 | private let SPELLER_PROVIDER_URL = "https://m.search.naver.com/search.naver?query=%EB%A7%9E%EC%B6%A4%EB%B2%95%EA%B2%80%EC%82%AC%EA%B8%B0" 7 | private let PASSPORT_KEY_REGEX = "SpellerProxy\\?passportKey=([a-zA-Z0-9]+)" 8 | private let SPELLER_API_URL_BASE = "https://m.search.naver.com/p/csearch/ocontent/util/SpellerProxy?passportKey=" 9 | 10 | private var spellerApiUrl: String? 11 | private var session: Session 12 | 13 | init() { 14 | session = Session() 15 | 16 | Task { 17 | await updateSpellerApiUrl() 18 | } 19 | } 20 | 21 | private func updateSpellerApiUrl() async { 22 | do { 23 | let html = try await fetchHtml(from: SPELLER_PROVIDER_URL) 24 | let passportKey = try extractPassportKey(from: html) 25 | spellerApiUrl = SPELLER_API_URL_BASE + passportKey + "&color_blindness=0&q=" 26 | } catch { 27 | print(error) 28 | } 29 | } 30 | 31 | private func fetchHtml(from url: String) async throws -> String { 32 | let response = try await AF.request(url).serializingString().value 33 | return response 34 | } 35 | 36 | private func extractPassportKey(from html: String) throws -> String { 37 | let document = try SwiftSoup.parse(html) 38 | let scripts = try document.select("script").array() 39 | 40 | for script in scripts { 41 | let content = try script.html() 42 | if content.contains("SpellerProxy?passportKey=") { 43 | let regex = try NSRegularExpression(pattern: PASSPORT_KEY_REGEX) 44 | if let match = regex.firstMatch(in: content, range: NSRange(content.startIndex..., in: content)) { 45 | if let range = Range(match.range(at: 1), in: content) { 46 | return String(content[range]) 47 | } 48 | } 49 | } 50 | } 51 | throw NSError(domain: "com.spellchecker.error", code: 0, userInfo: [NSLocalizedDescriptionKey: "Passport key not found"]) 52 | } 53 | 54 | func correctText(_ text: String, eol: String) async throws -> SpellerApiResponse { 55 | guard let apiUrl = spellerApiUrl else { 56 | throw NSError(domain: "com.spellchecker.error", code: 1, userInfo: [NSLocalizedDescriptionKey: "Speller API URL not set"]) 57 | } 58 | 59 | let url = apiUrl + text.addingPercentEncoding(withAllowedCharacters: .afURLQueryAllowed)! 60 | 61 | do { 62 | var spellerResponse = try await fetchSpellerResponse(from: url) 63 | if let error = spellerResponse.message.error, !error.isEmpty { 64 | await updateSpellerApiUrl() 65 | spellerResponse = try await correctText(text, eol: eol) 66 | } 67 | 68 | return spellerResponse 69 | } catch { 70 | print(error) 71 | throw error 72 | } 73 | } 74 | 75 | private func fetchSpellerResponse(from url: String) async throws -> SpellerApiResponse { 76 | let response = try await AF.request(url).serializingDecodable(SpellerApiResponse.self).value 77 | return response 78 | } 79 | } 80 | 81 | struct SpellerApiResponse: Codable { 82 | let message: Message 83 | } 84 | 85 | struct Message: Codable { 86 | let result: Result 87 | let error: String? 88 | } 89 | 90 | struct Result: Codable { 91 | let html: String 92 | let errata_count: Int 93 | let origin_html: String 94 | let notag_html: String 95 | } 96 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/swift,xcode,macos,cocoapods 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=swift,xcode,macos,cocoapods 3 | 4 | ### CocoaPods ### 5 | ## CocoaPods GitIgnore Template 6 | 7 | # CocoaPods - Only use to conserve bandwidth / Save time on Pushing 8 | # - Also handy if you have a large number of dependant pods 9 | # - AS PER https://guides.cocoapods.org/using/using-cocoapods.html NEVER IGNORE THE LOCK FILE 10 | Pods/ 11 | 12 | ### macOS ### 13 | # General 14 | .DS_Store 15 | .AppleDouble 16 | .LSOverride 17 | 18 | # Icon must end with two \r 19 | Icon 20 | 21 | 22 | # Thumbnails 23 | ._* 24 | 25 | # Files that might appear in the root of a volume 26 | .DocumentRevisions-V100 27 | .fseventsd 28 | .Spotlight-V100 29 | .TemporaryItems 30 | .Trashes 31 | .VolumeIcon.icns 32 | .com.apple.timemachine.donotpresent 33 | 34 | # Directories potentially created on remote AFP share 35 | .AppleDB 36 | .AppleDesktop 37 | Network Trash Folder 38 | Temporary Items 39 | .apdisk 40 | 41 | ### macOS Patch ### 42 | # iCloud generated files 43 | *.icloud 44 | 45 | ### Swift ### 46 | # Xcode 47 | # 48 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 49 | 50 | ## User settings 51 | xcuserdata/ 52 | 53 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 54 | *.xcscmblueprint 55 | *.xccheckout 56 | 57 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 58 | build/ 59 | DerivedData/ 60 | *.moved-aside 61 | *.pbxuser 62 | !default.pbxuser 63 | *.mode1v3 64 | !default.mode1v3 65 | *.mode2v3 66 | !default.mode2v3 67 | *.perspectivev3 68 | !default.perspectivev3 69 | 70 | ## Obj-C/Swift specific 71 | *.hmap 72 | 73 | ## App packaging 74 | *.ipa 75 | *.dSYM.zip 76 | *.dSYM 77 | 78 | ## Playgrounds 79 | timeline.xctimeline 80 | playground.xcworkspace 81 | 82 | # Swift Package Manager 83 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 84 | # Packages/ 85 | # Package.pins 86 | # Package.resolved 87 | # *.xcodeproj 88 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 89 | # hence it is not needed unless you have added a package configuration file to your project 90 | # .swiftpm 91 | 92 | .build/ 93 | 94 | # CocoaPods 95 | # We recommend against adding the Pods directory to your .gitignore. However 96 | # you should judge for yourself, the pros and cons are mentioned at: 97 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 98 | # Pods/ 99 | # Add this line if you want to avoid checking in source code from the Xcode workspace 100 | # *.xcworkspace 101 | 102 | # Carthage 103 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 104 | # Carthage/Checkouts 105 | 106 | Carthage/Build/ 107 | 108 | # Accio dependency management 109 | Dependencies/ 110 | .accio/ 111 | 112 | # fastlane 113 | # It is recommended to not store the screenshots in the git repo. 114 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 115 | # For more information about the recommended setup visit: 116 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 117 | 118 | fastlane/report.xml 119 | fastlane/Preview.html 120 | fastlane/screenshots/**/*.png 121 | fastlane/test_output 122 | 123 | # Code Injection 124 | # After new code Injection tools there's a generated folder /iOSInjectionProject 125 | # https://github.com/johnno1962/injectionforxcode 126 | 127 | iOSInjectionProject/ 128 | 129 | ### Xcode ### 130 | 131 | ## Xcode 8 and earlier 132 | 133 | ### Xcode Patch ### 134 | *.xcodeproj/* 135 | !*.xcodeproj/project.pbxproj 136 | !*.xcodeproj/xcshareddata/ 137 | !*.xcodeproj/project.xcworkspace/ 138 | !*.xcworkspace/contents.xcworkspacedata 139 | /*.gcno 140 | **/xcshareddata/WorkspaceSettings.xcsettings 141 | 142 | # End of https://www.toptal.com/developers/gitignore/api/swift,xcode,macos,cocoapods 143 | 144 | /latest_changes 145 | /new_version 146 | /title 147 | -------------------------------------------------------------------------------- /Matchumbeop/Matchumbeop/Utils.swift: -------------------------------------------------------------------------------- 1 | import ApplicationServices 2 | import SwiftUI 3 | import SwiftSoup 4 | import AppKit 5 | 6 | func getSelectedText() -> String? { 7 | let systemWideElement = AXUIElementCreateSystemWide() 8 | 9 | var selectedTextValue: AnyObject? 10 | let errorCode = AXUIElementCopyAttributeValue(systemWideElement, kAXFocusedUIElementAttribute as CFString, &selectedTextValue) 11 | 12 | if errorCode == .success { 13 | let selectedTextElement = selectedTextValue as! AXUIElement 14 | var selectedText: AnyObject? 15 | let textErrorCode = AXUIElementCopyAttributeValue(selectedTextElement, kAXSelectedTextAttribute as CFString, &selectedText) 16 | 17 | if textErrorCode == .success, let selectedTextString = selectedText as? String { 18 | return selectedTextString 19 | } else { 20 | return nil 21 | } 22 | } else { 23 | return nil 24 | } 25 | } 26 | 27 | func formatCorrectedText(_ htmlString: String) -> AttributedString? { 28 | do { 29 | let document = try SwiftSoup.parseBodyFragment(htmlString) 30 | let body = document.body() 31 | 32 | let attributedString = NSMutableAttributedString() 33 | for node in body?.getChildNodes() ?? [] { 34 | if let textNode = node as? TextNode { 35 | let text = textNode.text() 36 | attributedString.append(NSAttributedString(string: text, attributes: [.foregroundColor: NSColor.textColor])) 37 | } else if let element = node as? Element { 38 | let tagName = element.tagName() 39 | if tagName == "br" { 40 | attributedString.append(NSAttributedString(string: "\n")) 41 | } else if tagName == "em" { 42 | let elementText = try element.text() 43 | var attrs: [NSAttributedString.Key: Any] = [:] 44 | 45 | if let className = try? element.className() { 46 | switch className { 47 | case "green_text": 48 | attrs[.foregroundColor] = NSColor(Green).withAlphaComponent(1) 49 | 50 | case "violet_text": 51 | attrs[.foregroundColor] = NSColor(Violet).withAlphaComponent(1) 52 | 53 | case "red_text": 54 | attrs[.foregroundColor] = NSColor(Red).withAlphaComponent(1) 55 | 56 | case "blue_text": 57 | attrs[.foregroundColor] = NSColor(Blue).withAlphaComponent(1) 58 | 59 | default: 60 | break 61 | } 62 | } 63 | 64 | let attributedElement = NSAttributedString(string: elementText, attributes: attrs) 65 | attributedString.append(attributedElement) 66 | } else { 67 | let elementText = try element.text() 68 | attributedString.append(NSAttributedString(string: elementText, attributes: [.foregroundColor: NSColor.textColor])) 69 | } 70 | } 71 | } 72 | 73 | return AttributedString(attributedString) 74 | } catch { 75 | print(error) 76 | return nil 77 | } 78 | } 79 | 80 | func locateHostBundleURL(url: URL) -> URL? { 81 | var nextURL = url 82 | while nextURL.path != "/" { 83 | nextURL = nextURL.deletingLastPathComponent() 84 | if nextURL.lastPathComponent.hasSuffix(".app") { 85 | return nextURL 86 | } 87 | } 88 | 89 | let devAppURL = url 90 | .deletingLastPathComponent() 91 | .appendingPathComponent("Matchumbeop Dev.app") 92 | return devAppURL 93 | } 94 | 95 | func openKeyboardShortcutsSettings() { 96 | if let url = URL(string: "x-apple.systempreferences:com.apple.preference.keyboard?Shortcuts") { 97 | NSWorkspace.shared.open(url) 98 | } 99 | } 100 | 101 | -------------------------------------------------------------------------------- /Matchumbeop/Matchumbeop/AppState.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import KeyboardShortcuts 3 | import Combine 4 | import Defaults 5 | 6 | @MainActor 7 | final class AppState: ObservableObject { 8 | static let shared = AppState(appDelegate: nil) 9 | 10 | private weak var appDelegate: AppDelegate? 11 | 12 | @Published var text = "" 13 | @Published var isLoading = false 14 | @Published var result: AttributedString? 15 | @Published var errorMessage: String? 16 | @Published var showToast = false 17 | @Published var progress: Double = 0 18 | 19 | @Published var appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String 20 | 21 | private lazy var analytics: Analytics = MatchumbeopAnalytics.shared 22 | 23 | @Published var spellChecker: SpellChecker 24 | private var cancellables = Set() 25 | 26 | init(appDelegate: AppDelegate?) { 27 | self.spellChecker = AppState.createSpellChecker(for: Defaults[.spellCheckerEngine]) 28 | self.appDelegate = appDelegate 29 | 30 | Defaults.publisher(.spellCheckerEngine) 31 | .removeDuplicates() 32 | .sink { [weak self] value in 33 | self?.updateSpellChecker(for: value.newValue) 34 | } 35 | .store(in: &cancellables) 36 | 37 | KeyboardShortcuts.onKeyUp(for: .togglePopover) { [weak self] in 38 | self?.toggleWindow() 39 | } 40 | 41 | KeyboardShortcuts.onKeyUp(for: .pasteAndCheck) { [weak self] in 42 | let text = NSPasteboard.general.string(forType: .string) ?? "" 43 | self?.pasteAndCheck(input: text) 44 | 45 | if !text.isEmpty { 46 | self?.analytics.send(.spellChecked(method: .clipboard, length: text.count)) 47 | } 48 | } 49 | } 50 | 51 | private static func createSpellChecker(for engine: SpellCheckerEngine) -> SpellChecker { 52 | switch engine { 53 | case .naver: 54 | return NaverSpellChecker() 55 | // case .kakao: 56 | // print("changed to kakao") 57 | // return NaverSpellChecker() 58 | } 59 | } 60 | 61 | private func updateSpellChecker(for engine: SpellCheckerEngine) { 62 | self.spellChecker = AppState.createSpellChecker(for: engine) 63 | } 64 | 65 | func setAppDelegate(_ appDelegate: AppDelegate) { 66 | self.appDelegate = appDelegate 67 | } 68 | 69 | func openSettings() { 70 | appDelegate?.openSettings() 71 | } 72 | 73 | func toggleWindow() { 74 | if appDelegate?.togglePopover() == true { 75 | self.analytics.send(.applicationDidBecomeActive(method: .keyboardShortcut)) 76 | } 77 | } 78 | 79 | func checkForUpdate() { 80 | appDelegate?.checkForUpdate() 81 | } 82 | 83 | func pasteAndCheck(input: String) { 84 | appDelegate?.pasteAndCheck(input: input) 85 | } 86 | 87 | @MainActor func checkSpelling(text: String) async { 88 | guard !text.isEmpty else { 89 | self.result = nil 90 | return 91 | } 92 | 93 | isLoading = true 94 | progress = 10 95 | errorMessage = nil 96 | result = nil 97 | 98 | do { 99 | let htmlString: String 100 | if text.count > 300 { 101 | let responses = try await spellChecker.correctLongText(text, eol: "\n") 102 | htmlString = responses.map { $0.message.result.html }.joined(separator: " ") 103 | } else { 104 | let response = try await spellChecker.correctText(text, eol: "\n") 105 | htmlString = response.message.result.html 106 | } 107 | 108 | DispatchQueue.main.async { 109 | self.result = formatCorrectedText(htmlString) 110 | self.isLoading = false 111 | self.progress = 100 112 | } 113 | } catch { 114 | DispatchQueue.main.async { 115 | self.errorMessage = "오류: 잠시 후 다시 시도해 주세요." 116 | self.isLoading = false 117 | self.progress = 100 118 | } 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /Matchumbeop/Matchumbeop.xcodeproj/xcshareddata/xcschemes/Matchumbeop.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 35 | 41 | 42 | 43 | 46 | 52 | 53 | 54 | 55 | 56 | 66 | 68 | 74 | 75 | 76 | 77 | 80 | 81 | 82 | 83 | 89 | 91 | 97 | 98 | 99 | 100 | 106 | 107 | 108 | 109 | 111 | 112 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /Matchumbeop/Matchumbeop.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "5799a3cf98113e32fa91f098af004cfea63bcaf59aade2778d5534a8045d09ad", 3 | "pins" : [ 4 | { 5 | "identity" : "abseil-cpp-binary", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/google/abseil-cpp-binary.git", 8 | "state" : { 9 | "revision" : "194a6706acbd25e4ef639bcaddea16e8758a3e27", 10 | "version" : "1.2024011602.0" 11 | } 12 | }, 13 | { 14 | "identity" : "app-check", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/google/app-check.git", 17 | "state" : { 18 | "revision" : "21fe1af9be463a359aaf8d96789ef73fc3760d09", 19 | "version" : "11.0.1" 20 | } 21 | }, 22 | { 23 | "identity" : "defaults", 24 | "kind" : "remoteSourceControl", 25 | "location" : "https://github.com/sindresorhus/Defaults", 26 | "state" : { 27 | "branch" : "main", 28 | "revision" : "4c009d5c2496e7aa126e922c94dd3d6ae049efa2" 29 | } 30 | }, 31 | { 32 | "identity" : "firebase-ios-sdk", 33 | "kind" : "remoteSourceControl", 34 | "location" : "https://github.com/firebase/firebase-ios-sdk.git", 35 | "state" : { 36 | "revision" : "9118aca998dbe2ceac45d64b21a91c6376928df7", 37 | "version" : "11.1.0" 38 | } 39 | }, 40 | { 41 | "identity" : "googleappmeasurement", 42 | "kind" : "remoteSourceControl", 43 | "location" : "https://github.com/google/GoogleAppMeasurement.git", 44 | "state" : { 45 | "revision" : "07a2f57d147d2bf368a0d2dcb5579ff082d9e44f", 46 | "version" : "11.1.0" 47 | } 48 | }, 49 | { 50 | "identity" : "googledatatransport", 51 | "kind" : "remoteSourceControl", 52 | "location" : "https://github.com/google/GoogleDataTransport.git", 53 | "state" : { 54 | "revision" : "617af071af9aa1d6a091d59a202910ac482128f9", 55 | "version" : "10.1.0" 56 | } 57 | }, 58 | { 59 | "identity" : "googleutilities", 60 | "kind" : "remoteSourceControl", 61 | "location" : "https://github.com/google/GoogleUtilities.git", 62 | "state" : { 63 | "revision" : "53156c7ec267db846e6b64c9f4c4e31ba4cf75eb", 64 | "version" : "8.0.2" 65 | } 66 | }, 67 | { 68 | "identity" : "grpc-binary", 69 | "kind" : "remoteSourceControl", 70 | "location" : "https://github.com/google/grpc-binary.git", 71 | "state" : { 72 | "revision" : "f56d8fc3162de9a498377c7b6cea43431f4f5083", 73 | "version" : "1.65.1" 74 | } 75 | }, 76 | { 77 | "identity" : "gtm-session-fetcher", 78 | "kind" : "remoteSourceControl", 79 | "location" : "https://github.com/google/gtm-session-fetcher.git", 80 | "state" : { 81 | "revision" : "a2ab612cb980066ee56d90d60d8462992c07f24b", 82 | "version" : "3.5.0" 83 | } 84 | }, 85 | { 86 | "identity" : "interop-ios-for-google-sdks", 87 | "kind" : "remoteSourceControl", 88 | "location" : "https://github.com/google/interop-ios-for-google-sdks.git", 89 | "state" : { 90 | "revision" : "2d12673670417654f08f5f90fdd62926dc3a2648", 91 | "version" : "100.0.0" 92 | } 93 | }, 94 | { 95 | "identity" : "keyboardshortcuts", 96 | "kind" : "remoteSourceControl", 97 | "location" : "https://github.com/sindresorhus/KeyboardShortcuts", 98 | "state" : { 99 | "revision" : "2e5f15581fefb821d4b366e57d817be8bf12aa58", 100 | "version" : "2.0.1" 101 | } 102 | }, 103 | { 104 | "identity" : "launchatlogin-modern", 105 | "kind" : "remoteSourceControl", 106 | "location" : "https://github.com/sindresorhus/LaunchAtLogin-Modern", 107 | "state" : { 108 | "revision" : "a04ec1c363be3627734f6dad757d82f5d4fa8fcc", 109 | "version" : "1.1.0" 110 | } 111 | }, 112 | { 113 | "identity" : "leveldb", 114 | "kind" : "remoteSourceControl", 115 | "location" : "https://github.com/firebase/leveldb.git", 116 | "state" : { 117 | "revision" : "a0bc79961d7be727d258d33d5a6b2f1023270ba1", 118 | "version" : "1.22.5" 119 | } 120 | }, 121 | { 122 | "identity" : "nanopb", 123 | "kind" : "remoteSourceControl", 124 | "location" : "https://github.com/firebase/nanopb.git", 125 | "state" : { 126 | "revision" : "b7e1104502eca3a213b46303391ca4d3bc8ddec1", 127 | "version" : "2.30910.0" 128 | } 129 | }, 130 | { 131 | "identity" : "promises", 132 | "kind" : "remoteSourceControl", 133 | "location" : "https://github.com/google/promises.git", 134 | "state" : { 135 | "revision" : "540318ecedd63d883069ae7f1ed811a2df00b6ac", 136 | "version" : "2.4.0" 137 | } 138 | }, 139 | { 140 | "identity" : "sparkle", 141 | "kind" : "remoteSourceControl", 142 | "location" : "https://github.com/sparkle-project/Sparkle", 143 | "state" : { 144 | "revision" : "0ef1ee0220239b3776f433314515fd849025673f", 145 | "version" : "2.6.4" 146 | } 147 | }, 148 | { 149 | "identity" : "swift-protobuf", 150 | "kind" : "remoteSourceControl", 151 | "location" : "https://github.com/apple/swift-protobuf.git", 152 | "state" : { 153 | "revision" : "e17d61f26df0f0e06f58f6977ba05a097a720106", 154 | "version" : "1.27.1" 155 | } 156 | } 157 | ], 158 | "version" : 3 159 | } 160 | -------------------------------------------------------------------------------- /Matchumbeop/Matchumbeop.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "5799a3cf98113e32fa91f098af004cfea63bcaf59aade2778d5534a8045d09ad", 3 | "pins" : [ 4 | { 5 | "identity" : "abseil-cpp-binary", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/google/abseil-cpp-binary.git", 8 | "state" : { 9 | "revision" : "194a6706acbd25e4ef639bcaddea16e8758a3e27", 10 | "version" : "1.2024011602.0" 11 | } 12 | }, 13 | { 14 | "identity" : "app-check", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/google/app-check.git", 17 | "state" : { 18 | "revision" : "21fe1af9be463a359aaf8d96789ef73fc3760d09", 19 | "version" : "11.0.1" 20 | } 21 | }, 22 | { 23 | "identity" : "defaults", 24 | "kind" : "remoteSourceControl", 25 | "location" : "https://github.com/sindresorhus/Defaults", 26 | "state" : { 27 | "branch" : "main", 28 | "revision" : "4c009d5c2496e7aa126e922c94dd3d6ae049efa2" 29 | } 30 | }, 31 | { 32 | "identity" : "firebase-ios-sdk", 33 | "kind" : "remoteSourceControl", 34 | "location" : "https://github.com/firebase/firebase-ios-sdk.git", 35 | "state" : { 36 | "revision" : "9118aca998dbe2ceac45d64b21a91c6376928df7", 37 | "version" : "11.1.0" 38 | } 39 | }, 40 | { 41 | "identity" : "googleappmeasurement", 42 | "kind" : "remoteSourceControl", 43 | "location" : "https://github.com/google/GoogleAppMeasurement.git", 44 | "state" : { 45 | "revision" : "07a2f57d147d2bf368a0d2dcb5579ff082d9e44f", 46 | "version" : "11.1.0" 47 | } 48 | }, 49 | { 50 | "identity" : "googledatatransport", 51 | "kind" : "remoteSourceControl", 52 | "location" : "https://github.com/google/GoogleDataTransport.git", 53 | "state" : { 54 | "revision" : "617af071af9aa1d6a091d59a202910ac482128f9", 55 | "version" : "10.1.0" 56 | } 57 | }, 58 | { 59 | "identity" : "googleutilities", 60 | "kind" : "remoteSourceControl", 61 | "location" : "https://github.com/google/GoogleUtilities.git", 62 | "state" : { 63 | "revision" : "53156c7ec267db846e6b64c9f4c4e31ba4cf75eb", 64 | "version" : "8.0.2" 65 | } 66 | }, 67 | { 68 | "identity" : "grpc-binary", 69 | "kind" : "remoteSourceControl", 70 | "location" : "https://github.com/google/grpc-binary.git", 71 | "state" : { 72 | "revision" : "f56d8fc3162de9a498377c7b6cea43431f4f5083", 73 | "version" : "1.65.1" 74 | } 75 | }, 76 | { 77 | "identity" : "gtm-session-fetcher", 78 | "kind" : "remoteSourceControl", 79 | "location" : "https://github.com/google/gtm-session-fetcher.git", 80 | "state" : { 81 | "revision" : "a2ab612cb980066ee56d90d60d8462992c07f24b", 82 | "version" : "3.5.0" 83 | } 84 | }, 85 | { 86 | "identity" : "interop-ios-for-google-sdks", 87 | "kind" : "remoteSourceControl", 88 | "location" : "https://github.com/google/interop-ios-for-google-sdks.git", 89 | "state" : { 90 | "revision" : "2d12673670417654f08f5f90fdd62926dc3a2648", 91 | "version" : "100.0.0" 92 | } 93 | }, 94 | { 95 | "identity" : "keyboardshortcuts", 96 | "kind" : "remoteSourceControl", 97 | "location" : "https://github.com/sindresorhus/KeyboardShortcuts", 98 | "state" : { 99 | "revision" : "2e5f15581fefb821d4b366e57d817be8bf12aa58", 100 | "version" : "2.0.1" 101 | } 102 | }, 103 | { 104 | "identity" : "launchatlogin-modern", 105 | "kind" : "remoteSourceControl", 106 | "location" : "https://github.com/sindresorhus/LaunchAtLogin-Modern", 107 | "state" : { 108 | "revision" : "a04ec1c363be3627734f6dad757d82f5d4fa8fcc", 109 | "version" : "1.1.0" 110 | } 111 | }, 112 | { 113 | "identity" : "leveldb", 114 | "kind" : "remoteSourceControl", 115 | "location" : "https://github.com/firebase/leveldb.git", 116 | "state" : { 117 | "revision" : "a0bc79961d7be727d258d33d5a6b2f1023270ba1", 118 | "version" : "1.22.5" 119 | } 120 | }, 121 | { 122 | "identity" : "nanopb", 123 | "kind" : "remoteSourceControl", 124 | "location" : "https://github.com/firebase/nanopb.git", 125 | "state" : { 126 | "revision" : "b7e1104502eca3a213b46303391ca4d3bc8ddec1", 127 | "version" : "2.30910.0" 128 | } 129 | }, 130 | { 131 | "identity" : "promises", 132 | "kind" : "remoteSourceControl", 133 | "location" : "https://github.com/google/promises.git", 134 | "state" : { 135 | "revision" : "540318ecedd63d883069ae7f1ed811a2df00b6ac", 136 | "version" : "2.4.0" 137 | } 138 | }, 139 | { 140 | "identity" : "sparkle", 141 | "kind" : "remoteSourceControl", 142 | "location" : "https://github.com/sparkle-project/Sparkle", 143 | "state" : { 144 | "revision" : "0ef1ee0220239b3776f433314515fd849025673f", 145 | "version" : "2.6.4" 146 | } 147 | }, 148 | { 149 | "identity" : "swift-protobuf", 150 | "kind" : "remoteSourceControl", 151 | "location" : "https://github.com/apple/swift-protobuf.git", 152 | "state" : { 153 | "revision" : "e17d61f26df0f0e06f58f6977ba05a097a720106", 154 | "version" : "1.27.1" 155 | } 156 | } 157 | ], 158 | "version" : 3 159 | } 160 | -------------------------------------------------------------------------------- /Matchumbeop/Matchumbeop/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import KeyboardShortcuts 3 | import FirebaseCore 4 | import Defaults 5 | 6 | class AppDelegate: NSObject, NSApplicationDelegate { 7 | @StateObject var appState = AppState.shared 8 | 9 | var popover: NSPopover! 10 | var statusBarItem: NSStatusItem! 11 | var menu: NSMenu! 12 | var settingsWindow: NSWindow? 13 | var resultWindowController: NSWindowController? 14 | 15 | private lazy var analytics: Analytics = MatchumbeopAnalytics.shared 16 | 17 | let updateChecker = UpdateChecker( 18 | hostBundle: locateHostBundleURL(url: Bundle.main.bundleURL) 19 | .flatMap(Bundle.init(url:)), 20 | shouldAutomaticallyCheckForUpdate: true 21 | ) 22 | 23 | func applicationDidFinishLaunching(_ notification: Notification) { 24 | AppState.shared.setAppDelegate(self) 25 | updateChecker.updateCheckerDelegate = self 26 | 27 | FirebaseApp.configure() 28 | 29 | NSApp.servicesProvider = ServicesProvider(appDelegate: self) 30 | 31 | statusBarItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength) 32 | if let button = statusBarItem?.button { 33 | button.image = NSImage(systemSymbolName: "checkmark.circle", accessibilityDescription: "한글 맞춤법 검사기") 34 | button.target = self 35 | button.action = #selector(handleStatusBarClick(sender:)) 36 | button.sendAction(on: [.leftMouseUp, .rightMouseUp]) 37 | } 38 | 39 | popover = NSPopover() 40 | popover.contentViewController = NSHostingController(rootView: ContentView()) 41 | popover.behavior = .transient 42 | 43 | let statusBarMenu = NSMenu() 44 | statusBarMenu.minimumWidth = 120 45 | statusBarMenu.addItem(NSMenuItem(title: "설정", action: #selector(openSettings), keyEquivalent: "")) 46 | statusBarMenu.addItem(NSMenuItem(title: "업데이트 확인", action: #selector(checkForUpdate), keyEquivalent: "")) 47 | statusBarMenu.addItem(NSMenuItem.separator()) 48 | statusBarMenu.addItem(NSMenuItem(title: "종료", action: #selector(quitApp), keyEquivalent: "")) 49 | 50 | menu = statusBarMenu 51 | 52 | _ = updateHasLaunchedOnce() 53 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) { 54 | _ = self.togglePopover() 55 | } 56 | } 57 | 58 | func applicationDidBecomeActive(_ notification: Notification) { 59 | NSApp.unhide(self) 60 | 61 | if !popover.isShown { 62 | _ = togglePopover() 63 | } 64 | } 65 | 66 | func updateHasLaunchedOnce() -> Bool { 67 | if !Defaults[.hasLaunchedOnce] { 68 | Defaults[.hasLaunchedOnce] = true 69 | return true 70 | } 71 | 72 | return false 73 | } 74 | 75 | @MainActor @objc func pasteAndCheck(input: String) { 76 | _ = togglePopover() 77 | 78 | let text = input.trimmed() 79 | if text.isEmpty { 80 | return 81 | } 82 | 83 | AppState.shared.text = text 84 | 85 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { 86 | AppState.shared.isLoading = true 87 | Task { 88 | await AppState.shared.checkSpelling(text: text) 89 | } 90 | } 91 | } 92 | 93 | @objc func handleStatusBarClick(sender: NSStatusBarButton) { 94 | let event = NSApp.currentEvent! 95 | 96 | if event.type == NSEvent.EventType.rightMouseUp { 97 | statusBarItem.menu = menu 98 | statusBarItem.button?.performClick(nil) 99 | statusBarItem.menu = nil 100 | } else { 101 | if togglePopover() { 102 | self.analytics.send(.applicationDidBecomeActive(method: .menuBar)) 103 | } 104 | } 105 | } 106 | 107 | @objc func togglePopover() -> Bool { 108 | if popover.isShown { 109 | closePopover() 110 | return false 111 | } 112 | 113 | return openPopover() 114 | } 115 | 116 | @objc func closePopover() { 117 | popover.performClose(nil) 118 | } 119 | 120 | @objc func openPopover() -> Bool { 121 | if let button = statusBarItem?.button { 122 | popover.show(relativeTo: button.bounds, of: button, preferredEdge: .minY) 123 | NSApp.activate(ignoringOtherApps: true) 124 | return true 125 | } 126 | return false 127 | } 128 | 129 | @MainActor @objc func openSettings() { 130 | if settingsWindow == nil { 131 | let settingsView = SettingsView() 132 | .environmentObject(AppState.shared) 133 | 134 | settingsWindow = NSWindow( 135 | contentRect: NSRect(x: 0, y: 0, width: 600, height: 400), 136 | styleMask: [.titled, .closable], 137 | backing: .buffered, defer: false) 138 | if let settingsWindow = settingsWindow { 139 | settingsWindow.center() 140 | settingsWindow.title = "설정" 141 | settingsWindow.contentView = NSHostingView(rootView: settingsView) 142 | settingsWindow.isReleasedWhenClosed = false 143 | } 144 | } 145 | 146 | NSApp.deactivate() 147 | if let settingsWindow = settingsWindow { 148 | settingsWindow.makeKeyAndOrderFront(nil) 149 | settingsWindow.orderFrontRegardless() 150 | 151 | NSApp.activate(ignoringOtherApps: true) 152 | self.analytics.send(.settingsOpened) 153 | } 154 | } 155 | 156 | @objc func checkForUpdate() { 157 | updateChecker.checkForUpdates() 158 | } 159 | 160 | @objc func quitApp() { 161 | self.analytics.send(.quit, forceSend: true) 162 | 163 | NSApp.terminate(nil) 164 | } 165 | } 166 | 167 | extension AppDelegate: UpdateCheckerDelegate { 168 | func prepareForRelaunch(finish: @escaping () -> Void) { 169 | Task { 170 | finish() 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /Matchumbeop/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (5.9.1) 3 | - FirebaseAnalytics (10.29.0): 4 | - FirebaseAnalytics/AdIdSupport (= 10.29.0) 5 | - FirebaseCore (~> 10.0) 6 | - FirebaseInstallations (~> 10.0) 7 | - GoogleUtilities/AppDelegateSwizzler (~> 7.11) 8 | - GoogleUtilities/MethodSwizzler (~> 7.11) 9 | - GoogleUtilities/Network (~> 7.11) 10 | - "GoogleUtilities/NSData+zlib (~> 7.11)" 11 | - nanopb (< 2.30911.0, >= 2.30908.0) 12 | - FirebaseAnalytics/AdIdSupport (10.29.0): 13 | - FirebaseCore (~> 10.0) 14 | - FirebaseInstallations (~> 10.0) 15 | - GoogleAppMeasurement (= 10.29.0) 16 | - GoogleUtilities/AppDelegateSwizzler (~> 7.11) 17 | - GoogleUtilities/MethodSwizzler (~> 7.11) 18 | - GoogleUtilities/Network (~> 7.11) 19 | - "GoogleUtilities/NSData+zlib (~> 7.11)" 20 | - nanopb (< 2.30911.0, >= 2.30908.0) 21 | - FirebaseCore (10.29.0): 22 | - FirebaseCoreInternal (~> 10.0) 23 | - GoogleUtilities/Environment (~> 7.12) 24 | - GoogleUtilities/Logger (~> 7.12) 25 | - FirebaseCoreExtension (10.29.0): 26 | - FirebaseCore (~> 10.0) 27 | - FirebaseCoreInternal (10.29.0): 28 | - "GoogleUtilities/NSData+zlib (~> 7.8)" 29 | - FirebaseCrashlytics (10.29.0): 30 | - FirebaseCore (~> 10.5) 31 | - FirebaseInstallations (~> 10.0) 32 | - FirebaseRemoteConfigInterop (~> 10.23) 33 | - FirebaseSessions (~> 10.5) 34 | - GoogleDataTransport (~> 9.2) 35 | - GoogleUtilities/Environment (~> 7.8) 36 | - nanopb (< 2.30911.0, >= 2.30908.0) 37 | - PromisesObjC (~> 2.1) 38 | - FirebaseInstallations (10.29.0): 39 | - FirebaseCore (~> 10.0) 40 | - GoogleUtilities/Environment (~> 7.8) 41 | - GoogleUtilities/UserDefaults (~> 7.8) 42 | - PromisesObjC (~> 2.1) 43 | - FirebaseRemoteConfigInterop (10.29.0) 44 | - FirebaseSessions (10.29.0): 45 | - FirebaseCore (~> 10.5) 46 | - FirebaseCoreExtension (~> 10.0) 47 | - FirebaseInstallations (~> 10.0) 48 | - GoogleDataTransport (~> 9.2) 49 | - GoogleUtilities/Environment (~> 7.13) 50 | - GoogleUtilities/UserDefaults (~> 7.13) 51 | - nanopb (< 2.30911.0, >= 2.30908.0) 52 | - PromisesSwift (~> 2.1) 53 | - GoogleAppMeasurement (10.29.0): 54 | - GoogleAppMeasurement/AdIdSupport (= 10.29.0) 55 | - GoogleUtilities/AppDelegateSwizzler (~> 7.11) 56 | - GoogleUtilities/MethodSwizzler (~> 7.11) 57 | - GoogleUtilities/Network (~> 7.11) 58 | - "GoogleUtilities/NSData+zlib (~> 7.11)" 59 | - nanopb (< 2.30911.0, >= 2.30908.0) 60 | - GoogleAppMeasurement/AdIdSupport (10.29.0): 61 | - GoogleAppMeasurement/WithoutAdIdSupport (= 10.29.0) 62 | - GoogleUtilities/AppDelegateSwizzler (~> 7.11) 63 | - GoogleUtilities/MethodSwizzler (~> 7.11) 64 | - GoogleUtilities/Network (~> 7.11) 65 | - "GoogleUtilities/NSData+zlib (~> 7.11)" 66 | - nanopb (< 2.30911.0, >= 2.30908.0) 67 | - GoogleAppMeasurement/WithoutAdIdSupport (10.29.0): 68 | - GoogleUtilities/AppDelegateSwizzler (~> 7.11) 69 | - GoogleUtilities/MethodSwizzler (~> 7.11) 70 | - GoogleUtilities/Network (~> 7.11) 71 | - "GoogleUtilities/NSData+zlib (~> 7.11)" 72 | - nanopb (< 2.30911.0, >= 2.30908.0) 73 | - GoogleDataTransport (9.4.1): 74 | - GoogleUtilities/Environment (~> 7.7) 75 | - nanopb (< 2.30911.0, >= 2.30908.0) 76 | - PromisesObjC (< 3.0, >= 1.2) 77 | - GoogleUtilities/AppDelegateSwizzler (7.13.3): 78 | - GoogleUtilities/Environment 79 | - GoogleUtilities/Logger 80 | - GoogleUtilities/Network 81 | - GoogleUtilities/Privacy 82 | - GoogleUtilities/Environment (7.13.3): 83 | - GoogleUtilities/Privacy 84 | - PromisesObjC (< 3.0, >= 1.2) 85 | - GoogleUtilities/Logger (7.13.3): 86 | - GoogleUtilities/Environment 87 | - GoogleUtilities/Privacy 88 | - GoogleUtilities/MethodSwizzler (7.13.3): 89 | - GoogleUtilities/Logger 90 | - GoogleUtilities/Privacy 91 | - GoogleUtilities/Network (7.13.3): 92 | - GoogleUtilities/Logger 93 | - "GoogleUtilities/NSData+zlib" 94 | - GoogleUtilities/Privacy 95 | - GoogleUtilities/Reachability 96 | - "GoogleUtilities/NSData+zlib (7.13.3)": 97 | - GoogleUtilities/Privacy 98 | - GoogleUtilities/Privacy (7.13.3) 99 | - GoogleUtilities/Reachability (7.13.3): 100 | - GoogleUtilities/Logger 101 | - GoogleUtilities/Privacy 102 | - GoogleUtilities/UserDefaults (7.13.3): 103 | - GoogleUtilities/Logger 104 | - GoogleUtilities/Privacy 105 | - nanopb (2.30910.0): 106 | - nanopb/decode (= 2.30910.0) 107 | - nanopb/encode (= 2.30910.0) 108 | - nanopb/decode (2.30910.0) 109 | - nanopb/encode (2.30910.0) 110 | - PromisesObjC (2.4.0) 111 | - PromisesSwift (2.4.0): 112 | - PromisesObjC (= 2.4.0) 113 | - SwiftSoup (2.7.3) 114 | 115 | DEPENDENCIES: 116 | - Alamofire (~> 5.4) 117 | - FirebaseAnalytics 118 | - FirebaseCore 119 | - FirebaseCrashlytics 120 | - SwiftSoup (~> 2.3) 121 | 122 | SPEC REPOS: 123 | trunk: 124 | - Alamofire 125 | - FirebaseAnalytics 126 | - FirebaseCore 127 | - FirebaseCoreExtension 128 | - FirebaseCoreInternal 129 | - FirebaseCrashlytics 130 | - FirebaseInstallations 131 | - FirebaseRemoteConfigInterop 132 | - FirebaseSessions 133 | - GoogleAppMeasurement 134 | - GoogleDataTransport 135 | - GoogleUtilities 136 | - nanopb 137 | - PromisesObjC 138 | - PromisesSwift 139 | - SwiftSoup 140 | 141 | SPEC CHECKSUMS: 142 | Alamofire: f36a35757af4587d8e4f4bfa223ad10be2422b8c 143 | FirebaseAnalytics: 23717de130b779aa506e757edb9713d24b6ffeda 144 | FirebaseCore: 30e9c1cbe3d38f5f5e75f48bfcea87d7c358ec16 145 | FirebaseCoreExtension: 705ca5b14bf71d2564a0ddc677df1fc86ffa600f 146 | FirebaseCoreInternal: df84dd300b561c27d5571684f389bf60b0a5c934 147 | FirebaseCrashlytics: 34647b41e18de773717fdd348a22206f2f9bc774 148 | FirebaseInstallations: 913cf60d0400ebd5d6b63a28b290372ab44590dd 149 | FirebaseRemoteConfigInterop: 6efda51fb5e2f15b16585197e26eaa09574e8a4d 150 | FirebaseSessions: dbd14adac65ce996228652c1fc3a3f576bdf3ecc 151 | GoogleAppMeasurement: f9de05ee17401e3355f68e8fc8b5064d429f5918 152 | GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a 153 | GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15 154 | nanopb: 438bc412db1928dac798aa6fd75726007be04262 155 | PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 156 | PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851 157 | SwiftSoup: e3849c3293b1efee9cf892f91f6ae5c22bfeb5f7 158 | 159 | PODFILE CHECKSUM: 70c142d1df4db2a967cb2648db6fe9a9923ef3da 160 | 161 | COCOAPODS: 1.15.2 162 | -------------------------------------------------------------------------------- /Matchumbeop/Matchumbeop/SettingsView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import LaunchAtLogin 3 | import KeyboardShortcuts 4 | import Defaults 5 | 6 | extension KeyboardShortcuts.Name { 7 | static let togglePopover = Self("togglePopover", default: .init(.k, modifiers: [.command, .option])) 8 | static let checkSelection = Self("checkSelection", default: .init(.c, modifiers: [.command, .shift])) 9 | static let pasteAndCheck = Self("pasteAndCheck", default: .init(.v, modifiers: [.command, .shift])) 10 | } 11 | 12 | struct SettingsView: View { 13 | @StateObject var appState = AppState.shared 14 | 15 | @Default(.spellCheckerEngine) var spellCheckerEngine: SpellCheckerEngine 16 | 17 | var body: some View { 18 | VStack(alignment: .leading, spacing: 0) { 19 | HStack(alignment: .top) { 20 | HStack(alignment: .top) { 21 | Text("Matchumbeop: ").font(.title).fontWeight(.heavy) 22 | + Text("한글 ").font(.title) 23 | + Text("매츔법") 24 | .font(.title) 25 | .strikethrough() 26 | .foregroundColor(Color.primary.opacity(0.6)) 27 | + Text(" 맞춤법 검사기") 28 | .font(.title) 29 | Text("\(appState.appVersion ?? "")") 30 | .font(.footnote) 31 | .foregroundColor(.secondary) 32 | .padding(.bottom, 4) 33 | } 34 | 35 | Spacer() 36 | 37 | Button(action: { 38 | appState.checkForUpdate() 39 | }) { 40 | HStack(spacing: 4) { 41 | Image(systemName: "arrow.up.right.circle.fill") 42 | Text("업데이트 확인") 43 | } 44 | } 45 | .buttonStyle(LinkButtonStyle()) 46 | .focusable(false) 47 | } 48 | .padding() 49 | 50 | SettingsDivider() 51 | 52 | VStack(alignment: .leading) { 53 | Grid(alignment: .leading) { 54 | GridRow(alignment: .firstTextBaseline) { 55 | Text("검사 엔진:") 56 | .gridColumnAlignment(.trailing) 57 | 58 | VStack(alignment: .leading) { 59 | Picker("", selection: $spellCheckerEngine) { 60 | ForEach(SpellCheckerEngine.allCases) { engine in 61 | Text(engine.description).tag(engine) 62 | } 63 | } 64 | .pickerStyle(RadioGroupPickerStyle()) 65 | 66 | Text("") 67 | .font(.subheadline) 68 | .foregroundColor(.gray) 69 | .padding(.leading, 10) 70 | } 71 | .gridColumnAlignment(.leading) 72 | } 73 | 74 | 75 | GridRow(alignment: .firstTextBaseline) { 76 | Text("단축키:") 77 | .gridColumnAlignment(.trailing) 78 | 79 | VStack(alignment: .leading) { 80 | KeyboardShortcuts.Recorder("", name: .togglePopover) 81 | 82 | Text("") 83 | .font(.subheadline) 84 | .foregroundColor(.gray) 85 | .padding(.leading, 10) 86 | } 87 | .gridColumnAlignment(.leading) 88 | } 89 | 90 | GridRow(alignment: .firstTextBaseline) { 91 | Text("붙여넣기 단축키:") 92 | 93 | VStack(alignment: .leading) { 94 | KeyboardShortcuts.Recorder("", name: .pasteAndCheck) 95 | 96 | Text("클립보드에 있는 텍스트를 자동으로 붙여넣어 검사하는 단축키를 지정합니다.") 97 | .font(.subheadline) 98 | .foregroundColor(.gray) 99 | .padding(.leading, 10) 100 | } 101 | } 102 | 103 | GridRow(alignment: .firstTextBaseline) { 104 | Text("서비스 등록:") 105 | 106 | VStack(alignment: .leading) { 107 | Button("macOS 설정 열기") { 108 | openKeyboardShortcutsSettings() 109 | } 110 | .padding(.leading, 10) 111 | 112 | HStack(spacing: 4) { 113 | Text("서비스를 등록하여 다른 앱에서 선택된 텍스트를 바로 검사할 수 있습니다.") 114 | .font(.subheadline) 115 | .foregroundColor(.gray) 116 | .padding(.leading, 10) 117 | 118 | Link("설정 방법 확인하기", destination: URL(string: "https://github.com/ssut/Matchumbeop/wiki/macOS-서비스-등록-방법")!) 119 | .font(.subheadline) 120 | } 121 | } 122 | } 123 | 124 | GridRow(alignment: .firstTextBaseline) { 125 | Text("") 126 | 127 | VStack(alignment: .leading) { 128 | LaunchAtLogin.Toggle { 129 | Text("맥 시작 시 자동 실행") } 130 | .padding(.leading, 10) 131 | } 132 | } 133 | } 134 | } 135 | .padding() 136 | 137 | SettingsDivider() 138 | 139 | VStack(alignment: .leading) { 140 | Text("Disclaimer:") 141 | .font(.subheadline) 142 | .foregroundColor(.secondary) 143 | .padding(.top, 2) 144 | Text("이 앱은 웹에서 지원하는 맞춤법 검사기를 macOS에서 편하게 사용할 수 있도록 개발된 앱으로 각 검사기를 개발한 회사의 공식 앱이 아닙니다.") 145 | .font(.footnote) 146 | .foregroundColor(.secondary) 147 | .multilineTextAlignment(.leading) 148 | 149 | HStack(spacing: 16) { 150 | Link(destination: URL(string: "https://github.com/ssut/Matchumbeop")!) 151 | { 152 | HStack { 153 | Image(systemName: "link") 154 | .font(.system(size: 14)) 155 | .foregroundColor(.blue) 156 | Text("GitHub") 157 | } 158 | } 159 | .foregroundColor(.accentColor) 160 | .focusable(false) 161 | 162 | Link(destination: URL(string: "https://www.buymeacoffee.com/suhunhan95")!) { 163 | HStack(spacing: 2) { 164 | Image(systemName: "cup.and.saucer.fill") 165 | Text("Buy Me A Coffee") 166 | } 167 | } 168 | .foregroundColor(.accentColor) 169 | .focusable(false) 170 | } 171 | .padding(.top, 4) 172 | } 173 | .padding() 174 | } 175 | } 176 | } 177 | 178 | struct SettingsView_Previews: PreviewProvider { 179 | static var previews: some View { 180 | SettingsView() 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /Matchumbeop/Matchumbeop/ContentView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import Combine 3 | import Foundation 4 | import SwiftSoup 5 | import Defaults 6 | 7 | struct ContentView: View { 8 | @StateObject var appState = AppState.shared 9 | 10 | @State private var textLimit = 1800 // hard-limit (TODO: 풀기?) 11 | @State private var isSettingsButtonHovered = false 12 | 13 | @FocusState private var isTextEditorFocused: Bool 14 | 15 | @State private var lastSubmittedText: String = "" 16 | @State private var lastUsedEngine: SpellCheckerEngine = .naver 17 | 18 | @Default(.spellCheckerEngine) var spellCheckerEngine: SpellCheckerEngine 19 | 20 | private let analytics: Analytics = MatchumbeopAnalytics.shared 21 | 22 | var body: some View { 23 | VStack(spacing: 10) { 24 | TextEditor(text: $appState.text) 25 | .focused($isTextEditorFocused) 26 | .textFieldStyle(PlainTextFieldStyle()) 27 | .font(.system(size: 14)) 28 | .foregroundColor(.primary) 29 | .background(.clear) 30 | .scrollContentBackground(.hidden) 31 | .autocorrectionDisabled() 32 | .lineSpacing(2) 33 | .background(alignment: .topLeading) { 34 | if appState.text.isEmpty { 35 | Text("맞춤법 검사를 원하는 단어나 문장을 입력해 주세요.\n검사: command + return (⌘ + ↩)") 36 | .lineSpacing(2) 37 | .padding(.leading, 6) 38 | .font(.system(size: 14)) 39 | .foregroundColor(Color(.systemGray)) 40 | } 41 | } 42 | .clipShape(RoundedRectangle(cornerRadius: 10)) 43 | .overlay(alignment: .topTrailing) { 44 | Button(action: { 45 | appState.openSettings() 46 | }) { 47 | Image(systemName: "gearshape") 48 | .foregroundColor(.secondary) 49 | } 50 | .accessibilityHint("설정") 51 | .buttonStyle(BorderlessButtonStyle()) 52 | .animation(.easeInOut, value: 0.1) 53 | .background(.clear) 54 | .opacity(isSettingsButtonHovered ? 1 : 0.5) 55 | .onHover { isHovered in 56 | isSettingsButtonHovered = isHovered 57 | if isHovered { 58 | NSCursor.arrow.push() 59 | } else { 60 | NSCursor.pop() 61 | } 62 | } 63 | } 64 | .overlay(alignment: .bottomTrailing) { 65 | Text("\(appState.text.count)") 66 | .font(.system(size: 12)) 67 | .foregroundColor(Color(.systemGray)) 68 | .shadow( 69 | color: Color.primary.opacity(0.2), 70 | radius: 1, 71 | x: 0, 72 | y: 0 73 | ) 74 | .padding(.trailing, 6) 75 | .padding(.bottom, 6) 76 | .onReceive(Just(appState.text.count)) { count in 77 | if count > textLimit { 78 | appState.text = String(appState.text.prefix(textLimit)) 79 | } 80 | } 81 | } 82 | .overlay(alignment: .bottom) { 83 | Button(action: submitText) { 84 | EmptyView() 85 | } 86 | .keyboardShortcut(.return, modifiers: .command) 87 | .hidden() 88 | } 89 | .frame(height: 200) 90 | .onAppear { 91 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { 92 | isTextEditorFocused = true 93 | } 94 | } 95 | 96 | Group { 97 | if showBottomSection() { 98 | Divider() 99 | VStack { 100 | if appState.isLoading { 101 | ProgressView() 102 | .progressViewStyle(CircularProgressViewStyle()) 103 | .padding(.bottom, 4) 104 | } else if let errorMessage = appState.errorMessage { 105 | VStack { 106 | Text(errorMessage) 107 | .foregroundColor(.red) 108 | .padding() 109 | .background(Color(NSColor.windowBackgroundColor).opacity(0.9)) // Add a semi-transparent background 110 | .cornerRadius(8) // Optional: add corner radius to match design 111 | .padding(.bottom, 4) 112 | } 113 | .padding() 114 | } else if let result = appState.result { 115 | DraggableTextView(attributedText: NSAttributedString(result), font: .systemFont(ofSize: 14)) 116 | .padding(.horizontal, 4) 117 | .padding(.vertical, 2) 118 | .background(Color(NSColor.windowBackgroundColor)) 119 | 120 | HStack { 121 | HintView() 122 | .padding(.leading, 4) 123 | Spacer() 124 | Button(action: { 125 | NSPasteboard.general.clearContents() 126 | let plainText = String(result.characters) 127 | NSPasteboard.general.setString(plainText, forType: .string) 128 | 129 | appState.showToast = true 130 | DispatchQueue.main.asyncAfter(deadline: .now() + 2) { 131 | appState.showToast = false 132 | } 133 | 134 | self.analytics.send(.textCopied) 135 | }) { 136 | Text("복사") 137 | } 138 | .padding(.horizontal, 4) 139 | } 140 | .frame(height: 10) 141 | .padding(.bottom, 2) 142 | } 143 | } 144 | .frame(maxHeight: 280) 145 | } 146 | } 147 | } 148 | .padding(12) 149 | .background(Color(NSColor.windowBackgroundColor)) 150 | .cornerRadius(12) 151 | .shadow(radius: 10) 152 | .frame(width: 480, height: calculateHeight(), alignment: .top) 153 | .overlay( 154 | VStack { 155 | ProgressBar(progress: $appState.progress, isError: Binding(get: { appState.errorMessage != nil }, set: { _ in })) 156 | .frame(height: 2) 157 | .padding(.top, 0) 158 | Spacer() 159 | } 160 | .frame(maxWidth: .infinity, maxHeight: .infinity) 161 | .edgesIgnoringSafeArea(.top) 162 | ) 163 | .overlay( 164 | VStack { 165 | Spacer() 166 | if appState.showToast { 167 | ToastView(message: "클립보드에 복사되었습니다.") 168 | .padding(.bottom, 20) 169 | .transition(.opacity) 170 | } 171 | } 172 | .animation(.easeInOut(duration: 0.3), value: appState.showToast) 173 | ) 174 | } 175 | 176 | private func calculateHeight() -> CGFloat { 177 | let resultHeight = appState.result != nil ? 300 : 0 178 | return CGFloat(225 + resultHeight) 179 | } 180 | 181 | private func showBottomSection() -> Bool { 182 | return appState.isLoading || appState.errorMessage != nil || appState.result != nil 183 | } 184 | 185 | private func submitText() { 186 | if !appState.isLoading && 187 | (appState.text != lastSubmittedText || spellCheckerEngine != lastUsedEngine) { 188 | Task { 189 | await self.appState.checkSpelling(text: appState.text) 190 | self.analytics.send(.spellChecked(method: .inApp, length: appState.text.count)) 191 | 192 | lastSubmittedText = appState.text 193 | lastUsedEngine = spellCheckerEngine 194 | } 195 | } 196 | } 197 | } 198 | 199 | struct ContentView_Previews: PreviewProvider { 200 | static var previews: some View { 201 | ContentView() 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Matchumbeop - macOS용 한글 맞춤법 검사기 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 36 | 37 | 38 |
39 | 60 |
61 | 62 |
63 |
64 |
65 | 앱 스크린샷 70 |
71 |
72 |

73 | Matchumbeop 74 |

75 | 76 |
77 | macOS용 한글 맞춤법 검사기 78 |
79 |
80 | 간편한 macOS용 한글 맞춤법 검사기, 외없데? 여기 81 | 있어요! 82 |
83 |
84 | 90 | 마춤뻡 절대 안틀리는 노래도 꼭 들어보세요. 92 |
93 | 153 |

macOS 13 (Ventura) 이상, Universal (Apple Silicon, Intel)

154 |
155 |
156 |
157 | 158 | 166 | 167 | 168 | 176 | 220 | 221 | 222 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: "Create Release" 2 | on: 3 | push: 4 | branches: 5 | - "release/**" 6 | pull_request: 7 | branches: 8 | - "release/**" 9 | types: 10 | - ready_for_review 11 | - opened 12 | workflow_dispatch: 13 | 14 | concurrency: 15 | group: publish-release 16 | cancel-in-progress: true 17 | 18 | permissions: 19 | contents: read 20 | pull-requests: read 21 | 22 | env: 23 | projname: "Matchumbeop" 24 | beta-channel-name: "beta" 25 | 26 | jobs: 27 | preparation: 28 | name: Preparation job 29 | if: github.event.pull_request.draft == false 30 | runs-on: macos-14 31 | permissions: 32 | contents: write 33 | pull-requests: write 34 | steps: 35 | - uses: actions/checkout@v4 36 | - name: Extract latest changes 37 | id: latest_changes 38 | run: | 39 | python3 ./.github/releaser/generate_latest_changes.py 40 | # - name: Check if version already released 41 | # run: | 42 | # if [[ $(xcrun agvtool what-version -terse) == $(cat new_version) ]]; then 43 | # echo "Version already released" >> $GITHUB_STEP_SUMMARY 44 | # exit 1 45 | # fi 46 | - name: Check if release notes are empty 47 | run: | 48 | if [[ $(cat latest_changes) == "" ]]; then 49 | echo "Release notes are empty" >> $GITHUB_STEP_SUMMARY 50 | exit 1 51 | fi 52 | - name: Save generated info 53 | uses: actions/upload-artifact@v4 54 | with: 55 | path: | 56 | new_version 57 | title 58 | latest_changes 59 | - name: Clean up generated files for sync 60 | run: | 61 | rm latest_changes 62 | rm title 63 | rm new_version 64 | 65 | archive: 66 | name: Build and export app 67 | runs-on: macos-14 68 | needs: preparation 69 | environment: production 70 | permissions: 71 | contents: read 72 | pull-requests: read 73 | steps: 74 | - uses: actions/download-artifact@master # download all previously generated artifacts 75 | with: 76 | path: artifacts 77 | - name: Parse info generated in preparation job 78 | id: info 79 | run: | 80 | echo "new_version=$(cat artifacts/artifact/new_version)" >> $GITHUB_OUTPUT 81 | echo "title=$(cat artifacts/artifact/title)" >> $GITHUB_OUTPUT 82 | - uses: actions/checkout@v4 83 | - uses: irgaly/xcode-cache@v1 84 | with: 85 | key: xcode-cache-deriveddata-${{ github.workflow }}-${{ github.sha }} 86 | restore-keys: xcode-cache-deriveddata-${{ github.workflow }}- 87 | - name: 88 | Install the Apple certificate and provisioning profile 89 | # install the Apple certificate and provisioning profile 90 | # following https://docs.github.com/en/actions/deployment/deploying-xcode-applications/installing-an-apple-certificate-on-macos-runners-for-xcode-development 91 | env: 92 | MAC_DEV_CERT_BASE64: ${{ secrets.MAC_DEV_CERT_BASE64 }} 93 | DEVELOPER_ID_CERT_BASE64: ${{ secrets.DEVELOPER_ID_CERT_BASE64 }} 94 | P12_PASSWORD: ${{ secrets.P12_PASSWORD }} 95 | BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.BUILD_PROVISION_PROFILE_BASE64 }} 96 | KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} 97 | run: | 98 | # create variables 99 | # CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12 100 | PP_PATH=$RUNNER_TEMP/build_pp.mobileprovision 101 | KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db 102 | MAC_DEV_CERT_PATH=$RUNNER_TEMP/mac_dev_cert.p12 103 | DEVELOPER_ID_CERT_PATH=$RUNNER_TEMP/developer_id_cert.p12 104 | 105 | # import certificate and provisioning profile from secrets 106 | # echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode --output $CERTIFICATE_PATH 107 | echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode --output $PP_PATH 108 | echo -n "$MAC_DEV_CERT_BASE64" | base64 --decode --output $MAC_DEV_CERT_PATH 109 | echo -n "$DEVELOPER_ID_CERT_BASE64" | base64 --decode --output $DEVELOPER_ID_CERT_PATH 110 | 111 | # create temporary keychain 112 | security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH 113 | security set-keychain-settings -lut 21600 $KEYCHAIN_PATH 114 | security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH 115 | 116 | # import certificate to keychain 117 | # security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH 118 | security import $MAC_DEV_CERT_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH 119 | security import $DEVELOPER_ID_CERT_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH 120 | security list-keychain -d user -s $KEYCHAIN_PATH 121 | 122 | # apply provisioning profile 123 | mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles 124 | cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles 125 | - name: Switch Xcode version # Force Xcode version (macOS runner has multiple Xcode versions installed) 126 | run: | 127 | sudo xcode-select -s "/Applications/Xcode_15.4.app" 128 | /usr/bin/xcodebuild -version 129 | - name: setup-cocoapods 130 | uses: maxim-lobanov/setup-cocoapods@v1 131 | with: 132 | podfile-path: Matchumbeop/Podfile.lock 133 | - name: Pod Install 134 | run: | 135 | cd Matchumbeop 136 | pod install 137 | - name: Build and archive # create archive 138 | run: | 139 | xcodebuild archive -workspace Matchumbeop/${{ env.projname }}.xcworkspace -list 140 | xcodebuild clean archive -workspace Matchumbeop/${{ env.projname }}.xcworkspace -scheme ${{ env.projname }} -archivePath ${{ env.projname }} -destination 'generic/platform=macOS' 141 | - name: Export app # create .app 142 | run: xcodebuild -exportArchive -archivePath "${{ env.projname }}.xcarchive" -exportPath Release -exportOptionsPlist ".github/releaser/export_options.plist" 143 | - name: Zip app # zip .app 144 | run: | 145 | cd Release 146 | ditto -c -k --sequesterRsrc --keepParent ${{ env.projname }}.app ${{ env.projname }}.zip # todo check perhaps cd .. missing 147 | - name: Notary App 148 | run: | 149 | cd Release 150 | echo "notarizing ${{ env.projname }}.zip" 151 | xcrun notarytool submit ${{ env.projname }}.zip --team-id N7V29V6Q33 --apple-id ${{ env.NOTARY_ID }} --password ${{ env.NOTARY_PASSWORD }} --wait 152 | # echo "stapling ${{ env.projname }}.zip" 153 | # xcrun stapler staple ${{ env.projname }}.zip 154 | env: 155 | NOTARY_ID: ${{ secrets.NOTARY_ID }} 156 | NOTARY_PASSWORD: ${{ secrets.NOTARY_PASSWORD }} 157 | - name: Upload achived app 158 | uses: actions/upload-artifact@v4 159 | with: 160 | name: app 161 | path: Release/${{ env.projname }}.zip 162 | 163 | release: 164 | name: "Create Release" 165 | runs-on: macos-14 166 | environment: production 167 | needs: archive 168 | permissions: 169 | contents: write 170 | packages: write 171 | actions: write 172 | steps: 173 | - uses: actions/checkout@v4 174 | - uses: actions/download-artifact@master 175 | with: 176 | path: artifacts 177 | - name: Parse info generated in preparation job 178 | id: info 179 | run: | 180 | echo "new_version=$(cat artifacts/artifact/new_version)" >> $GITHUB_OUTPUT 181 | echo "title=$(cat artifacts/artifact/title)" >> $GITHUB_OUTPUT 182 | mv artifacts/artifact/new_version new_version 183 | mv artifacts/artifact/title title 184 | mv artifacts/artifact/latest_changes latest_changes 185 | mkdir Release 186 | mv artifacts/app/${{ env.projname }}.zip Release/ 187 | - name: Prepare Sparkle update creation 188 | env: 189 | SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }} 190 | run: | 191 | echo -n "$SPARKLE_PRIVATE_KEY" > ./.github/releaser/sparkle_private_key 192 | rm -rf Release/*.app 193 | rm -rf Release/*.log 194 | rm -rf Release/*.plist 195 | - name: Preparate Sparkle 196 | run: | 197 | pip3 install --break-system-packages -r .github/releaser/requirements.txt 198 | python3 ./.github/releaser/generate_html_for_sparkle_release.py 199 | mv Release/latest_changes.html Release/${{ env.projname }}.html 200 | python3 ./.github/releaser/remove_last_item_appcast.py 201 | - name: Update appcast 202 | run: | 203 | ./.github/releaser/generate_appcast \ 204 | --ed-key-file .github/releaser/sparkle_private_key \ 205 | --link https://github.com/${{ github.repository_owner }}/${{ github.event.repository.name }}/releases \ 206 | --download-url-prefix https://github.com/${{ github.repository_owner }}/${{ github.event.repository.name }}/releases/download/${{ steps.info.outputs.new_version }}/ \ 207 | -o docs/Support/appcast.xml \ 208 | Release/ 209 | - name: Create GitHub release 210 | uses: softprops/action-gh-release@v1 211 | with: 212 | name: ${{ steps.info.outputs.new_version }} - ${{ steps.info.outputs.title }} 213 | tag_name: ${{ steps.info.outputs.new_version }} 214 | fail_on_unmatched_files: true 215 | body_path: latest_changes 216 | files: Release/${{ env.projname }}.zip 217 | prerelease: ${{ steps.channel.outputs.prerelease }} 218 | - name: Saving changes 219 | uses: stefanzweifel/git-auto-commit-action@v4 220 | with: 221 | file_pattern: | 222 | docs/Support/appcast.xml 223 | Matchumbeop/${{ env.projname }}.xcodeproj/project.pbxproj 224 | commit_message: "chore(release): update version to ${{ steps.info.outputs.new_version }}" 225 | - name: Create summary 226 | run: | 227 | echo "Release ${{ steps.info.outputs.new_version }} created." > $GITHUB_STEP_SUMMARY 228 | 229 | ending: 230 | name: Ending job 231 | if: always() 232 | runs-on: ubuntu-24.04 233 | needs: [release] 234 | permissions: 235 | contents: read 236 | pull-requests: write 237 | steps: 238 | # - uses: actions/checkout@v4 239 | # - name: Merge PR 240 | # uses: devmasx/merge-branch@v1 241 | # with: 242 | # type: now 243 | # from_branch: ${{ steps.comment-branch.outputs.head_ref }} 244 | # target_branch: ${{ steps.comment-branch.outputs.base_ref }} 245 | # github_token: ${{ github.token }} 246 | # message: "chore(release): update version to ${{ steps.info.outputs.new_version }}" 247 | - uses: geekyeggo/delete-artifact@v2 248 | with: 249 | name: "*" 250 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2024 Suhun Han (ssut) 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Matchumbeop/Matchumbeop.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 302B5FB22C7620530043351B /* AnalyticsEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 302B5FB12C7620530043351B /* AnalyticsEngine.swift */; }; 11 | 302B5FB52C76208F0043351B /* Analytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 302B5FB42C76208F0043351B /* Analytics.swift */; }; 12 | 302B5FB72C7620CF0043351B /* AnalyticsEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 302B5FB62C7620CF0043351B /* AnalyticsEvent.swift */; }; 13 | 302B5FBA2C7621180043351B /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 302B5FB92C7621180043351B /* StringExtension.swift */; }; 14 | 302B5FBD2C7622170043351B /* AnalyticsEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 302B5FBC2C7622170043351B /* AnalyticsEvents.swift */; }; 15 | 302B5FC02C7756EB0043351B /* SpellChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 302B5FBF2C7756EB0043351B /* SpellChecker.swift */; }; 16 | 302B5FC32C7758690043351B /* Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 302B5FC22C7758690043351B /* Defaults.swift */; }; 17 | 30410C0A2C74EC1300AF9126 /* AppIcon.icns in Resources */ = {isa = PBXBuildFile; fileRef = 30410C092C74EC1300AF9126 /* AppIcon.icns */; }; 18 | 3042EB8B2C73810400F69690 /* HintView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3042EB8A2C73810400F69690 /* HintView.swift */; }; 19 | 3042EB8D2C7393BC00F69690 /* ToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3042EB8C2C7393BC00F69690 /* ToastView.swift */; }; 20 | 3042EB8F2C739E5100F69690 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3042EB8E2C739E5100F69690 /* Constants.swift */; }; 21 | 30467E152C6B862A007A0F19 /* MatchumbeopApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30467E142C6B862A007A0F19 /* MatchumbeopApp.swift */; }; 22 | 30467E1C2C6B862B007A0F19 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 30467E1B2C6B862B007A0F19 /* Preview Assets.xcassets */; }; 23 | 30467E272C6B862B007A0F19 /* MatchumbeopTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30467E262C6B862B007A0F19 /* MatchumbeopTests.swift */; }; 24 | 30467E312C6B862B007A0F19 /* MatchumbeopUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30467E302C6B862B007A0F19 /* MatchumbeopUITests.swift */; }; 25 | 30467E332C6B862B007A0F19 /* MatchumbeopUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30467E322C6B862B007A0F19 /* MatchumbeopUITestsLaunchTests.swift */; }; 26 | 30467E402C6B865B007A0F19 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30467E3F2C6B865B007A0F19 /* ContentView.swift */; }; 27 | 306064622C6B87BF00BC6350 /* NaverSpellChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 306064612C6B87BF00BC6350 /* NaverSpellChecker.swift */; }; 28 | 306064642C6B87D400BC6350 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 306064632C6B87D400BC6350 /* SettingsView.swift */; }; 29 | 306064672C6B8A7C00BC6350 /* KeyboardShortcuts in Frameworks */ = {isa = PBXBuildFile; productRef = 306064662C6B8A7C00BC6350 /* KeyboardShortcuts */; }; 30 | 306064692C6B8C5800BC6350 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 306064682C6B8C5800BC6350 /* Info.plist */; }; 31 | 3060646B2C6B8E8C00BC6350 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3060646A2C6B8E8C00BC6350 /* AppState.swift */; }; 32 | 3060646D2C6B957D00BC6350 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3060646C2C6B957D00BC6350 /* AppDelegate.swift */; }; 33 | 306064712C6BB82D00BC6350 /* DraggableTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 306064702C6BB82D00BC6350 /* DraggableTextView.swift */; }; 34 | 306064732C6C8AE400BC6350 /* SpellCheckerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 306064722C6C8AE400BC6350 /* SpellCheckerExtension.swift */; }; 35 | 30A0D8D92C775C1400F56515 /* Defaults in Frameworks */ = {isa = PBXBuildFile; productRef = 30A0D8D82C775C1400F56515 /* Defaults */; }; 36 | 30D5E0A02C73A5D700295E53 /* LaunchAtLogin in Frameworks */ = {isa = PBXBuildFile; productRef = 30D5E09F2C73A5D700295E53 /* LaunchAtLogin */; }; 37 | 30D5E0A22C74458C00295E53 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D5E0A12C74458C00295E53 /* Utils.swift */; }; 38 | 30D5E0A52C74BB4800295E53 /* SettingsDivider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D5E0A42C74BB4800295E53 /* SettingsDivider.swift */; }; 39 | 30D5E0A82C74BD5C00295E53 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 30D5E0A72C74BD5C00295E53 /* Sparkle */; }; 40 | 30D5E0BC2C74C3B800295E53 /* UpdateChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D5E0BB2C74C3B800295E53 /* UpdateChecker.swift */; }; 41 | 30D5E0C22C74D48B00295E53 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 30D5E0C12C74D48B00295E53 /* GoogleService-Info.plist */; }; 42 | 30D5E0C32C74EA3600295E53 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 30467E182C6B862B007A0F19 /* Assets.xcassets */; }; 43 | 30FA3ACB2C7CAEE0001D2BC8 /* ServicesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30FA3ACA2C7CAEE0001D2BC8 /* ServicesProvider.swift */; }; 44 | 30FA3ACE2C7CC4BB001D2BC8 /* ProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30FA3ACD2C7CC4BB001D2BC8 /* ProgressBar.swift */; }; 45 | 30FD9E4A2C7B4DCA001F0A67 /* Pods_Matchumbeop.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F824DF638CDA1CC6E6527CEE /* Pods_Matchumbeop.framework */; }; 46 | 3A31439163416A2CF1FE5904 /* Pods_MatchumbeopTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 82DBF95B47E43A32F9E8A0D2 /* Pods_MatchumbeopTests.framework */; }; 47 | B4F877EA4C02633AFAEBC07E /* Pods_Matchumbeop_MatchumbeopUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6F48963954137C846417FC93 /* Pods_Matchumbeop_MatchumbeopUITests.framework */; }; 48 | /* End PBXBuildFile section */ 49 | 50 | /* Begin PBXContainerItemProxy section */ 51 | 30467E232C6B862B007A0F19 /* PBXContainerItemProxy */ = { 52 | isa = PBXContainerItemProxy; 53 | containerPortal = 30467E092C6B862A007A0F19 /* Project object */; 54 | proxyType = 1; 55 | remoteGlobalIDString = 30467E102C6B862A007A0F19; 56 | remoteInfo = matchumbeop; 57 | }; 58 | 30467E2D2C6B862B007A0F19 /* PBXContainerItemProxy */ = { 59 | isa = PBXContainerItemProxy; 60 | containerPortal = 30467E092C6B862A007A0F19 /* Project object */; 61 | proxyType = 1; 62 | remoteGlobalIDString = 30467E102C6B862A007A0F19; 63 | remoteInfo = matchumbeop; 64 | }; 65 | /* End PBXContainerItemProxy section */ 66 | 67 | /* Begin PBXFileReference section */ 68 | 0106906D3DDFC1784AF771D5 /* Pods-Matchumbeop.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Matchumbeop.debug.xcconfig"; path = "Target Support Files/Pods-matchumbeop/Pods-matchumbeop.debug.xcconfig"; sourceTree = ""; }; 69 | 1894E3D37FC935BEA3EE42DF /* Pods-Matchumbeop.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Matchumbeop.release.xcconfig"; path = "Target Support Files/Pods-matchumbeop/Pods-matchumbeop.release.xcconfig"; sourceTree = ""; }; 70 | 2CCD25DF7D6A01504574BF17 /* Pods_Matchumbeop_MatchumbeopUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Matchumbeop_MatchumbeopUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 71 | 302B5FB12C7620530043351B /* AnalyticsEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsEngine.swift; sourceTree = ""; }; 72 | 302B5FB42C76208F0043351B /* Analytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Analytics.swift; sourceTree = ""; }; 73 | 302B5FB62C7620CF0043351B /* AnalyticsEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsEvent.swift; sourceTree = ""; }; 74 | 302B5FB92C7621180043351B /* StringExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtension.swift; sourceTree = ""; }; 75 | 302B5FBC2C7622170043351B /* AnalyticsEvents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsEvents.swift; sourceTree = ""; }; 76 | 302B5FBF2C7756EB0043351B /* SpellChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpellChecker.swift; sourceTree = ""; }; 77 | 302B5FC22C7758690043351B /* Defaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Defaults.swift; sourceTree = ""; }; 78 | 30410C092C74EC1300AF9126 /* AppIcon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = AppIcon.icns; sourceTree = ""; }; 79 | 3042EB8A2C73810400F69690 /* HintView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HintView.swift; sourceTree = ""; }; 80 | 3042EB8C2C7393BC00F69690 /* ToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastView.swift; sourceTree = ""; }; 81 | 3042EB8E2C739E5100F69690 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 82 | 30467E112C6B862A007A0F19 /* Matchumbeop.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Matchumbeop.app; sourceTree = BUILT_PRODUCTS_DIR; }; 83 | 30467E142C6B862A007A0F19 /* MatchumbeopApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchumbeopApp.swift; sourceTree = ""; }; 84 | 30467E182C6B862B007A0F19 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = matchumbeop/Assets.xcassets; sourceTree = SOURCE_ROOT; }; 85 | 30467E1B2C6B862B007A0F19 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 86 | 30467E1D2C6B862B007A0F19 /* Matchumbeop.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Matchumbeop.entitlements; sourceTree = ""; }; 87 | 30467E222C6B862B007A0F19 /* MatchumbeopTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MatchumbeopTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 88 | 30467E262C6B862B007A0F19 /* MatchumbeopTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchumbeopTests.swift; sourceTree = ""; }; 89 | 30467E2C2C6B862B007A0F19 /* MatchumbeopUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MatchumbeopUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 90 | 30467E302C6B862B007A0F19 /* MatchumbeopUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchumbeopUITests.swift; sourceTree = ""; }; 91 | 30467E322C6B862B007A0F19 /* MatchumbeopUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchumbeopUITestsLaunchTests.swift; sourceTree = ""; }; 92 | 30467E3F2C6B865B007A0F19 /* ContentView.swift */ = {isa = PBXFileReference; indentWidth = 5; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 93 | 306064612C6B87BF00BC6350 /* NaverSpellChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NaverSpellChecker.swift; sourceTree = ""; }; 94 | 306064632C6B87D400BC6350 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; 95 | 306064682C6B8C5800BC6350 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 96 | 3060646A2C6B8E8C00BC6350 /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = ""; }; 97 | 3060646C2C6B957D00BC6350 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 98 | 306064702C6BB82D00BC6350 /* DraggableTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraggableTextView.swift; sourceTree = ""; }; 99 | 306064722C6C8AE400BC6350 /* SpellCheckerExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpellCheckerExtension.swift; sourceTree = ""; }; 100 | 30D5E0A12C74458C00295E53 /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = ""; }; 101 | 30D5E0A42C74BB4800295E53 /* SettingsDivider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsDivider.swift; sourceTree = ""; }; 102 | 30D5E0A92C74C15700295E53 /* Pods_matchumbeop.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Pods_matchumbeop.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 103 | 30D5E0B32C74C17C00295E53 /* Pods_matchumbeopTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Pods_matchumbeopTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 104 | 30D5E0B72C74C18200295E53 /* Pods_matchumbeop_matchumbeopUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Pods_matchumbeop_matchumbeopUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 105 | 30D5E0BB2C74C3B800295E53 /* UpdateChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateChecker.swift; sourceTree = ""; }; 106 | 30D5E0C12C74D48B00295E53 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 107 | 30FA3ACA2C7CAEE0001D2BC8 /* ServicesProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServicesProvider.swift; sourceTree = ""; }; 108 | 30FA3ACD2C7CC4BB001D2BC8 /* ProgressBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressBar.swift; sourceTree = ""; }; 109 | 42D54CEFC3D0D4B5A8B9A049 /* Pods-MatchumbeopTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MatchumbeopTests.debug.xcconfig"; path = "Target Support Files/Pods-MatchumbeopTests/Pods-MatchumbeopTests.debug.xcconfig"; sourceTree = ""; }; 110 | 4AD7A1A528B8859594BCF5B2 /* Pods-matchumbeopTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-matchumbeopTests.release.xcconfig"; path = "Target Support Files/Pods-matchumbeopTests/Pods-matchumbeopTests.release.xcconfig"; sourceTree = ""; }; 111 | 515602CE4845FDE6082D32DD /* Pods-Matchumbeop-MatchumbeopUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Matchumbeop-MatchumbeopUITests.debug.xcconfig"; path = "Target Support Files/Pods-Matchumbeop-MatchumbeopUITests/Pods-Matchumbeop-MatchumbeopUITests.debug.xcconfig"; sourceTree = ""; }; 112 | 5D56015C3DA80757E22A6BE2 /* Pods-MatchumbeopTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MatchumbeopTests.release.xcconfig"; path = "Target Support Files/Pods-MatchumbeopTests/Pods-MatchumbeopTests.release.xcconfig"; sourceTree = ""; }; 113 | 6F48963954137C846417FC93 /* Pods_Matchumbeop_MatchumbeopUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Matchumbeop_MatchumbeopUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 114 | 82DBF95B47E43A32F9E8A0D2 /* Pods_MatchumbeopTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MatchumbeopTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 115 | 898C33C88652E63BFBED0FB4 /* Pods-matchumbeop-matchumbeopUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-matchumbeop-matchumbeopUITests.release.xcconfig"; path = "Target Support Files/Pods-matchumbeop-matchumbeopUITests/Pods-matchumbeop-matchumbeopUITests.release.xcconfig"; sourceTree = ""; }; 116 | 89DFA2DE2EEE8DE8FFEE7A0F /* Pods-matchumbeopTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-matchumbeopTests.debug.xcconfig"; path = "Target Support Files/Pods-matchumbeopTests/Pods-matchumbeopTests.debug.xcconfig"; sourceTree = ""; }; 117 | 90CD5A3CE3B7DF0B32838938 /* Pods_matchumbeopTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_matchumbeopTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 118 | A16852473B5D9C2C668F09C2 /* Pods-Matchumbeop-MatchumbeopUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Matchumbeop-MatchumbeopUITests.release.xcconfig"; path = "Target Support Files/Pods-Matchumbeop-MatchumbeopUITests/Pods-Matchumbeop-MatchumbeopUITests.release.xcconfig"; sourceTree = ""; }; 119 | C599C0E4C29875A660072236 /* Pods-matchumbeop-matchumbeopUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-matchumbeop-matchumbeopUITests.debug.xcconfig"; path = "Target Support Files/Pods-matchumbeop-matchumbeopUITests/Pods-matchumbeop-matchumbeopUITests.debug.xcconfig"; sourceTree = ""; }; 120 | C79414A5267BFA79DB55BBD7 /* Pods-Matchumbeop.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Matchumbeop.debug.xcconfig"; path = "Target Support Files/Pods-Matchumbeop/Pods-Matchumbeop.debug.xcconfig"; sourceTree = ""; }; 121 | DD823D2CC0C19124B038DF14 /* Pods-Matchumbeop.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Matchumbeop.release.xcconfig"; path = "Target Support Files/Pods-Matchumbeop/Pods-Matchumbeop.release.xcconfig"; sourceTree = ""; }; 122 | F824DF638CDA1CC6E6527CEE /* Pods_Matchumbeop.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Matchumbeop.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 123 | /* End PBXFileReference section */ 124 | 125 | /* Begin PBXFrameworksBuildPhase section */ 126 | 30467E0E2C6B862A007A0F19 /* Frameworks */ = { 127 | isa = PBXFrameworksBuildPhase; 128 | buildActionMask = 2147483647; 129 | files = ( 130 | 30A0D8D92C775C1400F56515 /* Defaults in Frameworks */, 131 | 30D5E0A02C73A5D700295E53 /* LaunchAtLogin in Frameworks */, 132 | 30D5E0A82C74BD5C00295E53 /* Sparkle in Frameworks */, 133 | 306064672C6B8A7C00BC6350 /* KeyboardShortcuts in Frameworks */, 134 | 30FD9E4A2C7B4DCA001F0A67 /* Pods_Matchumbeop.framework in Frameworks */, 135 | ); 136 | runOnlyForDeploymentPostprocessing = 0; 137 | }; 138 | 30467E1F2C6B862B007A0F19 /* Frameworks */ = { 139 | isa = PBXFrameworksBuildPhase; 140 | buildActionMask = 2147483647; 141 | files = ( 142 | 3A31439163416A2CF1FE5904 /* Pods_MatchumbeopTests.framework in Frameworks */, 143 | ); 144 | runOnlyForDeploymentPostprocessing = 0; 145 | }; 146 | 30467E292C6B862B007A0F19 /* Frameworks */ = { 147 | isa = PBXFrameworksBuildPhase; 148 | buildActionMask = 2147483647; 149 | files = ( 150 | B4F877EA4C02633AFAEBC07E /* Pods_Matchumbeop_MatchumbeopUITests.framework in Frameworks */, 151 | ); 152 | runOnlyForDeploymentPostprocessing = 0; 153 | }; 154 | /* End PBXFrameworksBuildPhase section */ 155 | 156 | /* Begin PBXGroup section */ 157 | 302B5FB32C7620860043351B /* Analytics */ = { 158 | isa = PBXGroup; 159 | children = ( 160 | 302B5FBB2C76220C0043351B /* Events */, 161 | 302B5FB42C76208F0043351B /* Analytics.swift */, 162 | 302B5FB12C7620530043351B /* AnalyticsEngine.swift */, 163 | 302B5FB62C7620CF0043351B /* AnalyticsEvent.swift */, 164 | ); 165 | path = Analytics; 166 | sourceTree = ""; 167 | }; 168 | 302B5FB82C7621100043351B /* Extensions */ = { 169 | isa = PBXGroup; 170 | children = ( 171 | 306064722C6C8AE400BC6350 /* SpellCheckerExtension.swift */, 172 | 302B5FB92C7621180043351B /* StringExtension.swift */, 173 | ); 174 | path = Extensions; 175 | sourceTree = ""; 176 | }; 177 | 302B5FBB2C76220C0043351B /* Events */ = { 178 | isa = PBXGroup; 179 | children = ( 180 | 302B5FBC2C7622170043351B /* AnalyticsEvents.swift */, 181 | ); 182 | path = Events; 183 | sourceTree = ""; 184 | }; 185 | 302B5FBE2C7756B20043351B /* SpellCheckers */ = { 186 | isa = PBXGroup; 187 | children = ( 188 | 306064612C6B87BF00BC6350 /* NaverSpellChecker.swift */, 189 | 302B5FBF2C7756EB0043351B /* SpellChecker.swift */, 190 | ); 191 | path = SpellCheckers; 192 | sourceTree = ""; 193 | }; 194 | 30467E082C6B862A007A0F19 = { 195 | isa = PBXGroup; 196 | children = ( 197 | 306064682C6B8C5800BC6350 /* Info.plist */, 198 | 30410C092C74EC1300AF9126 /* AppIcon.icns */, 199 | 30467E132C6B862A007A0F19 /* Matchumbeop */, 200 | 30467E252C6B862B007A0F19 /* MatchumbeopTests */, 201 | 30467E2F2C6B862B007A0F19 /* MatchumbeopUITests */, 202 | 30467E122C6B862A007A0F19 /* Products */, 203 | FB1A3C1335099160E11D5249 /* Pods */, 204 | 91FFB42A341B269B3C91CFD7 /* Frameworks */, 205 | 30FD9E492C7B4DA5001F0A67 /* Recovered References */, 206 | ); 207 | sourceTree = ""; 208 | }; 209 | 30467E122C6B862A007A0F19 /* Products */ = { 210 | isa = PBXGroup; 211 | children = ( 212 | 30467E112C6B862A007A0F19 /* Matchumbeop.app */, 213 | 30467E222C6B862B007A0F19 /* MatchumbeopTests.xctest */, 214 | 30467E2C2C6B862B007A0F19 /* MatchumbeopUITests.xctest */, 215 | ); 216 | name = Products; 217 | sourceTree = ""; 218 | }; 219 | 30467E132C6B862A007A0F19 /* Matchumbeop */ = { 220 | isa = PBXGroup; 221 | children = ( 222 | 302B5FBE2C7756B20043351B /* SpellCheckers */, 223 | 302B5FB82C7621100043351B /* Extensions */, 224 | 302B5FB32C7620860043351B /* Analytics */, 225 | 30D5E0C12C74D48B00295E53 /* GoogleService-Info.plist */, 226 | 30D5E0BD2C74C3E800295E53 /* UpdateChecker */, 227 | 30D5E0A32C74BB2F00295E53 /* SharedUIComponents */, 228 | 30467E142C6B862A007A0F19 /* MatchumbeopApp.swift */, 229 | 30467E182C6B862B007A0F19 /* Assets.xcassets */, 230 | 30467E1D2C6B862B007A0F19 /* Matchumbeop.entitlements */, 231 | 30467E1A2C6B862B007A0F19 /* Preview Content */, 232 | 30467E3F2C6B865B007A0F19 /* ContentView.swift */, 233 | 306064632C6B87D400BC6350 /* SettingsView.swift */, 234 | 3060646A2C6B8E8C00BC6350 /* AppState.swift */, 235 | 3060646C2C6B957D00BC6350 /* AppDelegate.swift */, 236 | 3042EB8E2C739E5100F69690 /* Constants.swift */, 237 | 30D5E0A12C74458C00295E53 /* Utils.swift */, 238 | 302B5FC22C7758690043351B /* Defaults.swift */, 239 | 30FA3ACA2C7CAEE0001D2BC8 /* ServicesProvider.swift */, 240 | ); 241 | path = Matchumbeop; 242 | sourceTree = ""; 243 | }; 244 | 30467E1A2C6B862B007A0F19 /* Preview Content */ = { 245 | isa = PBXGroup; 246 | children = ( 247 | 30467E1B2C6B862B007A0F19 /* Preview Assets.xcassets */, 248 | ); 249 | path = "Preview Content"; 250 | sourceTree = ""; 251 | }; 252 | 30467E252C6B862B007A0F19 /* MatchumbeopTests */ = { 253 | isa = PBXGroup; 254 | children = ( 255 | 30467E262C6B862B007A0F19 /* MatchumbeopTests.swift */, 256 | ); 257 | path = MatchumbeopTests; 258 | sourceTree = ""; 259 | }; 260 | 30467E2F2C6B862B007A0F19 /* MatchumbeopUITests */ = { 261 | isa = PBXGroup; 262 | children = ( 263 | 30467E302C6B862B007A0F19 /* MatchumbeopUITests.swift */, 264 | 30467E322C6B862B007A0F19 /* MatchumbeopUITestsLaunchTests.swift */, 265 | ); 266 | path = MatchumbeopUITests; 267 | sourceTree = ""; 268 | }; 269 | 30D5E0A32C74BB2F00295E53 /* SharedUIComponents */ = { 270 | isa = PBXGroup; 271 | children = ( 272 | 3042EB8C2C7393BC00F69690 /* ToastView.swift */, 273 | 30FA3ACD2C7CC4BB001D2BC8 /* ProgressBar.swift */, 274 | 306064702C6BB82D00BC6350 /* DraggableTextView.swift */, 275 | 3042EB8A2C73810400F69690 /* HintView.swift */, 276 | 30D5E0A42C74BB4800295E53 /* SettingsDivider.swift */, 277 | ); 278 | path = SharedUIComponents; 279 | sourceTree = ""; 280 | }; 281 | 30D5E0BD2C74C3E800295E53 /* UpdateChecker */ = { 282 | isa = PBXGroup; 283 | children = ( 284 | 30D5E0BB2C74C3B800295E53 /* UpdateChecker.swift */, 285 | ); 286 | path = UpdateChecker; 287 | sourceTree = ""; 288 | }; 289 | 30FD9E492C7B4DA5001F0A67 /* Recovered References */ = { 290 | isa = PBXGroup; 291 | children = ( 292 | 30D5E0A92C74C15700295E53 /* Pods_matchumbeop.framework */, 293 | 30D5E0B32C74C17C00295E53 /* Pods_matchumbeopTests.framework */, 294 | 30D5E0B72C74C18200295E53 /* Pods_matchumbeop_matchumbeopUITests.framework */, 295 | ); 296 | name = "Recovered References"; 297 | sourceTree = ""; 298 | }; 299 | 91FFB42A341B269B3C91CFD7 /* Frameworks */ = { 300 | isa = PBXGroup; 301 | children = ( 302 | 2CCD25DF7D6A01504574BF17 /* Pods_Matchumbeop_MatchumbeopUITests.framework */, 303 | 90CD5A3CE3B7DF0B32838938 /* Pods_matchumbeopTests.framework */, 304 | F824DF638CDA1CC6E6527CEE /* Pods_Matchumbeop.framework */, 305 | 6F48963954137C846417FC93 /* Pods_Matchumbeop_MatchumbeopUITests.framework */, 306 | 82DBF95B47E43A32F9E8A0D2 /* Pods_MatchumbeopTests.framework */, 307 | ); 308 | name = Frameworks; 309 | sourceTree = ""; 310 | }; 311 | FB1A3C1335099160E11D5249 /* Pods */ = { 312 | isa = PBXGroup; 313 | children = ( 314 | 0106906D3DDFC1784AF771D5 /* Pods-Matchumbeop.debug.xcconfig */, 315 | 1894E3D37FC935BEA3EE42DF /* Pods-Matchumbeop.release.xcconfig */, 316 | C599C0E4C29875A660072236 /* Pods-matchumbeop-matchumbeopUITests.debug.xcconfig */, 317 | 898C33C88652E63BFBED0FB4 /* Pods-matchumbeop-matchumbeopUITests.release.xcconfig */, 318 | 89DFA2DE2EEE8DE8FFEE7A0F /* Pods-matchumbeopTests.debug.xcconfig */, 319 | 4AD7A1A528B8859594BCF5B2 /* Pods-matchumbeopTests.release.xcconfig */, 320 | C79414A5267BFA79DB55BBD7 /* Pods-Matchumbeop.debug.xcconfig */, 321 | DD823D2CC0C19124B038DF14 /* Pods-Matchumbeop.release.xcconfig */, 322 | 515602CE4845FDE6082D32DD /* Pods-Matchumbeop-MatchumbeopUITests.debug.xcconfig */, 323 | A16852473B5D9C2C668F09C2 /* Pods-Matchumbeop-MatchumbeopUITests.release.xcconfig */, 324 | 42D54CEFC3D0D4B5A8B9A049 /* Pods-MatchumbeopTests.debug.xcconfig */, 325 | 5D56015C3DA80757E22A6BE2 /* Pods-MatchumbeopTests.release.xcconfig */, 326 | ); 327 | path = Pods; 328 | sourceTree = ""; 329 | }; 330 | /* End PBXGroup section */ 331 | 332 | /* Begin PBXNativeTarget section */ 333 | 30467E102C6B862A007A0F19 /* Matchumbeop */ = { 334 | isa = PBXNativeTarget; 335 | buildConfigurationList = 30467E362C6B862B007A0F19 /* Build configuration list for PBXNativeTarget "Matchumbeop" */; 336 | buildPhases = ( 337 | 00F88498609780D34240066B /* [CP] Check Pods Manifest.lock */, 338 | 30467E0D2C6B862A007A0F19 /* Sources */, 339 | 30467E0E2C6B862A007A0F19 /* Frameworks */, 340 | 30467E0F2C6B862A007A0F19 /* Resources */, 341 | 5E0F08EAA1159DD279F1A4D9 /* [CP] Embed Pods Frameworks */, 342 | 308A1CEF2C78D18D0038897F /* Firebase Crashlytics */, 343 | ); 344 | buildRules = ( 345 | ); 346 | dependencies = ( 347 | ); 348 | name = Matchumbeop; 349 | packageProductDependencies = ( 350 | 306064662C6B8A7C00BC6350 /* KeyboardShortcuts */, 351 | 30D5E09F2C73A5D700295E53 /* LaunchAtLogin */, 352 | 30D5E0A72C74BD5C00295E53 /* Sparkle */, 353 | 30A0D8D82C775C1400F56515 /* Defaults */, 354 | ); 355 | productName = matchumbeop; 356 | productReference = 30467E112C6B862A007A0F19 /* Matchumbeop.app */; 357 | productType = "com.apple.product-type.application"; 358 | }; 359 | 30467E212C6B862B007A0F19 /* MatchumbeopTests */ = { 360 | isa = PBXNativeTarget; 361 | buildConfigurationList = 30467E392C6B862B007A0F19 /* Build configuration list for PBXNativeTarget "MatchumbeopTests" */; 362 | buildPhases = ( 363 | B0BCCCA0AA4321A0458A0A78 /* [CP] Check Pods Manifest.lock */, 364 | 30467E1E2C6B862B007A0F19 /* Sources */, 365 | 30467E1F2C6B862B007A0F19 /* Frameworks */, 366 | 30467E202C6B862B007A0F19 /* Resources */, 367 | ); 368 | buildRules = ( 369 | ); 370 | dependencies = ( 371 | 30467E242C6B862B007A0F19 /* PBXTargetDependency */, 372 | ); 373 | name = MatchumbeopTests; 374 | productName = matchumbeopTests; 375 | productReference = 30467E222C6B862B007A0F19 /* MatchumbeopTests.xctest */; 376 | productType = "com.apple.product-type.bundle.unit-test"; 377 | }; 378 | 30467E2B2C6B862B007A0F19 /* MatchumbeopUITests */ = { 379 | isa = PBXNativeTarget; 380 | buildConfigurationList = 30467E3C2C6B862B007A0F19 /* Build configuration list for PBXNativeTarget "MatchumbeopUITests" */; 381 | buildPhases = ( 382 | 610BB47D42B04F06936D4BCF /* [CP] Check Pods Manifest.lock */, 383 | 30467E282C6B862B007A0F19 /* Sources */, 384 | 30467E292C6B862B007A0F19 /* Frameworks */, 385 | 30467E2A2C6B862B007A0F19 /* Resources */, 386 | 4A1DCC426867D7B6763D6452 /* [CP] Embed Pods Frameworks */, 387 | ); 388 | buildRules = ( 389 | ); 390 | dependencies = ( 391 | 30467E2E2C6B862B007A0F19 /* PBXTargetDependency */, 392 | ); 393 | name = MatchumbeopUITests; 394 | productName = matchumbeopUITests; 395 | productReference = 30467E2C2C6B862B007A0F19 /* MatchumbeopUITests.xctest */; 396 | productType = "com.apple.product-type.bundle.ui-testing"; 397 | }; 398 | /* End PBXNativeTarget section */ 399 | 400 | /* Begin PBXProject section */ 401 | 30467E092C6B862A007A0F19 /* Project object */ = { 402 | isa = PBXProject; 403 | attributes = { 404 | BuildIndependentTargetsInParallel = 1; 405 | LastSwiftUpdateCheck = 1500; 406 | LastUpgradeCheck = 1500; 407 | TargetAttributes = { 408 | 30467E102C6B862A007A0F19 = { 409 | CreatedOnToolsVersion = 15.0; 410 | }; 411 | 30467E212C6B862B007A0F19 = { 412 | CreatedOnToolsVersion = 15.0; 413 | TestTargetID = 30467E102C6B862A007A0F19; 414 | }; 415 | 30467E2B2C6B862B007A0F19 = { 416 | CreatedOnToolsVersion = 15.0; 417 | TestTargetID = 30467E102C6B862A007A0F19; 418 | }; 419 | }; 420 | }; 421 | buildConfigurationList = 30467E0C2C6B862A007A0F19 /* Build configuration list for PBXProject "Matchumbeop" */; 422 | compatibilityVersion = "Xcode 14.0"; 423 | developmentRegion = en; 424 | hasScannedForEncodings = 0; 425 | knownRegions = ( 426 | en, 427 | Base, 428 | ); 429 | mainGroup = 30467E082C6B862A007A0F19; 430 | packageReferences = ( 431 | 306064652C6B8A7C00BC6350 /* XCRemoteSwiftPackageReference "KeyboardShortcuts" */, 432 | 30D5E09E2C73A5D700295E53 /* XCRemoteSwiftPackageReference "LaunchAtLogin-Modern" */, 433 | 30D5E0A62C74BD5B00295E53 /* XCRemoteSwiftPackageReference "Sparkle" */, 434 | 30D5E0BE2C74CF7C00295E53 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, 435 | 302B5FC12C7757890043351B /* XCRemoteSwiftPackageReference "Defaults" */, 436 | ); 437 | productRefGroup = 30467E122C6B862A007A0F19 /* Products */; 438 | projectDirPath = ""; 439 | projectRoot = ""; 440 | targets = ( 441 | 30467E102C6B862A007A0F19 /* Matchumbeop */, 442 | 30467E212C6B862B007A0F19 /* MatchumbeopTests */, 443 | 30467E2B2C6B862B007A0F19 /* MatchumbeopUITests */, 444 | ); 445 | }; 446 | /* End PBXProject section */ 447 | 448 | /* Begin PBXResourcesBuildPhase section */ 449 | 30467E0F2C6B862A007A0F19 /* Resources */ = { 450 | isa = PBXResourcesBuildPhase; 451 | buildActionMask = 2147483647; 452 | files = ( 453 | 30410C0A2C74EC1300AF9126 /* AppIcon.icns in Resources */, 454 | 30467E1C2C6B862B007A0F19 /* Preview Assets.xcassets in Resources */, 455 | 30D5E0C22C74D48B00295E53 /* GoogleService-Info.plist in Resources */, 456 | 30D5E0C32C74EA3600295E53 /* Assets.xcassets in Resources */, 457 | 306064692C6B8C5800BC6350 /* Info.plist in Resources */, 458 | ); 459 | runOnlyForDeploymentPostprocessing = 0; 460 | }; 461 | 30467E202C6B862B007A0F19 /* Resources */ = { 462 | isa = PBXResourcesBuildPhase; 463 | buildActionMask = 2147483647; 464 | files = ( 465 | ); 466 | runOnlyForDeploymentPostprocessing = 0; 467 | }; 468 | 30467E2A2C6B862B007A0F19 /* Resources */ = { 469 | isa = PBXResourcesBuildPhase; 470 | buildActionMask = 2147483647; 471 | files = ( 472 | ); 473 | runOnlyForDeploymentPostprocessing = 0; 474 | }; 475 | /* End PBXResourcesBuildPhase section */ 476 | 477 | /* Begin PBXShellScriptBuildPhase section */ 478 | 00F88498609780D34240066B /* [CP] Check Pods Manifest.lock */ = { 479 | isa = PBXShellScriptBuildPhase; 480 | buildActionMask = 2147483647; 481 | files = ( 482 | ); 483 | inputFileListPaths = ( 484 | ); 485 | inputPaths = ( 486 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 487 | "${PODS_ROOT}/Manifest.lock", 488 | ); 489 | name = "[CP] Check Pods Manifest.lock"; 490 | outputFileListPaths = ( 491 | ); 492 | outputPaths = ( 493 | "$(DERIVED_FILE_DIR)/Pods-Matchumbeop-checkManifestLockResult.txt", 494 | ); 495 | runOnlyForDeploymentPostprocessing = 0; 496 | shellPath = /bin/sh; 497 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 498 | showEnvVarsInLog = 0; 499 | }; 500 | 308A1CEF2C78D18D0038897F /* Firebase Crashlytics */ = { 501 | isa = PBXShellScriptBuildPhase; 502 | buildActionMask = 2147483647; 503 | files = ( 504 | ); 505 | inputFileListPaths = ( 506 | ); 507 | inputPaths = ( 508 | "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}", 509 | "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${PRODUCT_NAME}", 510 | "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist", 511 | "$(TARGET_BUILD_DIR)/$(UNLOCALIZED_RESOURCES_FOLDER_PATH)/GoogleService-Info.plist", 512 | "$(TARGET_BUILD_DIR)/$(EXECUTABLE_PATH)", 513 | ); 514 | name = "Firebase Crashlytics"; 515 | outputFileListPaths = ( 516 | ); 517 | outputPaths = ( 518 | ); 519 | runOnlyForDeploymentPostprocessing = 0; 520 | shellPath = /bin/sh; 521 | shellScript = "\"${BUILD_DIR%/Build/*}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run\"\n"; 522 | }; 523 | 4A1DCC426867D7B6763D6452 /* [CP] Embed Pods Frameworks */ = { 524 | isa = PBXShellScriptBuildPhase; 525 | buildActionMask = 2147483647; 526 | files = ( 527 | ); 528 | inputFileListPaths = ( 529 | "${PODS_ROOT}/Target Support Files/Pods-Matchumbeop-MatchumbeopUITests/Pods-Matchumbeop-MatchumbeopUITests-frameworks-${CONFIGURATION}-input-files.xcfilelist", 530 | ); 531 | name = "[CP] Embed Pods Frameworks"; 532 | outputFileListPaths = ( 533 | "${PODS_ROOT}/Target Support Files/Pods-Matchumbeop-MatchumbeopUITests/Pods-Matchumbeop-MatchumbeopUITests-frameworks-${CONFIGURATION}-output-files.xcfilelist", 534 | ); 535 | runOnlyForDeploymentPostprocessing = 0; 536 | shellPath = /bin/sh; 537 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Matchumbeop-MatchumbeopUITests/Pods-Matchumbeop-MatchumbeopUITests-frameworks.sh\"\n"; 538 | showEnvVarsInLog = 0; 539 | }; 540 | 5E0F08EAA1159DD279F1A4D9 /* [CP] Embed Pods Frameworks */ = { 541 | isa = PBXShellScriptBuildPhase; 542 | buildActionMask = 2147483647; 543 | files = ( 544 | ); 545 | inputFileListPaths = ( 546 | "${PODS_ROOT}/Target Support Files/Pods-Matchumbeop/Pods-Matchumbeop-frameworks-${CONFIGURATION}-input-files.xcfilelist", 547 | ); 548 | name = "[CP] Embed Pods Frameworks"; 549 | outputFileListPaths = ( 550 | "${PODS_ROOT}/Target Support Files/Pods-Matchumbeop/Pods-Matchumbeop-frameworks-${CONFIGURATION}-output-files.xcfilelist", 551 | ); 552 | runOnlyForDeploymentPostprocessing = 0; 553 | shellPath = /bin/sh; 554 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Matchumbeop/Pods-Matchumbeop-frameworks.sh\"\n"; 555 | showEnvVarsInLog = 0; 556 | }; 557 | 610BB47D42B04F06936D4BCF /* [CP] Check Pods Manifest.lock */ = { 558 | isa = PBXShellScriptBuildPhase; 559 | buildActionMask = 2147483647; 560 | files = ( 561 | ); 562 | inputFileListPaths = ( 563 | ); 564 | inputPaths = ( 565 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 566 | "${PODS_ROOT}/Manifest.lock", 567 | ); 568 | name = "[CP] Check Pods Manifest.lock"; 569 | outputFileListPaths = ( 570 | ); 571 | outputPaths = ( 572 | "$(DERIVED_FILE_DIR)/Pods-Matchumbeop-MatchumbeopUITests-checkManifestLockResult.txt", 573 | ); 574 | runOnlyForDeploymentPostprocessing = 0; 575 | shellPath = /bin/sh; 576 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 577 | showEnvVarsInLog = 0; 578 | }; 579 | B0BCCCA0AA4321A0458A0A78 /* [CP] Check Pods Manifest.lock */ = { 580 | isa = PBXShellScriptBuildPhase; 581 | buildActionMask = 2147483647; 582 | files = ( 583 | ); 584 | inputFileListPaths = ( 585 | ); 586 | inputPaths = ( 587 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 588 | "${PODS_ROOT}/Manifest.lock", 589 | ); 590 | name = "[CP] Check Pods Manifest.lock"; 591 | outputFileListPaths = ( 592 | ); 593 | outputPaths = ( 594 | "$(DERIVED_FILE_DIR)/Pods-MatchumbeopTests-checkManifestLockResult.txt", 595 | ); 596 | runOnlyForDeploymentPostprocessing = 0; 597 | shellPath = /bin/sh; 598 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 599 | showEnvVarsInLog = 0; 600 | }; 601 | /* End PBXShellScriptBuildPhase section */ 602 | 603 | /* Begin PBXSourcesBuildPhase section */ 604 | 30467E0D2C6B862A007A0F19 /* Sources */ = { 605 | isa = PBXSourcesBuildPhase; 606 | buildActionMask = 2147483647; 607 | files = ( 608 | 306064732C6C8AE400BC6350 /* SpellCheckerExtension.swift in Sources */, 609 | 3042EB8B2C73810400F69690 /* HintView.swift in Sources */, 610 | 302B5FB52C76208F0043351B /* Analytics.swift in Sources */, 611 | 3060646D2C6B957D00BC6350 /* AppDelegate.swift in Sources */, 612 | 306064642C6B87D400BC6350 /* SettingsView.swift in Sources */, 613 | 302B5FB72C7620CF0043351B /* AnalyticsEvent.swift in Sources */, 614 | 302B5FB22C7620530043351B /* AnalyticsEngine.swift in Sources */, 615 | 3042EB8D2C7393BC00F69690 /* ToastView.swift in Sources */, 616 | 30FA3ACE2C7CC4BB001D2BC8 /* ProgressBar.swift in Sources */, 617 | 30D5E0BC2C74C3B800295E53 /* UpdateChecker.swift in Sources */, 618 | 3060646B2C6B8E8C00BC6350 /* AppState.swift in Sources */, 619 | 302B5FC02C7756EB0043351B /* SpellChecker.swift in Sources */, 620 | 30467E402C6B865B007A0F19 /* ContentView.swift in Sources */, 621 | 306064622C6B87BF00BC6350 /* NaverSpellChecker.swift in Sources */, 622 | 302B5FBA2C7621180043351B /* StringExtension.swift in Sources */, 623 | 3042EB8F2C739E5100F69690 /* Constants.swift in Sources */, 624 | 302B5FC32C7758690043351B /* Defaults.swift in Sources */, 625 | 302B5FBD2C7622170043351B /* AnalyticsEvents.swift in Sources */, 626 | 30467E152C6B862A007A0F19 /* MatchumbeopApp.swift in Sources */, 627 | 306064712C6BB82D00BC6350 /* DraggableTextView.swift in Sources */, 628 | 30D5E0A22C74458C00295E53 /* Utils.swift in Sources */, 629 | 30FA3ACB2C7CAEE0001D2BC8 /* ServicesProvider.swift in Sources */, 630 | 30D5E0A52C74BB4800295E53 /* SettingsDivider.swift in Sources */, 631 | ); 632 | runOnlyForDeploymentPostprocessing = 0; 633 | }; 634 | 30467E1E2C6B862B007A0F19 /* Sources */ = { 635 | isa = PBXSourcesBuildPhase; 636 | buildActionMask = 2147483647; 637 | files = ( 638 | 30467E272C6B862B007A0F19 /* MatchumbeopTests.swift in Sources */, 639 | ); 640 | runOnlyForDeploymentPostprocessing = 0; 641 | }; 642 | 30467E282C6B862B007A0F19 /* Sources */ = { 643 | isa = PBXSourcesBuildPhase; 644 | buildActionMask = 2147483647; 645 | files = ( 646 | 30467E332C6B862B007A0F19 /* MatchumbeopUITestsLaunchTests.swift in Sources */, 647 | 30467E312C6B862B007A0F19 /* MatchumbeopUITests.swift in Sources */, 648 | ); 649 | runOnlyForDeploymentPostprocessing = 0; 650 | }; 651 | /* End PBXSourcesBuildPhase section */ 652 | 653 | /* Begin PBXTargetDependency section */ 654 | 30467E242C6B862B007A0F19 /* PBXTargetDependency */ = { 655 | isa = PBXTargetDependency; 656 | target = 30467E102C6B862A007A0F19 /* Matchumbeop */; 657 | targetProxy = 30467E232C6B862B007A0F19 /* PBXContainerItemProxy */; 658 | }; 659 | 30467E2E2C6B862B007A0F19 /* PBXTargetDependency */ = { 660 | isa = PBXTargetDependency; 661 | target = 30467E102C6B862A007A0F19 /* Matchumbeop */; 662 | targetProxy = 30467E2D2C6B862B007A0F19 /* PBXContainerItemProxy */; 663 | }; 664 | /* End PBXTargetDependency section */ 665 | 666 | /* Begin XCBuildConfiguration section */ 667 | 30467E342C6B862B007A0F19 /* Debug */ = { 668 | isa = XCBuildConfiguration; 669 | buildSettings = { 670 | ALWAYS_SEARCH_USER_PATHS = NO; 671 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 672 | CLANG_ANALYZER_NONNULL = YES; 673 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 674 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 675 | CLANG_ENABLE_MODULES = YES; 676 | CLANG_ENABLE_OBJC_ARC = YES; 677 | CLANG_ENABLE_OBJC_WEAK = YES; 678 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 679 | CLANG_WARN_BOOL_CONVERSION = YES; 680 | CLANG_WARN_COMMA = YES; 681 | CLANG_WARN_CONSTANT_CONVERSION = YES; 682 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 683 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 684 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 685 | CLANG_WARN_EMPTY_BODY = YES; 686 | CLANG_WARN_ENUM_CONVERSION = YES; 687 | CLANG_WARN_INFINITE_RECURSION = YES; 688 | CLANG_WARN_INT_CONVERSION = YES; 689 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 690 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 691 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 692 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 693 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 694 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 695 | CLANG_WARN_STRICT_PROTOTYPES = YES; 696 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 697 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 698 | CLANG_WARN_UNREACHABLE_CODE = YES; 699 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 700 | COPY_PHASE_STRIP = NO; 701 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 702 | ENABLE_STRICT_OBJC_MSGSEND = YES; 703 | ENABLE_TESTABILITY = YES; 704 | ENABLE_USER_SCRIPT_SANDBOXING = NO; 705 | GCC_C_LANGUAGE_STANDARD = gnu17; 706 | GCC_DYNAMIC_NO_PIC = NO; 707 | GCC_NO_COMMON_BLOCKS = YES; 708 | GCC_OPTIMIZATION_LEVEL = 0; 709 | GCC_PREPROCESSOR_DEFINITIONS = ( 710 | "DEBUG=1", 711 | "$(inherited)", 712 | ); 713 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 714 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 715 | GCC_WARN_UNDECLARED_SELECTOR = YES; 716 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 717 | GCC_WARN_UNUSED_FUNCTION = YES; 718 | GCC_WARN_UNUSED_VARIABLE = YES; 719 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 720 | MACOSX_DEPLOYMENT_TARGET = 14.0; 721 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 722 | MTL_FAST_MATH = YES; 723 | ONLY_ACTIVE_ARCH = YES; 724 | SDKROOT = macosx; 725 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; 726 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 727 | }; 728 | name = Debug; 729 | }; 730 | 30467E352C6B862B007A0F19 /* Release */ = { 731 | isa = XCBuildConfiguration; 732 | buildSettings = { 733 | ALWAYS_SEARCH_USER_PATHS = NO; 734 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 735 | CLANG_ANALYZER_NONNULL = YES; 736 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 737 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 738 | CLANG_ENABLE_MODULES = YES; 739 | CLANG_ENABLE_OBJC_ARC = YES; 740 | CLANG_ENABLE_OBJC_WEAK = YES; 741 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 742 | CLANG_WARN_BOOL_CONVERSION = YES; 743 | CLANG_WARN_COMMA = YES; 744 | CLANG_WARN_CONSTANT_CONVERSION = YES; 745 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 746 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 747 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 748 | CLANG_WARN_EMPTY_BODY = YES; 749 | CLANG_WARN_ENUM_CONVERSION = YES; 750 | CLANG_WARN_INFINITE_RECURSION = YES; 751 | CLANG_WARN_INT_CONVERSION = YES; 752 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 753 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 754 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 755 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 756 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 757 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 758 | CLANG_WARN_STRICT_PROTOTYPES = YES; 759 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 760 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 761 | CLANG_WARN_UNREACHABLE_CODE = YES; 762 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 763 | COPY_PHASE_STRIP = NO; 764 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 765 | ENABLE_NS_ASSERTIONS = NO; 766 | ENABLE_STRICT_OBJC_MSGSEND = YES; 767 | ENABLE_USER_SCRIPT_SANDBOXING = NO; 768 | GCC_C_LANGUAGE_STANDARD = gnu17; 769 | GCC_NO_COMMON_BLOCKS = YES; 770 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 771 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 772 | GCC_WARN_UNDECLARED_SELECTOR = YES; 773 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 774 | GCC_WARN_UNUSED_FUNCTION = YES; 775 | GCC_WARN_UNUSED_VARIABLE = YES; 776 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 777 | MACOSX_DEPLOYMENT_TARGET = 14.0; 778 | MTL_ENABLE_DEBUG_INFO = NO; 779 | MTL_FAST_MATH = YES; 780 | SDKROOT = macosx; 781 | SWIFT_COMPILATION_MODE = wholemodule; 782 | }; 783 | name = Release; 784 | }; 785 | 30467E372C6B862B007A0F19 /* Debug */ = { 786 | isa = XCBuildConfiguration; 787 | baseConfigurationReference = C79414A5267BFA79DB55BBD7 /* Pods-Matchumbeop.debug.xcconfig */; 788 | buildSettings = { 789 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 790 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 791 | ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; 792 | CODE_SIGN_ENTITLEMENTS = Matchumbeop/Matchumbeop.entitlements; 793 | CODE_SIGN_IDENTITY = "Apple Development"; 794 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; 795 | CODE_SIGN_STYLE = Automatic; 796 | COMBINE_HIDPI_IMAGES = YES; 797 | CURRENT_PROJECT_VERSION = 1; 798 | DEVELOPMENT_ASSET_PATHS = "Matchumbeop/Preview\\ Content"; 799 | DEVELOPMENT_TEAM = N7V29V6Q33; 800 | ENABLE_HARDENED_RUNTIME = YES; 801 | ENABLE_PREVIEWS = YES; 802 | GENERATE_INFOPLIST_FILE = YES; 803 | INFOPLIST_FILE = Info.plist; 804 | INFOPLIST_KEY_CFBundleDisplayName = Matchumbeop; 805 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.education"; 806 | INFOPLIST_KEY_LSUIElement = YES; 807 | INFOPLIST_KEY_NSAppleEventsUsageDescription = "키보드 단축키를 사용하여 다른 앱에 선택된 텍스트를 자동으로 복사하기 위해 권한이 필요합니다."; 808 | INFOPLIST_KEY_NSHumanReadableCopyright = "2024 Suhun Han (@ssut)"; 809 | LD_RUNPATH_SEARCH_PATHS = ( 810 | "$(inherited)", 811 | "@executable_path/../Frameworks", 812 | ); 813 | MACOSX_DEPLOYMENT_TARGET = 13.0; 814 | MARKETING_VERSION = 1.1.0; 815 | OTHER_LDFLAGS = ( 816 | "$(inherited)", 817 | "-framework", 818 | "\"Alamofire\"", 819 | "-framework", 820 | "\"CFNetwork\"", 821 | "-framework", 822 | "\"SwiftSoup\"", 823 | ); 824 | PRODUCT_BUNDLE_IDENTIFIER = me.ssut.matchumbeop; 825 | PRODUCT_NAME = "$(TARGET_NAME)"; 826 | PROVISIONING_PROFILE_SPECIFIER = ""; 827 | SWIFT_EMIT_LOC_STRINGS = YES; 828 | SWIFT_VERSION = 5.0; 829 | }; 830 | name = Debug; 831 | }; 832 | 30467E382C6B862B007A0F19 /* Release */ = { 833 | isa = XCBuildConfiguration; 834 | baseConfigurationReference = DD823D2CC0C19124B038DF14 /* Pods-Matchumbeop.release.xcconfig */; 835 | buildSettings = { 836 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 837 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 838 | ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; 839 | CODE_SIGN_ENTITLEMENTS = Matchumbeop/Matchumbeop.entitlements; 840 | CODE_SIGN_IDENTITY = "Apple Development"; 841 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; 842 | CODE_SIGN_STYLE = Automatic; 843 | COMBINE_HIDPI_IMAGES = YES; 844 | CURRENT_PROJECT_VERSION = 1; 845 | DEVELOPMENT_ASSET_PATHS = "Matchumbeop/Preview\\ Content"; 846 | DEVELOPMENT_TEAM = N7V29V6Q33; 847 | ENABLE_HARDENED_RUNTIME = YES; 848 | ENABLE_PREVIEWS = YES; 849 | GENERATE_INFOPLIST_FILE = YES; 850 | INFOPLIST_FILE = Info.plist; 851 | INFOPLIST_KEY_CFBundleDisplayName = Matchumbeop; 852 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.education"; 853 | INFOPLIST_KEY_LSUIElement = YES; 854 | INFOPLIST_KEY_NSAppleEventsUsageDescription = "키보드 단축키를 사용하여 다른 앱에 선택된 텍스트를 자동으로 복사하기 위해 권한이 필요합니다."; 855 | INFOPLIST_KEY_NSHumanReadableCopyright = "2024 Suhun Han (@ssut)"; 856 | LD_RUNPATH_SEARCH_PATHS = ( 857 | "$(inherited)", 858 | "@executable_path/../Frameworks", 859 | ); 860 | MACOSX_DEPLOYMENT_TARGET = 13.0; 861 | MARKETING_VERSION = 1.1.0; 862 | OTHER_LDFLAGS = ( 863 | "$(inherited)", 864 | "-framework", 865 | "\"Alamofire\"", 866 | "-framework", 867 | "\"CFNetwork\"", 868 | "-framework", 869 | "\"SwiftSoup\"", 870 | ); 871 | PRODUCT_BUNDLE_IDENTIFIER = me.ssut.matchumbeop; 872 | PRODUCT_NAME = "$(TARGET_NAME)"; 873 | PROVISIONING_PROFILE_SPECIFIER = ""; 874 | SWIFT_EMIT_LOC_STRINGS = YES; 875 | SWIFT_VERSION = 5.0; 876 | }; 877 | name = Release; 878 | }; 879 | 30467E3A2C6B862B007A0F19 /* Debug */ = { 880 | isa = XCBuildConfiguration; 881 | baseConfigurationReference = 42D54CEFC3D0D4B5A8B9A049 /* Pods-MatchumbeopTests.debug.xcconfig */; 882 | buildSettings = { 883 | BUNDLE_LOADER = "$(TEST_HOST)"; 884 | CODE_SIGN_STYLE = Automatic; 885 | CURRENT_PROJECT_VERSION = 1; 886 | DEVELOPMENT_TEAM = 2V5P3KZ3LS; 887 | GENERATE_INFOPLIST_FILE = YES; 888 | MACOSX_DEPLOYMENT_TARGET = 14.0; 889 | MARKETING_VERSION = 1.0; 890 | PRODUCT_BUNDLE_IDENTIFIER = me.ssut.matchumbeopTests; 891 | PRODUCT_NAME = "$(TARGET_NAME)"; 892 | SWIFT_EMIT_LOC_STRINGS = NO; 893 | SWIFT_VERSION = 5.0; 894 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Matchumbeop.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Matchumbeop"; 895 | }; 896 | name = Debug; 897 | }; 898 | 30467E3B2C6B862B007A0F19 /* Release */ = { 899 | isa = XCBuildConfiguration; 900 | baseConfigurationReference = 5D56015C3DA80757E22A6BE2 /* Pods-MatchumbeopTests.release.xcconfig */; 901 | buildSettings = { 902 | BUNDLE_LOADER = "$(TEST_HOST)"; 903 | CODE_SIGN_STYLE = Automatic; 904 | CURRENT_PROJECT_VERSION = 1; 905 | DEVELOPMENT_TEAM = 2V5P3KZ3LS; 906 | GENERATE_INFOPLIST_FILE = YES; 907 | MACOSX_DEPLOYMENT_TARGET = 14.0; 908 | MARKETING_VERSION = 1.0; 909 | PRODUCT_BUNDLE_IDENTIFIER = me.ssut.matchumbeopTests; 910 | PRODUCT_NAME = "$(TARGET_NAME)"; 911 | SWIFT_EMIT_LOC_STRINGS = NO; 912 | SWIFT_VERSION = 5.0; 913 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Matchumbeop.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Matchumbeop"; 914 | }; 915 | name = Release; 916 | }; 917 | 30467E3D2C6B862B007A0F19 /* Debug */ = { 918 | isa = XCBuildConfiguration; 919 | baseConfigurationReference = 515602CE4845FDE6082D32DD /* Pods-Matchumbeop-MatchumbeopUITests.debug.xcconfig */; 920 | buildSettings = { 921 | CODE_SIGN_STYLE = Automatic; 922 | CURRENT_PROJECT_VERSION = 1; 923 | DEVELOPMENT_TEAM = 2V5P3KZ3LS; 924 | GENERATE_INFOPLIST_FILE = YES; 925 | MARKETING_VERSION = 1.0; 926 | PRODUCT_BUNDLE_IDENTIFIER = me.ssut.matchumbeopUITests; 927 | PRODUCT_NAME = "$(TARGET_NAME)"; 928 | SWIFT_EMIT_LOC_STRINGS = NO; 929 | SWIFT_VERSION = 5.0; 930 | TEST_TARGET_NAME = matchumbeop; 931 | }; 932 | name = Debug; 933 | }; 934 | 30467E3E2C6B862B007A0F19 /* Release */ = { 935 | isa = XCBuildConfiguration; 936 | baseConfigurationReference = A16852473B5D9C2C668F09C2 /* Pods-Matchumbeop-MatchumbeopUITests.release.xcconfig */; 937 | buildSettings = { 938 | CODE_SIGN_STYLE = Automatic; 939 | CURRENT_PROJECT_VERSION = 1; 940 | DEVELOPMENT_TEAM = 2V5P3KZ3LS; 941 | GENERATE_INFOPLIST_FILE = YES; 942 | MARKETING_VERSION = 1.0; 943 | PRODUCT_BUNDLE_IDENTIFIER = me.ssut.matchumbeopUITests; 944 | PRODUCT_NAME = "$(TARGET_NAME)"; 945 | SWIFT_EMIT_LOC_STRINGS = NO; 946 | SWIFT_VERSION = 5.0; 947 | TEST_TARGET_NAME = matchumbeop; 948 | }; 949 | name = Release; 950 | }; 951 | /* End XCBuildConfiguration section */ 952 | 953 | /* Begin XCConfigurationList section */ 954 | 30467E0C2C6B862A007A0F19 /* Build configuration list for PBXProject "Matchumbeop" */ = { 955 | isa = XCConfigurationList; 956 | buildConfigurations = ( 957 | 30467E342C6B862B007A0F19 /* Debug */, 958 | 30467E352C6B862B007A0F19 /* Release */, 959 | ); 960 | defaultConfigurationIsVisible = 0; 961 | defaultConfigurationName = Release; 962 | }; 963 | 30467E362C6B862B007A0F19 /* Build configuration list for PBXNativeTarget "Matchumbeop" */ = { 964 | isa = XCConfigurationList; 965 | buildConfigurations = ( 966 | 30467E372C6B862B007A0F19 /* Debug */, 967 | 30467E382C6B862B007A0F19 /* Release */, 968 | ); 969 | defaultConfigurationIsVisible = 0; 970 | defaultConfigurationName = Release; 971 | }; 972 | 30467E392C6B862B007A0F19 /* Build configuration list for PBXNativeTarget "MatchumbeopTests" */ = { 973 | isa = XCConfigurationList; 974 | buildConfigurations = ( 975 | 30467E3A2C6B862B007A0F19 /* Debug */, 976 | 30467E3B2C6B862B007A0F19 /* Release */, 977 | ); 978 | defaultConfigurationIsVisible = 0; 979 | defaultConfigurationName = Release; 980 | }; 981 | 30467E3C2C6B862B007A0F19 /* Build configuration list for PBXNativeTarget "MatchumbeopUITests" */ = { 982 | isa = XCConfigurationList; 983 | buildConfigurations = ( 984 | 30467E3D2C6B862B007A0F19 /* Debug */, 985 | 30467E3E2C6B862B007A0F19 /* Release */, 986 | ); 987 | defaultConfigurationIsVisible = 0; 988 | defaultConfigurationName = Release; 989 | }; 990 | /* End XCConfigurationList section */ 991 | 992 | /* Begin XCRemoteSwiftPackageReference section */ 993 | 302B5FC12C7757890043351B /* XCRemoteSwiftPackageReference "Defaults" */ = { 994 | isa = XCRemoteSwiftPackageReference; 995 | repositoryURL = "https://github.com/sindresorhus/Defaults"; 996 | requirement = { 997 | branch = main; 998 | kind = branch; 999 | }; 1000 | }; 1001 | 306064652C6B8A7C00BC6350 /* XCRemoteSwiftPackageReference "KeyboardShortcuts" */ = { 1002 | isa = XCRemoteSwiftPackageReference; 1003 | repositoryURL = "https://github.com/sindresorhus/KeyboardShortcuts"; 1004 | requirement = { 1005 | kind = upToNextMajorVersion; 1006 | minimumVersion = 2.0.1; 1007 | }; 1008 | }; 1009 | 30D5E09E2C73A5D700295E53 /* XCRemoteSwiftPackageReference "LaunchAtLogin-Modern" */ = { 1010 | isa = XCRemoteSwiftPackageReference; 1011 | repositoryURL = "https://github.com/sindresorhus/LaunchAtLogin-Modern"; 1012 | requirement = { 1013 | kind = exactVersion; 1014 | version = 1.1.0; 1015 | }; 1016 | }; 1017 | 30D5E0A62C74BD5B00295E53 /* XCRemoteSwiftPackageReference "Sparkle" */ = { 1018 | isa = XCRemoteSwiftPackageReference; 1019 | repositoryURL = "https://github.com/sparkle-project/Sparkle"; 1020 | requirement = { 1021 | kind = upToNextMajorVersion; 1022 | minimumVersion = 2.6.4; 1023 | }; 1024 | }; 1025 | 30D5E0BE2C74CF7C00295E53 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = { 1026 | isa = XCRemoteSwiftPackageReference; 1027 | repositoryURL = "https://github.com/firebase/firebase-ios-sdk.git"; 1028 | requirement = { 1029 | kind = upToNextMajorVersion; 1030 | minimumVersion = 11.1.0; 1031 | }; 1032 | }; 1033 | /* End XCRemoteSwiftPackageReference section */ 1034 | 1035 | /* Begin XCSwiftPackageProductDependency section */ 1036 | 306064662C6B8A7C00BC6350 /* KeyboardShortcuts */ = { 1037 | isa = XCSwiftPackageProductDependency; 1038 | package = 306064652C6B8A7C00BC6350 /* XCRemoteSwiftPackageReference "KeyboardShortcuts" */; 1039 | productName = KeyboardShortcuts; 1040 | }; 1041 | 30A0D8D82C775C1400F56515 /* Defaults */ = { 1042 | isa = XCSwiftPackageProductDependency; 1043 | package = 302B5FC12C7757890043351B /* XCRemoteSwiftPackageReference "Defaults" */; 1044 | productName = Defaults; 1045 | }; 1046 | 30D5E09F2C73A5D700295E53 /* LaunchAtLogin */ = { 1047 | isa = XCSwiftPackageProductDependency; 1048 | package = 30D5E09E2C73A5D700295E53 /* XCRemoteSwiftPackageReference "LaunchAtLogin-Modern" */; 1049 | productName = LaunchAtLogin; 1050 | }; 1051 | 30D5E0A72C74BD5C00295E53 /* Sparkle */ = { 1052 | isa = XCSwiftPackageProductDependency; 1053 | package = 30D5E0A62C74BD5B00295E53 /* XCRemoteSwiftPackageReference "Sparkle" */; 1054 | productName = Sparkle; 1055 | }; 1056 | /* End XCSwiftPackageProductDependency section */ 1057 | }; 1058 | rootObject = 30467E092C6B862A007A0F19 /* Project object */; 1059 | } 1060 | --------------------------------------------------------------------------------