forked from firka/firka
Add Shortcuts intents, entities & localizations
Introduce App Shortcuts and AppIntents support: add multiple shortcut intents (next/closest lesson, today/tomorrow/closest timetable, recent grades, subject/overall averages), AppShortcuts provider, and ShortcutError. Add AppEntity types (Average, Grade, Lesson, Subject) and corresponding entity queries. Refactor Controls to use AppIntent-based navigation (OpenHome/OpenGrades/OpenTimetable) with localized labels/descriptions and remove duplicate SubjectEntity from AveragesIntent. Update TimetableProvider to use a lock-screen-friendly timeline refresh policy. Add localization resources (AppShortcuts.strings and expanded Localizable.strings) for en/de/hu and register new strings in project files; update Info.plist/project.pbxproj accordingly.
This commit is contained in:
@@ -4,26 +4,12 @@ import AppIntents
|
||||
|
||||
private let appGroup = "group.app.firka.firkaa"
|
||||
|
||||
// MARK: - Home Control
|
||||
// MARK: - Navigation Intents (iOS 16+, used by Controls and Shortcuts)
|
||||
|
||||
@available(iOS 18.0, *)
|
||||
struct HomeControl: ControlWidget {
|
||||
static let kind = "app.firka.firkaa.control.home"
|
||||
|
||||
var body: some ControlWidgetConfiguration {
|
||||
StaticControlConfiguration(kind: Self.kind) {
|
||||
ControlWidgetButton(action: OpenHomeIntent()) {
|
||||
Label("Főoldal", systemImage: "house.fill")
|
||||
}
|
||||
}
|
||||
.displayName("Firka - Főoldal")
|
||||
.description("Firka app főoldal megnyitása")
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 18.0, *)
|
||||
@available(iOS 16.0, *)
|
||||
struct OpenHomeIntent: AppIntent {
|
||||
static var title: LocalizedStringResource = "Firka Főoldal"
|
||||
static var title: LocalizedStringResource = LocalizedStringResource("control_home_title", defaultValue: "Firka Home")
|
||||
static var description: IntentDescription = IntentDescription(LocalizedStringResource("control_home_description", defaultValue: "Open Firka home screen"))
|
||||
static var openAppWhenRun: Bool = true
|
||||
|
||||
func perform() async throws -> some IntentResult {
|
||||
@@ -32,26 +18,10 @@ struct OpenHomeIntent: AppIntent {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Grades Control
|
||||
|
||||
@available(iOS 18.0, *)
|
||||
struct GradesControl: ControlWidget {
|
||||
static let kind = "app.firka.firkaa.control.grades"
|
||||
|
||||
var body: some ControlWidgetConfiguration {
|
||||
StaticControlConfiguration(kind: Self.kind) {
|
||||
ControlWidgetButton(action: OpenGradesIntent()) {
|
||||
Label("Jegyek", systemImage: "star.fill")
|
||||
}
|
||||
}
|
||||
.displayName("Firka - Jegyek")
|
||||
.description("Firka app jegyek megnyitása")
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 18.0, *)
|
||||
@available(iOS 16.0, *)
|
||||
struct OpenGradesIntent: AppIntent {
|
||||
static var title: LocalizedStringResource = "Firka Jegyek"
|
||||
static var title: LocalizedStringResource = LocalizedStringResource("control_grades_title", defaultValue: "Firka Grades")
|
||||
static var description: IntentDescription = IntentDescription(LocalizedStringResource("control_grades_description", defaultValue: "Open Firka grades"))
|
||||
static var openAppWhenRun: Bool = true
|
||||
|
||||
func perform() async throws -> some IntentResult {
|
||||
@@ -60,7 +30,53 @@ struct OpenGradesIntent: AppIntent {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Timetable Control
|
||||
@available(iOS 16.0, *)
|
||||
struct OpenTimetableIntent: AppIntent {
|
||||
static var title: LocalizedStringResource = LocalizedStringResource("control_timetable_title", defaultValue: "Firka Timetable")
|
||||
static var description: IntentDescription = IntentDescription(LocalizedStringResource("control_timetable_description", defaultValue: "Open Firka timetable"))
|
||||
static var openAppWhenRun: Bool = true
|
||||
|
||||
func perform() async throws -> some IntentResult {
|
||||
UserDefaults(suiteName: appGroup)?.set("timetable", forKey: "controlNavigation")
|
||||
return .result()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Home Control (iOS 18+)
|
||||
|
||||
@available(iOS 18.0, *)
|
||||
struct HomeControl: ControlWidget {
|
||||
static let kind = "app.firka.firkaa.control.home"
|
||||
|
||||
var body: some ControlWidgetConfiguration {
|
||||
StaticControlConfiguration(kind: Self.kind) {
|
||||
ControlWidgetButton(action: OpenHomeIntent()) {
|
||||
Label(LocalizedStringResource("control_home_label", defaultValue: "Home"), systemImage: "house.fill")
|
||||
}
|
||||
}
|
||||
.displayName(LocalizedStringResource("control_home_display", defaultValue: "Firka - Home"))
|
||||
.description(LocalizedStringResource("control_home_description", defaultValue: "Open Firka home screen"))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Grades Control (iOS 18+)
|
||||
|
||||
@available(iOS 18.0, *)
|
||||
struct GradesControl: ControlWidget {
|
||||
static let kind = "app.firka.firkaa.control.grades"
|
||||
|
||||
var body: some ControlWidgetConfiguration {
|
||||
StaticControlConfiguration(kind: Self.kind) {
|
||||
ControlWidgetButton(action: OpenGradesIntent()) {
|
||||
Label(LocalizedStringResource("control_grades_label", defaultValue: "Grades"), systemImage: "star.fill")
|
||||
}
|
||||
}
|
||||
.displayName(LocalizedStringResource("control_grades_display", defaultValue: "Firka - Grades"))
|
||||
.description(LocalizedStringResource("control_grades_description", defaultValue: "Open Firka grades"))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Timetable Control (iOS 18+)
|
||||
|
||||
@available(iOS 18.0, *)
|
||||
struct TimetableControl: ControlWidget {
|
||||
@@ -69,21 +85,10 @@ struct TimetableControl: ControlWidget {
|
||||
var body: some ControlWidgetConfiguration {
|
||||
StaticControlConfiguration(kind: Self.kind) {
|
||||
ControlWidgetButton(action: OpenTimetableIntent()) {
|
||||
Label("Órarend", systemImage: "calendar")
|
||||
Label(LocalizedStringResource("control_timetable_label", defaultValue: "Timetable"), systemImage: "calendar")
|
||||
}
|
||||
}
|
||||
.displayName("Firka - Órarend")
|
||||
.description("Firka app órarend megnyitása")
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 18.0, *)
|
||||
struct OpenTimetableIntent: AppIntent {
|
||||
static var title: LocalizedStringResource = "Firka Órarend"
|
||||
static var openAppWhenRun: Bool = true
|
||||
|
||||
func perform() async throws -> some IntentResult {
|
||||
UserDefaults(suiteName: appGroup)?.set("timetable", forKey: "controlNavigation")
|
||||
return .result()
|
||||
.displayName(LocalizedStringResource("control_timetable_display", defaultValue: "Firka - Timetable"))
|
||||
.description(LocalizedStringResource("control_timetable_description", defaultValue: "Open Firka timetable"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,40 +1,6 @@
|
||||
import AppIntents
|
||||
import WidgetKit
|
||||
|
||||
struct SubjectEntity: AppEntity {
|
||||
let id: String
|
||||
let name: String
|
||||
|
||||
static var typeDisplayRepresentation: TypeDisplayRepresentation {
|
||||
TypeDisplayRepresentation(name: LocalizedStringResource("subjects_type", defaultValue: "Subjects"))
|
||||
}
|
||||
|
||||
static var defaultQuery = SubjectQuery()
|
||||
|
||||
var displayRepresentation: DisplayRepresentation {
|
||||
DisplayRepresentation(title: "\(name)")
|
||||
}
|
||||
}
|
||||
|
||||
struct SubjectQuery: EntityQuery {
|
||||
func entities(for identifiers: [String]) async throws -> [SubjectEntity] {
|
||||
let data = WidgetData.load()
|
||||
return data?.averages.subjects
|
||||
.filter { identifiers.contains($0.uid) }
|
||||
.map { SubjectEntity(id: $0.uid, name: $0.name) } ?? []
|
||||
}
|
||||
|
||||
func suggestedEntities() async throws -> [SubjectEntity] {
|
||||
let data = WidgetData.load()
|
||||
return data?.averages.subjects
|
||||
.map { SubjectEntity(id: $0.uid, name: $0.name) } ?? []
|
||||
}
|
||||
|
||||
func defaultResult() async -> SubjectEntity? {
|
||||
try? await suggestedEntities().first
|
||||
}
|
||||
}
|
||||
|
||||
struct AveragesWidgetIntent: WidgetConfigurationIntent {
|
||||
static var title: LocalizedStringResource = LocalizedStringResource("widget_averages_title", defaultValue: "Averages")
|
||||
static var description: IntentDescription = IntentDescription(LocalizedStringResource("widget_averages_description", defaultValue: "Shows subject averages"))
|
||||
|
||||
@@ -142,6 +142,18 @@ struct TimetableProvider: AppIntentTimelineProvider {
|
||||
}
|
||||
entries.sort { $0.date < $1.date }
|
||||
|
||||
if isLockScreenWidget {
|
||||
var refreshDate: Date
|
||||
if let next = nextLesson {
|
||||
refreshDate = next.start
|
||||
} else if let current = currentLesson {
|
||||
refreshDate = current.end.addingTimeInterval(1)
|
||||
} else {
|
||||
refreshDate = midnight
|
||||
}
|
||||
return Timeline(entries: entries, policy: .after(refreshDate))
|
||||
}
|
||||
|
||||
return Timeline(entries: entries, policy: .atEnd)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import AppIntents
|
||||
import Foundation
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
struct AverageEntity: AppEntity {
|
||||
static var typeDisplayRepresentation = TypeDisplayRepresentation(name: LocalizedStringResource("entity_average", defaultValue: "Average"))
|
||||
static var defaultQuery = AverageEntityQuery()
|
||||
|
||||
var id: String
|
||||
|
||||
@Property(title: LocalizedStringResource("entity_prop_subject", defaultValue: "Subject"))
|
||||
var subjectName: String
|
||||
|
||||
@Property(title: LocalizedStringResource("entity_prop_average", defaultValue: "Average"))
|
||||
var average: Double
|
||||
|
||||
@Property(title: LocalizedStringResource("entity_prop_grade_count", defaultValue: "Grade count"))
|
||||
var gradeCount: Int
|
||||
|
||||
var displayRepresentation: DisplayRepresentation {
|
||||
DisplayRepresentation(
|
||||
title: "\(subjectName)",
|
||||
subtitle: "\(String(format: "%.2f", average)) (\(gradeCount))"
|
||||
)
|
||||
}
|
||||
|
||||
init(from avg: SubjectAverage) {
|
||||
self.id = avg.uid
|
||||
self.subjectName = avg.name
|
||||
self.average = avg.average
|
||||
self.gradeCount = avg.gradeCount
|
||||
}
|
||||
|
||||
init(id: String, subjectName: String, average: Double, gradeCount: Int) {
|
||||
self.id = id
|
||||
self.subjectName = subjectName
|
||||
self.average = average
|
||||
self.gradeCount = gradeCount
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
struct AverageEntityQuery: EntityQuery {
|
||||
func entities(for identifiers: [String]) async throws -> [AverageEntity] {
|
||||
guard let data = WidgetData.load() else { return [] }
|
||||
return data.averages.subjects
|
||||
.filter { identifiers.contains($0.uid) }
|
||||
.map { AverageEntity(from: $0) }
|
||||
}
|
||||
|
||||
func suggestedEntities() async throws -> [AverageEntity] {
|
||||
guard let data = WidgetData.load() else { return [] }
|
||||
return data.averages.subjects.map { AverageEntity(from: $0) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import AppIntents
|
||||
import Foundation
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
struct GradeEntity: AppEntity {
|
||||
static var typeDisplayRepresentation = TypeDisplayRepresentation(name: LocalizedStringResource("entity_grade", defaultValue: "Grade"))
|
||||
static var defaultQuery = GradeEntityQuery()
|
||||
|
||||
var id: String
|
||||
|
||||
@Property(title: LocalizedStringResource("entity_prop_subject", defaultValue: "Subject"))
|
||||
var subject: String
|
||||
|
||||
@Property(title: LocalizedStringResource("entity_prop_value", defaultValue: "Value"))
|
||||
var numericValue: Int
|
||||
|
||||
@Property(title: LocalizedStringResource("entity_prop_grade", defaultValue: "Grade"))
|
||||
var strValue: String
|
||||
|
||||
@Property(title: LocalizedStringResource("entity_prop_topic", defaultValue: "Topic"))
|
||||
var topic: String
|
||||
|
||||
@Property(title: LocalizedStringResource("entity_prop_type", defaultValue: "Type"))
|
||||
var type: String
|
||||
|
||||
@Property(title: LocalizedStringResource("entity_prop_teacher", defaultValue: "Teacher"))
|
||||
var teacher: String
|
||||
|
||||
@Property(title: LocalizedStringResource("entity_prop_date", defaultValue: "Date"))
|
||||
var date: Date
|
||||
|
||||
@Property(title: LocalizedStringResource("entity_prop_weight", defaultValue: "Weight"))
|
||||
var weight: Int
|
||||
|
||||
var displayRepresentation: DisplayRepresentation {
|
||||
DisplayRepresentation(
|
||||
title: "\(subject): \(strValue)",
|
||||
subtitle: "\(topic)"
|
||||
)
|
||||
}
|
||||
|
||||
init(from grade: WidgetGrade) {
|
||||
self.id = grade.uid
|
||||
self.subject = grade.subject.name
|
||||
self.numericValue = grade.numericValue ?? 0
|
||||
self.strValue = grade.displayValue
|
||||
self.topic = grade.topic ?? ""
|
||||
self.type = grade.type.name
|
||||
self.teacher = grade.subject.teacherName ?? ""
|
||||
self.date = grade.recordDate
|
||||
self.weight = grade.weightPercentage ?? 100
|
||||
}
|
||||
|
||||
init(id: String, subject: String, numericValue: Int, strValue: String, topic: String, type: String, teacher: String, date: Date, weight: Int) {
|
||||
self.id = id
|
||||
self.subject = subject
|
||||
self.numericValue = numericValue
|
||||
self.strValue = strValue
|
||||
self.topic = topic
|
||||
self.type = type
|
||||
self.teacher = teacher
|
||||
self.date = date
|
||||
self.weight = weight
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
struct GradeEntityQuery: EntityQuery {
|
||||
func entities(for identifiers: [String]) async throws -> [GradeEntity] {
|
||||
guard let data = WidgetData.load() else { return [] }
|
||||
return data.grades
|
||||
.filter { identifiers.contains($0.uid) }
|
||||
.map { GradeEntity(from: $0) }
|
||||
}
|
||||
|
||||
func suggestedEntities() async throws -> [GradeEntity] {
|
||||
guard let data = WidgetData.load() else { return [] }
|
||||
return data.grades.prefix(10).map { GradeEntity(from: $0) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import AppIntents
|
||||
import Foundation
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
struct LessonEntity: AppEntity {
|
||||
static var typeDisplayRepresentation = TypeDisplayRepresentation(name: LocalizedStringResource("entity_lesson", defaultValue: "Lesson"))
|
||||
static var defaultQuery = LessonEntityQuery()
|
||||
|
||||
var id: String
|
||||
|
||||
@Property(title: LocalizedStringResource("entity_prop_name", defaultValue: "Name"))
|
||||
var name: String
|
||||
|
||||
@Property(title: LocalizedStringResource("entity_prop_teacher", defaultValue: "Teacher"))
|
||||
var teacher: String
|
||||
|
||||
@Property(title: LocalizedStringResource("entity_prop_room", defaultValue: "Room"))
|
||||
var room: String
|
||||
|
||||
@Property(title: LocalizedStringResource("entity_prop_start_time", defaultValue: "Start time"))
|
||||
var startTime: Date
|
||||
|
||||
@Property(title: LocalizedStringResource("entity_prop_end_time", defaultValue: "End time"))
|
||||
var endTime: Date
|
||||
|
||||
@Property(title: LocalizedStringResource("entity_prop_lesson_number", defaultValue: "Lesson number"))
|
||||
var lessonNumber: Int
|
||||
|
||||
@Property(title: LocalizedStringResource("entity_prop_cancelled", defaultValue: "Cancelled"))
|
||||
var isCancelled: Bool
|
||||
|
||||
@Property(title: LocalizedStringResource("entity_prop_substitution", defaultValue: "Substitution"))
|
||||
var isSubstitution: Bool
|
||||
|
||||
@Property(title: LocalizedStringResource("entity_prop_theme", defaultValue: "Theme"))
|
||||
var theme: String
|
||||
|
||||
var displayRepresentation: DisplayRepresentation {
|
||||
DisplayRepresentation(
|
||||
title: "\(name)",
|
||||
subtitle: "\(lessonNumber). \(room) - \(teacher)"
|
||||
)
|
||||
}
|
||||
|
||||
init(from lesson: WidgetLesson) {
|
||||
self.id = lesson.uid
|
||||
self.name = lesson.subject.name
|
||||
self.teacher = lesson.teacher ?? ""
|
||||
self.room = lesson.roomName ?? ""
|
||||
self.startTime = lesson.start
|
||||
self.endTime = lesson.end
|
||||
self.lessonNumber = lesson.lessonNumber ?? 0
|
||||
self.isCancelled = lesson.isCancelled
|
||||
self.isSubstitution = lesson.isSubstitution
|
||||
self.theme = lesson.theme ?? ""
|
||||
}
|
||||
|
||||
init(id: String, name: String, teacher: String, room: String, startTime: Date, endTime: Date, lessonNumber: Int, isCancelled: Bool, isSubstitution: Bool, theme: String) {
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.teacher = teacher
|
||||
self.room = room
|
||||
self.startTime = startTime
|
||||
self.endTime = endTime
|
||||
self.lessonNumber = lessonNumber
|
||||
self.isCancelled = isCancelled
|
||||
self.isSubstitution = isSubstitution
|
||||
self.theme = theme
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
struct LessonEntityQuery: EntityQuery {
|
||||
func entities(for identifiers: [String]) async throws -> [LessonEntity] {
|
||||
guard let data = WidgetData.load() else { return [] }
|
||||
let allLessons = data.timetable.today + data.timetable.tomorrow + (data.timetable.nextSchoolDay ?? [])
|
||||
return allLessons
|
||||
.filter { identifiers.contains($0.uid) }
|
||||
.map { LessonEntity(from: $0) }
|
||||
}
|
||||
|
||||
func suggestedEntities() async throws -> [LessonEntity] {
|
||||
guard let data = WidgetData.load() else { return [] }
|
||||
return data.timetable.today.map { LessonEntity(from: $0) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import AppIntents
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
struct SubjectEntity: AppEntity {
|
||||
let id: String
|
||||
let name: String
|
||||
|
||||
static var typeDisplayRepresentation: TypeDisplayRepresentation {
|
||||
TypeDisplayRepresentation(name: LocalizedStringResource("subjects_type", defaultValue: "Subjects"))
|
||||
}
|
||||
|
||||
static var defaultQuery = SubjectQuery()
|
||||
|
||||
var displayRepresentation: DisplayRepresentation {
|
||||
DisplayRepresentation(title: "\(name)")
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
struct SubjectQuery: EntityQuery {
|
||||
func entities(for identifiers: [String]) async throws -> [SubjectEntity] {
|
||||
let data = WidgetData.load()
|
||||
return data?.averages.subjects
|
||||
.filter { identifiers.contains($0.uid) }
|
||||
.map { SubjectEntity(id: $0.uid, name: $0.name) } ?? []
|
||||
}
|
||||
|
||||
func suggestedEntities() async throws -> [SubjectEntity] {
|
||||
let data = WidgetData.load()
|
||||
return data?.averages.subjects
|
||||
.map { SubjectEntity(id: $0.uid, name: $0.name) } ?? []
|
||||
}
|
||||
|
||||
func defaultResult() async -> SubjectEntity? {
|
||||
try? await suggestedEntities().first
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import AppIntents
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
struct FirkaShortcuts: AppShortcutsProvider {
|
||||
static var appShortcuts: [AppShortcut] {
|
||||
AppShortcut(
|
||||
intent: GetNextLessonIntent(),
|
||||
phrases: [
|
||||
"Next lesson \(.applicationName)",
|
||||
"What is my next lesson \(.applicationName)"
|
||||
],
|
||||
shortTitle: LocalizedStringResource("shortcut_short_next_lesson", defaultValue: "Next lesson"),
|
||||
systemImageName: "calendar"
|
||||
)
|
||||
AppShortcut(
|
||||
intent: GetClosestLessonIntent(),
|
||||
phrases: [
|
||||
"Closest lesson \(.applicationName)",
|
||||
"When is my next lesson \(.applicationName)"
|
||||
],
|
||||
shortTitle: LocalizedStringResource("shortcut_short_closest_lesson", defaultValue: "Closest lesson"),
|
||||
systemImageName: "forward"
|
||||
)
|
||||
AppShortcut(
|
||||
intent: GetTodayTimetableIntent(),
|
||||
phrases: [
|
||||
"Today's timetable \(.applicationName)",
|
||||
"What lessons do I have today \(.applicationName)"
|
||||
],
|
||||
shortTitle: LocalizedStringResource("shortcut_short_today_timetable", defaultValue: "Today's timetable"),
|
||||
systemImageName: "clock"
|
||||
)
|
||||
AppShortcut(
|
||||
intent: GetClosestTimetableIntent(),
|
||||
phrases: [
|
||||
"Closest timetable \(.applicationName)",
|
||||
"When are my next lessons \(.applicationName)"
|
||||
],
|
||||
shortTitle: LocalizedStringResource("shortcut_short_closest_timetable", defaultValue: "Closest timetable"),
|
||||
systemImageName: "forward.fill"
|
||||
)
|
||||
AppShortcut(
|
||||
intent: GetOverallAverageIntent(),
|
||||
phrases: [
|
||||
"My average \(.applicationName)",
|
||||
"What is my average \(.applicationName)"
|
||||
],
|
||||
shortTitle: LocalizedStringResource("shortcut_short_overall_average", defaultValue: "My average"),
|
||||
systemImageName: "chart.bar"
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import AppIntents
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
struct GetClosestLessonIntent: AppIntent {
|
||||
static var title: LocalizedStringResource = LocalizedStringResource("shortcut_closest_lesson_title", defaultValue: "Closest lesson")
|
||||
static var description: IntentDescription = IntentDescription(LocalizedStringResource("shortcut_closest_lesson_description", defaultValue: "Get the closest lesson (today, tomorrow, or next school day)"))
|
||||
|
||||
func perform() async throws -> some ReturnsValue<LessonEntity> & ProvidesDialog {
|
||||
guard let data = WidgetData.load() else {
|
||||
throw ShortcutError.noData
|
||||
}
|
||||
let now = Date()
|
||||
|
||||
if let lesson = data.timetable.today.first(where: { $0.start > now })
|
||||
?? data.timetable.today.first(where: { $0.end > now }) {
|
||||
let entity = LessonEntity(from: lesson)
|
||||
return .result(value: entity, dialog: "\(entity.name) - \(entity.room)")
|
||||
}
|
||||
|
||||
if let lesson = data.timetable.tomorrow.first {
|
||||
let entity = LessonEntity(from: lesson)
|
||||
return .result(value: entity, dialog: "\(entity.name) - \(entity.room)")
|
||||
}
|
||||
|
||||
if let lesson = data.timetable.nextSchoolDay?.first {
|
||||
let entity = LessonEntity(from: lesson)
|
||||
return .result(value: entity, dialog: "\(entity.name) - \(entity.room)")
|
||||
}
|
||||
|
||||
throw ShortcutError.noUpcomingLesson
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import AppIntents
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
struct GetClosestTimetableIntent: AppIntent {
|
||||
static var title: LocalizedStringResource = LocalizedStringResource("shortcut_closest_timetable_title", defaultValue: "Closest timetable")
|
||||
static var description: IntentDescription = IntentDescription(LocalizedStringResource("shortcut_closest_timetable_description", defaultValue: "Get the closest school day's timetable (today, tomorrow, or next school day)"))
|
||||
|
||||
func perform() async throws -> some ReturnsValue<[LessonEntity]> & ProvidesDialog {
|
||||
guard let data = WidgetData.load() else {
|
||||
throw ShortcutError.noData
|
||||
}
|
||||
let now = Date()
|
||||
|
||||
let remaining = data.timetable.today.filter { $0.end > now }
|
||||
if !remaining.isEmpty {
|
||||
let entities = remaining.map { LessonEntity(from: $0) }
|
||||
let summary = entities.map { "\($0.lessonNumber). \($0.name)" }.joined(separator: ", ")
|
||||
return .result(value: entities, dialog: "\(summary)")
|
||||
}
|
||||
|
||||
if !data.timetable.tomorrow.isEmpty {
|
||||
let entities = data.timetable.tomorrow.map { LessonEntity(from: $0) }
|
||||
let summary = entities.map { "\($0.lessonNumber). \($0.name)" }.joined(separator: ", ")
|
||||
return .result(value: entities, dialog: "\(summary)")
|
||||
}
|
||||
|
||||
if let nextDay = data.timetable.nextSchoolDay, !nextDay.isEmpty {
|
||||
let entities = nextDay.map { LessonEntity(from: $0) }
|
||||
let summary = entities.map { "\($0.lessonNumber). \($0.name)" }.joined(separator: ", ")
|
||||
return .result(value: entities, dialog: "\(summary)")
|
||||
}
|
||||
|
||||
throw ShortcutError.noUpcomingLesson
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import AppIntents
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
struct GetNextLessonIntent: AppIntent {
|
||||
static var title: LocalizedStringResource = LocalizedStringResource("shortcut_next_lesson_title", defaultValue: "Next lesson")
|
||||
static var description: IntentDescription = IntentDescription(LocalizedStringResource("shortcut_next_lesson_description", defaultValue: "Get the next lesson details"))
|
||||
|
||||
func perform() async throws -> some ReturnsValue<LessonEntity> & ProvidesDialog {
|
||||
guard let data = WidgetData.load() else {
|
||||
throw ShortcutError.noData
|
||||
}
|
||||
let now = Date()
|
||||
let upcoming = data.timetable.today.first { $0.start > now }
|
||||
?? data.timetable.today.first { $0.end > now }
|
||||
guard let lesson = upcoming else {
|
||||
throw ShortcutError.noUpcomingLesson
|
||||
}
|
||||
let entity = LessonEntity(from: lesson)
|
||||
return .result(value: entity, dialog: "\(entity.name) - \(entity.room)")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import AppIntents
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
struct GetOverallAverageIntent: AppIntent {
|
||||
static var title: LocalizedStringResource = LocalizedStringResource("shortcut_overall_average_title", defaultValue: "Overall average")
|
||||
static var description: IntentDescription = IntentDescription(LocalizedStringResource("shortcut_overall_average_description", defaultValue: "Get the overall academic average"))
|
||||
|
||||
func perform() async throws -> some ReturnsValue<Double> & ProvidesDialog {
|
||||
guard let data = WidgetData.load() else {
|
||||
throw ShortcutError.noData
|
||||
}
|
||||
guard let overall = data.averages.overall else {
|
||||
throw ShortcutError.noData
|
||||
}
|
||||
let rounded = (overall * 100).rounded() / 100
|
||||
return .result(value: rounded, dialog: "\(String(format: "%.2f", rounded))")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import AppIntents
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
struct GetRecentGradesIntent: AppIntent {
|
||||
static var title: LocalizedStringResource = LocalizedStringResource("shortcut_recent_grades_title", defaultValue: "Recent grades")
|
||||
static var description: IntentDescription = IntentDescription(LocalizedStringResource("shortcut_recent_grades_description", defaultValue: "Get the most recent grades"))
|
||||
|
||||
@Parameter(title: LocalizedStringResource("shortcut_param_count", defaultValue: "Count"), default: 5)
|
||||
var count: Int
|
||||
|
||||
func perform() async throws -> some ReturnsValue<[GradeEntity]> & ProvidesDialog {
|
||||
guard let data = WidgetData.load() else {
|
||||
throw ShortcutError.noData
|
||||
}
|
||||
let grades = Array(data.grades.prefix(count))
|
||||
let entities = grades.map { GradeEntity(from: $0) }
|
||||
let summary = entities.map { "\($0.subject): \($0.strValue)" }.joined(separator: ", ")
|
||||
return .result(value: entities, dialog: "\(summary)")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import AppIntents
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
struct GetSubjectAverageIntent: AppIntent {
|
||||
static var title: LocalizedStringResource = LocalizedStringResource("shortcut_subject_average_title", defaultValue: "Subject average")
|
||||
static var description: IntentDescription = IntentDescription(LocalizedStringResource("shortcut_subject_average_description", defaultValue: "Get the average for a subject"))
|
||||
|
||||
@Parameter(title: LocalizedStringResource("shortcut_param_subject", defaultValue: "Subject"))
|
||||
var subject: SubjectEntity
|
||||
|
||||
func perform() async throws -> some ReturnsValue<AverageEntity> & ProvidesDialog {
|
||||
guard let data = WidgetData.load() else {
|
||||
throw ShortcutError.noData
|
||||
}
|
||||
guard let avg = data.averages.subjects.first(where: { $0.uid == subject.id }) else {
|
||||
throw ShortcutError.subjectNotFound
|
||||
}
|
||||
let entity = AverageEntity(from: avg)
|
||||
return .result(value: entity, dialog: "\(entity.subjectName): \(String(format: "%.2f", entity.average))")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import AppIntents
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
struct GetTodayTimetableIntent: AppIntent {
|
||||
static var title: LocalizedStringResource = LocalizedStringResource("shortcut_today_timetable_title", defaultValue: "Today's timetable")
|
||||
static var description: IntentDescription = IntentDescription(LocalizedStringResource("shortcut_today_timetable_description", defaultValue: "Get today's full timetable"))
|
||||
|
||||
func perform() async throws -> some ReturnsValue<[LessonEntity]> & ProvidesDialog {
|
||||
guard let data = WidgetData.load() else {
|
||||
throw ShortcutError.noData
|
||||
}
|
||||
guard !data.timetable.today.isEmpty else {
|
||||
throw ShortcutError.noLessonsToday
|
||||
}
|
||||
let entities = data.timetable.today.map { LessonEntity(from: $0) }
|
||||
let summary = entities.map { "\($0.lessonNumber). \($0.name)" }.joined(separator: ", ")
|
||||
return .result(value: entities, dialog: "\(summary)")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import AppIntents
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
struct GetTomorrowTimetableIntent: AppIntent {
|
||||
static var title: LocalizedStringResource = LocalizedStringResource("shortcut_tomorrow_timetable_title", defaultValue: "Tomorrow's timetable")
|
||||
static var description: IntentDescription = IntentDescription(LocalizedStringResource("shortcut_tomorrow_timetable_description", defaultValue: "Get tomorrow's timetable"))
|
||||
|
||||
func perform() async throws -> some ReturnsValue<[LessonEntity]> & ProvidesDialog {
|
||||
guard let data = WidgetData.load() else {
|
||||
throw ShortcutError.noData
|
||||
}
|
||||
guard !data.timetable.tomorrow.isEmpty else {
|
||||
throw ShortcutError.noLessonsTomorrow
|
||||
}
|
||||
let entities = data.timetable.tomorrow.map { LessonEntity(from: $0) }
|
||||
let summary = entities.map { "\($0.lessonNumber). \($0.name)" }.joined(separator: ", ")
|
||||
return .result(value: entities, dialog: "\(summary)")
|
||||
}
|
||||
}
|
||||
25
firka/ios/HomeWidgetsExtension/Shortcuts/ShortcutError.swift
Normal file
25
firka/ios/HomeWidgetsExtension/Shortcuts/ShortcutError.swift
Normal file
@@ -0,0 +1,25 @@
|
||||
import Foundation
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
enum ShortcutError: Error, CustomLocalizedStringResourceConvertible {
|
||||
case noData
|
||||
case noUpcomingLesson
|
||||
case noLessonsToday
|
||||
case noLessonsTomorrow
|
||||
case subjectNotFound
|
||||
|
||||
var localizedStringResource: LocalizedStringResource {
|
||||
switch self {
|
||||
case .noData:
|
||||
return LocalizedStringResource("shortcut_error_no_data", defaultValue: "No data available. Open the Firka app to refresh.")
|
||||
case .noUpcomingLesson:
|
||||
return LocalizedStringResource("shortcut_error_no_upcoming_lesson", defaultValue: "No more lessons today.")
|
||||
case .noLessonsToday:
|
||||
return LocalizedStringResource("shortcut_error_no_lessons_today", defaultValue: "No lessons today.")
|
||||
case .noLessonsTomorrow:
|
||||
return LocalizedStringResource("shortcut_error_no_lessons_tomorrow", defaultValue: "No lessons tomorrow.")
|
||||
case .subjectNotFound:
|
||||
return LocalizedStringResource("shortcut_error_subject_not_found", defaultValue: "Subject not found.")
|
||||
}
|
||||
}
|
||||
}
|
||||
11
firka/ios/HomeWidgetsExtension/de.lproj/AppShortcuts.strings
Normal file
11
firka/ios/HomeWidgetsExtension/de.lproj/AppShortcuts.strings
Normal file
@@ -0,0 +1,11 @@
|
||||
/* Siri Phrases - German */
|
||||
"Next lesson ${applicationName}" = "Nächste Stunde ${applicationName}";
|
||||
"What is my next lesson ${applicationName}" = "Was ist meine nächste Stunde ${applicationName}";
|
||||
"Closest lesson ${applicationName}" = "Nächste anstehende Stunde ${applicationName}";
|
||||
"When is my next lesson ${applicationName}" = "Wann ist meine nächste Stunde ${applicationName}";
|
||||
"Today's timetable ${applicationName}" = "Stundenplan heute ${applicationName}";
|
||||
"What lessons do I have today ${applicationName}" = "Welche Stunden habe ich heute ${applicationName}";
|
||||
"Closest timetable ${applicationName}" = "Nächster Stundenplan ${applicationName}";
|
||||
"When are my next lessons ${applicationName}" = "Wann sind meine nächsten Stunden ${applicationName}";
|
||||
"My average ${applicationName}" = "Mein Durchschnitt ${applicationName}";
|
||||
"What is my average ${applicationName}" = "Wie ist mein Durchschnitt ${applicationName}";
|
||||
@@ -31,3 +31,90 @@
|
||||
/* Subject Selection */
|
||||
"subjects_type" = "Fächer";
|
||||
"no_subjects_available" = "Keine Fächer verfügbar";
|
||||
|
||||
/* Control Widget Titles */
|
||||
"control_home_title" = "Firka Startseite";
|
||||
"control_home_label" = "Startseite";
|
||||
"control_home_display" = "Firka - Startseite";
|
||||
"control_home_description" = "Firka Startseite öffnen";
|
||||
"control_grades_title" = "Firka Noten";
|
||||
"control_grades_label" = "Noten";
|
||||
"control_grades_display" = "Firka - Noten";
|
||||
"control_grades_description" = "Firka Noten öffnen";
|
||||
"control_timetable_title" = "Firka Stundenplan";
|
||||
"control_timetable_label" = "Stundenplan";
|
||||
"control_timetable_display" = "Firka - Stundenplan";
|
||||
"control_timetable_description" = "Firka Stundenplan öffnen";
|
||||
|
||||
/* Shortcut Intent Titles & Descriptions */
|
||||
"shortcut_next_lesson_title" = "Nächste Stunde";
|
||||
"shortcut_next_lesson_description" = "Details zur nächsten Stunde";
|
||||
"shortcut_closest_lesson_title" = "Nächste anstehende Stunde";
|
||||
"shortcut_closest_lesson_description" = "Nächste anstehende Stunde (heute, morgen oder nächster Schultag)";
|
||||
"shortcut_today_timetable_title" = "Stundenplan heute";
|
||||
"shortcut_today_timetable_description" = "Der vollständige Stundenplan für heute";
|
||||
"shortcut_tomorrow_timetable_title" = "Stundenplan morgen";
|
||||
"shortcut_tomorrow_timetable_description" = "Der Stundenplan für morgen";
|
||||
"shortcut_closest_timetable_title" = "Nächster Stundenplan";
|
||||
"shortcut_closest_timetable_description" = "Stundenplan des nächsten Schultags (heute, morgen oder nächster Schultag)";
|
||||
"shortcut_recent_grades_title" = "Letzte Noten";
|
||||
"shortcut_recent_grades_description" = "Die letzten Noten";
|
||||
"shortcut_subject_average_title" = "Fachdurchschnitt";
|
||||
"shortcut_subject_average_description" = "Durchschnitt eines Fachs";
|
||||
"shortcut_overall_average_title" = "Gesamtdurchschnitt";
|
||||
"shortcut_overall_average_description" = "Der Gesamtdurchschnitt";
|
||||
|
||||
/* Shortcut Parameters */
|
||||
"shortcut_param_count" = "Anzahl";
|
||||
"shortcut_param_subject" = "Fach";
|
||||
|
||||
/* Shortcut Phrases */
|
||||
"shortcut_phrase_next_lesson" = "Nächste Stunde";
|
||||
"shortcut_phrase_next_lesson_question" = "Was ist meine nächste Stunde";
|
||||
"shortcut_phrase_closest_lesson" = "Nächste anstehende Stunde";
|
||||
"shortcut_phrase_closest_lesson_question" = "Wann ist meine nächste Stunde";
|
||||
"shortcut_phrase_today_timetable" = "Stundenplan heute";
|
||||
"shortcut_phrase_today_timetable_question" = "Welche Stunden habe ich heute";
|
||||
"shortcut_phrase_closest_timetable" = "Nächster Stundenplan";
|
||||
"shortcut_phrase_closest_timetable_question" = "Wann sind meine nächsten Stunden";
|
||||
"shortcut_phrase_overall_average" = "Mein Durchschnitt";
|
||||
"shortcut_phrase_overall_average_question" = "Wie ist mein Durchschnitt";
|
||||
|
||||
/* Shortcut Short Titles */
|
||||
"shortcut_short_next_lesson" = "Nächste Stunde";
|
||||
"shortcut_short_closest_lesson" = "Nächste anstehende";
|
||||
"shortcut_short_today_timetable" = "Stundenplan heute";
|
||||
"shortcut_short_closest_timetable" = "Nächster Stundenplan";
|
||||
"shortcut_short_overall_average" = "Mein Durchschnitt";
|
||||
|
||||
/* Shortcut Errors */
|
||||
"shortcut_error_no_data" = "Keine Daten verfügbar. Öffne die Firka App zum Aktualisieren.";
|
||||
"shortcut_error_no_upcoming_lesson" = "Heute keine weiteren Stunden.";
|
||||
"shortcut_error_no_lessons_today" = "Heute keine Stunden.";
|
||||
"shortcut_error_no_lessons_tomorrow" = "Morgen keine Stunden.";
|
||||
"shortcut_error_subject_not_found" = "Fach nicht gefunden.";
|
||||
|
||||
/* Entity Type Names */
|
||||
"entity_lesson" = "Stunde";
|
||||
"entity_grade" = "Note";
|
||||
"entity_average" = "Durchschnitt";
|
||||
|
||||
/* Entity Property Names */
|
||||
"entity_prop_name" = "Name";
|
||||
"entity_prop_teacher" = "Lehrer";
|
||||
"entity_prop_room" = "Raum";
|
||||
"entity_prop_start_time" = "Startzeit";
|
||||
"entity_prop_end_time" = "Endzeit";
|
||||
"entity_prop_lesson_number" = "Stundennummer";
|
||||
"entity_prop_cancelled" = "Entfällt";
|
||||
"entity_prop_substitution" = "Vertretung";
|
||||
"entity_prop_theme" = "Thema";
|
||||
"entity_prop_subject" = "Fach";
|
||||
"entity_prop_value" = "Wert";
|
||||
"entity_prop_grade" = "Note";
|
||||
"entity_prop_topic" = "Thema";
|
||||
"entity_prop_type" = "Typ";
|
||||
"entity_prop_date" = "Datum";
|
||||
"entity_prop_weight" = "Gewicht";
|
||||
"entity_prop_average" = "Durchschnitt";
|
||||
"entity_prop_grade_count" = "Notenanzahl";
|
||||
|
||||
11
firka/ios/HomeWidgetsExtension/en.lproj/AppShortcuts.strings
Normal file
11
firka/ios/HomeWidgetsExtension/en.lproj/AppShortcuts.strings
Normal file
@@ -0,0 +1,11 @@
|
||||
/* Siri Phrases - English */
|
||||
"Next lesson ${applicationName}" = "Next lesson ${applicationName}";
|
||||
"What is my next lesson ${applicationName}" = "What is my next lesson ${applicationName}";
|
||||
"Closest lesson ${applicationName}" = "Closest lesson ${applicationName}";
|
||||
"When is my next lesson ${applicationName}" = "When is my next lesson ${applicationName}";
|
||||
"Today's timetable ${applicationName}" = "Today's timetable ${applicationName}";
|
||||
"What lessons do I have today ${applicationName}" = "What lessons do I have today ${applicationName}";
|
||||
"Closest timetable ${applicationName}" = "Closest timetable ${applicationName}";
|
||||
"When are my next lessons ${applicationName}" = "When are my next lessons ${applicationName}";
|
||||
"My average ${applicationName}" = "My average ${applicationName}";
|
||||
"What is my average ${applicationName}" = "What is my average ${applicationName}";
|
||||
@@ -31,3 +31,90 @@
|
||||
/* Subject Selection */
|
||||
"subjects_type" = "Subjects";
|
||||
"no_subjects_available" = "No subjects available";
|
||||
|
||||
/* Control Widget Titles */
|
||||
"control_home_title" = "Firka Home";
|
||||
"control_home_label" = "Home";
|
||||
"control_home_display" = "Firka - Home";
|
||||
"control_home_description" = "Open Firka home screen";
|
||||
"control_grades_title" = "Firka Grades";
|
||||
"control_grades_label" = "Grades";
|
||||
"control_grades_display" = "Firka - Grades";
|
||||
"control_grades_description" = "Open Firka grades";
|
||||
"control_timetable_title" = "Firka Timetable";
|
||||
"control_timetable_label" = "Timetable";
|
||||
"control_timetable_display" = "Firka - Timetable";
|
||||
"control_timetable_description" = "Open Firka timetable";
|
||||
|
||||
/* Shortcut Intent Titles & Descriptions */
|
||||
"shortcut_next_lesson_title" = "Next lesson";
|
||||
"shortcut_next_lesson_description" = "Get the next lesson details";
|
||||
"shortcut_closest_lesson_title" = "Closest lesson";
|
||||
"shortcut_closest_lesson_description" = "Get the closest lesson (today, tomorrow, or next school day)";
|
||||
"shortcut_today_timetable_title" = "Today's timetable";
|
||||
"shortcut_today_timetable_description" = "Get today's full timetable";
|
||||
"shortcut_tomorrow_timetable_title" = "Tomorrow's timetable";
|
||||
"shortcut_tomorrow_timetable_description" = "Get tomorrow's timetable";
|
||||
"shortcut_closest_timetable_title" = "Closest timetable";
|
||||
"shortcut_closest_timetable_description" = "Get the closest school day's timetable (today, tomorrow, or next school day)";
|
||||
"shortcut_recent_grades_title" = "Recent grades";
|
||||
"shortcut_recent_grades_description" = "Get the most recent grades";
|
||||
"shortcut_subject_average_title" = "Subject average";
|
||||
"shortcut_subject_average_description" = "Get the average for a subject";
|
||||
"shortcut_overall_average_title" = "Overall average";
|
||||
"shortcut_overall_average_description" = "Get the overall academic average";
|
||||
|
||||
/* Shortcut Parameters */
|
||||
"shortcut_param_count" = "Count";
|
||||
"shortcut_param_subject" = "Subject";
|
||||
|
||||
/* Shortcut Phrases */
|
||||
"shortcut_phrase_next_lesson" = "Next lesson";
|
||||
"shortcut_phrase_next_lesson_question" = "What is my next lesson";
|
||||
"shortcut_phrase_closest_lesson" = "Closest lesson";
|
||||
"shortcut_phrase_closest_lesson_question" = "When is my next lesson";
|
||||
"shortcut_phrase_today_timetable" = "Today's timetable";
|
||||
"shortcut_phrase_today_timetable_question" = "What lessons do I have today";
|
||||
"shortcut_phrase_closest_timetable" = "Closest timetable";
|
||||
"shortcut_phrase_closest_timetable_question" = "When are my next lessons";
|
||||
"shortcut_phrase_overall_average" = "My average";
|
||||
"shortcut_phrase_overall_average_question" = "What is my average";
|
||||
|
||||
/* Shortcut Short Titles */
|
||||
"shortcut_short_next_lesson" = "Next lesson";
|
||||
"shortcut_short_closest_lesson" = "Closest lesson";
|
||||
"shortcut_short_today_timetable" = "Today's timetable";
|
||||
"shortcut_short_closest_timetable" = "Closest timetable";
|
||||
"shortcut_short_overall_average" = "My average";
|
||||
|
||||
/* Shortcut Errors */
|
||||
"shortcut_error_no_data" = "No data available. Open the Firka app to refresh.";
|
||||
"shortcut_error_no_upcoming_lesson" = "No more lessons today.";
|
||||
"shortcut_error_no_lessons_today" = "No lessons today.";
|
||||
"shortcut_error_no_lessons_tomorrow" = "No lessons tomorrow.";
|
||||
"shortcut_error_subject_not_found" = "Subject not found.";
|
||||
|
||||
/* Entity Type Names */
|
||||
"entity_lesson" = "Lesson";
|
||||
"entity_grade" = "Grade";
|
||||
"entity_average" = "Average";
|
||||
|
||||
/* Entity Property Names */
|
||||
"entity_prop_name" = "Name";
|
||||
"entity_prop_teacher" = "Teacher";
|
||||
"entity_prop_room" = "Room";
|
||||
"entity_prop_start_time" = "Start time";
|
||||
"entity_prop_end_time" = "End time";
|
||||
"entity_prop_lesson_number" = "Lesson number";
|
||||
"entity_prop_cancelled" = "Cancelled";
|
||||
"entity_prop_substitution" = "Substitution";
|
||||
"entity_prop_theme" = "Theme";
|
||||
"entity_prop_subject" = "Subject";
|
||||
"entity_prop_value" = "Value";
|
||||
"entity_prop_grade" = "Grade";
|
||||
"entity_prop_topic" = "Topic";
|
||||
"entity_prop_type" = "Type";
|
||||
"entity_prop_date" = "Date";
|
||||
"entity_prop_weight" = "Weight";
|
||||
"entity_prop_average" = "Average";
|
||||
"entity_prop_grade_count" = "Grade count";
|
||||
|
||||
11
firka/ios/HomeWidgetsExtension/hu.lproj/AppShortcuts.strings
Normal file
11
firka/ios/HomeWidgetsExtension/hu.lproj/AppShortcuts.strings
Normal file
@@ -0,0 +1,11 @@
|
||||
/* Siri Phrases - Hungarian */
|
||||
"Next lesson ${applicationName}" = "Következő óra ${applicationName}";
|
||||
"What is my next lesson ${applicationName}" = "Mi a következő órám ${applicationName}";
|
||||
"Closest lesson ${applicationName}" = "Legközelebbi óra ${applicationName}";
|
||||
"When is my next lesson ${applicationName}" = "Mikor lesz órám ${applicationName}";
|
||||
"Today's timetable ${applicationName}" = "Mai órarend ${applicationName}";
|
||||
"What lessons do I have today ${applicationName}" = "Milyen óráim vannak ma ${applicationName}";
|
||||
"Closest timetable ${applicationName}" = "Legközelebbi órarend ${applicationName}";
|
||||
"When are my next lessons ${applicationName}" = "Mikor lesznek óráim ${applicationName}";
|
||||
"My average ${applicationName}" = "Átlagom ${applicationName}";
|
||||
"What is my average ${applicationName}" = "Mennyi az átlagom ${applicationName}";
|
||||
@@ -31,3 +31,90 @@
|
||||
/* Subject Selection */
|
||||
"subjects_type" = "Tantárgyak";
|
||||
"no_subjects_available" = "Nincsenek tantárgyak";
|
||||
|
||||
/* Control Widget Titles */
|
||||
"control_home_title" = "Firka Főoldal";
|
||||
"control_home_label" = "Főoldal";
|
||||
"control_home_display" = "Firka - Főoldal";
|
||||
"control_home_description" = "Firka app főoldal megnyitása";
|
||||
"control_grades_title" = "Firka Jegyek";
|
||||
"control_grades_label" = "Jegyek";
|
||||
"control_grades_display" = "Firka - Jegyek";
|
||||
"control_grades_description" = "Firka app jegyek megnyitása";
|
||||
"control_timetable_title" = "Firka Órarend";
|
||||
"control_timetable_label" = "Órarend";
|
||||
"control_timetable_display" = "Firka - Órarend";
|
||||
"control_timetable_description" = "Firka app órarend megnyitása";
|
||||
|
||||
/* Shortcut Intent Titles & Descriptions */
|
||||
"shortcut_next_lesson_title" = "Következő óra";
|
||||
"shortcut_next_lesson_description" = "A következő óra adatai";
|
||||
"shortcut_closest_lesson_title" = "Legközelebbi óra";
|
||||
"shortcut_closest_lesson_description" = "A legközelebbi óra adatai (ma, holnap, vagy a következő tanítási nap)";
|
||||
"shortcut_today_timetable_title" = "Mai órarend";
|
||||
"shortcut_today_timetable_description" = "A mai nap teljes órarendje";
|
||||
"shortcut_tomorrow_timetable_title" = "Holnapi órarend";
|
||||
"shortcut_tomorrow_timetable_description" = "A holnapi nap órarendje";
|
||||
"shortcut_closest_timetable_title" = "Legközelebbi órarend";
|
||||
"shortcut_closest_timetable_description" = "A legközelebbi tanítási nap órarendje (ma, holnap, vagy a következő tanítási nap)";
|
||||
"shortcut_recent_grades_title" = "Legutóbbi jegyek";
|
||||
"shortcut_recent_grades_description" = "A legutóbbi jegyek listája";
|
||||
"shortcut_subject_average_title" = "Tantárgyi átlag";
|
||||
"shortcut_subject_average_description" = "Egy tantárgy átlaga";
|
||||
"shortcut_overall_average_title" = "Összesített átlag";
|
||||
"shortcut_overall_average_description" = "Az összesített tanulmányi átlag";
|
||||
|
||||
/* Shortcut Parameters */
|
||||
"shortcut_param_count" = "Darabszám";
|
||||
"shortcut_param_subject" = "Tantárgy";
|
||||
|
||||
/* Shortcut Phrases */
|
||||
"shortcut_phrase_next_lesson" = "Következő óra";
|
||||
"shortcut_phrase_next_lesson_question" = "Mi a következő órám";
|
||||
"shortcut_phrase_closest_lesson" = "Legközelebbi óra";
|
||||
"shortcut_phrase_closest_lesson_question" = "Mikor lesz órám";
|
||||
"shortcut_phrase_today_timetable" = "Mai órarend";
|
||||
"shortcut_phrase_today_timetable_question" = "Milyen óráim vannak ma";
|
||||
"shortcut_phrase_closest_timetable" = "Legközelebbi órarend";
|
||||
"shortcut_phrase_closest_timetable_question" = "Mikor lesznek óráim";
|
||||
"shortcut_phrase_overall_average" = "Átlagom";
|
||||
"shortcut_phrase_overall_average_question" = "Mennyi az átlagom";
|
||||
|
||||
/* Shortcut Short Titles */
|
||||
"shortcut_short_next_lesson" = "Következő óra";
|
||||
"shortcut_short_closest_lesson" = "Legközelebbi óra";
|
||||
"shortcut_short_today_timetable" = "Mai órarend";
|
||||
"shortcut_short_closest_timetable" = "Legközelebbi órarend";
|
||||
"shortcut_short_overall_average" = "Átlagom";
|
||||
|
||||
/* Shortcut Errors */
|
||||
"shortcut_error_no_data" = "Nincs elérhető adat. Nyisd meg a Firka appot az adatok frissítéséhez.";
|
||||
"shortcut_error_no_upcoming_lesson" = "Ma nincs több órád.";
|
||||
"shortcut_error_no_lessons_today" = "Ma nincs órád.";
|
||||
"shortcut_error_no_lessons_tomorrow" = "Holnap nincs órád.";
|
||||
"shortcut_error_subject_not_found" = "A tantárgy nem található.";
|
||||
|
||||
/* Entity Type Names */
|
||||
"entity_lesson" = "Óra";
|
||||
"entity_grade" = "Jegy";
|
||||
"entity_average" = "Átlag";
|
||||
|
||||
/* Entity Property Names */
|
||||
"entity_prop_name" = "Név";
|
||||
"entity_prop_teacher" = "Tanár";
|
||||
"entity_prop_room" = "Terem";
|
||||
"entity_prop_start_time" = "Kezdés";
|
||||
"entity_prop_end_time" = "Vége";
|
||||
"entity_prop_lesson_number" = "Óraszám";
|
||||
"entity_prop_cancelled" = "Elmarad";
|
||||
"entity_prop_substitution" = "Helyettesítés";
|
||||
"entity_prop_theme" = "Téma";
|
||||
"entity_prop_subject" = "Tantárgy";
|
||||
"entity_prop_value" = "Érték";
|
||||
"entity_prop_grade" = "Jegy";
|
||||
"entity_prop_topic" = "Téma";
|
||||
"entity_prop_type" = "Típus";
|
||||
"entity_prop_date" = "Dátum";
|
||||
"entity_prop_weight" = "Súly";
|
||||
"entity_prop_average" = "Átlag";
|
||||
"entity_prop_grade_count" = "Jegyek száma";
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
AA00000100000005AABBCC05 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = AA00000100000004AABBCC04 /* Localizable.strings */; };
|
||||
14578EED4EA309B337AB389E /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A749415A687CBFC3F46FA876 /* Pods_RunnerTests.framework */; };
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||
213F8C0F6B5418B02DE14204 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 035E9CCBCC6585D0F5639031 /* Pods_Runner.framework */; };
|
||||
@@ -109,6 +110,9 @@
|
||||
AB2E15171B6907C52E8C2B42 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
AE756C46C544099A30412EAF /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||
EBD040A65B2746AF6A3D5C40 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
AA00000100000001AABBCC01 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
AA00000100000002AABBCC02 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
AA00000100000003AABBCC03 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
@@ -284,6 +288,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4F25FCBD2EB1790E0060DAAA /* Runner.entitlements */,
|
||||
AA00000100000004AABBCC04 /* Localizable.strings */,
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
||||
@@ -462,6 +467,7 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
AA00000100000005AABBCC05 /* Localizable.strings in Resources */,
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||
@@ -654,6 +660,16 @@
|
||||
name = LaunchScreen.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
AA00000100000004AABBCC04 /* Localizable.strings */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
AA00000100000001AABBCC01 /* hu */,
|
||||
AA00000100000002AABBCC02 /* en */,
|
||||
AA00000100000003AABBCC03 /* de */,
|
||||
);
|
||||
name = Localizable.strings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
|
||||
@@ -8,8 +8,16 @@
|
||||
</array>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleAllowMixedLocalizations</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<string>en</string>
|
||||
<key>CFBundleLocalizations</key>
|
||||
<array>
|
||||
<string>en</string>
|
||||
<string>hu</string>
|
||||
<string>de</string>
|
||||
</array>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Firka Testing</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
|
||||
7
firka/ios/Runner/de.lproj/Localizable.strings
Normal file
7
firka/ios/Runner/de.lproj/Localizable.strings
Normal file
@@ -0,0 +1,7 @@
|
||||
/* Navigation Intent Titles (shared with HomeWidgetsExtension) */
|
||||
"control_home_title" = "Firka Startseite";
|
||||
"control_home_description" = "Firka Startseite öffnen";
|
||||
"control_grades_title" = "Firka Noten";
|
||||
"control_grades_description" = "Firka Noten öffnen";
|
||||
"control_timetable_title" = "Firka Stundenplan";
|
||||
"control_timetable_description" = "Firka Stundenplan öffnen";
|
||||
7
firka/ios/Runner/en.lproj/Localizable.strings
Normal file
7
firka/ios/Runner/en.lproj/Localizable.strings
Normal file
@@ -0,0 +1,7 @@
|
||||
/* Navigation Intent Titles (shared with HomeWidgetsExtension) */
|
||||
"control_home_title" = "Firka Home";
|
||||
"control_home_description" = "Open Firka home screen";
|
||||
"control_grades_title" = "Firka Grades";
|
||||
"control_grades_description" = "Open Firka grades";
|
||||
"control_timetable_title" = "Firka Timetable";
|
||||
"control_timetable_description" = "Open Firka timetable";
|
||||
7
firka/ios/Runner/hu.lproj/Localizable.strings
Normal file
7
firka/ios/Runner/hu.lproj/Localizable.strings
Normal file
@@ -0,0 +1,7 @@
|
||||
/* Navigation Intent Titles (shared with HomeWidgetsExtension) */
|
||||
"control_home_title" = "Firka Főoldal";
|
||||
"control_home_description" = "Firka app főoldal megnyitása";
|
||||
"control_grades_title" = "Firka Jegyek";
|
||||
"control_grades_description" = "Firka app jegyek megnyitása";
|
||||
"control_timetable_title" = "Firka Órarend";
|
||||
"control_timetable_description" = "Firka app órarend megnyitása";
|
||||
Reference in New Issue
Block a user