From 15511a3a4978b8f75a7c6fa3a6465294b1fb6813 Mon Sep 17 00:00:00 2001 From: Lava <34743145+CanglongCl@users.noreply.github.com> Date: Mon, 18 Sep 2023 22:25:20 -0700 Subject: [PATCH] feat: widget refreshable in iOS 17+ (#142) --- .../View Modifier/ContainerBackground.swift | 40 +++++++++++++++++++ .../GeneralWidgetRefreshIntent.swift | 21 ++++++++++ .../Common View/AsWidgetRefreshButton.swift | 30 ++++++++++++++ .../GIStyleRectangularWidgetView.swift | 16 ++++++-- .../GIStyleSquareWidgetView.swift | 16 ++++++-- .../RectangularDailyNoteWidgetView.swift | 4 +- .../LargeSquareDailyNoteWidgetView.swift | 4 +- .../SmallSquareDailyNoteWidgetView.swift | 4 +- HSRPizzaHelper.xcodeproj/project.pbxproj | 28 +++++++++++++ .../xcschemes/Widget Debug.xcscheme | 18 ++++++++- .../xcschemes/Widget Release.xcscheme | 26 +++++++++--- 11 files changed, 185 insertions(+), 22 deletions(-) create mode 100644 Common/View Modifier/ContainerBackground.swift create mode 100644 Features/Daily Note Widget/App Intent/GeneralWidgetRefreshIntent.swift create mode 100644 Features/Daily Note Widget/Common View/AsWidgetRefreshButton.swift diff --git a/Common/View Modifier/ContainerBackground.swift b/Common/View Modifier/ContainerBackground.swift new file mode 100644 index 00000000..3d658d29 --- /dev/null +++ b/Common/View Modifier/ContainerBackground.swift @@ -0,0 +1,40 @@ +// +// ContainerBackground.swift +// HSRPizzaHelper +// +// Created by 戴藏龙 on 2023/9/18. +// + +import Foundation +import SwiftUI + +extension View { + func myWidgetContainerBackground( + withPadding padding: CGFloat, + @ViewBuilder _ content: @escaping () -> V + ) + -> some View { + modifier(ContainerBackgroundModifier(padding: padding, background: content)) + } +} + +// MARK: - ContainerBackgroundModifier + +private struct ContainerBackgroundModifier: ViewModifier { + let padding: CGFloat + let background: () -> V + + func body(content: Content) -> some View { + if #available(iOS 17, *) { + content.containerBackground(for: .widget) { + background() + } + } else { + content + .padding(padding) + .background { + background() + } + } + } +} diff --git a/Features/Daily Note Widget/App Intent/GeneralWidgetRefreshIntent.swift b/Features/Daily Note Widget/App Intent/GeneralWidgetRefreshIntent.swift new file mode 100644 index 00000000..5631248e --- /dev/null +++ b/Features/Daily Note Widget/App Intent/GeneralWidgetRefreshIntent.swift @@ -0,0 +1,21 @@ +// +// GeneralWidgetRefreshIntent.swift +// HSRPizzaHelper +// +// Created by 戴藏龙 on 2023/9/18. +// + +import AppIntents +import Foundation + +/// General intent for refresh widget timeline. +/// +/// System will automatically update widget timeline after act an app intent. So this intent need do nothing. +@available(iOSApplicationExtension 16, iOS 16, *) +struct GeneralWidgetRefreshIntent: AppIntent { + static var title: LocalizedStringResource = "Refresh" + + func perform() async throws -> some IntentResult { + .result() + } +} diff --git a/Features/Daily Note Widget/Common View/AsWidgetRefreshButton.swift b/Features/Daily Note Widget/Common View/AsWidgetRefreshButton.swift new file mode 100644 index 00000000..28329696 --- /dev/null +++ b/Features/Daily Note Widget/Common View/AsWidgetRefreshButton.swift @@ -0,0 +1,30 @@ +// +// AsWidgetRefreshButton.swift +// HSRPizzaHelper +// +// Created by 戴藏龙 on 2023/9/18. +// + +import Foundation +import SwiftUI + +extension View { + func asWidgetRefreshButton() -> some View { + modifier(AsWidgetRefreshButton()) + } +} + +// MARK: - AsWidgetRefreshButton + +private struct AsWidgetRefreshButton: ViewModifier { + func body(content: Content) -> some View { + if #available(iOS 17.0, iOSApplicationExtension 17.0, *) { + Button(intent: GeneralWidgetRefreshIntent()) { + content + } + .buttonStyle(.plain) + } else { + content + } + } +} diff --git a/Features/Daily Note Widget/GI Style Rectangular Widget/GIStyleRectangularWidgetView.swift b/Features/Daily Note Widget/GI Style Rectangular Widget/GIStyleRectangularWidgetView.swift index c1cc36dc..bb596f2a 100644 --- a/Features/Daily Note Widget/GI Style Rectangular Widget/GIStyleRectangularWidgetView.swift +++ b/Features/Daily Note Widget/GI Style Rectangular Widget/GIStyleRectangularWidgetView.swift @@ -29,8 +29,7 @@ struct GIStyleRectangularWidgetView: View { } } .foregroundColor(entry.configuration.textColor) - .padding(20) - .background { + .myWidgetContainerBackground(withPadding: 20) { Group { if let image = entry.configuration.backgroundImage() { image.resizable().scaledToFill() @@ -77,10 +76,19 @@ private struct WidgetGIStyleSuccessView: View { } Spacer() HStack { - if dailyNote.staminaInformation.currentStamina != dailyNote.staminaInformation - .maxStamina { + if #available(iOSApplicationExtension 17.0, *) { + Button(intent: GeneralWidgetRefreshIntent()) { + Image(systemSymbol: .arrowClockwiseCircle) + .font(.title3) + .clipShape(.circle) + } + .buttonStyle(.plain) + } else { Image(systemSymbol: .hourglassCircle) .font(.title3) + } + if dailyNote.staminaInformation.currentStamina != dailyNote.staminaInformation + .maxStamina { ( Text(dateFormatter.string(from: dailyNote.staminaInformation.fullTime)) + Text("\n") diff --git a/Features/Daily Note Widget/GI Style Square Widget/GIStyleSquareWidgetView.swift b/Features/Daily Note Widget/GI Style Square Widget/GIStyleSquareWidgetView.swift index f94044c7..de7d30a5 100644 --- a/Features/Daily Note Widget/GI Style Square Widget/GIStyleSquareWidgetView.swift +++ b/Features/Daily Note Widget/GI Style Square Widget/GIStyleSquareWidgetView.swift @@ -30,8 +30,7 @@ struct GIStyleSquareWidgetView: View { .textColor == .white ? .light : .dark ) .foregroundColor(entry.configuration.textColor) - .padding(20) - .background { + .myWidgetContainerBackground(withPadding: 20) { Group { if let image = entry.configuration.backgroundImage() { image.resizable().scaledToFill() @@ -79,8 +78,17 @@ private struct WidgetGIStyleSuccessView: View { .shadow(color: .black.opacity(0.2), radius: 0.5, x: 2, y: 2) } HStack { - Image(systemSymbol: .hourglassCircle) - .font(.title3) + if #available(iOSApplicationExtension 17.0, *) { + Button(intent: GeneralWidgetRefreshIntent()) { + Image(systemSymbol: .arrowClockwiseCircle) + .font(.title3) + .clipShape(.circle) + } + .buttonStyle(.plain) + } else { + Image(systemSymbol: .hourglassCircle) + .font(.title3) + } if dailyNote.staminaInformation.remainingTime >= 0 { Group { if dailyNote.staminaInformation.currentStamina != dailyNote.staminaInformation diff --git a/Features/Daily Note Widget/Rectangular Widget/RectangularDailyNoteWidgetView.swift b/Features/Daily Note Widget/Rectangular Widget/RectangularDailyNoteWidgetView.swift index 6b3ad0bd..58769983 100644 --- a/Features/Daily Note Widget/Rectangular Widget/RectangularDailyNoteWidgetView.swift +++ b/Features/Daily Note Widget/Rectangular Widget/RectangularDailyNoteWidgetView.swift @@ -40,9 +40,9 @@ struct RectangularDailyNoteWidgetView: View { WidgetErrorView(error: error) } } + .asWidgetRefreshButton() } - .padding(mainViewPadding) - .background { + .myWidgetContainerBackground(withPadding: mainViewPadding) { Group { if let image = entry.configuration.backgroundImage() { image.resizable().scaledToFill() diff --git a/Features/Daily Note Widget/Square Widget/LargeSquareDailyNoteWidgetView.swift b/Features/Daily Note Widget/Square Widget/LargeSquareDailyNoteWidgetView.swift index 63bde4a8..88782fde 100644 --- a/Features/Daily Note Widget/Square Widget/LargeSquareDailyNoteWidgetView.swift +++ b/Features/Daily Note Widget/Square Widget/LargeSquareDailyNoteWidgetView.swift @@ -40,9 +40,9 @@ struct LargeSquareDailyNoteWidgetView: View { WidgetErrorView(error: error) } } + .asWidgetRefreshButton() } - .padding(mainViewPadding) - .background { + .myWidgetContainerBackground(withPadding: mainViewPadding) { Group { if let image = entry.configuration.backgroundImage() { image.resizable().scaledToFill() diff --git a/Features/Daily Note Widget/Square Widget/SmallSquareDailyNoteWidgetView.swift b/Features/Daily Note Widget/Square Widget/SmallSquareDailyNoteWidgetView.swift index 817b093d..d8f3f26c 100644 --- a/Features/Daily Note Widget/Square Widget/SmallSquareDailyNoteWidgetView.swift +++ b/Features/Daily Note Widget/Square Widget/SmallSquareDailyNoteWidgetView.swift @@ -40,9 +40,9 @@ struct SmallSquareDailyNoteWidgetView: View { WidgetErrorView(error: error) } } + .asWidgetRefreshButton() } - .padding(mainViewPadding) - .background { + .myWidgetContainerBackground(withPadding: mainViewPadding) { Group { if let image = entry.configuration.backgroundImage() { image.resizable().scaledToFill() diff --git a/HSRPizzaHelper.xcodeproj/project.pbxproj b/HSRPizzaHelper.xcodeproj/project.pbxproj index d4c747a3..a5645ddb 100644 --- a/HSRPizzaHelper.xcodeproj/project.pbxproj +++ b/HSRPizzaHelper.xcodeproj/project.pbxproj @@ -121,6 +121,13 @@ EA60BE5E2A05053200CBF10B /* ThanksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA60BE5C2A05053200CBF10B /* ThanksView.swift */; }; EA60BE622A050C5400CBF10B /* ScenePhaseOnChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA60BE612A050C5400CBF10B /* ScenePhaseOnChange.swift */; }; EA664C742A092E3A00DCEAC1 /* WidgetAccountCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAD3C0962A081E43007B51AE /* WidgetAccountCard.swift */; }; + EA68B2A62AB9575E00D39095 /* GeneralWidgetRefreshIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA68B2A42AB9575B00D39095 /* GeneralWidgetRefreshIntent.swift */; }; + EA68B2A72AB9575F00D39095 /* GeneralWidgetRefreshIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA68B2A42AB9575B00D39095 /* GeneralWidgetRefreshIntent.swift */; }; + EA68B2A92AB95A2300D39095 /* ContainerBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA68B2A82AB95A2300D39095 /* ContainerBackground.swift */; }; + EA68B2AA2AB95A2300D39095 /* ContainerBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA68B2A82AB95A2300D39095 /* ContainerBackground.swift */; }; + EA68B2AB2AB95A2300D39095 /* ContainerBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA68B2A82AB95A2300D39095 /* ContainerBackground.swift */; }; + EA68B2AE2AB95F2100D39095 /* AsWidgetRefreshButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA68B2AC2AB95F2100D39095 /* AsWidgetRefreshButton.swift */; }; + EA68B2AF2AB95F2100D39095 /* AsWidgetRefreshButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA68B2AC2AB95F2100D39095 /* AsWidgetRefreshButton.swift */; }; EA68BC6A2A06986D00A7FF91 /* HBMihoyoAPI in Frameworks */ = {isa = PBXBuildFile; productRef = EA68BC692A06986D00A7FF91 /* HBMihoyoAPI */; }; EA68BC6C2A06987300A7FF91 /* SwiftyUserDefaults in Frameworks */ = {isa = PBXBuildFile; productRef = EA68BC6B2A06987300A7FF91 /* SwiftyUserDefaults */; }; EA702C072A06AE0E006D3BC9 /* WidgetConfigurationIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA702C062A06AE0E006D3BC9 /* WidgetConfigurationIntentHandler.swift */; }; @@ -394,6 +401,9 @@ EA60BE5B2A05053200CBF10B /* AboutView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = ""; }; EA60BE5C2A05053200CBF10B /* ThanksView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThanksView.swift; sourceTree = ""; }; EA60BE612A050C5400CBF10B /* ScenePhaseOnChange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScenePhaseOnChange.swift; sourceTree = ""; }; + EA68B2A42AB9575B00D39095 /* GeneralWidgetRefreshIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralWidgetRefreshIntent.swift; sourceTree = ""; }; + EA68B2A82AB95A2300D39095 /* ContainerBackground.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainerBackground.swift; sourceTree = ""; }; + EA68B2AC2AB95F2100D39095 /* AsWidgetRefreshButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsWidgetRefreshButton.swift; sourceTree = ""; }; EA68BC842A069FA400A7FF91 /* HSRPizzaHelperWidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = HSRPizzaHelperWidgetExtension.entitlements; sourceTree = ""; }; EA702C062A06AE0E006D3BC9 /* WidgetConfigurationIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetConfigurationIntentHandler.swift; sourceTree = ""; }; EA742BF62A020A3300ACB8E4 /* CreateAccountSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateAccountSheetView.swift; sourceTree = ""; }; @@ -740,13 +750,23 @@ EADCE9352A0B7857008D81A9 /* EmbedIn.swift */, EA5F23312A0BE87000D6D024 /* OnFocused.swift */, EA80CFE72A0E983C00002B53 /* InlineNavigationTitle.swift */, + EA68B2A82AB95A2300D39095 /* ContainerBackground.swift */, ); path = "View Modifier"; sourceTree = ""; }; + EA68B2A32AB9574300D39095 /* App Intent */ = { + isa = PBXGroup; + children = ( + EA68B2A42AB9575B00D39095 /* GeneralWidgetRefreshIntent.swift */, + ); + path = "App Intent"; + sourceTree = ""; + }; EA68BC632A065B0200A7FF91 /* Daily Note Widget */ = { isa = PBXGroup; children = ( + EA68B2A32AB9574300D39095 /* App Intent */, EA946A922A0FF856003FC598 /* Lockscreen Widget */, EA56DB912A0F63F600BCF884 /* GI Style Square Widget */, EA43EDFC2A0F578C0035977E /* GI Style Rectangular Widget */, @@ -776,6 +796,7 @@ EAD3C0962A081E43007B51AE /* WidgetAccountCard.swift */, EA9E22132A0B9E1B002F3351 /* WidgetDailyNoteSuccessLargeView.swift */, EAFC5B4F2A35D29600598F2F /* WidgetErrorView.swift */, + EA68B2AC2AB95F2100D39095 /* AsWidgetRefreshButton.swift */, ); path = "Common View"; sourceTree = ""; @@ -1436,6 +1457,7 @@ EA946A982A0FF97C003FC598 /* StaminaLockscreenWidgetCircularView.swift in Sources */, EA8A772E2A074AD40005C511 /* HasDefaultBackground.swift in Sources */, EA9E221B2A0BA3A0002F3351 /* SmallSquareDailyNoteTimelineProvider.swift in Sources */, + EA68B2A62AB9575E00D39095 /* GeneralWidgetRefreshIntent.swift in Sources */, EAA6916B2A0E6783003D17FF /* Persistence.swift in Sources */, EA43EDF72A0F38920035977E /* HSRNotificationCenter.swift in Sources */, EA8A77212A0743B70005C511 /* RectangularDailyNoteTimelineProvider.swift in Sources */, @@ -1464,10 +1486,12 @@ EA068D102A080818003C4E69 /* RectangularDailyNoteWidget.swift in Sources */, EA8A77472A0769AF0005C511 /* ContainingWidgetBackground.swift in Sources */, EA8A77162A0737390005C511 /* DailyNoteBackgroundWidgetConfiguration.swift in Sources */, + EA68B2AE2AB95F2100D39095 /* AsWidgetRefreshButton.swift in Sources */, EA8A77142A07365D0005C511 /* DailyNoteEntry.swift in Sources */, EA9E22152A0B9E25002F3351 /* WidgetDailyNoteSuccessLargeView.swift in Sources */, EA477FF82A08899C0082D211 /* AccountExtension.swift in Sources */, EA8A77332A074C670005C511 /* WidgetBackgroundExtension.swift in Sources */, + EA68B2AA2AB95A2300D39095 /* ContainerBackground.swift in Sources */, EA1673F82A063AF300436487 /* HSRPizzaHelperWidgetLiveActivity.swift in Sources */, EA9D80A02A08D92C00534237 /* IntentHandler.swift in Sources */, EA56DB8A2A0F62A200BCF884 /* GIStyleRectangularTimelineProvider.swift in Sources */, @@ -1486,6 +1510,7 @@ EA742BFC2A0238C500ACB8E4 /* AppConfig.swift in Sources */, EA80CFEC2A0E9E9A00002B53 /* DailyNoteNotification.swift in Sources */, EADCE9362A0B7857008D81A9 /* EmbedIn.swift in Sources */, + EA68B2A92AB95A2300D39095 /* ContainerBackground.swift in Sources */, EAE519892A01738600EF23B7 /* Persistence.swift in Sources */, EA86304A2A84CDC5001E484B /* GachaMeta.swift in Sources */, EAF286502A1249A600F876B6 /* MiHoYoAPIError+LocalizedError.swift in Sources */, @@ -1561,6 +1586,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + EA68B2AF2AB95F2100D39095 /* AsWidgetRefreshButton.swift in Sources */, EA56DB7E2A0F5C1900BCF884 /* GIStyleEntry.swift in Sources */, EA702C072A06AE0E006D3BC9 /* WidgetConfigurationIntentHandler.swift in Sources */, EA8A772F2A074AD40005C511 /* HasDefaultBackground.swift in Sources */, @@ -1576,11 +1602,13 @@ EAA6916C2A0E6783003D17FF /* Persistence.swift in Sources */, EA43EDFA2A0F3E080035977E /* DailyNoteNotification.swift in Sources */, EA8A77372A074D290005C511 /* RandomBackgroundDrawable.swift in Sources */, + EA68B2AB2AB95A2300D39095 /* ContainerBackground.swift in Sources */, EA8A773A2A0751980005C511 /* WidgetConfigurationIntentExtension.swift in Sources */, EA43EDF92A0F3E060035977E /* HSRNotificationCenter.swift in Sources */, EAE898712A06A134000AF830 /* IntentHandler.swift in Sources */, EA8A772C2A074AB70005C511 /* HasDefaultAccount.swift in Sources */, EA702C0B2A06B0BC006D3BC9 /* Account+CoreDataProperties.swift in Sources */, + EA68B2A72AB9575F00D39095 /* GeneralWidgetRefreshIntent.swift in Sources */, EA8A77172A0737450005C511 /* DailyNoteBackgroundWidgetConfiguration.swift in Sources */, EA702C0F2A06B13B006D3BC9 /* AppConfig.swift in Sources */, BFBF6DDA2A1B35FA009C684B /* HSRPizzaHelperWidget.intentdefinition in Sources */, diff --git a/HSRPizzaHelper.xcodeproj/xcshareddata/xcschemes/Widget Debug.xcscheme b/HSRPizzaHelper.xcodeproj/xcshareddata/xcschemes/Widget Debug.xcscheme index c235e159..3dc274f4 100644 --- a/HSRPizzaHelper.xcodeproj/xcshareddata/xcschemes/Widget Debug.xcscheme +++ b/HSRPizzaHelper.xcodeproj/xcshareddata/xcschemes/Widget Debug.xcscheme @@ -2,7 +2,7 @@ + version = "2.0"> @@ -49,11 +49,24 @@ selectedDebuggerIdentifier = "" selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn" launchStyle = "0" + askForAppToLaunch = "Yes" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" debugServiceExtension = "internal" - allowLocationSimulation = "YES"> + allowLocationSimulation = "YES" + launchAutomaticallySubstyle = "2"> + + + + diff --git a/HSRPizzaHelper.xcodeproj/xcshareddata/xcschemes/Widget Release.xcscheme b/HSRPizzaHelper.xcodeproj/xcshareddata/xcschemes/Widget Release.xcscheme index 78831c4c..6910ed37 100644 --- a/HSRPizzaHelper.xcodeproj/xcshareddata/xcschemes/Widget Release.xcscheme +++ b/HSRPizzaHelper.xcodeproj/xcshareddata/xcschemes/Widget Release.xcscheme @@ -2,7 +2,7 @@ + version = "2.0"> @@ -49,11 +49,24 @@ selectedDebuggerIdentifier = "" selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn" launchStyle = "0" + askForAppToLaunch = "Yes" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" debugServiceExtension = "internal" - allowLocationSimulation = "YES"> + allowLocationSimulation = "YES" + launchAutomaticallySubstyle = "2"> + + + + + value = "timeline" + isEnabled = "YES"> @@ -87,6 +100,7 @@ savedToolIdentifier = "" useCustomWorkingDirectory = "NO" debugDocumentVersioning = "YES" + askForAppToLaunch = "Yes" launchAutomaticallySubstyle = "2">