Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0007459eef | |||
| 152dcd9aac |
@@ -1,4 +1,4 @@
|
|||||||
import UIKit
|
import Foundation
|
||||||
|
|
||||||
struct WordBank: Codable {
|
struct WordBank: Codable {
|
||||||
let version: Int
|
let version: Int
|
||||||
@@ -19,9 +19,4 @@ struct Word: Codable, Identifiable, Hashable {
|
|||||||
let meaning: String
|
let meaning: String
|
||||||
let example: String
|
let example: String
|
||||||
let exampleMeaning: String
|
let exampleMeaning: String
|
||||||
let colorHex: String?
|
|
||||||
|
|
||||||
var color: UIColor? {
|
|
||||||
colorHex.flatMap { UIColor(hex: $0) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,26 +6,26 @@
|
|||||||
"name": "颜色",
|
"name": "颜色",
|
||||||
"icon": "paintpalette.fill",
|
"icon": "paintpalette.fill",
|
||||||
"words": [
|
"words": [
|
||||||
{ "word": "red", "colorHex": "#E53935", "phonetic": "/red/", "meaning": "红色", "example": "The apple is red.", "exampleMeaning": "这个苹果是红色的。" },
|
{ "word": "red", "phonetic": "/red/", "meaning": "红色", "example": "The apple is red.", "exampleMeaning": "这个苹果是红色的。" },
|
||||||
{ "word": "blue", "colorHex": "#1E88E5", "phonetic": "/bluː/", "meaning": "蓝色", "example": "The sky is blue.", "exampleMeaning": "天空是蓝色的。" },
|
{ "word": "blue", "phonetic": "/bluː/", "meaning": "蓝色", "example": "The sky is blue.", "exampleMeaning": "天空是蓝色的。" },
|
||||||
{ "word": "green", "colorHex": "#43A047", "phonetic": "/ɡriːn/", "meaning": "绿色", "example": "The grass is green.", "exampleMeaning": "草地是绿色的。" },
|
{ "word": "green", "phonetic": "/ɡriːn/", "meaning": "绿色", "example": "The grass is green.", "exampleMeaning": "草地是绿色的。" },
|
||||||
{ "word": "yellow", "colorHex": "#FDD835", "phonetic": "/ˈjeloʊ/", "meaning": "黄色", "example": "The banana is yellow.", "exampleMeaning": "香蕉是黄色的。" },
|
{ "word": "yellow", "phonetic": "/ˈjeloʊ/", "meaning": "黄色", "example": "The banana is yellow.", "exampleMeaning": "香蕉是黄色的。" },
|
||||||
{ "word": "black", "colorHex": "#212121", "phonetic": "/blæk/", "meaning": "黑色", "example": "The cat is black.", "exampleMeaning": "这只猫是黑色的。" },
|
{ "word": "black", "phonetic": "/blæk/", "meaning": "黑色", "example": "The cat is black.", "exampleMeaning": "这只猫是黑色的。" },
|
||||||
{ "word": "white", "colorHex": "#FFFFFF", "phonetic": "/waɪt/", "meaning": "白色", "example": "The snow is white.", "exampleMeaning": "雪是白色的。" },
|
{ "word": "white", "phonetic": "/waɪt/", "meaning": "白色", "example": "The snow is white.", "exampleMeaning": "雪是白色的。" },
|
||||||
{ "word": "orange", "colorHex": "#FB8C00", "phonetic": "/ˈɔːrɪndʒ/", "meaning": "橙色", "example": "The orange is orange.", "exampleMeaning": "橙子是橙色的。" },
|
{ "word": "orange", "phonetic": "/ˈɔːrɪndʒ/", "meaning": "橙色", "example": "The orange is orange.", "exampleMeaning": "橙子是橙色的。" },
|
||||||
{ "word": "purple", "colorHex": "#8E24AA", "phonetic": "/ˈpɜːrpl/", "meaning": "紫色", "example": "The grapes are purple.", "exampleMeaning": "葡萄是紫色的。" },
|
{ "word": "purple", "phonetic": "/ˈpɜːrpl/", "meaning": "紫色", "example": "The grapes are purple.", "exampleMeaning": "葡萄是紫色的。" },
|
||||||
{ "word": "pink", "colorHex": "#EC407A", "phonetic": "/pɪŋk/", "meaning": "粉色", "example": "The flower is pink.", "exampleMeaning": "这朵花是粉色的。" },
|
{ "word": "pink", "phonetic": "/pɪŋk/", "meaning": "粉色", "example": "The flower is pink.", "exampleMeaning": "这朵花是粉色的。" },
|
||||||
{ "word": "brown", "colorHex": "#6D4C41", "phonetic": "/braʊn/", "meaning": "棕色", "example": "The bear is brown.", "exampleMeaning": "这只熊是棕色的。" },
|
{ "word": "brown", "phonetic": "/braʊn/", "meaning": "棕色", "example": "The bear is brown.", "exampleMeaning": "这只熊是棕色的。" },
|
||||||
{ "word": "gray", "colorHex": "#9E9E9E", "phonetic": "/ɡreɪ/", "meaning": "灰色", "example": "The elephant is gray.", "exampleMeaning": "大象是灰色的。" },
|
{ "word": "gray", "phonetic": "/ɡreɪ/", "meaning": "灰色", "example": "The elephant is gray.", "exampleMeaning": "大象是灰色的。" },
|
||||||
{ "word": "gold", "colorHex": "#FFD700", "phonetic": "/ɡoʊld/", "meaning": "金色", "example": "The ring is gold.", "exampleMeaning": "这枚戒指是金色的。" },
|
{ "word": "gold", "phonetic": "/ɡoʊld/", "meaning": "金色", "example": "The ring is gold.", "exampleMeaning": "这枚戒指是金色的。" },
|
||||||
{ "word": "silver", "colorHex": "#C0C0C0", "phonetic": "/ˈsɪlvər/", "meaning": "银色", "example": "The car is silver.", "exampleMeaning": "这辆车是银色的。" },
|
{ "word": "silver", "phonetic": "/ˈsɪlvər/", "meaning": "银色", "example": "The car is silver.", "exampleMeaning": "这辆车是银色的。" },
|
||||||
{ "word": "cyan", "colorHex": "#00BCD4", "phonetic": "/ˈsaɪæn/", "meaning": "青色", "example": "The sea looks cyan.", "exampleMeaning": "大海看起来是青色的。" },
|
{ "word": "cyan", "phonetic": "/ˈsaɪæn/", "meaning": "青色", "example": "The sea looks cyan.", "exampleMeaning": "大海看起来是青色的。" },
|
||||||
{ "word": "magenta", "colorHex": "#E040FB", "phonetic": "/məˈdʒentə/", "meaning": "品红色", "example": "The dress is magenta.", "exampleMeaning": "这条裙子是品红色的。" },
|
{ "word": "magenta", "phonetic": "/məˈdʒentə/", "meaning": "品红色", "example": "The dress is magenta.", "exampleMeaning": "这条裙子是品红色的。" },
|
||||||
{ "word": "beige", "colorHex": "#F5F5DC", "phonetic": "/beɪʒ/", "meaning": "米色", "example": "The wall is beige.", "exampleMeaning": "这面墙是米色的。" },
|
{ "word": "beige", "phonetic": "/beɪʒ/", "meaning": "米色", "example": "The wall is beige.", "exampleMeaning": "这面墙是米色的。" },
|
||||||
{ "word": "turquoise", "colorHex": "#40E0D0", "phonetic": "/ˈtɜːrkwɔɪz/", "meaning": "绿松石色", "example": "The lake is turquoise.", "exampleMeaning": "湖水是绿松石色的。" },
|
{ "word": "turquoise", "phonetic": "/ˈtɜːrkwɔɪz/", "meaning": "绿松石色", "example": "The lake is turquoise.", "exampleMeaning": "湖水是绿松石色的。" },
|
||||||
{ "word": "violet", "colorHex": "#7C4DFF", "phonetic": "/ˈvaɪələt/", "meaning": "紫罗兰色", "example": "The violet flower is beautiful.", "exampleMeaning": "紫罗兰色的花很美。" },
|
{ "word": "violet", "phonetic": "/ˈvaɪələt/", "meaning": "紫罗兰色", "example": "The violet flower is beautiful.", "exampleMeaning": "紫罗兰色的花很美。" },
|
||||||
{ "word": "navy", "colorHex": "#1A237E", "phonetic": "/ˈneɪvi/", "meaning": "藏青色", "example": "He wears a navy coat.", "exampleMeaning": "他穿着一件藏青色外套。" },
|
{ "word": "navy", "phonetic": "/ˈneɪvi/", "meaning": "藏青色", "example": "He wears a navy coat.", "exampleMeaning": "他穿着一件藏青色外套。" },
|
||||||
{ "word": "ivory", "colorHex": "#FFFFF0", "phonetic": "/ˈaɪvəri/", "meaning": "象牙色", "example": "The keys are ivory.", "exampleMeaning": "琴键是象牙色的。" }
|
{ "word": "ivory", "phonetic": "/ˈaɪvəri/", "meaning": "象牙色", "example": "The keys are ivory.", "exampleMeaning": "琴键是象牙色的。" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -34,18 +34,6 @@ enum Theme {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension UIColor {
|
|
||||||
convenience init?(hex: String) {
|
|
||||||
var value = hex.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
||||||
if value.hasPrefix("#") { value.removeFirst() }
|
|
||||||
guard value.count == 6, let rgb = UInt64(value, radix: 16) else { return nil }
|
|
||||||
self.init(red: CGFloat((rgb >> 16) & 0xFF) / 255,
|
|
||||||
green: CGFloat((rgb >> 8) & 0xFF) / 255,
|
|
||||||
blue: CGFloat(rgb & 0xFF) / 255,
|
|
||||||
alpha: 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension UIView {
|
extension UIView {
|
||||||
func setPressed(_ pressed: Bool, animated: Bool = true) {
|
func setPressed(_ pressed: Bool, animated: Bool = true) {
|
||||||
let update = {
|
let update = {
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
import UIKit
|
|
||||||
|
|
||||||
final class ColorDotView: UIView {
|
|
||||||
override init(frame: CGRect) {
|
|
||||||
super.init(frame: frame)
|
|
||||||
layer.borderWidth = 1
|
|
||||||
updateBorder()
|
|
||||||
}
|
|
||||||
|
|
||||||
@available(*, unavailable)
|
|
||||||
required init?(coder: NSCoder) { fatalError() }
|
|
||||||
|
|
||||||
override func layoutSubviews() {
|
|
||||||
super.layoutSubviews()
|
|
||||||
layer.cornerRadius = bounds.width / 2
|
|
||||||
}
|
|
||||||
|
|
||||||
override func traitCollectionDidChange(_ previous: UITraitCollection?) {
|
|
||||||
super.traitCollectionDidChange(previous)
|
|
||||||
if traitCollection.hasDifferentColorAppearance(comparedTo: previous) {
|
|
||||||
updateBorder()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func updateBorder() {
|
|
||||||
layer.borderColor = UIColor.label.withAlphaComponent(0.15).cgColor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,6 @@ import UIKit
|
|||||||
|
|
||||||
final class WordCardCell: UITableViewCell {
|
final class WordCardCell: UITableViewCell {
|
||||||
private let cardView = UIView()
|
private let cardView = UIView()
|
||||||
private let colorDot = ColorDotView()
|
|
||||||
private let wordLabel = UILabel()
|
private let wordLabel = UILabel()
|
||||||
private let phoneticLabel = UILabel()
|
private let phoneticLabel = UILabel()
|
||||||
private let meaningLabel = UILabel()
|
private let meaningLabel = UILabel()
|
||||||
@@ -34,19 +33,7 @@ final class WordCardCell: UITableViewCell {
|
|||||||
meaningLabel.font = .preferredFont(forTextStyle: .subheadline)
|
meaningLabel.font = .preferredFont(forTextStyle: .subheadline)
|
||||||
meaningLabel.textColor = .secondaryLabel
|
meaningLabel.textColor = .secondaryLabel
|
||||||
|
|
||||||
let wordRowSpacer = UIView()
|
let textStack = UIStackView(arrangedSubviews: [wordLabel, phoneticLabel, meaningLabel])
|
||||||
wordRowSpacer.setContentHuggingPriority(.init(1), for: .horizontal)
|
|
||||||
let wordRow = UIStackView(arrangedSubviews: [wordLabel, colorDot, wordRowSpacer])
|
|
||||||
wordRow.axis = .horizontal
|
|
||||||
wordRow.alignment = .center
|
|
||||||
wordRow.spacing = 8
|
|
||||||
colorDot.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
colorDot.widthAnchor.constraint(equalToConstant: 12),
|
|
||||||
colorDot.heightAnchor.constraint(equalToConstant: 12)
|
|
||||||
])
|
|
||||||
|
|
||||||
let textStack = UIStackView(arrangedSubviews: [wordRow, phoneticLabel, meaningLabel])
|
|
||||||
textStack.axis = .vertical
|
textStack.axis = .vertical
|
||||||
textStack.spacing = 8
|
textStack.spacing = 8
|
||||||
|
|
||||||
@@ -81,8 +68,6 @@ final class WordCardCell: UITableViewCell {
|
|||||||
wordLabel.text = word.word
|
wordLabel.text = word.word
|
||||||
phoneticLabel.text = word.phonetic
|
phoneticLabel.text = word.phonetic
|
||||||
meaningLabel.text = word.meaning
|
meaningLabel.text = word.meaning
|
||||||
colorDot.backgroundColor = word.color
|
|
||||||
colorDot.isHidden = word.color == nil
|
|
||||||
updateSpeakerState()
|
updateSpeakerState()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import SafariServices
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
final class WordDetailViewController: UIViewController {
|
final class WordDetailViewController: UIViewController {
|
||||||
@@ -125,6 +126,20 @@ final class WordDetailViewController: UIViewController {
|
|||||||
meaningLabel.font = .preferredFont(forTextStyle: .title2)
|
meaningLabel.font = .preferredFont(forTextStyle: .title2)
|
||||||
meaningLabel.numberOfLines = 0
|
meaningLabel.numberOfLines = 0
|
||||||
|
|
||||||
|
var searchConfig = UIButton.Configuration.plain()
|
||||||
|
searchConfig.baseForegroundColor = Theme.Color.accent
|
||||||
|
searchConfig.image = UIImage(systemName: "magnifyingglass",
|
||||||
|
withConfiguration: UIImage.SymbolConfiguration(pointSize: 15, weight: .medium))
|
||||||
|
let searchButton = UIButton(type: .system)
|
||||||
|
searchButton.configuration = searchConfig
|
||||||
|
searchButton.addAction(UIAction { [weak self] _ in self?.searchMeaning() }, for: .touchUpInside)
|
||||||
|
searchButton.setContentHuggingPriority(.required, for: .horizontal)
|
||||||
|
|
||||||
|
let meaningRow = UIStackView(arrangedSubviews: [meaningLabel, searchButton])
|
||||||
|
meaningRow.axis = .horizontal
|
||||||
|
meaningRow.alignment = .top
|
||||||
|
meaningRow.spacing = 8
|
||||||
|
|
||||||
let divider = HairlineView()
|
let divider = HairlineView()
|
||||||
|
|
||||||
let exampleTitle = makeCaption("例句")
|
let exampleTitle = makeCaption("例句")
|
||||||
@@ -154,7 +169,7 @@ final class WordDetailViewController: UIViewController {
|
|||||||
exampleRow.alignment = .top
|
exampleRow.alignment = .top
|
||||||
exampleRow.spacing = 8
|
exampleRow.spacing = 8
|
||||||
|
|
||||||
let stack = UIStackView(arrangedSubviews: [meaningTitle, meaningLabel, divider, exampleTitle, exampleRow])
|
let stack = UIStackView(arrangedSubviews: [meaningTitle, meaningRow, divider, exampleTitle, exampleRow])
|
||||||
stack.axis = .vertical
|
stack.axis = .vertical
|
||||||
stack.spacing = 16
|
stack.spacing = 16
|
||||||
stack.setCustomSpacing(6, after: meaningTitle)
|
stack.setCustomSpacing(6, after: meaningTitle)
|
||||||
@@ -171,6 +186,13 @@ final class WordDetailViewController: UIViewController {
|
|||||||
return card
|
return card
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func searchMeaning() {
|
||||||
|
Haptics.selection()
|
||||||
|
guard let query = word.meaning.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
|
||||||
|
let url = URL(string: "https://www.google.com/search?q=\(query)") else { return }
|
||||||
|
present(SFSafariViewController(url: url), animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
private func makeCaption(_ text: String) -> UILabel {
|
private func makeCaption(_ text: String) -> UILabel {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.text = text
|
label.text = text
|
||||||
|
|||||||
Reference in New Issue
Block a user