forked from firka/firka
Add lock-screen/inline widgets & timeline updates
Introduce Lock Screen and Inline variants for Timetable, Grades and Averages widgets and register them in HomeWidgetsBundle. Add new views and widget configurations (accessoryInline, accessoryCircular, accessoryRectangular) under LockScreen/, plus grade badge and average display UI. Extend Localization with many new keys in Helpers/Localization.swift and add lock-screen descriptions in en/de/hu Localizable.strings. Update TimetableProvider timeline logic to generate more frequent (per-minute) update entries for lock-screen families, deduplicate timeline dates and ensure proper midnight transition. Adjust TimetableViews display logic to prefer next lesson when current is absent and fix displayed label selection. Apply project.pbxproj updates to file-exception/group entries and some build phase metadata.
This commit is contained in:
@@ -103,6 +103,96 @@ struct WidgetLocalization {
|
||||
"hu": "Terem",
|
||||
"en": "Room",
|
||||
"de": "Raum"
|
||||
],
|
||||
"until": [
|
||||
"hu": "eddig:",
|
||||
"en": "until",
|
||||
"de": "bis"
|
||||
],
|
||||
"no_more_lessons_today": [
|
||||
"hu": "Ma már nincs több óra",
|
||||
"en": "No more lessons today",
|
||||
"de": "Keine Stunden mehr heute"
|
||||
],
|
||||
"tomorrow": [
|
||||
"hu": "Holnap",
|
||||
"en": "Tomorrow",
|
||||
"de": "Morgen"
|
||||
],
|
||||
"next": [
|
||||
"hu": "Következő",
|
||||
"en": "Next",
|
||||
"de": "Nächste"
|
||||
],
|
||||
"minutes_short": [
|
||||
"hu": "perc",
|
||||
"en": "min",
|
||||
"de": "Min"
|
||||
],
|
||||
"lesson_short": [
|
||||
"hu": "óra",
|
||||
"en": "lesson",
|
||||
"de": "Std"
|
||||
],
|
||||
"in_minutes": [
|
||||
"hu": "%d perc múlva",
|
||||
"en": "in %d min",
|
||||
"de": "in %d Min"
|
||||
],
|
||||
"today_new_grades": [
|
||||
"hu": "Ma: %d új jegy",
|
||||
"en": "Today: %d new",
|
||||
"de": "Heute: %d neue"
|
||||
],
|
||||
"latest": [
|
||||
"hu": "Legutóbbi",
|
||||
"en": "Latest",
|
||||
"de": "Letzte"
|
||||
],
|
||||
"today_grades": [
|
||||
"hu": "Mai jegyek",
|
||||
"en": "Today's grades",
|
||||
"de": "Heutige Noten"
|
||||
],
|
||||
"pieces": [
|
||||
"hu": "%d db",
|
||||
"en": "%d pcs",
|
||||
"de": "%d Stk"
|
||||
],
|
||||
"latest_grade": [
|
||||
"hu": "Legutóbbi jegy",
|
||||
"en": "Latest grade",
|
||||
"de": "Letzte Note"
|
||||
],
|
||||
"average_short": [
|
||||
"hu": "Átlag",
|
||||
"en": "Avg",
|
||||
"de": "Durchschn."
|
||||
],
|
||||
"overall_average_title": [
|
||||
"hu": "Összesített átlag",
|
||||
"en": "Overall average",
|
||||
"de": "Gesamtdurchschnitt"
|
||||
],
|
||||
"subjects_count": [
|
||||
"hu": "%d tárgy",
|
||||
"en": "%d subjects",
|
||||
"de": "%d Fächer"
|
||||
],
|
||||
"subject_averages_title": [
|
||||
"hu": "Tantárgy átlagok",
|
||||
"en": "Subject averages",
|
||||
"de": "Fachdurchschnitte"
|
||||
],
|
||||
"subject_short": [
|
||||
"hu": "tárgy",
|
||||
"en": "subj",
|
||||
"de": "Fächer"
|
||||
],
|
||||
"minutes_abbrev": [
|
||||
"hu": "p",
|
||||
"en": "min",
|
||||
"de": "Min"
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
@@ -4,8 +4,19 @@ import SwiftUI
|
||||
@main
|
||||
struct HomeWidgetsBundle: WidgetBundle {
|
||||
var body: some Widget {
|
||||
// Home Screen Widgets
|
||||
TimetableWidget()
|
||||
GradesWidget()
|
||||
AveragesWidget()
|
||||
|
||||
// Lock Screen Widgets (circular & rectangular)
|
||||
TimetableLockScreenWidget()
|
||||
GradesLockScreenWidget()
|
||||
AveragesLockScreenWidget()
|
||||
|
||||
// Inline Widgets (above the clock)
|
||||
TimetableInlineWidget()
|
||||
GradesInlineWidget()
|
||||
AveragesInlineWidget()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
import WidgetKit
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - Lock Screen Averages Widget
|
||||
|
||||
struct AveragesLockScreenWidget: Widget {
|
||||
let kind: String = "AveragesLockScreenWidget"
|
||||
|
||||
var body: some WidgetConfiguration {
|
||||
AppIntentConfiguration(
|
||||
kind: kind,
|
||||
intent: AveragesWidgetIntent.self,
|
||||
provider: AveragesProvider()
|
||||
) { entry in
|
||||
AveragesLockScreenView(entry: entry)
|
||||
}
|
||||
.configurationDisplayName(LocalizedStringResource("widget_averages_title", defaultValue: "Averages"))
|
||||
.description(LocalizedStringResource("widget_averages_lockscreen_description", defaultValue: "Shows your averages on lock screen"))
|
||||
.supportedFamilies([.accessoryCircular, .accessoryRectangular])
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Lock Screen View
|
||||
|
||||
struct AveragesLockScreenView: View {
|
||||
@Environment(\.widgetFamily) var family
|
||||
let entry: AveragesEntry
|
||||
|
||||
var localization: WidgetLocalization {
|
||||
WidgetLocalization(locale: entry.locale)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
switch family {
|
||||
case .accessoryInline:
|
||||
AveragesInlineView(entry: entry, localization: localization)
|
||||
case .accessoryCircular:
|
||||
AveragesCircularView(entry: entry, localization: localization)
|
||||
case .accessoryRectangular:
|
||||
AveragesRectangularView(entry: entry, localization: localization)
|
||||
default:
|
||||
Text("--")
|
||||
}
|
||||
}
|
||||
.containerBackground(.clear, for: .widget)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Inline View
|
||||
|
||||
struct AveragesInlineView: View {
|
||||
let entry: AveragesEntry
|
||||
let localization: WidgetLocalization
|
||||
|
||||
var body: some View {
|
||||
if let overall = entry.overallAverage {
|
||||
Text("\(localization.string("average_short")): \(String(format: "%.2f", overall))")
|
||||
} else if let first = entry.subjectAverages.first {
|
||||
Text("\(first.name): \(String(format: "%.2f", first.average))")
|
||||
} else {
|
||||
Text(localization.string("no_averages"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Circular View
|
||||
|
||||
struct AveragesCircularView: View {
|
||||
let entry: AveragesEntry
|
||||
let localization: WidgetLocalization
|
||||
|
||||
var body: some View {
|
||||
if let overall = entry.overallAverage {
|
||||
Gauge(value: overall, in: 1...5) {
|
||||
Text("")
|
||||
} currentValueLabel: {
|
||||
Text(String(format: "%.1f", overall))
|
||||
.font(.system(.title2, design: .rounded, weight: .bold))
|
||||
.foregroundStyle(averageColor(overall))
|
||||
}
|
||||
.gaugeStyle(.accessoryCircularCapacity)
|
||||
.tint(averageColor(overall))
|
||||
} else if let first = entry.subjectAverages.first {
|
||||
ZStack {
|
||||
AccessoryWidgetBackground()
|
||||
VStack(spacing: 0) {
|
||||
Text(String(format: "%.1f", first.average))
|
||||
.font(.system(.title2, design: .rounded, weight: .bold))
|
||||
.foregroundStyle(averageColor(first.average))
|
||||
Text(String(first.name.prefix(4)))
|
||||
.font(.system(.caption2))
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ZStack {
|
||||
AccessoryWidgetBackground()
|
||||
Image(systemName: "chart.bar")
|
||||
.font(.title2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func averageColor(_ value: Double) -> Color {
|
||||
switch value {
|
||||
case 4.5...: return .green
|
||||
case 3.5..<4.5: return .blue
|
||||
case 2.5..<3.5: return .yellow
|
||||
case 1.5..<2.5: return .orange
|
||||
default: return .red
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Rectangular View
|
||||
|
||||
struct AveragesRectangularView: View {
|
||||
let entry: AveragesEntry
|
||||
let localization: WidgetLocalization
|
||||
|
||||
var body: some View {
|
||||
if let overall = entry.overallAverage {
|
||||
HStack(spacing: 8) {
|
||||
Text(String(format: "%.2f", overall))
|
||||
.font(.system(.title, design: .rounded, weight: .bold))
|
||||
.foregroundStyle(averageColor(overall))
|
||||
.fixedSize()
|
||||
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Text(localization.string("average_short"))
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
Text(localization.string("subjects_count", entry.subjectAverages.count))
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
} else if !entry.subjectAverages.isEmpty {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(localization.string("subject_averages_title"))
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
HStack(spacing: 8) {
|
||||
ForEach(entry.subjectAverages.prefix(3), id: \.uid) { subject in
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Text(String(format: "%.1f", subject.average))
|
||||
.font(.system(.subheadline, design: .rounded, weight: .bold))
|
||||
.foregroundStyle(averageColor(subject.average))
|
||||
Text(String(subject.name.prefix(5)))
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.secondary)
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
} else {
|
||||
VStack(alignment: .leading) {
|
||||
Label(localization.string("no_averages"), systemImage: "chart.bar")
|
||||
.font(.headline)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
}
|
||||
|
||||
private func averageColor(_ value: Double) -> Color {
|
||||
switch value {
|
||||
case 4.5...: return .green
|
||||
case 3.5..<4.5: return .blue
|
||||
case 2.5..<3.5: return .yellow
|
||||
case 1.5..<2.5: return .orange
|
||||
default: return .red
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
import WidgetKit
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - Lock Screen Grades Widget
|
||||
|
||||
struct GradesLockScreenWidget: Widget {
|
||||
let kind: String = "GradesLockScreenWidget"
|
||||
|
||||
var body: some WidgetConfiguration {
|
||||
AppIntentConfiguration(
|
||||
kind: kind,
|
||||
intent: GradesWidgetIntent.self,
|
||||
provider: GradesProvider()
|
||||
) { entry in
|
||||
GradesLockScreenView(entry: entry)
|
||||
}
|
||||
.configurationDisplayName(LocalizedStringResource("widget_grades_title", defaultValue: "Recent Grades"))
|
||||
.description(LocalizedStringResource("widget_grades_lockscreen_description", defaultValue: "Shows recent grades on lock screen"))
|
||||
.supportedFamilies([.accessoryCircular, .accessoryRectangular])
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Lock Screen View
|
||||
|
||||
struct GradesLockScreenView: View {
|
||||
@Environment(\.widgetFamily) var family
|
||||
let entry: GradesEntry
|
||||
|
||||
var localization: WidgetLocalization {
|
||||
WidgetLocalization(locale: entry.locale)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
switch family {
|
||||
case .accessoryInline:
|
||||
GradesInlineView(entry: entry, localization: localization)
|
||||
case .accessoryCircular:
|
||||
GradesCircularView(entry: entry, localization: localization)
|
||||
case .accessoryRectangular:
|
||||
GradesRectangularView(entry: entry, localization: localization)
|
||||
default:
|
||||
Text("--")
|
||||
}
|
||||
}
|
||||
.containerBackground(.clear, for: .widget)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Inline View
|
||||
|
||||
struct GradesInlineView: View {
|
||||
let entry: GradesEntry
|
||||
let localization: WidgetLocalization
|
||||
|
||||
var todayGrades: [WidgetGrade] {
|
||||
let calendar = Calendar.current
|
||||
let today = calendar.startOfDay(for: Date())
|
||||
return entry.grades.filter { calendar.startOfDay(for: $0.recordDate) == today }
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
if let latest = entry.grades.first {
|
||||
if todayGrades.count > 0 {
|
||||
Text(localization.string("today_new_grades", todayGrades.count))
|
||||
} else {
|
||||
Text("\(localization.string("latest")): \(latest.displayValue) \(latest.subject.name)")
|
||||
}
|
||||
} else {
|
||||
Text(localization.string("no_grades"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Circular View
|
||||
|
||||
struct GradesCircularView: View {
|
||||
let entry: GradesEntry
|
||||
let localization: WidgetLocalization
|
||||
|
||||
var todayGradesCount: Int {
|
||||
let calendar = Calendar.current
|
||||
let today = calendar.startOfDay(for: Date())
|
||||
return entry.grades.filter { calendar.startOfDay(for: $0.recordDate) == today }.count
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
if let latest = entry.grades.first {
|
||||
ZStack {
|
||||
AccessoryWidgetBackground()
|
||||
Text(latest.displayValue)
|
||||
.font(.system(.title, design: .rounded, weight: .bold))
|
||||
.foregroundStyle(gradeColor(latest.numericValue))
|
||||
}
|
||||
} else {
|
||||
ZStack {
|
||||
AccessoryWidgetBackground()
|
||||
Image(systemName: "graduationcap")
|
||||
.font(.title2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func gradeColor(_ value: Int?) -> Color {
|
||||
guard let value = value else { return .primary }
|
||||
switch value {
|
||||
case 5: return .green
|
||||
case 4: return .blue
|
||||
case 3: return .yellow
|
||||
case 2: return .orange
|
||||
case 1: return .red
|
||||
default: return .primary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Rectangular View
|
||||
|
||||
struct GradesRectangularView: View {
|
||||
let entry: GradesEntry
|
||||
let localization: WidgetLocalization
|
||||
|
||||
var todayGrades: [WidgetGrade] {
|
||||
let calendar = Calendar.current
|
||||
let today = calendar.startOfDay(for: Date())
|
||||
return entry.grades.filter { calendar.startOfDay(for: $0.recordDate) == today }
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
if !entry.grades.isEmpty {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
if todayGrades.count > 0 {
|
||||
HStack {
|
||||
Text(localization.string("today_grades"))
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
Spacer()
|
||||
Text(localization.string("pieces", todayGrades.count))
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
HStack(spacing: 4) {
|
||||
ForEach(todayGrades.prefix(5), id: \.uid) { grade in
|
||||
GradeBadge(grade: grade)
|
||||
}
|
||||
if todayGrades.count > 5 {
|
||||
Text("+\(todayGrades.count - 5)")
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
} else if let latest = entry.grades.first {
|
||||
HStack {
|
||||
Text(localization.string("latest_grade"))
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
Spacer()
|
||||
Text(formatDate(latest.recordDate))
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
HStack {
|
||||
Text(latest.displayValue)
|
||||
.font(.title2)
|
||||
.fontWeight(.bold)
|
||||
.foregroundStyle(gradeColor(latest.numericValue))
|
||||
Text(latest.subject.name)
|
||||
.font(.subheadline)
|
||||
.lineLimit(1)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
} else {
|
||||
VStack(alignment: .leading) {
|
||||
Label(localization.string("no_grades"), systemImage: "graduationcap")
|
||||
.font(.headline)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
}
|
||||
|
||||
private func formatDate(_ date: Date) -> String {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "MMM d."
|
||||
return formatter.string(from: date)
|
||||
}
|
||||
|
||||
private func gradeColor(_ value: Int?) -> Color {
|
||||
guard let value = value else { return .primary }
|
||||
switch value {
|
||||
case 5: return .green
|
||||
case 4: return .blue
|
||||
case 3: return .yellow
|
||||
case 2: return .orange
|
||||
case 1: return .red
|
||||
default: return .primary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Grade Badge
|
||||
|
||||
struct GradeBadge: View {
|
||||
let grade: WidgetGrade
|
||||
|
||||
var body: some View {
|
||||
Text(grade.displayValue)
|
||||
.font(.system(.caption, design: .rounded, weight: .bold))
|
||||
.foregroundStyle(gradeColor(grade.numericValue))
|
||||
.padding(.horizontal, 6)
|
||||
.padding(.vertical, 2)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 4)
|
||||
.fill(gradeColor(grade.numericValue).opacity(0.2))
|
||||
)
|
||||
}
|
||||
|
||||
private func gradeColor(_ value: Int?) -> Color {
|
||||
guard let value = value else { return .primary }
|
||||
switch value {
|
||||
case 5: return .green
|
||||
case 4: return .blue
|
||||
case 3: return .yellow
|
||||
case 2: return .orange
|
||||
case 1: return .red
|
||||
default: return .primary
|
||||
}
|
||||
}
|
||||
}
|
||||
142
firka/ios/HomeWidgetsExtension/LockScreen/InlineWidgets.swift
Normal file
142
firka/ios/HomeWidgetsExtension/LockScreen/InlineWidgets.swift
Normal file
@@ -0,0 +1,142 @@
|
||||
import WidgetKit
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - Timetable Inline Widget
|
||||
|
||||
struct TimetableInlineWidget: Widget {
|
||||
let kind: String = "TimetableInlineWidget"
|
||||
|
||||
var body: some WidgetConfiguration {
|
||||
AppIntentConfiguration(
|
||||
kind: kind,
|
||||
intent: TimetableWidgetIntent.self,
|
||||
provider: TimetableProvider()
|
||||
) { entry in
|
||||
TimetableInlineWidgetView(entry: entry)
|
||||
.containerBackground(.clear, for: .widget)
|
||||
}
|
||||
.configurationDisplayName(LocalizedStringResource("widget_timetable_title", defaultValue: "Timetable"))
|
||||
.description(LocalizedStringResource("widget_timetable_inline_description", defaultValue: "Shows next lesson above the clock"))
|
||||
.supportedFamilies([.accessoryInline])
|
||||
}
|
||||
}
|
||||
|
||||
struct TimetableInlineWidgetView: View {
|
||||
let entry: TimetableEntry
|
||||
|
||||
var localization: WidgetLocalization {
|
||||
WidgetLocalization(locale: entry.data?.locale ?? "hu")
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
if entry.state == .onBreak, let breakInfo = entry.breakInfo {
|
||||
Text(localization.string(breakInfo.nameKey))
|
||||
} else if let current = entry.currentLesson {
|
||||
let remaining = minutesRemaining(until: current.end)
|
||||
Text("\(current.subject.name) · \(remaining) \(localization.string("minutes_abbrev"))")
|
||||
} else if let next = entry.nextLesson {
|
||||
let until = minutesRemaining(until: next.start)
|
||||
if until <= 0 {
|
||||
Text("→ \(next.subject.name)")
|
||||
} else {
|
||||
Text("→ \(next.subject.name) · \(until) \(localization.string("minutes_abbrev"))")
|
||||
}
|
||||
} else if entry.isNextDay {
|
||||
if let first = entry.lessons.first {
|
||||
Text("\(localization.string("tomorrow")): \(first.subject.name)")
|
||||
} else {
|
||||
Text(localization.string("no_lessons"))
|
||||
}
|
||||
} else {
|
||||
Text(localization.string("no_lessons"))
|
||||
}
|
||||
}
|
||||
|
||||
private func minutesRemaining(until date: Date) -> Int {
|
||||
let diff = date.timeIntervalSince(entry.date)
|
||||
return max(0, Int(ceil(diff / 60)))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Grades Inline Widget
|
||||
|
||||
struct GradesInlineWidget: Widget {
|
||||
let kind: String = "GradesInlineWidget"
|
||||
|
||||
var body: some WidgetConfiguration {
|
||||
AppIntentConfiguration(
|
||||
kind: kind,
|
||||
intent: GradesWidgetIntent.self,
|
||||
provider: GradesProvider()
|
||||
) { entry in
|
||||
GradesInlineWidgetView(entry: entry)
|
||||
.containerBackground(.clear, for: .widget)
|
||||
}
|
||||
.configurationDisplayName(LocalizedStringResource("widget_grades_title", defaultValue: "Grades"))
|
||||
.description(LocalizedStringResource("widget_grades_inline_description", defaultValue: "Shows recent grades above the clock"))
|
||||
.supportedFamilies([.accessoryInline])
|
||||
}
|
||||
}
|
||||
|
||||
struct GradesInlineWidgetView: View {
|
||||
let entry: GradesEntry
|
||||
|
||||
var localization: WidgetLocalization {
|
||||
WidgetLocalization(locale: entry.locale)
|
||||
}
|
||||
|
||||
var todayGrades: [WidgetGrade] {
|
||||
let calendar = Calendar.current
|
||||
let today = calendar.startOfDay(for: Date())
|
||||
return entry.grades.filter { calendar.startOfDay(for: $0.recordDate) == today }
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
if todayGrades.count > 0 {
|
||||
Text("📝 \(localization.string("today_new_grades", todayGrades.count))")
|
||||
} else if let latest = entry.grades.first {
|
||||
// No grades today - show latest
|
||||
Text("\(localization.string("latest")): \(latest.displayValue) \(latest.subject.name)")
|
||||
} else {
|
||||
Text(localization.string("no_grades"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Averages Inline Widget
|
||||
|
||||
struct AveragesInlineWidget: Widget {
|
||||
let kind: String = "AveragesInlineWidget"
|
||||
|
||||
var body: some WidgetConfiguration {
|
||||
AppIntentConfiguration(
|
||||
kind: kind,
|
||||
intent: AveragesWidgetIntent.self,
|
||||
provider: AveragesProvider()
|
||||
) { entry in
|
||||
AveragesInlineWidgetView(entry: entry)
|
||||
.containerBackground(.clear, for: .widget)
|
||||
}
|
||||
.configurationDisplayName(LocalizedStringResource("widget_averages_title", defaultValue: "Averages"))
|
||||
.description(LocalizedStringResource("widget_averages_inline_description", defaultValue: "Shows your average above the clock"))
|
||||
.supportedFamilies([.accessoryInline])
|
||||
}
|
||||
}
|
||||
|
||||
struct AveragesInlineWidgetView: View {
|
||||
let entry: AveragesEntry
|
||||
|
||||
var localization: WidgetLocalization {
|
||||
WidgetLocalization(locale: entry.locale)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
if let overall = entry.overallAverage {
|
||||
Text("\(localization.string("average_short")): \(String(format: "%.2f", overall)) · \(entry.subjectAverages.count) \(localization.string("subject_short"))")
|
||||
} else if let first = entry.subjectAverages.first {
|
||||
Text("\(first.name): \(String(format: "%.2f", first.average))")
|
||||
} else {
|
||||
Text(localization.string("no_averages"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
import WidgetKit
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - Lock Screen Timetable Widget
|
||||
|
||||
struct TimetableLockScreenWidget: Widget {
|
||||
let kind: String = "TimetableLockScreenWidget"
|
||||
|
||||
var body: some WidgetConfiguration {
|
||||
AppIntentConfiguration(
|
||||
kind: kind,
|
||||
intent: TimetableWidgetIntent.self,
|
||||
provider: TimetableProvider()
|
||||
) { entry in
|
||||
TimetableLockScreenView(entry: entry)
|
||||
}
|
||||
.configurationDisplayName(LocalizedStringResource("widget_timetable_title", defaultValue: "Timetable"))
|
||||
.description(LocalizedStringResource("widget_timetable_lockscreen_description", defaultValue: "Shows current lesson on lock screen"))
|
||||
.supportedFamilies([.accessoryCircular, .accessoryRectangular])
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Lock Screen View
|
||||
|
||||
struct TimetableLockScreenView: View {
|
||||
@Environment(\.widgetFamily) var family
|
||||
let entry: TimetableEntry
|
||||
|
||||
var localization: WidgetLocalization {
|
||||
WidgetLocalization(locale: entry.data?.locale ?? "hu")
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
switch family {
|
||||
case .accessoryInline:
|
||||
TimetableInlineView(entry: entry, localization: localization)
|
||||
case .accessoryCircular:
|
||||
TimetableCircularView(entry: entry, localization: localization)
|
||||
case .accessoryRectangular:
|
||||
TimetableRectangularView(entry: entry, localization: localization)
|
||||
default:
|
||||
Text("--")
|
||||
}
|
||||
}
|
||||
.containerBackground(.clear, for: .widget)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Inline View (single line next to date)
|
||||
|
||||
struct TimetableInlineView: View {
|
||||
let entry: TimetableEntry
|
||||
let localization: WidgetLocalization
|
||||
|
||||
var body: some View {
|
||||
if entry.state == .onBreak, let breakInfo = entry.breakInfo {
|
||||
Text("🏖️ \(localization.string(breakInfo.nameKey))")
|
||||
} else if let current = entry.currentLesson {
|
||||
let remaining = minutesRemaining(until: current.end)
|
||||
Text("\(current.subject.name) - \(remaining) \(localization.string("minutes_short"))")
|
||||
} else if let next = entry.nextLesson {
|
||||
let until = minutesRemaining(until: next.start)
|
||||
if until <= 0 {
|
||||
Text("\(localization.string("next")): \(next.subject.name)")
|
||||
} else {
|
||||
Text("\(next.subject.name) \(localization.string("in_minutes", until))")
|
||||
}
|
||||
} else if entry.isNextDay {
|
||||
if let first = entry.lessons.first {
|
||||
Text("\(localization.string("tomorrow")): \(first.subject.name)")
|
||||
} else {
|
||||
Text(localization.string("no_lessons"))
|
||||
}
|
||||
} else {
|
||||
Text(localization.string("no_lessons"))
|
||||
}
|
||||
}
|
||||
|
||||
private func minutesRemaining(until date: Date) -> Int {
|
||||
let diff = date.timeIntervalSince(entry.date)
|
||||
return max(0, Int(ceil(diff / 60)))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Circular View (small circle)
|
||||
|
||||
struct TimetableCircularView: View {
|
||||
let entry: TimetableEntry
|
||||
let localization: WidgetLocalization
|
||||
|
||||
var body: some View {
|
||||
if entry.state == .onBreak {
|
||||
ZStack {
|
||||
AccessoryWidgetBackground()
|
||||
Image(systemName: "sun.max.fill")
|
||||
.font(.title2)
|
||||
}
|
||||
} else if let current = entry.currentLesson {
|
||||
let remaining = minutesRemaining(until: current.end)
|
||||
Gauge(value: Double(remaining), in: 0...45) {
|
||||
Text("")
|
||||
} currentValueLabel: {
|
||||
Text("\(remaining)")
|
||||
.font(.system(.title2, design: .rounded, weight: .bold))
|
||||
}
|
||||
.gaugeStyle(.accessoryCircularCapacity)
|
||||
} else if let next = entry.nextLesson {
|
||||
let until = minutesRemaining(until: next.start)
|
||||
ZStack {
|
||||
AccessoryWidgetBackground()
|
||||
VStack(spacing: 0) {
|
||||
Text("\(until)")
|
||||
.font(.system(.title2, design: .rounded, weight: .bold))
|
||||
Text(localization.string("minutes_short"))
|
||||
.font(.system(.caption2))
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
} else if let lesson = entry.lessons.first, let lessonNum = lesson.lessonNumber {
|
||||
ZStack {
|
||||
AccessoryWidgetBackground()
|
||||
VStack(spacing: 0) {
|
||||
Text("\(lessonNum).")
|
||||
.font(.system(.title2, design: .rounded, weight: .bold))
|
||||
Text(localization.string("lesson_short"))
|
||||
.font(.system(.caption2))
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ZStack {
|
||||
AccessoryWidgetBackground()
|
||||
Image(systemName: "calendar.badge.checkmark")
|
||||
.font(.title2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func minutesRemaining(until date: Date) -> Int {
|
||||
let diff = date.timeIntervalSince(entry.date)
|
||||
return max(0, Int(ceil(diff / 60)))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Rectangular View (medium rectangle)
|
||||
|
||||
struct TimetableRectangularView: View {
|
||||
let entry: TimetableEntry
|
||||
let localization: WidgetLocalization
|
||||
|
||||
var body: some View {
|
||||
if entry.state == .onBreak, let breakInfo = entry.breakInfo {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Label(localization.string(breakInfo.nameKey), systemImage: "sun.max.fill")
|
||||
.font(.headline)
|
||||
Text(localization.string("until") + " " + formatDate(breakInfo.endDate))
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
} else if let current = entry.currentLesson {
|
||||
let remaining = minutesRemaining(until: current.end)
|
||||
let lessonNum = current.lessonNumber ?? 0
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
HStack {
|
||||
Text("\(lessonNum). \(current.subject.name)")
|
||||
.font(.headline)
|
||||
.lineLimit(1)
|
||||
Spacer()
|
||||
Text("\(remaining)'")
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
HStack {
|
||||
if let room = current.roomName {
|
||||
Label(room, systemImage: "mappin")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
Spacer()
|
||||
Text(formatTimeRange(current.start, current.end))
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
} else if let next = entry.nextLesson {
|
||||
let until = minutesRemaining(until: next.start)
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
HStack {
|
||||
Text(entry.isNextDay ? localization.string("tomorrow") : localization.string("next"))
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
Spacer()
|
||||
if until > 0 && !entry.isNextDay {
|
||||
Text(localization.string("in_minutes", until))
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
Text("\(next.lessonNumber ?? 0). \(next.subject.name)")
|
||||
.font(.headline)
|
||||
.lineLimit(1)
|
||||
HStack {
|
||||
if let room = next.roomName {
|
||||
Label(room, systemImage: "mappin")
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
Spacer()
|
||||
Text(formatTimeRange(next.start, next.end))
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
} else {
|
||||
VStack(alignment: .leading) {
|
||||
Label(localization.string("no_lessons"), systemImage: "checkmark.circle")
|
||||
.font(.headline)
|
||||
Text(localization.string("no_more_lessons_today"))
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
}
|
||||
|
||||
private func minutesRemaining(until date: Date) -> Int {
|
||||
let diff = date.timeIntervalSince(entry.date)
|
||||
return max(0, Int(ceil(diff / 60)))
|
||||
}
|
||||
|
||||
private func formatTimeRange(_ start: Date, _ end: Date) -> String {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "HH:mm"
|
||||
return "\(formatter.string(from: start)) - \(formatter.string(from: end))"
|
||||
}
|
||||
|
||||
private func formatDate(_ date: Date) -> String {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "MMM d."
|
||||
return formatter.string(from: date)
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,7 @@ struct TimetableProvider: AppIntentTimelineProvider {
|
||||
func timeline(for configuration: TimetableWidgetIntent, in context: Context) async -> Timeline<TimetableEntry> {
|
||||
var entries: [TimetableEntry] = []
|
||||
let now = Date()
|
||||
let calendar = Calendar.current
|
||||
|
||||
let data = WidgetData.load()
|
||||
|
||||
@@ -66,31 +67,64 @@ struct TimetableProvider: AppIntentTimelineProvider {
|
||||
debugInfo: WidgetData.lastError
|
||||
)
|
||||
entries.append(entry)
|
||||
return Timeline(entries: entries, policy: .after(Calendar.current.startOfDay(for: now.addingTimeInterval(86400))))
|
||||
return Timeline(entries: entries, policy: .after(calendar.startOfDay(for: now.addingTimeInterval(86400))))
|
||||
}
|
||||
|
||||
let todayLessons = data?.timetable.today ?? []
|
||||
|
||||
entries.append(createEntry(for: configuration, date: now))
|
||||
|
||||
var transitionTimes: Set<Date> = []
|
||||
let currentLesson = todayLessons.first { now >= $0.start && now <= $0.end }
|
||||
let nextLesson = todayLessons.first { $0.start > now }
|
||||
|
||||
let isLockScreenWidget = context.family == .accessoryInline ||
|
||||
context.family == .accessoryCircular ||
|
||||
context.family == .accessoryRectangular
|
||||
|
||||
if isLockScreenWidget {
|
||||
var minuteEntries: [Date] = []
|
||||
|
||||
if let current = currentLesson {
|
||||
var time = now.addingTimeInterval(60)
|
||||
while time <= current.end && minuteEntries.count < 60 {
|
||||
minuteEntries.append(time)
|
||||
time = time.addingTimeInterval(60)
|
||||
}
|
||||
minuteEntries.append(current.end.addingTimeInterval(1))
|
||||
}
|
||||
|
||||
if let next = nextLesson {
|
||||
var time = currentLesson?.end.addingTimeInterval(60) ?? now.addingTimeInterval(60)
|
||||
while time < next.start && minuteEntries.count < 120 {
|
||||
minuteEntries.append(time)
|
||||
time = time.addingTimeInterval(60)
|
||||
}
|
||||
minuteEntries.append(next.start)
|
||||
}
|
||||
|
||||
for time in minuteEntries {
|
||||
if time > now {
|
||||
entries.append(createEntry(for: configuration, date: time))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for lesson in todayLessons {
|
||||
if lesson.start > now {
|
||||
transitionTimes.insert(lesson.start)
|
||||
entries.append(createEntry(for: configuration, date: lesson.start))
|
||||
}
|
||||
if lesson.end > now {
|
||||
transitionTimes.insert(lesson.end.addingTimeInterval(1))
|
||||
entries.append(createEntry(for: configuration, date: lesson.end.addingTimeInterval(1)))
|
||||
}
|
||||
}
|
||||
|
||||
let midnight = Calendar.current.startOfDay(for: now.addingTimeInterval(86400))
|
||||
transitionTimes.insert(midnight)
|
||||
let midnight = calendar.startOfDay(for: now.addingTimeInterval(86400))
|
||||
entries.append(createEntry(for: configuration, date: midnight))
|
||||
|
||||
for time in transitionTimes {
|
||||
entries.append(createEntry(for: configuration, date: time))
|
||||
let uniqueDates = Set(entries.map { $0.date })
|
||||
entries = uniqueDates.map { date in
|
||||
entries.first { $0.date == date }!
|
||||
}
|
||||
|
||||
entries.sort { $0.date < $1.date }
|
||||
|
||||
return Timeline(entries: entries, policy: .atEnd)
|
||||
|
||||
@@ -11,7 +11,20 @@ struct TimetableSmallView: View {
|
||||
}
|
||||
|
||||
var displayLesson: WidgetLesson? {
|
||||
(entry.configuration.displayMode ?? .current) == .current ? entry.currentLesson : entry.nextLesson
|
||||
let mode = entry.configuration.displayMode ?? .current
|
||||
if mode == .current {
|
||||
return entry.currentLesson ?? entry.nextLesson
|
||||
} else {
|
||||
return entry.nextLesson
|
||||
}
|
||||
}
|
||||
|
||||
var isShowingNextLesson: Bool {
|
||||
let mode = entry.configuration.displayMode ?? .current
|
||||
if mode == .current {
|
||||
return entry.currentLesson == nil && entry.nextLesson != nil
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var liquidGlassPrimary: Color {
|
||||
@@ -27,9 +40,9 @@ struct TimetableSmallView: View {
|
||||
WidgetBackground(style: style, colors: nil)
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text((entry.configuration.displayMode ?? .current) == .current ?
|
||||
localization.string("current_lesson") :
|
||||
localization.string("next_lesson"))
|
||||
Text(isShowingNextLesson ?
|
||||
localization.string("next_lesson") :
|
||||
localization.string("current_lesson"))
|
||||
.font(.caption)
|
||||
.widgetTextStyle(style, colors: nil, isPrimary: false)
|
||||
|
||||
|
||||
@@ -8,6 +8,11 @@
|
||||
"widget_grades_description" = "Zeigt deine letzten Noten";
|
||||
"widget_averages_description" = "Zeigt Fachdurchschnitte";
|
||||
|
||||
/* Lock Screen Widget Descriptions */
|
||||
"widget_timetable_lockscreen_description" = "Stundenplan auf dem Sperrbildschirm";
|
||||
"widget_grades_lockscreen_description" = "Noten auf dem Sperrbildschirm";
|
||||
"widget_averages_lockscreen_description" = "Durchschnitte auf dem Sperrbildschirm";
|
||||
|
||||
/* Parameter Titles */
|
||||
"param_style" = "Stil";
|
||||
"param_display_mode_small" = "Anzeige (kleines Widget)";
|
||||
|
||||
@@ -8,6 +8,11 @@
|
||||
"widget_grades_description" = "Shows your recent grades";
|
||||
"widget_averages_description" = "Shows subject averages";
|
||||
|
||||
/* Lock Screen Widget Descriptions */
|
||||
"widget_timetable_lockscreen_description" = "Timetable on lock screen";
|
||||
"widget_grades_lockscreen_description" = "Grades on lock screen";
|
||||
"widget_averages_lockscreen_description" = "Averages on lock screen";
|
||||
|
||||
/* Parameter Titles */
|
||||
"param_style" = "Style";
|
||||
"param_display_mode_small" = "Display (small widget)";
|
||||
|
||||
@@ -8,6 +8,11 @@
|
||||
"widget_grades_description" = "A legutóbbi jegyeidet mutatja";
|
||||
"widget_averages_description" = "A tantárgyi átlagokat mutatja";
|
||||
|
||||
/* Lock Screen Widget Descriptions */
|
||||
"widget_timetable_lockscreen_description" = "Órarend a zárolt képernyőn";
|
||||
"widget_grades_lockscreen_description" = "Jegyek a zárolt képernyőn";
|
||||
"widget_averages_lockscreen_description" = "Átlagok a zárolt képernyőn";
|
||||
|
||||
/* Parameter Titles */
|
||||
"param_style" = "Stílus";
|
||||
"param_display_mode_small" = "Megjelenítés (kis widget)";
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 70;
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
@@ -112,21 +112,21 @@
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
4F4E70D02EF565FF00C90AD1 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
|
||||
4F4E70D02EF565FF00C90AD1 /* Exceptions for "LiveActivityWidget" folder in "Runner" target */ = {
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
membershipExceptions = (
|
||||
ActivityAttributes.swift,
|
||||
);
|
||||
target = 97C146ED1CF9000F007C117D /* Runner */;
|
||||
};
|
||||
4F6C1D3E2ECD3FBD00F819D7 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
|
||||
4F6C1D3E2ECD3FBD00F819D7 /* Exceptions for "LiveActivityWidget" folder in "LiveActivityWidget" target */ = {
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
membershipExceptions = (
|
||||
Info.plist,
|
||||
);
|
||||
target = 4F30C7642E8FBF9D008BB46C /* LiveActivityWidget */;
|
||||
};
|
||||
4FE64E472F27B07B006F9205 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
|
||||
4FE64E472F27B07B006F9205 /* Exceptions for "HomeWidgetsExtension" folder in "HomeWidgetsExtension" target */ = {
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
membershipExceptions = (
|
||||
Info.plist,
|
||||
@@ -136,8 +136,31 @@
|
||||
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||
4F30C76A2E8FBF9D008BB46C /* LiveActivityWidget */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (4F4E70D02EF565FF00C90AD1 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 4F6C1D3E2ECD3FBD00F819D7 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = LiveActivityWidget; sourceTree = "<group>"; };
|
||||
4FE64E362F27B07A006F9205 /* HomeWidgetsExtension */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (4FE64E472F27B07B006F9205 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = HomeWidgetsExtension; sourceTree = "<group>"; };
|
||||
4F30C76A2E8FBF9D008BB46C /* LiveActivityWidget */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
4F4E70D02EF565FF00C90AD1 /* Exceptions for "LiveActivityWidget" folder in "Runner" target */,
|
||||
4F6C1D3E2ECD3FBD00F819D7 /* Exceptions for "LiveActivityWidget" folder in "LiveActivityWidget" target */,
|
||||
);
|
||||
explicitFileTypes = {
|
||||
};
|
||||
explicitFolders = (
|
||||
);
|
||||
path = LiveActivityWidget;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4FE64E362F27B07A006F9205 /* HomeWidgetsExtension */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
4FE64E472F27B07B006F9205 /* Exceptions for "HomeWidgetsExtension" folder in "HomeWidgetsExtension" target */,
|
||||
);
|
||||
explicitFileTypes = {
|
||||
};
|
||||
explicitFolders = (
|
||||
);
|
||||
path = HomeWidgetsExtension;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXFileSystemSynchronizedRootGroup section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -449,14 +472,10 @@
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
||||
@@ -545,14 +564,10 @@
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||
|
||||
Reference in New Issue
Block a user