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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 26 additions & 0 deletions Scrabbdict/Extensions/Bundle+Extension.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// Scrabbdict
// Copyright © 2026 Piotr Sochalewski.
// Licensed under the Apache License, Version 2.0.
//

import Foundation

extension Bundle {
static func localizationBundle(for locale: Locale) -> Bundle {
guard
let languageCode = locale.language.languageCode?.identifier,
let path = Bundle.main.path(forResource: languageCode, ofType: "lproj"),
let bundle = Bundle(path: path)
else {
return .main
}

return bundle
}

func localizedString(forKey key: String, locale: Locale, arguments: CVarArg...) -> String {
let format = localizedString(forKey: key, value: nil, table: nil)
return String(format: format, locale: locale, arguments: arguments)
}
}
13 changes: 0 additions & 13 deletions Scrabbdict/Extensions/Font+Extension.swift

This file was deleted.

5 changes: 5 additions & 0 deletions Scrabbdict/Models/SearchMode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ enum SearchMode: Int, CaseIterable, Hashable, Sendable {
}
}

var accessibilityDescription: String {
String(localized: description)
.replacingOccurrences(of: "?", with: String(localized: .searchModeQuestionMark))
}

var name: String {
switch self {
case .auto: "auto"
Expand Down
31 changes: 31 additions & 0 deletions Scrabbdict/Modules/App/ScrabbdictApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,37 @@ struct ScrabbdictApp: App {
ScrabbdictFeature()
}
)
#if DEBUG && targetEnvironment(simulator)
.dynamicTypeSizeOverlay()
#endif
}
}
}

#if DEBUG && targetEnvironment(simulator)
private extension View {
func dynamicTypeSizeOverlay() -> some View {
modifier(DynamicTypeSizeOverlayModifier())
}
}

private struct DynamicTypeSizeOverlayModifier: ViewModifier {
@Environment(\.dynamicTypeSize) var dynamicTypeSize

func body(content: Content) -> some View {
content
.overlay(alignment: .topLeading) {
Text(verbatim: .init(describing: dynamicTypeSize))
.font(.system(size: 12).monospaced())
.foregroundStyle(Color.white)
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(Color.black.opacity(0.72), in: .capsule)
.padding(12)
.opacity(dynamicTypeSize == .large ? 0 : 1)
.allowsHitTesting(false)
.accessibilityHidden(true)
}
}
}
#endif
106 changes: 61 additions & 45 deletions Scrabbdict/Modules/Scrabbdict/ScrabbdictView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,32 @@ struct ScrabbdictView: View {
@FocusState var isSearchFocused: Bool
@Environment(\.horizontalSizeClass) var horizontalSizeClass

private var contentMaxWidth: CGFloat {
horizontalSizeClass == .regular ? 560 : .infinity
}

var body: some View {
NavigationStack {
content
.onAppear { send(.loaded) }
.navigationTitle(.init(verbatim: ""))
.navigationBarTitleDisplayMode(.inline)
.toolbar {
titleToolbar
settingsToolbar
}
.bind($store.isSearchFocused, to: $isSearchFocused)
.alert(item: $store.alert) { alert in
Alert(
title: Text(alert.title),
message: Text(alert.message),
dismissButton: .default(Text(.alertOk))
)
}
.sheet(item: $store.scope(state: \.destination?.settings, action: \.destination.settings)) { settingsStore in
SettingsView(store: settingsStore)
.presentationDetents(horizontalSizeClass == .regular ? [.large] : [.medium, .large])
}
}
.tint(Color.brandAccent)
}
Expand Down Expand Up @@ -50,55 +67,44 @@ private extension ScrabbdictView {
Text(verbatim: "dict")
.foregroundStyle(Color.brandAccent)
}
.font(.system(size: 24, weight: .bold))
.font(.title2.weight(.bold))
.lineLimit(1)
.accessibilityElement(children: .ignore)
.accessibilityLabel(.init(verbatim: "Scrabbdict"))
}
}

var content: some View {
VStack(spacing: 32) {
SearchBarView(
text: $store.query,
searchMode: $store.searchMode,
isSearchModePickerExpanded: $store.isSearchModePickerExpanded,
isFocused: $isSearchFocused,
onClear: { send(.clearButtonTapped) },
onSearchModePickerTapped: { send(.searchModePickerTapped) },
onSearchModeSelected: { send(.searchModeSelected($0)) },
onSearch: { send(.searchButtonTapped) }
)
.zIndex(1)
GeometryReader { proxy in
ZStack {
ScrabbleTableBackground()

resultArea
.animation(.easeOut(duration: 0.12), value: store.search)
.zIndex(0)
VStack(spacing: 32) {
SearchBarView(
text: $store.query,
searchMode: $store.searchMode,
isSearchModePickerExpanded: $store.isSearchModePickerExpanded,
isFocused: $isSearchFocused,
onClear: { send(.clearButtonTapped) },
onSearchModePickerTapped: { send(.searchModePickerTapped) },
onSearchModeSelected: { send(.searchModeSelected($0)) },
onSearch: { send(.searchButtonTapped) },
searchModePickerAvailableHeight: proxy.size.height
)
.dynamicTypeSize(...DynamicTypeSize.xxxLarge)
.zIndex(1)

resultArea
.animation(.easeOut(duration: 0.12), value: store.search)
.zIndex(0)
}
.padding(.horizontal, 26)
.frame(maxWidth: contentMaxWidth, maxHeight: .infinity, alignment: .top)
}
}
.padding(.horizontal, 16)
.frame(maxWidth: 420, maxHeight: .infinity, alignment: .top)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
.background(background)
.ignoresSafeArea(.keyboard, edges: .bottom)
.contentShape(Rectangle())
.onTapGesture { send(.backgroundTapped) }
.bind($store.isSearchFocused, to: $isSearchFocused)
.onAppear { send(.loaded) }
.alert(item: $store.alert) { alert in
Alert(
title: Text(alert.title),
message: Text(alert.message),
dismissButton: .default(Text(.alertOk))
)
}
.sheet(item: $store.scope(state: \.destination?.settings, action: \.destination.settings)) { settingsStore in
SettingsView(store: settingsStore)
.presentationDetents(horizontalSizeClass == .regular ? [.large] : [.medium, .large])
}
}

var background: some View {
ScrabbleTableBackground()
}

var settingsButton: some View {
Expand All @@ -121,14 +127,8 @@ private extension ScrabbdictView {
SearchSkeletonView(variant: searchSkeletonVariant)
.transition(.opacity)
} else if let result = store.result {
VStack(spacing: 24) {
ResultCardView(result: result)

if store.showsRackWordsButton {
rackWordsButton
}
}
.transition(.opacity)
resultCardArea(result: result)
.transition(.opacity)
} else if !store.words.isEmpty {
WordsListView(words: store.words)
.transition(.opacity)
Expand All @@ -140,6 +140,22 @@ private extension ScrabbdictView {
}
}

func resultCardArea(result: ValidatorResult) -> some View {
ScrollView(.vertical) {
VStack(spacing: 24) {
ResultCardView(result: result)

if store.showsRackWordsButton {
rackWordsButton
}
}
.frame(maxWidth: .infinity)
.padding(.bottom, 24)
}
.scrollBounceBehavior(.basedOnSize)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
}

var rackWordsButton: some View {
RackWordsButton {
send(.rackWordsButtonTapped)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,23 @@ struct EmptySearchResultView: View {
var body: some View {
VStack(spacing: 10) {
Image(systemName: "magnifyingglass")
.font(.system(size: 28, weight: .semibold))
.font(.title.weight(.semibold))
.foregroundStyle(Color.secondaryText)
.frame(width: 44, height: 44)

Text(result.title)
.font(.system(size: 18, weight: .semibold))
.font(.headline.weight(.semibold))
.foregroundStyle(Color.primaryInk)

Text(result.message)
.font(.system(size: 15, weight: .medium))
.font(.subheadline.weight(.medium))
.foregroundStyle(Color.secondaryText)
.multilineTextAlignment(.center)
.fixedSize(horizontal: false, vertical: true)
}
.multilineTextAlignment(.center)
.padding(.horizontal, 24)
.padding(.vertical, 28)
.frame(maxWidth: 337)
.frame(maxWidth: .infinity)
}
}

Expand Down
9 changes: 5 additions & 4 deletions Scrabbdict/Modules/Scrabbdict/Subviews/RackWordsButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ struct RackWordsButton: View {
var body: some View {
Button(action: action) {
Label(.rackWordsButtonTitle, systemImage: "magnifyingglass")
.font(.system(size: 16, weight: .semibold))
.font(.callout.weight(.semibold))
.foregroundStyle(.white)
.padding(14)
.frame(maxWidth: 268)
.frame(height: 46)
.background(Color.brandAccent, in: RoundedRectangle(cornerRadius: 23, style: .continuous))
.shadow(color: Color.appShadow, radius: 10, x: 0, y: 5)
.background(Color.brandAccent)
.clipShape(.capsule)
}
.buttonStyle(.plain)
.shadow(color: Color.appShadow, radius: 10, x: 0, y: 5)
}
}

Expand Down
Loading