diff --git a/firka/ios/HomeWidgetsExtension/Controls/AppControls.swift b/firka/ios/HomeWidgetsExtension/Controls/AppControls.swift index 12530720..d42de9ef 100644 --- a/firka/ios/HomeWidgetsExtension/Controls/AppControls.swift +++ b/firka/ios/HomeWidgetsExtension/Controls/AppControls.swift @@ -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")) } } diff --git a/firka/ios/HomeWidgetsExtension/Intents/AveragesIntent.swift b/firka/ios/HomeWidgetsExtension/Intents/AveragesIntent.swift index d9b43b88..531b2712 100644 --- a/firka/ios/HomeWidgetsExtension/Intents/AveragesIntent.swift +++ b/firka/ios/HomeWidgetsExtension/Intents/AveragesIntent.swift @@ -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")) diff --git a/firka/ios/HomeWidgetsExtension/Providers/TimetableProvider.swift b/firka/ios/HomeWidgetsExtension/Providers/TimetableProvider.swift index df2ec897..599613b6 100644 --- a/firka/ios/HomeWidgetsExtension/Providers/TimetableProvider.swift +++ b/firka/ios/HomeWidgetsExtension/Providers/TimetableProvider.swift @@ -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) } diff --git a/firka/ios/HomeWidgetsExtension/Shortcuts/Entities/AverageEntity.swift b/firka/ios/HomeWidgetsExtension/Shortcuts/Entities/AverageEntity.swift new file mode 100644 index 00000000..390dbf9f --- /dev/null +++ b/firka/ios/HomeWidgetsExtension/Shortcuts/Entities/AverageEntity.swift @@ -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) } + } +} diff --git a/firka/ios/HomeWidgetsExtension/Shortcuts/Entities/GradeEntity.swift b/firka/ios/HomeWidgetsExtension/Shortcuts/Entities/GradeEntity.swift new file mode 100644 index 00000000..b189a16e --- /dev/null +++ b/firka/ios/HomeWidgetsExtension/Shortcuts/Entities/GradeEntity.swift @@ -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) } + } +} diff --git a/firka/ios/HomeWidgetsExtension/Shortcuts/Entities/LessonEntity.swift b/firka/ios/HomeWidgetsExtension/Shortcuts/Entities/LessonEntity.swift new file mode 100644 index 00000000..5cdb0f70 --- /dev/null +++ b/firka/ios/HomeWidgetsExtension/Shortcuts/Entities/LessonEntity.swift @@ -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) } + } +} diff --git a/firka/ios/HomeWidgetsExtension/Shortcuts/Entities/SubjectEntity.swift b/firka/ios/HomeWidgetsExtension/Shortcuts/Entities/SubjectEntity.swift new file mode 100644 index 00000000..aca45708 --- /dev/null +++ b/firka/ios/HomeWidgetsExtension/Shortcuts/Entities/SubjectEntity.swift @@ -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 + } +} diff --git a/firka/ios/HomeWidgetsExtension/Shortcuts/FirkaShortcuts.swift b/firka/ios/HomeWidgetsExtension/Shortcuts/FirkaShortcuts.swift new file mode 100644 index 00000000..10a55720 --- /dev/null +++ b/firka/ios/HomeWidgetsExtension/Shortcuts/FirkaShortcuts.swift @@ -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" + ) + } +} diff --git a/firka/ios/HomeWidgetsExtension/Shortcuts/Intents/GetClosestLessonIntent.swift b/firka/ios/HomeWidgetsExtension/Shortcuts/Intents/GetClosestLessonIntent.swift new file mode 100644 index 00000000..2809b2df --- /dev/null +++ b/firka/ios/HomeWidgetsExtension/Shortcuts/Intents/GetClosestLessonIntent.swift @@ -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 & 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 + } +} diff --git a/firka/ios/HomeWidgetsExtension/Shortcuts/Intents/GetClosestTimetableIntent.swift b/firka/ios/HomeWidgetsExtension/Shortcuts/Intents/GetClosestTimetableIntent.swift new file mode 100644 index 00000000..40f3555a --- /dev/null +++ b/firka/ios/HomeWidgetsExtension/Shortcuts/Intents/GetClosestTimetableIntent.swift @@ -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 + } +} diff --git a/firka/ios/HomeWidgetsExtension/Shortcuts/Intents/GetNextLessonIntent.swift b/firka/ios/HomeWidgetsExtension/Shortcuts/Intents/GetNextLessonIntent.swift new file mode 100644 index 00000000..2e211957 --- /dev/null +++ b/firka/ios/HomeWidgetsExtension/Shortcuts/Intents/GetNextLessonIntent.swift @@ -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 & 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)") + } +} diff --git a/firka/ios/HomeWidgetsExtension/Shortcuts/Intents/GetOverallAverageIntent.swift b/firka/ios/HomeWidgetsExtension/Shortcuts/Intents/GetOverallAverageIntent.swift new file mode 100644 index 00000000..3439dff4 --- /dev/null +++ b/firka/ios/HomeWidgetsExtension/Shortcuts/Intents/GetOverallAverageIntent.swift @@ -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 & 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))") + } +} diff --git a/firka/ios/HomeWidgetsExtension/Shortcuts/Intents/GetRecentGradesIntent.swift b/firka/ios/HomeWidgetsExtension/Shortcuts/Intents/GetRecentGradesIntent.swift new file mode 100644 index 00000000..b4d8f4e8 --- /dev/null +++ b/firka/ios/HomeWidgetsExtension/Shortcuts/Intents/GetRecentGradesIntent.swift @@ -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)") + } +} diff --git a/firka/ios/HomeWidgetsExtension/Shortcuts/Intents/GetSubjectAverageIntent.swift b/firka/ios/HomeWidgetsExtension/Shortcuts/Intents/GetSubjectAverageIntent.swift new file mode 100644 index 00000000..43e7d926 --- /dev/null +++ b/firka/ios/HomeWidgetsExtension/Shortcuts/Intents/GetSubjectAverageIntent.swift @@ -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 & 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))") + } +} diff --git a/firka/ios/HomeWidgetsExtension/Shortcuts/Intents/GetTodayTimetableIntent.swift b/firka/ios/HomeWidgetsExtension/Shortcuts/Intents/GetTodayTimetableIntent.swift new file mode 100644 index 00000000..1fb7aabe --- /dev/null +++ b/firka/ios/HomeWidgetsExtension/Shortcuts/Intents/GetTodayTimetableIntent.swift @@ -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)") + } +} diff --git a/firka/ios/HomeWidgetsExtension/Shortcuts/Intents/GetTomorrowTimetableIntent.swift b/firka/ios/HomeWidgetsExtension/Shortcuts/Intents/GetTomorrowTimetableIntent.swift new file mode 100644 index 00000000..272932a4 --- /dev/null +++ b/firka/ios/HomeWidgetsExtension/Shortcuts/Intents/GetTomorrowTimetableIntent.swift @@ -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)") + } +} diff --git a/firka/ios/HomeWidgetsExtension/Shortcuts/ShortcutError.swift b/firka/ios/HomeWidgetsExtension/Shortcuts/ShortcutError.swift new file mode 100644 index 00000000..8b54af0f --- /dev/null +++ b/firka/ios/HomeWidgetsExtension/Shortcuts/ShortcutError.swift @@ -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.") + } + } +} diff --git a/firka/ios/HomeWidgetsExtension/de.lproj/AppShortcuts.strings b/firka/ios/HomeWidgetsExtension/de.lproj/AppShortcuts.strings new file mode 100644 index 00000000..6bd2adfa --- /dev/null +++ b/firka/ios/HomeWidgetsExtension/de.lproj/AppShortcuts.strings @@ -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}"; diff --git a/firka/ios/HomeWidgetsExtension/de.lproj/Localizable.strings b/firka/ios/HomeWidgetsExtension/de.lproj/Localizable.strings index 87edb042..c698858b 100644 --- a/firka/ios/HomeWidgetsExtension/de.lproj/Localizable.strings +++ b/firka/ios/HomeWidgetsExtension/de.lproj/Localizable.strings @@ -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"; diff --git a/firka/ios/HomeWidgetsExtension/en.lproj/AppShortcuts.strings b/firka/ios/HomeWidgetsExtension/en.lproj/AppShortcuts.strings new file mode 100644 index 00000000..064cecc5 --- /dev/null +++ b/firka/ios/HomeWidgetsExtension/en.lproj/AppShortcuts.strings @@ -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}"; diff --git a/firka/ios/HomeWidgetsExtension/en.lproj/Localizable.strings b/firka/ios/HomeWidgetsExtension/en.lproj/Localizable.strings index 8e8a26aa..694b021d 100644 --- a/firka/ios/HomeWidgetsExtension/en.lproj/Localizable.strings +++ b/firka/ios/HomeWidgetsExtension/en.lproj/Localizable.strings @@ -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"; diff --git a/firka/ios/HomeWidgetsExtension/hu.lproj/AppShortcuts.strings b/firka/ios/HomeWidgetsExtension/hu.lproj/AppShortcuts.strings new file mode 100644 index 00000000..76500a93 --- /dev/null +++ b/firka/ios/HomeWidgetsExtension/hu.lproj/AppShortcuts.strings @@ -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}"; diff --git a/firka/ios/HomeWidgetsExtension/hu.lproj/Localizable.strings b/firka/ios/HomeWidgetsExtension/hu.lproj/Localizable.strings index 91d3b97e..273c0400 100644 --- a/firka/ios/HomeWidgetsExtension/hu.lproj/Localizable.strings +++ b/firka/ios/HomeWidgetsExtension/hu.lproj/Localizable.strings @@ -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"; diff --git a/firka/ios/Runner.xcodeproj/project.pbxproj b/firka/ios/Runner.xcodeproj/project.pbxproj index 94840220..91e7f727 100644 --- a/firka/ios/Runner.xcodeproj/project.pbxproj +++ b/firka/ios/Runner.xcodeproj/project.pbxproj @@ -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 = ""; }; 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 = ""; }; 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 = ""; }; + AA00000100000001AABBCC01 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Localizable.strings; sourceTree = ""; }; + AA00000100000002AABBCC02 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + AA00000100000003AABBCC03 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; /* 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 = ""; }; + AA00000100000004AABBCC04 /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + AA00000100000001AABBCC01 /* hu */, + AA00000100000002AABBCC02 /* en */, + AA00000100000003AABBCC03 /* de */, + ); + name = Localizable.strings; + sourceTree = ""; + }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ diff --git a/firka/ios/Runner/Info.plist b/firka/ios/Runner/Info.plist index 3792d8b3..499c497c 100644 --- a/firka/ios/Runner/Info.plist +++ b/firka/ios/Runner/Info.plist @@ -8,8 +8,16 @@ CADisableMinimumFrameDurationOnPhone + CFBundleAllowMixedLocalizations + CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) + en + CFBundleLocalizations + + en + hu + de + CFBundleDisplayName Firka Testing CFBundleExecutable diff --git a/firka/ios/Runner/de.lproj/Localizable.strings b/firka/ios/Runner/de.lproj/Localizable.strings new file mode 100644 index 00000000..d4f95c1c --- /dev/null +++ b/firka/ios/Runner/de.lproj/Localizable.strings @@ -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"; diff --git a/firka/ios/Runner/en.lproj/Localizable.strings b/firka/ios/Runner/en.lproj/Localizable.strings new file mode 100644 index 00000000..dc35f9fb --- /dev/null +++ b/firka/ios/Runner/en.lproj/Localizable.strings @@ -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"; diff --git a/firka/ios/Runner/hu.lproj/Localizable.strings b/firka/ios/Runner/hu.lproj/Localizable.strings new file mode 100644 index 00000000..4974516c --- /dev/null +++ b/firka/ios/Runner/hu.lproj/Localizable.strings @@ -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";