Skip to content
Open
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
15 changes: 7 additions & 8 deletions Sources/SimpleKeyboard/Helper/ThemingModifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,21 @@

import SwiftUI

struct OuterKeyboardThemingModifier<Background: View>: ViewModifier {
struct OuterKeyboardThemingModifier: ViewModifier {
var theme: KeyboardTheme
var backroundColor: Background

func body(content: Content) -> some View {
if theme == .system {
content
.padding(10)
.background(backroundColor)
} else if theme == .floating {
if theme == .floating {
content
.cornerRadius(25, corners: [.bottomLeft, .bottomRight])
.padding(10)
.background(backroundColor)
.background(theme.keyboardBackground)
.cornerRadius(25)
.padding(10)
} else {
content
.padding(10)
.background(theme.keyboardBackground)
}
}
}
13 changes: 8 additions & 5 deletions Sources/SimpleKeyboard/Helper/View+Background.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import SwiftUI

public enum KeyboardTheme {
case system, floating
case system, floating, clear
}

protocol ThemeableView {
Expand All @@ -24,12 +24,15 @@ private typealias PlatformColor = NSColor
extension NSColor { static var systemGray3: NSColor { NSColor.systemGray } }
#endif

extension View where Self: ThemeableView {
extension KeyboardTheme {
@ViewBuilder
var keyboardBackground: some View {
if #available(iOS 15.0, macOS 12.0, *) {
return AnyView(EmptyView().background(.ultraThinMaterial))
if self == .clear {
Color.clear
} else if #available(iOS 15.0, macOS 12.0, *) {
Rectangle().fill(.clear).background(.ultraThinMaterial)
} else {
return AnyView(Color(PlatformColor.systemGray3.withAlphaComponent(0.75)))
Color(PlatformColor.systemGray3.withAlphaComponent(0.75))
}
}
}
Expand Down
75 changes: 71 additions & 4 deletions Sources/SimpleKeyboard/Models/Language.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,39 @@ public enum Language: CaseIterable {
}

case english, german, spanish, french, russian, hindi
case swedish, danish, norwegian, finnish
case korean

/// General purpose layout using QWERTY with the common diacritics
case latinWithAccents

var alternateKeys: [String: String] {
switch self {
case .german: return ["a": "ä", "o": "ö", "u": "ü"]
// spanish also has ü
case .spanish: return ["e": "é", "a": "á", "i": "í", "o": "ó", "u": "ú", "n": "ñ"]
case .french: return ["e": "é", "a": "à", "u": "ù", "i": "î", "o": "ô", "c": "ç"]
// Nynorsk uses several letters with diacritic signs: é, è, ê, ó, ò, â, and ô. The diacritic signs are not compulsory
// Danish has no compulsory diacritics, but allows the use of an acute accent. Most often, an accent on e.
case .danish, .norwegian: return ["e": "é"]
// Though not in the official alphabet, á is a Swedish (old-fashioned) letter. In native Swedish personal names, ü and è and others are also used.
case .swedish: return ["a":"á", "u":"ü", "e": "è"]
case .finnish: return ["s": "š", "z": "ž"]
case .korean: return ["ㅂ":"ㅃ", "ㅈ":"ㅉ", "ㄷ":"ㄸ", "ㄱ":"ㄲ", "ㅅ":"ㅆ", "ㅐ":"ㅒ", "ㅔ":"ㅖ"]
case .latinWithAccents:

// 300 = Grave; 301 = Acute;
// 302 = Circumflex; 303 = Tilde;
// 308 = Diaeresis; 30A = Ring;
// 327 = Cedilla; 30C = Caron;
return [
"\u{0300}": "\u{0301}",
"\u{0302}": "\u{0303}",
"\u{0308}": "\u{030A}",
"\u{0327}": "\u{030C}",]
default: return [:]
}
}

func rows(areUppercased: Bool) -> [[String]] {
var result = [[String]]()
Expand All @@ -34,14 +67,14 @@ public enum Language: CaseIterable {
]
case .german:
result = [
["q", "w", "e", "r", "t", "z", "u", "i", "o", "p"],
["a", "s", "d", "f", "g", "h", "j", "k", "l"],
["q", "w", "e", "r", "t", "z", "u", "i", "o", "p"], // "ü"],
["a", "s", "d", "f", "g", "h", "j", "k", "l"], // "ö", "ä"],
["y", "x", "c", "v", "b", "n", "m"]
]
case .spanish:
result = [
["q", "w", "e", "r", "t", "y", "u", "i", "o", "p"],
["a", "s", "d", "f", "g", "h", "j", "k", "l", "ñ"],
["a", "s", "d", "f", "g", "h", "j", "k", "l", "ü"],
["z", "x", "c", "v", "b", "n", "m"]
]
case .french:
Expand All @@ -62,10 +95,44 @@ public enum Language: CaseIterable {
["ो", "े", "्", "ि", "ु", "प", "र", "क", "त", "च"],
["ं", "म", "न", "व", "ल", "स", "य"]
]
case .swedish, .finnish:
result = [
["q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "å"],
["a", "s", "d", "f", "g", "h", "j", "k", "l", "ö", "ä"],
["z", "x", "c", "v", "b", "n", "m"]
]
case .danish:
result = [
["q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "å"],
["a", "s", "d", "f", "g", "h", "j", "k", "l", "æ", "ø"],
["z", "x", "c", "v", "b", "n", "m"]
]
case .norwegian:
result = [
["q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "å"],
["a", "s", "d", "f", "g", "h", "j", "k", "l", "ø", "æ"],
["z", "x", "c", "v", "b", "n", "m"]
]

case .latinWithAccents:
result = [
["q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "\u{0300}"],
["a", "s", "d", "f", "g", "h", "j", "k", "l", "\u{0302}", "\u{0308}"],
["z", "x", "c", "v", "b", "n", "m", "\u{0327}"]
]
case .korean:
result = [
["ㅂ","ㅈ","ㄷ","ㄱ","ㅅ","ㅛ","ㅕ","ㅑ","ㅐ","ㅔ"],
["ㅁ","ㄴ","ㅇ","ㄹ","ㅎ","ㅗ","ㅓ","ㅏ","ㅣ"],
["ㅋ","ㅌ","ㅊ","ㅍ","ㅠ","ㅜ","ㅡ"]
]
}

if areUppercased {
result = result.map { $0.map { $0.uppercased() } }
result = result.map { $0.map {
let upper = $0.uppercased()
return $0 != upper ? upper : alternateKeys[$0] ?? $0
} }
}

return result
Expand Down
59 changes: 34 additions & 25 deletions Sources/SimpleKeyboard/Views/KeyButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,18 @@ protocol ClickableKey {
extension ClickableKey {
func didClick() {
#if canImport(UIKit)
let generator = UIImpactFeedbackGenerator(style: .light)
generator.impactOccurred()
UIDevice.current.playInputClick()
#endif
}
}

struct ShiftKeyButton: View {
struct ShiftKeyButton: View, ClickableKey {
@Binding var isUpperCase: Bool!

var body: some View {
Button(action: { self.isUpperCase?.toggle() }) {
Button(action: { self.isUpperCase?.toggle(); didClick() }) {
if #available(iOS 15, macOS 12, *) {
AnyView(Image(systemName: isUpperCase ? "shift.fill" : "shift")
.dynamicTypeSize(.large))
Expand All @@ -45,13 +47,11 @@ struct ShiftKeyButton: View {
struct KeyButton: View, ClickableKey {
@Binding var text: String
var letter: String
var alternateLetter: String?
@Environment(\.colorScheme) var colorScheme

var body: some View {
Button(action: {
self.text.append(self.letter)
didClick()
}) {
Button(action: { /* use Gesture */}) {
Text(letter)
.font(.system(size: 25))
.fixedSize()
Expand All @@ -64,11 +64,24 @@ struct KeyButton: View, ClickableKey {
.cornerRadius(5)
.shadow(color: .black, radius: 0, y: 1)
}
.highPriorityGesture(TapGesture().onEnded({ _ in
self.text.append(self.letter)
didClick()
}))
.simultaneousGesture(LongPressGesture().onEnded({ _ in
guard let alternateLetter else { return }
self.text.append(alternateLetter)
didClick()
}))
}
}

struct FRAccentKeyButton: View {
/// Replaces the last typed character with another (special) character. E.g. "a" -> "ä"
@available(*, deprecated, message: "Use `Language.alternateKeys` instead")
struct AccentKeyButton: View, ClickableKey {
@Binding var text: String
/// The lookup for modified characters all lowercased. E.g. `["a": "ä"]`
var modifiedLetters: [Character: String]

var body: some View {
Button(action: {
Expand All @@ -88,22 +101,17 @@ struct FRAccentKeyButton: View {
}

internal func action() {
var modified = ""
let suffix = self.text.popLast()
switch suffix {
case "a": modified = "à"
case "e": modified = "é"
case "i": modified = "î"
case "u": modified = "û"
case "o": modified = "ô"
case "c": modified = "ç"
default:
modified = "’"
if let suffix = suffix {
self.text.append(suffix)
}
defer { didClick() }
guard let suffix = self.text.popLast() else {
return text.append("’")
}

guard var modified = modifiedLetters[Character(suffix.lowercased())] else {
return text.append(String(suffix) + "’")
}
if suffix.isUppercase {
modified = modified.uppercased()
}
text.append(modified)
}
}
Expand Down Expand Up @@ -136,13 +144,14 @@ struct SpaceKeyButton: View, ClickableKey {
}
}

struct DeleteKeyButton: View {
struct DeleteKeyButton: View, ClickableKey {
@Binding var text: String

var body: some View {
Button(action: {
guard !self.text.isEmpty else { return }
_ = self.text.removeLast()
didClick()
}) {
if #available(iOS 15, macOS 12, *) {
AnyView(Image(systemName: "delete.left").dynamicTypeSize(.large))
Expand All @@ -165,7 +174,7 @@ struct DeleteKeyButton: View {
}
}

struct ActionKeyButton: View {
struct ActionKeyButton: View, ClickableKey {
@State var icon: Icon
var action: () -> Void

Expand All @@ -178,7 +187,7 @@ struct ActionKeyButton: View {
}

var body: some View {
Button(action: self.action) {
Button(action: { self.action(); didClick() }) {
iconView
.padding()
.frame(minWidth: 100, maxWidth: .infinity)
Expand All @@ -200,7 +209,7 @@ public enum Icon {
case .search:
if #available(iOS 14, macOS 11, *) {
return AnyView(Image(systemName: "magnifyingglass"))
}else {
} else {
return AnyView(Text("Search", bundle: .module))
}
case .go: return AnyView(Text("Go!", bundle: .module))
Expand Down
4 changes: 2 additions & 2 deletions Sources/SimpleKeyboard/Views/SimpleKeyboard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public struct SimpleKeyboard: View, ThemeableView {
}
}
HStack {
if let actionButton = actionButton {
if let actionButton {
ActionKeyButton(icon: actionButton) {
self.action?()
}
Expand All @@ -52,7 +52,7 @@ public struct SimpleKeyboard: View, ThemeableView {

public var body: some View {
if isShown {
content.modifier(OuterKeyboardThemingModifier(theme: theme, backroundColor: keyboardBackground))
content.modifier(OuterKeyboardThemingModifier(theme: theme))
}
}
}
Expand Down
21 changes: 13 additions & 8 deletions Sources/SimpleKeyboard/Views/SimpleStandardKeyboard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ import SwiftUI

public struct SimpleStandardKeyboard: View, ThemeableView {
var theme: KeyboardTheme { settings.theme }

@ViewBuilder
var bgColor: some View {
if #available(iOS 15.0, macOS 12.0, *) {
Rectangle().fill(.clear).background(.ultraThinMaterial)
} else {
Color.clear
}
}

@ObservedObject var settings: KeyboardSettings

Expand Down Expand Up @@ -63,10 +72,6 @@ public struct SimpleStandardKeyboard: View, ThemeableView {
Spacer(minLength: 2)
.frame(maxWidth: 15)
.layoutPriority(2)
if settings.language == .french {
FRAccentKeyButton(text: $settings.text)
Spacer()
}
DeleteKeyButton(text: self.$settings.text)
}
} else if idx == 1 {
Expand All @@ -82,7 +87,7 @@ public struct SimpleStandardKeyboard: View, ThemeableView {
let rows = self.settings.language.rows(areUppercased: settings.isUpperCase ?? false)[index]
return ForEach(rows, id: \.self) { key in
Spacer(minLength: settings.language.spacing)
KeyButton(text: self.$settings.text, letter: key)
KeyButton(text: self.$settings.text, letter: key, alternateLetter: settings.language.alternateKeys[key])
Spacer(minLength: settings.language.spacing)
}
}
Expand All @@ -98,7 +103,7 @@ public struct SimpleStandardKeyboard: View, ThemeableView {
spaceRow
}
.transition(.move(edge: .bottom).combined(with: .opacity))
.modifier(OuterKeyboardThemingModifier(theme: theme, backroundColor: keyboardBackground))
.modifier(OuterKeyboardThemingModifier(theme: theme))
}
}
}
Expand All @@ -120,9 +125,9 @@ struct SimpleStandardKeyboard_Previews: PreviewProvider {
isUpperCase: true))
SimpleStandardKeyboard(
settings: KeyboardSettings(
language: .english,
language: .latinWithAccents,
textInput: nil,
theme: .system,
theme: .clear,
actionButton: .search,
showNumbers: true,
showSpace: false,
Expand Down
12 changes: 12 additions & 0 deletions Sources/SimpleKeyboard/l10n/da.lproj/Localizable.strings
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"space" = "mellemrum";

"Up" = "St";

"lw" = "sm";

"Done!" = "Udført!";

"Search" = "Søg";

"Go!" = "Gå!";

Loading