Skip to content
Open
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,12 @@ Package.resolved
*.plist
*.xcprivacy
.DS_Store

# Documentation artifacts
docs/

# Local agent instruction file
AGENTS.md

# Local architecture note
ARCHITECTURE.md
186 changes: 85 additions & 101 deletions COMFIE.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion COMFIE.xcodeproj/xcshareddata/xcschemes/COMFIE.xcscheme
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1620"
LastUpgradeVersion = "2620"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
1 change: 1 addition & 0 deletions COMFIE/App/COMFIEApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ struct COMFIEApp: App {

init() {
let router = Router()
UITestBootstrap.applyIfNeeded(router: router)
self.router = router
self.diContainer = DIContainer(router: router)
}
Expand Down
27 changes: 27 additions & 0 deletions COMFIE/App/UITestBootstrap.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// UITestBootstrap.swift
// COMFIE
//
// Created by zaehorang on 2/13/26.
//

import Foundation

#if DEBUG
enum UITestBootstrap {
private static let uiTestingArgument = "-ui-testing"

static func applyIfNeeded(router: Router, userDefaults: UserDefaults = .standard) {
guard ProcessInfo.processInfo.arguments.contains(uiTestingArgument) else { return }

userDefaults.set(true, forKey: UserDefaultsConstants.Keys.hasEverOnboarded.rawValue)
userDefaults.set(true, forKey: UserDefaultsConstants.Keys.hasSeenTutorial.rawValue)
router.hasEverOnboarded = true
router.isLoadingViewFinished = true
}
}
#else
enum UITestBootstrap {
static func applyIfNeeded(router _: Router, userDefaults _: UserDefaults = .standard) {}
}
#endif
1 change: 1 addition & 0 deletions COMFIE/Data/UserDefaults/UserDefaultsConstants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ import Foundation
struct UserDefaultsConstants {
enum Keys: String {
case hasEverOnboarded = "hasEverOnboarded"
case hasSeenTutorial = "hasSeenTutorial"
}
}
73 changes: 62 additions & 11 deletions COMFIE/Domain/TextToEmoji/Model/EmojiCharacter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,77 @@
//
// Created by zaehorang on 4/15/25.
//
import Foundation

/// 각 문자를 하나의 이모지와 매칭하는 구조입니다.
/// 예: 'a' → 🐯, '한' → 🐯
struct EmojiCharacter {
var originalCharacter: Character
var emojiCharacter: Character?

mutating func setEmojiCharacter() {
guard emojiCharacter == nil else { return }

if originalCharacter == " "
|| originalCharacter == "\n"
|| isEmoji(originalCharacter) {
emojiCharacter = originalCharacter
} else {

if Self.isEmojiConvertibleCharacter(originalCharacter) {
emojiCharacter = EmojiPool.getRandomEmoji()
} else {
emojiCharacter = originalCharacter
}
}

private func isEmoji(_ char: Character) -> Bool {

static func isEmojiConvertibleCharacter(_ char: Character) -> Bool {
if isWhitespaceOrNewline(char) || isEmoji(char) {
return false
}

let scalars = char.unicodeScalars
if isLetterCharacter(scalars) {
return true
}

return isNumberPunctuationOrSymbolCharacter(scalars)
}

private static func isWhitespaceOrNewline(_ char: Character) -> Bool {
char == " " || char == "\n"
}

private static func isLetterCharacter(_ scalars: String.UnicodeScalarView) -> Bool {
guard !scalars.isEmpty else { return false }

var hasLetterCore = false
for scalar in scalars {
switch scalar.properties.generalCategory {
case .uppercaseLetter, .lowercaseLetter, .titlecaseLetter, .modifierLetter, .otherLetter:
hasLetterCore = true
case .nonspacingMark, .spacingMark, .enclosingMark, .format:
continue
default:
return false
}
}
return hasLetterCore
}

private static func isNumberPunctuationOrSymbolCharacter(_ scalars: String.UnicodeScalarView) -> Bool {
guard !scalars.isEmpty else { return false }

var hasCoreCategory = false
for scalar in scalars {
switch scalar.properties.generalCategory {
case .decimalNumber, .letterNumber, .otherNumber,
.connectorPunctuation, .dashPunctuation, .openPunctuation, .closePunctuation,
.initialPunctuation, .finalPunctuation, .otherPunctuation,
.mathSymbol, .currencySymbol, .modifierSymbol, .otherSymbol:
hasCoreCategory = true
case .nonspacingMark, .spacingMark, .enclosingMark, .format:
continue
default:
return false
}
}
return hasCoreCategory
}

private static func isEmoji(_ char: Character) -> Bool {
char.unicodeScalars
.contains(where: { $0.properties.isEmojiPresentation })
&& char.unicodeScalars
Expand Down
Loading