Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 27 additions & 8 deletions Lexeme.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@
objectVersion = 77;
objects = {

/* Begin PBXBuildFile section */
847E4A882FDC4588007B4BFC /* lexeme.icon in Resources */ = {isa = PBXBuildFile; fileRef = 847E4A872FDC4588007B4BFC /* lexeme.icon */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
847E4A872FDC4588007B4BFC /* lexeme.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = lexeme.icon; sourceTree = "<group>"; };
84D4927C2FC00D9900ACEC1C /* Lexeme.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Lexeme.app; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */

Expand Down Expand Up @@ -47,6 +52,7 @@
children = (
84D4927E2FC00D9900ACEC1C /* Lexeme */,
84D4927D2FC00D9900ACEC1C /* Products */,
847E4A872FDC4588007B4BFC /* lexeme.icon */,
);
sourceTree = "<group>";
};
Expand Down Expand Up @@ -122,6 +128,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
847E4A882FDC4588007B4BFC /* lexeme.icon in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -141,8 +148,9 @@
84D4928B2FC00D9A00ACEC1C /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_APPICON_NAME = lexeme;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
CODE_SIGN_ENTITLEMENTS = Lexeme/Lexeme.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
Expand All @@ -153,9 +161,10 @@
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UILaunchStoryboardName = "";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 26.6;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = UIInterfaceOrientationPortrait;
IPHONEOS_DEPLOYMENT_TARGET = 26.5;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand All @@ -164,20 +173,25 @@
PRODUCT_BUNDLE_IDENTIFIER = black.harrison.Lexeme;
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TARGETED_DEVICE_FAMILY = 1;
};
name = Debug;
};
84D4928C2FC00D9A00ACEC1C /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_APPICON_NAME = lexeme;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
CODE_SIGN_ENTITLEMENTS = Lexeme/Lexeme.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
Expand All @@ -188,9 +202,10 @@
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UILaunchStoryboardName = "";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 26.6;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = UIInterfaceOrientationPortrait;
IPHONEOS_DEPLOYMENT_TARGET = 26.5;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand All @@ -199,12 +214,16 @@
PRODUCT_BUNDLE_IDENTIFIER = black.harrison.Lexeme;
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TARGETED_DEVICE_FAMILY = 1;
};
name = Release;
};
Expand Down
3 changes: 1 addition & 2 deletions Lexeme/Lexeme.swift
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import SwiftData
import SwiftUI

@main
struct Lexeme: App {
var body: some Scene {
WindowGroup {
DefinitionView()
LexemeView()
}
}
}
41 changes: 41 additions & 0 deletions Lexeme/Model/DictionarySourceSetting.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import Foundation

struct DictionarySourceSetting: Identifiable {
let id: String
let name: String
let detail: String
let requiresKey: Bool
var isEnabled: Bool
var apiKey: String
var keyWarning: String?

static let defaults = [
DictionarySourceSetting(
id: "dictionaryapi-dev",
name: "dictionaryapi.dev",
detail: "Free default source",
requiresKey: false,
isEnabled: true,
apiKey: "",
keyWarning: nil
),
DictionarySourceSetting(
id: "merriam-webster",
name: "Merriam-Webster",
detail: "User-provided API key",
requiresKey: true,
isEnabled: false,
apiKey: "",
keyWarning: nil
),
DictionarySourceSetting(
id: "oxford",
name: "Oxford",
detail: "User-provided API key",
requiresKey: true,
isEnabled: false,
apiKey: "",
keyWarning: nil
)
]
}
20 changes: 20 additions & 0 deletions Lexeme/Model/LookupCard.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Foundation

enum LookupCard: Identifiable, Equatable {
case definition(PlaceholderDefinition)
case notFound(String)

var id: String {
switch self {
case .definition(let definition): "definition-\(definition.id)"
case .notFound(let word): "missing-\(word)"
}
}

var lookupKey: String {
switch self {
case .definition(let definition): definition.id
case .notFound(let word): "missing-\(word)"
}
}
}
100 changes: 100 additions & 0 deletions Lexeme/Model/PlaceholderDefinition.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import Foundation

struct PlaceholderDefinition: Identifiable, Equatable {
let id: String
let word: String
let tags: [String]
let definition: String
let source: String
let rootWord: String?

var shortDefinition: String {
String(definition.prefix(96)) + (definition.count > 96 ? "..." : "")
}

init(
word: String,
tags: [String],
definition: String,
source: String,
rootWord: String? = nil
) {
self.id = Self.normalizedKey(word)
self.word = word
self.tags = tags
self.definition = definition
self.source = source
self.rootWord = rootWord
}

static func normalizedKey(_ value: String) -> String {
value
.trimmingCharacters(in: .whitespacesAndNewlines)
.trimmingCharacters(in: .punctuationCharacters)
.lowercased()
}

static let samples = [
PlaceholderDefinition(
word: "lexeme",
tags: ["Linguistics"],
definition: "A fundamental unit of meaning in a language, considered independently from the inflected forms it may take.",
source: "dictionaryapi.dev"
),
PlaceholderDefinition(
word: "orthodoxy",
tags: ["Philosophy", "Religion"],
definition: "The quality or state of conforming to an accepted doctrine, especially in belief or practice.",
source: "Oxford Dictionary",
rootWord: "orthodox"
),
PlaceholderDefinition(
word: "orthodox",
tags: ["Philosophy", "Religion"],
definition: "Conforming to what is generally or traditionally accepted as right or true; established and approved.",
source: "Oxford Dictionary"
),
PlaceholderDefinition(
word: "aporia",
tags: ["Philosophy", "Rhetoric"],
definition: "An irresolvable internal contradiction or logical disjunction in a text, argument, or theory.",
source: "Merriam-Webster"
),
PlaceholderDefinition(
word: "absurd",
tags: ["Philosophy", "Literature"],
definition: "A condition in which human beings search for meaning in a world that offers no final, rational answer.",
source: "dictionaryapi.dev"
),
PlaceholderDefinition(
word: "nihilism",
tags: ["Philosophy"],
definition: "The doctrine or attitude that values, meaning, and truth lack objective foundation.",
source: "Oxford Dictionary"
),
PlaceholderDefinition(
word: "dialectic",
tags: ["Philosophy"],
definition: "A method of reasoning that examines opposing ideas to expose tensions and move toward clearer understanding.",
source: "Merriam-Webster"
),
PlaceholderDefinition(
word: "entropy",
tags: ["Physics"],
definition: "A measure of disorder, uncertainty, or unavailable energy within a system.",
source: "dictionaryapi.dev"
),
PlaceholderDefinition(
word: "epistemology",
tags: ["Philosophy"],
definition: "The branch of philosophy concerned with knowledge, justification, belief, and the limits of understanding.",
source: "Oxford Dictionary"
),
PlaceholderDefinition(
word: "ontology",
tags: ["Philosophy"],
definition: "The study of being, existence, and the categories by which reality is understood.",
source: "Merriam-Webster"
)
]
}
71 changes: 71 additions & 0 deletions Lexeme/View/Components/DefinitionCard.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import SwiftUI

struct DefinitionCard: View {
let definition: PlaceholderDefinition
let isStarred: Bool
let onToggleStar: () -> Void
let onDefineRoot: (String) -> Void

private var visibleRootWord: String? {
guard let rootWord = definition.rootWord else { return nil }
return PlaceholderDefinition.normalizedKey(rootWord) == definition.id ? nil : rootWord
}

var body: some View {
VStack(alignment: .leading, spacing: 9) {
HStack(alignment: .firstTextBaseline) {
Text(definition.word)
.font(.system(size: 36, weight: .semibold, design: .serif))
.lineLimit(2)
.minimumScaleFactor(0.82)

Spacer(minLength: 12)

Button(action: onToggleStar) {
Image(systemName: isStarred ? "star.fill" : "star")
.font(.system(size: 19, weight: .medium))
.symbolRenderingMode(.hierarchical)
.foregroundStyle(isStarred ? .primary : .secondary)
.frame(width: 34, height: 34)
}
.buttonStyle(.plain)
.accessibilityLabel(isStarred ? "Unstar word" : "Star word")
}

if !definition.tags.isEmpty {
HStack(spacing: 8) {
ForEach(definition.tags, id: \.self) { tag in
Text(tag.uppercased())
.font(.caption2.weight(.semibold))
.tracking(0.7)
.foregroundStyle(Color(red: 0.42, green: 0.51, blue: 0.62))
}
}
}

Text(definition.definition)
.font(.body)
.lineSpacing(2)
.foregroundStyle(.primary)

if let visibleRootWord {
Button("Define \(visibleRootWord)") {
onDefineRoot(visibleRootWord)
}
.buttonStyle(.bordered)
.controlSize(.small)
.padding(.top, 2)
}

Text("Source: \(definition.source)")
.font(.caption2)
.foregroundStyle(.secondary)
.padding(.top, 2)
}
.padding(16)
.frame(maxWidth: .infinity, alignment: .leading)
.background(.regularMaterial, in: RoundedRectangle(cornerRadius: 22, style: .continuous))
.glassEffect(.regular, in: RoundedRectangle(cornerRadius: 22, style: .continuous))
.shadow(color: .black.opacity(0.12), radius: 18, y: 8)
}
}
20 changes: 20 additions & 0 deletions Lexeme/View/Components/DefinitionRow.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import SwiftUI

struct DefinitionRow: View {
let word: String
let definition: String

var body: some View {
VStack(alignment: .leading, spacing: 4) {
Text(word)
.font(.system(size: 18, weight: .semibold, design: .serif))
.foregroundStyle(.primary)

Text(definition)
.font(.subheadline)
.foregroundStyle(.secondary)
.lineLimit(2)
}
.padding(.vertical, 4)
}
}
Loading