diff --git a/Widgets/Apps Widget/AppsWidget.swift b/Widgets/Apps Widget/AppsWidget.swift new file mode 100644 index 00000000..909753bb --- /dev/null +++ b/Widgets/Apps Widget/AppsWidget.swift @@ -0,0 +1,178 @@ +// +// AppsWidget.swift +// WidgetsExtension +// +// Created by ned on 08/03/21. +// Copyright © 2021 ned. All rights reserved. +// + +import Foundation +import WidgetKit +import SwiftUI +import Intents +import Combine +import Localize_Swift + +private var cancellables = Set() + +struct AppsWidgetsTimelineEntry: TimelineEntry { + let date: Date + let configuration: ConfigurationIntent + let content: AppdbSearchResource.Response? +} + +struct AppsWidgetsProvider: IntentTimelineProvider { + + typealias Entry = AppsWidgetsTimelineEntry + + let appdbRepository = AppdbRepository() + + func placeholder(in context: Context) -> AppsWidgetsTimelineEntry { + Entry(date: Date(), configuration: ConfigurationIntent(), content: nil) + } + + func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Entry) -> Void) { + completion(Entry(date: Date(), configuration: configuration, content: nil)) + } + + func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline) -> Void) { + + appdbRepository.fetchAPIResource(AppdbSearchResource(configuration.type, configuration.order, configuration.price)) + .receive(on: RunLoop.main) + .sink(receiveCompletion: { + switch $0 { + case .failure(let error): + print(error.localizedDescription) + completion(Timeline(entries: [], policy: .atEnd)) + default: break + } + }, receiveValue: { + let entries = [ + Entry(date: Date(), configuration: configuration, content: $0) + ] + let nextUpdate = Calendar.autoupdatingCurrent.date(byAdding: .hour, value: 6, to: Calendar.autoupdatingCurrent.startOfDay(for: Date()))! + let timeline = Timeline(entries: entries, policy: .after(nextUpdate)) + completion(timeline) + }) + .store(in: &cancellables) + } +} + +struct AppsWidgetsEntryView: View { + + var entry: AppsWidgetsProvider.Entry + + var orderString: String { + switch entry.configuration.order { + case .recent: return "Recently Uploaded".localized() + case .today: return "Popular Today".localized() + case .week: return "Popular This Week".localized() + case .month: return "Popular This Month".localized() + case .year: return "Popular This Year".localized() + case .all_time: return "Popular All Time".localized() + case .unknown: return "" + } + } + + var typeString: String { + switch entry.configuration.type { + case .ios: return "iOS".localized() + case .cydia: return "Cydia".localized() + case .books: return "Books".localized() + case .unknown: return "" + } + } + + var body: some View { + if entry.content == nil || entry.content!.data.isEmpty { + let dummyData = [Content](repeating: Content.dummy, count: 25) + AppsWidgetsMainContentView(date: entry.date, header: orderString, type: typeString, content: dummyData) + .redacted(reason: .placeholder) + } else { + AppsWidgetsMainContentView(date: entry.date, header: orderString, type: typeString, content: entry.content!.data) + } + } +} + +struct AppsWidgetsMainContentView: View { + + let date: Date + let header: String + let type: String + let content: [Content] + @Environment(\.widgetFamily) var family + + var body: some View { + VStack(spacing: 0) { + AppsWidgetHeader(date: date, header: header, type: type) + .padding(.leading) + .padding(.trailing) + .padding(.top, 10) + .padding(.bottom, 8) + .background(Color("BackgroundColorHeader")) + + Divider() + + if family == .systemSmall { + let slicedData = Array(content.prefix(3)) + VStack(spacing: 6) { + ForEach(0.. String { + switch index { + case 0: return "🥇" + case 1: return "🥈" + default: return "🥉" + } + } +} + +struct AppsWidgets: Widget { + + var body: some WidgetConfiguration { + IntentConfiguration(kind: "appdb-apps-widget", intent: ConfigurationIntent.self, provider: AppsWidgetsProvider()) { entry in + AppsWidgetsEntryView(entry: entry) + } + .configurationDisplayName("AppDB content") + .description("Show and configure appdb content") + .supportedFamilies([.systemSmall, .systemMedium, .systemLarge]) + } +} + +struct Apps_Previews: PreviewProvider { + + static var previews: some View { + let dummyData = [Content](repeating: Content.dummy, count: 25) + AppsWidgetsMainContentView(date: Date(), header: "", type: "cydia", content: dummyData) + .previewContext(WidgetPreviewContext(family: .systemSmall)) + } +} diff --git a/Widgets/Apps Widget/Components/AppGridView.swift b/Widgets/Apps Widget/Components/AppGridView.swift new file mode 100644 index 00000000..11fbbd35 --- /dev/null +++ b/Widgets/Apps Widget/Components/AppGridView.swift @@ -0,0 +1,29 @@ +// +// AppGridView.swift +// WidgetsExtension +// +// Created by ned on 09/03/21. +// Copyright © 2021 ned. All rights reserved. +// + +import SwiftUI + +struct AppGridView: View { + + let app: Content + let contentType: String + + var body: some View { + + let redirectUrl = "appdb-ios://?trackid=\(app.id)&type=\(contentType)" + + VStack { + Link(destination: URL(string: redirectUrl)!) { + RemoteImage(urlString: app.image) + .animation(.easeInOut(duration: 0.25)) + .scaledToFit() + .clipShape(AppIconShape(rounded: contentType != "books")) + } + } + } +} diff --git a/Widgets/Apps Widget/Components/AppListView.swift b/Widgets/Apps Widget/Components/AppListView.swift new file mode 100644 index 00000000..aee1c047 --- /dev/null +++ b/Widgets/Apps Widget/Components/AppListView.swift @@ -0,0 +1,30 @@ +// +// AppListView.swift +// WidgetsExtension +// +// Created by ned on 09/03/21. +// Copyright © 2021 ned. All rights reserved. +// + +import SwiftUI + +struct AppListView: View { + let app: Content + let contentType: String + + var body: some View { + HStack { + RemoteImage(urlString: app.image) + .animation(.easeInOut(duration: 0.25)) + .scaledToFit() + .clipShape(AppIconShape(rounded: contentType != "books")) + + Text(app.name) + .font(.system(size: 12)) + .lineLimit(2) + .padding(.trailing, 10) + .fixedSize(horizontal: false, vertical: true) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading) + } + } +} diff --git a/Widgets/Apps Widget/Components/AppsWidgetHeader.swift b/Widgets/Apps Widget/Components/AppsWidgetHeader.swift new file mode 100644 index 00000000..1e43fedc --- /dev/null +++ b/Widgets/Apps Widget/Components/AppsWidgetHeader.swift @@ -0,0 +1,57 @@ +// +// AppsWidgetHeader.swift +// WidgetsExtension +// +// Created by ned on 09/03/21. +// Copyright © 2021 ned. All rights reserved. +// + +import SwiftUI + +struct AppsWidgetHeader: View { + + let date: Date + let header: String + let type: String + @Environment(\.widgetFamily) var family + + let formatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateStyle = .none + formatter.timeStyle = .short + return formatter + }() + + var body: some View { + HStack { + HStack(spacing: 7) { + Image("appdb") + .resizable() + .frame(width: 20, height: 20, alignment: .center) + .scaledToFit() + .clipShape(RoundedRectangle(cornerRadius: 6, style: .continuous)) + .unredacted() + Text(family == .systemSmall ? "Top 3" : header) + .lineLimit(1) + .font(.system(size: 16, weight: .medium, design: .rounded)) + .fixedSize() + if !properUppercase(type).isEmpty { + RoundedBadge(text: properUppercase(type)) + .fixedSize() + .frame(maxWidth: .infinity, alignment: .leading) + } + } + Spacer() + if family != .systemSmall { + Text(formatter.string(from: date)) + .font(.system(size: 13)) + .foregroundColor(.secondary) + } + } + } + + func properUppercase(_ string: String) -> String { + if string == "iOS".localized() { return string } + return string.uppercased() + } +} diff --git a/Widgets/Apps Widget/Components/GridStack.swift b/Widgets/Apps Widget/Components/GridStack.swift new file mode 100644 index 00000000..2f0037c0 --- /dev/null +++ b/Widgets/Apps Widget/Components/GridStack.swift @@ -0,0 +1,34 @@ +// +// GridStack.swift +// WidgetsExtension +// +// Created by ned on 09/03/21. +// Copyright © 2021 ned. All rights reserved. +// + +import SwiftUI + +// https://www.hackingwithswift.com/quick-start/swiftui/how-to-position-views-in-a-grid-using-lazyvgrid-and-lazyhgrid +struct GridStack: View { + let rows: Int + let columns: Int + let content: (Int, Int) -> Content + + var body: some View { + VStack { + ForEach(0 ..< rows, id: \.self) { row in + HStack(spacing: 15) { + ForEach(0 ..< columns, id: \.self) { column in + content(row, column) + } + } + } + } + } + + init(rows: Int, columns: Int, @ViewBuilder content: @escaping (Int, Int) -> Content) { + self.rows = rows + self.columns = columns + self.content = content + } +} diff --git a/Widgets/Apps Widget/Components/RemoteImage.swift b/Widgets/Apps Widget/Components/RemoteImage.swift new file mode 100644 index 00000000..6c4393b2 --- /dev/null +++ b/Widgets/Apps Widget/Components/RemoteImage.swift @@ -0,0 +1,30 @@ +// +// RemoteImage.swift +// WidgetsExtension +// +// Created by ned on 09/03/21. +// Copyright © 2021 ned. All rights reserved. +// + +import SwiftUI + +// https://github.com/pawello2222/WidgetExamples/blob/main/WidgetExtension/URLImageWidget/URLImageView.swift +struct RemoteImage: View { + + let urlString: String + + @ViewBuilder + var body: some View { + if let url = URL(string: urlString), + let data = try? Data(contentsOf: url), + let uiImage = UIImage(data: data) { + Image(uiImage: uiImage) + .resizable() + } else { + // todo corner + Image("appdb") + .resizable() + .redacted(reason: .placeholder) + } + } +} diff --git a/Widgets/Apps Widget/Components/RoundedBadge.swift b/Widgets/Apps Widget/Components/RoundedBadge.swift new file mode 100644 index 00000000..ac66d210 --- /dev/null +++ b/Widgets/Apps Widget/Components/RoundedBadge.swift @@ -0,0 +1,30 @@ +// +// RoundedBadge.swift +// WidgetsExtension +// +// Created by ned on 09/03/21. +// Copyright © 2021 ned. All rights reserved. +// + +import SwiftUI + +struct RoundedBadge: View { + + let text: String + + var body: some View { + Text(text) + .font(.system(size: 10, weight: .bold, design: .rounded)) + .kerning(0.5) + .foregroundColor(.white) + .padding(.top, 2) + .padding(.bottom, 2) + .padding(.leading, 6) + .padding(.trailing, 6) + .lineLimit(1) + .background( + RoundedRectangle(cornerRadius: 8, style: .continuous) + .foregroundColor(.accentColor) + ) + } +} diff --git a/Widgets/Assets.xcassets/AccentColor.colorset/Contents.json b/Widgets/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..eb878970 --- /dev/null +++ b/Widgets/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Widgets/Assets.xcassets/AppIcon.appiconset/Contents.json b/Widgets/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..9221b9bb --- /dev/null +++ b/Widgets/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Widgets/Assets.xcassets/BackgroundColor.colorset/Contents.json b/Widgets/Assets.xcassets/BackgroundColor.colorset/Contents.json new file mode 100644 index 00000000..9f02a4e5 --- /dev/null +++ b/Widgets/Assets.xcassets/BackgroundColor.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0.957", + "green" : "0.937", + "red" : "0.937" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0.071", + "green" : "0.071", + "red" : "0.071" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Widgets/Assets.xcassets/BackgroundColorHeader.colorset/Contents.json b/Widgets/Assets.xcassets/BackgroundColorHeader.colorset/Contents.json new file mode 100644 index 00000000..854a01df --- /dev/null +++ b/Widgets/Assets.xcassets/BackgroundColorHeader.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.992", + "green" : "0.992", + "red" : "0.992" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.118", + "green" : "0.118", + "red" : "0.118" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Widgets/Assets.xcassets/Contents.json b/Widgets/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/Widgets/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Widgets/Assets.xcassets/WidgetBackground.colorset/Contents.json b/Widgets/Assets.xcassets/WidgetBackground.colorset/Contents.json new file mode 100644 index 00000000..274babba --- /dev/null +++ b/Widgets/Assets.xcassets/WidgetBackground.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Widgets/Assets.xcassets/appdb.imageset/Contents.json b/Widgets/Assets.xcassets/appdb.imageset/Contents.json new file mode 100644 index 00000000..ce8dad6f --- /dev/null +++ b/Widgets/Assets.xcassets/appdb.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "appdb.jpg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "appdb-1.jpg", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "appdb-2.jpg", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Widgets/Assets.xcassets/appdb.imageset/appdb-1.jpg b/Widgets/Assets.xcassets/appdb.imageset/appdb-1.jpg new file mode 100644 index 00000000..1e43d59c Binary files /dev/null and b/Widgets/Assets.xcassets/appdb.imageset/appdb-1.jpg differ diff --git a/Widgets/Assets.xcassets/appdb.imageset/appdb-2.jpg b/Widgets/Assets.xcassets/appdb.imageset/appdb-2.jpg new file mode 100644 index 00000000..1e43d59c Binary files /dev/null and b/Widgets/Assets.xcassets/appdb.imageset/appdb-2.jpg differ diff --git a/Widgets/Assets.xcassets/appdb.imageset/appdb.jpg b/Widgets/Assets.xcassets/appdb.imageset/appdb.jpg new file mode 100644 index 00000000..1e43d59c Binary files /dev/null and b/Widgets/Assets.xcassets/appdb.imageset/appdb.jpg differ diff --git a/Widgets/Base.lproj/Widgets.intentdefinition b/Widgets/Base.lproj/Widgets.intentdefinition new file mode 100644 index 00000000..c0ceb6b8 --- /dev/null +++ b/Widgets/Base.lproj/Widgets.intentdefinition @@ -0,0 +1,443 @@ + + + + + INEnums + + + INEnumDisplayName + Sort Order + INEnumDisplayNameID + O5uO3Q + INEnumGeneratesHeader + + INEnumName + SortOrder + INEnumType + Regular + INEnumValues + + + INEnumValueDisplayName + unknown + INEnumValueDisplayNameID + VQhz4p + INEnumValueName + unknown + + + INEnumValueDisplayName + Recently Uploaded + INEnumValueDisplayNameID + pvWCIL + INEnumValueIndex + 1 + INEnumValueName + recent + + + INEnumValueDisplayName + Popular Today + INEnumValueDisplayNameID + 1k5mNj + INEnumValueIndex + 2 + INEnumValueName + today + + + INEnumValueDisplayName + Popular This Week + INEnumValueDisplayNameID + BNtSaX + INEnumValueIndex + 3 + INEnumValueName + week + + + INEnumValueDisplayName + Popular This Month + INEnumValueDisplayNameID + Nzb0Ow + INEnumValueIndex + 4 + INEnumValueName + month + + + INEnumValueDisplayName + Popular This Year + INEnumValueDisplayNameID + 6ML9dG + INEnumValueIndex + 5 + INEnumValueName + year + + + INEnumValueDisplayName + Popular All Time + INEnumValueDisplayNameID + MobFEx + INEnumValueIndex + 6 + INEnumValueName + all_time + + + + + INEnumDisplayName + Type + INEnumDisplayNameID + RLtLxp + INEnumGeneratesHeader + + INEnumName + ContentType + INEnumType + Regular + INEnumValues + + + INEnumValueDisplayName + unknown + INEnumValueDisplayNameID + dzy0r3 + INEnumValueName + unknown + + + INEnumValueDisplayName + iOS + INEnumValueDisplayNameID + WeJIpH + INEnumValueIndex + 1 + INEnumValueName + ios + + + INEnumValueDisplayName + Cydia + INEnumValueDisplayNameID + f4LE5Z + INEnumValueIndex + 2 + INEnumValueName + cydia + + + INEnumValueDisplayName + Books + INEnumValueDisplayNameID + KJPQLK + INEnumValueIndex + 3 + INEnumValueName + books + + + + + INEnumDisplayName + Content Price + INEnumDisplayNameID + T06Tej + INEnumGeneratesHeader + + INEnumName + ContentPrice + INEnumType + Regular + INEnumValues + + + INEnumValueDisplayName + unknown + INEnumValueDisplayNameID + JrwSbi + INEnumValueName + unknown + + + INEnumValueDisplayName + Any Price + INEnumValueDisplayNameID + eal2Ol + INEnumValueIndex + 1 + INEnumValueName + any + + + INEnumValueDisplayName + Free + INEnumValueDisplayNameID + TT8u1d + INEnumValueIndex + 2 + INEnumValueName + free + + + INEnumValueDisplayName + Paid + INEnumValueDisplayNameID + lhivwH + INEnumValueIndex + 3 + INEnumValueName + paid + + + + + INIntentDefinitionModelVersion + 1.2 + INIntentDefinitionNamespace + 88xZPY + INIntentDefinitionSystemVersion + 20D80 + INIntentDefinitionToolsBuildVersion + 12D4e + INIntentDefinitionToolsVersion + 12.4 + INIntents + + + INIntentCategory + information + INIntentDescriptionID + tVvJ9c + INIntentEligibleForWidgets + + INIntentIneligibleForSuggestions + + INIntentLastParameterTag + 11 + INIntentName + Configuration + INIntentParameters + + + INIntentParameterConfigurable + + INIntentParameterDisplayName + Type + INIntentParameterDisplayNameID + jwhQHU + INIntentParameterDisplayPriority + 1 + INIntentParameterEnumType + ContentType + INIntentParameterEnumTypeNamespace + 88xZPY + INIntentParameterMetadata + + INIntentParameterMetadataDefaultValue + ios + + INIntentParameterName + type + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Configuration + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Primary + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + There are ${count} options matching ‘${type}’. + INIntentParameterPromptDialogFormatStringID + UGPBnM + INIntentParameterPromptDialogType + DisambiguationIntroduction + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + Just to confirm, you wanted ‘${type}’? + INIntentParameterPromptDialogFormatStringID + 9Wn4dF + INIntentParameterPromptDialogType + Confirmation + + + INIntentParameterTag + 4 + INIntentParameterType + Integer + + + INIntentParameterConfigurable + + INIntentParameterDisplayName + Price + INIntentParameterDisplayNameID + yZ26tx + INIntentParameterDisplayPriority + 2 + INIntentParameterEnumType + ContentPrice + INIntentParameterEnumTypeNamespace + 88xZPY + INIntentParameterMetadata + + INIntentParameterMetadataDefaultValue + any + + INIntentParameterName + price + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Configuration + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Primary + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + There are ${count} options matching ‘${price}’. + INIntentParameterPromptDialogFormatStringID + hJdiQz + INIntentParameterPromptDialogType + DisambiguationIntroduction + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + Just to confirm, you wanted ‘${price}’? + INIntentParameterPromptDialogFormatStringID + 8yUMXD + INIntentParameterPromptDialogType + Confirmation + + + INIntentParameterRelationship + + INIntentParameterRelationshipParentName + type + INIntentParameterRelationshipPredicateName + EnumHasExactValue + INIntentParameterRelationshipPredicateValue + ios + + INIntentParameterTag + 11 + INIntentParameterType + Integer + + + INIntentParameterConfigurable + + INIntentParameterDisplayName + Order + INIntentParameterDisplayNameID + J8A7mL + INIntentParameterDisplayPriority + 3 + INIntentParameterEnumType + SortOrder + INIntentParameterEnumTypeNamespace + 88xZPY + INIntentParameterMetadata + + INIntentParameterMetadataDefaultValue + week + + INIntentParameterName + order + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Configuration + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Primary + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + There are ${count} options matching ‘${order}’. + INIntentParameterPromptDialogFormatStringID + RP8IL1 + INIntentParameterPromptDialogType + DisambiguationIntroduction + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + Just to confirm, you wanted ‘${order}’? + INIntentParameterPromptDialogFormatStringID + Jtggq4 + INIntentParameterPromptDialogType + Confirmation + + + INIntentParameterTag + 6 + INIntentParameterType + Integer + + + INIntentResponse + + INIntentResponseCodes + + + INIntentResponseCodeName + success + INIntentResponseCodeSuccess + + + + INIntentResponseCodeName + failure + + + + INIntentTitle + Configuration + INIntentTitleID + gpCwrM + INIntentType + Custom + INIntentVerb + View + + + INTypes + + + diff --git a/Widgets/Extensions/Extensions.swift b/Widgets/Extensions/Extensions.swift new file mode 100644 index 00000000..344c8e62 --- /dev/null +++ b/Widgets/Extensions/Extensions.swift @@ -0,0 +1,41 @@ +// +// Extensions.swift +// WidgetsExtension +// +// Created by ned on 09/03/21. +// Copyright © 2021 ned. All rights reserved. +// + +import SwiftUI +import Localize_Swift + +extension String { + var rfc2822decoded: String { + let formatter = DateFormatter() + formatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss Z" // RFC 2822 + formatter.locale = Locale(identifier: "en_US") + if let date = formatter.date(from: self) { + formatter.locale = Locale(identifier: Localize.currentLanguage()) + formatter.dateStyle = .short + formatter.timeStyle = .none + return formatter.string(from: date) + } + return "" + } +} + +struct AppIconShape: Shape { + + var rounded: Bool + + init(rounded: Bool = true) { + self.rounded = rounded + } + + func path(in rect: CGRect) -> Path { + var path = Path() + let size: CGSize = rounded ? CGSize(width: rect.height * 0.225, height: rect.height * 0.225) : .zero + path.addRoundedRect(in: rect, cornerSize: size, style: .continuous) + return path + } +} diff --git a/Widgets/Info.plist b/Widgets/Info.plist new file mode 100644 index 00000000..88bd5116 --- /dev/null +++ b/Widgets/Info.plist @@ -0,0 +1,34 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Widgets + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + 1 + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + NSExtension + + NSExtensionPointIdentifier + com.apple.widgetkit-extension + + + diff --git a/Widgets/Models/Content.swift b/Widgets/Models/Content.swift new file mode 100644 index 00000000..01c3f96e --- /dev/null +++ b/Widgets/Models/Content.swift @@ -0,0 +1,20 @@ +// +// App.swift +// WidgetsExtension +// +// Created by ned on 08/03/21. +// Copyright © 2021 ned. All rights reserved. +// + +import Foundation + +struct Content: Identifiable, Decodable { + + let id: String + let name: String + let image: String + + static var dummy: Content { + Content(id: "", name: "Example Name", image: "") + } +} diff --git a/Widgets/Models/News.swift b/Widgets/Models/News.swift new file mode 100644 index 00000000..c0efd503 --- /dev/null +++ b/Widgets/Models/News.swift @@ -0,0 +1,20 @@ +// +// News.swift +// WidgetsExtension +// +// Created by ned on 09/03/21. +// Copyright © 2021 ned. All rights reserved. +// + +import Foundation + +struct News: Identifiable, Decodable { + + let id: String + let title: String + let added: String + + static var dummy: News { + News(id: "", title: "Example News Title Goes Here", added: "Tue, 16 Feb 2021 14:30:48 +0000") + } +} diff --git a/Widgets/Network/AppdbRepository.swift b/Widgets/Network/AppdbRepository.swift new file mode 100644 index 00000000..7e9938f5 --- /dev/null +++ b/Widgets/Network/AppdbRepository.swift @@ -0,0 +1,95 @@ +// +// AppdbRepository.swift +// WidgetsExtension +// +// Created by ned on 08/03/21. +// Copyright © 2021 ned. All rights reserved. +// + +import Foundation +import Combine + +class AppdbRepository: Repository { + let session: URLSession + + init(session: URLSession = .shared) { + self.session = session + } +} + +extension AppdbRepository { + struct ErrorResponse: Decodable { + let error: String + } +} + +extension AppdbRepository { + func fetchAPIResource(_ resource: Resource) -> AnyPublisher where Resource: APIResource { + guard let url = resource.url else { + let error = APIError.invalidRequest(description: "Invalid `resource.url`: \(String(describing: resource.url))") + return Fail(error: error).eraseToAnyPublisher() + } + return fetch(url: url) + .flatMap(decode) + .eraseToAnyPublisher() + } + + func decode(data: Data) -> AnyPublisher where Response: Decodable { + if let response = try? JSONDecoder().decode(Response.self, from: data) { + return Just(response).setFailureType(to: APIError.self).eraseToAnyPublisher() + } + do { + let errorResponse = try JSONDecoder().decode(ErrorResponse.self, from: data) + return Fail(error: .invalidRequest(description: errorResponse.error)).eraseToAnyPublisher() + } catch { + return Fail(error: .unknown).eraseToAnyPublisher() + } + } +} + +struct AppdbSearchResource: APIResource { + let serverPath = "api.dbservices.to" + let methodPath: String + var queryItems: [URLQueryItem]? + + init(_ contentType: ContentType, _ sortOrder: SortOrder, _ contentPrice: ContentPrice) { + methodPath = "/v1.3/" + queryItems = [ + URLQueryItem(name: "action", value: "search"), + URLQueryItem(name: "type", value: type(from: contentType)), + URLQueryItem(name: "price", value: price(from: contentPrice)), + URLQueryItem(name: "order", value: order(from: sortOrder)) + ] + } +} + +extension AppdbSearchResource { + struct Response: Decodable { + let success: Bool + let errors: [String] + let data: [Content] + } +} + +struct AppdbNewsResource: APIResource { + let serverPath = "api.dbservices.to" + let methodPath: String + var queryItems: [URLQueryItem]? + + init() { + methodPath = "/v1.3/" + queryItems = [ + URLQueryItem(name: "action", value: "get_pages"), + URLQueryItem(name: "category", value: "news"), + URLQueryItem(name: "length", value: "8") + ] + } +} + +extension AppdbNewsResource { + struct Response: Decodable { + let success: Bool + let errors: [String] + let data: [News] + } +} diff --git a/Widgets/Network/Repository.swift b/Widgets/Network/Repository.swift new file mode 100644 index 00000000..bead2434 --- /dev/null +++ b/Widgets/Network/Repository.swift @@ -0,0 +1,85 @@ +// +// Repository.swift +// WidgetsExtension +// +// Created by ned on 08/03/21. +// Copyright © 2021 ned. All rights reserved. +// + +import Combine +import Foundation + +// https://github.com/pawello2222/WidgetExamples/tree/main/WidgetExtension/NetworkWidget + +protocol Repository { + var session: URLSession { get } +} + +extension Repository { + func fetch(url: URL) -> AnyPublisher { + session.dataTaskPublisher(for: URLRequest(url: url)) + .mapError { error in + if error.code.rawValue == -1009 { + return .offline + } + return .network(code: error.code.rawValue, description: error.localizedDescription) + } + .map(\.data) + .eraseToAnyPublisher() + } +} + +protocol APIResource { + associatedtype Response: Decodable + var serverPath: String { get } + var methodPath: String { get } + var queryItems: [URLQueryItem]? { get } +} + +extension APIResource { + var url: URL? { + var components = URLComponents() + components.scheme = "https" + components.host = serverPath + components.path = methodPath + components.queryItems = queryItems + return components.url + } +} + +extension APIResource { + func type(from type: ContentType) -> String { + switch type { + case .ios: return "ios" + case .cydia: return "cydia" + case .books: return "books" + case .unknown: return "" + } + } + func order(from order: SortOrder) -> String { + switch order { + case .recent: return "added" + case .today: return "clicks_day" + case .week: return "clicks_week" + case .month: return "clicks_month" + case .year: return "clicks_year" + case .all_time: return "clicks_all" + case .unknown: return "" + } + } + func price(from type: ContentPrice) -> String { + switch type { + case .any: return "0" + case .paid: return "1" + case .free: return "2" + case .unknown: return "" + } + } +} + +enum APIError: Error { + case offline + case network(code: Int, description: String) + case invalidRequest(description: String) + case unknown +} diff --git a/Widgets/News Widget/Components/NewsWidgetHeader.swift b/Widgets/News Widget/Components/NewsWidgetHeader.swift new file mode 100644 index 00000000..8923b4b0 --- /dev/null +++ b/Widgets/News Widget/Components/NewsWidgetHeader.swift @@ -0,0 +1,46 @@ +// +// NewsWidgetHeader.swift +// WidgetsExtension +// +// Created by ned on 10/03/21. +// Copyright © 2021 ned. All rights reserved. +// + +import SwiftUI + +struct NewsWidgetHeader: View { + + let date: Date + let header: String + @Environment(\.widgetFamily) var family + + let formatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateStyle = .none + formatter.timeStyle = .short + return formatter + }() + + var body: some View { + HStack { + HStack(spacing: 7) { + Image("appdb") + .resizable() + .frame(width: 20, height: 20, alignment: .center) + .scaledToFit() + .clipShape(AppIconShape()) + .unredacted() + Text(header) + .lineLimit(1) + .font(.system(size: 16, weight: .medium, design: .rounded)) + .fixedSize() + } + Spacer() + if family != .systemSmall { + Text(formatter.string(from: date)) + .font(.system(size: 13)) + .foregroundColor(.secondary) + } + } + } +} diff --git a/Widgets/News Widget/NewsWidget.swift b/Widgets/News Widget/NewsWidget.swift new file mode 100644 index 00000000..2596c26f --- /dev/null +++ b/Widgets/News Widget/NewsWidget.swift @@ -0,0 +1,178 @@ +// +// NewsWidget.swift +// WidgetsExtension +// +// Created by ned on 09/03/21. +// Copyright © 2021 ned. All rights reserved. +// + +import Foundation +import WidgetKit +import SwiftUI +import Intents +import Combine +import Localize_Swift + +private var cancellables = Set() + +struct NewsWidgetsTimelineEntry: TimelineEntry { + let date: Date + let content: AppdbNewsResource.Response? +} + +struct NewsWidgetsProvider: TimelineProvider { + + typealias Entry = NewsWidgetsTimelineEntry + + let appdbRepository = AppdbRepository() + + func placeholder(in context: Context) -> NewsWidgetsTimelineEntry { + Entry(date: Date(), content: nil) + } + + func getSnapshot(in context: Context, completion: @escaping (NewsWidgetsTimelineEntry) -> Void) { + completion(Entry(date: Date(), content: nil)) + } + + func getTimeline(in context: Context, completion: @escaping (Timeline) -> Void) { + appdbRepository.fetchAPIResource(AppdbNewsResource()) + .receive(on: RunLoop.main) + .sink(receiveCompletion: { + switch $0 { + case .failure(let error): + print(error.localizedDescription) + completion(Timeline(entries: [], policy: .atEnd)) + default: break + } + }, receiveValue: { + let entries = [ + Entry(date: Date(), content: $0) + ] + let nextUpdate = Calendar.autoupdatingCurrent.date(byAdding: .hour, value: 6, to: Calendar.autoupdatingCurrent.startOfDay(for: Date()))! + let timeline = Timeline(entries: entries, policy: .after(nextUpdate)) + completion(timeline) + }) + .store(in: &cancellables) + } +} + +struct NewsWidgetsEntryView: View { + + var entry: NewsWidgetsProvider.Entry + + var body: some View { + if entry.content == nil || entry.content!.data.isEmpty { + let dummyData = [News](repeating: News.dummy, count: 10) + NewsWidgetsMainContentView(date: entry.date, content: dummyData) + .redacted(reason: .placeholder) + } else { + NewsWidgetsMainContentView(date: entry.date, content: entry.content!.data) + } + } +} + +struct NewsWidgetsMainContentView: View { + + let date: Date + let content: [News] + @Environment(\.widgetFamily) var family + + var newsCount: Int { + switch family { + case .systemSmall: return 1 + case .systemMedium: return 3 + case .systemLarge: return 8 + @unknown default: return 3 + } + } + + var body: some View { + VStack(spacing: 0) { + NewsWidgetHeader(date: date, header: "News".localized()) + .padding(.leading) + .padding(.trailing) + .padding(.top, 10) + .padding(.bottom, 8) + .background(Color("BackgroundColorHeader")) + + Divider() + + if family == .systemSmall { + if let latestNews = content.first { + let redirectUrl = "appdb-ios://?news_id=\(latestNews.id)" + + VStack(alignment: .leading, spacing: 2) { + Text(latestNews.added.rfc2822decoded) + .lineLimit(1) + .font(.system(size: 12)) + .foregroundColor(.secondary) + .frame(alignment: .leading) + Text(latestNews.title) + .font(.system(size: 17)) + .lineLimit(4) + .frame(alignment: .leading) + } + .padding(.leading) + .padding(.trailing) + .padding(.bottom, 5) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading) + .widgetURL(URL(string: redirectUrl)!) + } + } else { + VStack { + let data = Array(content.prefix(newsCount)) + ForEach(0.. + + + + INEnums + + + INEnumDisplayName + Sort Order + INEnumDisplayNameID + O5uO3Q + INEnumGeneratesHeader + + INEnumName + SortOrder + INEnumType + Regular + INEnumValues + + + INEnumValueDisplayName + unknown + INEnumValueDisplayNameID + VQhz4p + INEnumValueName + unknown + + + INEnumValueDisplayName + Recently Uploaded + INEnumValueDisplayNameID + pvWCIL + INEnumValueIndex + 1 + INEnumValueName + recent + + + INEnumValueDisplayName + Popular Today + INEnumValueDisplayNameID + 1k5mNj + INEnumValueIndex + 2 + INEnumValueName + today + + + INEnumValueDisplayName + Popular This Week + INEnumValueDisplayNameID + BNtSaX + INEnumValueIndex + 3 + INEnumValueName + week + + + INEnumValueDisplayName + Popular This Month + INEnumValueDisplayNameID + Nzb0Ow + INEnumValueIndex + 4 + INEnumValueName + month + + + INEnumValueDisplayName + Popular This Year + INEnumValueDisplayNameID + 6ML9dG + INEnumValueIndex + 5 + INEnumValueName + year + + + INEnumValueDisplayName + Popular All Time + INEnumValueDisplayNameID + MobFEx + INEnumValueIndex + 6 + INEnumValueName + all_time + + + + + INEnumDisplayName + Type + INEnumDisplayNameID + RLtLxp + INEnumGeneratesHeader + + INEnumName + ContentType + INEnumType + Regular + INEnumValues + + + INEnumValueDisplayName + unknown + INEnumValueDisplayNameID + dzy0r3 + INEnumValueName + unknown + + + INEnumValueDisplayName + iOS + INEnumValueDisplayNameID + WeJIpH + INEnumValueIndex + 1 + INEnumValueName + ios + + + INEnumValueDisplayName + Cydia + INEnumValueDisplayNameID + f4LE5Z + INEnumValueIndex + 2 + INEnumValueName + cydia + + + INEnumValueDisplayName + Books + INEnumValueDisplayNameID + KJPQLK + INEnumValueIndex + 3 + INEnumValueName + books + + + + + INEnumDisplayName + Content Price + INEnumDisplayNameID + T06Tej + INEnumGeneratesHeader + + INEnumName + ContentPrice + INEnumType + Regular + INEnumValues + + + INEnumValueDisplayName + unknown + INEnumValueDisplayNameID + JrwSbi + INEnumValueName + unknown + + + INEnumValueDisplayName + Any Price + INEnumValueDisplayNameID + eal2Ol + INEnumValueIndex + 1 + INEnumValueName + any + + + INEnumValueDisplayName + Free + INEnumValueDisplayNameID + TT8u1d + INEnumValueIndex + 2 + INEnumValueName + free + + + INEnumValueDisplayName + Paid + INEnumValueDisplayNameID + lhivwH + INEnumValueIndex + 3 + INEnumValueName + paid + + + + + INIntentDefinitionModelVersion + 1.2 + INIntentDefinitionNamespace + 88xZPY + INIntentDefinitionSystemVersion + 20D80 + INIntentDefinitionToolsBuildVersion + 12D4e + INIntentDefinitionToolsVersion + 12.4 + INIntents + + + INIntentCategory + information + INIntentDescriptionID + tVvJ9c + INIntentEligibleForWidgets + + INIntentIneligibleForSuggestions + + INIntentLastParameterTag + 11 + INIntentName + Configuration + INIntentParameters + + + INIntentParameterConfigurable + + INIntentParameterCustomDisambiguation + + INIntentParameterDisplayName + Type + INIntentParameterDisplayNameID + jwhQHU + INIntentParameterDisplayPriority + 1 + INIntentParameterEnumType + ContentType + INIntentParameterEnumTypeNamespace + 88xZPY + INIntentParameterMetadata + + INIntentParameterMetadataDefaultValue + ios + + INIntentParameterName + type + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Configuration + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Primary + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + There are ${count} options matching ‘${type}’. + INIntentParameterPromptDialogFormatStringID + UGPBnM + INIntentParameterPromptDialogType + DisambiguationIntroduction + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + Just to confirm, you wanted ‘${type}’? + INIntentParameterPromptDialogFormatStringID + 9Wn4dF + INIntentParameterPromptDialogType + Confirmation + + + INIntentParameterSupportsResolution + + INIntentParameterTag + 4 + INIntentParameterType + Integer + + + INIntentParameterConfigurable + + INIntentParameterCustomDisambiguation + + INIntentParameterDisplayName + Price + INIntentParameterDisplayNameID + yZ26tx + INIntentParameterDisplayPriority + 2 + INIntentParameterEnumType + ContentPrice + INIntentParameterEnumTypeNamespace + 88xZPY + INIntentParameterMetadata + + INIntentParameterMetadataDefaultValue + any + + INIntentParameterName + price + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Configuration + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Primary + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + There are ${count} options matching ‘${price}’. + INIntentParameterPromptDialogFormatStringID + hJdiQz + INIntentParameterPromptDialogType + DisambiguationIntroduction + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + Just to confirm, you wanted ‘${price}’? + INIntentParameterPromptDialogFormatStringID + 8yUMXD + INIntentParameterPromptDialogType + Confirmation + + + INIntentParameterRelationship + + INIntentParameterRelationshipParentName + type + INIntentParameterRelationshipPredicateName + EnumHasExactValue + INIntentParameterRelationshipPredicateValue + ios + + INIntentParameterSupportsResolution + + INIntentParameterTag + 11 + INIntentParameterType + Integer + + + INIntentParameterConfigurable + + INIntentParameterCustomDisambiguation + + INIntentParameterDisplayName + Order + INIntentParameterDisplayNameID + J8A7mL + INIntentParameterDisplayPriority + 3 + INIntentParameterEnumType + SortOrder + INIntentParameterEnumTypeNamespace + 88xZPY + INIntentParameterMetadata + + INIntentParameterMetadataDefaultValue + week + + INIntentParameterName + order + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Configuration + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Primary + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + There are ${count} options matching ‘${order}’. + INIntentParameterPromptDialogFormatStringID + RP8IL1 + INIntentParameterPromptDialogType + DisambiguationIntroduction + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + Just to confirm, you wanted ‘${order}’? + INIntentParameterPromptDialogFormatStringID + Jtggq4 + INIntentParameterPromptDialogType + Confirmation + + + INIntentParameterSupportsResolution + + INIntentParameterTag + 6 + INIntentParameterType + Integer + + + INIntentResponse + + INIntentResponseCodes + + + INIntentResponseCodeName + success + INIntentResponseCodeSuccess + + + + INIntentResponseCodeName + failure + + + + INIntentTitle + Configuration + INIntentTitleID + gpCwrM + INIntentType + Custom + INIntentVerb + View + + + INTypes + + + diff --git a/appdb.xcodeproj/project.pbxproj b/appdb.xcodeproj/project.pbxproj index cc3c3693..079d1b1a 100644 --- a/appdb.xcodeproj/project.pbxproj +++ b/appdb.xcodeproj/project.pbxproj @@ -46,6 +46,19 @@ 8126201C215444A400B06F88 /* Style.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81262016215444A400B06F88 /* Style.swift */; }; 8126201D215444A400B06F88 /* NSAttributedString+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81262017215444A400B06F88 /* NSAttributedString+Utils.swift */; }; 8126201E215444A400B06F88 /* String+Detection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81262018215444A400B06F88 /* String+Detection.swift */; }; + 812E362E25F812AD0097B6CC /* RemoteImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 812E362D25F812AD0097B6CC /* RemoteImage.swift */; }; + 812E363225F812D90097B6CC /* GridStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 812E363125F812D90097B6CC /* GridStack.swift */; }; + 812E363525F818A70097B6CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 819EC2D21E41013D00BA7A50 /* Localizable.strings */; }; + 812E366625F81D790097B6CC /* Widgets.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 812E366825F81D790097B6CC /* Widgets.intentdefinition */; }; + 812E368525F821530097B6CC /* Localize_Swift.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81CD0A9825F27FBF001C9EEA /* Localize_Swift.xcframework */; }; + 812E368625F821530097B6CC /* Localize_Swift.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 81CD0A9825F27FBF001C9EEA /* Localize_Swift.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 812E368825F821BF0097B6CC /* Widgets.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 812E366825F81D790097B6CC /* Widgets.intentdefinition */; }; + 812E368A25F82B4D0097B6CC /* AppsWidgetHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 812E368925F82B4D0097B6CC /* AppsWidgetHeader.swift */; }; + 812E368C25F82B780097B6CC /* AppGridView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 812E368B25F82B780097B6CC /* AppGridView.swift */; }; + 812E368E25F82BAB0097B6CC /* AppListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 812E368D25F82BAB0097B6CC /* AppListView.swift */; }; + 812E369025F82CB90097B6CC /* NewsWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 812E368F25F82CB90097B6CC /* NewsWidget.swift */; }; + 812E369425F830D20097B6CC /* News.swift in Sources */ = {isa = PBXBuildFile; fileRef = 812E369325F830D20097B6CC /* News.swift */; }; + 812F349025F68CEA00426AED /* Content.swift in Sources */ = {isa = PBXBuildFile; fileRef = 812F348F25F68CEA00426AED /* Content.swift */; }; 81324068205BD86000945CE9 /* Settings+StaticCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81324067205BD86000945CE9 /* Settings+StaticCells.swift */; }; 8132406B205BE50000945CE9 /* News+Detail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8132406A205BE50000945CE9 /* News+Detail.swift */; }; 8133C45C208B629600F2F9F5 /* SeeAll.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8133C45B208B629600F2F9F5 /* SeeAll.swift */; }; @@ -77,6 +90,11 @@ 815016A22312DAFE00E06E1E /* Wishes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8150169C2312DAFE00E06E1E /* Wishes.swift */; }; 815016A32312DAFE00E06E1E /* NewWishes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8150169E2312DAFE00E06E1E /* NewWishes.swift */; }; 815016A42312DAFE00E06E1E /* FulfilledWishes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 815016A02312DAFE00E06E1E /* FulfilledWishes.swift */; }; + 81521EAC25F672600064CA84 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81521EAB25F672600064CA84 /* WidgetKit.framework */; }; + 81521EAE25F672600064CA84 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81521EAD25F672600064CA84 /* SwiftUI.framework */; }; + 81521EB125F672600064CA84 /* Widgets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81521EB025F672600064CA84 /* Widgets.swift */; }; + 81521EB425F672620064CA84 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 81521EB325F672620064CA84 /* Assets.xcassets */; }; + 81521EBA25F672620064CA84 /* WidgetsExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 81521EAA25F672600064CA84 /* WidgetsExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 815234D22278A6BF00B1AE65 /* SHA1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 815234D12278A6BF00B1AE65 /* SHA1.swift */; }; 815234D52278C10A00B1AE65 /* BackgroundTaskUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 815234D42278C10A00B1AE65 /* BackgroundTaskUtil.swift */; }; 8155094A22748F0E00509B20 /* LibrarySectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8155094922748F0E00509B20 /* LibrarySectionHeaderView.swift */; }; @@ -143,12 +161,15 @@ 819EC27620A724F2007138CF /* Acknowledgements.swift in Sources */ = {isa = PBXBuildFile; fileRef = 819EC27520A724F2007138CF /* Acknowledgements.swift */; }; 819EC27A20A76EAD007138CF /* ThemeChooser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 819EC27920A76EAD007138CF /* ThemeChooser.swift */; }; 81A1038E1F7FCA030011F4B3 /* DynamicFontSizeHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8112C35B1F7FC6E30014EDB2 /* DynamicFontSizeHelper.swift */; }; + 81A474FF25F67F0C00DC4642 /* AppsWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81A474FE25F67F0C00DC4642 /* AppsWidget.swift */; }; 81A9DBB5214D36A400AB1A8C /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 819EC2D21E41013D00BA7A50 /* Localizable.strings */; }; 81A9DBB6214D36A800AB1A8C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8167A61D1DAC246000F96AC2 /* LaunchScreen.storyboard */; }; 81A9DBB7214D36AB00AB1A8C /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 818A55A42058439B00162E51 /* Images.xcassets */; }; 81A9DBB8214D36BD00AB1A8C /* Acknowledgements.plist in Resources */ = {isa = PBXBuildFile; fileRef = 819EC27220A72472007138CF /* Acknowledgements.plist */; }; 81AB37AE1DAC30BD003A586F /* TabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81AB37AD1DAC30BD003A586F /* TabBarController.swift */; }; 81AF3532215115B200A798D7 /* RoundedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81AF3531215115B200A798D7 /* RoundedButton.swift */; }; + 81B63EDD25F8013800108E71 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81B63EDC25F8013800108E71 /* Extensions.swift */; }; + 81B63EE125F802B600108E71 /* RoundedBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81B63EE025F802B600108E71 /* RoundedBadge.swift */; }; 81BE066C1E6C2EA500AD9827 /* Details+ExternalLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81BE066B1E6C2EA500AD9827 /* Details+ExternalLink.swift */; }; 81BE066F1E6C52FE00AD9827 /* Details+Publisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81BE066E1E6C52FE00AD9827 /* Details+Publisher.swift */; }; 81BE06721E6CB6DE00AD9827 /* Details+SegmentControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81BE06711E6CB6DE00AD9827 /* Details+SegmentControl.swift */; }; @@ -225,6 +246,9 @@ 81F271E221690B97005C7314 /* NoScreenshotsSearchCell~Book+Stars.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81F271E121690B97005C7314 /* NoScreenshotsSearchCell~Book+Stars.swift */; }; 81F3D6581E1310BA003CD808 /* ILTranslucentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81F3D6571E1310BA003CD808 /* ILTranslucentView.swift */; }; 81F504E61E5CE4F70000FACC /* DimmableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81F504E51E5CE4F70000FACC /* DimmableView.swift */; }; + 81F872DA25F6C22D00ED94B9 /* Repository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81F872D925F6C22D00ED94B9 /* Repository.swift */; }; + 81F872DE25F6C24400ED94B9 /* AppdbRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81F872DD25F6C24400ED94B9 /* AppdbRepository.swift */; }; + 81FB0AC625F837D700738E0A /* NewsWidgetHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81FB0AC525F837D700738E0A /* NewsWidgetHeader.swift */; }; 81FC8B4122735E5E003788F3 /* API+MyAppstore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81FC8B4022735E5E003788F3 /* API+MyAppstore.swift */; }; 81FC8B4322735EA8003788F3 /* MyAppstoreApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81FC8B4222735EA8003788F3 /* MyAppstoreApp.swift */; }; 81FC8B4522736CC1003788F3 /* MyAppstoreCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81FC8B4422736CC1003788F3 /* MyAppstoreCell.swift */; }; @@ -232,6 +256,16 @@ 81FCAFD0226E1D0C004FAFD6 /* RequestedApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81FCAFCF226E1D0C004FAFD6 /* RequestedApp.swift */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 81521EB825F672620064CA84 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8167A6071DAC246000F96AC2 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 81521EA925F672600064CA84; + remoteInfo = WidgetsExtension; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 811BEE35215154D700F1F56A /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; @@ -259,6 +293,28 @@ name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; + 812E368725F821530097B6CC /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 812E368625F821530097B6CC /* Localize_Swift.xcframework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + 81521EBB25F672620064CA84 /* Embed App Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 81521EBA25F672620064CA84 /* WidgetsExtension.appex in Embed App Extensions */, + ); + name = "Embed App Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ @@ -302,6 +358,15 @@ 81262016215444A400B06F88 /* Style.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Style.swift; sourceTree = ""; }; 81262017215444A400B06F88 /* NSAttributedString+Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+Utils.swift"; sourceTree = ""; }; 81262018215444A400B06F88 /* String+Detection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Detection.swift"; sourceTree = ""; }; + 812E362D25F812AD0097B6CC /* RemoteImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteImage.swift; sourceTree = ""; }; + 812E363125F812D90097B6CC /* GridStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GridStack.swift; sourceTree = ""; }; + 812E366725F81D790097B6CC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; name = Base; path = Base.lproj/Widgets.intentdefinition; sourceTree = ""; }; + 812E368925F82B4D0097B6CC /* AppsWidgetHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppsWidgetHeader.swift; sourceTree = ""; }; + 812E368B25F82B780097B6CC /* AppGridView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppGridView.swift; sourceTree = ""; }; + 812E368D25F82BAB0097B6CC /* AppListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppListView.swift; sourceTree = ""; }; + 812E368F25F82CB90097B6CC /* NewsWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsWidget.swift; sourceTree = ""; }; + 812E369325F830D20097B6CC /* News.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = News.swift; sourceTree = ""; }; + 812F348F25F68CEA00426AED /* Content.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Content.swift; sourceTree = ""; }; 81324067205BD86000945CE9 /* Settings+StaticCells.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Settings+StaticCells.swift"; sourceTree = ""; }; 8132406A205BE50000945CE9 /* News+Detail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "News+Detail.swift"; sourceTree = ""; }; 8133C45B208B629600F2F9F5 /* SeeAll.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeeAll.swift; sourceTree = ""; }; @@ -333,6 +398,12 @@ 8150169C2312DAFE00E06E1E /* Wishes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Wishes.swift; sourceTree = ""; }; 8150169E2312DAFE00E06E1E /* NewWishes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewWishes.swift; sourceTree = ""; }; 815016A02312DAFE00E06E1E /* FulfilledWishes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FulfilledWishes.swift; sourceTree = ""; }; + 81521EAA25F672600064CA84 /* WidgetsExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetsExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 81521EAB25F672600064CA84 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; + 81521EAD25F672600064CA84 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; + 81521EB025F672600064CA84 /* Widgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Widgets.swift; sourceTree = ""; }; + 81521EB325F672620064CA84 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 81521EB525F672620064CA84 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 815234D12278A6BF00B1AE65 /* SHA1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SHA1.swift; sourceTree = ""; }; 815234D42278C10A00B1AE65 /* BackgroundTaskUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundTaskUtil.swift; sourceTree = ""; }; 8155094922748F0E00509B20 /* LibrarySectionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibrarySectionHeaderView.swift; sourceTree = ""; }; @@ -408,9 +479,12 @@ 819EC27920A76EAD007138CF /* ThemeChooser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeChooser.swift; sourceTree = ""; }; 819EC2D11E41013D00BA7A50 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; usesTabs = 1; }; 819EC2D31E41016D00BA7A50 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; usesTabs = 1; }; + 81A474FE25F67F0C00DC4642 /* AppsWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppsWidget.swift; sourceTree = ""; }; 81AB37AD1DAC30BD003A586F /* TabBarController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = TabBarController.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 81AF3531215115B200A798D7 /* RoundedButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedButton.swift; sourceTree = ""; }; 81B601AD23675C950070555D /* jv-ID */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "jv-ID"; path = "jv-ID.lproj/Localizable.strings"; sourceTree = ""; }; + 81B63EDC25F8013800108E71 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; + 81B63EE025F802B600108E71 /* RoundedBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedBadge.swift; sourceTree = ""; }; 81BE066B1E6C2EA500AD9827 /* Details+ExternalLink.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = "Details+ExternalLink.swift"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 81BE066E1E6C52FE00AD9827 /* Details+Publisher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Details+Publisher.swift"; sourceTree = ""; }; 81BE06711E6CB6DE00AD9827 /* Details+SegmentControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Details+SegmentControl.swift"; sourceTree = ""; }; @@ -473,6 +547,9 @@ 81F271E121690B97005C7314 /* NoScreenshotsSearchCell~Book+Stars.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NoScreenshotsSearchCell~Book+Stars.swift"; sourceTree = ""; }; 81F3D6571E1310BA003CD808 /* ILTranslucentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ILTranslucentView.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 81F504E51E5CE4F70000FACC /* DimmableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DimmableView.swift; sourceTree = ""; }; + 81F872D925F6C22D00ED94B9 /* Repository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Repository.swift; sourceTree = ""; }; + 81F872DD25F6C24400ED94B9 /* AppdbRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppdbRepository.swift; sourceTree = ""; }; + 81FB0AC525F837D700738E0A /* NewsWidgetHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsWidgetHeader.swift; sourceTree = ""; }; 81FC8B4022735E5E003788F3 /* API+MyAppstore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "API+MyAppstore.swift"; sourceTree = ""; }; 81FC8B4222735EA8003788F3 /* MyAppstoreApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyAppstoreApp.swift; sourceTree = ""; }; 81FC8B4422736CC1003788F3 /* MyAppstoreCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyAppstoreCell.swift; sourceTree = ""; }; @@ -483,6 +560,16 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 81521EA725F672600064CA84 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 812E368525F821530097B6CC /* Localize_Swift.xcframework in Frameworks */, + 81521EAE25F672600064CA84 /* SwiftUI.framework in Frameworks */, + 81521EAC25F672600064CA84 /* WidgetKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 81CD0A9325F27DF0001C9EEA /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -591,6 +678,24 @@ path = Atributika; sourceTree = ""; }; + 812E369125F82CD10097B6CC /* Apps Widget */ = { + isa = PBXGroup; + children = ( + 81A474FE25F67F0C00DC4642 /* AppsWidget.swift */, + 81B63EDA25F800E200108E71 /* Components */, + ); + path = "Apps Widget"; + sourceTree = ""; + }; + 812E369225F82CDC0097B6CC /* News Widget */ = { + isa = PBXGroup; + children = ( + 812E368F25F82CB90097B6CC /* NewsWidget.swift */, + 81FB0AC425F837CA00738E0A /* Components */, + ); + path = "News Widget"; + sourceTree = ""; + }; 812E97861E5772BD00C1379F /* Search */ = { isa = PBXGroup; children = ( @@ -603,6 +708,15 @@ path = Search; sourceTree = ""; }; + 812F349225F68CEF00426AED /* Models */ = { + isa = PBXGroup; + children = ( + 812F348F25F68CEA00426AED /* Content.swift */, + 812E369325F830D20097B6CC /* News.swift */, + ); + path = Models; + sourceTree = ""; + }; 81324069205BE4C700945CE9 /* News */ = { isa = PBXGroup; children = ( @@ -712,6 +826,22 @@ path = Fulfilled; sourceTree = ""; }; + 81521EAF25F672600064CA84 /* Widgets */ = { + isa = PBXGroup; + children = ( + 812E369225F82CDC0097B6CC /* News Widget */, + 812E369125F82CD10097B6CC /* Apps Widget */, + 81B63EDB25F8012800108E71 /* Extensions */, + 81F872D825F6C21800ED94B9 /* Network */, + 812F349225F68CEF00426AED /* Models */, + 812E366825F81D790097B6CC /* Widgets.intentdefinition */, + 81521EB025F672600064CA84 /* Widgets.swift */, + 81521EB325F672620064CA84 /* Assets.xcassets */, + 81521EB525F672620064CA84 /* Info.plist */, + ); + path = Widgets; + sourceTree = ""; + }; 815234D02278A6AD00B1AE65 /* SHA1 */ = { isa = PBXGroup; children = ( @@ -768,6 +898,7 @@ isa = PBXGroup; children = ( 8167A6111DAC246000F96AC2 /* appdb */, + 81521EAF25F672600064CA84 /* Widgets */, 81AB37A31DAC2ED7003A586F /* Frameworks */, 8167A6101DAC246000F96AC2 /* Products */, ); @@ -777,6 +908,7 @@ isa = PBXGroup; children = ( 8167A60F1DAC246000F96AC2 /* appdb.app */, + 81521EAA25F672600064CA84 /* WidgetsExtension.appex */, ); name = Products; sourceTree = ""; @@ -1128,10 +1260,33 @@ 81CD0A9D25F27FBF001C9EEA /* SwiftTheme.xcframework */, 81CD0A9525F27FBF001C9EEA /* SwiftyJSON.xcframework */, 81CD0AA225F27FBF001C9EEA /* ZIPFoundation.xcframework */, + 81521EAB25F672600064CA84 /* WidgetKit.framework */, + 81521EAD25F672600064CA84 /* SwiftUI.framework */, ); name = Frameworks; sourceTree = ""; }; + 81B63EDA25F800E200108E71 /* Components */ = { + isa = PBXGroup; + children = ( + 81B63EE025F802B600108E71 /* RoundedBadge.swift */, + 812E362D25F812AD0097B6CC /* RemoteImage.swift */, + 812E363125F812D90097B6CC /* GridStack.swift */, + 812E368925F82B4D0097B6CC /* AppsWidgetHeader.swift */, + 812E368B25F82B780097B6CC /* AppGridView.swift */, + 812E368D25F82BAB0097B6CC /* AppListView.swift */, + ); + path = Components; + sourceTree = ""; + }; + 81B63EDB25F8012800108E71 /* Extensions */ = { + isa = PBXGroup; + children = ( + 81B63EDC25F8013800108E71 /* Extensions.swift */, + ); + path = Extensions; + sourceTree = ""; + }; 81BE066A1E6C2E8600AD9827 /* ExternalLink */ = { isa = PBXGroup; children = ( @@ -1412,6 +1567,23 @@ path = "• DimmableView"; sourceTree = ""; }; + 81F872D825F6C21800ED94B9 /* Network */ = { + isa = PBXGroup; + children = ( + 81F872D925F6C22D00ED94B9 /* Repository.swift */, + 81F872DD25F6C24400ED94B9 /* AppdbRepository.swift */, + ); + path = Network; + sourceTree = ""; + }; + 81FB0AC425F837CA00738E0A /* Components */ = { + isa = PBXGroup; + children = ( + 81FB0AC525F837D700738E0A /* NewsWidgetHeader.swift */, + ); + path = Components; + sourceTree = ""; + }; 81FCAFCA226E0FF9004FAFD6 /* Queued Apps */ = { isa = PBXGroup; children = ( @@ -1448,6 +1620,26 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 81521EA925F672600064CA84 /* WidgetsExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = 81521EBE25F672620064CA84 /* Build configuration list for PBXNativeTarget "WidgetsExtension" */; + buildPhases = ( + 81521EA625F672600064CA84 /* Sources */, + 81521EA725F672600064CA84 /* Frameworks */, + 81521EA825F672600064CA84 /* Resources */, + 812E368725F821530097B6CC /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = WidgetsExtension; + packageProductDependencies = ( + ); + productName = WidgetsExtension; + productReference = 81521EAA25F672600064CA84 /* WidgetsExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; 8167A60E1DAC246000F96AC2 /* appdb */ = { isa = PBXNativeTarget; buildConfigurationList = 8167A6231DAC246000F96AC2 /* Build configuration list for PBXNativeTarget "appdb" */; @@ -1457,10 +1649,12 @@ 81A9DBB4214D36A000AB1A8C /* Resources */, 811BEE35215154D700F1F56A /* Embed Frameworks */, 81CD0A9325F27DF0001C9EEA /* Frameworks */, + 81521EBB25F672620064CA84 /* Embed App Extensions */, ); buildRules = ( ); dependencies = ( + 81521EB925F672620064CA84 /* PBXTargetDependency */, ); name = appdb; productName = appdb; @@ -1473,10 +1667,13 @@ 8167A6071DAC246000F96AC2 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 0800; + LastSwiftUpdateCheck = 1240; LastUpgradeCheck = 1220; ORGANIZATIONNAME = ned; TargetAttributes = { + 81521EA925F672600064CA84 = { + CreatedOnToolsVersion = 12.4; + }; 8167A60E1DAC246000F96AC2 = { CreatedOnToolsVersion = 8.0; DevelopmentTeam = 9Y4562FZWB; @@ -1507,16 +1704,28 @@ ar, ); mainGroup = 8167A6061DAC246000F96AC2; + packageReferences = ( + ); productRefGroup = 8167A6101DAC246000F96AC2 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 8167A60E1DAC246000F96AC2 /* appdb */, + 81521EA925F672600064CA84 /* WidgetsExtension */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 81521EA825F672600064CA84 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 81521EB425F672620064CA84 /* Assets.xcassets in Resources */, + 812E363525F818A70097B6CC /* Localizable.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 81A9DBB4214D36A000AB1A8C /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -1552,6 +1761,29 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 81521EA625F672600064CA84 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 812E369025F82CB90097B6CC /* NewsWidget.swift in Sources */, + 812E366625F81D790097B6CC /* Widgets.intentdefinition in Sources */, + 812E363225F812D90097B6CC /* GridStack.swift in Sources */, + 812E369425F830D20097B6CC /* News.swift in Sources */, + 812E362E25F812AD0097B6CC /* RemoteImage.swift in Sources */, + 81B63EE125F802B600108E71 /* RoundedBadge.swift in Sources */, + 81A474FF25F67F0C00DC4642 /* AppsWidget.swift in Sources */, + 812F349025F68CEA00426AED /* Content.swift in Sources */, + 812E368C25F82B780097B6CC /* AppGridView.swift in Sources */, + 81F872DE25F6C24400ED94B9 /* AppdbRepository.swift in Sources */, + 81521EB125F672600064CA84 /* Widgets.swift in Sources */, + 81FB0AC625F837D700738E0A /* NewsWidgetHeader.swift in Sources */, + 81F872DA25F6C22D00ED94B9 /* Repository.swift in Sources */, + 81B63EDD25F8013800108E71 /* Extensions.swift in Sources */, + 812E368E25F82BAB0097B6CC /* AppListView.swift in Sources */, + 812E368A25F82B4D0097B6CC /* AppsWidgetHeader.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 8167A60B1DAC246000F96AC2 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1720,6 +1952,7 @@ 81236ACC1E3B8A56009748E0 /* PaddingLabel.swift in Sources */, 8182756420791DE9000E9F87 /* News+Detail+TitleDateCell.swift in Sources */, 81C274D8229FFA57000D8BEB /* Credits+Views.swift in Sources */, + 812E368825F821BF0097B6CC /* Widgets.intentdefinition in Sources */, 817A40E41E5E2FB2003FD6F1 /* SnappableFlowLayout.swift in Sources */, 811B570B227DDBED000B2866 /* LocalIPAUploadUtil.swift in Sources */, 8189255A227CC9690096F661 /* AdaptiveUIAlertController.swift in Sources */, @@ -1748,7 +1981,23 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 81521EB925F672620064CA84 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 81521EA925F672600064CA84 /* WidgetsExtension */; + targetProxy = 81521EB825F672620064CA84 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ + 812E366825F81D790097B6CC /* Widgets.intentdefinition */ = { + isa = PBXVariantGroup; + children = ( + 812E366725F81D790097B6CC /* Base */, + ); + name = Widgets.intentdefinition; + sourceTree = ""; + }; 8167A61D1DAC246000F96AC2 /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( @@ -1778,6 +2027,69 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 81521EBC25F672620064CA84 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 9Y4562FZWB; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = Widgets/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0.5; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "it.ned.appdb-ios.Widgets"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 81521EBD25F672620064CA84 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 9Y4562FZWB; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = Widgets/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0.5; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "it.ned.appdb-ios.Widgets"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; 8167A6211DAC246000F96AC2 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1923,6 +2235,7 @@ 8167A6241DAC246000F96AC2 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = ""; @@ -1960,6 +2273,7 @@ 8167A6251DAC246000F96AC2 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = ""; @@ -1996,6 +2310,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 81521EBE25F672620064CA84 /* Build configuration list for PBXNativeTarget "WidgetsExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 81521EBC25F672620064CA84 /* Debug */, + 81521EBD25F672620064CA84 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 8167A60A1DAC246000F96AC2 /* Build configuration list for PBXProject "appdb" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/appdb.xcodeproj/xcuserdata/ned.xcuserdatad/xcschemes/xcschememanagement.plist b/appdb.xcodeproj/xcuserdata/ned.xcuserdatad/xcschemes/xcschememanagement.plist index 12eb5662..c5000357 100644 --- a/appdb.xcodeproj/xcuserdata/ned.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/appdb.xcodeproj/xcuserdata/ned.xcuserdatad/xcschemes/xcschememanagement.plist @@ -4,6 +4,11 @@ SchemeUserState + WidgetsExtension.xcscheme_^#shared#^_ + + orderHint + 1 + appdb.xcscheme orderHint @@ -12,7 +17,7 @@ appdb.xcscheme_^#shared#^_ orderHint - 1 + 0 SuppressBuildableAutocreation @@ -20,7 +25,7 @@ 8167A60E1DAC246000F96AC2 primary - + diff --git a/appdb/Other/Info.plist b/appdb/Other/Info.plist index 960a3cef..34ebf148 100644 --- a/appdb/Other/Info.plist +++ b/appdb/Other/Info.plist @@ -67,6 +67,10 @@ NSAllowsArbitraryLoads + NSUserActivityTypes + + ConfigurationIntent + UIFileSharingEnabled UILaunchStoryboardName diff --git a/appdb/Startup/AppDelegate.swift b/appdb/Startup/AppDelegate.swift index f3be1928..bc57890e 100644 --- a/appdb/Startup/AppDelegate.swift +++ b/appdb/Startup/AppDelegate.swift @@ -9,6 +9,7 @@ import UIKit import SwiftTheme import AlamofireNetworkActivityIndicator +import WidgetKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate, UITabBarControllerDelegate { @@ -71,6 +72,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UITabBarControllerDelegat application.shortcutItems = Global.ShortcutItem.createItems(for: [.search, .wishes, .updates, .news]) + if #available(iOS 14.0, *) { + WidgetCenter.shared.reloadAllTimelines() + } + return true }