Skip to content

Commit

Permalink
Merge pull request #34 from konstantintutsch/main
Browse files Browse the repository at this point in the history
Add search for flashcards
  • Loading branch information
david-swift authored Mar 27, 2024
2 parents 527eb88 + 224820f commit 8394fc0
Show file tree
Hide file tree
Showing 12 changed files with 189 additions and 53 deletions.
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import PackageDescription
let package = Package(
name: "Flashcards",
dependencies: [
.package(url: "https://github.com/AparokshaUI/Adwaita", from: "0.2.4"),
.package(url: "https://github.com/AparokshaUI/Adwaita", branch: "main"),
.package(url: "https://github.com/AparokshaUI/Localized", from: "0.2.2")
],
targets: [
Expand Down
6 changes: 5 additions & 1 deletion Sources/Flashcards.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ struct Flashcards: App {

var scene: Scene {
Window(id: "main") { window in
ContentView(sets: $sets, app: app, window: window)
ContentView(sets: $sets, app: app, window: window) { newValue in
if let index = sets.firstIndex(where: { $0.id == newValue.id }) {
_sets.rawValue[index] = newValue
}
}
}
.title("Memorize")
.quitShortcut()
Expand Down
8 changes: 8 additions & 0 deletions Sources/Model/Extensions/Array.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,11 @@ extension Array where Element == FlashcardsSet {
}

}

extension Array where Element: SearchScore {

func sortScore(_ search: String) -> Self {
map { ($0, $0.score(search)) }.sorted { $0.1 > $1.1 }.filter { $0.1 != 0 }.map { $0.0 }
}

}
14 changes: 14 additions & 0 deletions Sources/Model/Extensions/String.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,18 @@ extension String: Identifiable {
/// The identifier.
public var id: Self { self }

func search(_ search: String) -> Bool {
guard !search.isEmpty else {
return true
}
var remainder = search.lowercased()[...]
for char in lowercased() where char == remainder[remainder.startIndex] {
remainder.removeFirst()
if remainder.isEmpty {
return true
}
}
return false
}

}
24 changes: 5 additions & 19 deletions Sources/Model/FlashcardsSet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import Foundation

struct FlashcardsSet: Identifiable, Codable {
struct FlashcardsSet: SearchScore, Identifiable, Codable {

let id: String
var name: String
Expand Down Expand Up @@ -122,31 +122,17 @@ struct FlashcardsSet: Identifiable, Codable {
}
}

func score(_ filter: String?) -> Int {
func score(_ query: String?) -> Int {
var totalScore = 1
if let filter, !filter.isEmpty {
totalScore = search(filter, in: name) ? 5 : 0
if let query, !query.isEmpty {
totalScore = name.search(query) ? 5 : 0
for keyword in keywords.nonOptional {
totalScore += search(filter, in: keyword) ? 1 : 0
totalScore += keyword.search(query) ? 1 : 0
}
}
return totalScore
}

func search(_ search: String, in text: String) -> Bool {
guard !search.isEmpty else {
return true
}
var remainder = search.lowercased()[...]
for char in text.lowercased() where char == remainder[remainder.startIndex] {
remainder.removeFirst()
if remainder.isEmpty {
return true
}
}
return false
}

mutating func filterTags() {
for (index, flashcard) in flashcards.enumerated() {
flashcards[safe: index]?.tags.nonOptional = flashcard.tags.nonOptional
Expand Down
26 changes: 14 additions & 12 deletions Sources/Model/Localized.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,9 @@ viewMenu:
pt_BR: Ver
fr: Voir

filter:
en: Filter...
de: Filtern ...
it: Filtro...
pt_BR: Filtrar...
fr: Filtrer...
search:
en: Search...
de: Suchen ...

noFlashcards:
en: No flashcards available
Expand Down Expand Up @@ -568,12 +565,9 @@ deleteDescription:
pt_BR: Não existe desfazer. Os flashcards serão perdidos
fr: Cette action est irréversible. Les cartes mémoire seront perdues.

filterSets:
en: Filter Sets
de: Sets filtern
it: Set di filtri
pt_BR: Filtrar Sets
fr: Filtrer les jeux
searchSets:
en: Search Sets
de: Sets suchen

noSelection:
en: No Selection
Expand Down Expand Up @@ -727,3 +721,11 @@ toggleSidebar:
de: Seitenleiste ein- und ausblenden
it: Attiva la barra laterale
fr: Basculer la barre latérale

searchTitle:
en: Search
de: Suche

searchFlashcards:
en: Search Flashcards
de: Karteikarten suchen
61 changes: 61 additions & 0 deletions Sources/Model/Search.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// Search.swift
// Memorize
//

enum Search {

case visible(query: String)
case hidden(query: String)

var query: String {
get {
switch self {
case let .visible(query), let .hidden(query):
query
}
}
set {
switch self {
case .visible:
self = .visible(query: newValue)
case .hidden:
self = .hidden(query: newValue)
}
}
}

var effectiveQuery: String {
visible ? query : ""
}

var visible: Bool {
get {
switch self {
case .visible:
true
case .hidden:
false
}
}
set {
switch self {
case let .visible(query), let .hidden(query):
self = newValue ? .visible(query: query) : .hidden(query: query)
}
}
}

mutating func show() {
self = .visible(query: query)
}

mutating func toggle() {
if visible {
self = .hidden(query: query)
} else {
self = .visible(query: query)
}
}

}
10 changes: 10 additions & 0 deletions Sources/Model/SearchScore.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//
// SearchScore.swift
// Memorize
//

protocol SearchScore {

func score(_ query: String?) -> Int

}
26 changes: 18 additions & 8 deletions Sources/View/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ struct ContentView: WindowView {
@State("selected-set")
private var selectedSet = ""
@State private var flashcardsView: NavigationStack<FlashcardsView> = .init()
@State private var filter: String?
@State private var search: Search = .hidden(query: "")
@State private var editSearch: Search = .hidden(query: "")
@State private var searchFocused = false
@State private var editMode = false
@State("width")
private var width = 700
Expand All @@ -24,6 +26,7 @@ struct ContentView: WindowView {
@State private var sidebarVisible = false
var app: GTUIApp
var window: GTUIApplicationWindow
var modifySet: (FlashcardsSet) -> Void

var smallWindow: Bool { width < 600 }

Expand Down Expand Up @@ -62,12 +65,13 @@ struct ContentView: WindowView {
var sidebar: View {
ScrollView {
List(
sets.map { ($0, $0.score(filter)) }.sorted { $0.1 > $1.1 }.filter { $0.1 != 0 }.map { $0.0 },
sets.sortScore(search.effectiveQuery),
selection: .init {
selectedSet
} set: { newValue in
selectedSet = newValue
sidebarVisible = false
editSearch = .hidden(query: "")
}
) { set in
Text(set.name)
Expand All @@ -78,18 +82,21 @@ struct ContentView: WindowView {
.style("navigation-sidebar")
}
.hexpand()
.topToolbar(visible: filter != nil) {
.topToolbar(visible: search.visible) {
SearchEntry()
.placeholderText(Loc.filterSets)
.text(.init { filter ?? "" } set: { filter = $0 })
.focused(.constant(filter != nil))
.placeholderText(Loc.searchSets)
.text($search.query)
.focused(.constant(true))
.padding(5, .horizontal.add(.bottom))
}
.topToolbar {
ToolbarView(
sets: $sets,
selectedSet: $selectedSet,
filter: $filter,
search: $search,
editSearch: $editSearch,
searchFocused: $searchFocused,
editMode: $editMode,
app: app,
window: window,
addSet: addSet
Expand All @@ -103,9 +110,12 @@ struct ContentView: WindowView {
SetOverview(
set: binding,
editMode: $editMode,
editSearch: $editSearch,
searchFocused: $searchFocused,
flashcardsView: $flashcardsView,
sidebarVisible: $sidebarVisible,
smallWindow: smallWindow
smallWindow: smallWindow,
modifySet: modifySet
) {
sets = sets.filter { $0.id != set.id }
}
Expand Down
35 changes: 29 additions & 6 deletions Sources/View/EditView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ struct EditView: View {

@Binding var set: FlashcardsSet
@Binding var editMode: Bool
@Binding var editSearch: Search
@Binding var searchFocused: Bool
@State private var expanded = false
@State private var focusedFront: String?
@State private var importFlashcards = false
var modifySet: (FlashcardsSet) -> Void

var view: Body {
ScrollView {
Expand All @@ -24,8 +27,25 @@ struct EditView: View {
.formWidth()
}
.vexpand()
.topToolbar(visible: editSearch.visible) {
SearchEntry()
.placeholderText(Loc.searchFlashcards)
.text($editSearch.query)
.focused($searchFocused)
.padding(5, .horizontal.add(.bottom))
.frame(maxWidth: 300)
}
.topToolbar {
HeaderBar(titleButtons: false) {
Toggle(icon: .default(icon: .editFind), isOn: .init {
editSearch.visible
} set: { newValue in
editSearch.visible = newValue
if newValue {
searchFocused.toggle()
}
})
.tooltip(Loc.searchTitle)
} end: {
Button(Loc.done) {
editMode = false
Expand Down Expand Up @@ -79,12 +99,15 @@ struct EditView: View {

var flashcards: View {
ForEach(.init(set.flashcards.indices)) { index in
if set.flashcards[safe: index] != nil {
if let flashcard = set.flashcards[safe: index],
flashcard.front.search(editSearch.effectiveQuery) || flashcard.back.search(editSearch.effectiveQuery) {
EditFlashcardView(
flashcard: .init {
set.flashcards[safe: index] ?? .init()
} set: { newValue in
set.flashcards[safe: index] = newValue
flashcard: .init { flashcard } set: { newValue in
if !searchFocused {
var set = set
set.flashcards[safe: index] = newValue
modifySet(set)
}
},
index: index,
tags: set.tags.nonOptional,
Expand All @@ -97,7 +120,7 @@ struct EditView: View {
appendFlashcard()
}
} delete: {
set.flashcards = set.flashcards.filter { $0.id != set.flashcards[safe: index]?.id }
set.flashcards = set.flashcards.filter { $0.id != flashcard.id }
Task {
try? await Task.sleep(nanoseconds: 100)
focusedFront = set.flashcards[safe: index - 1]?.id
Expand Down
11 changes: 10 additions & 1 deletion Sources/View/SetOverview.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ struct SetOverview: View {

@Binding var set: FlashcardsSet
@Binding var editMode: Bool
@Binding var editSearch: Search
@Binding var searchFocused: Bool
@Binding var flashcardsView: NavigationStack<FlashcardsView>
@Binding var sidebarVisible: Bool
@State private var export = false
@State private var deleteState = false
@State private var copied = Signal()
var smallWindow: Bool
var modifySet: (FlashcardsSet) -> Void
var delete: () -> Void

var view: Body {
Expand Down Expand Up @@ -68,7 +71,13 @@ struct SetOverview: View {
}
}
.dialog(visible: $editMode, id: "edit", width: 700, height: 550) {
EditView(set: $set, editMode: $editMode)
EditView(
set: $set,
editMode: $editMode,
editSearch: $editSearch,
searchFocused: $searchFocused,
modifySet: modifySet
)
}
.toast(Loc.copied, signal: copied)
}
Expand Down
Loading

0 comments on commit 8394fc0

Please sign in to comment.